Преглед изворни кода

修改大唐项目现场问题

liujiejie пре 4 недеља
родитељ
комит
39860408fc

+ 28 - 28
.env.jl

@@ -6,19 +6,19 @@ VUE_APP_PROJECT='jl'
 
 # 吉林打包专用
 # VUE_APP_MAPVIEW="http://127.0.0.1:8080/tiles/{z}/{x}/{y}.png"
-VUE_APP_MAPVIEW=/tiles/{z}/{x}/{y}.png
-VUE_APP_UPLOAD="http://127.0.0.1:8080/energy-manage-service/api/check/upload"
-VUE_APP_UPLOAD="http://127.0.0.1:8080/energy-manage-service/api/check/upload"
-VUE_APP_APIPROXY='http://127.0.0.1:8080'
-VUE_APP_MAP='http://127.0.0.1:8080'
-VUE_APP_WZLAPIPROXY='http://127.0.0.1:8080/WindTransDev'
-VUE_APP_ETLAPIPROXY='http://127.0.0.1:8080/WindTransDev'
-VUE_APP_AnalysisMultiAPIPROXY='http://127.0.0.1:8080/AnalysisMulti'
-#自定义算法文佳 目前无法使用,可能是服务未启动
-VUE_APP_sAlgorithmAPIPROXY='http://127.0.0.1:8080'
-VUE_APP_databaseApiAPIPROXY='http://127.0.0.1:8080'
-# #暂时不知下载报告内网dev 环境地址
-VUE_APP_downLoadChartAPIPROXY='http://127.0.0.1:8080'
+# VUE_APP_MAPVIEW=/tiles/{z}/{x}/{y}.png
+# VUE_APP_UPLOAD="http://127.0.0.1:8080/energy-manage-service/api/check/upload"
+# VUE_APP_UPLOAD="http://127.0.0.1:8080/energy-manage-service/api/check/upload"
+# VUE_APP_APIPROXY='http://127.0.0.1:8080'
+# VUE_APP_MAP='http://127.0.0.1:8080'
+# VUE_APP_WZLAPIPROXY='http://127.0.0.1:8080/WindTransDev'
+# VUE_APP_ETLAPIPROXY='http://127.0.0.1:8080/WindTransDev'
+# VUE_APP_AnalysisMultiAPIPROXY='http://127.0.0.1:8080/AnalysisMulti'
+# #自定义算法文佳 目前无法使用,可能是服务未启动
+# VUE_APP_sAlgorithmAPIPROXY='http://127.0.0.1:8080'
+# VUE_APP_databaseApiAPIPROXY='http://127.0.0.1:8080'
+# # #暂时不知下载报告内网dev 环境地址
+# VUE_APP_downLoadChartAPIPROXY='http://127.0.0.1:8080'
 #吉林外网
 
 # VUE_APP_UPLOAD="http://127.0.0.1:8080/energy-manage-service/api/check/upload"
@@ -38,18 +38,18 @@ VUE_APP_downLoadChartAPIPROXY='http://127.0.0.1:8080'
 # VUE_APP_downLoadChartAPIPROXY='http://192.168.50.234:3001'
 # VUE_APP_downLoadChartAPIPROXY='http://0.0.0.0:3001'
 
