diff --git a/.env.base b/.env.base deleted file mode 100644 index 47bf9f9..0000000 --- a/.env.base +++ /dev/null @@ -1,7 +0,0 @@ -# 正式环境 - base -VITE_BASE_HOST=https://base.bestsch.com -VITE_SSO_HOST=http://sso.yunhaixingtong.com -VITE_SERVICE_HOST=https://base.bestsch.com/LearnCenter -VITE_ZIYUANKU_HOST=https://base.bestsch.com/BschZyk -VITE_ZHUANXIANG_HOST=https://base.bestsch.com/BschZncpd -VITE_IS_MOCK=false diff --git a/.env.dev b/.env.dev index a05a62f..3aeb063 100644 --- a/.env.dev +++ b/.env.dev @@ -1,4 +1,6 @@ -# 开发环境 (dev) +# 开发环境 (dev):仅 vite dev 加载(--mode dev)。值可自由指本地或线上地址。 +# build 产物不读此文件(build 默认 production mode,只认 .env / .env.production,两者均不存在), +# 线上环境差异全部由 projman 的 env-config-{目标}.json 承担。 VITE_BASE_HOST=http://base.bestsch.com VITE_SSO_HOST=http://sso.yunhaixingtong.com VITE_SERVICE_HOST=http://localhost:8060 diff --git a/.env.ld b/.env.ld deleted file mode 100644 index 92de76c..0000000 --- a/.env.ld +++ /dev/null @@ -1,7 +0,0 @@ -# 正式环境 - ld -VITE_BASE_HOST=https://ld.bestsch.com -VITE_SSO_HOST=http://sso.yunhaixingtong.com -VITE_SERVICE_HOST=https://ld.bestsch.com/LearnCenter -VITE_ZIYUANKU_HOST=https://ld.bestsch.com/BschZyk -VITE_ZHUANXIANG_HOST=https://ld.bestsch.com/BschZncpd -VITE_IS_MOCK=false diff --git a/.env.prod b/.env.prod deleted file mode 100644 index 0803e53..0000000 --- a/.env.prod +++ /dev/null @@ -1,7 +0,0 @@ -# 正式环境 - 云海星通(部署在 base.yunhaixingtong.com/userbase) -VITE_BASE_HOST=http://base.yunhaixingtong.com -VITE_SSO_HOST=http://sso.yunhaixingtong.com -VITE_SERVICE_HOST=http://base.yunhaixingtong.com/userbase -VITE_ZIYUANKU_HOST=https://base.bestsch.com/BschZyk -VITE_ZHUANXIANG_HOST=https://base.bestsch.com/BschZncpd -VITE_IS_MOCK=false diff --git a/config/index.js b/config/index.js deleted file mode 100644 index ccebf6a..0000000 --- a/config/index.js +++ /dev/null @@ -1,6 +0,0 @@ -//铃铛debug -window.conf = { - baseHost: "http://basedebug.bestsch.com", - ssoHost: "http://ssodebug.bestsch.com", - serviceHost: "http://basedebug.bestsch.com/QualityEvaluate", -}; diff --git a/index.html b/index.html index 9c0b251..e5b0241 100644 --- a/index.html +++ b/index.html @@ -6,24 +6,24 @@ diff --git a/package.json b/package.json index df221b1..0094dbb 100644 --- a/package.json +++ b/package.json @@ -6,10 +6,7 @@ "type": "module", "scripts": { "dev": "vite --mode dev", - "build": "vite build --mode base", - "build:base": "vite build --mode base", - "build:ld": "vite build --mode ld", - "build:prod": "vite build --mode prod", + "build": "vite build", "preview": "vite preview", "api:gen": "alova gen -f" }, diff --git a/src/assets/config/host.config.ts b/src/assets/config/host.config.ts index 7c0b794..cf620db 100644 --- a/src/assets/config/host.config.ts +++ b/src/assets/config/host.config.ts @@ -2,13 +2,51 @@ import { BaseConf } from "@/types/Basics"; /** * 配置优先级(高 → 低): - * ① window.__APP_CONFIG__:运行时配置,index.html 引导脚本在应用模块加载前同步拉取 - * /<应用>/<前端目标>/api/env-config(projman 配置管理在线维护、双写 Nacos)写入 - * ② Vite 环境变量(构建期,定义在 .env.dev / .env.base / .env.ld 中):接口拉取失败/本地开发时兜底 + * ① window.__APP_CONFIG__:线上运行时配置,index.html 引导脚本在应用模块加载前同步拉取 + * /<应用>/<前端目标>/api/env-config?target=xxx(projman 配置管理在线维护、双写 Nacos)写入 + * ② import.meta.env.VITE_*:仅本地开发(vite dev --mode dev 读 .env.dev)——值可自由指本地 + * 或线上地址,供联调切换;build(production mode)时 .env/.env.production 均不存在, + * VITE_* 恒为 undefined,本层自动跳过,产物不含任何环境值 + * ③ 同域推导兜底:env-config 拉取失败(线上后端/Nacos 不可用)时框架至少能渲染; + * ssoHost/relayConf 为空 → 登录与跨域中转不可用——与后端不可用状态一致,可接受。 */ const env = import.meta.env; +// ③ 同域推导兜底:context 段取当前路径首段(/userbase/front/ → userbase),与服务端 context-path 对齐 +const ctxSeg = (window.location.pathname.split("/").filter(Boolean)[0] || "") as string; +const origin = window.location.origin; + let conf: BaseConf = { + baseHost: origin, + ssoHost: "", + serviceHost: ctxSeg ? `${origin}/${ctxSeg}` : origin, + relayConf: { + ziyuankuHost: "", + zhuanxiangHost: "", + }, + isMock: false, + isDebug: env.DEV, +}; + +// 逐键覆盖:只覆盖来源里实际出现且非空的键(含 relayConf 二级逐键合并),未出现的键沿用底层值 +const applyConf = (target: any, source: any) => { + if (!source || typeof source !== "object") return; + const { relayConf, ...flat } = source; + Object.keys(flat).forEach(key => { + if (flat[key] !== undefined && flat[key] !== null && flat[key] !== "") { + (target as any)[key] = flat[key]; + } + }); + if (relayConf && typeof relayConf === "object") { + if (!target.relayConf || typeof target.relayConf !== "object") target.relayConf = {}; + Object.keys(relayConf).forEach(key => { + if (relayConf[key]) target.relayConf[key] = relayConf[key]; + }); + } +}; + +// ② 本地开发(.env.dev;构建产物中恒 undefined → 自动跳过) +applyConf(conf, { baseHost: env.VITE_BASE_HOST, ssoHost: env.VITE_SSO_HOST, serviceHost: env.VITE_SERVICE_HOST, @@ -17,28 +55,13 @@ let conf: BaseConf = { zhuanxiangHost: env.VITE_ZHUANXIANG_HOST, }, isMock: env.VITE_IS_MOCK === "true", - isDebug: env.DEV, -}; +}); -// 运行时配置覆盖:只覆盖运行时配置里实际出现的键(含 relayConf 逐键合并),未配置的键沿用构建期值 -const runtime = (window as any).__APP_CONFIG__; -if (runtime && typeof runtime === "object") { - const { relayConf, ...flat } = runtime; - Object.keys(flat).forEach(key => { - if (flat[key] !== undefined && flat[key] !== null && flat[key] !== "") { - (conf as any)[key] = flat[key]; - } - }); - if (relayConf && typeof relayConf === "object") { - Object.keys(relayConf).forEach(key => { - if (relayConf[key]) (conf.relayConf as any)[key] = relayConf[key]; - }); - } -} +// ① 线上运行时配置(最高优先,覆盖前面所有) +applyConf(conf, (window as any).__APP_CONFIG__); // HTTPS 自动升级:当前页面是 https 时,将所有 http:// 配置项升级为 https:// -var ishttps = "https:" == document.location.protocol ? true : false; -if (ishttps) { +if ("https:" === document.location.protocol) { Object.keys(conf).forEach(element => { if (typeof conf[element] === "string" && conf[element].indexOf("http://") > -1) { conf[element] = conf[element].replace("http://", "https://"); diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index d378dbe..bebc7b2 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -1,5 +1,8 @@ /// +// import.meta.env 类型声明:VITE_* 仅供本地开发(.env.dev,vite dev --mode dev)读取; +// build(production mode)无 .env/.env.production,VITE_* 恒为 undefined,不会进构建产物。 +// 线上环境差异由 env-config 运行时配置承担(见 host.config.ts)。 interface ImportMetaEnv { readonly VITE_BASE_HOST: string; readonly VITE_SSO_HOST: string; diff --git a/vite.config.ts b/vite.config.ts index dc1542a..39d40a8 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,4 +1,4 @@ -import { ConfigEnv, UserConfig, defineConfig } from "vite"; +import { defineConfig } from "vite"; import { createHtmlPlugin } from "vite-plugin-html"; import vue from "@vitejs/plugin-vue"; import path, { resolve } from "path"; @@ -11,136 +11,135 @@ import presetUno from "@unocss/preset-uno"; import presetRemToPx from "@unocss/preset-rem-to-px"; -export default defineConfig(({ command, mode }: ConfigEnv): UserConfig => { - // 构建输出目录:base -> dist/base, ld -> dist/ld, prod -> dist/prod, 默认 dist/base - const subDir = mode === "ld" || mode === "prod" ? mode : "base"; - const outDir = command === "build" ? `dist/${subDir}` : "dist"; - - return { - plugins: [ - vue(), - createSvgIconsPlugin({ - iconDirs: [resolve(process.cwd(), "src/assets/icons")], - symbolId: "icon-[dir]-[name]", // symbol的id - inject: "body-last", // 插入的位置 - customDomId: "__svg__icons__dom__", // svg的id - }), - viteMockServe({ - mockPath: "./src/assets/mock", - }), - ViteAutoImport({ - imports: [ - "vue", - "vue-router", - { - "view-ui-plus": ["Button", "Table", "Modal", "Message", "Form", "FormItem", "Input", "Select"], - }, - { - from: "alova/client", - imports: ["usePagination", "useRequest", "useWatcher", "useForm"], - }, - ], - dts: "./src/auto-imports.d.ts", - dirs: ["src/api"], // 自动导入 hooks - }), - createHtmlPlugin({ - minify: true, - pages: [ - { - entry: "src/main.ts", - template: "index.html", - filename: "index.html", - injectOptions: { - data: { - title: "用户中心", - injectScript: ``, - }, +// 构建输出统一 dist:不再按 mode 出子目录(base/ld/prod)。 +// 环境差异全部由「部署目录名(即 env-config 的 target)+ projman 运行时配置」承担: +// dev 读 .env.dev(import.meta.env.VITE_*);build(默认 production mode)时 .env/.env.production +// 均不存在 → VITE_* 全为 undefined,host.config.ts 自动跳过 → 线上仅走 env-config 接口。 +// 注意:勿新建 .env.production / .env,否则值会烧进构建产物、污染线上兜底。 +export default defineConfig({ + plugins: [ + vue(), + createSvgIconsPlugin({ + iconDirs: [resolve(process.cwd(), "src/assets/icons")], + symbolId: "icon-[dir]-[name]", // symbol的id + inject: "body-last", // 插入的位置 + customDomId: "__svg__icons__dom__", // svg的id + }), + viteMockServe({ + mockPath: "./src/assets/mock", + }), + ViteAutoImport({ + imports: [ + "vue", + "vue-router", + { + "view-ui-plus": ["Button", "Table", "Modal", "Message", "Form", "FormItem", "Input", "Select"], + }, + { + from: "alova/client", + imports: ["usePagination", "useRequest", "useWatcher", "useForm"], + }, + ], + dts: "./src/auto-imports.d.ts", + dirs: ["src/api"], // 自动导入 hooks + }), + createHtmlPlugin({ + minify: true, + pages: [ + { + entry: "src/main.ts", + template: "index.html", + filename: "index.html", + injectOptions: { + data: { + title: "用户中心", + injectScript: ``, }, }, - ], - }), - UnoCSS({ - presets: [ - presetUno(), // 核心预设(Tailwind风格) - presetRemToPx({ baseFontSize: 4 }), // 1单位=1px(去rem化) - ], - shortcuts: { - // 布局 - "flex-center": "flex items-center justify-center", - // 常用组件 - btn: "px-16px py-8px rounded-4px cursor-pointer transition-all duration-200", - "btn-primary": "bg-blue-600 text-white hover:bg-blue-700", - card: "bg-white rounded-8px shadow-md p-16px", - // 文本截断 - "text-ellipsis": "truncate", - "text-multi-ellipsis": "line-clamp-2", }, - theme: { - fontSize: { - xs: "12px", - sm: "14px", - base: "16px", - lg: "18px", - xl: "20px", - "2xl": "24px", - }, - }, - }), - ], - resolve: { - alias: { - "@": path.resolve(__dirname, "./src"), + ], + }), + UnoCSS({ + presets: [ + presetUno(), // 核心预设(Tailwind风格) + presetRemToPx({ baseFontSize: 4 }), // 1单位=1px(去rem化) + ], + shortcuts: { + // 布局 + "flex-center": "flex items-center justify-center", + // 常用组件 + btn: "px-16px py-8px rounded-4px cursor-pointer transition-all duration-200", + "btn-primary": "bg-blue-600 text-white hover:bg-blue-700", + card: "bg-white rounded-8px shadow-md p-16px", + // 文本截断 + "text-ellipsis": "truncate", + "text-multi-ellipsis": "line-clamp-2", }, - }, - server: { - host: "localhost", - port: 8130, - // proxy: { - // // //api是自行设置的请求前缀,任何请求路径以/api开头的请求将被代理到对应的target目标 - // // "/api": { - // // target: "http://localhost:8064/", - // // // target: "http://debugold.hongyucloud.com/ChildOptCourse", - // // changeOrigin: true, //需要代理跨域 - // // rewrite: (path: any) => path.replace(/^\/api/, "/api/"), //路径重写,把'/api'替换为'' - // // }, - // }, - }, - // css预处理器 - css: { - // preprocessorOptions: { - // scss: { - // additionalData: '@import "./src/assets/styles/utils/_varible.scss";', - // }, - // }, - }, - base: "./", - build: { - target: "modules", //设置最终构建的浏览器兼容目标 //es2015(编译成es5) | modules - outDir, // 构建输出目录,根据环境变量区分 base/ld - assetsDir: "assets", // 静态资源得存放路径文件名 assets - sourcemap: false, //构建后是否生成 source map 文件 - minify: "esbuild", // 项目压缩 :boolean | 'terser' | 'esbuild' - chunkSizeWarningLimit: 1000, //chunk 大小警告的限制(以 kbs 为单位)默认:500 - cssTarget: "chrome61", //防止 vite 将 rgba() 颜色转化为 #RGBA 十六进制符号的形式 (要兼容的场景是安卓微信中的 webview 时,它不支持 CSS 中的 #RGBA 十六进制颜色符号) - rollupOptions: { - input: { - main: resolve("index.html"), + theme: { + fontSize: { + xs: "12px", + sm: "14px", + base: "16px", + lg: "18px", + xl: "20px", + "2xl": "24px", }, - output: { - chunkFileNames: "static/js/[name]-[hash].js", - entryFileNames: "static/js/[name]-[hash].js", - assetFileNames: (assetInfo: any) => { - if (assetInfo.type === "asset" && /\.(jpe?g|png|gif|svg)$/i.test(assetInfo.name)) { - return "static/img/[name].[hash][ext]"; - } - if (assetInfo.type === "asset" && /\.(ttf|woff|woff2|eot)$/i.test(assetInfo.name)) { - return "static/fonts/[name].[hash][ext]"; - } - return "static/[ext]/[name]-[hash].[ext]"; - }, - }, - manualEntrypoint: "index.html", }, + }), + ], + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), }, - }; + }, + server: { + host: "localhost", + port: 8130, + // proxy: { + // // //api是自行设置的请求前缀,任何请求路径以/api开头的请求将被代理到对应的target目标 + // // "/api": { + // // target: "http://localhost:8064/", + // // // target: "http://debugold.hongyucloud.com/ChildOptCourse", + // // changeOrigin: true, //需要代理跨域 + // // rewrite: (path: any) => path.replace(/^\/api/, "/api/"), //路径重写,把'/api'替换为'' + // // }, + // }, + }, + // css预处理器 + css: { + // preprocessorOptions: { + // scss: { + // additionalData: '@import "./src/assets/styles/utils/_varible.scss";', + // }, + // }, + }, + base: "./", + build: { + target: "modules", //设置最终构建的浏览器兼容目标 //es2015(编译成es5) | modules + outDir: "dist", // 构建输出目录(统一 dist,不再按环境区分子目录) + assetsDir: "assets", // 静态资源得存放路径文件名 assets + sourcemap: false, //构建后是否生成 source map 文件 + minify: "esbuild", // 项目压缩 :boolean | 'terser' | 'esbuild' + chunkSizeWarningLimit: 1000, //chunk 大小警告的限制(以 kbs 为单位)默认:500 + cssTarget: "chrome61", //防止 vite 将 rgba() 颜色转化为 #RGBA 十六进制符号的形式 (要兼容的场景是安卓微信中的 webview 时,它不支持 CSS 中的 #RGBA 十六进制颜色符号) + rollupOptions: { + input: { + main: resolve("index.html"), + }, + output: { + chunkFileNames: "static/js/[name]-[hash].js", + entryFileNames: "static/js/[name]-[hash].js", + assetFileNames: (assetInfo: any) => { + if (assetInfo.type === "asset" && /\.(jpe?g|png|gif|svg)$/i.test(assetInfo.name)) { + return "static/img/[name].[hash][ext]"; + } + if (assetInfo.type === "asset" && /\.(ttf|woff|woff2|eot)$/i.test(assetInfo.name)) { + return "static/fonts/[name].[hash][ext]"; + } + return "static/[ext]/[name]-[hash].[ext]"; + }, + }, + manualEntrypoint: "index.html", + }, + }, });