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 { DynamicAntdLess } from 'ele-admin-pro/lib/utils/dynamic-theme'; import { resolve } from 'path'; import { visualizer } from 'rollup-plugin-visualizer'; import { splitVendorChunkPlugin } from 'vite'; // 简化的智能端口管理(避免构建时模块解析问题) function getSmartPort() { try { // 从环境变量获取基础配置 const basePort = parseInt(process.env.VITE_BASE_PORT || '3000'); const tenantId = process.env.VITE_TENANT_ID || '10411'; 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 写子路径 base: '/', resolve: { alias: { '@/': resolve('src') + '/', 'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js' } }, // 智能服务器配置 server: { port: smartPort || 3000, host: '0.0.0.0', // 允许外部访问 open: true, // 自动打开浏览器 cors: true, // 启用 CORS // 代理配置 proxy: { '/api': { target: process.env.VITE_API_URL || 'http://192.168.222.129:8000', 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, cors: true, strictPort: false, }, plugins: [ vue({ script: { defineModel: true, propsDestructure: true } }), // 组件按需引入 ViteComponents({ dts: false, resolvers: [ AntDesignVueResolver({ importStyle: isBuild ? 'less' : false }), EleAdminResolver({ importStyle: isBuild ? 'less' : false }) ], directoryAsNamespace: true }), // 代码分割 splitVendorChunkPlugin(), // gzip 压缩 ViteCompression({ disable: !isBuild, threshold: 10240, algorithm: 'gzip', ext: '.gz' }), // brotli 压缩 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, plugins: [new DynamicAntdLess()], 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'] }, build: { target: 'es2015', cssCodeSplit: true, chunkSizeWarningLimit: 1000, rollupOptions: { output: { // 手动分包 manualChunks: { // Vue 生态 'vue-vendor': ['vue', 'vue-router', 'pinia'], // UI 组件库 'ui-vendor': ['ant-design-vue', 'ele-admin-pro'], // 工具库 'utils-vendor': ['lodash-es', 'dayjs', 'crypto-js', 'js-md5'], // 图表库 'charts-vendor': ['echarts', 'vue-echarts', 'echarts-wordcloud'], // 编辑器 'editor-vendor': ['tinymce', 'bytemd', 'md-editor-v3'], // 文件处理 'file-vendor': ['xlsx', 'exceljs', 'file-saver', 'ali-oss'] }, // 文件命名 chunkFileNames: (chunkInfo) => { const facadeModuleId = chunkInfo.facadeModuleId ? chunkInfo.facadeModuleId.split('/').pop().replace(/\.\w+$/, '') : 'chunk'; return `js/${facadeModuleId}-[hash].js`; }, assetFileNames: (assetInfo) => { const info = assetInfo.name.split('.'); const ext = info[info.length - 1]; if (/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/i.test(assetInfo.name)) { return `media/[name]-[hash].${ext}`; } if (/\.(png|jpe?g|gif|svg)(\?.*)?$/.test(assetInfo.name)) { return `images/[name]-[hash].${ext}`; } if (/\.(woff2?|eot|ttf|otf)(\?.*)?$/i.test(assetInfo.name)) { return `fonts/[name]-[hash].${ext}`; } return `assets/[name]-[hash].${ext}`; } } }, // 压缩配置 minify: 'terser', terserOptions: { compress: { drop_console: true, drop_debugger: true, pure_funcs: ['console.log'] } } } }; });