Ver código fonte

引入ElementUI按需加载,新增临时方案时间选择功能

  - 安装element-ui和babel-plugin-component,配置按需引入DatePicker、TimePicker、Select组件
  - CrossingDetailPanel新增时间栏(日期、时间、时长、周期选择),仅临时方案时显示
  - 设置PopupManager.zIndex=3000解决弹窗被SmartDialog遮挡问题,picker添加append-to-body
  - 添加ElementUI深色主题适配样式(输入框、弹出面板、下拉选项等)
  - 当前阶段时间改为阶段总时长(p[8]),修复过滤条件为p[5]==='green'确保4个阶段都正确显示
  - 添加time-bar的mock数据,applyData方法接收时间栏字段
  - 中心控制改名为中心计划
画安 2 semanas atrás
pai
commit
ec358dc172
7 arquivos alterados com 372 adições e 10 exclusões
  1. 10 1
      babel.config.js
  2. 109 1
      package-lock.json
  3. 2 0
      package.json
  4. 118 3
      src/components/ui/CrossingDetailPanel.vue
  5. 9 0
      src/main.js
  6. 12 5
      src/mock/api.js
  7. 112 0
      src/styles/base.css

+ 10 - 1
babel.config.js

@@ -1,3 +1,12 @@
 module.exports = {
-  presets: ["@vue/cli-plugin-babel/preset"]
+  presets: ["@vue/cli-plugin-babel/preset"],
+  plugins: [
+    [
+      "component",
+      {
+        libraryName: "element-ui",
+        styleLibraryName: "theme-chalk"
+      }
+    ]
+  ]
 };

+ 109 - 1
package-lock.json

@@ -15,6 +15,7 @@
         "core-js": "^3.8.3",
         "echarts": "^5.6.0",
         "echarts-gl": "^2.0.9",
+        "element-ui": "^2.15.14",
         "konva": "^10.2.0",
         "swiper": "^5.4.5",
         "three": "^0.183.1",
@@ -31,6 +32,7 @@
         "@vue/cli-plugin-eslint": "~5.0.0",
         "@vue/cli-service": "~5.0.0",
         "axios-mock-adapter": "^2.1.0",
+        "babel-plugin-component": "^1.1.1",
         "eslint": "^7.32.0",
         "eslint-plugin-vue": "^8.0.3",
         "postcss-pxtorem": "^5.1.1",
@@ -3608,6 +3610,14 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/async-validator": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-1.8.5.tgz",
+      "integrity": "sha512-tXBM+1m056MAX0E8TL2iCjg8WvSyXu0Zc8LNtYqrVeyoL3+esHRZ4SieE9fKQyyU09uONjnMEjrNBMqT0mbvmA==",
+      "dependencies": {
+        "babel-runtime": "6.x"
+      }
+    },
     "node_modules/asynckit": {
       "version": "0.4.0",
       "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
@@ -3695,6 +3705,11 @@
         "axios": ">= 0.17.0"
       }
     },
+    "node_modules/babel-helper-vue-jsx-merge-props": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-2.0.3.tgz",
+      "integrity": "sha512-gsLiKK7Qrb7zYJNgiXKpXblxbV5ffSwR0f5whkPAaBAR4fhi6bwRZxX9wBlIc5M/v8CCkXUbXZL4N/nSE97cqg=="
+    },
     "node_modules/babel-loader": {
       "version": "8.4.1",
       "resolved": "https://registry.npmmirror.com/babel-loader/-/babel-loader-8.4.1.tgz",
@@ -3730,6 +3745,36 @@
         "node": ">=8.9.0"
       }
     },
