Commit b59f2fb2a4d619d7897e06c15b4c3e0e06e1db91

Authored by shaofan
1 parent 67f451d6

init

package-lock.json
... ... @@ -289,9 +289,6 @@
289 289 "arm64"
290 290 ],
291 291 "dev": true,
292   - "libc": [
293   - "glibc"
294   - ],
295 292 "license": "MIT",
296 293 "optional": true,
297 294 "os": [
... ... @@ -309,9 +306,6 @@
309 306 "arm64"
310 307 ],
311 308 "dev": true,
312   - "libc": [
313   - "musl"
314   - ],
315 309 "license": "MIT",
316 310 "optional": true,
317 311 "os": [
... ... @@ -329,9 +323,6 @@
329 323 "ppc64"
330 324 ],
331 325 "dev": true,
332   - "libc": [
333   - "glibc"
334   - ],
335 326 "license": "MIT",
336 327 "optional": true,
337 328 "os": [
... ... @@ -349,9 +340,6 @@
349 340 "s390x"
350 341 ],
351 342 "dev": true,
352   - "libc": [
353   - "glibc"
354   - ],
355 343 "license": "MIT",
356 344 "optional": true,
357 345 "os": [
... ... @@ -369,9 +357,6 @@
369 357 "x64"
370 358 ],
371 359 "dev": true,
372   - "libc": [
373   - "glibc"
374   - ],
375 360 "license": "MIT",
376 361 "optional": true,
377 362 "os": [
... ... @@ -389,9 +374,6 @@
389 374 "x64"
390 375 ],
391 376 "dev": true,
392   - "libc": [
393   - "musl"
394   - ],
395 377 "license": "MIT",
396 378 "optional": true,
397 379 "os": [
... ... @@ -1325,9 +1307,6 @@
1325 1307 "arm64"
1326 1308 ],
1327 1309 "dev": true,
1328   - "libc": [
1329   - "glibc"
1330   - ],
1331 1310 "license": "MPL-2.0",
1332 1311 "optional": true,
1333 1312 "os": [
... ... @@ -1349,9 +1328,6 @@
1349 1328 "arm64"
1350 1329 ],
1351 1330 "dev": true,
1352   - "libc": [
1353   - "musl"
1354   - ],
1355 1331 "license": "MPL-2.0",
1356 1332 "optional": true,
1357 1333 "os": [
... ... @@ -1373,9 +1349,6 @@
1373 1349 "x64"
1374 1350 ],
1375 1351 "dev": true,
1376   - "libc": [
1377   - "glibc"
1378   - ],
1379 1352 "license": "MPL-2.0",
1380 1353 "optional": true,
1381 1354 "os": [
... ... @@ -1397,9 +1370,6 @@
1397 1370 "x64"
1398 1371 ],
1399 1372 "dev": true,
1400   - "libc": [
1401   - "musl"
1402   - ],
1403 1373 "license": "MPL-2.0",
1404 1374 "optional": true,
1405 1375 "os": [
... ...
src/App.vue
1 1 <template>
2   - <router-view />
  2 + <a-config-provider
  3 + :theme="{
  4 + algorithm: app.isDarkMode ? theme.darkAlgorithm : theme.defaultAlgorithm,
  5 + token: {
  6 + colorPrimary: '#8c7cf0',
  7 + borderRadius: 12,
  8 + fontFamily: 'Outfit, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif'
  9 + }
  10 + }"
  11 + >
  12 + <router-view />
  13 + </a-config-provider>
