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

211 lines
8.6 KiB
TypeScript
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.
// 预览专用:把页面会调用的 /api/* 接口就地 mock 掉。
//
// 为什么需要它:业务 dev 端口(8081/8078/8079)的 /api 会经 vite 代理打到真实后端,
// 未登录时后端返回 {code:302,urlToRedirectTo},而 alova 的 onSuccess 对该 code 会执行
// `window.location.href = urlToRedirectTo`(见 src/utils/alova/alova-core.ts),整页被
// 跳去 SSO —— 预览页什么都看不到。
//
// 因此在预览入口里用 fetch 拦截替换掉网络层,使该 harness 在任意端口(含 8081)
// 都能独立渲染,不再依赖 mock-server 或真实后端。
const LEAVE_TYPES = [
{ k: "sj", v: "事假" },
{ k: "bj", v: "病假" },
{ k: "tx", v: "调休" },
{ k: "hq", v: "婚假" },
{ k: "cq", v: "产假" },
{ k: "sq", v: "丧假" },
];
const TYPE_NAMES: Record<string, string> = Object.fromEntries(LEAVE_TYPES.map((t) => [t.k, t.v]));
const STATUS_NAMES = ["审批中", "已通过", "已拒绝", "已销假", "已撤回"];
const SEED = [
{ type: "sj", start: "2026-10-22 08:00:00", end: "2026-10-22 12:00:00", hours: 4, reason: "子女家长会", status: 0 },
{ type: "sj", start: "2026-10-21 08:00:00", end: "2026-10-21 12:00:00", hours: 4, reason: "家中有事需处理", status: 4 },
{ type: "tx", start: "2026-10-20 08:00:00", end: "2026-10-20 12:00:00", hours: 4, reason: "上月加班调休", status: 0 },
{ type: "cq", start: "2026-10-09 08:00:00", end: "2026-10-18 17:00:00", hours: 80, reason: "产假", status: 1 },
{ type: "bj", start: "2026-09-15 13:00:00", end: "2026-09-15 17:00:00", hours: 4, reason: "感冒发热,需居家休息", status: 1 },
{ type: "hq", start: "2026-08-03 08:00:00", end: "2026-08-07 17:00:00", hours: 40, reason: "婚假(含往返)", status: 2 },
{ type: "sq", start: "2026-06-11 08:00:00", end: "2026-06-13 17:00:00", hours: 24, reason: "家中长辈丧事", status: 3 },
{ type: "sj", start: "2026-05-06 13:00:00", end: "2026-05-06 17:00:00", hours: 4, reason: "子女家长会", status: 1 },
];
const NAMES = ["王雅静", "李国栋", "张明远", "陈思彤", "周建华", "孙丽娟", "吴强", "郑小满"];
/** 复刻 LeaveVo */
function leaveRows(scope: string) {
return SEED.map((s, i) => ({
id: i + 1,
userId: 1,
userName: scope === "user" ? "王雅静" : NAMES[i % NAMES.length],
leaveType: s.type,
leaveTypeName: TYPE_NAMES[s.type],
startTime: s.start,
endTime: s.end,
totalHours: s.hours,
reason: s.reason,
status: s.status,
statusName: STATUS_NAMES[s.status],
applyTime: `2026-10-0${(i % 9) + 1} 0${(i % 9) + 1}:3${i % 10}:1${i}`,
ccTime: scope === "cc" ? `2026-10-0${(i % 9) + 1} 10:0${i}:00` : undefined,
canEdit: s.status === 0 || s.status === 4,
canWithdraw: s.status === 0,
canCancel: s.status === 1,
canDelete: s.status !== 1,
}));
}
/** 复刻 TodoVo */
function todoRows() {
return SEED.slice(0, 5).map((s, i) => ({
taskId: 900 + i,
instanceId: 500 + i,
leaveId: i + 1,
userName: NAMES[i % NAMES.length],
leaveType: s.type,
leaveTypeName: TYPE_NAMES[s.type],
startTime: s.start,
endTime: s.end,
totalHours: s.hours,
reason: s.reason,
status: 0,
statusName: "审批中",
applyTime: `2026-10-0${i + 1} 09:0${i}:00`,
}));
}
/** 复刻 UserTimeRecordVo */
function recordRows() {
return SEED.map((s, i) => ({
id: i + 1,
userId: 1,
userName: NAMES[i % NAMES.length],
leaveId: i + 1,
leaveType: s.type,
leaveTypeName: TYPE_NAMES[s.type],
editHours: [-8, 8, -4, 4, 2, -2, 1, -1][i],
oldHours: 16 + i,
newHours: 16 + i + [-8, 8, -4, 4, 2, -2, 1, -1][i],
describeText: i % 2 ? "请假扣减" : "管理员调增",
operatorName: "系统",
occurTime: s.end,
}));
}
/** 复刻 LeaveStatsVo */
function stats() {
const types = LEAVE_TYPES.map((t, i) => ({
type: t.k,
typeName: t.v,
count: 3 + i,
hours: 12 + i * 6,
}));
const months = Array.from({ length: 6 }, (_, i) => ({
month: `${i + 5}月`,
count: 2 + (i % 4),
hours: 8 + i * 5,
}));
return {
totalCount: 18,
totalHours: 176.5,
totalDays: 22,
topTypeName: "事假",
typeStats: types,
monthStats: months,
};
}
/** 复刻 LeaveDetailVo(打印预览用;含流程 steps 与抄送人) */
function detailRow(id: number) {
const s = SEED[(id - 1 + SEED.length) % SEED.length];
return {
id,
userId: 1,
userName: "王雅静",
leaveType: s.type,
leaveTypeName: TYPE_NAMES[s.type],
startTime: s.start,
endTime: s.end,
totalHours: s.hours,
reason: s.reason,
courseArrange: 2,
courseArrangeName: "教务安排",
status: s.status,
statusName: STATUS_NAMES[s.status],
applyTime: "2026-10-09 19:28:00",
files: [],
ccUsers: [
{ userId: 2, userName: "李国栋" },
{ userId: 3, userName: "张明远" },
],
progress: {
instanceId: 500,
flowName: "教职工请假流程",
currentNodeName: "校长审批",
submitterName: "王雅静",
startTime: "2026-10-09 19:28:00",
steps: [
{ nodeName: "发起申请", assigneeName: "王雅静", action: "submit", opinion: "", time: "2026-10-09 19:28:00", finished: true },
{ nodeName: "部门审核", assigneeName: "李国栋", action: "approve", opinion: "情况属实,同意", time: "2026-10-09 20:15:00", finished: true },
{ nodeName: "校长审批", assigneeName: "周建华", action: "approve", opinion: "同意", time: "2026-10-09 21:02:00", finished: true },
],
},
canCancel: s.status === 1,
};
}
function page<T>(rows: T[], url: string) {
const q = new URL(url, "http://x").searchParams;
const pageIndex = Number(q.get("pageIndex") || 1);
const pageSize = Number(q.get("pageSize") || 10);
return { content: rows.slice((pageIndex - 1) * pageSize, pageIndex * pageSize), total: rows.length };
}
/** 按 URL 解析出 mock 数据;返回 undefined 表示「无数据」 */
export function resolveMock(rawUrl: string): any {
const url = rawUrl;
const q = new URL(url, "http://x").searchParams;
if (/leave\/todoList/i.test(url)) return page(todoRows(), url);
if (/leave\/auditedList/i.test(url)) return page(leaveRows("all").filter((r) => r.status !== 0), url);
if (/leave\/list/i.test(url)) {
const scope = q.get("scope") || "user";
let rows = leaveRows(scope);
const status = q.get("status");
const kw = (q.get("keyword") || "").trim();
const type = q.get("leaveType");
if (status !== null && 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));
return page(rows, url);
}
if (/leave\/time\/records/i.test(url)) return page(recordRows(), url);
if (/leave\/detail/i.test(url)) return detailRow(Number(q.get("leaveId") || 4));
if (/leave\/setting\/getoption/i.test(url)) return { reasonMust: 1, showExplain: 1, enableCourse: 1, allowExchange: 0, timeLimitEnabled: 0, timeManagedTypes: "sj,bj", calcWay: 1 };
if (/leave\/time\/sum/i.test(url)) return { addHours: 24, remainHours: 16.5 };
if (/leave\/statis/i.test(url)) return stats();
if (/dict\/list/i.test(url)) return LEAVE_TYPES;
if (/base\/getuserinfo/i.test(url)) return { id: 1, name: "王雅静", profilePicture: "" };
return undefined;
}
/** 安装 fetch 拦截:仅接管 /api/* ,其余原样放行 */
export function installApiStub() {
const w = window as any;
if (w.__apiStubInstalled) return;
const original = w.fetch.bind(window);
w.__apiStubHit = 0;
w.fetch = async (input: any, init?: any) => {
const url: string = typeof input === "string" ? input : input?.url || "";
if (!/\/api\//.test(url)) return original(input, init);
w.__apiStubHit++;
const data = resolveMock(url);
return new Response(JSON.stringify({ code: 0, msg: "ok", data: data === undefined ? null : data }), {
status: 200,
headers: { "Content-Type": "application/json;charset=utf-8" },
});
};
w.__apiStubInstalled = true;
}