-#公司 生产配置
-# VUE_APP_MAPVIEW= "http://106.120.102.238:18000/tiles/{z}/{x}/{y}.png"
-# VUE_APP_MAPVIEW=/tiles/{z}/{x}/{y}.png
-# VUE_APP_UPLOAD="http://106.120.102.238:16700/energy-manage-service/api/check/upload"
-# #外网演示环境 26500  外网开发16700 
-# VUE_APP_APIPROXY='http://106.120.102.238:16700'
-# VUE_APP_MAP='http://106.120.102.238:18080'
-# VUE_APP_WZLAPIPROXY='http://106.120.102.238:58880/transDataWeb'
-# VUE_APP_ETLAPIPROXY='http://106.120.102.238:58880/transDataWeb'
-# VUE_APP_AnalysisMultiAPIPROXY='http://106.120.102.238:18000/AnalysisMulti'
-# #自定义算法文佳 目前无法使用,可能是服务未启动
-# VUE_APP_sAlgorithmAPIPROXY='http://106.120.102.238:58880'
-# VUE_APP_databaseApiAPIPROXY='http://106.120.102.238:58880'
-# # #暂时不知下载报告内网dev 环境地址
-# VUE_APP_downLoadChartAPIPROXY='http://106.120.102.238:58880'
+# 公司 生产配置
+VUE_APP_MAPVIEW= "http://106.120.102.238:18000/tiles/{z}/{x}/{y}.png"
+VUE_APP_MAPVIEW=/tiles/{z}/{x}/{y}.png
+VUE_APP_UPLOAD="http://106.120.102.238:16700/energy-manage-service/api/check/upload"
+#外网演示环境 26500  外网开发16700 
+VUE_APP_APIPROXY='http://106.120.102.238:16700'
+VUE_APP_MAP='http://106.120.102.238:18080'
+VUE_APP_WZLAPIPROXY='http://106.120.102.238:58880/transDataWeb'
+VUE_APP_ETLAPIPROXY='http://106.120.102.238:58880/transDataWeb'
+VUE_APP_AnalysisMultiAPIPROXY='http://106.120.102.238:18000/AnalysisMulti'
+#自定义算法文佳 目前无法使用,可能是服务未启动
+VUE_APP_sAlgorithmAPIPROXY='http://106.120.102.238:58880'
+VUE_APP_databaseApiAPIPROXY='http://106.120.102.238:58880'
+# #暂时不知下载报告内网dev 环境地址
+VUE_APP_downLoadChartAPIPROXY='http://106.120.102.238:58880'

+ 17 - 0
public/index.html

@@ -13,6 +13,23 @@
     <!-- 标签标题不在此写死:由下方 runtime-config.js 同步写入 document.title,部署后只改 dist/runtime-config.js -->
     <title></title>
     <script src="<%= BASE_URL %>runtime-config.js"></script>
