Merge pull request #13 from NanGua-QWQ/main

推送一个稳定版本
This commit is contained in:
NanGua_QWQ
2026-02-22 17:11:24 +08:00
committed by GitHub
12 changed files with 49 additions and 46 deletions
+3 -1
View File
@@ -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" (
+20 -18
View File
@@ -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]
} }
+3 -4
View File
@@ -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) {
-2
View File
@@ -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
+4 -1
View File
@@ -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