diff --git a/video-gen-admin/src/api/index.ts b/video-gen-admin/src/api/index.ts index 7af2799a..dc40e4fc 100644 --- a/video-gen-admin/src/api/index.ts +++ b/video-gen-admin/src/api/index.ts @@ -180,6 +180,19 @@ export async function deleteAdminTeam(id: string): Promise { await api.delete(`/admin/teams/${id}`); } +export async function setTeamManager(teamId: string, userId: string | null): Promise { + return api.put(`/admin/teams/${teamId}/manager`, { user_id: userId }); +} + +export async function getTeamMembersForAdmin(teamId: string, page = 1, pageSize = 50): Promise<{ items: AdminUser[]; total: number }> { + const params = new URLSearchParams(); + params.set('page', String(page)); + params.set('page_size', String(pageSize)); + params.set('user_type', 'frontend'); + params.set('team_id', teamId); + return api.get(`/admin/users?${params.toString()}`); +} + export async function adjustCredits(userId: string, amount: number, description: string): Promise { await api.post(`/admin/users/${userId}/credits`, { amount, description }); } diff --git a/video-gen-admin/src/pages/AdminTeams.tsx b/video-gen-admin/src/pages/AdminTeams.tsx index e74faba0..55df1a30 100644 --- a/video-gen-admin/src/pages/AdminTeams.tsx +++ b/video-gen-admin/src/pages/AdminTeams.tsx @@ -1,8 +1,8 @@ import React, { useEffect, useMemo, useState } from 'react'; import { Button, Card, Form, Input, InputNumber, message, Modal, Popconfirm, Select, Space, Table, Tag, Typography } from 'antd'; -import { DeleteOutlined, EditOutlined, PlusOutlined, ReloadOutlined, SearchOutlined, TeamOutlined } from '@ant-design/icons'; -import { deleteAdminTeam, getAdminTeams, saveAdminTeam } from '../api'; -import type { AdminTeam } from '../types'; +import { DeleteOutlined, EditOutlined, PlusOutlined, ReloadOutlined, SearchOutlined, SettingOutlined, TeamOutlined, UserOutlined } from '@ant-design/icons'; +import { deleteAdminTeam, getAdminTeams, getTeamMembersForAdmin, saveAdminTeam, setTeamManager } from '../api'; +import type { AdminTeam, AdminUser } from '../types'; import { formatDate } from '../utils/formatDate'; const statusOptions = [ @@ -34,6 +34,12 @@ const AdminTeams: React.FC = () => { const [modal, setModal] = useState<{ open: boolean; item: AdminTeam | null }>({ open: false, item: null }); const [form] = Form.useForm(); + // 管理人弹窗状态 + const [managerModal, setManagerModal] = useState<{ open: boolean; team: AdminTeam | null }>({ open: false, team: null }); + const [members, setMembers] = useState([]); + const [managerLoading, setManagerLoading] = useState(false); + const [selectedManagerId, setSelectedManagerId] = useState(null); + const query = useMemo(() => ({ page, pageSize, @@ -107,6 +113,36 @@ const AdminTeams: React.FC = () => { } }; + const openManagerModal = async (team: AdminTeam) => { + setManagerModal({ open: true, team }); + setSelectedManagerId(team.managerId || null); + setManagerLoading(true); + try { + const res = await getTeamMembersForAdmin(team.id); + setMembers(res.items || []); + } catch (e: any) { + message.error(e?.message || '加载成员失败'); + setMembers([]); + } finally { + setManagerLoading(false); + } + }; + + const handleSetManager = async () => { + if (!managerModal.team) return; + try { + setManagerLoading(true); + await setTeamManager(managerModal.team.id, selectedManagerId); + message.success(selectedManagerId ? '已设置管理人' : '已取消管理人'); + setManagerModal({ open: false, team: null }); + load(); + } catch (e: any) { + message.error(e?.message || '设置失败'); + } finally { + setManagerLoading(false); + } + }; + const columns = [ { title: '团队名称', @@ -136,6 +172,16 @@ const AdminTeams: React.FC = () => { width: 100, render: (v: number) => {Number(v || 0).toLocaleString()}, }, + { + title: '管理人', + key: 'manager', + width: 140, + render: (_: any, r: AdminTeam) => ( + + {r.managerName || 未设置} + + ), + }, { title: '排序', dataIndex: 'sortOrder', @@ -161,6 +207,7 @@ const AdminTeams: React.FC = () => { fixed: 'right' as const, render: (_: any, r: AdminTeam) => ( + { + + {/* 设置管理人弹窗 */} + 设置团队管理人} + open={managerModal.open} + confirmLoading={managerLoading} + onOk={handleSetManager} + onCancel={() => { setManagerModal({ open: false, team: null }); setSelectedManagerId(null); }} + okText="保存" + cancelText="取消" + width={480} + > +
+ + 团队:{managerModal.team?.name || '-'} + + + 选择该团队的一名成员作为管理人。管理人可在前台管理团队成员和分配积分。 + + + + + +
+ ); +}; + +export default TeamManagementPage; diff --git a/video-gen-app/src/types/index.ts b/video-gen-app/src/types/index.ts index 38260eb0..ca8e2ef7 100644 --- a/video-gen-app/src/types/index.ts +++ b/video-gen-app/src/types/index.ts @@ -22,6 +22,61 @@ export interface User { allowedMenus?: string[] | null; mustSetPassword?: boolean; resourceCapacity?: ResourceCapacity; + teamId?: string | null; + teamName?: string | null; + isTeamManager?: boolean; +} + +// ── Team Management Types ───────────────────────────── + +export interface TeamMember { + id: string; + username: string; + phone?: string | null; + credits: number; + isActive: boolean; + joinedAt: string; +} + +export interface TeamInvitation { + id: string; + code: string; + status: string; + maxUses?: number | null; + useCount: number; + expiresAt?: string | null; + inviteLink: string; + createdAt?: string | null; +} + +export interface TeamJoinRequest { + id: string; + teamId: string; + teamName: string; + userId: string; + username: string; + phone?: string | null; + status: string; + note?: string | null; + createdAt?: string | null; +} + +export interface ManagedTeam { + id: string; + name: string; + code?: string | null; + description?: string | null; + status: string; + memberCount: number; + managerId?: string | null; + managerName?: string | null; +} + +export interface JoinTeamInfo { + teamName: string; + teamId: string; + valid: boolean; + alreadyInTeam: boolean; } export interface CreditRecord {