declare const process: { cwd(): string }; declare function require(name: string): { existsSync?: (path: string) => boolean; readFileSync?: (path: string, encoding: string) => string; join?: (...parts: string[]) => string; }; const { existsSync, readFileSync } = require("node:fs"); const { join } = require("node:path"); if (!existsSync || !readFileSync || !join) { throw new Error("测试运行环境缺少文件读取能力"); } const root = process.cwd(); const realtimeApiPath = join(root, "src/api/realtimeAttendance.ts"); const monthlyApiPath = join(root, "src/api/monthlyPayroll.ts"); const realtimeViewPath = join(root, "src/views/RealtimeAttendanceView.vue"); const monthlyViewPath = join(root, "src/views/MonthlyPayrollView.vue"); const resultsTablePath = join(root, "src/components/ResultsTable.vue"); const topBarPath = join(root, "src/components/TopBar.vue"); const dashboardViewPath = join(root, "src/views/DashboardView.vue"); const routerPath = join(root, "src/router/index.ts"); assertTruthy(existsSync(realtimeApiPath), "缺少实时考勤 API 客户端"); assertTruthy(existsSync(monthlyApiPath), "缺少月度核算 API 客户端"); assertTruthy(existsSync(resultsTablePath), "缺少工资明细表组件"); assertTruthy(existsSync(topBarPath), "缺少顶部导航组件"); const realtimeApi = readFileSync(realtimeApiPath, "utf-8"); assertIncludes(realtimeApi, "/api/attendance/realtime/today"); assertIncludes(realtimeApi, "/api/attendance/realtime/sync"); const monthlyApi = readFileSync(monthlyApiPath, "utf-8"); assertIncludes(monthlyApi, "/api/payroll/monthly/runs"); assertIncludes(monthlyApi, "/api/payroll/monthly/dingtalk"); assertIncludes(monthlyApi, "/api/payroll/monthly/excel"); const realtimeView = readFileSync(realtimeViewPath, "utf-8"); assertNotIncludes(realtimeView, "待接入菜单"); assertNotIncludes(realtimeView, "待接入钉钉同步"); assertIncludes(realtimeView, "预估金额,最终以月度核算锁定结果为准"); assertIncludes(realtimeView, "realtime-page-header"); assertIncludes(realtimeView, "realtime-header-actions"); assertIncludes(realtimeView, "realtime-date-field"); assertIncludes(realtimeView, "realtime-overview-panel"); assertIncludes(realtimeView, "realtime-overview-header"); assertIncludes(realtimeView, "realtime-overview-title"); assertIncludes(realtimeView, "realtime-overview-stats"); assertIncludes(realtimeView, "syncStatusText"); assertIncludes(realtimeView, "formatSyncTime"); assertIncludes(realtimeView, "realtime-sync-chip"); assertNotIncludes(realtimeView, 'response?.sync_job_id ? "已同步" : "未同步"'); assertIncludes(realtimeView, "employeeQuery"); assertIncludes(realtimeView, "departmentFilter"); assertIncludes(realtimeView, "departmentOptions"); assertIncludes(realtimeView, "filteredRows"); assertIncludes(realtimeView, "hasRealtimeFilters"); assertIncludes(realtimeView, "clearRealtimeFilters"); assertIncludes(realtimeView, "realtime-table-filter-bar"); assertIncludes(realtimeView, "部门筛选"); assertIncludes(realtimeView, "员工搜索"); assertIncludes(realtimeView, "realtime-clear-filter"); assertIncludes(realtimeView, ":disabled=\"!hasRealtimeFilters\""); assertIncludes(realtimeView, "@click=\"clearRealtimeFilters\""); assertIncludes(realtimeView, "清空"); assertIncludes(realtimeView, "显示 {{ filteredRows.length }} / {{ rows.length }} 人"); assertIncludes(realtimeView, "v-for=\"row in filteredRows\""); const topBar = readFileSync(topBarPath, "utf-8"); assertIncludes(topBar, "useRoute"); assertIncludes(topBar, "showGlobalSearch"); assertIncludes(topBar, 'route.path !== "/attendance/realtime"'); assertIncludes(topBar, 'v-if="showGlobalSearch"'); const monthlyView = readFileSync(monthlyViewPath, "utf-8"); assertNotIncludes(monthlyView, "下一阶段接入"); assertNotIncludes(monthlyView, "核算流程"); assertNotIncludes(monthlyView, "计算编号仅用于系统排查"); assertIncludes(monthlyView, "本月核算"); assertNotIncludes(monthlyView, "工资核算工作台"); assertIncludes(monthlyView, "同步钉钉核算"); assertIncludes(monthlyView, "导入 Excel 核算"); assertNotIncludes(monthlyView, "payroll-workbench"); assertNotIncludes(monthlyView, "payroll-flow-strip"); assertIncludes(monthlyView, "payroll-compact-toolbar"); assertIncludes(monthlyView, "payroll-toolbar-month"); assertIncludes(monthlyView, "payroll-toolbar-summary"); assertIncludes(monthlyView, "payroll-toolbar-actions"); assertIncludes(monthlyView, "payroll-toolbar-export"); assertIncludes(monthlyView, "payroll-toolbar-range"); assertIncludes(monthlyView, "payroll-detail-panel"); assertIncludes(monthlyView, "payroll-detail-summary"); assertIncludes(monthlyView, "payroll-secondary-grid"); assertIncludes(monthlyView, ":exceptions=\"detail.exceptions\""); assertNotIncludes(monthlyView, "payroll-command-copy"); assertNotIncludes(monthlyView, "payroll-command-card"); assertNotIncludes(monthlyView, "payroll-command-step"); assertNotIncludes(monthlyView, "payroll-step-index"); assertNotIncludes(monthlyView, "payroll-period-row"); assertNotIncludes(monthlyView, "payroll-source-row"); assertNotIncludes(monthlyView, "payroll-source-copy"); assertNotIncludes(monthlyView, "payroll-summary-band"); assertNotIncludes(monthlyView, "核算操作"); assertNotIncludes(monthlyView, "1 选择月份"); assertNotIncludes(monthlyView, "2 取数核算"); assertNotIncludes(monthlyView, "3 核对工资"); assertNotIncludes(monthlyView, "4 锁定导出"); assertNotIncludes(monthlyView, "payroll-action-card"); assertNotIncludes(monthlyView, "payroll-result-panel"); assertIncludes(monthlyView, "锁定后本月工资不能重新计算"); assertIncludes(monthlyView, "核算历史"); assertIncludes(monthlyView, "查看明细"); assertIncludes(monthlyView, "技术信息"); assertIncludes(monthlyView, "技术信息"); assertNotIncludes(monthlyView, "请输入任务编号"); assertIncludes(monthlyView, "calculateMonthlyFromDingTalk"); assertIncludes(monthlyView, "calculateMonthlyFromExcel"); assertIncludes(monthlyView, "monthly_payroll:calculate"); assertNotIncludes(monthlyView, "monthly-source-strip"); assertIncludes(monthlyView, "核算月份"); assertIncludes(monthlyView, "同步范围"); assertIncludes(monthlyView, "Excel 文件月份"); assertIncludes(monthlyView, "请选择核算月份后再同步钉钉"); assertIncludes(monthlyView, "已根据文件识别核算月份"); assertIncludes(monthlyView, "生成 Excel 结果文件"); assertNotIncludes(monthlyView, "优先同步钉钉,也可导入钉钉月度汇总 Excel。"); assertNotIncludes(monthlyView, "选择 Excel 后会自动识别文件月份。"); assertNotIncludes(monthlyView, ">01<"); assertNotIncludes(monthlyView, ">02<"); assertBefore(monthlyView, "核算月份", "同步钉钉核算"); assertBefore(monthlyView, "工资明细", "核算历史"); assertBefore(monthlyView, "工资明细", "异常清单"); assertBefore(monthlyView, "payroll-compact-toolbar", "payroll-detail-panel"); assertBefore(monthlyView, "payroll-detail-panel", "payroll-secondary-grid"); const resultsTable = readFileSync(resultsTablePath, "utf-8"); assertIncludes(resultsTable, "embedded"); assertIncludes(resultsTable, "salary-detail-table"); assertIncludes(resultsTable, "salary-summary-toolbar"); assertIncludes(resultsTable, "salary-summary-table"); assertIncludes(resultsTable, "salary-detail-drawer"); assertIncludes(resultsTable, "filteredResults"); assertIncludes(resultsTable, "anomalyLabel"); assertIncludes(resultsTable, "查看构成"); assertIncludes(resultsTable, "部门"); assertIncludes(resultsTable, "异常"); assertIncludes(resultsTable, "实发工资"); assertBefore(resultsTable, "salary-summary-table", "salary-detail-drawer"); const dashboardView = readFileSync(dashboardViewPath, "utf-8"); assertNotIncludes(dashboardView, "/payroll/jobs"); assertNotIncludes(dashboardView, 'to="/payroll"'); assertNotIncludes(dashboardView, "readRecentJobs"); assertNotIncludes(dashboardView, "RecentPayrollJob"); assertIncludes(dashboardView, "listMonthlyRuns"); assertIncludes(dashboardView, "exportMonthlyRun"); assertNotIncludes(dashboardView, "payroll:dingtalk:calculate"); assertIncludes(dashboardView, "monthly_payroll:calculate"); assertNotIncludes(dashboardView, "任务编号"); assertNotIncludes(dashboardView, "任务记录"); assertNotIncludes(dashboardView, "最近任务"); assertNotIncludes(dashboardView, "任务来源"); assertNotIncludes(dashboardView, "暂无计算记录"); assertIncludes(dashboardView, "核算历史"); assertIncludes(dashboardView, "最近核算人数"); assertIncludes(dashboardView, "工资月份"); assertIncludes(dashboardView, "实发合计"); const router = readFileSync(routerPath, "utf-8"); assertIncludes(router, 'path: "payroll"'); assertIncludes(router, 'redirect: "/payroll/monthly"'); assertNotIncludes(router, "../views/PayrollView.vue"); assertNotIncludes(router, "component: PayrollView"); assertNotIncludes(router, 'meta: { permission: "payroll:excel:calculate" }'); function assertIncludes(content: string, expected: string): void { if (!content.includes(expected)) { throw new Error(`期望包含 ${expected}`); } } function assertNotIncludes(content: string, unexpected: string): void { if (content.includes(unexpected)) { throw new Error(`不应包含 ${unexpected}`); } } function assertTruthy(value: boolean, message: string): void { if (!value) { throw new Error(message); } } function assertBefore(content: string, first: string, second: string): void { const firstIndex = content.indexOf(first); const secondIndex = content.indexOf(second); if (firstIndex === -1 || secondIndex === -1 || firstIndex >= secondIndex) { throw new Error(`期望 ${first} 出现在 ${second} 之前`); } }