+    "node_modules/babel-plugin-component": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-component/-/babel-plugin-component-1.1.1.tgz",
+      "integrity": "sha512-WUw887kJf2GH80Ng/ZMctKZ511iamHNqPhd9uKo14yzisvV7Wt1EckIrb8oq/uCz3B3PpAW7Xfl7AkTLDYT6ag==",
+      "dev": true,
+      "dependencies": {
+        "@babel/helper-module-imports": "7.0.0-beta.35"
+      }
+    },
+    "node_modules/babel-plugin-component/node_modules/@babel/helper-module-imports": {
+      "version": "7.0.0-beta.35",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0-beta.35.tgz",
+      "integrity": "sha512-vaC1KyIZSuyWb3Lj277fX0pxivyHwuDU4xZsofqgYAbkDxNieMg2vuhzP5AgMweMY7fCQUMTi+BgPqTLjkxXFg==",
+      "dev": true,
+      "dependencies": {
+        "@babel/types": "7.0.0-beta.35",
+        "lodash": "^4.2.0"
+      }
+    },
+    "node_modules/babel-plugin-component/node_modules/@babel/types": {
+      "version": "7.0.0-beta.35",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.35.tgz",
+      "integrity": "sha512-y9XT11CozHDgjWcTdxmhSj13rJVXpa5ZXwjjOiTedjaM0ba5ItqdS02t31EhPl7HtOWxsZkYCCUNrSfrOisA6w==",
+      "dev": true,
+      "dependencies": {
+        "esutils": "^2.0.2",
+        "lodash": "^4.2.0",
+        "to-fast-properties": "^2.0.0"
+      }
+    },
     "node_modules/babel-plugin-dynamic-import-node": {
       "version": "2.3.3",
       "resolved": "https://registry.npmmirror.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz",
@@ -3782,6 +3827,22 @@
         "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
       }
     },
+    "node_modules/babel-runtime": {
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
+      "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==",
+      "dependencies": {
+        "core-js": "^2.4.0",
+        "regenerator-runtime": "^0.11.0"
+      }
+    },
+    "node_modules/babel-runtime/node_modules/core-js": {
+      "version": "2.6.12",
+      "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
+      "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==",
+      "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.",
+      "hasInstallScript": true
+    },
     "node_modules/balanced-match": {
       "version": "1.0.2",
       "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -5073,7 +5134,6 @@
       "version": "1.5.2",
       "resolved": "https://registry.npmmirror.com/deepmerge/-/deepmerge-1.5.2.tgz",
       "integrity": "sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=0.10.0"
@@ -5563,6 +5623,22 @@
       "dev": true,
       "license": "ISC"
     },
+    "node_modules/element-ui": {
+      "version": "2.15.14",
+      "resolved": "https://registry.npmjs.org/element-ui/-/element-ui-2.15.14.tgz",
+      "integrity": "sha512-2v9fHL0ZGINotOlRIAJD5YuVB8V7WKxrE9Qy7dXhRipa035+kF7WuU/z+tEmLVPBcJ0zt8mOu1DKpWcVzBK8IA==",
+      "dependencies": {
+        "async-validator": "~1.8.1",
+        "babel-helper-vue-jsx-merge-props": "^2.0.0",
+        "deepmerge": "^1.2.0",
+        "normalize-wheel": "^1.0.1",
+        "resize-observer-polyfill": "^1.5.0",
+        "throttle-debounce": "^1.0.1"
+      },
+      "peerDependencies": {
+        "vue": "^2.5.17"
+      }
+    },
     "node_modules/emoji-regex": {
       "version": "8.0.0",
       "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
@@ -8824,6 +8900,11 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/normalize-wheel": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/normalize-wheel/-/normalize-wheel-1.0.1.tgz",
+      "integrity": "sha512-1OnlAPZ3zgrk8B91HyRj+eVv+kS5u+Z0SCsak6Xil/kmgEia50ga7zfkumayonZrImffAxPU/5WcyGhzetHNPA=="
+    },
     "node_modules/nosleep.js": {
       "version": "0.12.0",
       "resolved": "https://registry.npmjs.org/nosleep.js/-/nosleep.js-0.12.0.tgz",
@@ -10347,6 +10428,11 @@
         "node": ">=4"
       }
     },
