From ccced0a35e6ee6bbe8a3c937f9b918d2988457ac Mon Sep 17 00:00:00 2001
From: ouyangxq <775153912@qq.com>
Date: Mon, 3 Aug 2026 15:50:24 +0800
Subject: [PATCH] feat: update admin navigation and customer management
---
nginx/default.conf.template | 16 ++++++
src/components/AdminSidebarMenu.vue | 2 +-
src/components/AdminSidebarMenuNode.vue | 21 ++++++-
src/deployment/nginxConfig.test.ts | 23 ++++++++
src/layouts/AppLayout.vue | 23 +++++---
src/navigation/adminNavigation.ts | 6 ++
src/styles/foundation.css | 6 +-
src/styles/index.css | 3 +-
src/styles/shell.css | 45 ++++++++-------
src/views/CustomersView.vue | 74 +++++++++++++++++++++++--
10 files changed, 178 insertions(+), 41 deletions(-)
create mode 100644 src/deployment/nginxConfig.test.ts
diff --git a/nginx/default.conf.template b/nginx/default.conf.template
index 2ae3e079..6ab77528 100644
--- a/nginx/default.conf.template
+++ b/nginx/default.conf.template
@@ -22,8 +22,24 @@ server {
proxy_set_header X-Forwarded-Proto $scheme;
}
+ # HTML must be revalidated on every visit so a new deployment cannot keep
+ # referencing hashed chunks that were removed with the previous image.
+ location = /index.html {
+ add_header Cache-Control "no-cache, no-store, must-revalidate" always;
+ add_header Pragma "no-cache" always;
+ add_header Expires "0" always;
+ }
+
+ # Vite assets are content-hashed and safe to cache permanently. Missing
+ # chunks must remain a real 404 instead of falling back to index.html.
+ location /assets/ {
+ try_files $uri =404;
+ add_header Cache-Control "public, max-age=31536000, immutable" always;
+ }
+
# Vue Router 的 history 路由刷新时仍返回 index.html。
location / {
try_files $uri $uri/ /index.html;
+ add_header Cache-Control "no-cache" always;
}
}
diff --git a/src/components/AdminSidebarMenu.vue b/src/components/AdminSidebarMenu.vue
index 86d121b0..bf254295 100644
--- a/src/components/AdminSidebarMenu.vue
+++ b/src/components/AdminSidebarMenu.vue
@@ -26,6 +26,6 @@ const defaultOpeneds = computed(() => getGroupKeys(props.items))
:default-openeds="defaultOpeneds"
router
>
-
+
diff --git a/src/components/AdminSidebarMenuNode.vue b/src/components/AdminSidebarMenuNode.vue
index 548c7044..ec2f3162 100644
--- a/src/components/AdminSidebarMenuNode.vue
+++ b/src/components/AdminSidebarMenuNode.vue
@@ -5,21 +5,36 @@ defineOptions({ name: 'AdminSidebarMenuNode' })
defineProps<{
node: AdminNavigationNode
+ level?: number
}>()
-
+
{{ node.title }}
-
+
-
+
{{ node.title }}
diff --git a/src/deployment/nginxConfig.test.ts b/src/deployment/nginxConfig.test.ts
new file mode 100644
index 00000000..4517b6b7
--- /dev/null
+++ b/src/deployment/nginxConfig.test.ts
@@ -0,0 +1,23 @@
+import { readFileSync } from 'node:fs'
+import { resolve } from 'node:path'
+import { describe, expect, it } from 'vitest'
+
+const config = readFileSync(resolve(process.cwd(), 'nginx/default.conf.template'), 'utf8')
+
+describe('admin nginx deployment config', () => {
+ it('prevents the application shell from retaining stale chunk references', () => {
+ expect(config).toContain('location = /index.html')
+ expect(config).toContain('Cache-Control "no-cache, no-store, must-revalidate"')
+ })
+
+ it('serves hashed assets immutably and never falls back missing chunks to HTML', () => {
+ expect(config).toContain('location /assets/')
+ expect(config).toContain('try_files $uri =404;')
+ expect(config).toContain('Cache-Control "public, max-age=31536000, immutable"')
+ })
+
+ it('keeps Vue history routes on the application shell without caching them', () => {
+ expect(config).toContain('try_files $uri $uri/ /index.html;')
+ expect(config).toContain('Cache-Control "no-cache"')
+ })
+})
diff --git a/src/layouts/AppLayout.vue b/src/layouts/AppLayout.vue
index 2000aba9..5fb76200 100644
--- a/src/layouts/AppLayout.vue
+++ b/src/layouts/AppLayout.vue
@@ -2,7 +2,7 @@
import { computed, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
-import { ChevronLeft, ChevronRight, LogOut, Menu } from '@lucide/vue'
+import { LogOut, Menu } from '@lucide/vue'
import { getApiErrorDetails } from '@/api/http'
import baiyueLogoWhite from '@/assets/brand/baiyue-logo-white.png'
import baiyueMarkWhite from '@/assets/brand/baiyue-mark-white.png'
@@ -51,6 +51,14 @@ async function handleLogout() {
}
}
+function handleMenuToggle() {
+ if (window.matchMedia('(max-width: 720px)').matches) {
+ mobileMenuOpen.value = !mobileMenuOpen.value
+ return
+ }
+ collapsed.value = !collapsed.value
+}
+
watch(() => route.fullPath, () => { mobileMenuOpen.value = false })
@@ -65,16 +73,17 @@ watch(() => route.fullPath, () => { mobileMenuOpen.value = false })
-