diff --git a/admin/service/route/kb.js b/admin/service/route/kb.js index 706dcc574..1290bccbc 100644 --- a/admin/service/route/kb.js +++ b/admin/service/route/kb.js @@ -24,7 +24,6 @@ export const useKbRoute = (app) => { } : {}) }; - console.log(where); const kbsRaw = await Kb.find(where) .skip(start) diff --git a/admin/service/route/user.js b/admin/service/route/user.js index b4a614d3a..8c2d4329d 100644 --- a/admin/service/route/user.js +++ b/admin/service/route/user.js @@ -8,11 +8,12 @@ const hashPassword = (psw) => { return crypto.createHash('sha256').update(psw).digest('hex'); }; +const day = 60; + export const useUserRoute = (app) => { // 统计近 30 天注册用户数量 app.get('/users/data', auth(), async (req, res) => { try { - const day = 60; let startCount = await User.countDocuments({ createTime: { $lt: new Date(Date.now() - day * 24 * 60 * 60 * 1000) } }); @@ -92,7 +93,6 @@ export const useUserRoute = (app) => { res.status(500).json({ error: 'Error fetching users' }); } }); - // 创建用户 app.post('/users', auth(), async (req, res) => { try { @@ -134,7 +134,6 @@ export const useUserRoute = (app) => { res.status(500).json({ error: 'Error updating user' }); } }); - // 新增: 获取 pays 列表 app.get('/pays', auth(), async (req, res) => { try { @@ -179,4 +178,52 @@ export const useUserRoute = (app) => { res.status(500).json({ error: 'Error fetching pays', details: err.message }); } }); + // 获取本月账单 + app.get('/pays/data', auth(), async (req, res) => { + try { + let startCount = 0; + + const paysRaw = await Pay.aggregate([ + { + $match: { + status: 'SUCCESS', + createTime: { + $gte: new Date(Date.now() - day * 24 * 60 * 60 * 1000) + } + } + }, + { + $group: { + _id: { + year: { $year: '$createTime' }, + month: { $month: '$createTime' }, + day: { $dayOfMonth: '$createTime' } + }, + count: { $sum: '$price' } + } + }, + { + $project: { + _id: 0, + date: { $dateFromParts: { year: '$_id.year', month: '$_id.month', day: '$_id.day' } }, + count: 1 + } + }, + { $sort: { date: 1 } } + ]); + + const countResult = paysRaw.map((item) => { + startCount += item.count; + return { + date: item.date, + count: startCount + }; + }); + + res.json(countResult); + } catch (err) { + console.log(`Error fetching users: ${err}`); + res.status(500).json({ error: 'Error fetching users' }); + } + }); }; diff --git a/admin/src/Dashboard.tsx b/admin/src/Dashboard.tsx index 7a9eb6df8..5fa321c39 100644 --- a/admin/src/Dashboard.tsx +++ b/admin/src/Dashboard.tsx @@ -14,14 +14,24 @@ import { import dayjs from 'dayjs'; const authStorageKey = 'tushan:auth'; +const PRICE_SCALE = 100000; -type UsersChartDataType = { count: number; date: string; increase: number; increaseRate: string }; +type fetchChatData = { count: number; date: string; increase?: number; increaseRate?: string }; + +type chatDataType = { + date: string; + userCount: number; + userIncrease?: number; + userIncreaseRate?: string; + payCount: number; +}; export const Dashboard: React.FC = React.memo(() => { const [userCount, setUserCount] = useState(0); //用户数量 const [kbCount, setkbCount] = useState(0); const [modelCount, setmodelCount] = useState(0); - const [usersData, setUsersData] = useState([]); + + const [chatData, setChatData] = useState([]); useEffect(() => { const baseUrl = import.meta.env.VITE_PUBLIC_SERVER_URL; @@ -56,20 +66,29 @@ export const Dashboard: React.FC = React.memo(() => { setmodelCount(Number(modelTotalCount)); } }; - const fetchUserData = async () => { - const userResponse: UsersChartDataType[] = await fetch(`${baseUrl}/users/data`, { - headers - }).then((res) => res.json()); - setUsersData( - userResponse.map((item) => ({ - ...item, - date: dayjs(item.date).format('MM/DD') - })) - ); + + const fetchChatData = async () => { + const [userResponse, payResponse]: fetchChatData[][] = await Promise.all([ + fetch(`${baseUrl}/users/data`, { + headers + }).then((res) => res.json()), + fetch(`${baseUrl}/pays/data`, { + headers + }).then((res) => res.json()) + ]); + + const data = userResponse.map((item, i) => ({ + date: dayjs(item.date).format('MM/DD'), + userCount: item.count, + userIncrease: item.increase, + userIncreaseRate: item.increaseRate, + payCount: payResponse[i].count / PRICE_SCALE + })); + setChatData(data); }; fetchCounts(); - fetchUserData(); + fetchChatData(); }, []); return ( @@ -101,7 +120,13 @@ export const Dashboard: React.FC = React.memo(() => { - + +
+ 用户数量 & 支付情况 + +
+ + @@ -162,7 +187,7 @@ const DataItem = React.memo((props: { icon: React.ReactElement; title: string; c DataItem.displayName = 'DataItem'; const CustomTooltip = ({ active, payload }: any) => { - const data = payload?.[0]?.payload as UsersChartDataType; + const data = payload?.[0]?.payload as chatDataType; if (active && data) { return (
{ }} >

- count: {data.count} + 用户总数: {data.userCount}

- increase: {data.increase} + 60天累计支付: {data.payCount}

- increaseRate: {data.increaseRate} + 用户昨日增长数量: {data.userIncrease} +

+

+ 用户昨日增长比例: {data.userIncreaseRate}

); @@ -188,7 +216,7 @@ const CustomTooltip = ({ active, payload }: any) => { return null; }; -const UserChart = ({ data }: { data: UsersChartDataType[] }) => { +const UserChart = ({ data }: { data: chatDataType[] }) => { return ( { margin={{ top: 10, right: 30, left: 0, bottom: 0 }} > - - - - - + + + + + @@ -213,10 +241,17 @@ const UserChart = ({ data }: { data: UsersChartDataType[] }) => { } /> +