mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-27 08:25:07 +00:00
feat: admin
This commit is contained in:
@@ -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)
|
||||||
|
@@ -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' });
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@@ -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 () => {
|
||||||
headers
|
const [userResponse, payResponse]: fetchChatData[][] = await Promise.all([
|
||||||
}).then((res) => res.json());
|
fetch(`${baseUrl}/users/data`, {
|
||||||
setUsersData(
|
headers
|
||||||
userResponse.map((item) => ({
|
}).then((res) => res.json()),
|
||||||
...item,
|
fetch(`${baseUrl}/pays/data`, {
|
||||||
date: dayjs(item.date).format('MM/DD')
|
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();
|
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>
|
||||||
|
Reference in New Issue
Block a user