- 将 nginx 反向代理路径从 /ollama-proxy 修改为 /proxy - 更新 .env.development 和 .env.example 中的 VITE_OLLAMA_API_URL 配置 - 修改 src/config/setting.ts 中的默认代理路径配置 - 更新 vite.config.ts 中的代理配置路径映射 - 优化代码格式化和多行语句的换行处理 - 调整打包配置中的文件命名逻辑格式
285 lines
8.8 KiB
TypeScript
285 lines
8.8 KiB
TypeScript
import { defineConfig, loadEnv } 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 || '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, mode }) => {
|
|
// Load env for Vite config usage (including non-VITE_ keys like AI_API_KEY).
|
|
const env = loadEnv(mode, process.cwd(), '');
|
|
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:
|
|
env.VITE_API_URL ||
|
|
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
|
|
);
|
|
});
|
|
}
|
|
},
|
|
// OpenAI-compatible gateway reverse proxy (dev only).
|
|
// Example:
|
|
// GET /ai-proxy/models -> https://ai.websoft.top/api/v1/models
|
|
// POST /ai-proxy/chat/completions -> https://ai.websoft.top/api/v1/chat/completions
|
|
'/ai-proxy': {
|
|
target: env.AI_PROXY_TARGET || 'https://ai-api.websoft.top',
|
|
changeOrigin: true,
|
|
secure: false,
|
|
rewrite: (path) =>
|
|
path.replace(
|
|
/^\/ai-proxy/,
|
|
env.AI_PROXY_REWRITE_PREFIX || '/api/v1'
|
|
),
|
|
configure: (proxy) => {
|
|
proxy.on('proxyReq', (proxyReq) => {
|
|
// Inject auth for local dev to avoid putting API keys in the browser.
|
|
const key = env.AI_API_KEY || process.env.AI_API_KEY;
|
|
if (key && !proxyReq.getHeader('Authorization')) {
|
|
const trimmed = String(key).trim();
|
|
const value = /^bearer\\s+/i.test(trimmed)
|
|
? trimmed
|
|
: `Bearer ${trimmed}`;
|
|
proxyReq.setHeader('Authorization', value);
|
|
}
|
|
});
|
|
}
|
|
},
|
|
// Ollama native API reverse proxy (dev only).
|
|
// GET /proxy/api/tags -> http://47.119.165.234:11434/api/tags
|
|
// POST /proxy/api/chat -> http://47.119.165.234:11434/api/chat
|
|
'/proxy': {
|
|
target: 'http://47.119.165.234:11434',
|
|
changeOrigin: true,
|
|
secure: false,
|
|
rewrite: (path) => path.replace(/^\/proxy/, '')
|
|
}
|
|
},
|
|
// 端口冲突时的处理
|
|
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']
|
|
}
|
|
}
|
|
}
|
|
};
|
|
});
|