Commit b59f2fb2a4d619d7897e06c15b4c3e0e06e1db91

Authored by shaofan
1 parent 67f451d6

init

package-lock.json
@@ -289,9 +289,6 @@ @@ -289,9 +289,6 @@
289 "arm64" 289 "arm64"
290 ], 290 ],
291 "dev": true, 291 "dev": true,
292 - "libc": [  
293 - "glibc"  
294 - ],  
295 "license": "MIT", 292 "license": "MIT",
296 "optional": true, 293 "optional": true,
297 "os": [ 294 "os": [
@@ -309,9 +306,6 @@ @@ -309,9 +306,6 @@
309 "arm64" 306 "arm64"
310 ], 307 ],
311 "dev": true, 308 "dev": true,
312 - "libc": [  
313 - "musl"  
314 - ],  
315 "license": "MIT", 309 "license": "MIT",
316 "optional": true, 310 "optional": true,
317 "os": [ 311 "os": [
@@ -329,9 +323,6 @@ @@ -329,9 +323,6 @@
329 "ppc64" 323 "ppc64"
330 ], 324 ],
331 "dev": true, 325 "dev": true,
332 - "libc": [  
333 - "glibc"  
334 - ],  
335 "license": "MIT", 326 "license": "MIT",
336 "optional": true, 327 "optional": true,
337 "os": [ 328 "os": [
@@ -349,9 +340,6 @@ @@ -349,9 +340,6 @@
349 "s390x" 340 "s390x"
350 ], 341 ],
351 "dev": true, 342 "dev": true,
352 - "libc": [  
353 - "glibc"  
354 - ],  
355 "license": "MIT", 343 "license": "MIT",
356 "optional": true, 344 "optional": true,
357 "os": [ 345 "os": [
@@ -369,9 +357,6 @@ @@ -369,9 +357,6 @@
369 "x64" 357 "x64"
370 ], 358 ],
371 "dev": true, 359 "dev": true,
372 - "libc": [  
373 - "glibc"  
374 - ],  
375 "license": "MIT", 360 "license": "MIT",
376 "optional": true, 361 "optional": true,
377 "os": [ 362 "os": [
@@ -389,9 +374,6 @@ @@ -389,9 +374,6 @@
389 "x64" 374 "x64"
390 ], 375 ],
391 "dev": true, 376 "dev": true,
392 - "libc": [  
393 - "musl"  
394 - ],  
395 "license": "MIT", 377 "license": "MIT",
396 "optional": true, 378 "optional": true,
397 "os": [ 379 "os": [
@@ -1325,9 +1307,6 @@ @@ -1325,9 +1307,6 @@
1325 "arm64" 1307 "arm64"
1326 ], 1308 ],
1327 "dev": true, 1309 "dev": true,
1328 - "libc": [  
1329 - "glibc"  
1330 - ],  
1331 "license": "MPL-2.0", 1310 "license": "MPL-2.0",
1332 "optional": true, 1311 "optional": true,
1333 "os": [ 1312 "os": [
@@ -1349,9 +1328,6 @@ @@ -1349,9 +1328,6 @@
1349 "arm64" 1328 "arm64"
1350 ], 1329 ],
1351 "dev": true, 1330 "dev": true,
1352 - "libc": [  
1353 - "musl"  
1354 - ],  
1355 "license": "MPL-2.0", 1331 "license": "MPL-2.0",
1356 "optional": true, 1332 "optional": true,
1357 "os": [ 1333 "os": [
@@ -1373,9 +1349,6 @@ @@ -1373,9 +1349,6 @@
1373 "x64" 1349 "x64"
1374 ], 1350 ],
1375 "dev": true, 1351 "dev": true,
1376 - "libc": [  
1377 - "glibc"  
1378 - ],  
1379 "license": "MPL-2.0", 1352 "license": "MPL-2.0",
1380 "optional": true, 1353 "optional": true,
1381 "os": [ 1354 "os": [
@@ -1397,9 +1370,6 @@ @@ -1397,9 +1370,6 @@
1397 "x64" 1370 "x64"
1398 ], 1371 ],
1399 "dev": true, 1372 "dev": true,
1400 - "libc": [  
1401 - "musl"  
1402 - ],  
1403 "license": "MPL-2.0", 1373 "license": "MPL-2.0",
1404 "optional": true, 1374 "optional": true,
1405 "os": [ 1375 "os": [
src/App.vue
1 <template> 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 </template> 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 <template> 1 <template>
2 <div class="layout-shell" :class="{ collapsed }"> 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 <button class="sider-toggle" type="button" @click="collapsed = !collapsed"> 4 <button class="sider-toggle" type="button" @click="collapsed = !collapsed">
5 <menu-fold-outlined v-if="!collapsed" /> 5 <menu-fold-outlined v-if="!collapsed" />
6 <menu-unfold-outlined v-else /> 6 <menu-unfold-outlined v-else />
@@ -77,12 +77,49 @@ @@ -77,12 +77,49 @@
77 77
78 <main class="content-column"> 78 <main class="content-column">
79 <header class="soft-topbar"> 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 </div> 97 </div>
84 <div class="topbar-actions"> 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 <calendar-outlined /> 123 <calendar-outlined />
87 <span>{{ todayLabel }}</span> 124 <span>{{ todayLabel }}</span>
88 </div> 125 </div>
@@ -108,45 +145,134 @@ @@ -108,45 +145,134 @@
108 <router-view /> 145 <router-view />
109 </div> 146 </div>
110 </main> 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 </div> 212 </div>
112 </template> 213 </template>
113 214
114 <script setup lang="ts"> 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 import { useRouter, useRoute } from 'vue-router' 218 import { useRouter, useRoute } from 'vue-router'
117 import { useAuthStore } from '@/stores/auth' 219 import { useAuthStore } from '@/stores/auth'
  220 +import { useAppStore } from '@/stores/app'
118 import { 221 import {
119 GlobalOutlined, ApartmentOutlined, ShopOutlined, 222 GlobalOutlined, ApartmentOutlined, ShopOutlined,
120 UserOutlined, UnorderedListOutlined, ApiOutlined, DownOutlined, StarOutlined, 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 } from '@ant-design/icons-vue' 227 } from '@ant-design/icons-vue'
123 228
124 const router = useRouter() 229 const router = useRouter()
125 const route = useRoute() 230 const route = useRoute()
126 const auth = useAuthStore() 231 const auth = useAuthStore()
  232 +const app = useAppStore()
127 const collapsed = ref(false) 233 const collapsed = ref(false)
128 const selectedKeys = ref([route.path]) 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 watch(() => route.path, (p) => { selectedKeys.value = [p] }) 248 watch(() => route.path, (p) => { selectedKeys.value = [p] })
147 249
148 const isAdmin = computed(() => auth.userInfo?.role === 'admin') 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 const avatarText = computed(() => (auth.userInfo?.userNickname || '็ฎก็†ๅ‘˜').slice(0, 1)) 276 const avatarText = computed(() => (auth.userInfo?.userNickname || '็ฎก็†ๅ‘˜').slice(0, 1))
151 const todayLabel = computed(() => new Intl.DateTimeFormat('zh-CN', { 277 const todayLabel = computed(() => new Intl.DateTimeFormat('zh-CN', {
152 month: 'long', 278 month: 'long',
@@ -179,10 +305,10 @@ function handleLogout() { @@ -179,10 +305,10 @@ function handleLogout() {
179 305
180 .soft-sider, 306 .soft-sider,
181 .soft-topbar { 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 backdrop-filter: blur(22px); 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 .soft-sider { 314 .soft-sider {
@@ -228,8 +354,8 @@ function handleLogout() { @@ -228,8 +354,8 @@ function handleLogout() {
228 height: 38px; 354 height: 38px;
229 border-radius: 12px; 355 border-radius: 12px;
230 border: none; 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 cursor: pointer; 359 cursor: pointer;
234 margin-bottom: 12px; 360 margin-bottom: 12px;
235 } 361 }
@@ -285,7 +411,7 @@ function handleLogout() { @@ -285,7 +411,7 @@ function handleLogout() {
285 .insight-card strong, 411 .insight-card strong,
286 h1 { 412 h1 {
287 font-family: 'Outfit', sans-serif; 413 font-family: 'Outfit', sans-serif;
288 - color: #2f2946; 414 + color: var(--text-dark);
289 } 415 }
290 416
291 .brand-copy span, 417 .brand-copy span,
@@ -294,7 +420,7 @@ h1 { @@ -294,7 +420,7 @@ h1 {
294 .hero-copy p, 420 .hero-copy p,
295 .insight-card p, 421 .insight-card p,
296 .note-list { 422 .note-list {
297 - color: #8d88a4; 423 + color: var(--text-soft);
298 } 424 }
299 425
300 :deep(.ant-menu) { 426 :deep(.ant-menu) {
@@ -310,7 +436,7 @@ h1 { @@ -310,7 +436,7 @@ h1 {
310 margin-top: 10px; 436 margin-top: 10px;
311 flex-shrink: 0; 437 flex-shrink: 0;
312 border-radius: 18px; 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 padding: 12px; 440 padding: 12px;
315 } 441 }
316 442
@@ -367,10 +493,11 @@ h1 { @@ -367,10 +493,11 @@ h1 {
367 align-items: center; 493 align-items: center;
368 gap: 10px; 494 gap: 10px;
369 border-radius: 999px; 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 padding: 7px 10px; 498 padding: 7px 10px;
373 font-size: 12px; 499 font-size: 12px;
  500 + color: var(--text-main);
374 } 501 }
375 502
376 .profile-button { 503 .profile-button {
@@ -443,25 +570,68 @@ h1 { @@ -443,25 +570,68 @@ h1 {
443 display: none !important; 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 @media (max-width: 960px) { 620 @media (max-width: 960px) {
447 .layout-shell { 621 .layout-shell {
448 grid-template-columns: 1fr; 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 .soft-topbar { 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,6 +110,16 @@ router.beforeEach((to) =&gt; {
110 if (!to.meta.public && !auth.token) { 110 if (!to.meta.public && !auth.token) {
111 return { path: '/login' } 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 export default router 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,6 +34,7 @@
34 --font-size-sm: 12px; 34 --font-size-sm: 12px;
35 --font-size-md: 13px; 35 --font-size-md: 13px;
36 --font-size-lg: 15px; 36 --font-size-lg: 15px;
  37 + --bg-color: #f8f8ff;
37 color: var(--text-main); 38 color: var(--text-main);
38 font-family: var(--font-body); 39 font-family: var(--font-body);
39 line-height: 1.5; 40 line-height: 1.5;
@@ -42,7 +43,23 @@ @@ -42,7 +43,23 @@
42 text-rendering: optimizeLegibility; 43 text-rendering: optimizeLegibility;
43 -webkit-font-smoothing: antialiased; 44 -webkit-font-smoothing: antialiased;
44 -moz-osx-font-smoothing: grayscale; 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,11 +109,11 @@ a {
92 109
93 .ant-btn-default { 110 .ant-btn-default {
94 border-color: var(--line); 111 border-color: var(--line);
95 - background: rgba(255, 255, 255, 0.82); 112 + background: var(--panel-strong);
96 } 113 }
97 114
98 .ant-card { 115 .ant-card {
99 - border: 1px solid rgba(255, 255, 255, 0.55); 116 + border: 1px solid var(--line);
100 background: var(--panel); 117 background: var(--panel);
101 backdrop-filter: blur(18px); 118 backdrop-filter: blur(18px);
102 border-radius: var(--radius-lg); 119 border-radius: var(--radius-lg);
@@ -104,7 +121,7 @@ a { @@ -104,7 +121,7 @@ a {
104 } 121 }
105 122
106 .ant-card .ant-card-head { 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 min-height: 56px; 125 min-height: 56px;
109 padding-inline: 20px; 126 padding-inline: 20px;
110 } 127 }
@@ -131,7 +148,7 @@ a { @@ -131,7 +148,7 @@ a {
131 } 148 }
132 149
133 .ant-table-wrapper .ant-table-thead > tr > th { 150 .ant-table-wrapper .ant-table-thead > tr > th {
134 - background: rgba(244, 240, 255, 0.88); 151 + background: var(--panel-strong);
135 color: var(--text-dark); 152 color: var(--text-dark);
136 border-bottom: none; 153 border-bottom: none;
137 font-weight: 700; 154 font-weight: 700;
@@ -141,15 +158,16 @@ a { @@ -141,15 +158,16 @@ a {
141 } 158 }
142 159
143 .ant-table-wrapper .ant-table-tbody > tr > td { 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 font-size: var(--font-size-md); 164 font-size: var(--font-size-md);
147 padding-top: 11px; 165 padding-top: 11px;
148 padding-bottom: 11px; 166 padding-bottom: 11px;
149 } 167 }
150 168
151 .ant-table-wrapper .ant-table-tbody > tr:hover > td { 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 .ant-input, 173 .ant-input,
@@ -160,8 +178,9 @@ a { @@ -160,8 +178,9 @@ a {
160 .ant-input-number-input-wrap, 178 .ant-input-number-input-wrap,
161 .ant-picker { 179 .ant-picker {
162 border-radius: 16px !important; 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 box-shadow: none !important; 184 box-shadow: none !important;
166 font-size: var(--font-size-md); 185 font-size: var(--font-size-md);
167 } 186 }
@@ -201,15 +220,15 @@ a { @@ -201,15 +220,15 @@ a {
201 .ant-modal .ant-modal-content, 220 .ant-modal .ant-modal-content,
202 .ant-dropdown .ant-dropdown-menu { 221 .ant-dropdown .ant-dropdown-menu {
203 border-radius: 22px; 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 backdrop-filter: blur(24px); 225 backdrop-filter: blur(24px);
207 box-shadow: var(--shadow-xl); 226 box-shadow: var(--shadow-xl);
208 } 227 }
209 228
210 .ant-modal .ant-modal-header { 229 .ant-modal .ant-modal-header {
211 background: transparent; 230 background: transparent;
212 - border-bottom: 1px solid rgba(189, 180, 234, 0.18); 231 + border-bottom: 1px solid var(--line);
213 padding: 18px 20px 12px; 232 padding: 18px 20px 12px;
214 } 233 }
215 234
@@ -226,7 +245,7 @@ a { @@ -226,7 +245,7 @@ a {
226 245
227 .ant-modal .ant-modal-footer { 246 .ant-modal .ant-modal-footer {
228 padding: 12px 20px 18px; 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 .ant-modal .ant-modal-footer .ant-btn + .ant-btn { 251 .ant-modal .ant-modal-footer .ant-btn + .ant-btn {
@@ -276,14 +295,15 @@ a { @@ -276,14 +295,15 @@ a {
276 295
277 .ant-descriptions .ant-descriptions-item-label { 296 .ant-descriptions .ant-descriptions-item-label {
278 width: 128px; 297 width: 128px;
279 - background: rgba(244, 240, 255, 0.72) !important; 298 + background: var(--panel-strong) !important;
280 color: var(--text-dark) !important; 299 color: var(--text-dark) !important;
281 font-size: var(--font-size-md); 300 font-size: var(--font-size-md);
282 font-weight: 600; 301 font-weight: 600;
283 } 302 }
284 303
285 .ant-descriptions .ant-descriptions-item-content { 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 font-size: var(--font-size-md); 307 font-size: var(--font-size-md);
288 } 308 }
289 309
@@ -341,10 +361,10 @@ a { @@ -341,10 +361,10 @@ a {
341 .ant-menu-submenu-popup .ant-menu { 361 .ant-menu-submenu-popup .ant-menu {
342 padding: 8px !important; 362 padding: 8px !important;
343 border-radius: 16px !important; 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 backdrop-filter: blur(20px); 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 .ant-menu-submenu-popup .ant-menu-item, 370 .ant-menu-submenu-popup .ant-menu-item,
@@ -359,18 +379,18 @@ a { @@ -359,18 +379,18 @@ a {
359 379
360 .ant-menu-submenu-popup .ant-menu-item:hover, 380 .ant-menu-submenu-popup .ant-menu-item:hover,
361 .ant-menu-submenu-popup .ant-menu-submenu-title:hover { 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 .ant-menu-item:hover, 385 .ant-menu-item:hover,
366 .ant-menu-submenu-title:hover { 386 .ant-menu-submenu-title:hover {
367 color: var(--brand-deep) !important; 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 .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled) { 391 .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled) {
372 color: var(--brand-deep); 392 color: var(--brand-deep);
373 - background: rgba(255, 255, 255, 0.92); 393 + background: var(--panel-strong);
374 border-color: rgba(140, 124, 240, 0.45); 394 border-color: rgba(140, 124, 240, 0.45);
375 box-shadow: 0 8px 18px rgba(140, 124, 240, 0.12); 395 box-shadow: 0 8px 18px rgba(140, 124, 240, 0.12);
376 } 396 }
@@ -450,8 +470,8 @@ a { @@ -450,8 +470,8 @@ a {
450 .soft-note-card { 470 .soft-note-card {
451 border-radius: 16px; 471 border-radius: 16px;
452 padding: 14px 16px; 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 .soft-note-card strong { 477 .soft-note-card strong {
@@ -478,7 +498,7 @@ a { @@ -478,7 +498,7 @@ a {
478 .soft-dashed-block { 498 .soft-dashed-block {
479 border-radius: 18px; 499 border-radius: 18px;
480 border: 1px dashed rgba(180, 170, 231, 0.5); 500 border: 1px dashed rgba(180, 170, 231, 0.5);
481 - background: rgba(255, 255, 255, 0.48); 501 + background: var(--panel);
482 padding: 14px; 502 padding: 14px;
483 } 503 }
484 504
src/views/Login.vue
@@ -103,10 +103,10 @@ async function onSubmit() { @@ -103,10 +103,10 @@ async function onSubmit() {
103 103
104 .login-visual, 104 .login-visual,
105 .login-card { 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 backdrop-filter: blur(24px); 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 .login-visual { 112 .login-visual {
@@ -133,7 +133,7 @@ async function onSubmit() { @@ -133,7 +133,7 @@ async function onSubmit() {
133 .login-visual h1, 133 .login-visual h1,
134 .login-card h2 { 134 .login-card h2 {
135 font-family: 'Outfit', sans-serif; 135 font-family: 'Outfit', sans-serif;
136 - color: #2f2946; 136 + color: var(--text-dark);
137 } 137 }
138 138
139 .login-visual h1 { 139 .login-visual h1 {
@@ -144,7 +144,7 @@ async function onSubmit() { @@ -144,7 +144,7 @@ async function onSubmit() {
144 144
145 .login-visual p, 145 .login-visual p,
146 .login-card-head p { 146 .login-card-head p {
147 - color: #8d88a4; 147 + color: var(--text-soft);
148 max-width: 520px; 148 max-width: 520px;
149 } 149 }
150 150
@@ -158,13 +158,13 @@ async function onSubmit() { @@ -158,13 +158,13 @@ async function onSubmit() {
158 .visual-stats > div { 158 .visual-stats > div {
159 min-width: 210px; 159 min-width: 210px;
160 border-radius: 22px; 160 border-radius: 22px;
161 - background: rgba(255, 255, 255, 0.7); 161 + background: var(--panel-strong);
162 padding: 14px 16px; 162 padding: 14px 16px;
163 } 163 }
164 164
165 .visual-stats span { 165 .visual-stats span {
166 display: block; 166 display: block;
167 - color: #9893ad; 167 + color: var(--text-soft);
168 font-size: 12px; 168 font-size: 12px;
169 text-transform: uppercase; 169 text-transform: uppercase;
170 letter-spacing: 0.08em; 170 letter-spacing: 0.08em;
@@ -172,7 +172,7 @@ async function onSubmit() { @@ -172,7 +172,7 @@ async function onSubmit() {
172 } 172 }
173 173
174 .visual-stats strong { 174 .visual-stats strong {
175 - color: #342d4f; 175 + color: var(--text-dark);
176 font-family: 'Outfit', sans-serif; 176 font-family: 'Outfit', sans-serif;
177 } 177 }
178 178
src/views/config/FeePlanList.vue
@@ -847,8 +847,8 @@ onMounted(loadCities) @@ -847,8 +847,8 @@ onMounted(loadCities)
847 .plan-sidebar, 847 .plan-sidebar,
848 .plan-content { 848 .plan-content {
849 border-radius: 24px; 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 padding: 18px; 852 padding: 18px;
853 } 853 }
854 854
@@ -867,7 +867,7 @@ onMounted(loadCities) @@ -867,7 +867,7 @@ onMounted(loadCities)
867 flex-direction: column; 867 flex-direction: column;
868 gap: 12px; 868 gap: 12px;
869 padding-bottom: 6px; 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 .plan-content-body { 873 .plan-content-body {
@@ -925,8 +925,8 @@ onMounted(loadCities) @@ -925,8 +925,8 @@ onMounted(loadCities)
925 } 925 }
926 926
927 .plan-item { 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 border-radius: 18px; 930 border-radius: 18px;
931 padding: 14px; 931 padding: 14px;
932 text-align: left; 932 text-align: left;
@@ -934,9 +934,9 @@ onMounted(loadCities) @@ -934,9 +934,9 @@ onMounted(loadCities)
934 } 934 }
935 935
936 .plan-item.active { 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 .plan-item-top, 942 .plan-item-top,
@@ -954,10 +954,10 @@ onMounted(loadCities) @@ -954,10 +954,10 @@ onMounted(loadCities)
954 flex-wrap: wrap; 954 flex-wrap: wrap;
955 align-items: center; 955 align-items: center;
956 padding: 14px 16px; 956 padding: 14px 16px;
957 - border: 1px solid rgba(194, 185, 239, 0.22); 957 + border: 1px solid var(--line);
958 border-radius: 20px; 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 .plan-toolbar-meta, 963 .plan-toolbar-meta,
@@ -995,9 +995,9 @@ onMounted(loadCities) @@ -995,9 +995,9 @@ onMounted(loadCities)
995 995
996 .plan-section { 996 .plan-section {
997 padding: 18px 20px 20px; 997 padding: 18px 20px 20px;
998 - border: 1px solid rgba(194, 185, 239, 0.18); 998 + border: 1px solid var(--line);
999 border-radius: 22px; 999 border-radius: 22px;
1000 - background: rgba(255, 255, 255, 0.72); 1000 + background: var(--panel-strong);
1001 } 1001 }
1002 1002
1003 .plan-section-last { 1003 .plan-section-last {
src/views/dashboard/DashboardHome.vue
@@ -86,18 +86,16 @@ function go(path: string) { @@ -86,18 +86,16 @@ function go(path: string) {
86 gap: 14px; 86 gap: 14px;
87 border-radius: 24px; 87 border-radius: 24px;
88 padding: 20px; 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 .hero-pill { 94 .hero-pill {
97 display: inline-flex; 95 display: inline-flex;
98 padding: 6px 12px; 96 padding: 6px 12px;
99 border-radius: 999px; 97 border-radius: 999px;
100 - background: rgba(255, 255, 255, 0.76); 98 + background: var(--panel-strong);
101 color: #6f5fe2; 99 color: #6f5fe2;
102 font-size: 12px; 100 font-size: 12px;
103 font-weight: 700; 101 font-weight: 700;
@@ -110,7 +108,7 @@ function go(path: string) { @@ -110,7 +108,7 @@ function go(path: string) {
110 font-size: 24px; 108 font-size: 24px;
111 line-height: 1.15; 109 line-height: 1.15;
112 font-family: 'Outfit', sans-serif; 110 font-family: 'Outfit', sans-serif;
113 - color: #2f2946; 111 + color: var(--text-dark);
114 } 112 }
115 113
116 .hero-copy p { 114 .hero-copy p {
@@ -123,7 +121,7 @@ function go(path: string) { @@ -123,7 +121,7 @@ function go(path: string) {
123 .hero-metric span, 121 .hero-metric span,
124 .quick-link span, 122 .quick-link span,
125 .soft-notes { 123 .soft-notes {
126 - color: #8d88a4; 124 + color: var(--text-soft);
127 } 125 }
128 126
129 .hero-grid { 127 .hero-grid {
@@ -136,7 +134,7 @@ function go(path: string) { @@ -136,7 +134,7 @@ function go(path: string) {
136 .hero-metric { 134 .hero-metric {
137 min-width: 190px; 135 min-width: 190px;
138 border-radius: 18px; 136 border-radius: 18px;
139 - background: rgba(255, 255, 255, 0.74); 137 + background: var(--panel-strong);
140 padding: 11px 13px; 138 padding: 11px 13px;
141 } 139 }
142 140
@@ -150,7 +148,7 @@ function go(path: string) { @@ -150,7 +148,7 @@ function go(path: string) {
150 148
151 .hero-metric strong, 149 .hero-metric strong,
152 .quick-link strong { 150 .quick-link strong {
153 - color: #322c4a; 151 + color: var(--text-dark);
154 font-family: 'Outfit', sans-serif; 152 font-family: 'Outfit', sans-serif;
155 } 153 }
156 154
@@ -204,8 +202,8 @@ function go(path: string) { @@ -204,8 +202,8 @@ function go(path: string) {
204 202
205 .quick-link { 203 .quick-link {
206 text-align: left; 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 border-radius: 18px; 207 border-radius: 18px;
210 padding: 14px; 208 padding: 14px;
211 cursor: pointer; 209 cursor: pointer;
src/views/dispatch/DispatchRuleList.vue
@@ -515,8 +515,8 @@ onMounted(loadCities) @@ -515,8 +515,8 @@ onMounted(loadCities)
515 .plan-sidebar, 515 .plan-sidebar,
516 .plan-content { 516 .plan-content {
517 border-radius: 24px; 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 padding: 18px; 520 padding: 18px;
521 } 521 }
522 522
@@ -535,7 +535,7 @@ onMounted(loadCities) @@ -535,7 +535,7 @@ onMounted(loadCities)
535 flex-direction: column; 535 flex-direction: column;
536 gap: 12px; 536 gap: 12px;
537 padding-bottom: 6px; 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 .plan-content-body { 541 .plan-content-body {
@@ -583,8 +583,8 @@ onMounted(loadCities) @@ -583,8 +583,8 @@ onMounted(loadCities)
583 min-height: 36px; 583 min-height: 36px;
584 padding: 0 14px; 584 padding: 0 14px;
585 border-radius: 999px; 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 .plan-list { 590 .plan-list {
@@ -595,8 +595,8 @@ onMounted(loadCities) @@ -595,8 +595,8 @@ onMounted(loadCities)
595 } 595 }
596 596
597 .plan-item { 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 border-radius: 18px; 600 border-radius: 18px;
601 padding: 14px; 601 padding: 14px;
602 text-align: left; 602 text-align: left;
@@ -604,9 +604,9 @@ onMounted(loadCities) @@ -604,9 +604,9 @@ onMounted(loadCities)
604 } 604 }
605 605
606 .plan-item.active { 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 .plan-item-top, 612 .plan-item-top,
@@ -630,10 +630,10 @@ onMounted(loadCities) @@ -630,10 +630,10 @@ onMounted(loadCities)
630 flex-wrap: wrap; 630 flex-wrap: wrap;
631 align-items: center; 631 align-items: center;
632 padding: 14px 16px; 632 padding: 14px 16px;
633 - border: 1px solid rgba(194, 185, 239, 0.22); 633 + border: 1px solid var(--line);
634 border-radius: 20px; 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 .plan-toolbar-meta, 639 .plan-toolbar-meta,
@@ -682,9 +682,9 @@ onMounted(loadCities) @@ -682,9 +682,9 @@ onMounted(loadCities)
682 682
683 .plan-section { 683 .plan-section {
684 padding: 18px 20px 20px; 684 padding: 18px 20px 20px;
685 - border: 1px solid rgba(194, 185, 239, 0.18); 685 + border: 1px solid var(--line);
686 border-radius: 22px; 686 border-radius: 22px;
687 - background: rgba(255, 255, 255, 0.72); 687 + background: var(--panel-strong);
688 } 688 }
689 689
690 .plan-section-head { 690 .plan-section-head {
@@ -778,8 +778,8 @@ onMounted(loadCities) @@ -778,8 +778,8 @@ onMounted(loadCities)
778 min-height: 88px; 778 min-height: 88px;
779 padding: 14px 16px; 779 padding: 14px 16px;
780 border-radius: 18px; 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 transition: all 0.2s ease; 783 transition: all 0.2s ease;
784 } 784 }
785 785
@@ -815,11 +815,11 @@ onMounted(loadCities) @@ -815,11 +815,11 @@ onMounted(loadCities)
815 display: grid; 815 display: grid;
816 grid-template-columns: 48px minmax(0, 1fr); 816 grid-template-columns: 48px minmax(0, 1fr);
817 gap: 14px; 817 gap: 14px;
818 - border: 1px solid rgba(194, 185, 239, 0.22); 818 + border: 1px solid var(--line);
819 border-radius: 20px; 819 border-radius: 20px;
820 - background: rgba(255, 255, 255, 0.9); 820 + background: var(--panel-strong);
821 padding: 16px; 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 .dispatch-condition-row.disabled { 825 .dispatch-condition-row.disabled {