Files
wanghu 2e36d7a9c8
build / build (push) Successful in 9m55s
commit
2026-10-10 15:58:02 +08:00

226 lines
12 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 本地 mock 服务:仅为预览「教师时长」页视觉,供 preview-ut 专用 vite 代理使用
// 用法:node preview-ut/mock-server.mjs8109
import http from "node:http";
const USERS = [
{ userId: 1, userName: "王雅静", usableHours: 24, remark: "语文组教研组长" },
{ userId: 2, userName: "李国栋", usableHours: 16, remark: null },
{ userId: 3, userName: "张明远", usableHours: 6, remark: null },
{ userId: 4, userName: "陈思彤", usableHours: 0, remark: null },
{ userId: 5, userName: "周建华", usableHours: 40, remark: null },
{ userId: 6, userName: "孙丽娟", usableHours: 12.5, remark: null },
{ userId: 7, userName: "吴强", usableHours: 3, remark: null },
{ userId: 8, userName: "郑小满", usableHours: 32, remark: null },
];
const TYPES = [
{ SerID: "1", CodeID: "sj", Name: "事假", Enable: 1 },
{ SerID: "2", CodeID: "bj", Name: "病假", Enable: 1 },
{ SerID: "3", CodeID: "tx", Name: "调休", Enable: 1 },
{ SerID: "4", CodeID: "hq", Name: "婚假", Enable: 1 },
{ SerID: "5", CodeID: "cq", Name: "产假", Enable: 1 },
{ SerID: "6", CodeID: "sq", Name: "丧假", Enable: 1 },
];
// 复刻后端 UserCenterService.getSchoolAllTree 的扁平结构:
// 组 type=1/isParent=true,教师 type=2,用 pid 指向父组
const TREE = [
{ id: 100, name: "云海中学", pid: 0, type: 1, isParent: true },
{ id: 200, name: "语文组", pid: 100, type: 1, isParent: true },
{ id: 201, name: "数学组", pid: 100, type: 1, isParent: true },
{ id: 202, name: "英语组", pid: 100, type: 1, isParent: true },
{ id: 203, name: "综合组", pid: 100, type: 1, isParent: true },
{ id: 1, name: "王雅静", pid: 200, type: 2 },
{ id: 2, name: "李国栋", pid: 200, type: 2 },
{ id: 5, name: "周建华", pid: 200, type: 2 },
{ id: 3, name: "张明远", pid: 201, type: 2 },
{ id: 4, name: "陈思彤", pid: 201, type: 2 },
{ id: 6, name: "孙丽娟", pid: 202, type: 2 },
{ id: 7, name: "吴强", pid: 202, type: 2 },
{ id: 8, name: "郑小满", pid: 203, type: 2 },
];
const OPTION = { reasonMust: 1, showExplain: 1, timeLimitEnabled: 1, timeManagedTypes: "sj,bj,tx", enableCourse: 0, allowExchange: 0, calcWay: 1 };
const TIMESET = { forenoon1: "08:00", forenoon2: "12:00", afternoon1: "13:00", afternoon2: "17:00", intervalMinutes: 10, minMinutes: 0 };
// 平台 GetUserMod 的真实返回(2026-10 环境):扁平一级菜单,id = modId = 97–105,
// page 是历史路径。用于整壳验收侧栏「点击 → 跳转 → 高亮」链路(preview-app.html)。
const USER_MOD = [
{ id: 97, modId: 97, name: "我要请假", page: "/leave/", parentId: 0, level: 1, orderNumber: 100, usable: true, children: [] },
{ id: 98, modId: 98, name: "我的请假", page: "/leave/myleave", parentId: 0, level: 1, orderNumber: 110, usable: true, children: [] },
{ id: 99, modId: 99, name: "审批列表", page: "/leave/shenhe", parentId: 0, level: 1, orderNumber: 120, usable: true, children: [] },
{ id: 100, modId: 100, name: "抄送列表", page: "/leave/leave_send", parentId: 0, level: 1, orderNumber: 130, usable: true, children: [] },
{ id: 101, modId: 101, name: "全部请假", page: "/leave/totallist", parentId: 0, level: 1, orderNumber: 140, usable: true, children: [] },
{ id: 102, modId: 102, name: "流程管理", page: "/leave/liucheng", parentId: 0, level: 1, orderNumber: 160, usable: true, children: [] },
{ id: 103, modId: 103, name: "请假设置", page: "/leave/leave_timeSetting", parentId: 0, level: 1, orderNumber: 170, usable: true, children: [] },
{ id: 104, modId: 104, name: "时长日志", page: "/leave/leave_amendant", parentId: 0, level: 1, orderNumber: 180, usable: true, children: [] },
{ id: 105, modId: 105, name: "统计分析", page: "/leave/tongji", parentId: 0, level: 1, orderNumber: 190, usable: true, children: [] },
];
const USER_INFO = { id: 1, name: "王雅静", profilePicture: "" };
// ===== 流程管理(/api/leave/flow/*):内存态,可交互(开关/绑定即时生效)=====
// 复刻流程管理页 FlowManageVo;通知流程默认「未启用」,演示 oapi 混入非本应用流程的场景
const CENTER_FLOWS = [
{ flowGuid: "35dc167902ef436e8767809eb6bf11ee", name: "设课(5天)", version: 6, nodeSummary: "审核中", enabled: true },
{ flowGuid: "28bc3ca8acc24611809d7dd1b6b9a597", name: "产假(10天)", version: 5, nodeSummary: "审核中", enabled: true },
{ flowGuid: "d2237a5b2b95492e845da9caeaa35590", name: "请假申请(小于3天或大于等于3天)", version: 4, nodeSummary: "审批节点—222", enabled: true },
{ flowGuid: "fdb2c9046ede4db583b0a4899d065f75", name: "通知流程", version: 2, nodeSummary: "审批节点", enabled: false },
];
let BIND_SEQ = 3;
const FLOW_BINDS = [
{ id: 1, scope: 0, scopeKey: "-", scopeName: "全校默认", flowGuid: "d2237a5b2b95492e845da9caeaa35590", flowName: "请假申请(小于3天或大于等于3天)", flowVersion: 4, nodeSummary: "审批节点—222", existsInCenter: true },
{ id: 2, scope: 1, scopeKey: "cq", scopeName: "产假", flowGuid: "28bc3ca8acc24611809d7dd1b6b9a597", flowName: "产假(10天)", flowVersion: 5, nodeSummary: "审核中", existsInCenter: true },
];
function flowManageData() {
const flows = CENTER_FLOWS.map((f) => {
const typeKeys = FLOW_BINDS.filter((b) => b.scope === 1 && b.flowGuid === f.flowGuid).map((b) => b.scopeKey);
return {
...f,
defaultBind: FLOW_BINDS.some((b) => b.scope === 0 && b.flowGuid === f.flowGuid),
typeKeys,
typeNames: typeKeys.map((k) => TYPES.find((t) => t.CodeID === k)?.Name || k),
};
});
return {
bizType: "leave",
configFlowGuid: "d2237a5b2b95492e845da9caeaa35590",
centerReachable: true,
centerError: null,
flows,
binds: FLOW_BINDS.map((b) => ({ ...b })),
leaveTypes: TYPES.map((t) => ({ key: t.CodeID, name: t.Name })),
};
}
/** 写操作:返回 true 表示已识别并处理(POST /api/leave/flow/*) */
function flowMutate(url, body) {
const guid = body?.flowGuid;
if (/flow\/Enable/i.test(url)) {
const f = CENTER_FLOWS.find((x) => x.flowGuid === guid);
if (f) f.enabled = true;
return true;
}
if (/flow\/Disable/i.test(url)) {
const f = CENTER_FLOWS.find((x) => x.flowGuid === guid);
if (f) f.enabled = false;
for (let i = FLOW_BINDS.length - 1; i >= 0; i--) if (FLOW_BINDS[i].flowGuid === guid) FLOW_BINDS.splice(i, 1);
return true;
}
if (/flow\/Bind/i.test(url)) {
const scope = Number(body?.scope ?? 0);
const scopeKey = scope === 1 ? String(body?.scopeKey || "") : "-";
const f = CENTER_FLOWS.find((x) => x.flowGuid === guid);
if (!f) return true;
const hit = FLOW_BINDS.find((b) => b.scope === scope && b.scopeKey === scopeKey);
const row = {
id: hit ? hit.id : BIND_SEQ++,
scope,
scopeKey,
scopeName: scope === 0 ? "全校默认" : TYPES.find((t) => t.CodeID === scopeKey)?.Name || scopeKey,
flowGuid: f.flowGuid,
flowName: f.name,
flowVersion: f.version,
nodeSummary: f.nodeSummary,
existsInCenter: true,
};
if (hit) Object.assign(hit, row);
else FLOW_BINDS.push(row);
return true;
}
if (/flow\/Unbind/i.test(url)) {
const scope = Number(body?.scope ?? 0);
const scopeKey = scope === 1 ? String(body?.scopeKey || "") : "-";
for (let i = FLOW_BINDS.length - 1; i >= 0; i--) {
if (FLOW_BINDS[i].scope === scope && FLOW_BINDS[i].scopeKey === scopeKey) FLOW_BINDS.splice(i, 1);
}
return true;
}
return false;
}
// ===== 请假列表(/api/leave/List):我的请假 / 全部请假 / 抄送列表 / 审批列表 共用一个接口 =====
// 复刻 LeaveVo;状态 0审批中 1已通过 2已拒绝 3已销假 4已撤回
const LEAVE_ROWS = [
{ id: 1, leaveType: "sj", leaveTypeName: "事假", startTime: "2026-10-22 08:00:00", endTime: "2026-10-22 12:00:00", totalHours: 4, reason: "E2E扣减链路验证-事假", status: 0, canEdit: true, canWithdraw: true, canDelete: true },
{ id: 2, leaveType: "sj", leaveTypeName: "事假", startTime: "2026-10-21 08:00:00", endTime: "2026-10-21 12:00:00", totalHours: 4, reason: "E2E扣减链路验证-事假", status: 4, canEdit: true, canDelete: true },
{ id: 3, leaveType: "tx", leaveTypeName: "调休", startTime: "2026-10-20 08:00:00", endTime: "2026-10-20 12:00:00", totalHours: 4, reason: "E2E回调链路验证-调休", status: 0, canEdit: true, canWithdraw: true, canDelete: true },
{ id: 4, leaveType: "cq", leaveTypeName: "产假", startTime: "2026-10-09 08:00:00", endTime: "2026-10-18 17:00:00", totalHours: 80, reason: "1111", status: 1, canCancel: true },
{ id: 5, leaveType: "bj", leaveTypeName: "病假", startTime: "2026-09-15 13:00:00", endTime: "2026-09-15 17:00:00", totalHours: 4, reason: "感冒发热,需居家休息", status: 1, canCancel: true },
{ id: 6, leaveType: "hq", leaveTypeName: "婚假", startTime: "2026-08-03 08:00:00", endTime: "2026-08-07 17:00:00", totalHours: 40, reason: "婚假(含往返)", status: 2 },
{ id: 7, leaveType: "sq", leaveTypeName: "丧假", startTime: "2026-06-11 08:00:00", endTime: "2026-06-13 17:00:00", totalHours: 24, reason: "家中长辈丧事", status: 3 },
{ id: 8, leaveType: "sj", leaveTypeName: "事假", startTime: "2026-05-06 13:00:00", endTime: "2026-05-06 17:00:00", totalHours: 4, reason: "子女家长会", status: 1, canCancel: true },
];
function leaveList(url) {
const q = new URL(url, "http://x").searchParams;
const scope = q.get("scope") || "user";
const status = q.get("status");
const kw = (q.get("keyword") || "").trim();
const type = q.get("leaveType");
const pageIndex = Number(q.get("pageIndex") || 1);
const pageSize = Number(q.get("pageSize") || 10);
let rows = LEAVE_ROWS.map((r, i) => ({
...r,
userId: 1,
userName: scope === "user" ? "王雅静" : USERS[i % USERS.length].userName,
statusName: ["审批中", "已通过", "已拒绝", "已销假", "已撤回"][r.status],
applyTime: `2026-10-0${(i % 9) + 1} 0${(i % 9) + 1}:3${i % 10}:${10 + i}`,
}));
if (scope === "cc") rows = rows.map((r) => ({ ...r, ccTime: r.applyTime }));
if (status !== null && status !== undefined && status !== "") rows = rows.filter((r) => String(r.status) === String(status));
if (type) rows = rows.filter((r) => r.leaveType === type);
if (kw) rows = rows.filter((r) => (r.reason || "").includes(kw) || r.leaveTypeName.includes(kw) || r.userName.includes(kw));
const total = rows.length;
const content = rows.slice((pageIndex - 1) * pageSize, pageIndex * pageSize);
return { content, total, pageIndex, pageSize };
}
function resolve(url) {
// 应用外壳:用户信息 / 授权模块(整壳验收用)
if (/Base\/GetUserMod|base\/GetUserMod/i.test(url)) return USER_MOD;
if (/leave\/List/i.test(url)) return leaveList(url);
if (/Base\/GetUserInfo|base\/GetUserInfo/i.test(url)) return USER_INFO;
if (/GetSchoolAll/.test(url)) return TREE;
if (/UserTimeList/.test(url)) {
const kw = new URL(url, "http://x").searchParams.get("keyword");
return kw ? USERS.filter((u) => u.userName.includes(kw)) : USERS;
}
// 下拉接口返回 { k, v }(与后端 DictService.List 一致);管理列表返回完整字段
if (/Dict\/List/i.test(url)) return TYPES.map((t) => ({ k: t.CodeID, v: t.Name }));
if (/\/dict|Dict/.test(url)) return TYPES;
if (/Option/.test(url)) return OPTION;
if (/TimeSet/.test(url)) return TIMESET;
if (/Section/.test(url)) return [];
if (/leave\/flow\/List/i.test(url)) return flowManageData();
return null;
}
http
.createServer((req, res) => {
console.log("[mock] ->", req.method, req.url);
const chunks = [];
req.on("data", (c) => chunks.push(c));
req.on("end", () => {
let body = null;
const raw = Buffer.concat(chunks).toString("utf8");
if (raw) {
try {
body = JSON.parse(raw);
} catch {
body = Object.fromEntries(new URLSearchParams(raw));
}
}
const url = req.url || "";
let data;
if (req.method === "POST" && /leave\/flow\/(Enable|Disable|Bind|Unbind)/i.test(url)) {
flowMutate(url, body);
data = undefined; // 写接口返回 data:null
} else {
data = resolve(url);
}
res.setHeader("Content-Type", "application/json;charset=utf-8");
res.setHeader("Access-Control-Allow-Origin", "*");
res.end(JSON.stringify({ code: 0, msg: "ok", data: data === undefined ? null : data }));
});
})
.listen(8109, () => console.log("mock server on http://localhost:8109"));