mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-21 09:39:03 +08:00
feat: 自动化加分基本修复完毕 现在是全新的自动化qwqqq!
This commit is contained in:
@@ -51,6 +51,7 @@ export type ActionDraftError = "action_required" | "score_required" | "tag_requi
|
||||
|
||||
const TRIGGER_FIELD_INTERVAL = "interval_minutes"
|
||||
const TRIGGER_FIELD_TAG = "student_tag"
|
||||
const TRIGGER_FIELD_SQL = "student_sql"
|
||||
const TRIGGER_TYPE_INTERVAL = "interval_duration"
|
||||
const TRIGGER_WIDGET_INTERVAL = "interval_duration"
|
||||
const OP_EQUAL = "equal"
|
||||
@@ -146,6 +147,24 @@ const ruleFromTrigger = (trigger: AutoScoreTrigger): JsonRule | null => {
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
trigger.event === "query_sql" ||
|
||||
trigger.event === "student_query_sql" ||
|
||||
trigger.event === "student_sql"
|
||||
) {
|
||||
const sql = toStringValue(trigger.value).trim()
|
||||
if (!sql) return null
|
||||
return {
|
||||
id: QbUtils.uuid(),
|
||||
type: "rule",
|
||||
properties: {
|
||||
field: TRIGGER_FIELD_SQL,
|
||||
operator: OP_EQUAL,
|
||||
value: [sql],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -175,6 +194,15 @@ const triggerFromRule = (rule: JsonRule): AutoScoreTrigger | null => {
|
||||
}
|
||||
}
|
||||
|
||||
if (field === TRIGGER_FIELD_SQL) {
|
||||
const sql = toStringValue(value).trim()
|
||||
if (!sql) return null
|
||||
return {
|
||||
event: "query_sql",
|
||||
value: sql,
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -197,6 +225,25 @@ const collectTriggersFromItems = (items: JsonItem[] | undefined): AutoScoreTrigg
|
||||
export const createTriggerQueryConfig = (t: TFunction, tagOptions: AutoScoreTagOption[]): Config =>
|
||||
({
|
||||
...AntdConfig,
|
||||
conjunctions: {
|
||||
...AntdConfig.conjunctions,
|
||||
AND: {
|
||||
...AntdConfig.conjunctions.AND,
|
||||
label: t("autoScore.relationAnd"),
|
||||
},
|
||||
OR: {
|
||||
...AntdConfig.conjunctions.OR,
|
||||
label: t("autoScore.relationOr"),
|
||||
},
|
||||
},
|
||||
operators: {
|
||||
...AntdConfig.operators,
|
||||
[OP_MULTISELECT_CONTAINS]: {
|
||||
...AntdConfig.operators[OP_MULTISELECT_CONTAINS],
|
||||
label: t("autoScore.operatorContains"),
|
||||
labelForFormat: t("autoScore.operatorContains"),
|
||||
},
|
||||
},
|
||||
widgets: {
|
||||
...AntdConfig.widgets,
|
||||
[TRIGGER_WIDGET_INTERVAL]: {
|
||||
@@ -250,6 +297,15 @@ export const createTriggerQueryConfig = (t: TFunction, tagOptions: AutoScoreTagO
|
||||
showSearch: true,
|
||||
},
|
||||
},
|
||||
[TRIGGER_FIELD_SQL]: {
|
||||
label: t("autoScore.triggerStudentSql"),
|
||||
type: "text",
|
||||
operators: [OP_EQUAL],
|
||||
valueSources: ["value"],
|
||||
fieldSettings: {
|
||||
placeholder: t("autoScore.triggerStudentSqlPlaceholder"),
|
||||
},
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
...AntdConfig.settings,
|
||||
@@ -257,6 +313,7 @@ export const createTriggerQueryConfig = (t: TFunction, tagOptions: AutoScoreTagO
|
||||
compactMode: false,
|
||||
renderSize: "medium",
|
||||
showNot: true,
|
||||
notLabel: t("autoScore.relationNot"),
|
||||
forceShowConj: true,
|
||||
canLeaveEmptyGroup: false,
|
||||
canReorder: true,
|
||||
@@ -267,6 +324,9 @@ export const createTriggerQueryConfig = (t: TFunction, tagOptions: AutoScoreTagO
|
||||
export const createEmptyTriggerTree = (config: Config): ImmutableTree =>
|
||||
QbUtils.checkTree(QbUtils.loadTree(buildEmptyGroup()), config)
|
||||
|
||||
export const normalizeTriggerTree = (tree: ImmutableTree, config: Config): ImmutableTree =>
|
||||
QbUtils.checkTree(tree, config)
|
||||
|
||||
export const triggersToQueryTree = (
|
||||
config: Config,
|
||||
triggers: AutoScoreTrigger[]
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
createEmptyTriggerTree,
|
||||
createTriggerQueryConfig,
|
||||
hasUnsupportedTriggerLogic,
|
||||
normalizeTriggerTree,
|
||||
normalizeActionDrafts,
|
||||
queryTreeToTriggers,
|
||||
triggersToQueryTree,
|
||||
@@ -54,6 +55,8 @@ interface AutoScoreManagerProps {
|
||||
canEdit: boolean
|
||||
}
|
||||
|
||||
const getRuleFileRelativePath = (ruleId: number) => `auto-score/rule-${ruleId}.json`
|
||||
|
||||
function AutoScoreManager({ canEdit }: AutoScoreManagerProps): React.JSX.Element {
|
||||
const { t, i18n } = useTranslation()
|
||||
const [form] = Form.useForm<RuleFormValues>()
|
||||
@@ -92,6 +95,10 @@ function AutoScoreManager({ canEdit }: AutoScoreManagerProps): React.JSX.Element
|
||||
}
|
||||
}, [currentPage, pageSize, rules.length])
|
||||
|
||||
useEffect(() => {
|
||||
setTriggerTree((prevTree) => normalizeTriggerTree(prevTree, triggerConfig))
|
||||
}, [triggerConfig])
|
||||
|
||||
const resetEditor = () => {
|
||||
setEditingRuleId(null)
|
||||
form.setFieldsValue({ name: "", studentNames: [] })
|
||||
@@ -262,6 +269,7 @@ function AutoScoreManager({ canEdit }: AutoScoreManagerProps): React.JSX.Element
|
||||
|
||||
const res = await api.autoScoreDeleteRule(ruleId)
|
||||
if (res.success) {
|
||||
await api.fsDeleteFile(getRuleFileRelativePath(ruleId), "automatic").catch(() => void 0)
|
||||
messageApi.success(t("autoScore.deleteSuccess"))
|
||||
if (editingRuleId === ruleId) {
|
||||
resetEditor()
|
||||
@@ -295,6 +303,38 @@ function AutoScoreManager({ canEdit }: AutoScoreManagerProps): React.JSX.Element
|
||||
}
|
||||
}
|
||||
|
||||
const handleOpenRuleFile = async (rule: AutoScoreRule) => {
|
||||
const api = (window as any).api
|
||||
if (!api) return
|
||||
|
||||
try {
|
||||
const relativePath = getRuleFileRelativePath(rule.id)
|
||||
const fileContent = {
|
||||
id: rule.id,
|
||||
name: rule.name,
|
||||
enabled: rule.enabled,
|
||||
studentNames: rule.studentNames,
|
||||
triggers: rule.triggers,
|
||||
actions: rule.actions,
|
||||
lastExecuted: rule.lastExecuted ?? null,
|
||||
exportedAt: new Date().toISOString(),
|
||||
}
|
||||
|
||||
const writeRes = await api.fsWriteJson(relativePath, fileContent, "automatic")
|
||||
|
||||
if (!writeRes?.success) {
|
||||
throw new Error("prepare rule file failed")
|
||||
}
|
||||
|
||||
const openRes = await api.fsOpenPath(relativePath, "automatic")
|
||||
if (!openRes?.success) {
|
||||
throw new Error(openRes?.message || "open rule file failed")
|
||||
}
|
||||
} catch {
|
||||
messageApi.error(t("autoScore.openFileFailed"))
|
||||
}
|
||||
}
|
||||
|
||||
const formatLastExecuted = (value?: string | null) => {
|
||||
if (!value) return t("autoScore.notExecuted")
|
||||
const date = new Date(value)
|
||||
@@ -358,12 +398,15 @@ function AutoScoreManager({ canEdit }: AutoScoreManagerProps): React.JSX.Element
|
||||
{
|
||||
title: t("common.operation"),
|
||||
key: "operation",
|
||||
width: 140,
|
||||
width: 220,
|
||||
render: (_, row) => (
|
||||
<Space size={4}>
|
||||
<Button type="link" disabled={!canEdit} onClick={() => handleEdit(row)}>
|
||||
{t("common.edit")}
|
||||
</Button>
|
||||
<Button type="link" onClick={() => handleOpenRuleFile(row).catch(() => void 0)}>
|
||||
{t("autoScore.openFile")}
|
||||
</Button>
|
||||
<Popconfirm
|
||||
title={t("autoScore.deleteConfirm")}
|
||||
onConfirm={() => {
|
||||
@@ -391,7 +434,7 @@ function AutoScoreManager({ canEdit }: AutoScoreManagerProps): React.JSX.Element
|
||||
type="warning"
|
||||
showIcon
|
||||
style={{ marginBottom: "16px" }}
|
||||
message={t("autoScore.adminRequired")}
|
||||
title={t("autoScore.adminRequired")}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -403,7 +446,7 @@ function AutoScoreManager({ canEdit }: AutoScoreManagerProps): React.JSX.Element
|
||||
name="name"
|
||||
rules={[{ required: true, message: t("autoScore.nameRequired") }]}
|
||||
>
|
||||
<Input placeholder={t("autoScore.namePlaceholder")} disabled={!canEdit} />
|
||||
<Input placeholder={t("autoScore.namePlaceholder")} disabled={!canEdit} autoComplete="off"/>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("autoScore.applicableStudents")} name="studentNames">
|
||||
<Select
|
||||
|
||||
@@ -814,9 +814,11 @@
|
||||
"tagNamesPlaceholder": "e.g. excellent_student,class_monitor",
|
||||
"triggerIntervalTime": "Interval Time",
|
||||
"triggerStudentTag": "Student Tag",
|
||||
"triggerStudentSql": "Student SQL",
|
||||
"triggerStudentSqlPlaceholder": "Enter student SQL or a WHERE condition",
|
||||
"actionAddScore": "Add Score",
|
||||
"actionAddTag": "Add Tag",
|
||||
"intervalAmountPlaceholder": "Enter interval amount",
|
||||
"intervalAmountPlaceholder": "Enter interval time",
|
||||
"intervalUnitMonth": "month(s) later",
|
||||
"intervalUnitDay": "day(s) later",
|
||||
"intervalUnitMinute": "minute(s) later",
|
||||
@@ -827,7 +829,11 @@
|
||||
"tagNamePlaceholder": "Enter tag name",
|
||||
"reasonPlaceholder": "Enter reason",
|
||||
"relationAnd": "AND",
|
||||
"relationOr": "OR"
|
||||
"relationOr": "OR",
|
||||
"operatorContains": "Contains",
|
||||
"relationNot": "Not",
|
||||
"openFile": "Open File",
|
||||
"openFileFailed": "Failed to open automation file"
|
||||
},
|
||||
"triggers": {
|
||||
"studentTag": {
|
||||
|
||||
@@ -812,9 +812,11 @@
|
||||
"operationNoteLabel": "操作说明(可选)",
|
||||
"triggerIntervalTime": "间隔时间",
|
||||
"triggerStudentTag": "学生标签",
|
||||
"triggerStudentSql": "学生 SQL 条件",
|
||||
"triggerStudentSqlPlaceholder": "输入筛选学生的 SQL 或 WHERE 条件",
|
||||
"actionAddScore": "加分",
|
||||
"actionAddTag": "添加标签",
|
||||
"intervalAmountPlaceholder": "请输入间隔数量",
|
||||
"intervalAmountPlaceholder": "请输入间隔时间数值",
|
||||
"intervalUnitMonth": "个月后",
|
||||
"intervalUnitDay": "天后",
|
||||
"intervalUnitMinute": "分钟后",
|
||||
@@ -824,8 +826,12 @@
|
||||
"scorePlaceholder": "请输入分数",
|
||||
"tagNamePlaceholder": "请输入标签名称",
|
||||
"reasonPlaceholder": "请输入理由",
|
||||
"relationAnd": "并且",
|
||||
"relationOr": "或者"
|
||||
"relationAnd": "且",
|
||||
"relationOr": "或",
|
||||
"operatorContains": "包含",
|
||||
"relationNot": "非",
|
||||
"openFile": "打开文件",
|
||||
"openFileFailed": "打开自动化文件失败"
|
||||
},
|
||||
"triggers": {
|
||||
"studentTag": {
|
||||
|
||||
@@ -613,6 +613,11 @@ const api = {
|
||||
folder?: "automatic" | "script"
|
||||
): Promise<{ success: boolean; data: boolean }> =>
|
||||
invoke("fs_file_exists", { relativePath, folder }),
|
||||
fsOpenPath: (
|
||||
relativePath: string,
|
||||
folder?: "automatic" | "script"
|
||||
): Promise<{ success: boolean; message?: string }> =>
|
||||
invoke("fs_open_path", { relativePath, folder }),
|
||||
|
||||
// App
|
||||
registerUrlProtocol: (): Promise<{
|
||||
|
||||
Reference in New Issue
Block a user