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) {