From a10d0e49fe03af557678bce5ec7a2eac81628731 Mon Sep 17 00:00:00 2001 From: "mula.liu" Date: Sun, 30 Nov 2025 23:25:56 +0800 Subject: [PATCH] feat: Update admin dashboard to show registered user count and set default date range for NASA data download to current month --- backend/app/api/user.py | 17 ++++++++-- frontend/src/pages/admin/Dashboard.tsx | 40 ++++++++++++++++++----- frontend/src/pages/admin/NASADownload.tsx | 4 +-- 3 files changed, 49 insertions(+), 12 deletions(-) diff --git a/backend/app/api/user.py b/backend/app/api/user.py index 8e6f7a6..1f66a4a 100644 --- a/backend/app/api/user.py +++ b/backend/app/api/user.py @@ -1,14 +1,14 @@ from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload -from sqlalchemy import select +from sqlalchemy import select, func from typing import List from pydantic import BaseModel from app.database import get_db from app.models.db import User from app.services.auth import hash_password -from app.services.auth_deps import get_current_user # To protect endpoints +from app.services.auth_deps import get_current_user, get_current_admin_user # To protect endpoints router = APIRouter(prefix="/users", tags=["users"]) @@ -105,3 +105,16 @@ async def reset_user_password( await db.commit() return {"message": f"Password for user {user.username} has been reset."} + +@router.get("/count", response_model=dict) +async def get_user_count( + db: AsyncSession = Depends(get_db), + current_admin_user: User = Depends(get_current_admin_user) # Ensure only admin can access +): + """ + Get the total count of registered users. + """ + result = await db.execute(select(func.count(User.id))) + total_users = result.scalar_one() + return {"total_users": total_users} + diff --git a/frontend/src/pages/admin/Dashboard.tsx b/frontend/src/pages/admin/Dashboard.tsx index 7b7ba06..641cae4 100644 --- a/frontend/src/pages/admin/Dashboard.tsx +++ b/frontend/src/pages/admin/Dashboard.tsx @@ -1,10 +1,33 @@ /** * Dashboard Page */ -import { Card, Row, Col, Statistic } from 'antd'; -import { DatabaseOutlined, GlobalOutlined, RocketOutlined } from '@ant-design/icons'; +import { Card, Row, Col, Statistic, message } from 'antd'; +import { GlobalOutlined, RocketOutlined, UserOutlined } from '@ant-design/icons'; +import { useEffect, useState } from 'react'; +import { request } from '../../utils/request'; export function Dashboard() { + const [totalUsers, setTotalUsers] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchUserCount = async () => { + try { + setLoading(true); + // Assuming '/users/count' is the new endpoint we just created in the backend + const response = await request.get('/users/count'); + setTotalUsers(response.data.total_users); + } catch (error) { + console.error('Failed to fetch user count:', error); + message.error('无法获取用户总数'); + setTotalUsers(0); // Set to 0 or handle error display + } finally { + setLoading(false); + } + }; + fetchUserCount(); + }, []); // Run once on mount + return (

控制台

@@ -13,7 +36,7 @@ export function Dashboard() { } /> @@ -22,7 +45,7 @@ export function Dashboard() { } /> @@ -30,13 +53,14 @@ export function Dashboard() { } + title="注册用户数" + value={totalUsers !== null ? totalUsers : '-'} + loading={loading} + prefix={} />
); -} +} \ No newline at end of file diff --git a/frontend/src/pages/admin/NASADownload.tsx b/frontend/src/pages/admin/NASADownload.tsx index b3a27d3..bba2ce0 100644 --- a/frontend/src/pages/admin/NASADownload.tsx +++ b/frontend/src/pages/admin/NASADownload.tsx @@ -56,8 +56,8 @@ export function NASADownload() { const [bodies, setBodies] = useState({}); const [selectedBodies, setSelectedBodies] = useState([]); const [dateRange, setDateRange] = useState<[Dayjs, Dayjs]>([ - dayjs().subtract(1, 'month').startOf('month'), - dayjs().subtract(1, 'month').endOf('month') + dayjs().startOf('month'), + dayjs().endOf('month') ]); const [availableDates, setAvailableDates] = useState>(new Set()); const [loadingDates, setLoadingDates] = useState(false);