mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-19 19:04:23 +08:00
feat: 自动化加上学生分数大于或小于的trigger
feat: 自动化表格tyle统一
This commit is contained in:
Binary file not shown.
@@ -563,6 +563,20 @@ fn build_trigger_rule_node(trigger: &AutoScoreTrigger) -> JsonValue {
|
|||||||
trigger.value.clone().unwrap_or_default(),
|
trigger.value.clone().unwrap_or_default(),
|
||||||
)]),
|
)]),
|
||||||
),
|
),
|
||||||
|
"student_score_gt" => (
|
||||||
|
"student_score",
|
||||||
|
"greater",
|
||||||
|
JsonValue::Array(vec![JsonValue::String(
|
||||||
|
trigger.value.clone().unwrap_or_default(),
|
||||||
|
)]),
|
||||||
|
),
|
||||||
|
"student_score_lt" => (
|
||||||
|
"student_score",
|
||||||
|
"less",
|
||||||
|
JsonValue::Array(vec![JsonValue::String(
|
||||||
|
trigger.value.clone().unwrap_or_default(),
|
||||||
|
)]),
|
||||||
|
),
|
||||||
_ => (
|
_ => (
|
||||||
"student_sql",
|
"student_sql",
|
||||||
"equal",
|
"equal",
|
||||||
@@ -742,6 +756,11 @@ fn trigger_from_rule_node(node: &JsonValue) -> Result<AutoScoreTrigger, String>
|
|||||||
.and_then(|value| value.get("field"))
|
.and_then(|value| value.get("field"))
|
||||||
.and_then(JsonValue::as_str)
|
.and_then(JsonValue::as_str)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
let operator = node
|
||||||
|
.get("properties")
|
||||||
|
.and_then(|value| value.get("operator"))
|
||||||
|
.and_then(JsonValue::as_str)
|
||||||
|
.unwrap_or_default();
|
||||||
let first_value = node
|
let first_value = node
|
||||||
.get("properties")
|
.get("properties")
|
||||||
.and_then(|value| value.get("value"))
|
.and_then(|value| value.get("value"))
|
||||||
@@ -785,6 +804,23 @@ fn trigger_from_rule_node(node: &JsonValue) -> Result<AutoScoreTrigger, String>
|
|||||||
};
|
};
|
||||||
normalize_trigger(trigger)
|
normalize_trigger(trigger)
|
||||||
}
|
}
|
||||||
|
"student_score" | "student_score_gt" | "student_score_lt" => {
|
||||||
|
let score = match first_value {
|
||||||
|
Some(JsonValue::Number(value)) => value.to_string(),
|
||||||
|
Some(JsonValue::String(value)) => value,
|
||||||
|
_ => String::new(),
|
||||||
|
};
|
||||||
|
let event = if operator.eq_ignore_ascii_case("less") || field == "student_score_lt" {
|
||||||
|
"student_score_lt"
|
||||||
|
} else {
|
||||||
|
"student_score_gt"
|
||||||
|
};
|
||||||
|
let trigger = AutoScoreTrigger {
|
||||||
|
event: event.to_string(),
|
||||||
|
value: if score.trim().is_empty() { None } else { Some(score) },
|
||||||
|
};
|
||||||
|
normalize_trigger(trigger)
|
||||||
|
}
|
||||||
_ => Err(format!("Unsupported trigger tree field: {}", field)),
|
_ => Err(format!("Unsupported trigger tree field: {}", field)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -829,6 +865,14 @@ fn normalize_trigger(trigger: AutoScoreTrigger) -> Result<AutoScoreTrigger, Stri
|
|||||||
value: Some(raw_sql),
|
value: Some(raw_sql),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
"student_score_gt" | "student_score_lt" => {
|
||||||
|
let threshold = normalize_integer_string(trigger.value.as_deref())
|
||||||
|
.ok_or_else(|| "Student score trigger requires an integer value".to_string())?;
|
||||||
|
Ok(AutoScoreTrigger {
|
||||||
|
event,
|
||||||
|
value: Some(threshold),
|
||||||
|
})
|
||||||
|
}
|
||||||
_ => Err(format!("Unsupported trigger event: {}", event)),
|
_ => Err(format!("Unsupported trigger event: {}", event)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -932,6 +976,15 @@ fn normalize_non_zero_integer_string(value: Option<&str>) -> Option<String> {
|
|||||||
Some(parsed.to_string())
|
Some(parsed.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn normalize_integer_string(value: Option<&str>) -> Option<String> {
|
||||||
|
let normalized = value?.trim();
|
||||||
|
if normalized.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let parsed = normalized.parse::<i32>().ok()?;
|
||||||
|
Some(parsed.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
fn normalize_last_executed(value: Option<String>) -> Option<String> {
|
fn normalize_last_executed(value: Option<String>) -> Option<String> {
|
||||||
value
|
value
|
||||||
.as_deref()
|
.as_deref()
|
||||||
@@ -1816,6 +1869,18 @@ fn evaluate_trigger_tree_for_student(
|
|||||||
.map(|refs| refs.ids.contains(&student.id) || refs.names.contains(&student.name))
|
.map(|refs| refs.ids.contains(&student.id) || refs.names.contains(&student.name))
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
"student_score_gt" => trigger
|
||||||
|
.value
|
||||||
|
.as_deref()
|
||||||
|
.and_then(|value| value.trim().parse::<i32>().ok())
|
||||||
|
.map(|threshold| student.score > threshold)
|
||||||
|
.unwrap_or(false),
|
||||||
|
"student_score_lt" => trigger
|
||||||
|
.value
|
||||||
|
.as_deref()
|
||||||
|
.and_then(|value| value.trim().parse::<i32>().ok())
|
||||||
|
.map(|threshold| student.score < threshold)
|
||||||
|
.unwrap_or(false),
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
Ok(matched)
|
Ok(matched)
|
||||||
|
|||||||
@@ -75,9 +75,13 @@ export type ActionDraftError = "action_required" | "score_required" | "tag_requi
|
|||||||
const TRIGGER_FIELD_INTERVAL = "interval_minutes"
|
const TRIGGER_FIELD_INTERVAL = "interval_minutes"
|
||||||
const TRIGGER_FIELD_TAG = "student_tag"
|
const TRIGGER_FIELD_TAG = "student_tag"
|
||||||
const TRIGGER_FIELD_SQL = "student_sql"
|
const TRIGGER_FIELD_SQL = "student_sql"
|
||||||
|
const TRIGGER_FIELD_SCORE = "student_score"
|
||||||
|
const TRIGGER_FIELD_SCORE_GT = "student_score_gt"
|
||||||
const TRIGGER_TYPE_INTERVAL = "interval_duration"
|
const TRIGGER_TYPE_INTERVAL = "interval_duration"
|
||||||
const TRIGGER_WIDGET_INTERVAL = "interval_duration"
|
const TRIGGER_WIDGET_INTERVAL = "interval_duration"
|
||||||
const OP_EQUAL = "equal"
|
const OP_EQUAL = "equal"
|
||||||
|
const OP_GREATER = "greater"
|
||||||
|
const OP_LESS = "less"
|
||||||
const OP_MULTISELECT_CONTAINS = "multiselect_contains"
|
const OP_MULTISELECT_CONTAINS = "multiselect_contains"
|
||||||
|
|
||||||
const buildEmptyGroup = (): JsonGroup => ({
|
const buildEmptyGroup = (): JsonGroup => ({
|
||||||
@@ -188,11 +192,26 @@ const ruleFromTrigger = (trigger: AutoScoreTrigger): JsonRule | null => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (trigger.event === "student_score_gt" || trigger.event === "student_score_lt") {
|
||||||
|
const score = toFiniteNumber(trigger.value)
|
||||||
|
if (score === null) return null
|
||||||
|
return {
|
||||||
|
id: QbUtils.uuid(),
|
||||||
|
type: "rule",
|
||||||
|
properties: {
|
||||||
|
field: TRIGGER_FIELD_SCORE,
|
||||||
|
operator: trigger.event === "student_score_lt" ? OP_LESS : OP_GREATER,
|
||||||
|
value: [score],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const triggerFromRule = (rule: JsonRule): AutoScoreTrigger | null => {
|
const triggerFromRule = (rule: JsonRule): AutoScoreTrigger | null => {
|
||||||
const field = typeof rule.properties?.field === "string" ? rule.properties.field : ""
|
const field = typeof rule.properties?.field === "string" ? rule.properties.field : ""
|
||||||
|
const operator = typeof rule.properties?.operator === "string" ? rule.properties.operator : ""
|
||||||
const value = Array.isArray(rule.properties?.value) ? rule.properties.value[0] : undefined
|
const value = Array.isArray(rule.properties?.value) ? rule.properties.value[0] : undefined
|
||||||
|
|
||||||
if (field === TRIGGER_FIELD_INTERVAL) {
|
if (field === TRIGGER_FIELD_INTERVAL) {
|
||||||
@@ -226,6 +245,15 @@ const triggerFromRule = (rule: JsonRule): AutoScoreTrigger | null => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (field === TRIGGER_FIELD_SCORE || field === TRIGGER_FIELD_SCORE_GT) {
|
||||||
|
const score = toFiniteNumber(value)
|
||||||
|
if (score === null) return null
|
||||||
|
return {
|
||||||
|
event: operator === OP_LESS ? "student_score_lt" : "student_score_gt",
|
||||||
|
value: String(score),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,6 +289,16 @@ export const createTriggerQueryConfig = (t: TFunction, tagOptions: AutoScoreTagO
|
|||||||
},
|
},
|
||||||
operators: {
|
operators: {
|
||||||
...AntdConfig.operators,
|
...AntdConfig.operators,
|
||||||
|
[OP_GREATER]: {
|
||||||
|
...AntdConfig.operators[OP_GREATER],
|
||||||
|
label: ">",
|
||||||
|
labelForFormat: ">",
|
||||||
|
},
|
||||||
|
[OP_LESS]: {
|
||||||
|
...AntdConfig.operators[OP_LESS],
|
||||||
|
label: "<",
|
||||||
|
labelForFormat: "<",
|
||||||
|
},
|
||||||
[OP_MULTISELECT_CONTAINS]: {
|
[OP_MULTISELECT_CONTAINS]: {
|
||||||
...AntdConfig.operators[OP_MULTISELECT_CONTAINS],
|
...AntdConfig.operators[OP_MULTISELECT_CONTAINS],
|
||||||
label: t("autoScore.operatorContains"),
|
label: t("autoScore.operatorContains"),
|
||||||
@@ -329,6 +367,13 @@ export const createTriggerQueryConfig = (t: TFunction, tagOptions: AutoScoreTagO
|
|||||||
placeholder: t("autoScore.triggerStudentSqlPlaceholder"),
|
placeholder: t("autoScore.triggerStudentSqlPlaceholder"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
[TRIGGER_FIELD_SCORE]: {
|
||||||
|
label: t("autoScore.triggerStudentScore"),
|
||||||
|
type: "number",
|
||||||
|
defaultOperator: OP_GREATER,
|
||||||
|
operators: [OP_GREATER, OP_LESS],
|
||||||
|
valueSources: ["value"],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
...AntdConfig.settings,
|
...AntdConfig.settings,
|
||||||
@@ -341,6 +386,7 @@ export const createTriggerQueryConfig = (t: TFunction, tagOptions: AutoScoreTagO
|
|||||||
canLeaveEmptyGroup: false,
|
canLeaveEmptyGroup: false,
|
||||||
canReorder: true,
|
canReorder: true,
|
||||||
canRegroup: true,
|
canRegroup: true,
|
||||||
|
setOpOnChangeField: ["default"],
|
||||||
},
|
},
|
||||||
}) as Config
|
}) as Config
|
||||||
|
|
||||||
@@ -401,7 +447,14 @@ const hydrateTriggerTreeNode = (
|
|||||||
fallbackIndex.current += 1
|
fallbackIndex.current += 1
|
||||||
const parsed = triggerFromRule(node)
|
const parsed = triggerFromRule(node)
|
||||||
if (parsed) {
|
if (parsed) {
|
||||||
return node
|
const normalizedRule = ruleFromTrigger(parsed)
|
||||||
|
if (!normalizedRule) {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
properties: normalizedRule.properties,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fallbackTrigger) {
|
if (!fallbackTrigger) {
|
||||||
|
|||||||
@@ -96,6 +96,8 @@ function AutoScoreManager({ canEdit }: AutoScoreManagerProps): React.JSX.Element
|
|||||||
const [editingRuleId, setEditingRuleId] = useState<number | null>(null)
|
const [editingRuleId, setEditingRuleId] = useState<number | null>(null)
|
||||||
const [currentPage, setCurrentPage] = useState(1)
|
const [currentPage, setCurrentPage] = useState(1)
|
||||||
const [pageSize, setPageSize] = useState(10)
|
const [pageSize, setPageSize] = useState(10)
|
||||||
|
const [batchCurrentPage, setBatchCurrentPage] = useState(1)
|
||||||
|
const [batchPageSize, setBatchPageSize] = useState(10)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const maxPage = Math.max(1, Math.ceil(rules.length / pageSize))
|
const maxPage = Math.max(1, Math.ceil(rules.length / pageSize))
|
||||||
@@ -104,6 +106,13 @@ function AutoScoreManager({ canEdit }: AutoScoreManagerProps): React.JSX.Element
|
|||||||
}
|
}
|
||||||
}, [currentPage, pageSize, rules.length])
|
}, [currentPage, pageSize, rules.length])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const maxPage = Math.max(1, Math.ceil(batches.length / batchPageSize))
|
||||||
|
if (batchCurrentPage > maxPage) {
|
||||||
|
setBatchCurrentPage(maxPage)
|
||||||
|
}
|
||||||
|
}, [batchCurrentPage, batchPageSize, batches.length])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTriggerTree((prevTree) => normalizeTriggerTree(prevTree, triggerConfig))
|
setTriggerTree((prevTree) => normalizeTriggerTree(prevTree, triggerConfig))
|
||||||
}, [triggerConfig])
|
}, [triggerConfig])
|
||||||
@@ -584,6 +593,10 @@ function AutoScoreManager({ canEdit }: AutoScoreManagerProps): React.JSX.Element
|
|||||||
]
|
]
|
||||||
|
|
||||||
const pagedRules = rules.slice((currentPage - 1) * pageSize, currentPage * pageSize)
|
const pagedRules = rules.slice((currentPage - 1) * pageSize, currentPage * pageSize)
|
||||||
|
const pagedBatches = batches.slice(
|
||||||
|
(batchCurrentPage - 1) * batchPageSize,
|
||||||
|
batchCurrentPage * batchPageSize
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: "24px" }}>
|
<div style={{ padding: "24px" }}>
|
||||||
@@ -701,11 +714,24 @@ function AutoScoreManager({ canEdit }: AutoScoreManagerProps): React.JSX.Element
|
|||||||
<Table
|
<Table
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
columns={batchColumns}
|
columns={batchColumns}
|
||||||
dataSource={batches.slice(0, 50)}
|
dataSource={pagedBatches}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
tableLayout="fixed"
|
tableLayout="fixed"
|
||||||
scroll={{ x: 860 }}
|
scroll={{ x: 860 }}
|
||||||
/>
|
/>
|
||||||
|
<div style={{ marginTop: 16, textAlign: "right" }}>
|
||||||
|
<Pagination
|
||||||
|
current={batchCurrentPage}
|
||||||
|
pageSize={batchPageSize}
|
||||||
|
total={batches.length}
|
||||||
|
showSizeChanger
|
||||||
|
showTotal={(total) => t("common.total", { count: total })}
|
||||||
|
onChange={(page, size) => {
|
||||||
|
setBatchCurrentPage(page)
|
||||||
|
setBatchPageSize(size)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -84,12 +84,21 @@ export const ScoreManager: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
|
|||||||
const [students, setStudents] = useState<student[]>([])
|
const [students, setStudents] = useState<student[]>([])
|
||||||
const [reasons, setReasons] = useState<reason[]>([])
|
const [reasons, setReasons] = useState<reason[]>([])
|
||||||
const [events, setEvents] = useState<scoreEvent[]>([])
|
const [events, setEvents] = useState<scoreEvent[]>([])
|
||||||
|
const [recordCurrentPage, setRecordCurrentPage] = useState(1)
|
||||||
|
const [recordPageSize, setRecordPageSize] = useState(10)
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [submitLoading, setSubmitLoading] = useState(false)
|
const [submitLoading, setSubmitLoading] = useState(false)
|
||||||
const [form] = Form.useForm()
|
const [form] = Form.useForm()
|
||||||
const [messageApi, contextHolder] = message.useMessage()
|
const [messageApi, contextHolder] = message.useMessage()
|
||||||
const selectedStudentNames = Form.useWatch("student_name", form) as string[] | undefined
|
const selectedStudentNames = Form.useWatch("student_name", form) as string[] | undefined
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const maxPage = Math.max(1, Math.ceil(events.length / recordPageSize))
|
||||||
|
if (recordCurrentPage > maxPage) {
|
||||||
|
setRecordCurrentPage(maxPage)
|
||||||
|
}
|
||||||
|
}, [events.length, recordCurrentPage, recordPageSize])
|
||||||
|
|
||||||
const emitDataUpdated = (category: "events" | "students" | "reasons" | "all") => {
|
const emitDataUpdated = (category: "events" | "students" | "reasons" | "all") => {
|
||||||
window.dispatchEvent(new CustomEvent("ss:data-updated", { detail: { category } }))
|
window.dispatchEvent(new CustomEvent("ss:data-updated", { detail: { category } }))
|
||||||
}
|
}
|
||||||
@@ -102,7 +111,7 @@ export const ScoreManager: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
|
|||||||
const [stuRes, reaRes, eveRes] = await Promise.all([
|
const [stuRes, reaRes, eveRes] = await Promise.all([
|
||||||
(window as any).api.queryStudents({}),
|
(window as any).api.queryStudents({}),
|
||||||
(window as any).api.queryReasons(),
|
(window as any).api.queryReasons(),
|
||||||
(window as any).api.queryEvents({ limit: 10 }),
|
(window as any).api.queryEvents({ limit: 100 }),
|
||||||
])
|
])
|
||||||
|
|
||||||
if (stuRes.success) setStudents(stuRes.data)
|
if (stuRes.success) setStudents(stuRes.data)
|
||||||
@@ -231,12 +240,12 @@ export const ScoreManager: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const columns: ColumnsType<scoreEvent> = [
|
const columns: ColumnsType<scoreEvent> = [
|
||||||
{ title: t("score.student"), dataIndex: "student_name", key: "student_name", width: 100 },
|
{ title: t("score.student"), dataIndex: "student_name", key: "student_name", width: 120 },
|
||||||
{
|
{
|
||||||
title: t("score.change"),
|
title: t("score.change"),
|
||||||
dataIndex: "delta",
|
dataIndex: "delta",
|
||||||
key: "delta",
|
key: "delta",
|
||||||
width: 80,
|
width: 92,
|
||||||
render: (delta: number) => (
|
render: (delta: number) => (
|
||||||
<Tag color={delta > 0 ? "success" : "error"}>{delta > 0 ? `+${delta}` : delta}</Tag>
|
<Tag color={delta > 0 ? "success" : "error"}>{delta > 0 ? `+${delta}` : delta}</Tag>
|
||||||
),
|
),
|
||||||
@@ -245,6 +254,7 @@ export const ScoreManager: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
|
|||||||
title: t("score.reason"),
|
title: t("score.reason"),
|
||||||
dataIndex: "reason_content",
|
dataIndex: "reason_content",
|
||||||
key: "reason_content",
|
key: "reason_content",
|
||||||
|
width: 280,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -257,7 +267,7 @@ export const ScoreManager: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
|
|||||||
{
|
{
|
||||||
title: t("common.operation"),
|
title: t("common.operation"),
|
||||||
key: "operation",
|
key: "operation",
|
||||||
width: 80,
|
width: 96,
|
||||||
render: (_, row) => (
|
render: (_, row) => (
|
||||||
<Popconfirm title={t("score.undoConfirm")} onConfirm={() => handleUndo(row.uuid)}>
|
<Popconfirm title={t("score.undoConfirm")} onConfirm={() => handleUndo(row.uuid)}>
|
||||||
<Button type="link" danger disabled={!canEdit} icon={<UndoOutlined />}>
|
<Button type="link" danger disabled={!canEdit} icon={<UndoOutlined />}>
|
||||||
@@ -388,8 +398,19 @@ export const ScoreManager: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
|
|||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey="uuid"
|
rowKey="uuid"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
size="small"
|
pagination={{
|
||||||
pagination={{ pageSize: 5, total: events.length, defaultCurrent: 1 }}
|
current: recordCurrentPage,
|
||||||
|
pageSize: recordPageSize,
|
||||||
|
total: events.length,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showTotal: (total) => t("common.total", { count: total }),
|
||||||
|
onChange: (page, size) => {
|
||||||
|
setRecordCurrentPage(page)
|
||||||
|
setRecordPageSize(size)
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
tableLayout="fixed"
|
||||||
|
scroll={{ x: 860 }}
|
||||||
style={{ color: "var(--ss-text-main)" }}
|
style={{ color: "var(--ss-text-main)" }}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -816,6 +816,8 @@
|
|||||||
"tagNamesPlaceholder": "e.g. excellent_student,class_monitor",
|
"tagNamesPlaceholder": "e.g. excellent_student,class_monitor",
|
||||||
"triggerIntervalTime": "Interval Time",
|
"triggerIntervalTime": "Interval Time",
|
||||||
"triggerStudentTag": "Student Tag",
|
"triggerStudentTag": "Student Tag",
|
||||||
|
"triggerStudentScore": "Student Score",
|
||||||
|
"triggerStudentScoreGreater": "Student Score Greater Than",
|
||||||
"triggerStudentSql": "Custom SQL Condition",
|
"triggerStudentSql": "Custom SQL Condition",
|
||||||
"triggerStudentSqlPlaceholder": "Enter student SQL or a WHERE condition",
|
"triggerStudentSqlPlaceholder": "Enter student SQL or a WHERE condition",
|
||||||
"actionAddScore": "Add Score",
|
"actionAddScore": "Add Score",
|
||||||
|
|||||||
@@ -814,6 +814,8 @@
|
|||||||
"operationNoteLabel": "操作说明(可选)",
|
"operationNoteLabel": "操作说明(可选)",
|
||||||
"triggerIntervalTime": "间隔时间",
|
"triggerIntervalTime": "间隔时间",
|
||||||
"triggerStudentTag": "学生标签",
|
"triggerStudentTag": "学生标签",
|
||||||
|
"triggerStudentScore": "学生分数",
|
||||||
|
"triggerStudentScoreGreater": "学生分数大于",
|
||||||
"triggerStudentSql": "自定义 SQL 条件",
|
"triggerStudentSql": "自定义 SQL 条件",
|
||||||
"triggerStudentSqlPlaceholder": "输入筛选学生的 SQL 或 WHERE 条件",
|
"triggerStudentSqlPlaceholder": "输入筛选学生的 SQL 或 WHERE 条件",
|
||||||
"actionAddScore": "加分",
|
"actionAddScore": "加分",
|
||||||
|
|||||||
Reference in New Issue
Block a user