diff --git a/admin-frontend/src/components/StatsCards.vue b/admin-frontend/src/components/StatsCards.vue index 666b5b0..1359b5e 100644 --- a/admin-frontend/src/components/StatsCards.vue +++ b/admin-frontend/src/components/StatsCards.vue @@ -8,8 +8,8 @@ const props = defineProps({ const items = computed(() => [ { key: 'total_users', label: '总用户数' }, - { key: 'approved_users', label: '已审核' }, - { key: 'pending_users', label: '待审核' }, + { key: 'new_users_today', label: '今日注册' }, + { key: 'new_users_7d', label: '近7天注册' }, { key: 'total_accounts', label: '总账号数' }, { key: 'vip_users', label: 'VIP用户' }, ]) @@ -49,4 +49,3 @@ const items = computed(() => [ color: var(--app-muted); } - diff --git a/admin-frontend/src/layouts/AdminLayout.vue b/admin-frontend/src/layouts/AdminLayout.vue index e3e9934..64824c6 100644 --- a/admin-frontend/src/layouts/AdminLayout.vue +++ b/admin-frontend/src/layouts/AdminLayout.vue @@ -103,8 +103,8 @@ onBeforeUnmount(() => { }) const menuItems = [ - { path: '/pending', label: '待审核', icon: Document, badgeKey: 'pending' }, - { path: '/users', label: '用户', icon: User }, + { path: '/reports', label: '报表', icon: Document }, + { path: '/users', label: '用户', icon: User, badgeKey: 'resets' }, { path: '/feedbacks', label: '反馈', icon: ChatLineSquare, badgeKey: 'feedbacks' }, { path: '/stats', label: '统计', icon: DataAnalysis }, { path: '/logs', label: '任务日志', icon: List }, @@ -118,9 +118,7 @@ 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 === 'resets') return Number(pendingResetsCount.value || 0) if (item.badgeKey === 'feedbacks') { return Number(pendingFeedbackCount.value || 0) } diff --git a/admin-frontend/src/pages/PendingPage.vue b/admin-frontend/src/pages/PendingPage.vue deleted file mode 100644 index 5d6df87..0000000 --- a/admin-frontend/src/pages/PendingPage.vue +++ /dev/null @@ -1,228 +0,0 @@ - - - - - diff --git a/admin-frontend/src/pages/ReportPage.vue b/admin-frontend/src/pages/ReportPage.vue new file mode 100644 index 0000000..477dc70 --- /dev/null +++ b/admin-frontend/src/pages/ReportPage.vue @@ -0,0 +1,687 @@ + + + + + diff --git a/admin-frontend/src/pages/SystemPage.vue b/admin-frontend/src/pages/SystemPage.vue index 1f43feb..abbe044 100644 --- a/admin-frontend/src/pages/SystemPage.vue +++ b/admin-frontend/src/pages/SystemPage.vue @@ -295,7 +295,7 @@ async function saveAutoApprove() { try { const res = await updateSystemConfig(payload) - ElMessage.success(res?.message || '自动审核配置已保存') + ElMessage.success(res?.message || '注册设置已保存') } catch { // handled by interceptor } @@ -438,12 +438,12 @@ onBeforeUnmount(stopUpdatePolling) -

注册自动审核

+

注册设置

- + -
开启后,新用户注册将自动通过审核,无需管理员手动审批。
+
开启后,新用户注册成功后将赠送下方设置的VIP天数(注册已默认无需审核)。
@@ -455,7 +455,7 @@ onBeforeUnmount(stopUpdatePolling)
- 保存自动审核配置 + 保存注册设置
diff --git a/admin-frontend/src/pages/UsersPage.vue b/admin-frontend/src/pages/UsersPage.vue index 24592f3..56c2693 100644 --- a/admin-frontend/src/pages/UsersPage.vue +++ b/admin-frontend/src/pages/UsersPage.vue @@ -4,21 +4,26 @@ import { ElMessage, ElMessageBox } from 'element-plus' import { adminResetUserPassword, - approveUser, deleteUser, fetchAllUsers, + approveUser, rejectUser, removeUserVip, setUserVip, } from '../api/users' +import { approvePasswordReset, fetchPasswordResets, rejectPasswordReset } from '../api/passwordResets' import { parseSqliteDateTime } from '../utils/datetime' import { validatePasswordStrength } from '../utils/password' const refreshStats = inject('refreshStats', null) +const refreshNavBadges = inject('refreshNavBadges', null) const loading = ref(false) const users = ref([]) +const resetLoading = ref(false) +const passwordResets = ref([]) + function isVip(user) { const expire = user?.vip_expire_time if (!expire) return false @@ -38,9 +43,8 @@ function vipLabel(user) { } function statusMeta(status) { - if (status === 'approved') return { label: '已通过', type: 'success' } - if (status === 'rejected') return { label: '已拒绝', type: 'danger' } - return { label: '待审核', type: 'warning' } + if (status === 'rejected') return { label: '禁用', type: 'danger' } + return { label: '正常', type: 'success' } } async function loadUsers() { @@ -54,10 +58,27 @@ async function loadUsers() { } } -async function onApprove(row) { +async function loadResets() { + resetLoading.value = true try { - await ElMessageBox.confirm(`确定通过用户「${row.username}」的注册申请吗?`, '审核通过', { - confirmButtonText: '通过', + const list = await fetchPasswordResets() + passwordResets.value = Array.isArray(list) ? list : [] + } catch { + passwordResets.value = [] + } finally { + resetLoading.value = false + } +} + +async function refreshAll() { + await Promise.all([loadUsers(), loadResets()]) + await refreshNavBadges?.({ pendingResets: passwordResets.value.length }) +} + +async function onEnableUser(row) { + try { + await ElMessageBox.confirm(`确定启用用户「${row.username}」吗?启用后用户可正常登录。`, '启用用户', { + confirmButtonText: '启用', cancelButtonText: '取消', type: 'success', }) @@ -67,7 +88,7 @@ async function onApprove(row) { try { await approveUser(row.id) - ElMessage.success('用户审核通过') + ElMessage.success('用户已启用') await loadUsers() await refreshStats?.() } catch { @@ -75,10 +96,10 @@ async function onApprove(row) { } } -async function onReject(row) { +async function onDisableUser(row) { try { - await ElMessageBox.confirm(`确定拒绝用户「${row.username}」的注册申请吗?`, '拒绝申请', { - confirmButtonText: '拒绝', + await ElMessageBox.confirm(`确定禁用用户「${row.username}」吗?禁用后用户将无法登录。`, '禁用用户', { + confirmButtonText: '禁用', cancelButtonText: '取消', type: 'warning', }) @@ -88,7 +109,7 @@ async function onReject(row) { try { await rejectUser(row.id) - ElMessage.success('已拒绝用户') + ElMessage.success('用户已禁用') await loadUsers() await refreshStats?.() } catch { @@ -96,6 +117,48 @@ async function onReject(row) { } } +async function onApproveReset(row) { + try { + await ElMessageBox.confirm(`确定批准「${row.username}」的密码重置申请吗?`, '批准重置', { + confirmButtonText: '批准', + cancelButtonText: '取消', + type: 'success', + }) + } catch { + return + } + + try { + const res = await approvePasswordReset(row.id) + ElMessage.success(res?.message || '密码重置申请已批准') + await loadResets() + await refreshNavBadges?.({ pendingResets: passwordResets.value.length }) + } catch { + // handled by interceptor + } +} + +async function onRejectReset(row) { + try { + await ElMessageBox.confirm(`确定拒绝「${row.username}」的密码重置申请吗?`, '拒绝重置', { + confirmButtonText: '拒绝', + cancelButtonText: '取消', + type: 'warning', + }) + } catch { + return + } + + try { + const res = await rejectPasswordReset(row.id) + ElMessage.success(res?.message || '密码重置申请已拒绝') + await loadResets() + await refreshNavBadges?.({ pendingResets: passwordResets.value.length }) + } catch { + // handled by interceptor + } +} + async function onDelete(row) { try { await ElMessageBox.confirm( @@ -200,7 +263,7 @@ async function onResetPassword(row) { } } -onMounted(loadUsers) +onMounted(refreshAll)