feat: admin

This commit is contained in:
archer
2023-06-27 21:55:18 +08:00
parent 62489ef12f
commit 1367ba9d32
3 changed files with 111 additions and 30 deletions

View File

@@ -24,7 +24,6 @@ export const useKbRoute = (app) => {
} }
: {}) : {})
}; };
console.log(where);
const kbsRaw = await Kb.find(where) const kbsRaw = await Kb.find(where)
.skip(start) .skip(start)

View File

@@ -8,11 +8,12 @@ const hashPassword = (psw) => {
return crypto.createHash('sha256').update(psw).digest('hex'); return crypto.createHash('sha256').update(psw).digest('hex');
}; };
const day = 60;
export const useUserRoute = (app) => { export const useUserRoute = (app) => {
// 统计近 30 天注册用户数量 // 统计近 30 天注册用户数量
app.get('/users/data', auth(), async (req, res) => { app.get('/users/data', auth(), async (req, res) => {
try { try {
const day = 60;
let startCount = await User.countDocuments({ let startCount = await User.countDocuments({
createTime: { $lt: new Date(Date.now() - day * 24 * 60 * 60 * 1000) } 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' }); res.status(500).json({ error: 'Error fetching users' });
} }
}); });
// 创建用户 // 创建用户
app.post('/users', auth(), async (req, res) => { app.post('/users', auth(), async (req, res) => {
try { try {
@@ -134,7 +134,6 @@ export const useUserRoute = (app) => {
res.status(500).json({ error: 'Error updating user' }); res.status(500).json({ error: 'Error updating user' });
} }
}); });
// 新增: 获取 pays 列表 // 新增: 获取 pays 列表
app.get('/pays', auth(), async (req, res) => { app.get('/pays', auth(), async (req, res) => {
try { try {
@@ -179,4 +178,52 @@ export const useUserRoute = (app) => {
res.status(500).json({ error: 'Error fetching pays', details: err.message }); 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' });
}
});
}; };

View File

@@ -14,14 +14,24 @@ import {
import dayjs from 'dayjs'; import dayjs from 'dayjs';
const authStorageKey = 'tushan:auth'; 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(() => { export const Dashboard: React.FC = React.memo(() => {
const [userCount, setUserCount] = useState(0); //用户数量 const [userCount, setUserCount] = useState(0); //用户数量
const [kbCount, setkbCount] = useState(0); const [kbCount, setkbCount] = useState(0);
const [modelCount, setmodelCount] = useState(0); const [modelCount, setmodelCount] = useState(0);
const [usersData, setUsersData] = useState<UsersChartDataType[]>([]);
const [chatData, setChatData] = useState<chatDataType[]>([]);
useEffect(() => { useEffect(() => {
const baseUrl = import.meta.env.VITE_PUBLIC_SERVER_URL; const baseUrl = import.meta.env.VITE_PUBLIC_SERVER_URL;
@@ -56,20 +66,29 @@ export const Dashboard: React.FC = React.memo(() => {
setmodelCount(Number(modelTotalCount)); setmodelCount(Number(modelTotalCount));
} }
}; };
const fetchUserData = async () => {
const userResponse: UsersChartDataType[] = await fetch(`${baseUrl}/users/data`, { const fetchChatData = async () => {
const [userResponse, payResponse]: fetchChatData[][] = await Promise.all([
fetch(`${baseUrl}/users/data`, {
headers headers
}).then((res) => res.json()); }).then((res) => res.json()),
setUsersData( fetch(`${baseUrl}/pays/data`, {
userResponse.map((item) => ({ headers
...item, }).then((res) => res.json())
date: dayjs(item.date).format('MM/DD') ]);
}))
); 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(); fetchCounts();
fetchUserData(); fetchChatData();
}, []); }, []);
return ( return (
@@ -101,7 +120,13 @@ export const Dashboard: React.FC = React.memo(() => {
</Grid.Row> </Grid.Row>
<Divider /> <Divider />
<UserChart data={usersData} />
<div>
<strong> & </strong>
<UserChart data={chatData} />
</div>
<Divider />
</Card> </Card>
</Space> </Space>
</div> </div>
@@ -162,7 +187,7 @@ const DataItem = React.memo((props: { icon: React.ReactElement; title: string; c
DataItem.displayName = 'DataItem'; DataItem.displayName = 'DataItem';
const CustomTooltip = ({ active, payload }: any) => { const CustomTooltip = ({ active, payload }: any) => {
const data = payload?.[0]?.payload as UsersChartDataType; const data = payload?.[0]?.payload as chatDataType;
if (active && data) { if (active && data) {
return ( return (
<div <div
@@ -174,13 +199,16 @@ const CustomTooltip = ({ active, payload }: any) => {
}} }}
> >
<p className="label"> <p className="label">
count: <strong>{data.count}</strong> : <strong>{data.userCount}</strong>
</p> </p>
<p className="label"> <p className="label">
increase: <strong>{data.increase}</strong> 60: <strong>{data.payCount}</strong>
</p> </p>
<p className="label"> <p className="label">
increaseRate: <strong>{data.increaseRate}</strong> : <strong>{data.userIncrease}</strong>
</p>
<p className="label">
: <strong>{data.userIncreaseRate}</strong>
</p> </p>
</div> </div>
); );
@@ -188,7 +216,7 @@ const CustomTooltip = ({ active, payload }: any) => {
return null; return null;
}; };
const UserChart = ({ data }: { data: UsersChartDataType[] }) => { const UserChart = ({ data }: { data: chatDataType[] }) => {
return ( return (
<ResponsiveContainer width="100%" height={320}> <ResponsiveContainer width="100%" height={320}>
<AreaChart <AreaChart
@@ -198,14 +226,14 @@ const UserChart = ({ data }: { data: UsersChartDataType[] }) => {
margin={{ top: 10, right: 30, left: 0, bottom: 0 }} margin={{ top: 10, right: 30, left: 0, bottom: 0 }}
> >
<defs> <defs>
<linearGradient id="colorUv" x1="0" y1="0" x2="0" y2="1"> <linearGradient id="userCount" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#8884d8" stopOpacity={0.8} />
<stop offset="95%" stopColor="#8884d8" stopOpacity={0} />
</linearGradient>
<linearGradient id="colorPv" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#82ca9d" stopOpacity={0.8} /> <stop offset="5%" stopColor="#82ca9d" stopOpacity={0.8} />
<stop offset="95%" stopColor="#82ca9d" stopOpacity={0} /> <stop offset="95%" stopColor="#82ca9d" stopOpacity={0} />
</linearGradient> </linearGradient>
<linearGradient id="payCount" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#8884d8" stopOpacity={0.8} />
<stop offset="95%" stopColor="#8884d8" stopOpacity={0} />
</linearGradient>
</defs> </defs>
<XAxis dataKey="date" /> <XAxis dataKey="date" />
<YAxis /> <YAxis />
@@ -213,10 +241,17 @@ const UserChart = ({ data }: { data: UsersChartDataType[] }) => {
<Tooltip content={<CustomTooltip />} /> <Tooltip content={<CustomTooltip />} />
<Area <Area
type="monotone" type="monotone"
dataKey="count" dataKey="userCount"
stroke="#82ca9d" stroke="#82ca9d"
fillOpacity={1} fillOpacity={1}
fill="url(#colorPv)" fill="url(#userCount)"
/>
<Area
type="monotone"
dataKey="payCount"
stroke="#8884d8"
fillOpacity={1}
fill="url(#payCount)"
/> />
</AreaChart> </AreaChart>
</ResponsiveContainer> </ResponsiveContainer>