diff --git a/video-gen-api/app/api/v1/team.py b/video-gen-api/app/api/v1/team.py index 86823ed9..29e6c982 100644 --- a/video-gen-api/app/api/v1/team.py +++ b/video-gen-api/app/api/v1/team.py @@ -253,3 +253,33 @@ async def handle_join_request( db, request_id, current_user.id, req.action, req.note ) return {"message": "ok"} + + +# ── 团队积分变动记录 ──────────────────────────────────── +@router.get("/credit-records") +async def list_team_credit_records( + page: int = Query(1, ge=1), + page_size: int = Query(20, ge=1, le=100), + user_id: str | None = Query(None), + record_type: str | None = Query(None, pattern="^(recharge|consume|refund|admin)$"), + start_date: str | None = Query(None), + end_date: str | None = Query(None), + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_db), +): + """查看团队所有成员的积分变动记录(仅管理人)。""" + team = await get_managed_team(db, current_user.id) + if not team: + raise HTTPException(status_code=403, detail="只有团队管理人可查看") + + from app.services.admin_credit_record_service import list_admin_credit_records + return await list_admin_credit_records( + db, + page=page, + page_size=page_size, + team_id=team.id, + user_id=user_id, + record_type=record_type, + start_date=start_date, + end_date=end_date, + ) diff --git a/video-gen-app/src/api/index.ts b/video-gen-app/src/api/index.ts index 2d4babd4..174f1bb2 100644 --- a/video-gen-app/src/api/index.ts +++ b/video-gen-app/src/api/index.ts @@ -782,3 +782,21 @@ export async function getJoinTeamInfo(code: string): Promise { export async function submitJoinRequest(code: string): Promise { await api.post('/team/join', { invitation_code: code }); } + +export async function getTeamCreditRecords(params: { + page?: number; + pageSize?: number; + userId?: string; + recordType?: string; + startDate?: string; + endDate?: string; +}): Promise { + const p = new URLSearchParams(); + if (params.page) p.set('page', String(params.page)); + if (params.pageSize) p.set('page_size', String(params.pageSize)); + if (params.userId) p.set('user_id', params.userId); + if (params.recordType) p.set('record_type', params.recordType); + if (params.startDate) p.set('start_date', params.startDate); + if (params.endDate) p.set('end_date', params.endDate); + return api.get(`/team/credit-records?${p.toString()}`); +} diff --git a/video-gen-app/src/components/Layout/AppLayout.tsx b/video-gen-app/src/components/Layout/AppLayout.tsx index 76e9c487..946cf92b 100644 --- a/video-gen-app/src/components/Layout/AppLayout.tsx +++ b/video-gen-app/src/components/Layout/AppLayout.tsx @@ -597,6 +597,7 @@ const AppLayout: React.FC = () => { { key: 'profile', icon: , label: `账号: ${user?.username}`, disabled: true }, { key: 'credits', icon: , label: `积分: ${user?.credits ?? 0}`, disabled: true }, { type: 'divider' as const }, + ...(user?.isTeamManager ? [{ key: 'teamManagement' as const, icon: , label: '团队管理' }] : []), { key: 'myCredits', icon: , label: '积分明细' }, { key: 'orderRecords', icon: , label: '订单记录' }, { key: 'messages', icon: , label: `消息中心${unreadCount > 0 ? `(${unreadCount})` : ''}` }, @@ -612,6 +613,7 @@ const AppLayout: React.FC = () => { else if (key === 'changePwd') { setPwdModalOpen(true); } else if (key === 'messages') { navigate('/messages'); } else if (key === 'recharge') { setRechargeModalOpen(true); } + else if (key === 'teamManagement') { navigate('/team-management'); } else if (key === 'myCredits') { navigate('/user-center?tab=credits'); } else if (key === 'orderRecords') { navigate('/user-center?tab=orders'); } else if (key === 'manual') { window.open(operationManualUrl, '_blank'); } @@ -837,16 +839,6 @@ const AppLayout: React.FC = () => {
{topLevelItems.map(item => renderMenuItem(item))} - {user?.isTeamManager && renderMenuItem({ - id: 'team-management', - key: 'team-management', - label: '团队管理', - path: '/team-management', - icon: 'TeamOutlined', - sortOrder: 999, - isActive: true, - menuType: 'page', - } as any)}
setRechargeModalOpen(true)} style={{ diff --git a/video-gen-app/src/pages/TeamManagementPage.tsx b/video-gen-app/src/pages/TeamManagementPage.tsx index 1532e11f..3030416d 100644 --- a/video-gen-app/src/pages/TeamManagementPage.tsx +++ b/video-gen-app/src/pages/TeamManagementPage.tsx @@ -3,11 +3,11 @@ import { Button, Card, Empty, Form, Input, InputNumber, message, Modal, Space, Table, Tabs, Tag, Tooltip, Typography, } from 'antd'; import { - CopyOutlined, PlusOutlined, ReloadOutlined, UserOutlined, HistoryOutlined, + CopyOutlined, PlusOutlined, ReloadOutlined, UserOutlined, HistoryOutlined, WalletOutlined, } from '@ant-design/icons'; import { createTeamInvitation, getJoinTeamInfo, getManagedTeam, getPendingJoinRequests, - getTeamInvitations, getTeamMembers, handleJoinRequest, revokeInvitation, submitJoinRequest, transferCredits, + getTeamCreditRecords, getTeamInvitations, getTeamMembers, handleJoinRequest, revokeInvitation, submitJoinRequest, transferCredits, } from '../api'; import type { ManagedTeam, TeamInvitation, TeamJoinRequest, TeamMember } from '../types'; import { formatDate } from '../utils/formatDate'; @@ -160,6 +160,32 @@ const TeamManagementPage: React.FC = () => { } }; + // ── Tab 4: 团队积分变动 ── + const [creditRecords, setCreditRecords] = useState([]); + const [creditTotal, setCreditTotal] = useState(0); + const [creditLoading, setCreditLoading] = useState(false); + const [creditPage, setCreditPage] = useState(1); + const [creditFilterType, setCreditFilterType] = useState(''); + + const loadCreditRecords = useCallback(async () => { + setCreditLoading(true); + try { + const res = await getTeamCreditRecords({ + page: creditPage, + pageSize: 20, + recordType: creditFilterType || undefined, + }); + setCreditRecords(res.items || []); + setCreditTotal(res.total || 0); + } catch (e: any) { + message.error(e?.message || '加载积分记录失败'); + } finally { + setCreditLoading(false); + } + }, [creditPage, creditFilterType]); + + useEffect(() => { loadCreditRecords(); }, [loadCreditRecords]); + const memberColumns = [ { title: '用户名', dataIndex: 'username', width: 150, render: (v: string) => {v} }, { title: '手机号', dataIndex: 'phone', width: 130, render: (v: string) => v || '-' }, @@ -285,6 +311,66 @@ const TeamManagementPage: React.FC = () => { /> ), }, + { + key: 'credits', + label: 团队积分变动, + children: ( +
+
+