+    "node_modules/regenerator-runtime": {
+      "version": "0.11.1",
+      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
+      "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
+    },
     "node_modules/regexpp": {
       "version": "3.2.0",
       "resolved": "https://registry.npmmirror.com/regexpp/-/regexpp-3.2.0.tgz",
@@ -10449,6 +10535,11 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/resize-observer-polyfill": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
+      "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
+    },
     "node_modules/resolve": {
       "version": "1.22.11",
       "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.11.tgz",
@@ -11589,6 +11680,14 @@
       "integrity": "sha512-Psv6bbd3d/M/01MT2zZ+VmD0Vj2dbWTNhfe4CuSg7w5TuW96M3NOyCVuh9SZQ05CpGmD7NEcJhZw4GVjhCYxfQ==",
       "license": "MIT"
     },
+    "node_modules/throttle-debounce": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-1.1.0.tgz",
+      "integrity": "sha512-XH8UiPCQcWNuk2LYePibW/4qL97+ZQ1AN3FNXwZRBNPPowo/NRU5fAlDCSNBJIYCKbioZfuYtMhG4quqoJhVzg==",
+      "engines": {
+        "node": ">=4"
+      }
+    },
     "node_modules/thunky": {
       "version": "1.1.0",
       "resolved": "https://registry.npmmirror.com/thunky/-/thunky-1.1.0.tgz",
@@ -11596,6 +11695,15 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/to-fast-properties": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+      "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
     "node_modules/to-regex-range": {
       "version": "5.0.1",
       "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz",

+ 2 - 0
package.json

@@ -15,6 +15,7 @@
     "core-js": "^3.8.3",
     "echarts": "^5.6.0",
     "echarts-gl": "^2.0.9",
+    "element-ui": "^2.15.14",
     "konva": "^10.2.0",
     "swiper": "^5.4.5",
     "three": "^0.183.1",
@@ -31,6 +32,7 @@
     "@vue/cli-plugin-eslint": "~5.0.0",
     "@vue/cli-service": "~5.0.0",
     "axios-mock-adapter": "^2.1.0",
+    "babel-plugin-component": "^1.1.1",
     "eslint": "^7.32.0",
     "eslint-plugin-vue": "^8.0.3",
     "postcss-pxtorem": "^5.1.1",

+ 118 - 3
src/components/ui/CrossingDetailPanel.vue

@@ -43,10 +43,50 @@
                                 <DropdownSelect v-model="currentScheme" :options="schemeOptions" size="auto" />
                             </div>
 
+                            <div class="time-form-bar" v-if="currentMethod === 'temp'">
+
+                                <el-date-picker v-model="startDate" type="date" placeholder="选择日期"
+                                    value-format="yyyy-MM-dd" size="small" :append-to-body="true"
+                                    :popper-options="{ boundariesPadding: 0, gpuAcceleration: false }"
+                                    class="form-item">
+                                </el-date-picker>
+
+                                <el-time-picker v-model="startTime" placeholder="选择时间" value-format="HH:mm:ss"
+                                    size="small" :append-to-body="true"
+                                    :popper-options="{ boundariesPadding: 0, gpuAcceleration: false }"
+                                    class="form-item">
+                                </el-time-picker>
+
+                                <el-date-picker v-model="endDate" type="date" placeholder="选择日期"
+                                    value-format="yyyy-MM-dd" size="small" :append-to-body="true"
+                                    :popper-options="{ boundariesPadding: 0, gpuAcceleration: false }"
+                                    class="form-item">
+                                </el-date-picker>
+
+                                <el-time-picker v-model="endTime" placeholder="选择时间" value-format="HH:mm:ss"
+                                    size="small" :append-to-body="true"
+                                    :popper-options="{ boundariesPadding: 0, gpuAcceleration: false }"
+                                    class="form-item">
+                                </el-time-picker>
+
+                                <el-select v-model="duration" placeholder="请选择时长" size="small"
+                                    :popper-append-to-body="true" class="form-item">
+                                    <el-option v-for="d in durationOptions" :key="d" :label="d"
+                                        :value="d"></el-option>
+                                </el-select>
+
+                                <el-select v-model="period" placeholder="请选择周期" size="small"
+                                    :popper-append-to-body="true" class="form-item">
+                                    <el-option v-for="p in 8" :key="p" :label="'周期' + p" :value="p"></el-option>
+                                </el-select>
+
+                            </div>
+
                             <div class="current-stage">
                                 <div class="current-stage-warp">
                                     <div class="current-stage-label">当前阶段:</div>
-                                    <div v-for="(item, index) in currentStageList" :key="index" class="stage-item-wrapper">
+                                    <div v-for="(item, index) in currentStageList" :key="index"
+                                        class="stage-item-wrapper">
                                         <div class="phase-box" :class="{ 'is-active': item.value === currentStage }"
                                             @click="currentStage = item.value">
                                             <img :src="item.img" alt="stage" class="phase-image" />
@@ -143,6 +183,12 @@ export default {
     },
     data() {
         return {
+            startDate: '2026-04-13',
+            startTime: '14:03:06',
+            endDate: '2026-04-13',
+            endTime: '15:03:06',
+            duration: null,
+            period: null,
             // 核心状态控制
             isManualMode: false, // 是否处于手动控制模式
             showLockTime: false, // 是否显示锁定时间弹窗
@@ -184,9 +230,15 @@ export default {
         isSchemeDisabled() {
             return ['yellow_flash', 'lights_off', 'all_red'].includes(this.currentMethod);
         },
-        // 定周期、中心控制、感应控制、临时方案可编辑当前阶段
+        // 定周期、中心计划、感应控制、临时方案可编辑当前阶段
         canEditStage() {
             return ['fixed', 'system', 'sensor', 'temp'].includes(this.currentMethod);
+        },
+        // 时长选项:30, 60, 90 ... 300
+        durationOptions() {
+            const list = [];
+            for (let i = 30; i <= 300; i += 30) list.push(i);
+            return list;
         }
     },
     watch: {
@@ -364,6 +416,12 @@ export default {
             if (data.controlMethodOptions) this.controlMethodOptions = data.controlMethodOptions;
             if (data.currentMethod) this.currentMethod = data.currentMethod;
             if (data.locktimeOptions) this.locktimeOptions = data.locktimeOptions;
+            if (data.startDate) this.startDate = data.startDate;
+            if (data.startTime) this.startTime = data.startTime;
+            if (data.endDate) this.endDate = data.endDate;
+            if (data.endTime) this.endTime = data.endTime;
+            if (data.duration) this.duration = data.duration;
+            if (data.period) this.period = data.period;
         },
         // 切换手动控制模式
         toggleManualMode() {
@@ -839,7 +897,8 @@ export default {
     display: flex;
     align-items: center;
     justify-content: center;
-    gap: clamp(4px, calc(var(--s) * 6px), 8px); /* 输入框和百分比的间距 */
+    gap: clamp(4px, calc(var(--s) * 6px), 8px);
+    /* 输入框和百分比的间距 */
 }
 
 /* 新增包裹层的相对定位 */
@@ -937,4 +996,60 @@ export default {
     color: #a0aec0;
     margin-bottom: 4px;
 }
+
+/* ===== 时间表单栏布局 ===== */
+.time-form-bar {
+    display: flex;
+    flex-wrap: wrap;
+    gap: clamp(4px, calc(var(--s) * 8px), 10px);
+    margin-bottom: clamp(4px, calc(var(--s) * 10px), 20px);
+}
+
+.time-form-bar .form-item {
+    flex: 1 1 calc(30% - 8px);
+    min-width: clamp(80px, calc(var(--s) * 140px), 140px);
+}
+
+/* ===== ElementUI 深色主题适配 ===== */
+
+/* 覆盖 el-date-editor 固定宽度,让其跟随 flex 布局 */
+.time-form-bar>>>.el-date-editor.el-input,
+.time-form-bar>>>.el-date-editor.el-input__inner,
+.time-form-bar>>>.el-select {
+    width: 100%;
+}
+
+/* 输入框样式 */
+.time-form-bar>>>.el-input__inner {
+    background-color: rgba(255, 255, 255, 0.06);
+    border: 1px solid rgba(161, 190, 255, 0.35);
+    color: #e0e6f1;
+    font-size: clamp(9px, calc(var(--s) * 13px), 13px);
+    height: clamp(22px, calc(var(--s) * 32px), 32px);
+    line-height: clamp(22px, calc(var(--s) * 32px), 32px);
+}
+
+.time-form-bar>>>.el-input__inner::placeholder {
+    color: rgba(255, 255, 255, 0.3);
+}
+
+.time-form-bar>>>.el-input__inner:hover {
+    border-color: rgba(161, 190, 255, 0.6);
+}
+
+.time-form-bar>>>.el-input__inner:focus {
+    border-color: #3b74ff;
+}
+
+/* 图标颜色与大小 */
+.time-form-bar>>>.el-input__prefix,
+.time-form-bar>>>.el-input__suffix {
+    color: rgba(255, 255, 255, 0.4);
+    font-size: clamp(9px, calc(var(--s) * 14px), 14px);
+}
+
+.time-form-bar>>>.el-input__icon {
+    line-height: clamp(22px, calc(var(--s) * 32px), 32px);
+    width: clamp(16px, calc(var(--s) * 25px), 25px);
+}
 </style>

+ 9 - 0
src/main.js

@@ -3,6 +3,15 @@ import App from "./App.vue";
 import router from "./router";
 import "./styles/base.css";
 import '@/utils/rem.js';
+import { DatePicker, TimePicker, Select, Option } from "element-ui";
+import PopupManager from "element-ui/lib/utils/popup/popup-manager";
+
+PopupManager.zIndex = 3000;
+
+Vue.use(DatePicker);
+Vue.use(TimePicker);
+Vue.use(Select);
+Vue.use(Option);
 
 // Mock 拦截器(切真实后端时删除下面这行)
 import '@/mock/mockAdapter';

+ 12 - 5
src/mock/api.js

@@ -408,9 +408,9 @@ export async function apiGetIntersectionStages(id) {
   const timing = DB.signalTimings[id]
   if (timing) {
     const hasTrack1 = timing.data.phaseData.some(p => p[0] === 1)
-    const phases = timing.data.phaseData.filter(p => p[0] === (hasTrack1 ? 1 : 0) && p[4] !== null)
+    const phases = timing.data.phaseData.filter(p => p[0] === (hasTrack1 ? 1 : 0) && p[5] === 'green')
     return ok(phases.map((p, i) => ({
-      value: String(i + 1), time: p[4], phaseName: p[3], direction: p[6], img: ARROWS[i % ARROWS.length],
+      value: String(i + 1), time: p[8], phaseName: p[3], direction: p[6], img: ARROWS[i % ARROWS.length],
     })))
   }
   return ok(_makeStageList())
@@ -901,10 +901,10 @@ export async function apiGetCrossingDetailData(id, { iconMode = 'default' } = {}
 
   // 从相位数据中提取阶段列表(优先上轨道绿灯相位,单轨道时取 track 0,最多4个)
   const hasTrack1 = phaseData.some(p => p[0] === 1)
-  const greenPhases = phaseData.filter(p => p[0] === (hasTrack1 ? 1 : 0) && p[4] !== null).slice(0, 4)
+  const greenPhases = phaseData.filter(p => p[0] === (hasTrack1 ? 1 : 0) && p[5] === 'green').slice(0, 4)
   const stageList = greenPhases.map((p, i) => ({
     value: String(i + 1),
-    time: p[4],
+    time: p[8],
     phaseName: p[3],
     direction: p[6],
     img: ARROWS[i],
@@ -917,7 +917,7 @@ export async function apiGetCrossingDetailData(id, { iconMode = 'default' } = {}
     { label: '全红', value: 'all_red' },
     { label: '定周期', value: 'fixed' },
     { label: '步进', value: 'step' },
-    { label: '中心控制', value: 'system' },
+    { label: '中心计划', value: 'system' },
     { label: '感应控制', value: 'sensor' },
     { label: '临时方案', value: 'temp' },
   ]
@@ -991,6 +991,13 @@ export async function apiGetCrossingDetailData(id, { iconMode = 'default' } = {}
       { label: '100', value: 100 },
       { label: '300', value: 300 },
     ],
+    // 临时方案时间栏数据
+    startDate: '2026-04-14',
+    startTime: '08:00:00',
+    endDate: '2026-04-14',
+    endTime: '10:00:00',
+    duration: 120,
+    period: 1,
   })
 }
 

+ 112 - 0
src/styles/base.css

@@ -179,6 +179,118 @@ html, body {
   }
 }
 
+/* ===== ElementUI 弹出面板深色主题(挂载在 body 上,需全局覆写)===== */
+
+/* 日期/时间面板 & 下拉面板 */
+.el-picker-panel,
+.el-time-panel,
+.el-select-dropdown {
+    background-color: #0c1a35;
+    border-color: rgba(161, 190, 255, 0.2);
+    color: #e0e6f1;
+}
+
+/* 日期面板头部(年月切换) */
+.el-date-picker__header-label,
+.el-picker-panel__icon-btn {
+    color: #e0e6f1;
+}
+.el-date-picker__header-label:hover,
+.el-picker-panel__icon-btn:hover {
+    color: #3b74ff;
+}
+
+/* 日期表格 */
+.el-date-table th {
+    color: rgba(255, 255, 255, 0.45);
+    border-bottom-color: rgba(161, 190, 255, 0.12);
+}
+.el-date-table td.available span {
+    color: #e0e6f1;
+}
+.el-date-table td.today span {
+    color: #3b74ff;
+}
+.el-date-table td.current:not(.disabled) span {
+    background-color: #3b74ff;
+    color: #fff;
+}
+.el-date-table td:hover span {
+    background-color: rgba(59, 116, 255, 0.15);
+    color: #fff;
+}
+.el-date-table td.next-month span,
+.el-date-table td.prev-month span {
+    color: rgba(255, 255, 255, 0.2);
+}
+
+/* 月份/年份面板 */
+.el-month-table td .cell,
+.el-year-table td .cell {
+    color: #e0e6f1;
+}
+.el-month-table td .cell:hover,
+.el-year-table td .cell:hover,
+.el-month-table td.current:not(.disabled) .cell,
+.el-year-table td.current:not(.disabled) .cell {
+    color: #3b74ff;
+}
+
+/* 时间滚轮 */
+.el-time-spinner__item {
+    color: #e0e6f1;
+}
+.el-time-spinner__item:hover:not(.disabled):not(.active) {
+    background-color: rgba(59, 116, 255, 0.1);
+}
+.el-time-spinner__item.active:not(.disabled) {
+    color: #3b74ff;
+    font-weight: bold;
+}
+.el-time-panel__footer {
+    border-top-color: rgba(161, 190, 255, 0.12);
+}
+.el-time-panel__btn {
+    color: #e0e6f1;
+}
+.el-time-panel__btn.confirm {
+    color: #3b74ff;
+}
+
+/* 下拉选项 */
+.el-select-dropdown__item {
+    color: #e0e6f1;
+}
+.el-select-dropdown__item:hover {
+    background-color: rgba(59, 116, 255, 0.12);
+}
+.el-select-dropdown__item.selected {
+    color: #3b74ff;
+    font-weight: bold;
+}
+
+/* 弹出箭头 */
+.el-popper[x-placement^=bottom] .popper__arrow {
+    border-bottom-color: rgba(161, 190, 255, 0.2);
+}
+.el-popper[x-placement^=bottom] .popper__arrow::after {
+    border-bottom-color: #0c1a35;
+}
+.el-popper[x-placement^=top] .popper__arrow {
+    border-top-color: rgba(161, 190, 255, 0.2);
+}
+.el-popper[x-placement^=top] .popper__arrow::after {
+    border-top-color: #0c1a35;
+}
+
+/* 滚动条 */
+.el-scrollbar__thumb {
+    background-color: rgba(161, 190, 255, 0.25);
+}
+.el-scrollbar__thumb:hover {
+    background-color: rgba(161, 190, 255, 0.4);
+}
+
 /* 背景光晕扩散 */
 @keyframes glowBreathe {
   0%, 100% {