mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-19 19:04:23 +08:00
+3
-1
@@ -31,7 +31,9 @@ export default defineConfig(
|
|||||||
'react-refresh/only-export-components': 'off',
|
'react-refresh/only-export-components': 'off',
|
||||||
'react-hooks/exhaustive-deps': 'warn',
|
'react-hooks/exhaustive-deps': 'warn',
|
||||||
'react-hooks/set-state-in-effect': 'off',
|
'react-hooks/set-state-in-effect': 'off',
|
||||||
'@typescript-eslint/no-require-imports': 'off'
|
'@typescript-eslint/no-require-imports': 'off',
|
||||||
|
// we use TypeScript types instead of PropTypes in React components
|
||||||
|
'react/prop-types': 'off'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
eslintConfigPrettier
|
eslintConfigPrettier
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export class InitSchema2026011800000 implements MigrationInterface {
|
|||||||
('迟到','纪律',-1,1,CURRENT_TIMESTAMP),
|
('迟到','纪律',-1,1,CURRENT_TIMESTAMP),
|
||||||
('未交作业','作业情况',-2,1,CURRENT_TIMESTAMP)
|
('未交作业','作业情况',-2,1,CURRENT_TIMESTAMP)
|
||||||
`)
|
`)
|
||||||
|
|
||||||
if (!(await queryRunner.hasTable('tags'))) {
|
if (!(await queryRunner.hasTable('tags'))) {
|
||||||
await queryRunner.query(`
|
await queryRunner.query(`
|
||||||
CREATE TABLE "tags" (
|
CREATE TABLE "tags" (
|
||||||
|
|||||||
@@ -357,7 +357,7 @@ export class AutoScoreService extends Service {
|
|||||||
|
|
||||||
// 使用AND/OR逻辑评估触发器
|
// 使用AND/OR逻辑评估触发器
|
||||||
const matchedStudents = this.evaluateTriggersWithLogic(rule, studentsToScore)
|
const matchedStudents = this.evaluateTriggersWithLogic(rule, studentsToScore)
|
||||||
|
|
||||||
if (matchedStudents.length > 0) {
|
if (matchedStudents.length > 0) {
|
||||||
for (const action of rule.actions || []) {
|
for (const action of rule.actions || []) {
|
||||||
await this.executeAction(action, matchedStudents, rule.name)
|
await this.executeAction(action, matchedStudents, rule.name)
|
||||||
@@ -387,7 +387,7 @@ export class AutoScoreService extends Service {
|
|||||||
for (let i = 0; i < rule.triggers.length; i++) {
|
for (let i = 0; i < rule.triggers.length; i++) {
|
||||||
const trigger = rule.triggers[i]
|
const trigger = rule.triggers[i]
|
||||||
const logic = getTriggerLogic(trigger.event)
|
const logic = getTriggerLogic(trigger.event)
|
||||||
|
|
||||||
if (!logic?.check) {
|
if (!logic?.check) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -404,9 +404,9 @@ export class AutoScoreService extends Service {
|
|||||||
},
|
},
|
||||||
now: new Date()
|
now: new Date()
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = logic.check(context, trigger.value || '')
|
const result = logic.check(context, trigger.value || '')
|
||||||
|
|
||||||
if (!result.matchedStudents || result.matchedStudents.length === 0) {
|
if (!result.matchedStudents || result.matchedStudents.length === 0) {
|
||||||
// 当前触发器没有匹配的学生
|
// 当前触发器没有匹配的学生
|
||||||
if (currentRelation === 'AND') {
|
if (currentRelation === 'AND') {
|
||||||
@@ -418,13 +418,13 @@ export class AutoScoreService extends Service {
|
|||||||
// 当前触发器有匹配的学生
|
// 当前触发器有匹配的学生
|
||||||
if (currentRelation === 'AND') {
|
if (currentRelation === 'AND') {
|
||||||
// AND关系下,取交集
|
// AND关系下,取交集
|
||||||
currentGroup = currentGroup.filter(student =>
|
currentGroup = currentGroup.filter((student) =>
|
||||||
result.matchedStudents!.some(matched => matched.id === student.id)
|
result.matchedStudents!.some((matched) => matched.id === student.id)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// OR关系下,取并集
|
// OR关系下,取并集
|
||||||
const newStudents = result.matchedStudents.filter(matched =>
|
const newStudents = result.matchedStudents.filter(
|
||||||
!currentGroup.some(student => student.id === matched.id)
|
(matched) => !currentGroup.some((student) => student.id === matched.id)
|
||||||
)
|
)
|
||||||
currentGroup = [...currentGroup, ...newStudents]
|
currentGroup = [...currentGroup, ...newStudents]
|
||||||
}
|
}
|
||||||
@@ -433,14 +433,14 @@ export class AutoScoreService extends Service {
|
|||||||
// 处理下一个关系(如果存在)
|
// 处理下一个关系(如果存在)
|
||||||
if (i < rule.triggers.length - 1) {
|
if (i < rule.triggers.length - 1) {
|
||||||
const nextRelation = rule.triggers[i + 1].relation || 'AND'
|
const nextRelation = rule.triggers[i + 1].relation || 'AND'
|
||||||
|
|
||||||
if (nextRelation !== currentRelation) {
|
if (nextRelation !== currentRelation) {
|
||||||
// 关系发生变化,处理当前组的结果
|
// 关系发生变化,处理当前组的结果
|
||||||
if (currentRelation === 'AND') {
|
if (currentRelation === 'AND') {
|
||||||
// AND组结束,如果当前组不为空,则合并到结果
|
// AND组结束,如果当前组不为空,则合并到结果
|
||||||
if (currentGroup.length > 0) {
|
if (currentGroup.length > 0) {
|
||||||
resultStudents = resultStudents.filter(student =>
|
resultStudents = resultStudents.filter((student) =>
|
||||||
currentGroup.some(groupStudent => groupStudent.id === student.id)
|
currentGroup.some((groupStudent) => groupStudent.id === student.id)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// AND组为空,整个规则不匹配
|
// AND组为空,整个规则不匹配
|
||||||
@@ -448,12 +448,13 @@ export class AutoScoreService extends Service {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// OR组结束,合并当前组到结果
|
// OR组结束,合并当前组到结果
|
||||||
const newStudents = currentGroup.filter(groupStudent =>
|
const newStudents = currentGroup.filter(
|
||||||
!resultStudents.some(resultStudent => resultStudent.id === groupStudent.id)
|
(groupStudent) =>
|
||||||
|
!resultStudents.some((resultStudent) => resultStudent.id === groupStudent.id)
|
||||||
)
|
)
|
||||||
resultStudents = [...resultStudents, ...newStudents]
|
resultStudents = [...resultStudents, ...newStudents]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重置当前组为所有学生,开始新的关系组
|
// 重置当前组为所有学生,开始新的关系组
|
||||||
currentGroup = [...initialStudents]
|
currentGroup = [...initialStudents]
|
||||||
currentRelation = nextRelation
|
currentRelation = nextRelation
|
||||||
@@ -465,8 +466,8 @@ export class AutoScoreService extends Service {
|
|||||||
if (currentRelation === 'AND') {
|
if (currentRelation === 'AND') {
|
||||||
// AND组结束,如果当前组不为空,则合并到结果
|
// AND组结束,如果当前组不为空,则合并到结果
|
||||||
if (currentGroup.length > 0) {
|
if (currentGroup.length > 0) {
|
||||||
resultStudents = resultStudents.filter(student =>
|
resultStudents = resultStudents.filter((student) =>
|
||||||
currentGroup.some(groupStudent => groupStudent.id === student.id)
|
currentGroup.some((groupStudent) => groupStudent.id === student.id)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// AND组为空,整个规则不匹配
|
// AND组为空,整个规则不匹配
|
||||||
@@ -474,8 +475,9 @@ export class AutoScoreService extends Service {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// OR组结束,合并当前组到结果
|
// OR组结束,合并当前组到结果
|
||||||
const newStudents = currentGroup.filter(groupStudent =>
|
const newStudents = currentGroup.filter(
|
||||||
!resultStudents.some(resultStudent => resultStudent.id === groupStudent.id)
|
(groupStudent) =>
|
||||||
|
!resultStudents.some((resultStudent) => resultStudent.id === groupStudent.id)
|
||||||
)
|
)
|
||||||
resultStudents = [...resultStudents, ...newStudents]
|
resultStudents = [...resultStudents, ...newStudents]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -337,9 +337,7 @@ export class HttpServerService extends Service {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// ww
|
// stray expression removed
|
||||||
this.app.use
|
|
||||||
|
|
||||||
// 404处理
|
// 404处理
|
||||||
this.app.use((_req: Request, res: Response) => {
|
this.app.use((_req: Request, res: Response) => {
|
||||||
res.status(404).json({
|
res.status(404).json({
|
||||||
@@ -349,7 +347,8 @@ export class HttpServerService extends Service {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 错误处理中间件
|
// 错误处理中间件
|
||||||
this.app.use((error: Error, _: Request, res: Response, __: NextFunction) => {
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
this.app.use((error: Error, _: Request, res: Response, _next: NextFunction) => {
|
||||||
this.mainCtx.logger.error(`HTTP server error: ${error.message}`)
|
this.mainCtx.logger.error(`HTTP server error: ${error.message}`)
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
|
|||||||
@@ -569,7 +569,7 @@ export const AutoScoreManager: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card style={{ marginBottom: '24px', backgroundColor: 'var(--ss-card-bg)' }}>
|
<Card style={{ marginBottom: '24px' }}>
|
||||||
<Code
|
<Code
|
||||||
code={(() => {
|
code={(() => {
|
||||||
if (editingRuleId !== null) {
|
if (editingRuleId !== null) {
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import { useEffect } from 'react'
|
|||||||
import Prism from 'prismjs'
|
import Prism from 'prismjs'
|
||||||
import { useTheme } from '../contexts/ThemeContext'
|
import { useTheme } from '../contexts/ThemeContext'
|
||||||
|
|
||||||
// 预先导入所有主题
|
|
||||||
import 'prismjs/themes/prism-coy.min.css'
|
|
||||||
import 'prismjs/themes/prism-okaidia.min.css'
|
import 'prismjs/themes/prism-okaidia.min.css'
|
||||||
|
|
||||||
const Code = ({ code, language }) => {
|
const Code = ({ code, language }) => {
|
||||||
|
|||||||
@@ -29,15 +29,8 @@ export const TagEditorDialog: React.FC<TagEditorDialogProps> = ({
|
|||||||
|
|
||||||
const themeMode = currentTheme?.mode || 'light' // 默认为 light
|
const themeMode = currentTheme?.mode || 'light' // 默认为 light
|
||||||
|
|
||||||
useEffect(() => {
|
// fetchAllTags is declared as a function so it can be called from useEffect
|
||||||
if (visible) {
|
async function fetchAllTags() {
|
||||||
setSelectedTagIds(new Set(initialTagIds))
|
|
||||||
setInputValue('')
|
|
||||||
fetchAllTags()
|
|
||||||
}
|
|
||||||
}, [visible, initialTagIds])
|
|
||||||
|
|
||||||
const fetchAllTags = async () => {
|
|
||||||
if (!(window as any).api) return
|
if (!(window as any).api) return
|
||||||
try {
|
try {
|
||||||
const res = await (window as any).api.tagsGetAll()
|
const res = await (window as any).api.tagsGetAll()
|
||||||
@@ -50,6 +43,14 @@ export const TagEditorDialog: React.FC<TagEditorDialogProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible) {
|
||||||
|
setSelectedTagIds(new Set(initialTagIds))
|
||||||
|
setInputValue('')
|
||||||
|
fetchAllTags()
|
||||||
|
}
|
||||||
|
}, [visible, initialTagIds])
|
||||||
|
|
||||||
const handleAddTag = async () => {
|
const handleAddTag = async () => {
|
||||||
const trimmed = inputValue.trim()
|
const trimmed = inputValue.trim()
|
||||||
if (!trimmed) return
|
if (!trimmed) return
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ const actionDefinitions: ActionDefinition[] = [
|
|||||||
description: sendNotificationDescription,
|
description: sendNotificationDescription,
|
||||||
component: SendNotificationAction,
|
component: SendNotificationAction,
|
||||||
hasReason: sendNotificationHasReason
|
hasReason: sendNotificationHasReason
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
actionDefinitions.forEach((def) => actionRegistry.register(def))
|
actionDefinitions.forEach((def) => actionRegistry.register(def))
|
||||||
|
|||||||
@@ -14,8 +14,4 @@ export { default as TriggerItemComponent } from './TriggerItem'
|
|||||||
export { default as ActionItemComponent } from './ActionItem'
|
export { default as ActionItemComponent } from './ActionItem'
|
||||||
|
|
||||||
export { IntervalTimeTrigger, StudentTagTrigger, RandomTimeTrigger } from './triggers'
|
export { IntervalTimeTrigger, StudentTagTrigger, RandomTimeTrigger } from './triggers'
|
||||||
export {
|
export { AddScoreAction, AddTagAction, SendNotificationAction } from './actions'
|
||||||
AddScoreAction,
|
|
||||||
AddTagAction,
|
|
||||||
SendNotificationAction,
|
|
||||||
} from './actions'
|
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ const IntervalTimeTrigger: React.FC<TriggerComponentProps> = ({ value, onChange
|
|||||||
numValue === undefined || isNaN(numValue)
|
numValue === undefined || isNaN(numValue)
|
||||||
? undefined
|
? undefined
|
||||||
: unit === 'minutes'
|
: unit === 'minutes'
|
||||||
? numValue
|
? numValue
|
||||||
: Math.max(1, Math.round(numValue / 1440))
|
: Math.max(1, Math.round(numValue / 1440))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Space>
|
<Space>
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ const RandomTimeTrigger: React.FC<TriggerComponentProps> = ({ value, onChange })
|
|||||||
const parsed = JSON.parse(value)
|
const parsed = JSON.parse(value)
|
||||||
config = { ...config, ...parsed }
|
config = { ...config, ...parsed }
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch (e) {
|
||||||
|
console.debug('RandomTimeTrigger parse error', e)
|
||||||
|
}
|
||||||
|
|
||||||
const handleChange = (key: keyof RandomTimeConfig, v: any) => {
|
const handleChange = (key: keyof RandomTimeConfig, v: any) => {
|
||||||
const numV = typeof v === 'number' ? v : v ? Number(v) : undefined
|
const numV = typeof v === 'number' ? v : v ? Number(v) : undefined
|
||||||
|
|||||||
@@ -34,7 +34,10 @@ export const randomTimeTrigger: TriggerLogic = {
|
|||||||
const parsed = JSON.parse(value)
|
const parsed = JSON.parse(value)
|
||||||
config = { ...config, ...parsed }
|
config = { ...config, ...parsed }
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch (e) {
|
||||||
|
// swallow errors, use default config
|
||||||
|
console.debug('randomTimeTrigger calculateNextTime parse error', e)
|
||||||
|
}
|
||||||
|
|
||||||
const minHour = config.minHour ?? 0
|
const minHour = config.minHour ?? 0
|
||||||
const maxHour = config.maxHour ?? 23
|
const maxHour = config.maxHour ?? 23
|
||||||
|
|||||||
Reference in New Issue
Block a user