This commit is contained in:
Fox_block
2026-03-15 10:22:36 +08:00
7 changed files with 223 additions and 691 deletions
+1 -2
View File
@@ -16,8 +16,7 @@ export {
ThemeServiceToken, ThemeServiceToken,
WindowManagerToken, WindowManagerToken,
TrayServiceToken, TrayServiceToken,
AutoScoreServiceToken, AutoScoreServiceToken
FileSystemServiceToken
} from './tokens' } from './tokens'
export type { export type {
appRuntimeContext, appRuntimeContext,
-1
View File
@@ -15,4 +15,3 @@ export const ThemeServiceToken = Symbol.for('secscore.themeService')
export const WindowManagerToken = Symbol.for('secscore.windowManager') export const WindowManagerToken = Symbol.for('secscore.windowManager')
export const TrayServiceToken = Symbol.for('secscore.trayService') export const TrayServiceToken = Symbol.for('secscore.trayService')
export const AutoScoreServiceToken = Symbol.for('secscore.autoScoreService') export const AutoScoreServiceToken = Symbol.for('secscore.autoScoreService')
export const FileSystemServiceToken = Symbol.for('secscore.fileSystemService')
+1 -8
View File
@@ -16,7 +16,6 @@ import { ThemeService } from './services/ThemeService'
import { WindowManager, type windowManagerOptions } from './services/WindowManager' import { WindowManager, type windowManagerOptions } from './services/WindowManager'
import { TrayService } from './services/TrayService' import { TrayService } from './services/TrayService'
import { AutoScoreService } from './services/AutoScoreService' import { AutoScoreService } from './services/AutoScoreService'
import { FileSystemService } from './services/FileSystemService'
import { DbConnectionService } from './services/DbConnectionService' import { DbConnectionService } from './services/DbConnectionService'
import { StudentRepository } from './repos/StudentRepository' import { StudentRepository } from './repos/StudentRepository'
import { ReasonRepository } from './repos/ReasonRepository' import { ReasonRepository } from './repos/ReasonRepository'
@@ -39,8 +38,7 @@ import {
ThemeServiceToken, ThemeServiceToken,
WindowManagerToken, WindowManagerToken,
TrayServiceToken, TrayServiceToken,
AutoScoreServiceToken, AutoScoreServiceToken
FileSystemServiceToken
} from './hosting' } from './hosting'
type mainAppConfig = { type mainAppConfig = {
@@ -267,10 +265,6 @@ app.whenReady().then(async () => {
TrayServiceToken, TrayServiceToken,
(p) => new TrayService(p.get(MainContext), config.window) (p) => new TrayService(p.get(MainContext), config.window)
) )
services.addSingleton(
FileSystemServiceToken,
(p) => new FileSystemService(p.get(MainContext), config.configDir)
)
services.addSingleton(AutoScoreServiceToken, (p) => new AutoScoreService(p.get(MainContext))) services.addSingleton(AutoScoreServiceToken, (p) => new AutoScoreService(p.get(MainContext)))
services.addSingleton(DbConnectionService, (p) => new DbConnectionService(p.get(MainContext))) services.addSingleton(DbConnectionService, (p) => new DbConnectionService(p.get(MainContext)))
}) })
@@ -320,7 +314,6 @@ app.whenReady().then(async () => {
const tray = services.get(TrayServiceToken) as TrayService const tray = services.get(TrayServiceToken) as TrayService
tray.initialize() tray.initialize()
} }
services.get(FileSystemServiceToken)
const autoScore = services.get(AutoScoreServiceToken) as AutoScoreService const autoScore = services.get(AutoScoreServiceToken) as AutoScoreService
await autoScore.initialize?.() await autoScore.initialize?.()
services.get(DbConnectionService) services.get(DbConnectionService)
-119
View File
@@ -11,30 +11,12 @@ interface AutoScoreRule extends RuleConfig {
lastExecuted?: Date lastExecuted?: Date
} }
interface AutoScoreRuleFileData {
id: number
enabled: boolean
name: string
studentNames: string[]
lastExecuted?: string
triggers?: Array<{ event: string; value?: string; relation?: 'AND' | 'OR' }>
actions?: Array<{ event: string; value?: string; reason?: string }>
}
interface AutoScoreRulesFile {
version: number
rules: AutoScoreRuleFileData[]
updatedAt?: string
}
declare module '../../shared/kernel' { declare module '../../shared/kernel' {
interface Context { interface Context {
autoScore: AutoScoreService autoScore: AutoScoreService
} }
} }
const RULES_FILE_NAME = 'auto-score-rules.json'
export class AutoScoreService extends Service { export class AutoScoreService extends Service {
private rules: AutoScoreRule[] = [] private rules: AutoScoreRule[] = []
private timers: Map<number, NodeJS.Timeout> = new Map() private timers: Map<number, NodeJS.Timeout> = new Map()
@@ -130,83 +112,7 @@ export class AutoScoreService extends Service {
} }
private async loadRulesFromFile(): Promise<void> { private async loadRulesFromFile(): Promise<void> {
try {
const fs = this.mainCtx.fileSystem
if (!fs) {
this.logger.warn('FileSystemService not available, falling back to settings')
await this.loadRulesFromSettings() await this.loadRulesFromSettings()
return
}
const data = await fs.readJsonFile<AutoScoreRulesFile>(RULES_FILE_NAME, 'automatic')
if (data && data.rules) {
this.rules = data.rules.map((rule: any) => {
const migratedRule = this.migrateRule(rule)
return {
...migratedRule,
lastExecuted: migratedRule.lastExecuted
? new Date(migratedRule.lastExecuted)
: undefined
}
})
if (
data.rules.some(
(rule: any) => rule.intervalMinutes !== undefined || rule.scoreValue !== undefined
)
) {
await this.saveRulesToFile()
}
} else {
await this.loadRulesFromSettings()
await this.saveRulesToFile()
}
} catch (error) {
this.logger.warn('Failed to load auto score rules from file, falling back to settings', {
error
})
await this.loadRulesFromSettings()
}
}
private migrateRule(rule: any): AutoScoreRule {
if (!rule.intervalMinutes && !rule.scoreValue) {
return rule
}
const migratedRule: AutoScoreRule = {
id: rule.id,
enabled: rule.enabled,
name: rule.name,
studentNames: rule.studentNames || [],
lastExecuted: rule.lastExecuted,
triggers: rule.triggers || [],
actions: rule.actions || []
}
if (
rule.intervalMinutes &&
!migratedRule.triggers?.find((t) => t.event === 'interval_time_passed')
) {
migratedRule.triggers = migratedRule.triggers || []
migratedRule.triggers.push({
event: 'interval_time_passed',
value: String(rule.intervalMinutes)
})
}
if (
rule.scoreValue !== undefined &&
!migratedRule.actions?.find((a) => a.event === 'add_score')
) {
migratedRule.actions = migratedRule.actions || []
migratedRule.actions.push({
event: 'add_score',
value: String(rule.scoreValue),
reason: rule.reason
})
}
return migratedRule
} }
private async loadRulesFromSettings() { private async loadRulesFromSettings() {
@@ -226,32 +132,7 @@ export class AutoScoreService extends Service {
} }
private async saveRulesToFile(): Promise<void> { private async saveRulesToFile(): Promise<void> {
try {
const fs = this.mainCtx.fileSystem
if (!fs) {
this.logger.warn('FileSystemService not available, falling back to settings')
await this.saveRulesToSettings() await this.saveRulesToSettings()
return
}
const data: AutoScoreRulesFile = {
version: 1,
rules: this.rules.map(({ lastExecuted, ...rule }) => ({
...rule,
lastExecuted: lastExecuted?.toISOString()
})),
updatedAt: new Date().toISOString()
}
const success = await fs.writeJsonFile(RULES_FILE_NAME, data, 'automatic')
if (!success) {
this.logger.warn('Failed to save rules to file, falling back to settings')
await this.saveRulesToSettings()
}
} catch (error) {
this.logger.error('Failed to save auto score rules to file:', { error })
await this.saveRulesToSettings()
}
} }
private async saveRulesToSettings() { private async saveRulesToSettings() {
-281
View File
@@ -1,281 +0,0 @@
import { Service } from '../../shared/kernel'
import { MainContext } from '../context'
import { join } from 'path'
import fs from 'fs/promises'
declare module '../../shared/kernel' {
interface Context {
fileSystem: FileSystemService
}
}
export interface ConfigFileInfo {
name: string
path: string
size: number
modified: Date
}
export interface ConfigFolderStructure {
configRoot: string
automatic: string
script: string
}
export class FileSystemService extends Service {
private configRoot: string
private automaticDir: string
private scriptDir: string
private initPromise: Promise<void> | null = null
constructor(ctx: MainContext, ConfigRoot: string) {
super(ctx, 'fileSystem')
this.configRoot = ConfigRoot
this.automaticDir = join(this.configRoot, 'automatic')
this.scriptDir = join(this.configRoot, 'script')
this.initPromise = this.initialize()
this.registerIpc()
}
private async initialize(): Promise<void> {
await this.ensureDirectories()
}
private async ensureDirectories(): Promise<void> {
try {
await fs.mkdir(this.configRoot, { recursive: true })
await fs.mkdir(this.automaticDir, { recursive: true })
await fs.mkdir(this.scriptDir, { recursive: true })
} catch (error) {
this.ctx.logger.warn('FileSystemService: Failed to create config directories', { error })
}
}
getConfigStructure(): ConfigFolderStructure {
return {
configRoot: this.configRoot,
automatic: this.automaticDir,
script: this.scriptDir
}
}
async readJsonFile<T = any>(
relativePath: string,
folder: 'automatic' | 'script' = 'automatic'
): Promise<T | null> {
await this.initPromise
const baseDir = folder === 'automatic' ? this.automaticDir : this.scriptDir
const filePath = join(baseDir, relativePath)
try {
const content = await fs.readFile(filePath, 'utf-8')
return JSON.parse(content) as T
} catch (error) {
if (error instanceof Error && 'code' in error && (error as any).code === 'ENOENT') {
return null
}
this.ctx.logger.warn('FileSystemService: Failed to read JSON file', { path: filePath, error })
return null
}
}
async writeJsonFile(
relativePath: string,
data: any,
folder: 'automatic' | 'script' = 'automatic'
): Promise<boolean> {
await this.initPromise
const baseDir = folder === 'automatic' ? this.automaticDir : this.scriptDir
const filePath = join(baseDir, relativePath)
try {
const content = JSON.stringify(data, null, 2)
await fs.writeFile(filePath, content, 'utf-8')
return true
} catch (error) {
this.ctx.logger.warn('FileSystemService: Failed to write JSON file', {
path: filePath,
error
})
return false
}
}
async readTextFile(
relativePath: string,
folder: 'automatic' | 'script' = 'automatic'
): Promise<string | null> {
await this.initPromise
const baseDir = folder === 'automatic' ? this.automaticDir : this.scriptDir
const filePath = join(baseDir, relativePath)
try {
return await fs.readFile(filePath, 'utf-8')
} catch (error) {
if (error instanceof Error && 'code' in error && (error as any).code === 'ENOENT') {
return null
}
this.ctx.logger.warn('FileSystemService: Failed to read text file', { path: filePath, error })
return null
}
}
async writeTextFile(
content: string,
relativePath: string,
folder: 'automatic' | 'script' = 'automatic'
): Promise<boolean> {
await this.initPromise
const baseDir = folder === 'automatic' ? this.automaticDir : this.scriptDir
const filePath = join(baseDir, relativePath)
try {
await fs.writeFile(filePath, content, 'utf-8')
return true
} catch (error) {
this.ctx.logger.warn('FileSystemService: Failed to write text file', {
path: filePath,
error
})
return false
}
}
async deleteFile(
relativePath: string,
folder: 'automatic' | 'script' = 'automatic'
): Promise<boolean> {
await this.initPromise
const baseDir = folder === 'automatic' ? this.automaticDir : this.scriptDir
const filePath = join(baseDir, relativePath)
try {
await fs.unlink(filePath)
return true
} catch (error) {
if (error instanceof Error && 'code' in error && (error as any).code === 'ENOENT') {
return false
}
this.ctx.logger.warn('FileSystemService: Failed to delete file', { path: filePath, error })
return false
}
}
async listFiles(folder: 'automatic' | 'script' = 'automatic'): Promise<ConfigFileInfo[]> {
await this.initPromise
const baseDir = folder === 'automatic' ? this.automaticDir : this.scriptDir
try {
const entries = await fs.readdir(baseDir, { withFileTypes: true })
const files: ConfigFileInfo[] = []
for (const entry of entries) {
if (entry.isFile()) {
const filePath = join(baseDir, entry.name)
const stats = await fs.stat(filePath)
files.push({
name: entry.name,
path: filePath,
size: stats.size,
modified: stats.mtime
})
}
}
return files
} catch (error) {
if (error instanceof Error && 'code' in error && (error as any).code === 'ENOENT') {
return []
}
this.ctx.logger.warn('FileSystemService: Failed to list files', { path: baseDir, error })
return []
}
}
async fileExists(
relativePath: string,
folder: 'automatic' | 'script' = 'automatic'
): Promise<boolean> {
await this.initPromise
const baseDir = folder === 'automatic' ? this.automaticDir : this.scriptDir
const filePath = join(baseDir, relativePath)
try {
await fs.access(filePath)
return true
} catch {
return false
}
}
private get mainCtx() {
return this.ctx as MainContext
}
private registerIpc(): void {
this.mainCtx.handle('fs:getConfigStructure', async () => {
await this.initPromise
return { success: true, data: this.getConfigStructure() }
})
this.mainCtx.handle(
'fs:readJson',
async (_event, relativePath: string, folder: 'automatic' | 'script') => {
await this.initPromise
const data = await this.readJsonFile(relativePath, folder)
return { success: true, data }
}
)
this.mainCtx.handle(
'fs:writeJson',
async (_event, relativePath: string, data: any, folder: 'automatic' | 'script') => {
await this.initPromise
const success = await this.writeJsonFile(relativePath, data, folder)
return { success }
}
)
this.mainCtx.handle(
'fs:readText',
async (_event, relativePath: string, folder: 'automatic' | 'script') => {
await this.initPromise
const content = await this.readTextFile(relativePath, folder)
return { success: true, data: content }
}
)
this.mainCtx.handle(
'fs:writeText',
async (_event, content: string, relativePath: string, folder: 'automatic' | 'script') => {
await this.initPromise
const success = await this.writeTextFile(content, relativePath, folder)
return { success }
}
)
this.mainCtx.handle(
'fs:deleteFile',
async (_event, relativePath: string, folder: 'automatic' | 'script') => {
await this.initPromise
const success = await this.deleteFile(relativePath, folder)
return { success }
}
)
this.mainCtx.handle('fs:listFiles', async (_event, folder: 'automatic' | 'script') => {
await this.initPromise
const files = await this.listFiles(folder)
return { success: true, data: files }
})
this.mainCtx.handle(
'fs:fileExists',
async (_event, relativePath: string, folder: 'automatic' | 'script') => {
await this.initPromise
const exists = await this.fileExists(relativePath, folder)
return { success: true, data: exists }
}
)
}
}
@@ -1,309 +1,251 @@
.queryBuilder { .ruleGroup {
--querybuilder-font-family: background-color: var(--ss-card-bg, #fff);
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', border: 1px solid var(--ss-border-color, #d9d9d9);
sans-serif; border-radius: 8px;
--querybuilder-background: var(--ss-card-bg, #ffffff); padding: 16px;
--querybuilder-color: var(--ss-text-main, rgba(0, 0, 0, 0.85)); margin-bottom: 12px;
--querybuilder-border-radius: 6px;
--querybuilder-border-color: #d9d9d9;
--querybuilder-disabled-color: rgba(0, 0, 0, 0.25);
font-family: var(--querybuilder-font-family);
font-size: 14px;
line-height: 1.5715;
color: var(--querybuilder-color);
background: var(--querybuilder-background);
padding: 0;
border-radius: var(--querybuilder-border-radius);
} }
.queryBuilder .ruleGroup { .ruleGroup-header {
background: var(--querybuilder-background);
border: 1px solid var(--querybuilder-border-color);
border-radius: var(--querybuilder-border-radius);
padding: 8px;
margin: 0;
}
.queryBuilder .ruleGroup-header {
display: flex; display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
flex-wrap: wrap; flex-wrap: wrap;
}
.ruleGroup-combinators,
.ruleGroup-addRule,
.ruleGroup-addGroup {
margin-right: 8px;
}
.ruleGroup-body {
padding-left: 12px;
border-left: 2px solid var(--ss-primary-color, #1677ff);
margin-left: 4px;
}
.rule {
display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
padding: 8px 12px;
background-color: var(--ss-item-bg, rgba(0, 0, 0, 0.02));
border-radius: 6px;
margin-bottom: 8px; margin-bottom: 8px;
}
.queryBuilder .ruleGroup-body {
padding-left: 20px;
border-left: 2px solid var(--querybuilder-border-color);
margin-left: 0;
}
.queryBuilder .rule {
display: flex;
flex-wrap: wrap; flex-wrap: wrap;
align-items: center;
gap: 8px;
padding: 4px 8px;
margin: 0;
background: rgba(0, 0, 0, 0.02);
border-radius: 4px;
border: 1px solid transparent;
transition: all 0.3s;
} }
.queryBuilder .rule:hover { .rule:hover {
background: rgba(0, 0, 0, 0.04); background-color: var(--ss-item-hover, rgba(0, 0, 0, 0.04));
border-color: var(--querybuilder-border-color);
} }
.queryBuilder select, .rule-fields,
.queryBuilder .ruleGroup-combinators select { .rule-operators,
appearance: none; .rule-value,
background: var(--querybuilder-background); .rule-subfields {
border: 1px solid var(--querybuilder-border-color);
border-radius: var(--querybuilder-border-radius);
padding: 4px 28px 4px 11px;
height: 32px;
font-size: 14px;
color: var(--querybuilder-color);
cursor: pointer;
transition: all 0.3s;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='rgba(0,0,0,0.25)' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 8px center;
}
.queryBuilder select:hover,
.queryBuilder .ruleGroup-combinators select:hover {
border-color: #40a9ff;
}
.queryBuilder select:focus,
.queryBuilder .ruleGroup-combinators select:focus {
outline: none;
border-color: #40a9ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.queryBuilder input[type='text'],
.queryBuilder input[type='number'],
.queryBuilder input:not([type]) {
background: var(--querybuilder-background);
border: 1px solid var(--querybuilder-border-color);
border-radius: var(--querybuilder-border-radius);
padding: 4px 11px;
height: 32px;
font-size: 14px;
color: var(--querybuilder-color);
transition: all 0.3s;
min-width: 120px; min-width: 120px;
} }
.queryBuilder input[type='text']:hover, .rule-fields .ant-select-selector,
.queryBuilder input[type='number']:hover, .rule-operators .ant-select-selector,
.queryBuilder input:not([type]):hover { .rule-subfields .ant-select-selector {
border-color: #40a9ff; background-color: var(--ss-input-bg, #fff) !important;
border-color: var(--ss-border-color, #d9d9d9) !important;
color: var(--ss-text-main, rgba(0, 0, 0, 0.88)) !important;
} }
.queryBuilder input[type='text']:focus, .rule-fields .ant-select-selector:hover,
.queryBuilder input[type='number']:focus, .rule-operators .ant-select-selector:hover,
.queryBuilder input:not([type]):focus { .rule-subfields .ant-select-selector:hover {
outline: none; border-color: var(--ss-primary-color, #1677ff) !important;
border-color: #40a9ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
} }
.queryBuilder input::placeholder { .rule-fields .ant-select-focused .ant-select-selector,
color: #bfbfbf; .rule-operators .ant-select-focused .ant-select-selector,
.rule-subfields .ant-select-focused .ant-select-selector {
border-color: var(--ss-primary-color, #1677ff) !important;
box-shadow: 0 0 0 2px rgba(var(--ss-primary-rgb, 22, 119, 255), 0.1) !important;
} }
.queryBuilder button { .rule-value .ant-input,
display: inline-flex; .rule-value .ant-input-number,
.rule-value .ant-select-selector {
background-color: var(--ss-input-bg, #fff) !important;
border-color: var(--ss-border-color, #d9d9d9) !important;
color: var(--ss-text-main, rgba(0, 0, 0, 0.88)) !important;
}
.rule-value .ant-input:hover,
.rule-value .ant-input-number:hover,
.rule-value .ant-select-selector:hover {
border-color: var(--ss-primary-color, #1677ff) !important;
}
.rule-value .ant-input:focus,
.rule-value .ant-input-number-focused,
.rule-value .ant-select-focused .ant-select-selector {
border-color: var(--ss-primary-color, #1677ff) !important;
box-shadow: 0 0 0 2px rgba(var(--ss-primary-rgb, 22, 119, 255), 0.1) !important;
}
.rule-value .ant-input-number-input {
color: var(--ss-text-main, rgba(0, 0, 0, 0.88)) !important;
}
.rule-remove,
.ruleGroup-remove {
color: #fff !important;
background-color: var(--ant-color-error, #ff4d4f) !important;
border-color: var(--ant-color-error, #ff4d4f) !important;
transition: all 0.2s;
}
.rule-remove:hover,
.ruleGroup-remove:hover {
color: #fff !important;
background-color: #ff7875 !important;
border-color: #ff7875 !important;
}
.ruleGroup-addRule,
.ruleGroup-addGroup {
color: var(--ss-primary-color, #1677ff) !important;
border-color: var(--ss-primary-color, #1677ff) !important;
background-color: transparent !important;
}
.ruleGroup-addRule:hover,
.ruleGroup-addGroup:hover {
color: #fff !important;
background-color: var(--ss-primary-color, #1677ff) !important;
}
.betweenRules {
display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-weight: 400; margin: 8px 0;
white-space: nowrap; color: var(--ss-text-secondary, rgba(0, 0, 0, 0.45));
text-align: center;
background-image: none;
border: 1px solid transparent;
cursor: pointer;
transition: all 0.3s;
height: 32px;
padding: 4px 15px;
font-size: 14px;
border-radius: var(--querybuilder-border-radius);
color: var(--querybuilder-color);
background: var(--querybuilder-background);
border-color: var(--querybuilder-border-color);
}
.queryBuilder button:hover {
color: #40a9ff;
border-color: #40a9ff;
}
.queryBuilder button:focus {
outline: none;
color: #40a9ff;
border-color: #40a9ff;
}
.queryBuilder button.ruleGroup-addRule,
.queryBuilder button.ruleGroup-addGroup {
color: #fff;
background: var(--ant-color-primary, #1677ff);
border-color: var(--ant-color-primary, #1677ff);
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.12);
box-shadow: 0 2px 0 rgba(0, 0, 0, 0.045);
}
.queryBuilder button.ruleGroup-addRule:hover,
.queryBuilder button.ruleGroup-addGroup:hover {
color: #fff;
background: var(--ant-color-primary-hover, #4096ff);
border-color: var(--ant-color-primary-hover, #4096ff);
}
.queryBuilder button.ruleGroup-addRule:focus,
.queryBuilder button.ruleGroup-addGroup:focus {
color: #fff;
background: var(--ant-color-primary-hover, #4096ff);
border-color: var(--ant-color-primary-hover, #4096ff);
}
.queryBuilder button.rule-remove,
.queryBuilder button.ruleGroup-remove {
color: #ff4d4f;
border-color: #ff4d4f;
background: transparent;
}
.queryBuilder button.rule-remove:hover,
.queryBuilder button.ruleGroup-remove:hover {
color: #ff7875;
border-color: #ff7875;
}
.queryBuilder button.rule-remove:focus,
.queryBuilder button.ruleGroup-remove:focus {
color: #ff7875;
border-color: #ff7875;
}
.queryBuilder .ruleGroup-combinators {
display: inline-flex;
align-items: center;
gap: 8px;
}
.queryBuilder .betweenRules {
display: inline-flex;
align-items: center;
gap: 4px;
}
.queryBuilder .betweenRules .operator-joiner {
color: var(--querybuilder-color);
padding: 0 4px;
}
.queryBuilder .ruleGroup-notToggle {
display: inline-flex;
align-items: center;
gap: 4px;
margin-left: 8px;
color: var(--querybuilder-color);
}
.queryBuilder .ruleGroup-notToggle input[type='checkbox'] {
width: 16px;
height: 16px;
cursor: pointer;
accent-color: #1890ff;
}
.queryBuilder .dragHandle {
cursor: move;
color: var(--querybuilder-disabled-color);
padding: 4px;
}
.queryBuilder .dragHandle:hover {
color: var(--querybuilder-color);
}
.queryBuilder .shiftActions {
display: inline-flex;
gap: 4px;
margin-left: 8px;
}
.queryBuilder .shiftActions button {
padding: 4px 8px;
min-width: auto;
}
.queryBuilder .valueEditor-caseSensitive,
.queryBuilder .valueEditor-caseSensitive input[type='checkbox'] {
cursor: pointer;
}
.queryBuilder .valueEditor-caseSensitive input[type='checkbox'] {
width: 14px;
height: 14px;
accent-color: #1890ff;
}
.queryBuilder .valueSource-dropdown {
min-width: 100px;
}
.queryBuilder:disabled,
.queryBuilder .disabled {
opacity: 0.65;
cursor: not-allowed;
pointer-events: none;
}
.queryBuilder .validation-error {
color: #ff4d4f;
font-size: 12px; font-size: 12px;
margin-top: 4px;
} }
.queryBuilder .rule-with-error { .betweenRules::before,
border-color: #ff4d4f !important; .betweenRules::after {
content: '';
flex: 1;
height: 1px;
background-color: var(--ss-border-color, #d9d9d9);
} }
.queryBuilder .rule-with-error input, .betweenRules::before {
.queryBuilder .rule-with-error select { margin-right: 12px;
border-color: #ff4d4f !important;
} }
.queryBuilder .rule-with-error input:focus, .betweenRules::after {
.queryBuilder .rule-with-error select:focus { margin-left: 12px;
box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.2) !important;
} }
@media (prefers-color-scheme: dark) { .dragHandle {
.queryBuilder { cursor: grab;
--querybuilder-border-color: #434343; color: var(--ss-text-secondary, rgba(0, 0, 0, 0.45));
--querybuilder-disabled-color: rgba(255, 255, 255, 0.3); padding: 4px;
} margin-right: 4px;
}
.queryBuilder .rule {
background: rgba(255, 255, 255, 0.04); .dragHandle:hover {
} color: var(--ss-text-main, rgba(0, 0, 0, 0.88));
}
.queryBuilder .rule:hover {
background: rgba(255, 255, 255, 0.08); .queryBuilder .ant-select-dropdown {
} background-color: var(--ss-card-bg, #fff);
}
.queryBuilder select {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='rgba(255,255,255,0.3)' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); .queryBuilder .ant-select-item {
} color: var(--ss-text-main, rgba(0, 0, 0, 0.88));
}
.queryBuilder input::placeholder {
color: rgba(255, 255, 255, 0.3); .queryBuilder .ant-select-item-option-active {
} background-color: var(--ss-item-hover, rgba(0, 0, 0, 0.04));
}
.queryBuilder .ant-select-item-option-selected {
background-color: rgba(var(--ss-primary-rgb, 22, 119, 255), 0.1);
color: var(--ss-primary-color, #1677ff);
}
.queryBuilder .ant-btn {
border-radius: 6px;
}
.queryBuilder .ant-select-selector,
.queryBuilder .ant-input,
.queryBuilder .ant-input-number {
border-radius: 6px;
}
[theme-mode='dark'] .ruleGroup {
background-color: var(--ss-card-bg, #1f1f1f);
border-color: var(--ss-border-color, #424242);
}
[theme-mode='dark'] .rule {
background-color: var(--ss-item-bg, rgba(255, 255, 255, 0.04));
}
[theme-mode='dark'] .rule:hover {
background-color: var(--ss-item-hover, rgba(255, 255, 255, 0.08));
}
[theme-mode='dark'] .rule-fields .ant-select-selector,
[theme-mode='dark'] .rule-operators .ant-select-selector,
[theme-mode='dark'] .rule-subfields .ant-select-selector,
[theme-mode='dark'] .rule-value .ant-input,
[theme-mode='dark'] .rule-value .ant-input-number,
[theme-mode='dark'] .rule-value .ant-select-selector {
background-color: var(--ss-input-bg, #141414) !important;
border-color: var(--ss-border-color, #424242) !important;
color: var(--ss-text-main, rgba(255, 255, 255, 0.85)) !important;
}
[theme-mode='dark'] .rule-value .ant-input-number-input {
color: var(--ss-text-main, rgba(255, 255, 255, 0.85)) !important;
}
[theme-mode='dark'] .betweenRules {
color: var(--ss-text-secondary, rgba(255, 255, 255, 0.45));
}
[theme-mode='dark'] .betweenRules::before,
[theme-mode='dark'] .betweenRules::after {
background-color: var(--ss-border-color, #424242);
}
[theme-mode='dark'] .dragHandle {
color: var(--ss-text-secondary, rgba(255, 255, 255, 0.45));
}
[theme-mode='dark'] .dragHandle:hover {
color: var(--ss-text-main, rgba(255, 255, 255, 0.85));
}
[theme-mode='dark'] .queryBuilder .ant-select-dropdown {
background-color: var(--ss-card-bg, #1f1f1f);
}
[theme-mode='dark'] .queryBuilder .ant-select-item {
color: var(--ss-text-main, rgba(255, 255, 255, 0.85));
}
[theme-mode='dark'] .queryBuilder .ant-select-item-option-active {
background-color: var(--ss-item-hover, rgba(255, 255, 255, 0.08));
}
[theme-mode='dark'] .queryBuilder .ant-select-item-option-selected {
background-color: rgba(var(--ss-primary-rgb, 22, 119, 255), 0.2);
}
.ruleGroup-header .ant-select-selector {
min-width: 80px;
} }
+1 -2
View File
@@ -6,8 +6,7 @@
"src/renderer/src/**/*.tsx", "src/renderer/src/**/*.tsx",
"src/preload/*.d.ts", "src/preload/*.d.ts",
"src/preload/types.ts", "src/preload/types.ts",
"src/shared/**/*", "src/shared/**/*"
"src/main/services/HttpService.ts"
], ],
"compilerOptions": { "compilerOptions": {
"composite": true, "composite": true,