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 调试真实后端,不用于生产构建): // 在 .env.development.local(已被 .gitignore 的 *.local 规则忽略,不会提交)中配置 // DEV_API_PROXY_TARGET=真实后端地址 后,dev server 会把浏览器发往同源 /api 的请求 // 在 Node 端转发给真实后端;浏览器全程只与同源的 localhost:5173 通信,天然不触发 // 浏览器 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 // 内容过长(如文件上传/OCR的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: { port: 5173, proxy } } })