From 49bc8b83b1252102f39eb195941afbac6a8079c0 Mon Sep 17 00:00:00 2001 From: yuyx <237899745@qq.com> Date: Sat, 13 Dec 2025 21:13:57 +0800 Subject: [PATCH] perf(admin): lazy routes and nav badges --- admin-frontend/src/api/feedbacks.js | 6 +- admin-frontend/src/layouts/AdminLayout.vue | 96 +++++++++++- admin-frontend/src/pages/FeedbacksPage.vue | 7 +- admin-frontend/src/pages/PendingPage.vue | 4 + admin-frontend/src/router/index.js | 19 ++- static/admin/.vite/manifest.json | 148 +++++++++++++++++- .../assets/AnnouncementsPage-CXFfpdyD.js | 1 + .../assets/AnnouncementsPage-CjcC-aWD.css | 1 + static/admin/assets/EmailPage-D5rz9N2M.js | 1 + static/admin/assets/EmailPage-Dk6eRUoe.css | 1 + .../admin/assets/FeedbacksPage-BKNQYWPz.css | 1 + static/admin/assets/FeedbacksPage-zx0MksLD.js | 1 + static/admin/assets/LogsPage-DnqHdnu7.js | 1 + static/admin/assets/LogsPage-R-XyhzDW.css | 1 + static/admin/assets/PendingPage-C_mZDlzP.css | 1 + static/admin/assets/PendingPage-DDGug1ac.js | 1 + static/admin/assets/SettingsPage-BNOqaz0O.js | 1 + static/admin/assets/SettingsPage-DGdwb4W2.css | 1 + static/admin/assets/StatsPage-CfWiD1Ty.js | 1 + static/admin/assets/StatsPage-kYXPdoa5.css | 1 + static/admin/assets/SystemPage-DC1VKbLw.css | 1 + static/admin/assets/SystemPage-Di4QNzPH.js | 16 ++ static/admin/assets/UsersPage-D2Xg1a62.css | 1 + static/admin/assets/UsersPage-zxqUvIyG.js | 1 + static/admin/assets/datetime-CpkTDmvr.js | 1 + static/admin/assets/index-C2CkOw_I.css | 1 - static/admin/assets/index-CCJGmygT.js | 30 ++++ static/admin/assets/index-D-MDwNCD.js | 44 ------ static/admin/assets/index-lm5BCraY.css | 1 + static/admin/assets/tasks-BUxA_MMn.js | 1 + static/admin/assets/users-DVl5a2To.js | 1 + static/admin/index.html | 4 +- 32 files changed, 328 insertions(+), 68 deletions(-) create mode 100644 static/admin/assets/AnnouncementsPage-CXFfpdyD.js create mode 100644 static/admin/assets/AnnouncementsPage-CjcC-aWD.css create mode 100644 static/admin/assets/EmailPage-D5rz9N2M.js create mode 100644 static/admin/assets/EmailPage-Dk6eRUoe.css create mode 100644 static/admin/assets/FeedbacksPage-BKNQYWPz.css create mode 100644 static/admin/assets/FeedbacksPage-zx0MksLD.js create mode 100644 static/admin/assets/LogsPage-DnqHdnu7.js create mode 100644 static/admin/assets/LogsPage-R-XyhzDW.css create mode 100644 static/admin/assets/PendingPage-C_mZDlzP.css create mode 100644 static/admin/assets/PendingPage-DDGug1ac.js create mode 100644 static/admin/assets/SettingsPage-BNOqaz0O.js create mode 100644 static/admin/assets/SettingsPage-DGdwb4W2.css create mode 100644 static/admin/assets/StatsPage-CfWiD1Ty.js create mode 100644 static/admin/assets/StatsPage-kYXPdoa5.css create mode 100644 static/admin/assets/SystemPage-DC1VKbLw.css create mode 100644 static/admin/assets/SystemPage-Di4QNzPH.js create mode 100644 static/admin/assets/UsersPage-D2Xg1a62.css create mode 100644 static/admin/assets/UsersPage-zxqUvIyG.js create mode 100644 static/admin/assets/datetime-CpkTDmvr.js delete mode 100644 static/admin/assets/index-C2CkOw_I.css create mode 100644 static/admin/assets/index-CCJGmygT.js delete mode 100644 static/admin/assets/index-D-MDwNCD.js create mode 100644 static/admin/assets/index-lm5BCraY.css create mode 100644 static/admin/assets/tasks-BUxA_MMn.js create mode 100644 static/admin/assets/users-DVl5a2To.js diff --git a/admin-frontend/src/api/feedbacks.js b/admin-frontend/src/api/feedbacks.js index 4d6c8ba..92e4605 100644 --- a/admin-frontend/src/api/feedbacks.js +++ b/admin-frontend/src/api/feedbacks.js @@ -5,6 +5,11 @@ export async function fetchFeedbacks(status = '') { return data } +export async function fetchFeedbackStats() { + const { data } = await api.get('/feedbacks', { params: { limit: 1, offset: 0 } }) + return data?.stats +} + export async function replyFeedback(feedbackId, reply) { const { data } = await api.post(`/feedbacks/${feedbackId}/reply`, { reply }) return data @@ -19,4 +24,3 @@ export async function deleteFeedback(feedbackId) { const { data } = await api.delete(`/feedbacks/${feedbackId}`) return data } - diff --git a/admin-frontend/src/layouts/AdminLayout.vue b/admin-frontend/src/layouts/AdminLayout.vue index bcc0941..8f50e56 100644 --- a/admin-frontend/src/layouts/AdminLayout.vue +++ b/admin-frontend/src/layouts/AdminLayout.vue @@ -15,6 +15,8 @@ import { } from '@element-plus/icons-vue' import { api } from '../api/client' +import { fetchFeedbackStats } from '../api/feedbacks' +import { fetchPasswordResets } from '../api/passwordResets' import { fetchSystemStats } from '../api/stats' import StatsCards from '../components/StatsCards.vue' @@ -35,8 +37,46 @@ async function refreshStats() { } } +const loadingBadges = ref(false) +const pendingResetsCount = ref(0) +const pendingFeedbackCount = ref(0) +let badgeTimer + +async function refreshNavBadges(partial = null) { + if (partial && typeof partial === 'object') { + if (Object.prototype.hasOwnProperty.call(partial, 'pendingResets')) { + pendingResetsCount.value = Number(partial.pendingResets || 0) + } + if (Object.prototype.hasOwnProperty.call(partial, 'pendingFeedbacks')) { + pendingFeedbackCount.value = Number(partial.pendingFeedbacks || 0) + } + return + } + + if (loadingBadges.value) return + loadingBadges.value = true + + try { + const [resetsResult, feedbackResult] = await Promise.allSettled([ + fetchPasswordResets(), + fetchFeedbackStats(), + ]) + + if (resetsResult.status === 'fulfilled') { + pendingResetsCount.value = Array.isArray(resetsResult.value) ? resetsResult.value.length : 0 + } + + if (feedbackResult.status === 'fulfilled') { + pendingFeedbackCount.value = Number(feedbackResult.value?.pending || 0) + } + } finally { + loadingBadges.value = false + } +} + provide('refreshStats', refreshStats) provide('adminStats', stats) +provide('refreshNavBadges', refreshNavBadges) const isMobile = ref(false) const drawerOpen = ref(false) @@ -53,16 +93,19 @@ onMounted(async () => { syncIsMobile() await refreshStats() + await refreshNavBadges() + badgeTimer = window.setInterval(refreshNavBadges, 60_000) }) onBeforeUnmount(() => { mediaQuery?.removeEventListener?.('change', syncIsMobile) + window.clearInterval(badgeTimer) }) const menuItems = [ - { path: '/pending', label: '待审核', icon: Document }, + { path: '/pending', label: '待审核', icon: Document, badgeKey: 'pending' }, { path: '/users', label: '用户', icon: User }, - { path: '/feedbacks', label: '反馈', icon: ChatLineSquare }, + { path: '/feedbacks', label: '反馈', icon: ChatLineSquare, badgeKey: 'feedbacks' }, { path: '/stats', label: '统计', icon: DataAnalysis }, { path: '/logs', label: '任务日志', icon: List }, { path: '/announcements', label: '公告', icon: Bell }, @@ -73,6 +116,17 @@ const menuItems = [ const activeMenu = computed(() => route.path) +function badgeFor(item) { + if (!item?.badgeKey) return 0 + if (item.badgeKey === 'pending') { + return Number(stats.value?.pending_users || 0) + Number(pendingResetsCount.value || 0) + } + if (item.badgeKey === 'feedbacks') { + return Number(pendingFeedbackCount.value || 0) + } + return 0 +} + async function logout() { try { await ElMessageBox.confirm('确定退出管理员登录吗?', '退出登录', { @@ -108,7 +162,10 @@ async function go(path) { - {{ item.label }} + + {{ item.label }} + + {{ item.label }} @@ -133,7 +190,16 @@ async function go(path) { - + + + + @@ -145,7 +211,10 @@ async function go(path) { - {{ item.label }} + + {{ item.label }} + + {{ item.label }} @@ -185,6 +254,22 @@ async function go(path) { border-right: none; } +.menu-label { + display: inline-flex; + align-items: center; + min-width: 0; +} + +.menu-badge { + display: inline-flex; + align-items: center; +} + +.fallback-card { + border-radius: var(--app-radius); + border: 1px solid var(--app-border); +} + .layout-header { display: flex; align-items: center; @@ -238,4 +323,3 @@ async function go(path) { } } - diff --git a/admin-frontend/src/pages/FeedbacksPage.vue b/admin-frontend/src/pages/FeedbacksPage.vue index b1b2b19..3aacbf6 100644 --- a/admin-frontend/src/pages/FeedbacksPage.vue +++ b/admin-frontend/src/pages/FeedbacksPage.vue @@ -1,9 +1,11 @@ - + +