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'],
})
);