服务打包初步结构

This commit is contained in:
2026-01-02 14:56:14 +08:00
parent 19026c1b30
commit 89bc8bf1d4
77 changed files with 5290 additions and 2070 deletions

View File

@@ -1,24 +1,22 @@
/**
* @description 应用运行时配置文件
* @author yslg
* @since 2025-12-06
* @description 应用运行时配置文件 (支持 Docker 环境变量替换)
*
* 说明:
* 1. 此文件在生产环境中被加载,用于覆盖内置配置
* 2. Docker 部署时,可通过挂载此文件来修改配置,无需重新构建
* 3. 配置结构必须与 packages/shared/src/config/index.ts 中的 AppRuntimeConfig 保持一致
* 占位符说明:
* - __PLACEHOLDER__ 格式的值会在 Docker 启动时被环境变量替换
* - 如果环境变量未设置,将使用默认值
*
* 使用示例(Docker
* docker run -v /path/to/app-config.js:/app/public/app-config.js my-app:latest
* Docker 部署
* 1. 通过 volume 挂载覆盖此文件
* 2. 或通过启动脚本替换占位符
*/
window.APP_RUNTIME_CONFIG = {
// 环境标识
env: 'production',
env: '__APP_ENV__',
// API 配置
api: {
baseUrl: '/api',
baseUrl: '__API_BASE_URL__',
timeout: 30000
},
@@ -27,12 +25,12 @@ window.APP_RUNTIME_CONFIG = {
// 文件配置
file: {
downloadUrl: '/api/urban-lifeline/file/download/',
uploadUrl: '/api/urban-lifeline/file/upload',
downloadUrl: '__API_BASE_URL__/urban-lifeline/file/download/',
uploadUrl: '__API_BASE_URL__/urban-lifeline/file/upload',
maxSize: {
image: 5, // MB
video: 100, // MB
document: 10 // MB
image: 5,
video: 100,
document: 10
},
acceptTypes: {
image: 'image/*',
@@ -44,20 +42,23 @@ window.APP_RUNTIME_CONFIG = {
// Token 配置
token: {
key: 'token',
refreshThreshold: 300000 // 5分钟
refreshThreshold: 300000
},
// 公共资源路径
publicImgPath: '/img',
publicWebPath: '/',
publicImgPath: '__PUBLIC_PATH__/img',
publicWebPath: '__PUBLIC_PATH__',
// 单点登录配置
sso: {
platformUrl: '/', // platform 平台地址
workcaseUrl: '/workcase', // workcase 服务地址
biddingUrl: '/bidding' // bidding 服务地址
platformUrl: '__SSO_PLATFORM_URL__',
workcaseUrl: '__SSO_WORKCASE_URL__',
biddingUrl: '__SSO_BIDDING_URL__'
},
// AES 加密密钥
aesSecretKey: '__AES_SECRET_KEY__',
// 功能开关
features: {
enableDebug: false,

View File

@@ -1,16 +1,10 @@
/**
* @description Bidding 应用运行时配置
* @author yslg
* @since 2025-12-06
* @description 应用运行时配置
*
* 配置加载策略:
* 1. 开发环境:使用下面定义的开发配置
* 1. 开发环境:使用内置开发配置
* 2. 生产环境:从 window.APP_RUNTIME_CONFIG 读取(来自 app-config.js
* 3. Docker部署替换 app-config.js 文件实现配置外挂
*
* 配置结构说明:
* 此文件的配置结构与 app-config.js 完全对应
* 修改 app-config.js 后,这里的配置会自动应用
* 3. Docker部署启动时替换 app-config.js 中的占位符
*/
// ============================================
@@ -43,12 +37,12 @@ export interface AppRuntimeConfig {
};
publicImgPath: string;
publicWebPath: string;
// 单点登录配置
sso?: {
platformUrl: string; // platform 平台地址
workcaseUrl: string; // workcase 服务地址
biddingUrl: string; // bidding 服务地址
platformUrl: string;
workcaseUrl: string;
biddingUrl: string;
};
aesSecretKey?: string;
features?: {
enableDebug?: boolean;
enableMockData?: boolean;
@@ -57,101 +51,80 @@ export interface AppRuntimeConfig {
}
// ============================================
// 配置定义(与 app-config.js 结构一致)
// 环境检测
// ============================================
const isDev = (import.meta as any).env?.DEV ?? false;
// ============================================
// 开发环境配置
// ============================================
const devConfig: AppRuntimeConfig = {
env: 'development',
api: {
// 开发环境通过 Vite 代理转发到后端,避免浏览器直接跨域
// 实际请求路径示例:/api/... → 由代理转发到实际后端
baseUrl: '/api',
timeout: 30000
},
baseUrl: '/',
file: {
// 通过 Nginx → Gateway 访问文件服务,使用 /urban-lifeline 前缀
downloadUrl: '/api/urban-lifeline/file/download/',
uploadUrl: '/api/urban-lifeline/file/upload',
maxSize: {
image: 5,
video: 100,
document: 10
},
maxSize: { image: 5, video: 100, document: 10 },
acceptTypes: {
image: 'image/*',
video: 'video/*',
document: '.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx'
}
},
token: {
key: 'token',
refreshThreshold: 300000
},
publicImgPath: 'http://localhost:7002/img',
publicWebPath: 'http://localhost:7002',
// 单点登录配置
publicImgPath: '/img',
publicWebPath: '/',
sso: {
platformUrl: '/', // 通过nginx访问platform
workcaseUrl: '/workcase', // 通过nginx访问workcase
biddingUrl: '/bidding' // 通过nginx访问bidding
platformUrl: '/',
workcaseUrl: '/workcase',
biddingUrl: '/bidding'
},
aesSecretKey: 'MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=',
features: {
enableDebug: true,
enableMockData: false
}
};
// ============================================
// 生产环境默认配置(兜底)
// ============================================
const prodDefaultConfig: AppRuntimeConfig = {
env: 'production',
api: {
baseUrl: '/api',
timeout: 30000
},
baseUrl: '/',
file: {
downloadUrl: '/api/urban-lifeline/file/download/',
uploadUrl: '/api/urban-lifeline/file/upload',
maxSize: {
image: 5,
video: 100,
document: 10
},
maxSize: { image: 5, video: 100, document: 10 },
acceptTypes: {
image: 'image/*',
video: 'video/*',
document: '.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx'
}
},
token: {
key: 'token',
refreshThreshold: 300000
},
publicImgPath: '/img',
publicWebPath: '/',
// 单点登录配置生产环境通过nginx代理
sso: {
platformUrl: '/',
workcaseUrl: '/workcase',
biddingUrl: '/bidding'
},
aesSecretKey: 'MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=',
features: {
enableDebug: false,
enableMockData: false
@@ -162,92 +135,82 @@ const prodDefaultConfig: AppRuntimeConfig = {
// 配置加载
// ============================================
/**
* 检查值是否为未替换的占位符
*/
const isPlaceholder = (value: any): boolean => {
return typeof value === 'string' && value.startsWith('__') && value.endsWith('__');
};
/**
* 深度合并配置,跳过占位符值
*/
const mergeConfig = (target: any, source: any): any => {
const result = { ...target };
for (const key in source) {
const value = source[key];
if (value && typeof value === 'object' && !Array.isArray(value)) {
result[key] = mergeConfig(target[key] || {}, value);
} else if (!isPlaceholder(value)) {
result[key] = value;
}
}
return result;
};
/**
* 获取运行时配置
* 生产环境优先从 window.APP_RUNTIME_CONFIG 读取app-config.js 注入)
*/
const getRuntimeConfig = (): AppRuntimeConfig => {
if (isDev) {
console.log('[配置] 开发环境,使用内置配置');
console.log('[Config] 开发环境,使用内置配置');
return devConfig;
}
// 生产环境:尝试读取外部配置
try {
const runtimeConfig = (window as any).APP_RUNTIME_CONFIG;
if (runtimeConfig && typeof runtimeConfig === 'object') {
console.log('[配置] 加载外部配置 app-config.js');
console.log('[配置] API地址:', runtimeConfig.api?.baseUrl);
console.log('[配置] 环境:', runtimeConfig.env || 'production');
return runtimeConfig as AppRuntimeConfig;
const merged = mergeConfig(prodDefaultConfig, runtimeConfig);
console.log('[Config] 加载运行时配置', merged);
return merged;
}
} catch (e) {
console.warn('[配置] 无法读取外部配置,使用默认配置', e);
console.warn('[Config] 无法读取外部配置', e);
}
console.log('[配置] 使用默认生产配置');
console.log('[Config] 使用默认生产配置');
return prodDefaultConfig;
};
// 当前应用配置
// 当前配置
const config = getRuntimeConfig();
console.log('[配置] 当前配置', config);
// ============================================
// 导出配置(向后兼容)
// 导出
// ============================================
// 单独导出常用配置项
// AES 密钥
export const AES_SECRET_KEY = config.aesSecretKey || 'MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=';
// 常用配置项
export const API_BASE_URL = config.api.baseUrl;
export const FILE_DOWNLOAD_URL = config.file.downloadUrl;
export const FILE_UPLOAD_URL = config.file.uploadUrl;
export const PUBLIC_IMG_PATH = config.publicImgPath;
export const PUBLIC_WEB_PATH = config.publicWebPath;
// 导出完整配置对象
// 完整配置对象
export const APP_CONFIG = {
// 应用标题
title: '泰豪电源招投标系统',
// 环境标识
env: config.env || 'production',
// 应用基础路径
baseUrl: config.baseUrl,
// API 配置
api: {
baseUrl: config.api.baseUrl,
timeout: config.api.timeout
},
// 文件配置
file: {
downloadUrl: config.file.downloadUrl,
uploadUrl: config.file.uploadUrl,
maxSize: config.file.maxSize,
acceptTypes: config.file.acceptTypes
},
// Token 配置
token: {
key: config.token.key,
refreshThreshold: config.token.refreshThreshold
},
// 公共路径
api: config.api,
file: config.file,
token: config.token,
publicImgPath: config.publicImgPath,
publicWebPath: config.publicWebPath,
// 单点登录配置
sso: config.sso || {
platformUrl: '/',
workcaseUrl: '/workcase',
biddingUrl: '/bidding'
},
// 功能开关
sso: config.sso || { platformUrl: '/', workcaseUrl: '/workcase', biddingUrl: '/bidding' },
features: config.features || {}
};
// 默认导出
export default APP_CONFIG;

View File

@@ -1,6 +1,7 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import { federation } from '@module-federation/vite'
import { resolve, dirname } from 'path'
import { fileURLToPath } from 'url'
import fs from 'fs'
@@ -8,61 +9,95 @@ import fs from 'fs'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
export default defineConfig(({ mode }) => ({
// 开发和生产环境都通过nginx代理访问/bidding
base: '/bidding/',
plugins: [
vue({
script: {
defineModel: true,
propsDestructure: true
}
}),
vueJsx()
],
define: {
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: true,
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: true
},
resolve: {
alias: {
'@': resolve(__dirname, 'src')
}
},
server: {
port: 7002,
host: true,
cors: true,
open: '/bidding/', // 开发时自动打开到 /bidding/ 路径
// HTTPS 配置(使用 mkcert 生成的本地开发证书)
https: {
key: fs.readFileSync('C:/Users/FK05/443/localhost+3-key.pem'),
cert: fs.readFileSync('C:/Users/FK05/443/localhost+3.pem')
// 开发环境 shared 模块地址
const DEV_SHARED_URL = 'https://localhost:7000/shared/mf-manifest.json'
// 生产环境使用相对路径,通过 Nginx 代理访问
const PROD_SHARED_URL = '/shared/mf-manifest.json'
export default defineConfig(({ mode }) => {
const isDev = mode === 'development'
const sharedEntry = isDev ? DEV_SHARED_URL : PROD_SHARED_URL
return {
base: '/bidding/',
plugins: [
vue({
script: {
defineModel: true,
propsDestructure: true
}
}),
vueJsx(),
federation({
name: 'bidding',
remotes: {
shared: {
type: 'module',
name: 'shared',
entry: sharedEntry
}
},
shared: {
vue: {},
'vue-router': {},
'element-plus': {},
axios: {}
}
})
],
define: {
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: true,
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: true
},
proxy: {
'/api': {
target: 'http://localhost:8180',
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/api/, '')
resolve: {
alias: {
'@': resolve(__dirname, 'src')
}
}
},
build: {
outDir: 'dist',
sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
'vue-vendor': ['vue', 'vue-router', 'pinia'],
'element-plus': ['element-plus']
},
server: {
port: 7002,
host: true,
cors: true,
open: '/bidding/',
https: (() => {
try {
return {
key: fs.readFileSync('C:/Users/FK05/443/localhost+3-key.pem'),
cert: fs.readFileSync('C:/Users/FK05/443/localhost+3.pem')
}
} catch {
return undefined
}
})(),
hmr: {
path: '/@vite/client',
port: 7002
},
proxy: {
'/api': {
target: 'http://localhost:8180',
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/api/, '')
}
}
},
build: {
outDir: 'dist',
sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
'vue-vendor': ['vue', 'vue-router', 'pinia'],
'element-plus': ['element-plus']
}
}
}
}
}
}))
})