diff --git a/cwd-admin/package.json b/cwd-admin/package.json index 6f592c3..ae3e89c 100644 --- a/cwd-admin/package.json +++ b/cwd-admin/package.json @@ -12,7 +12,8 @@ }, "dependencies": { "vue": "^3.5.13", - "vue-router": "^4.4.5" + "vue-router": "^4.4.5", + "echarts": "^5.5.0" }, "devDependencies": { "@vitejs/plugin-vue": "^5.1.4", diff --git a/cwd-admin/src/api/admin.ts b/cwd-admin/src/api/admin.ts index e91e3f9..5d86a8c 100644 --- a/cwd-admin/src/api/admin.ts +++ b/cwd-admin/src/api/admin.ts @@ -62,6 +62,26 @@ export type EmailNotifySettingsResponse = { }; }; +export type CommentStatsResponse = { + summary: { + total: number; + approved: number; + pending: number; + rejected: number; + }; + domains: { + domain: string; + total: number; + approved: number; + pending: number; + rejected: number; + }[]; + last7Days: { + date: string; + total: number; + }[]; +}; + export async function loginAdmin(name: string, password: string): Promise { const res = await post('/admin/login', { name, password }); const key = res.data.key; @@ -160,3 +180,7 @@ export function exportComments(): Promise { export function importComments(data: any[]): Promise<{ message: string }> { return post<{ message: string }>('/admin/comments/import', data); } + +export function fetchCommentStats(): Promise { + return get('/admin/stats/comments'); +} diff --git a/cwd-admin/src/router/index.ts b/cwd-admin/src/router/index.ts index 55c5e4b..52dc22e 100644 --- a/cwd-admin/src/router/index.ts +++ b/cwd-admin/src/router/index.ts @@ -4,6 +4,7 @@ import LayoutView from '../views/LayoutView.vue'; import CommentsView from '../views/CommentsView.vue'; import SettingsView from '../views/SettingsView.vue'; import DataView from '../views/DataView.vue'; +import StatsView from '../views/StatsView.vue'; const routes: RouteRecordRaw[] = [ { @@ -24,6 +25,11 @@ const routes: RouteRecordRaw[] = [ name: 'comments', component: CommentsView }, + { + path: 'stats', + name: 'stats', + component: StatsView + }, { path: 'settings', name: 'settings', @@ -55,4 +61,3 @@ router.beforeEach((to, from, next) => { } next(); }); - diff --git a/cwd-admin/src/views/DataView.vue b/cwd-admin/src/views/DataView.vue index c8084fc..a4c256f 100644 --- a/cwd-admin/src/views/DataView.vue +++ b/cwd-admin/src/views/DataView.vue @@ -425,6 +425,72 @@ async function executeImport(comments: any[]) { font-size: 15px; } +.stats-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px; +} + +.stats-item { + padding: 10px 12px; + border-radius: 6px; + background-color: #f6f8fa; + border: 1px solid #d0d7de; +} + +.stats-label { + font-size: 12px; + color: #57606a; + margin-bottom: 4px; +} + +.stats-value { + font-size: 18px; + font-weight: 600; + color: #24292f; +} + +.stats-value-approved { + color: #1a7f37; +} + +.stats-value-pending { + color: #9a6700; +} + +.stats-value-rejected { + color: #d1242f; +} + +.domain-table { + border: 1px solid #d0d7de; + border-radius: 6px; + overflow: hidden; +} + +.domain-table-header { + display: flex; + background-color: #f6f8fa; +} + +.domain-table-row { + display: flex; + border-top: 1px solid #eaeae0; +} + +.domain-cell { + flex: 1; + padding: 8px 10px; + font-size: 13px; + color: #24292f; + box-sizing: border-box; +} + +.domain-cell-domain { + flex: 2; + font-weight: 500; +} + .card-desc { font-size: 14px; color: #57606a; diff --git a/cwd-admin/src/views/LayoutView.vue b/cwd-admin/src/views/LayoutView.vue index 7b7cb48..9c2525c 100644 --- a/cwd-admin/src/views/LayoutView.vue +++ b/cwd-admin/src/views/LayoutView.vue @@ -68,6 +68,13 @@ > 评论管理 +