// 视觉走查专用:在 vite.config.ts 之上叠加 mock 中间件(仅本地临时使用,不参与构建) import { fileURLToPath, URL } from 'node:url' import { readFileSync, existsSync } from 'node:fs' import vue from '@vitejs/plugin-vue' import { defineConfig } from 'vite' const MOCK_DIR = fileURLToPath(new URL('./.mock', import.meta.url)) /** 把 mock 目录下的 json 按路径映射回响应 */ const ROUTES: Array<[RegExp, string]> = [ [/\/web\/requirementPool\/get\/progress\//, 'progress.json'], [/\/web\/requirementPool\/get\/files\//, 'files.json'], [/\/web\/requirementPool\/get\/finance\//, 'finance.json'], [/\/web\/requirementPool\/get\/afterSales\//, 'afterSales.json'], [/\/web\/bill\/list/, 'bills.json'], ] /** * 状态变体覆盖(走查专用)。 * *

同一张设计稿的多个状态(如「售后信息」只看已完成 + 处理中)无法靠一份静态 * json 同时表达,所以支持 `?state=done` 之类的开关在服务端就地改写字段, * 免得为了看一个状态去反复改 mock 文件。

* *

注意:详情页首屏 4 个接口同帧并发,顶部信息卡的 `status` 取**任意一支**先返回 * 的,所以只改一支会出现「头说进行中、体说已完成」的错位。变体必须 4 支都打。

*/ const VARIANTS: Record> = { // 项目已完成 + 有处理中的售后工单 → 对应设计稿 ef0166c6 done: [['status', 'COMPLETED']], } /** 按点号路径写值,创建不存在的中间层 */ function setPath(obj: Record, path: string, value: unknown): void { const keys = path.split('.') let cur = obj for (let i = 0; i < keys.length - 1; i += 1) { const k = keys[i] if (typeof cur[k] !== 'object' || cur[k] === null) cur[k] = {} cur = cur[k] as Record } cur[keys[keys.length - 1]] = value } export default defineConfig({ plugins: [ vue(), { name: 'visual-mock', configureServer(server) { server.middlewares.use((req, res, next) => { const url = req.url ?? '' // 状态开关从 **Referer** 取(= 当前页面 URL),而不是 req.url: // 业务代码发的接口地址里没有页面 query,走查期又不想为看一个状态改应用代码。 const referer = String(req.headers.referer ?? '') const state = referer ? new URL(referer).searchParams.get('state') : null for (const [re, file] of ROUTES) { if (!re.test(url)) continue const path = `${MOCK_DIR}/${file}` if (!existsSync(path)) break let body = readFileSync(path, 'utf-8') const patch = state ? VARIANTS[state] : undefined if (patch) { const json = JSON.parse(body) for (const [p, v] of patch) setPath(json, `data.${p}`, v) body = JSON.stringify(json) } res.setHeader('Content-Type', 'application/json; charset=utf-8') res.end(body) return } next() }) }, }, ], server: { proxy: { // 走查期不转发后端:未命中的接口直接 404(避免真实后端的 401 干扰观感) '/api': { target: 'http://127.0.0.1:1', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, ''), configure: (proxy) => { proxy.on('error', (_err, _req, res) => { if (res && !res.headersSent) { res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' }) } res?.end?.(JSON.stringify({ code: 200, msg: 'ok', data: null })) }) }, }, }, }, resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)), }, }, css: { preprocessorOptions: { less: { additionalData: '@import "@/styles/variables.less";\n@import "@/styles/mixins.less";', }, }, }, })