Files
cz-web/vite.visual.config.ts
2026-10-01 10:53:13 +08:00

111 lines
4.0 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.
// 视觉走查专用:在 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'],
]
/**
* 状态变体覆盖(走查专用)。
*
* <p>同一张设计稿的多个状态(如「售后信息」只看已完成 + 处理中)无法靠一份静态
* json 同时表达,所以支持 `?state=done` 之类的开关在服务端就地改写字段,
* 免得为了看一个状态去反复改 mock 文件。</p>
*
* <p>注意:详情页首屏 4 个接口同帧并发,顶部信息卡的 `status` 取**任意一支**先返回
* 的,所以只改一支会出现「头说进行中、体说已完成」的错位。变体必须 4 支都打。</p>
*/
const VARIANTS: Record<string, Array<[string, unknown]>> = {
// 项目已完成 + 有处理中的售后工单 → 对应设计稿 ef0166c6
done: [['status', 'COMPLETED']],
}
/** 按点号路径写值,创建不存在的中间层 */
function setPath(obj: Record<string, unknown>, 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<string, unknown>
}
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";',
},
},
},
})