// 预览专用:把页面会调用的 /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 = 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(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; }