111 lines
4.0 KiB
TypeScript
111 lines
4.0 KiB
TypeScript
// 视觉走查专用:在 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";',
|
||
},
|
||
},
|
||
},
|
||
})
|