import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; import ViteCompression from 'vite-plugin-compression'; import ViteComponents from 'unplugin-vue-components/vite'; import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'; import { EleAdminResolver } from 'ele-admin-pro/lib/utils/resolvers'; import { resolve } from 'path'; import { visualizer } from 'rollup-plugin-visualizer'; /** * AntDv4 兼容:替换 ele-admin-pro Less 中的 colorPalette JS 调用 * AntDv4 移除了 Less 的 colorPalette 函数,ele-admin-pro 的 common.less * 中有 ~`colorPalette('@{primary-color}', 5)` 等 JS 表达式, * 此插件在 Less 编译前将其替换为硬编码的颜色值。 */ function colorPaletteReplacer() { return { name: 'color-palette-replacer', enforce: 'pre', async transform(code, id) { if (!id.includes('ele-admin-pro') || !id.endsWith('.less')) { return null; } console.log('🎨 Processing Less file:', id); let result = code; result = result.replace(/~`colorPalette\('@\{primary-color\}',\s*(\d+)\)\s*`/g, (_match, idx) => { const colors = { 1: '#e6f4ff', 2: '#bae0ff', 3: '#91caff', 4: '#69b1ff', 5: '#4096ff', 6: '#1677ff', 7: '#0958d9', 8: '#003eb3', 9: '#002c8c', 10: '#001d66' }; return colors[idx] || '#1677ff'; }); result = result.replace(/~`colorPalette\('@\{success-color\}',\s*(\d+)\)\s*`/g, (_match, idx) => { const colors = { 1: '#f6ffed', 2: '#d9f7be', 3: '#b7eb8f', 4: '#95de64', 5: '#73d13d', 6: '#52c41a', 7: '#389e0d', 8: '#237804', 9: '#135200', 10: '#092b00' }; return colors[idx] || '#52c41a'; }); result = result.replace(/~`colorPalette\('@\{warning-color\}',\s*(\d+)\)\s*`/g, (_match, idx) => { const colors = { 1: '#fffbe6', 2: '#fff1b8', 3: '#ffe58f', 4: '#ffd666', 5: '#ffc53d', 6: '#faad14', 7: '#d48806', 8: '#ad6800', 9: '#874d00', 10: '#612500' }; return colors[idx] || '#faad14'; }); result = result.replace(/~`colorPalette\('@\{error-color\}',\s*(\d+)\)\s*`/g, (_match, idx) => { const colors = { 1: '#fff2f0', 2: '#ffccc7', 3: '#ffa39e', 4: '#ff7875', 5: '#ff4d4f', 6: '#f5222d', 7: '#cf1322', 8: '#a8071a', 9: '#820014', 10: '#5c0011' }; return colors[idx] || '#ff4d4f'; }); if (result !== code) { console.log('✅ Replaced colorPalette in:', id); } return result !== code ? result : null; } }; } /** * AntDv4 兼容:将 ele-admin-pro 引用的 AntDv3 样式模块重定向到空模块 * AntDv4 使用 CSS-in-JS,不再有 es/xxx/style 目录 */ function antdStyleRedirect() { const emptyModule = resolve('src/styles/antd-less-stub/empty-style.js'); return { name: 'antd-style-redirect', enforce: 'pre', resolveId(source) { if (source.startsWith('ant-design-vue/es/') && source.endsWith('/style')) { return emptyModule; } return null; } }; } // 简化的智能端口管理(避免构建时模块解析问题) function getSmartPort() { try { const basePort = parseInt(process.env.VITE_BASE_PORT || '3000'); const tenantId = process.env.VITE_TENANT_ID || '10258'; const environment = process.env.NODE_ENV || 'development'; let recommendedPort = basePort; if (environment === 'development') { const tenantOffset = (parseInt(tenantId) % 1000) * 10; recommendedPort = basePort + tenantOffset; } else if (environment === 'test') { recommendedPort = basePort + 1000; } else if (environment === 'production') { recommendedPort = 8080; } console.log('🎯 智能端口计算:', { environment, tenantId, basePort, recommendedPort }); return recommendedPort; } catch (error) { console.warn('⚠️ 端口计算失败,使用默认端口 3000:', error); return 3000; } } export default defineConfig(({ command }) => { const isBuild = command === 'build'; const smartPort = !isBuild ? getSmartPort() : undefined; return { base: '/', resolve: { alias: { '@/': resolve('src') + '/', 'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js', // AntDv4 兼容层:将 ele-admin-pro 引用的 AntD Less 主题文件重定向到 stub 'ant-design-vue/es/style/themes/default.less': resolve('src/styles/antd-less-stub/default.less'), 'ant-design-vue/es/style/themes/dark.less': resolve('src/styles/antd-less-stub/dark.less') } }, server: { port: smartPort || 3000, host: '0.0.0.0', open: true, proxy: { '/api': { target: process.env.VITE_API_URL || 'https://server.websoft.top', changeOrigin: true, secure: false, configure: (proxy, _options) => { proxy.on('error', (err, _req, _res) => { console.log('proxy error', err); }); proxy.on('proxyReq', (proxyReq, req, _res) => { console.log('Sending Request to the Target:', req.method, req.url); }); proxy.on('proxyRes', (proxyRes, req, _res) => { console.log('Received Response from the Target:', proxyRes.statusCode, req.url); }); }, } }, strictPort: false, }, preview: { port: smartPort ? smartPort + 1000 : 4173, host: '0.0.0.0', open: true, strictPort: false, }, plugins: [ colorPaletteReplacer(), antdStyleRedirect(), vue({ script: { defineModel: true, propsDestructure: true } }), ViteComponents({ dts: false, resolvers: [ AntDesignVueResolver({ importStyle: false // AntDv4 使用 CSS-in-JS,无需 Less 导入 }), EleAdminResolver({ importStyle: isBuild ? 'less' : false }) ], directoryAsNamespace: true }), ViteCompression({ disable: !isBuild, threshold: 10240, algorithm: 'gzip', ext: '.gz' }), ViteCompression({ disable: !isBuild, threshold: 10240, algorithm: 'brotliCompress', ext: '.br' }), isBuild && visualizer({ filename: 'dist/stats.html', open: false, gzipSize: true, brotliSize: true }) ].filter(Boolean), css: { preprocessorOptions: { less: { javascriptEnabled: true, modifyVars: { 'style-entry-file': isBuild ? 'as-needed' : 'global-import' } } } }, optimizeDeps: { include: [ 'sortablejs', 'vuedraggable', 'echarts/core', 'echarts/charts', 'echarts/renderers', 'echarts/components', 'vue-echarts', 'echarts-wordcloud', 'xlsx', 'lodash-es', 'dayjs', 'crypto-js', 'js-md5', 'qrcode', 'nprogress' ], exclude: ['@iconify/json', 'tinymce'] }, build: { target: 'es2015', cssCodeSplit: true, chunkSizeWarningLimit: 1000, rollupOptions: { output: { manualChunks(id) { if (!id.includes('node_modules')) return undefined; if (id.includes('/vue-router/') || id.includes('/pinia/') || id.includes('/vue/')) { return 'vue-vendor'; } if (id.includes('/ant-design-vue/') || id.includes('/ele-admin-pro/')) { return 'ui-vendor'; } if (id.includes('/lodash-es/') || id.includes('/dayjs/') || id.includes('/crypto-js/') || id.includes('/js-md5/')) { return 'utils-vendor'; } if (id.includes('/echarts/') || id.includes('/vue-echarts/') || id.includes('/echarts-wordcloud/')) { return 'charts-vendor'; } if (id.includes('/tinymce/') || id.includes('/bytemd/') || id.includes('/md-editor-v3/')) { return 'editor-vendor'; } if (id.includes('/xlsx/') || id.includes('/exceljs/') || id.includes('/file-saver/') || id.includes('/ali-oss/')) { return 'file-vendor'; } return undefined; }, chunkFileNames: (chunkInfo) => { const name = chunkInfo.facadeModuleId ? chunkInfo.facadeModuleId.split('/').pop().replace(/\.\w+$/, '') : 'chunk'; return `js/${name}-[hash].js`; }, assetFileNames: (assetInfo) => { const name = (assetInfo.name || '').split('/').pop(); if (/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/i.test(name)) { return `media/[hash].[ext]`; } if (/\.(png|jpe?g|gif|svg)(\?.*)?$/.test(name)) { return `images/[hash].[ext]`; } if (/\.(woff2?|eot|ttf|otf)(\?.*)?$/i.test(name)) { return `fonts/[hash].[ext]`; } return `assets/[hash].[ext]`; } } }, minify: 'terser', terserOptions: { compress: { drop_console: true, drop_debugger: true, pure_funcs: ['console.log'] } } } }; });