diff --git a/server/admin-next/src/client/components/Dashboard.tsx b/server/admin-next/src/client/components/Dashboard.tsx index 29c58e8e..1e8638fa 100644 --- a/server/admin-next/src/client/components/Dashboard.tsx +++ b/server/admin-next/src/client/components/Dashboard.tsx @@ -85,6 +85,12 @@ export const Dashboard: React.FC = React.memo(() => { + + {t('custom.dashboard.newUserCount')} + + + + {t('custom.dashboard.messageCount')} @@ -93,12 +99,12 @@ export const Dashboard: React.FC = React.memo(() => { - + {t('tushan.dashboard.tip.docs')} - + { {t('custom.dashboard.tip.github')} - + { + const { t } = useTranslation(); + const { value: newUserCountSummary } = useAsync(async () => { + const { data } = await request.get<{ + summary: { + count: number; + date: string; + }[]; + }>('/user/count/summary'); + + return data.summary; + }, []); + + return ( + + + + + + + + + + + + + + + + ); +}); +UserCountChart.displayName = 'UserCountChart'; + const MessageCountChart: React.FC = React.memo(() => { const { t } = useTranslation(); const { value: messageCountSummary } = useAsync(async () => { diff --git a/server/admin-next/src/client/i18n.ts b/server/admin-next/src/client/i18n.ts index 154cd5a8..69bf0a9f 100644 --- a/server/admin-next/src/client/i18n.ts +++ b/server/admin-next/src/client/i18n.ts @@ -1,4 +1,4 @@ -import { TushanContextProps } from 'tushan'; +import type { TushanContextProps } from 'tushan'; import { i18nEnTranslation } from 'tushan/client/i18n/resources/en'; import { i18nZhTranslation } from 'tushan/client/i18n/resources/zh'; @@ -15,6 +15,7 @@ export const i18n: TushanContextProps['i18n'] = { }, dashboard: { file: 'File', + newUserCount: 'New User Count', messageCount: 'Message Count', tip: { github: @@ -137,6 +138,7 @@ export const i18n: TushanContextProps['i18n'] = { }, dashboard: { file: '文件', + newUserCount: '用户新增', messageCount: '消息数', tip: { github: 'Tailchat 是在你私有空间内的下一代noIM应用', diff --git a/server/admin-next/src/server/router/api.ts b/server/admin-next/src/server/router/api.ts index d6e66bcb..ffdc4cf3 100644 --- a/server/admin-next/src/server/router/api.ts +++ b/server/admin-next/src/server/router/api.ts @@ -6,6 +6,7 @@ import { configRouter } from './config'; import { networkRouter } from './network'; import { fileRouter } from './file'; import dayjs from 'dayjs'; +import userModel from '../../../../models/user/user'; import messageModel from '../../../../models/chat/message'; import { raExpressMongoose } from '../middleware/express-mongoose-ra-json-server'; @@ -46,10 +47,60 @@ router.use('/network', networkRouter); router.use('/config', configRouter); router.use('/file', fileRouter); +router.get('/user/count/summary', auth(), async (req, res) => { + // 返回最近7天的用户数统计 + const day = 7; + const aggregateRes: { count: number; date: string }[] = await userModel + .aggregate([ + { + $match: { + createdAt: { + $gte: dayjs().subtract(day, 'd').startOf('d').toDate(), + $lt: dayjs().endOf('d').toDate(), + }, + }, + }, + { + $group: { + _id: { + createdAt: { + $dateToString: { + format: '%Y-%m-%d', + date: '$createdAt', + }, + }, + } as any, + count: { + $sum: 1, + }, + }, + }, + { + $project: { + date: '$_id.createdAt', + count: '$count', + }, + }, + ]) + .exec(); + + const summary = Array.from({ length: day }) + .map((_, d) => { + const date = dayjs().subtract(d, 'd').format('YYYY-MM-DD'); + + return { + date, + count: aggregateRes.find((r) => r.date === date)?.count ?? 0, + }; + }) + .reverse(); + + res.json({ summary }); +}); router.use( '/users', auth(), - raExpressMongoose(require('../../../../models/user/user').default, { + raExpressMongoose(userModel, { q: ['nickname', 'email'], }) );