mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-19 19:04:23 +08:00
改
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
---
|
||||
alwaysApply: false
|
||||
description: 任务完成前的检查
|
||||
---
|
||||
|
||||
1.每次完成任务之前必须跑一遍测试(比如说npm系的typecheck/lint)
|
||||
|
||||
2.每次完成任务之前必须跑一遍格式化(比如说npm系的prettier)
|
||||
|
||||
3.任务完成时详细输出你所做的事情和需要用户确认的问题(如有)
|
||||
|
||||
4.每次规划任务必须按照步骤来,每个步骤结束后遵循第三条规定。
|
||||
@@ -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<AutoScoreRule> = [
|
||||
{
|
||||
key: 'drag',
|
||||
@@ -443,61 +398,6 @@ export const AutoScoreManager: React.FC = () => {
|
||||
}
|
||||
]
|
||||
|
||||
const renderActionItem = (action: ActionItem) => (
|
||||
<div
|
||||
key={action.id}
|
||||
style={{
|
||||
padding: '12px',
|
||||
border: '1px solid #d9d9d9',
|
||||
borderRadius: '6px',
|
||||
backgroundColor: 'var(--ss-card-bg)',
|
||||
marginBottom: '8px'
|
||||
}}
|
||||
>
|
||||
<Space wrap>
|
||||
<Select
|
||||
value={action.eventName}
|
||||
onChange={(value) => handleActionChange(action.id, 'eventName', value)}
|
||||
style={{ width: 150 }}
|
||||
options={ACTION_DEFINITIONS.map((d) => ({
|
||||
label: t(d.labelKey),
|
||||
value: d.eventName
|
||||
}))}
|
||||
/>
|
||||
{action.eventName === 'add_score' && (
|
||||
<InputNumber
|
||||
value={action.value ? parseInt(action.value) : undefined}
|
||||
onChange={(value) => handleActionChange(action.id, 'value', String(value || 0))}
|
||||
placeholder={t('autoScore.scorePlaceholder')}
|
||||
min={-100}
|
||||
max={100}
|
||||
style={{ width: 120 }}
|
||||
/>
|
||||
)}
|
||||
{action.eventName === 'add_tag' && (
|
||||
<Input
|
||||
value={action.value}
|
||||
onChange={(e) => handleActionChange(action.id, 'value', e.target.value)}
|
||||
placeholder={t('autoScore.tagNamePlaceholder')}
|
||||
style={{ width: 200 }}
|
||||
/>
|
||||
)}
|
||||
<Input
|
||||
value={action.reason}
|
||||
onChange={(e) => handleActionChange(action.id, 'reason', e.target.value)}
|
||||
placeholder={t('autoScore.reasonPlaceholder')}
|
||||
style={{ width: 200 }}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => handleRemoveAction(action.id)}
|
||||
/>
|
||||
</Space>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div style={{ padding: '24px' }}>
|
||||
{contextHolder}
|
||||
@@ -528,7 +428,7 @@ export const AutoScoreManager: React.FC = () => {
|
||||
|
||||
<RuleComponent />
|
||||
|
||||
{/* <Card
|
||||
{/* <Card
|
||||
style={{ marginBottom: '24px', backgroundColor: 'var(--ss-card-bg)' }}
|
||||
title={t('autoScore.whenTriggered')}
|
||||
>
|
||||
@@ -547,7 +447,7 @@ export const AutoScoreManager: React.FC = () => {
|
||||
</Space>
|
||||
</Card> */}
|
||||
|
||||
{/* <Card
|
||||
{/* <Card
|
||||
style={{ marginBottom: '24px', backgroundColor: 'var(--ss-card-bg)' }}
|
||||
title={t('autoScore.triggeredActions')}
|
||||
>
|
||||
|
||||
@@ -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<oobeProps> = ({ visible, onComplete }) => {
|
||||
const [reasons, setReasons] = useState<reasonItem[]>([])
|
||||
const [newReasonContent, setNewReasonContent] = useState('')
|
||||
const [newReasonDelta, setNewReasonDelta] = useState(1)
|
||||
const [adminPassword, setAdminPassword] = useState('')
|
||||
const [pointsPassword, setPointsPassword] = useState('')
|
||||
|
||||
const fileInputRef = useRef<HTMLInputElement>(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<oobeProps> = ({ 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<oobeProps> = ({ visible, onComplete }) => {
|
||||
</div>
|
||||
)
|
||||
|
||||
case 'password':
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||
<Typography.Text type="secondary">
|
||||
{t('oobe.steps.password.description')}
|
||||
</Typography.Text>
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<div>
|
||||
<Typography.Text strong>{t('oobe.steps.password.adminPassword')}</Typography.Text>
|
||||
<Typography.Text
|
||||
type="secondary"
|
||||
style={{ display: 'block', fontSize: 12, marginTop: 4 }}
|
||||
>
|
||||
{t('oobe.steps.password.adminPasswordHint')}
|
||||
</Typography.Text>
|
||||
<Input.Password
|
||||
value={adminPassword}
|
||||
onChange={(e) => setAdminPassword(e.target.value)}
|
||||
placeholder={t('oobe.steps.password.passwordPlaceholder')}
|
||||
maxLength={6}
|
||||
style={{ marginTop: 8 }}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Typography.Text strong>{t('oobe.steps.password.pointsPassword')}</Typography.Text>
|
||||
<Typography.Text
|
||||
type="secondary"
|
||||
style={{ display: 'block', fontSize: 12, marginTop: 4 }}
|
||||
>
|
||||
{t('oobe.steps.password.pointsPasswordHint')}
|
||||
</Typography.Text>
|
||||
<Input.Password
|
||||
value={pointsPassword}
|
||||
onChange={(e) => setPointsPassword(e.target.value)}
|
||||
placeholder={t('oobe.steps.password.passwordPlaceholder')}
|
||||
maxLength={6}
|
||||
style={{ marginTop: 8 }}
|
||||
/>
|
||||
</div>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
{t('oobe.steps.password.hint')}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
</div>
|
||||
)
|
||||
|
||||
case 'students':
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||
|
||||
@@ -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[]
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "深色",
|
||||
|
||||
Reference in New Issue
Block a user