import { fileURLToPath, URL } from 'node:url' import { defineConfig, loadEnv } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd(), '') // 临时CORS规避方案(仅用于本地 npm run dev 调试真实后端,不用于生产构建),与根目录管理后台 // vite.config.js 的方案完全一致:在 .env.development.local(已被 .gitignore 的 *.local 规则 // 忽略,不会提交)中配置 DEV_API_PROXY_TARGET=真实后端地址,dev server 会把浏览器发往同源 // /api 的请求在 Node 端转发给真实后端;浏览器全程只与同源的 localhost:5174 通信,天然不触发 // 浏览器 CORS 检查。同时需要把该 .env.development.local 中的 VITE_API_BASE_URL 设为空字符串, // 让 axios 走相对路径(同源),否则请求不会经过下面的代理规则。未配置 DEV_API_PROXY_TARGET 时 // proxy 为 undefined,行为与之前完全一致(直接请求 VITE_API_BASE_URL 指向的地址,默认走本地 // mock server)。 const proxy = env.DEV_API_PROXY_TARGET ? { '/api': { target: env.DEV_API_PROXY_TARGET, changeOrigin: true, cookieDomainRewrite: 'localhost', configure: (proxyServer, options) => { let seq = 0 const startTimeMap = new Map() const LOG_MAX_LEN = 2000 // 内容过长(如人脸识别的base64图片)时截断,避免刷屏终端 const truncate = (str) => { if (str.length <= LOG_MAX_LEN) return str return `${str.slice(0, LOG_MAX_LEN)}...(已截断,原文共${str.length}字符)` } // 请求/响应体优先按JSON美化打印,非JSON内容按原文打印 const formatBody = (buf) => { const text = buf.toString('utf-8') if (!text) return '' try { return truncate(JSON.stringify(JSON.parse(text))) } catch { return truncate(text) } } proxyServer.on('proxyReq', (proxyReq, req) => { const id = ++seq startTimeMap.set(req, { id, startAt: Date.now() }) // 强制真实后端返回未压缩内容,否则响应体是gzip二进制,日志打印会是乱码 proxyReq.setHeader('Accept-Encoding', 'identity') const targetUrl = new URL(options.target) const fullUrl = `${targetUrl.protocol}//${targetUrl.host}${proxyReq.path}` // 在 http-proxy 内部把 req 自动 pipe 到 proxyReq 之前先挂上监听, // 这样既能拿到完整请求体,又不影响正常转发流程 const chunks = [] req.on('data', (chunk) => chunks.push(chunk)) req.on('end', () => { const body = formatBody(Buffer.concat(chunks)) console.log(`\n[REAL-API #${id}] → ${req.method} ${fullUrl}`) if (body) console.log(` 请求体: ${body}`) }) }) proxyServer.on('proxyRes', (proxyRes, req) => { const meta = startTimeMap.get(req) || {} const chunks = [] proxyRes.on('data', (chunk) => chunks.push(chunk)) proxyRes.on('end', () => { const body = formatBody(Buffer.concat(chunks)) const cost = meta.startAt ? `${Date.now() - meta.startAt}ms` : '' console.log(`[REAL-API #${meta.id ?? '?'}] ← ${proxyRes.statusCode} (${cost})`) if (body) console.log(` 响应体: ${body}`) startTimeMap.delete(req) }) }) proxyServer.on('error', (err, req) => { const meta = startTimeMap.get(req) || {} console.log(`[REAL-API #${meta.id ?? '?'}] × 转发异常 ${req?.method} ${req?.url}: ${err.message}`) startTimeMap.delete(req) }) } } } : undefined return { plugins: [vue()], resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } }, server: { // 与主项目管理后台(端口 5173)区分,避免本机同时开发两个工程时端口冲突 port: 5174, proxy } } })