3 14 </template>
  15 +
  16 +<script setup lang="ts">
  17 +import { theme } from 'ant-design-vue'
  18 +import { useAppStore } from '@/stores/app'
  19 +
  20 +const app = useAppStore()
  21 +</script>
  22 +
  23 +<style>
  24 +/* Loading bar simulation */
  25 +body::before {
  26 + content: "";
  27 + position: fixed;
  28 + top: 0;
  29 + left: 0;
  30 + height: 3px;
  31 + background: linear-gradient(90deg, #8c7cf0, #f1bfd8);
  32 + z-index: 9999;
  33 + width: 0;
  34 + transition: width 0.3s ease;
  35 +}
  36 +
  37 +body.loading::before {
  38 + width: 80%;
  39 + animation: loading-pulse 2s infinite linear;
  40 +}
  41 +
  42 +@keyframes loading-pulse {
  43 + 0% { width: 0%; opacity: 1; }
  44 + 50% { width: 70%; opacity: 0.8; }
  45 + 100% { width: 90%; opacity: 0.6; }
  46 +}
  47 +
  48 +body:not(.loading)::before {
  49 + width: 100%;
  50 + opacity: 0;
  51 + transition: width 0.3s ease, opacity 0.3s 0.2s ease;
  52 +}
  53 +</style>
... ...
src/layouts/MainLayout.vue
1 1 <template>
2 2 <div class="layout-shell" :class="{ collapsed }">
3   - <aside class="soft-sider" :class="{ collapsed }">
  3 + <aside v-if="!isMobile" class="soft-sider" :class="{ collapsed }">
4 4 <button class="sider-toggle" type="button" @click="collapsed = !collapsed">
5 5 <menu-fold-outlined v-if="!collapsed" />
6 6 <menu-unfold-outlined v-else />
... ... @@ -77,12 +77,49 @@
77 77  
78 78 <main class="content-column">
79 79 <header class="soft-topbar">
80   - <div>
81   - <p class="eyebrow">Soft-Neo Admin</p>
82   - <h1>{{ currentTitle }}</h1>
  80 + <div class="topbar-left">
  81 + <button v-if="isMobile" class="mobile-menu-trigger" type="button" @click="collapsed = !collapsed">
  82 + <menu-unfold-outlined />
  83 + </button>
  84 + <div class="topbar-header-info">
  85 + <a-breadcrumb separator="/">
  86 + <a-breadcrumb-item key="home">
  87 + <router-link to="/">
  88 + <home-outlined />
  89 + </router-link>
  90 + </a-breadcrumb-item>
  91 + <a-breadcrumb-item v-for="bc in breadcrumbs" :key="bc.path">
  92 + {{ bc.title }}
  93 + </a-breadcrumb-item>
  94 + </a-breadcrumb>
  95 + <h1>{{ currentTitle }}</h1>
  96 + </div>
83 97 </div>
84 98 <div class="topbar-actions">
85   - <div class="date-pill">
  99 + <div class="header-action-group">
  100 + <a-tooltip :title="app.isDarkMode ? 'ๅˆ‡ๆขไบฎ่‰ฒๆจกๅผ' : 'ๅˆ‡ๆขๆš—่‰ฒๆจกๅผ'">
  101 + <a-button type="text" shape="circle" @click="app.toggleDarkMode()">
  102 + <template #icon>
  103 + <bulb-outlined v-if="!app.isDarkMode" />
  104 + <bulb-filled v-else style="color: #fadb14" />
  105 + </template>
  106 + </a-button>
  107 + </a-tooltip>
  108 + <a-tooltip title="้€š็Ÿฅ">
  109 + <a-button type="text" shape="circle">
  110 + <template #icon><bell-outlined /></template>
  111 + </a-button>
  112 + </a-tooltip>
  113 + <a-tooltip :title="isFullscreen ? '้€€ๅ‡บๅ…จๅฑ' : 'ๅ…จๅฑ'">
  114 + <a-button type="text" shape="circle" @click="toggleFullscreen">
  115 + <template #icon>
  116 + <fullscreen-outlined v-if="!isFullscreen" />
  117 + <fullscreen-exit-outlined v-else />
  118 + </template>
  119 + </a-button>
  120 + </a-tooltip>
  121 + </div>
  122 + <div class="date-pill hide-sm">
86 123 <calendar-outlined />
87 124 <span>{{ todayLabel }}</span>
88 125 </div>
... ... @@ -108,45 +145,134 @@
108 145 <router-view />
109 146 </div>
110 147 </main>
  148 +
  149 + <a-drawer
  150 + v-if="isMobile"
  151 + :open="!collapsed"
  152 + placement="left"
  153 + :closable="false"
  154 + @update:open="collapsed = true"
  155 + >
  156 + <div class="drawer-brand">
  157 + <div class="brand-mark">DL</div>
  158 + <strong>ๅœฐๅˆฉ้ช‘ๆ‰‹</strong>
  159 + </div>
  160 + <a-menu
  161 + v-model:selectedKeys="selectedKeys"
  162 + mode="inline"
  163 + @click="onMenuClick(); collapsed = true"
  164 + >
  165 + <a-menu-item key="/dashboard">
  166 + <template #icon><home-outlined /></template>
  167 + ๅทฅไฝœๅฐ
  168 + </a-menu-item>
  169 + <a-menu-item v-if="isAdmin" key="/city">
  170 + <template #icon><global-outlined /></template>
  171 + ็งŸๆˆท็ฎก็†
  172 + </a-menu-item>
  173 + <a-menu-item v-if="isAdmin" key="/substation">
  174 + <template #icon><apartment-outlined /></template>
  175 + ๅˆ†็ซ™็ฎก็†
  176 + </a-menu-item>
  177 + <a-sub-menu key="merchant">
  178 + <template #icon><shop-outlined /></template>
  179 + <template #title>ๅ•†ๅฎถ็ฎก็†</template>
  180 + <a-menu-item key="/merchant/enter">ๅ…ฅ้ฉป็”ณ่ฏท</a-menu-item>
  181 + <a-menu-item key="/merchant/store">ๅบ—้“บ็ฎก็†</a-menu-item>
  182 + </a-sub-menu>
  183 + <a-menu-item key="/rider">
  184 + <template #icon><user-outlined /></template>
  185 + ้ช‘ๆ‰‹็ฎก็†
  186 + </a-menu-item>
  187 + <a-menu-item key="/rider/evaluate">
  188 + <template #icon><star-outlined /></template>
  189 + ้ช‘ๆ‰‹่ฏ„ไปท
  190 + </a-menu-item>
  191 + <a-sub-menu key="orders">
  192 + <template #icon><unordered-list-outlined /></template>
  193 + <template #title>่ฎขๅ•็ฎก็†</template>
  194 + <a-menu-item key="/order">่ฎขๅ•ๅˆ—่กจ</a-menu-item>
  195 + <a-menu-item key="/refund">้€€ๆฌพ็ฎก็†</a-menu-item>
  196 + <a-menu-item key="/delivery/order">้…้€่ฎขๅ•</a-menu-item>
  197 + </a-sub-menu>
  198 + <a-sub-menu key="config">
  199 + <template #icon><control-outlined /></template>
  200 + <template #title>้…็ฝฎไธญๅฟƒ</template>
  201 + <a-menu-item key="/config/fee-plan">้…้€่ดน้…็ฝฎ</a-menu-item>
  202 + <a-menu-item key="/dispatch/rule">่ฐƒๅบฆ้…็ฝฎ</a-menu-item>
  203 + </a-sub-menu>
  204 + <a-sub-menu key="open">
  205 + <template #icon><api-outlined /></template>
  206 + <template #title>ๅผ€ๆ”พๅนณๅฐ</template>
  207 + <a-menu-item key="/open">ๅบ”็”จ็ฎก็†</a-menu-item>
  208 + <a-menu-item key="/open/mock-delivery">ๆจกๆ‹ŸๆŽจๅ•</a-menu-item>
  209 + </a-sub-menu>
  210 + </a-menu>
  211 + </a-drawer>
111 212 </div>
112 213 </template>
113 214  
114 215 <script setup lang="ts">
115   -import { computed, ref, watch } from 'vue'
  216 +import { theme } from 'ant-design-vue'
  217 +import { computed, ref, watch, onMounted, onUnmounted } from 'vue'
116 218 import { useRouter, useRoute } from 'vue-router'
117 219 import { useAuthStore } from '@/stores/auth'
  220 +import { useAppStore } from '@/stores/app'
118 221 import {
119 222 GlobalOutlined, ApartmentOutlined, ShopOutlined,
120 223 UserOutlined, UnorderedListOutlined, ApiOutlined, DownOutlined, StarOutlined,
121   - CalendarOutlined, MenuFoldOutlined, MenuUnfoldOutlined, HomeOutlined, ControlOutlined
  224 + CalendarOutlined, MenuFoldOutlined, MenuUnfoldOutlined, HomeOutlined, ControlOutlined,
  225 + FullscreenOutlined, FullscreenExitOutlined, BellOutlined,
  226 + BulbOutlined, BulbFilled
122 227 } from '@ant-design/icons-vue'
123 228  
124 229 const router = useRouter()
125 230 const route = useRoute()
126 231 const auth = useAuthStore()
  232 +const app = useAppStore()
127 233 const collapsed = ref(false)
128 234 const selectedKeys = ref([route.path])
129   -const titleMap: Record<string, string> = {
130   - '/dashboard': 'ๅทฅไฝœๅฐ',
131   - '/city': '็งŸๆˆท็ฎก็†',
132   - '/substation': 'ๅˆ†็ซ™็ฎก็†',
133   - '/merchant/enter': 'ๅ•†ๅฎถๅ…ฅ้ฉป',
134   - '/merchant/store': 'ๅบ—้“บ็ฎก็†',
135   - '/rider': '้ช‘ๆ‰‹็ฎก็†',
136   - '/rider/evaluate': '้ช‘ๆ‰‹่ฏ„ไปท',
137   - '/order': '่ฎขๅ•ๅˆ—่กจ',
138   - '/refund': '้€€ๆฌพ็ฎก็†',
139   - '/delivery/order': '้…้€่ฎขๅ•',
140   - '/config/fee-plan': '้…้€่ดน้…็ฝฎ',
141   - '/dispatch/rule': '่ฐƒๅบฆ้…็ฝฎ',
142   - '/open': 'ๅผ€ๆ”พๅนณๅฐ',
143   - '/open/mock-delivery': 'ๆจกๆ‹ŸๆŽจๅ•',
  235 +
  236 +const isMobile = ref(window.innerWidth <= 960)
  237 +function handleResize() {
  238 + isMobile.value = window.innerWidth <= 960
144 239 }
145 240  
  241 +onMounted(() => {
  242 + window.addEventListener('resize', handleResize)
  243 +})
  244 +onUnmounted(() => {
  245 + window.removeEventListener('resize', handleResize)
  246 +})
  247 +
146 248 watch(() => route.path, (p) => { selectedKeys.value = [p] })
147 249  
148 250 const isAdmin = computed(() => auth.userInfo?.role === 'admin')
149   -const currentTitle = computed(() => titleMap[route.path] || 'ๅค–ๅ–็ฎก็†')
  251 +const currentTitle = computed(() => (route.meta.title as string) || 'ๅค–ๅ–็ฎก็†')
  252 +
  253 +// Breadcrumbs logic
  254 +const breadcrumbs = computed(() => {
  255 + return route.matched
  256 + .filter(r => r.meta && r.meta.title)
  257 + .map(r => ({
  258 + title: r.meta.title,
  259 + path: r.path || '/',
  260 + }))
  261 +})
  262 +
  263 +const isFullscreen = ref(false)
  264 +function toggleFullscreen() {
  265 + if (!document.fullscreenElement) {
  266 + document.documentElement.requestFullscreen()
  267 + isFullscreen.value = true
  268 + } else {
  269 + if (document.exitFullscreen) {
  270 + document.exitFullscreen()
  271 + isFullscreen.value = false
  272 + }
  273 + }
  274 +}
  275 +
150 276 const avatarText = computed(() => (auth.userInfo?.userNickname || '็ฎก็†ๅ‘˜').slice(0, 1))
151 277 const todayLabel = computed(() => new Intl.DateTimeFormat('zh-CN', {
152 278 month: 'long',
... ... @@ -179,10 +305,10 @@ function handleLogout() {
179 305  
180 306 .soft-sider,
181 307 .soft-topbar {
182   - border: 1px solid rgba(255, 255, 255, 0.58);
183   - background: rgba(255, 255, 255, 0.74);
  308 + border: 1px solid var(--line);
  309 + background: var(--panel);
184 310 backdrop-filter: blur(22px);
185   - box-shadow: 0 16px 40px rgba(130, 110, 218, 0.12);
  311 + box-shadow: var(--shadow-xl);
186 312 }
187 313  
188 314 .soft-sider {
... ... @@ -228,8 +354,8 @@ function handleLogout() {
228 354 height: 38px;
229 355 border-radius: 12px;
230 356 border: none;
231   - background: rgba(246, 242, 255, 0.9);
232   - color: #7f6de5;
  357 + background: var(--line-strong);
  358 + color: var(--soft-primary);
233 359 cursor: pointer;
234 360 margin-bottom: 12px;
235 361 }
... ... @@ -285,7 +411,7 @@ function handleLogout() {
285 411 .insight-card strong,
286 412 h1 {
287 413 font-family: 'Outfit', sans-serif;
288   - color: #2f2946;
  414 + color: var(--text-dark);
289 415 }
290 416  
291 417 .brand-copy span,
... ... @@ -294,7 +420,7 @@ h1 {
294 420 .hero-copy p,
295 421 .insight-card p,
296 422 .note-list {
297   - color: #8d88a4;
  423 + color: var(--text-soft);
298 424 }
299 425  
300 426 :deep(.ant-menu) {
... ... @@ -310,7 +436,7 @@ h1 {
310 436 margin-top: 10px;
311 437 flex-shrink: 0;
312 438 border-radius: 18px;
313   - background: linear-gradient(180deg, rgba(245, 241, 255, 0.85), rgba(255, 247, 250, 0.92));
  439 + background: var(--panel-tint);
314 440 padding: 12px;
315 441 }
316 442  
... ... @@ -367,10 +493,11 @@ h1 {
367 493 align-items: center;
368 494 gap: 10px;
369 495 border-radius: 999px;
370   - background: rgba(255, 255, 255, 0.82);
371   - border: 1px solid rgba(194, 184, 237, 0.38);
  496 + background: var(--panel-strong);
  497 + border: 1px solid var(--line);
372 498 padding: 7px 10px;
373 499 font-size: 12px;
  500 + color: var(--text-main);
374 501 }
375 502  
376 503 .profile-button {
... ... @@ -443,25 +570,68 @@ h1 {
443 570 display: none !important;
444 571 }
445 572  
  573 +.topbar-left {
  574 + display: flex;
  575 + align-items: center;
  576 + gap: 16px;
  577 +}
  578 +
  579 +.topbar-header-info h1 {
  580 + margin: 2px 0 0;
  581 + font-size: 20px;
  582 +}
  583 +
  584 +.header-action-group {
  585 + display: flex;
  586 + align-items: center;
  587 + gap: 4px;
  588 + margin-right: 8px;
  589 +}
  590 +
  591 +.mobile-menu-trigger {
  592 + width: 40px;
  593 + height: 40px;
  594 + border-radius: 12px;
  595 + border: none;
  596 + background: rgba(140, 124, 240, 0.1);
  597 + color: #8c7cf0;
  598 + cursor: pointer;
  599 + display: flex;
  600 + align-items: center;
  601 + justify-content: center;
  602 + font-size: 18px;
  603 +}
  604 +
  605 +.drawer-brand {
  606 + display: flex;
  607 + align-items: center;
  608 + gap: 12px;
  609 + padding: 12px 16px 24px;
  610 +}
  611 +
  612 +:deep(.ant-breadcrumb) {
  613 + font-size: 11px;
  614 +}
  615 +
  616 +:deep(.ant-breadcrumb-link), :deep(.ant-breadcrumb-separator) {
  617 + color: var(--soft-subtext) !important;
  618 +}
  619 +
446 620 @media (max-width: 960px) {
447 621 .layout-shell {
448 622 grid-template-columns: 1fr;
449   - padding: 14px;
  623 + padding: 12px;
450 624 }
451   -
452   - .soft-sider {
453   - position: relative;
454   - top: 0;
455   - height: auto;
  625 +
  626 + .hide-sm {
  627 + display: none !important;
456 628 }
457 629  
458   - .menu-scroll {
459   - overflow: visible;
460   - padding-right: 0;
461   - }
462 630 .soft-topbar {
463   - flex-direction: column;
464   - align-items: flex-start;
  631 + padding: 10px 14px;
  632 + flex-direction: row;
  633 + align-items: center;
  634 + border-radius: 18px;
465 635 }
466 636 }
467 637  
... ...
src/router/index.ts
... ... @@ -110,6 +110,16 @@ router.beforeEach((to) =&gt; {
110 110 if (!to.meta.public && !auth.token) {
111 111 return { path: '/login' }
112 112 }
  113 +
  114 + // Start loading progress
  115 + document.body.classList.add('loading')
  116 +})
  117 +
  118 +router.afterEach(() => {
  119 + // Stop loading progress
  120 + setTimeout(() => {
  121 + document.body.classList.remove('loading')
  122 + }, 300)
113 123 })
114 124  
115 125 export default router
... ...
src/stores/app.ts 0 โ†’ 100644
  1 +import { defineStore } from 'pinia'
  2 +import { ref } from 'vue'
  3 +
  4 +export const useAppStore = defineStore('app', () => {
  5 + const isDarkMode = ref(localStorage.getItem('dark-mode') === 'true')
  6 +
  7 + function toggleDarkMode() {
  8 + isDarkMode.value = !isDarkMode.value
  9 + localStorage.setItem('dark-mode', isDarkMode.value.toString())
  10 + if (isDarkMode.value) {
  11 + document.documentElement.classList.add('dark')
  12 + } else {
  13 + document.documentElement.classList.remove('dark')
  14 + }
  15 + }
  16 +
  17 + // Initialize
  18 + if (isDarkMode.value) {
  19 + document.documentElement.classList.add('dark')
  20 + }
  21 +
  22 + return {
  23 + isDarkMode,
  24 + toggleDarkMode,
  25 + }
  26 +})
... ...
src/style.css
... ... @@ -34,6 +34,7 @@
34 34 --font-size-sm: 12px;
35 35 --font-size-md: 13px;
36 36 --font-size-lg: 15px;
  37 + --bg-color: #f8f8ff;
37 38 color: var(--text-main);
38 39 font-family: var(--font-body);
39 40 line-height: 1.5;
... ... @@ -42,7 +43,23 @@
42 43 text-rendering: optimizeLegibility;
43 44 -webkit-font-smoothing: antialiased;
44 45 -moz-osx-font-smoothing: grayscale;
45   - background: #f8f8ff;
  46 + background: var(--bg-color);
  47 +}
  48 +
  49 +.dark {
  50 + --app-bg: #14111d;
  51 + --bg-color: #14111d;
  52 + --panel: rgba(30, 26, 46, 0.76);
  53 + --panel-strong: rgba(40, 36, 60, 0.9);
  54 + --panel-tint: linear-gradient(135deg, rgba(74, 58, 145, 0.45), rgba(125, 48, 88, 0.4));
  55 + --line: rgba(255, 255, 255, 0.1);
  56 + --line-strong: rgba(255, 255, 255, 0.15);
  57 + --text-main: #eeecf1;
  58 + --text-soft: #a4a0b8;
  59 + --text-dark: #ffffff;
  60 + --shadow-xl: 0 22px 60px rgba(0, 0, 0, 0.5);
  61 + --shadow-lg: 0 16px 35px rgba(0, 0, 0, 0.4);
  62 + --shadow-sm: 0 10px 20px rgba(0, 0, 0, 0.3);
46 63 }
47 64  
48 65 * {
... ... @@ -92,11 +109,11 @@ a {
92 109  
93 110 .ant-btn-default {
94 111 border-color: var(--line);
95   - background: rgba(255, 255, 255, 0.82);
  112 + background: var(--panel-strong);
96 113 }
97 114  
98 115 .ant-card {
99   - border: 1px solid rgba(255, 255, 255, 0.55);
  116 + border: 1px solid var(--line);
100 117 background: var(--panel);
101 118 backdrop-filter: blur(18px);
102 119 border-radius: var(--radius-lg);
... ... @@ -104,7 +121,7 @@ a {
104 121 }
105 122  
106 123 .ant-card .ant-card-head {
107   - border-bottom: 1px solid rgba(188, 180, 230, 0.18);
  124 + border-bottom: 1px solid var(--line);
108 125 min-height: 56px;
109 126 padding-inline: 20px;
110 127 }
... ... @@ -131,7 +148,7 @@ a {
131 148 }
132 149  
133 150 .ant-table-wrapper .ant-table-thead > tr > th {
134   - background: rgba(244, 240, 255, 0.88);
  151 + background: var(--panel-strong);
135 152 color: var(--text-dark);
136 153 border-bottom: none;
137 154 font-weight: 700;
... ... @@ -141,15 +158,16 @@ a {
141 158 }
142 159  
143 160 .ant-table-wrapper .ant-table-tbody > tr > td {
144   - border-bottom: 1px solid rgba(226, 220, 247, 0.72);
145   - background: rgba(255, 255, 255, 0.48);
  161 + border-bottom: 1px solid var(--line);
  162 + background: transparent;
  163 + color: var(--text-main);
146 164 font-size: var(--font-size-md);
147 165 padding-top: 11px;
148 166 padding-bottom: 11px;
149 167 }
150 168  
151 169 .ant-table-wrapper .ant-table-tbody > tr:hover > td {
152   - background: rgba(247, 242, 255, 0.95) !important;
  170 + background: var(--line-strong) !important;
153 171 }
154 172  
155 173 .ant-input,
... ... @@ -160,8 +178,9 @@ a {
160 178 .ant-input-number-input-wrap,
161 179 .ant-picker {
162 180 border-radius: 16px !important;
163   - border-color: rgba(189, 180, 234, 0.4) !important;
164   - background: rgba(255, 255, 255, 0.78) !important;
  181 + border-color: var(--line-strong) !important;
  182 + background: var(--panel-strong) !important;
  183 + color: var(--text-main) !important;
165 184 box-shadow: none !important;
166 185 font-size: var(--font-size-md);
167 186 }
... ... @@ -201,15 +220,15 @@ a {
201 220 .ant-modal .ant-modal-content,
202 221 .ant-dropdown .ant-dropdown-menu {
203 222 border-radius: 22px;
204   - border: 1px solid rgba(228, 223, 247, 0.7);
205   - background: rgba(255, 255, 255, 0.92);
  223 + border: 1px solid var(--line);
  224 + background: var(--panel-strong);
206 225 backdrop-filter: blur(24px);
207 226 box-shadow: var(--shadow-xl);
208 227 }
209 228  
210 229 .ant-modal .ant-modal-header {
211 230 background: transparent;
212   - border-bottom: 1px solid rgba(189, 180, 234, 0.18);
  231 + border-bottom: 1px solid var(--line);
213 232 padding: 18px 20px 12px;
214 233 }
215 234  
... ... @@ -226,7 +245,7 @@ a {
226 245  
227 246 .ant-modal .ant-modal-footer {
228 247 padding: 12px 20px 18px;
229   - border-top: 1px solid rgba(189, 180, 234, 0.14);
  248 + border-top: 1px solid var(--line);
230 249 }
231 250  
232 251 .ant-modal .ant-modal-footer .ant-btn + .ant-btn {
... ... @@ -276,14 +295,15 @@ a {
276 295  
277 296 .ant-descriptions .ant-descriptions-item-label {
278 297 width: 128px;
279   - background: rgba(244, 240, 255, 0.72) !important;
  298 + background: var(--panel-strong) !important;
280 299 color: var(--text-dark) !important;
281 300 font-size: var(--font-size-md);
282 301 font-weight: 600;
283 302 }
284 303  
285 304 .ant-descriptions .ant-descriptions-item-content {
286   - background: rgba(255, 255, 255, 0.72) !important;
  305 + background: var(--panel) !important;
  306 + color: var(--text-main);
287 307 font-size: var(--font-size-md);
288 308 }
289 309  
... ... @@ -341,10 +361,10 @@ a {
341 361 .ant-menu-submenu-popup .ant-menu {
342 362 padding: 8px !important;
343 363 border-radius: 16px !important;
344   - border: 1px solid rgba(228, 223, 247, 0.72) !important;
345   - background: rgba(255, 255, 255, 0.96) !important;
  364 + border: 1px solid var(--line) !important;
  365 + background: var(--panel-strong) !important;
346 366 backdrop-filter: blur(20px);
347   - box-shadow: 0 18px 40px rgba(121, 104, 213, 0.14) !important;
  367 + box-shadow: var(--shadow-xl) !important;
348 368 }
349 369  
350 370 .ant-menu-submenu-popup .ant-menu-item,
... ... @@ -359,18 +379,18 @@ a {
359 379  
360 380 .ant-menu-submenu-popup .ant-menu-item:hover,
361 381 .ant-menu-submenu-popup .ant-menu-submenu-title:hover {
362   - background: rgba(244, 240, 255, 0.92) !important;
  382 + background: var(--line) !important;
363 383 }
364 384  
365 385 .ant-menu-item:hover,
366 386 .ant-menu-submenu-title:hover {
367 387 color: var(--brand-deep) !important;
368   - background: rgba(255, 255, 255, 0.5) !important;
  388 + background: var(--line) !important;
369 389 }
370 390  
371 391 .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled) {
372 392 color: var(--brand-deep);
373   - background: rgba(255, 255, 255, 0.92);
  393 + background: var(--panel-strong);
374 394 border-color: rgba(140, 124, 240, 0.45);
375 395 box-shadow: 0 8px 18px rgba(140, 124, 240, 0.12);
376 396 }
... ... @@ -450,8 +470,8 @@ a {
450 470 .soft-note-card {
451 471 border-radius: 16px;
452 472 padding: 14px 16px;
453   - background: linear-gradient(135deg, rgba(244, 240, 255, 0.9), rgba(255, 250, 253, 0.9));
454   - border: 1px solid rgba(206, 196, 244, 0.36);
  473 + background: var(--panel-strong);
  474 + border: 1px solid var(--line);
455 475 }
456 476  
457 477 .soft-note-card strong {
... ... @@ -478,7 +498,7 @@ a {
478 498 .soft-dashed-block {
479 499 border-radius: 18px;
480 500 border: 1px dashed rgba(180, 170, 231, 0.5);
481   - background: rgba(255, 255, 255, 0.48);
  501 + background: var(--panel);
482 502 padding: 14px;
483 503 }
484 504  
... ...
src/views/Login.vue
... ... @@ -103,10 +103,10 @@ async function onSubmit() {
103 103  
104 104 .login-visual,
105 105 .login-card {
106   - border: 1px solid rgba(255, 255, 255, 0.62);
107   - background: rgba(255, 255, 255, 0.76);
  106 + border: 1px solid var(--line);
  107 + background: var(--panel);
108 108 backdrop-filter: blur(24px);
109   - box-shadow: 0 20px 50px rgba(126, 110, 211, 0.15);
  109 + box-shadow: var(--shadow-xl);
110 110 }
111 111  
112 112 .login-visual {
... ... @@ -133,7 +133,7 @@ async function onSubmit() {
133 133 .login-visual h1,
134 134 .login-card h2 {
135 135 font-family: 'Outfit', sans-serif;
136   - color: #2f2946;
  136 + color: var(--text-dark);
137 137 }
138 138  
139 139 .login-visual h1 {
... ... @@ -144,7 +144,7 @@ async function onSubmit() {
144 144  
145 145 .login-visual p,
146 146 .login-card-head p {
147   - color: #8d88a4;
  147 + color: var(--text-soft);
148 148 max-width: 520px;
149 149 }
150 150  
... ... @@ -158,13 +158,13 @@ async function onSubmit() {
158 158 .visual-stats > div {
159 159 min-width: 210px;
160 160 border-radius: 22px;
161   - background: rgba(255, 255, 255, 0.7);
  161 + background: var(--panel-strong);
162 162 padding: 14px 16px;
163 163 }
164 164  
165 165 .visual-stats span {
166 166 display: block;
167   - color: #9893ad;
  167 + color: var(--text-soft);
168 168 font-size: 12px;
169 169 text-transform: uppercase;
170 170 letter-spacing: 0.08em;
... ... @@ -172,7 +172,7 @@ async function onSubmit() {
172 172 }
173 173  
174 174 .visual-stats strong {
175   - color: #342d4f;
  175 + color: var(--text-dark);
176 176 font-family: 'Outfit', sans-serif;
177 177 }
178 178  
... ...
src/views/config/FeePlanList.vue
... ... @@ -847,8 +847,8 @@ onMounted(loadCities)
847 847 .plan-sidebar,
848 848 .plan-content {
849 849 border-radius: 24px;
850   - border: 1px solid rgba(194, 185, 239, 0.22);
851   - background: rgba(255, 255, 255, 0.58);
  850 + border: 1px solid var(--line);
  851 + background: var(--panel);
852 852 padding: 18px;
853 853 }
854 854  
... ... @@ -867,7 +867,7 @@ onMounted(loadCities)
867 867 flex-direction: column;
868 868 gap: 12px;
869 869 padding-bottom: 6px;
870   - background: linear-gradient(180deg, rgba(255, 255, 255, 0.94), rgba(255, 255, 255, 0.76) 78%, rgba(255, 255, 255, 0));
  870 + background: var(--bg-color);
871 871 }
872 872  
873 873 .plan-content-body {
... ... @@ -925,8 +925,8 @@ onMounted(loadCities)
925 925 }
926 926  
927 927 .plan-item {
928   - border: 1px solid rgba(194, 185, 239, 0.22);
929   - background: rgba(255, 255, 255, 0.7);
  928 + border: 1px solid var(--line);
  929 + background: var(--panel-strong);
930 930 border-radius: 18px;
931 931 padding: 14px;
932 932 text-align: left;
... ... @@ -934,9 +934,9 @@ onMounted(loadCities)
934 934 }
935 935  
936 936 .plan-item.active {
937   - border-color: rgba(140, 124, 240, 0.44);
938   - background: rgba(246, 242, 255, 0.95);
939   - box-shadow: 0 10px 24px rgba(140, 124, 240, 0.12);
  937 + border-color: var(--brand);
  938 + background: var(--panel-tint);
  939 + box-shadow: var(--shadow-sm);
940 940 }
941 941  
942 942 .plan-item-top,
... ... @@ -954,10 +954,10 @@ onMounted(loadCities)
954 954 flex-wrap: wrap;
955 955 align-items: center;
956 956 padding: 14px 16px;
957   - border: 1px solid rgba(194, 185, 239, 0.22);
  957 + border: 1px solid var(--line);
958 958 border-radius: 20px;
959   - background: rgba(255, 255, 255, 0.88);
960   - box-shadow: 0 10px 24px rgba(140, 124, 240, 0.08);
  959 + background: var(--panel-strong);
  960 + box-shadow: var(--shadow-sm);
961 961 }
962 962  
963 963 .plan-toolbar-meta,
... ... @@ -995,9 +995,9 @@ onMounted(loadCities)
995 995  
996 996 .plan-section {
997 997 padding: 18px 20px 20px;
998   - border: 1px solid rgba(194, 185, 239, 0.18);
  998 + border: 1px solid var(--line);
999 999 border-radius: 22px;
1000   - background: rgba(255, 255, 255, 0.72);
  1000 + background: var(--panel-strong);
1001 1001 }
1002 1002  
1003 1003 .plan-section-last {
... ...
src/views/dashboard/DashboardHome.vue
... ... @@ -86,18 +86,16 @@ function go(path: string) {
86 86 gap: 14px;
87 87 border-radius: 24px;
88 88 padding: 20px;
89   - background:
90   - linear-gradient(160deg, rgba(255, 255, 255, 0.82), rgba(255, 255, 255, 0.62)),
91   - linear-gradient(135deg, rgba(198, 185, 255, 0.72), rgba(255, 217, 236, 0.78));
92   - border: 1px solid rgba(255, 255, 255, 0.62);
93   - box-shadow: 0 18px 44px rgba(132, 114, 212, 0.14);
  89 + background: var(--panel-tint);
  90 + border: 1px solid var(--line);
  91 + box-shadow: var(--shadow-xl);
94 92 }
95 93  
96 94 .hero-pill {
97 95 display: inline-flex;
98 96 padding: 6px 12px;
99 97 border-radius: 999px;
100   - background: rgba(255, 255, 255, 0.76);
  98 + background: var(--panel-strong);
101 99 color: #6f5fe2;
102 100 font-size: 12px;
103 101 font-weight: 700;
... ... @@ -110,7 +108,7 @@ function go(path: string) {
110 108 font-size: 24px;
111 109 line-height: 1.15;
112 110 font-family: 'Outfit', sans-serif;
113   - color: #2f2946;
  111 + color: var(--text-dark);
114 112 }
115 113  
116 114 .hero-copy p {
... ... @@ -123,7 +121,7 @@ function go(path: string) {
123 121 .hero-metric span,
124 122 .quick-link span,
125 123 .soft-notes {
126   - color: #8d88a4;
  124 + color: var(--text-soft);
127 125 }
128 126  
129 127 .hero-grid {
... ... @@ -136,7 +134,7 @@ function go(path: string) {
136 134 .hero-metric {
137 135 min-width: 190px;
138 136 border-radius: 18px;
139   - background: rgba(255, 255, 255, 0.74);
  137 + background: var(--panel-strong);
140 138 padding: 11px 13px;
141 139 }
142 140  
... ... @@ -150,7 +148,7 @@ function go(path: string) {
150 148  
151 149 .hero-metric strong,
152 150 .quick-link strong {
153   - color: #322c4a;
  151 + color: var(--text-dark);
154 152 font-family: 'Outfit', sans-serif;
155 153 }
156 154  
... ... @@ -204,8 +202,8 @@ function go(path: string) {
204 202  
205 203 .quick-link {
206 204 text-align: left;
207   - border: 1px solid rgba(202, 193, 240, 0.34);
208   - background: rgba(255, 255, 255, 0.78);
  205 + border: 1px solid var(--line);
  206 + background: var(--panel);
209 207 border-radius: 18px;
210 208 padding: 14px;
211 209 cursor: pointer;
... ...
src/views/dispatch/DispatchRuleList.vue
... ... @@ -515,8 +515,8 @@ onMounted(loadCities)
515 515 .plan-sidebar,
516 516 .plan-content {
517 517 border-radius: 24px;
518   - border: 1px solid rgba(194, 185, 239, 0.22);
519   - background: rgba(255, 255, 255, 0.58);
  518 + border: 1px solid var(--line);
  519 + background: var(--panel);
520 520 padding: 18px;
521 521 }
522 522  
... ... @@ -535,7 +535,7 @@ onMounted(loadCities)
535 535 flex-direction: column;
536 536 gap: 12px;
537 537 padding-bottom: 6px;
538   - background: linear-gradient(180deg, rgba(255, 255, 255, 0.94), rgba(255, 255, 255, 0.76) 78%, rgba(255, 255, 255, 0));
  538 + background: var(--bg-color);
539 539 }
540 540  
541 541 .plan-content-body {
... ... @@ -583,8 +583,8 @@ onMounted(loadCities)
583 583 min-height: 36px;
584 584 padding: 0 14px;
585 585 border-radius: 999px;
586   - border: 1px solid rgba(194, 185, 239, 0.28);
587   - background: rgba(246, 242, 255, 0.84);
  586 + border: 1px solid var(--line);
  587 + background: var(--panel-strong);
588 588 }
589 589  
590 590 .plan-list {
... ... @@ -595,8 +595,8 @@ onMounted(loadCities)
595 595 }
596 596  
597 597 .plan-item {
598   - border: 1px solid rgba(194, 185, 239, 0.22);
599   - background: rgba(255, 255, 255, 0.7);
  598 + border: 1px solid var(--line);
  599 + background: var(--panel-strong);
600 600 border-radius: 18px;
601 601 padding: 14px;
602 602 text-align: left;
... ... @@ -604,9 +604,9 @@ onMounted(loadCities)
604 604 }
605 605  
606 606 .plan-item.active {
607   - border-color: rgba(140, 124, 240, 0.44);
608   - background: rgba(246, 242, 255, 0.95);
609   - box-shadow: 0 10px 24px rgba(140, 124, 240, 0.12);
  607 + border-color: var(--brand);
  608 + background: var(--panel-tint);
  609 + box-shadow: var(--shadow-sm);
610 610 }
611 611  
612 612 .plan-item-top,
... ... @@ -630,10 +630,10 @@ onMounted(loadCities)
630 630 flex-wrap: wrap;
631 631 align-items: center;
632 632 padding: 14px 16px;
633   - border: 1px solid rgba(194, 185, 239, 0.22);
  633 + border: 1px solid var(--line);
634 634 border-radius: 20px;
635   - background: rgba(255, 255, 255, 0.88);
636   - box-shadow: 0 10px 24px rgba(140, 124, 240, 0.08);
  635 + background: var(--panel-strong);
  636 + box-shadow: var(--shadow-sm);
637 637 }
638 638  
639 639 .plan-toolbar-meta,
... ... @@ -682,9 +682,9 @@ onMounted(loadCities)
682 682  
683 683 .plan-section {
684 684 padding: 18px 20px 20px;
685   - border: 1px solid rgba(194, 185, 239, 0.18);
  685 + border: 1px solid var(--line);
686 686 border-radius: 22px;
687   - background: rgba(255, 255, 255, 0.72);
  687 + background: var(--panel-strong);
688 688 }
689 689  
690 690 .plan-section-head {
... ... @@ -778,8 +778,8 @@ onMounted(loadCities)
778 778 min-height: 88px;
779 779 padding: 14px 16px;
780 780 border-radius: 18px;
781   - border: 1px solid rgba(194, 185, 239, 0.22);
782   - background: rgba(255, 255, 255, 0.84);
  781 + border: 1px solid var(--line);
  782 + background: var(--panel-strong);
783 783 transition: all 0.2s ease;
784 784 }
785 785  
... ... @@ -815,11 +815,11 @@ onMounted(loadCities)
815 815 display: grid;
816 816 grid-template-columns: 48px minmax(0, 1fr);
817 817 gap: 14px;
818   - border: 1px solid rgba(194, 185, 239, 0.22);
  818 + border: 1px solid var(--line);
819 819 border-radius: 20px;
820   - background: rgba(255, 255, 255, 0.9);
  820 + background: var(--panel-strong);
821 821 padding: 16px;
822   - box-shadow: 0 12px 24px rgba(140, 124, 240, 0.08);
  822 + box-shadow: var(--shadow-sm);
823 823 }
824 824  
825 825 .dispatch-condition-row.disabled {
... ...