+    <script>
+      // 首屏同步写入主题,避免 JS 加载完成前缺少主题变量导致的颜色闪烁
+      (function () {
+        try {
+          var fallback = "<%= process.env.VUE_APP_THEM %>";
+          if (!fallback || fallback === "undefined") fallback = "tech-blue";
+          var stored = window.localStorage.getItem("theme") || "";
+          var theme = (stored || fallback).replace(/^["']|["']$/g, "").trim();
+          document.documentElement.setAttribute(
+            "data-theme",
+            theme || "tech-blue"
+          );
+        } catch (e) {
+          document.documentElement.setAttribute("data-theme", "tech-blue");
+        }
+      })();
+    </script>
   </head>
 
   <body>

+ 4 - 42
src/main.js

@@ -3,6 +3,7 @@ import App from "./App.vue";
 import router from "./router";
 import { store } from "./store/index.js";
 import { getEnvDefaultTheme, ensureDefaultTheme } from "./store/themes.js";
+import { applyTheme } from "./utils/theme.js";
 // import "./assets/style/element-variables.module.scss";
 import "./icons/index"; // icon
 
@@ -141,56 +142,17 @@ window.addEventListener("beforeunload", () => {
   store.commit("dragChart/clearchart");
 });
 ensureDefaultTheme(store);
-document.documentElement.setAttribute(
-  "data-theme",
-  store.state.themes.theme || getEnvDefaultTheme(),
-);
+applyTheme(store.state.themes.theme || getEnvDefaultTheme());
 
 new Vue({
   router,
   store,
-  mounted() {
-    this.applyTheme();
-  },
   watch: {
     "$store.state.themes.theme": {
       handler(newTheme, oldTheme) {
-        this.applyTheme(newTheme, oldTheme);
+        if (!newTheme || newTheme === oldTheme) return;
+        applyTheme(newTheme, oldTheme);
       },
-      immediate: true,
-    },
-  },
-  methods: {
-    async applyTheme(theme, oldTheme) {
-      const themeFile = theme || this.$store.state.themes.theme;
-      document.documentElement.setAttribute("data-theme", themeFile);
-      // 动态加载新的主题样式
-      try {
-        // 动态导入 SCSS 文件
-        const theme = await import(`@/themes/${themeFile}.scss`);
-
-        Vue.prototype.$backgroundColor = getComputedStyle(
-          document.documentElement,
-        ).getPropertyValue("--background-color");
-
-        // 移除之前的主题样式
-        let oldThemeStyle = document.getElementById("theme-style");
-        if (oldThemeStyle) {
-          oldThemeStyle.parentNode.removeChild(oldThemeStyle);
-        }
-
-        // 创建新的样式标签并插入到 head 中
-        const style = document.createElement("style");
-        style.id = "theme-style";
-        style.type = "text/css";
-        style.innerHTML = theme.default;
-        document.head.appendChild(style);
-        if (theme !== oldTheme && oldTheme !== undefined) {
-          this.$router.go(0);
-        }
-      } catch (error) {
-        console.error("Failed to load theme", error);
-      }
     },
   },
   render: (h) => h(App),

+ 4 - 2
src/styles/base.css

@@ -129,9 +129,11 @@ small {
   font-size: 80%;
 }
 
+/* 浏览器自动填充会用系统底色强行覆盖 background,只有 box-shadow inset 能盖住它;
+   颜色必须走主题变量,否则非科技蓝主题下自动填充的输入框会显示成科技蓝配色。 */
 input:-webkit-autofill {
-  -webkit-box-shadow: 0 0 0 1000px #000c17 inset !important;
-  -webkit-text-fill-color: #52c8f2 !important;
+  -webkit-box-shadow: 0 0 0 1000px var(--input-bg, #ffffff) inset !important;
+  -webkit-text-fill-color: var(--text-cyan, #303133) !important;
 }
 
 sub,

+ 1 - 0
src/styles/index.scss

@@ -1,4 +1,5 @@
 // @import 'minireset.css/minireset.sass';
+@import "../themes/index.scss";
 @import "./element-ui.scss";
 @import "./tailwind.css";
 @import "./base.css";

+ 1 - 1
src/themes/blue.scss

@@ -2,7 +2,7 @@ $primary-color: #1A3B7F;
 $background-color: #1A3B7F;
 $text-color: #ffffff;
 $bgopacity-color: rgba(0, 187, 212, 0.145);
-:root {
+html[data-theme="blue"] {
   --primary-color: #{$primary-color};
   --background-color: #{$background-color};
   --text-color: #{$text-color};

+ 1 - 1
src/themes/dark.scss

@@ -2,7 +2,7 @@ $primary-color: #607d8b;
 $background-color: #607d8b;
 $text-color: #ffffff;
 $bgopacity-color: #607d8b31;
-:root {
+html[data-theme="dark"] {
   --primary-color: #{$primary-color};
   --background-color: #{$background-color};
   --text-color: #{$text-color};

+ 1 - 1
src/themes/green.scss

@@ -3,7 +3,7 @@ $background-color: #008080;
 $text-color: #ffffff;
 $bgopacity-color: rgba(0, 128, 128, 0.12);
 
-:root {
+html[data-theme="green"] {
   --primary-color: #{$primary-color};
   --background-color: #{$background-color};
   --text-color: #{$text-color};

+ 10 - 0
src/themes/index.scss

@@ -0,0 +1,10 @@
+/**
+ * 所有主题的 CSS 变量在构建期一次性打包,按 html[data-theme] 作用域隔离。
+ * 切换主题只需改写 <html data-theme>,无需动态加载样式或刷新页面。
+ * 每个主题文件内部都重新定义了自己的 $primary-color 等 SCSS 变量,导入顺序不影响结果。
+ */
+@import "./tech-blue.scss";
+@import "./green.scss";
+@import "./light.scss";
+@import "./blue.scss";
+@import "./dark.scss";

+ 1 - 1
src/themes/light.scss

@@ -3,7 +3,7 @@ $background-color: #82780c;
 $text-color: #ffffff;
 $bgopacity-color: #82780c27;
 
-:root {
+html[data-theme="light"] {
   --primary-color: #{$primary-color};
   --background-color: #{$background-color};
   --bgopacity-color: #{$bgopacity-color};

+ 1 - 1
src/themes/tech-blue.scss

@@ -4,7 +4,7 @@ $background-color: #001529;
 $text-color: #ffffff;
 $bgopacity-color: rgba(0, 170, 255, 0.12);
 
-:root {
+html[data-theme="tech-blue"] {
   --primary-color: #1890ff;
   --primary-hover: #40a9ff;
   --background-color: #001529;

+ 2 - 0
src/utils/chartTheme.js

@@ -1,3 +1,5 @@
+export { onThemeChange } from "./theme";
+
 export function readCssVar(name, fallback = "") {
   if (typeof window === "undefined" || typeof document === "undefined") {
     return fallback;

+ 46 - 0
src/utils/theme.js

@@ -0,0 +1,46 @@
+import Vue from "vue";
+
+/**
+ * 主题运行时:所有主题变量已在构建期打包为 html[data-theme] 作用域,
+ * 切换主题只改写根节点属性,样式即时生效,无需加载样式或刷新页面。
+ */
+export const THEME_CHANGE_EVENT = "app-theme-change";
+
+/** 响应式主题状态,供在 JS 中取色的组件用 computed 订阅 */
+export const themeState = Vue.observable({
+  name: "",
+  backgroundColor: "",
+});
+
+function readBackgroundColor() {
+  return getComputedStyle(document.documentElement)
+    .getPropertyValue("--background-color")
+    .trim();
+}
+
+export function applyTheme(theme, previousTheme) {
+  if (!theme) return;
+  document.documentElement.setAttribute("data-theme", theme);
+
+  themeState.name = theme;
+  themeState.backgroundColor = readBackgroundColor();
+  // 兼容历史用法,部分组件仍从原型链上取主色
+  Vue.prototype.$backgroundColor = themeState.backgroundColor;
+
+  if (previousTheme === undefined) return;
+  window.dispatchEvent(
+    new CustomEvent(THEME_CHANGE_EVENT, {
+      detail: { theme, previousTheme },
+    }),
+  );
+}
+
+/**
+ * 供在 JS 中读取 CSS 变量生成配色的图表订阅,返回取消订阅函数。
+ * 组件销毁时必须调用返回值,避免监听器泄漏。
+ */
+export function onThemeChange(handler) {
+  const listener = (event) => handler(event.detail || {});
+  window.addEventListener(THEME_CHANGE_EVENT, listener);
+  return () => window.removeEventListener(THEME_CHANGE_EVENT, listener);
+}

+ 8 - 2
src/views/home/Index.vue

@@ -16,7 +16,7 @@
         <!-- 主体内容-->
         <div class="main-content">
           <transition name="slide-fade">
-            <router-view />
+            <router-view :key="currentTheme" />
           </transition>
         </div>
       </el-main>
@@ -30,7 +30,7 @@
         <!-- 主体内容-->
         <div class="main-content">
           <transition name="slide-fade">
-            <router-view />
+            <router-view :key="currentTheme" />
           </transition>
         </div>
       </el-main>
@@ -59,6 +59,12 @@ export default {
       loading: false,
     };
   },
+  computed: {
+    // 主题变更时重挂业务页,让 JS 中按 CSS 变量取色的图表重新生成配置
+    currentTheme() {
+      return this.$store.state.themes.theme;
+    },
+  },
   methods: {
     setLoding(state) {
       this.loading = state;

+ 4 - 2
src/views/home/components/Menu.vue

@@ -105,7 +105,7 @@
 
 <script>
 import { mapActions, mapState } from "vuex";
-import Vue from "vue";
+import { themeState } from "@/utils/theme";
 
 export default {
   data() {
@@ -116,7 +116,6 @@ export default {
       defaultActive: this.$route.fullPath,
       activeIndex: false,
       keyObject: {},
-      activeTextColor: Vue.prototype.$backgroundColor,
       routerList: [
         {
           id: 1,
@@ -138,6 +137,9 @@ export default {
     ...mapState({
       currentMenuIndex: (state) => state.breadStore?.currentUrl?.routeUrl,
     }),
+    activeTextColor() {
+      return themeState.backgroundColor;
+    },
     allMenuIndexes() {
       return this.routerList.map((item) =>
         item.children && item.children.length && item.meta?.hidden === false

+ 4 - 2
src/views/home/components/MenuHD.vue

@@ -91,7 +91,7 @@
 <script>
 import { mapActions, mapState } from "vuex";
 import { orgList } from "./mockData";
-import Vue from "vue";
+import { themeState } from "@/utils/theme";
 
 export default {
   data() {
@@ -104,7 +104,6 @@ export default {
       defaultActive: this.$route.fullPath,
       activeIndex: false,
       keyObject: {},
-      activeTextColor: Vue.prototype.$backgroundColor,
       routerList: [
         {
           id: 1,
@@ -128,6 +127,9 @@ export default {
     ...mapState({
       currentMenuIndex: (state) => state.breadStore?.currentUrl?.routeUrl,
     }),
+    activeTextColor() {
+      return themeState.backgroundColor;
+    },
     // 获取所有父级菜单的 index
     allMenuIndexes() {
       return this.routerList.map((item) => {

+ 113 - 21
src/views/login/Index.vue

@@ -141,6 +141,27 @@ export default {
   --login-text-title: #52c8f2;
   --login-panel-bg: rgba(0, 12, 23, 0.88);
   --login-panel-border: rgba(0, 170, 255, 0.45);
+  --login-bg-tail: #000a18;
+  --login-grid-color: rgba(0, 170, 255, 0.05);
+  --login-glow-primary: rgba(24, 144, 255, 0.14);
+  --login-glow-secondary: rgba(0, 80, 160, 0.06);
+  --login-corner-border: rgba(0, 170, 255, 0.22);
+  --login-panel-shadow: 0 0 24px rgba(0, 120, 255, 0.18);
+  --login-panel-inset: inset 0 0 40px rgba(0, 40, 100, 0.12);
+  --login-title-shadow: 0 0 20px rgba(0, 170, 255, 0.25);
+  --login-divider-shadow: 0 0 8px rgba(0, 170, 255, 0.45);
+  // --login-input-bg: rgba(0, 12, 23, 0.95);
+  --login-placeholder: rgba(183, 223, 245, 0.38);
+  --login-input-focus-border: rgba(0, 170, 255, 0.75);
+  --login-input-focus-shadow: 0 0 0 1px rgba(0, 170, 255, 0.2);
+  --login-input-icon: rgba(0, 170, 255, 0.65);
+  --login-input-suffix-icon: rgba(0, 170, 255, 0.55);
+  --login-button-border: #177ddc;
+  --login-button-bg-start: #1d8cff;
+  --login-button-bg-end: #177ddc;
+  --login-button-hover-start: #40a9ff;
+  --login-button-hover-end: #1d8cff;
+  --login-button-shadow: 0 4px 14px rgba(24, 144, 255, 0.28);
 
   position: relative;
   width: 100vw;
@@ -153,7 +174,7 @@ export default {
     155deg,
     var(--login-bg-deep) 0%,
     var(--login-bg-mid) 42%,
-    #000a18 100%
+    var(--login-bg-tail) 100%
   );
 
   &::before {
@@ -161,10 +182,10 @@ export default {
     position: absolute;
     inset: 0;
     background-image: linear-gradient(
-        rgba(0, 170, 255, 0.05) 1px,
+        var(--login-grid-color) 1px,
         transparent 1px
       ),
-      linear-gradient(90deg, rgba(0, 170, 255, 0.05) 1px, transparent 1px);
+      linear-gradient(90deg, var(--login-grid-color) 1px, transparent 1px);
     background-size: 56px 56px;
     mask-image: radial-gradient(
       ellipse 85% 75% at 50% 45%,
@@ -184,8 +205,8 @@ export default {
     transform: translate(-50%, -50%);
     background: radial-gradient(
       circle,
-      rgba(24, 144, 255, 0.14) 0%,
-      rgba(0, 80, 160, 0.06) 42%,
+      var(--login-glow-primary) 0%,
+      var(--login-glow-secondary) 42%,
       transparent 68%
     );
     pointer-events: none;
@@ -202,7 +223,7 @@ export default {
   position: absolute;
   width: 120px;
   height: 120px;
-  border: 1px solid rgba(0, 170, 255, 0.22);
+  border: 1px solid var(--login-corner-border);
   opacity: 0.85;
 
   &--tl {
@@ -242,8 +263,7 @@ export default {
   background: var(--login-panel-bg);
   border: 1px solid var(--login-panel-border);
   border-radius: 4px;
-  box-shadow: 0 0 24px rgba(0, 120, 255, 0.18),
-    inset 0 0 40px rgba(0, 40, 100, 0.12);
+  box-shadow: var(--login-panel-shadow), var(--login-panel-inset);
   backdrop-filter: blur(8px);
 
   &::before,
@@ -293,7 +313,7 @@ export default {
   font-weight: 600;
   line-height: 1.45;
   color: var(--login-text-title);
-  text-shadow: 0 0 20px rgba(0, 170, 255, 0.25);
+  text-shadow: var(--login-title-shadow);
 }
 
 .login-panel__divider {
@@ -307,7 +327,7 @@ export default {
     var(--login-accent) 80%,
     transparent
   );
-  box-shadow: 0 0 8px rgba(0, 170, 255, 0.45);
+  box-shadow: var(--login-divider-shadow);
 }
 
 .login-panel__body {
@@ -326,28 +346,28 @@ export default {
   ::v-deep .el-input__inner {
     height: 42px;
     line-height: 42px;
-    background-color: rgba(0, 12, 23, 0.95) !important;
+    background-color: var(--login-input-bg) !important;
     border-color: var(--login-accent-soft) !important;
     color: var(--login-text) !important;
     border-radius: 4px;
     transition: border-color 0.2s, box-shadow 0.2s;
 
     &::placeholder {
-      color: rgba(183, 223, 245, 0.38) !important;
+      color: var(--login-placeholder) !important;
     }
 
     &:focus {
-      border-color: rgba(0, 170, 255, 0.75) !important;
-      box-shadow: 0 0 0 1px rgba(0, 170, 255, 0.2);
+      border-color: var(--login-input-focus-border) !important;
+      box-shadow: var(--login-input-focus-shadow);
     }
   }
 
   ::v-deep .el-input__prefix {
-    color: rgba(0, 170, 255, 0.65);
+    color: var(--login-input-icon);
   }
 
   ::v-deep .el-input__suffix .el-input__icon {
-    color: rgba(0, 170, 255, 0.55);
+    color: var(--login-input-suffix-icon);
   }
 }
 
@@ -358,14 +378,86 @@ export default {
   font-size: 15px;
   letter-spacing: 0.2em;
   border-radius: 4px;
-  border: 1px solid #177ddc !important;
-  background: linear-gradient(180deg, #1d8cff 0%, #177ddc 100%) !important;
-  box-shadow: 0 4px 14px rgba(24, 144, 255, 0.28);
+  border: 1px solid var(--login-button-border) !important;
+  background: linear-gradient(
+    180deg,
+    var(--login-button-bg-start) 0%,
+    var(--login-button-bg-end) 100%
+  ) !important;
+  box-shadow: var(--login-button-shadow);
 
   &:hover,
   &:focus {
-    background: linear-gradient(180deg, #40a9ff 0%, #1d8cff 100%) !important;
-    border-color: #40a9ff !important;
+    background: linear-gradient(
+      180deg,
+      var(--login-button-hover-start) 0%,
+      var(--login-button-hover-end) 100%
+    ) !important;
+    border-color: var(--login-button-hover-start) !important;
+  }
+}
+
+/* 非科技蓝主题采用业务登录页布局与背景图。 */
+html:not([data-theme="tech-blue"]) .login-page {
+  --login-accent: var(--primary-color, #008080);
+  --login-accent-soft: #dcdfe6;
+  --login-text: var(--text-cyan, #303133);
+  --login-text-title: var(--primary-color, #008080);
+  --login-panel-bg: rgba(255, 255, 255, 0.94);
+  --login-panel-border: rgba(0, 128, 128, 0.3);
+  --login-grid-color: rgba(0, 128, 128, 0.06);
+  --login-glow-primary: rgba(0, 128, 128, 0.12);
+  --login-glow-secondary: rgba(0, 128, 128, 0.04);
+  --login-corner-border: rgba(0, 128, 128, 0.22);
+  --login-panel-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
+  --login-panel-inset: inset 0 0 0 transparent;
+  --login-title-shadow: none;
+  --login-divider-shadow: none;
+  --login-input-bg: #ffffff;
+  --login-placeholder: #909399;
+  --login-input-focus-border: var(--primary-color, #008080);
+  --login-input-focus-shadow: 0 0 0 1px
+    var(--bgopacity-color, rgba(0, 128, 128, 0.16));
+  --login-input-icon: var(--primary-color, #008080);
+  --login-input-suffix-icon: #909399;
+  --login-button-border: var(--primary-color, #008080);
+  --login-button-bg-start: var(--primary-color, #008080);
+  --login-button-bg-end: var(--primary-color, #008080);
+  --login-button-hover-start: var(--primary-color, #008080);
+  --login-button-hover-end: var(--primary-color, #008080);
+  --login-button-shadow: 0 4px 12px
+    var(--bgopacity-color, rgba(0, 128, 128, 0.2));
+
+  background: #f5f7fa url("../../assets/login-img.png") center / cover no-repeat;
+}
+
+html:not([data-theme="tech-blue"]) .login-page::before,
+html:not([data-theme="tech-blue"]) .login-page::after,
+html:not([data-theme="tech-blue"]) .login-page__decor {
+  display: none;
+}
+
+html:not([data-theme="tech-blue"]) .login-panel {
+  backdrop-filter: none;
+}
+
+html:not([data-theme="tech-blue"]) .login-panel::before,
+html:not([data-theme="tech-blue"]) .login-panel::after {
+  display: none;
+}
+
+html:not([data-theme="tech-blue"]) .login-panel__divider {
+  width: 100%;
+  background: var(--primary-color, #008080);
+  box-shadow: none;
+}
+
+/* 提升权重,避免全局 Element UI 规则覆盖登录框底色。 */
+html:not([data-theme="tech-blue"]) .login-form {
+  ::v-deep .el-input__inner {
+    background-color: var(--login-input-bg) !important;
+    border-color: var(--login-accent-soft) !important;
+    color: var(--login-text) !important;
   }
 }
 </style>

+ 11 - 6
vue.config.js

@@ -52,12 +52,17 @@ module.exports = {
     //   },
     // ]);
 
-    config.plugin("workbox").use(WorkboxPlugin.GenerateSW, [
-      {
-        clientsClaim: true,
-        skipWaiting: true,
-      },
-    ]);
+    // 仅生产构建注册 Service Worker:dev server(--watch)下 GenerateSW 重复调用
+    // 存在已知问题——首次生成的 precache 版本号后续不会正确更新,会导致浏览器
+    // 一直用旧缓存响应请求,改代码、重启 dev server、强制刷新都不生效。
+    if (process.env.NODE_ENV === "production") {
+      config.plugin("workbox").use(WorkboxPlugin.GenerateSW, [
+        {
+          clientsClaim: true,
+          skipWaiting: true,
+        },
+      ]);
+    }
   },
 
   css: {