diff --git a/.trae/rules/rule.md b/.trae/rules/rule.md new file mode 100644 index 0000000..80d5c94 --- /dev/null +++ b/.trae/rules/rule.md @@ -0,0 +1,12 @@ +--- +alwaysApply: false +description: 任务完成前的检查 +--- + +1.每次完成任务之前必须跑一遍测试(比如说npm系的typecheck/lint) + +2.每次完成任务之前必须跑一遍格式化(比如说npm系的prettier) + +3.任务完成时详细输出你所做的事情和需要用户确认的问题(如有) + +4.每次规划任务必须按照步骤来,每个步骤结束后遵循第三条规定。 diff --git a/src/renderer/src/components/AutoScoreManager.tsx b/src/renderer/src/components/AutoScoreManager.tsx index ead5ad6..5d8af2a 100644 --- a/src/renderer/src/components/AutoScoreManager.tsx +++ b/src/renderer/src/components/AutoScoreManager.tsx @@ -1,12 +1,11 @@ import { useState, useEffect } from 'react' -import { HolderOutlined, DeleteOutlined } from '@ant-design/icons' +import { HolderOutlined } from '@ant-design/icons' import { useTranslation } from 'react-i18next' import RuleComponent from './autoScore/ruleComponent' import { Card, Form, Input, - InputNumber, Button, message, Table, @@ -297,50 +296,6 @@ export const AutoScoreManager: React.FC = () => { setActionList([]) } - const handleAddTrigger = () => { - const nextId = triggerList.length ? Math.max(...triggerList.map((t) => t.id)) + 1 : 1 - const defaultTrigger = TRIGGER_DEFINITIONS[0] - if (!defaultTrigger) { - messageApi.error(t('autoScore.noTriggerAvailable')) - return - } - setTriggerList((prev) => [ - ...prev, - { - id: nextId, - eventName: defaultTrigger.eventName, - value: '', - relation: 'AND' - } - ]) - } - - const handleAddAction = () => { - const nextId = actionList.length ? Math.max(...actionList.map((a) => a.id)) + 1 : 1 - const defaultAction = ACTION_DEFINITIONS[0] - if (!defaultAction) { - messageApi.error(t('autoScore.noActionAvailable')) - return - } - setActionList((prev) => [ - ...prev, - { - id: nextId, - eventName: defaultAction.eventName, - value: '', - reason: '' - } - ]) - } - - const handleRemoveAction = (id: number) => { - setActionList((prev) => prev.filter((a) => a.id !== id)) - } - - const handleActionChange = (id: number, field: keyof ActionItem, value: string | number) => { - setActionList((prev) => prev.map((a) => (a.id === id ? { ...a, [field]: value } : a))) - } - const columns: ColumnsType = [ { key: 'drag', @@ -443,61 +398,6 @@ export const AutoScoreManager: React.FC = () => { } ] - const renderActionItem = (action: ActionItem) => ( -
- - handleActionChange(action.id, 'value', e.target.value)} - placeholder={t('autoScore.tagNamePlaceholder')} - style={{ width: 200 }} - /> - )} - handleActionChange(action.id, 'reason', e.target.value)} - placeholder={t('autoScore.reasonPlaceholder')} - style={{ width: 200 }} - /> -
- ) - return (
{contextHolder} @@ -527,8 +427,8 @@ export const AutoScoreManager: React.FC = () => { - -{/* @@ -547,7 +447,7 @@ export const AutoScoreManager: React.FC = () => { */} -{/* diff --git a/src/renderer/src/components/OOBE/OOBE.tsx b/src/renderer/src/components/OOBE/OOBE.tsx index 4584cf3..e136658 100644 --- a/src/renderer/src/components/OOBE/OOBE.tsx +++ b/src/renderer/src/components/OOBE/OOBE.tsx @@ -1,6 +1,6 @@ import React, { useState, useRef, useCallback, useEffect } from 'react' import { useTranslation } from 'react-i18next' -import { Button, Segmented, Input, Tag, message, Typography, InputNumber } from 'antd' +import { Button, Segmented, Input, Tag, message, Typography, InputNumber, Space } from 'antd' import { PlusOutlined, UploadOutlined, FileExcelOutlined } from '@ant-design/icons' import { OOBEBackground } from './OOBEBackground' import { useTheme } from '../../contexts/ThemeContext' @@ -13,7 +13,7 @@ interface oobeProps { onComplete: () => void } -type oobeStep = 'language' | 'theme' | 'students' | 'reasons' | 'start' +type oobeStep = 'language' | 'theme' | 'password' | 'students' | 'reasons' | 'start' interface studentItem { name: string @@ -86,10 +86,12 @@ export const OOBE: React.FC = ({ visible, onComplete }) => { const [reasons, setReasons] = useState([]) const [newReasonContent, setNewReasonContent] = useState('') const [newReasonDelta, setNewReasonDelta] = useState(1) + const [adminPassword, setAdminPassword] = useState('') + const [pointsPassword, setPointsPassword] = useState('') const fileInputRef = useRef(null) - const steps: oobeStep[] = ['language', 'theme', 'students', 'reasons', 'start'] + const steps: oobeStep[] = ['language', 'theme', 'password', 'students', 'reasons', 'start'] const stepIndex = steps.indexOf(currentStep) + 1 const totalSteps = steps.length @@ -315,6 +317,13 @@ export const OOBE: React.FC = ({ visible, onComplete }) => { }) } + if (adminPassword || pointsPassword) { + await (window as any).api.authSetPasswords({ + adminPassword: adminPassword || null, + pointsPassword: pointsPassword || null + }) + } + const res = await (window as any).api.setSetting('is_wizard_completed', true) if (!res?.success) throw new Error(res?.message || 'failed') @@ -436,6 +445,52 @@ export const OOBE: React.FC = ({ visible, onComplete }) => {
) + case 'password': + return ( +
+ + {t('oobe.steps.password.description')} + + +
+ {t('oobe.steps.password.adminPassword')} + + {t('oobe.steps.password.adminPasswordHint')} + + setAdminPassword(e.target.value)} + placeholder={t('oobe.steps.password.passwordPlaceholder')} + maxLength={6} + style={{ marginTop: 8 }} + /> +
+
+ {t('oobe.steps.password.pointsPassword')} + + {t('oobe.steps.password.pointsPasswordHint')} + + setPointsPassword(e.target.value)} + placeholder={t('oobe.steps.password.passwordPlaceholder')} + maxLength={6} + style={{ marginTop: 8 }} + /> +
+ + {t('oobe.steps.password.hint')} + +
+
+ ) + case 'students': return (
diff --git a/src/renderer/src/components/autoScore/actionComponent.tsx b/src/renderer/src/components/autoScore/actionComponent.tsx index f014aca..830c9be 100644 --- a/src/renderer/src/components/autoScore/actionComponent.tsx +++ b/src/renderer/src/components/autoScore/actionComponent.tsx @@ -1,8 +1,20 @@ import { Space, Input, InputNumber, Select, Button } from 'antd' import { PlusOutlined, DeleteOutlined } from '@ant-design/icons' import { useTranslation } from 'react-i18next' -import type { ActionItem } from './types' -import { ACTION_DEFINITIONS, SCORE_RANGE } from './constants' + +const SCORE_RANGE = { MIN: -999, MAX: 999 } + +const ACTION_DEFINITIONS = [ + { eventName: 'add_score', labelKey: 'autoScore.actionAddScore' }, + { eventName: 'add_tag', labelKey: 'autoScore.actionAddTag' } +] + +interface ActionItem { + id: number + eventName: string + value?: string + reason?: string +} interface ActionComponentProps { actions: ActionItem[] diff --git a/src/renderer/src/components/autoScore/ruleBuilderOverride.css b/src/renderer/src/components/autoScore/ruleBuilderOverride.css index 3a41518..5dde63b 100644 --- a/src/renderer/src/components/autoScore/ruleBuilderOverride.css +++ b/src/renderer/src/components/autoScore/ruleBuilderOverride.css @@ -1,5 +1,7 @@ .queryBuilder { - --querybuilder-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif; + --querybuilder-font-family: + -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', + sans-serif; --querybuilder-background: var(--ss-card-bg, #ffffff); --querybuilder-color: var(--ss-text-main, rgba(0, 0, 0, 0.85)); --querybuilder-border-radius: 6px; diff --git a/src/renderer/src/components/autoScore/ruleBuilderUtils.ts b/src/renderer/src/components/autoScore/ruleBuilderUtils.ts index 35e186d..4e75962 100644 --- a/src/renderer/src/components/autoScore/ruleBuilderUtils.ts +++ b/src/renderer/src/components/autoScore/ruleBuilderUtils.ts @@ -1,5 +1,5 @@ import type { RuleGroupType, Field, RuleType } from 'react-querybuilder' -import {defaultOperators} from 'react-querybuilder' +import { defaultOperators } from 'react-querybuilder' import { fetchAllTags } from '../TagEditorDialog' const tags = await fetchAllTags() @@ -21,7 +21,7 @@ export interface AutoScoreRuleData { actions: AutoScoreAction[] } -export const validator = (r: RuleType) => !!r.value; +export const validator = (r: RuleType) => !!r.value export const getFields = (t: (key: string) => string): Field[] => [ { @@ -31,18 +31,30 @@ export const getFields = (t: (key: string) => string): Field[] => [ valueEditorType: 'multiselect', values: tags.map((tag) => tag.name), defaultValue: tags.length > 0 ? [tags[0].name] : [], - operators: defaultOperators.filter((op) => op.name === 'in'), + operators: defaultOperators.filter((op) => op.name === 'in') }, { name: 'interval_time_passed', label: t('triggers.intervalTime.label'), matchModes: ['all'], subproperties: [ - { name: 'month', label: t('triggers.intervalTime.monthLabel'), inputType: 'number', datatype: 'month', operators: ['='] }, -/* { name: 'week', label: t('triggers.intervalTime.weekLabel'), inputType: 'week', datatype: 'week', operators: ['='] }, - */ { name: 'time', label: t('triggers.intervalTime.timeLabel'), inputType: 'time', datatype: 'time', operators: ['='] }, - ], - }, + { + name: 'month', + label: t('triggers.intervalTime.monthLabel'), + inputType: 'number', + datatype: 'month', + operators: ['='] + }, + /* { name: 'week', label: t('triggers.intervalTime.weekLabel'), inputType: 'week', datatype: 'week', operators: ['='] }, + */ { + name: 'time', + label: t('triggers.intervalTime.timeLabel'), + inputType: 'time', + datatype: 'time', + operators: ['='] + } + ] + } ] export const defaultQuery: RuleGroupType = { @@ -50,10 +62,10 @@ export const defaultQuery: RuleGroupType = { rules: [{ field: 'interval_time_passed', operator: '=', value: '1440' }] } -export function queryToAutoScoreRule(query: RuleGroupType): AutoScoreRuleData { +export function queryToAutoScoreRule(_query: RuleGroupType): AutoScoreRuleData { const triggers: AutoScoreTrigger[] = [] -/* const processRuleGroup = (group: RuleGroupType, relation: 'AND' | 'OR' = 'AND') => { + /* const processRuleGroup = (group: RuleGroupType, relation: 'AND' | 'OR' = 'AND') => { group.rules.forEach((rule, index) => { if ('rules' in rule) { processRuleGroup(rule, group.combinator === 'and' ? 'AND' : 'OR') diff --git a/src/renderer/src/i18n/locales/en-US.json b/src/renderer/src/i18n/locales/en-US.json index 186c27d..7ec8820 100644 --- a/src/renderer/src/i18n/locales/en-US.json +++ b/src/renderer/src/i18n/locales/en-US.json @@ -50,6 +50,16 @@ "title": "Set Theme", "description": "Choose your preferred interface appearance", "mode": "Mode", + "password": { + "title": "Set Password", + "description": "Set login password to protect your data (optional)", + "adminPassword": "Admin Password", + "adminPasswordHint": "Full access to all features, 6 digits", + "pointsPassword": "Points Password", + "pointsPasswordHint": "Points operations only, 6 digits", + "passwordPlaceholder": "Enter 6 digits", + "hint": "Leave empty to skip, you can configure in settings later" + }, "lightMode": "Light", "darkMode": "Dark", "primaryColor": "Primary Color", @@ -111,6 +121,7 @@ "language": "Language", "languageHint": "Select interface display language", "theme": "Theme", + "password": "Set Password", "themeMode": "Mode", "lightMode": "Light", "darkMode": "Dark", diff --git a/src/renderer/src/i18n/locales/zh-CN.json b/src/renderer/src/i18n/locales/zh-CN.json index b5a298b..f46ce6d 100644 --- a/src/renderer/src/i18n/locales/zh-CN.json +++ b/src/renderer/src/i18n/locales/zh-CN.json @@ -50,6 +50,16 @@ "title": "设置主题", "description": "选择您喜欢的界面外观", "mode": "模式", + "password": { + "title": "设置密码", + "description": "设置登录密码以保护您的数据(可选)", + "adminPassword": "管理密码", + "adminPasswordHint": "可管理所有功能,6位数字", + "pointsPassword": "积分密码", + "pointsPasswordHint": "仅可操作积分,6位数字", + "passwordPlaceholder": "请输入6位数字", + "hint": "留空则不设置密码,之后可在设置中配置" + }, "lightMode": "浅色", "darkMode": "深色", "primaryColor": "主色", @@ -111,6 +121,7 @@ "language": "语言", "languageHint": "选择界面显示语言", "theme": "主题", + "password": "设置密码", "themeMode": "模式", "lightMode": "浅色", "darkMode": "深色",