211 lines
8.6 KiB
TypeScript
211 lines
8.6 KiB
TypeScript
// 预览专用:把页面会调用的 /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;
|
||
}
|