升级学生档案管理

This commit is contained in:
Codex
2026-06-30 11:35:25 +08:00
parent 93cd2e9c75
commit 124ffca03e
17 changed files with 409 additions and 436 deletions
+136 -17
View File
@@ -1,25 +1,39 @@
const fs = require("fs");
const path = require("path");
const vm = require("vm");
const staticDir = path.resolve(__dirname, "../app/static");
function domNode(id, extras = {}) {
let value = "";
return {
id,
textContent: "",
innerHTML: "",
hidden: false,
disabled: false,
dataset: {},
classList: { toggle() {} },
get value() {
return value;
},
set value(nextValue) {
value = String(nextValue ?? "");
},
setAttribute(name, nextValue) {
this[name] = nextValue;
},
addEventListener() {},
focus() {},
...extras,
};
}
const elements = new Map();
function element(id) {
if (!elements.has(id)) {
elements.set(id, {
id,
textContent: "",
innerHTML: "",
hidden: false,
disabled: false,
value: "",
dataset: {},
classList: { toggle() {} },
setAttribute(name, value) {
this[name] = value;
},
addEventListener() {},
focus() {},
});
elements.set(id, domNode(id));
}
return elements.get(id);
}
@@ -52,6 +66,9 @@ const context = {
Number,
String,
};
context.window = { location: { href: "" } };
context.confirm = () => true;
context.alert = () => {};
function assertEqual(name, actual, expected) {
if (actual !== expected) {
@@ -64,7 +81,7 @@ function value(expression) {
}
vm.createContext(context);
vm.runInContext(fs.readFileSync("app/static/app.js", "utf8"), context);
vm.runInContext(fs.readFileSync(path.join(staticDir, "app.js"), "utf8"), context);
vm.runInContext(
`
@@ -102,4 +119,106 @@ assertEqual("纠错后排序仍使用显示记录", value("currentRecordOrder.jo
vm.runInContext("updateCorrectionToolbar('测试提交按钮')", context);
assertEqual("提交审核按钮文案", value("submitCorrectionsBtn.textContent"), "提交审核 1 条");
console.log("smoke test passed");
const adminElements = new Map();
function adminElement(id) {
if (!adminElements.has(id)) {
adminElements.set(id, domNode(id, {
scrollIntoView() {},
}));
}
return adminElements.get(id);
}
const adminFetchUrls = [];
const adminContext = {
console,
navigator: {},
Date,
JSON,
Map,
Number,
String,
URLSearchParams,
window: { location: { href: "" } },
confirm: () => true,
alert: () => {},
document: {
querySelector(selector) {
return adminElement(selector.replace(/^#/, ""));
},
querySelectorAll() {
return [];
},
addEventListener() {},
createElement(tag) {
return adminElement(`created-${tag}-${adminElements.size}`);
},
body: { appendChild() {} },
execCommand() {
return true;
},
},
fetch: async (url) => {
adminFetchUrls.push(String(url));
if (String(url).startsWith("/api/students")) {
return {
ok: true,
json: async () => ({
summary: { total: 1, debt: 0, warning: 0, normal: 1 },
count: 1,
students: [
{
student_id: "XS001",
student: "甲",
primary_entry_year: 2020,
account_status: "正常",
remaining: 12,
remaining_duration: "12小时",
payments: [{ date: "2026-06-01", hours: 12, duration: "12小时" }],
note: "备注",
},
],
}),
};
}
return {
ok: true,
json: async () => ({
ok: true,
students_count: 1,
active_teachers_count: 0,
records_count: 0,
course_summaries_count: 0,
current_month_hours: 0,
current_month_duration: "0小时",
overview: {},
students: { summary: {}, low_remaining: [] },
teachers: {},
course_summaries: {},
tasks: {},
teaching: {},
period: {},
}),
};
},
};
vm.createContext(adminContext);
vm.runInContext(fs.readFileSync(path.join(staticDir, "admin.js"), "utf8"), adminContext);
vm.runInContext("loadStudents()", adminContext);
setTimeout(() => {
if (!adminFetchUrls.some((url) => url.startsWith("/api/students"))) {
throw new Error(`学生档案列表未调用 /api/students${adminFetchUrls.join(",")}`);
}
const rowsHtml = adminElements.get("studentRows").innerHTML;
if (!rowsHtml.includes("XS001") || !rowsHtml.includes("2020")) {
throw new Error("学生档案列表未渲染学生ID和入学年份");
}
vm.runInContext('openEditStudentEditor("XS001")', adminContext);
const payload = vm.runInContext("buildStudentPayload()", adminContext);
if (Object.prototype.hasOwnProperty.call(payload, "remaining")) {
throw new Error("学生档案保存 payload 不应包含 remaining");
}
assertEqual("学生档案剩余课时只读展示", adminElements.get("editRemaining").value, "12小时");
console.log("smoke test passed");
}, 0);