From 5eb830fe027e582dc8f98edbd476736adf114564 Mon Sep 17 00:00:00 2001 From: dengqichen Date: Wed, 29 Oct 2025 09:40:37 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=9B=A2=E9=98=9F=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/Deploy/Application/List/index.tsx | 10 + .../pages/Deploy/Application/List/types.ts | 1 + .../Team/List/components/DeleteDialog.tsx | 124 +++++ .../List/components/MemberManageDialog.tsx | 468 ++++++++++++++++++ .../Deploy/Team/List/components/TeamModal.tsx | 282 +++++++++++ frontend/src/pages/Deploy/Team/List/index.tsx | 410 +++++++++++++++ frontend/src/pages/Deploy/Team/List/schema.ts | 27 + .../src/pages/Deploy/Team/List/service.ts | 46 ++ frontend/src/pages/Deploy/Team/List/types.ts | 39 ++ .../src/pages/Deploy/Team/Member/schema.ts | 17 + .../src/pages/Deploy/Team/Member/service.ts | 40 ++ .../src/pages/Deploy/Team/Member/types.ts | 32 ++ frontend/src/pages/System/User/service.ts | 4 + frontend/src/router/index.tsx | 5 + 14 files changed, 1505 insertions(+) create mode 100644 frontend/src/pages/Deploy/Team/List/components/DeleteDialog.tsx create mode 100644 frontend/src/pages/Deploy/Team/List/components/MemberManageDialog.tsx create mode 100644 frontend/src/pages/Deploy/Team/List/components/TeamModal.tsx create mode 100644 frontend/src/pages/Deploy/Team/List/index.tsx create mode 100644 frontend/src/pages/Deploy/Team/List/schema.ts create mode 100644 frontend/src/pages/Deploy/Team/List/service.ts create mode 100644 frontend/src/pages/Deploy/Team/List/types.ts create mode 100644 frontend/src/pages/Deploy/Team/Member/schema.ts create mode 100644 frontend/src/pages/Deploy/Team/Member/service.ts create mode 100644 frontend/src/pages/Deploy/Team/Member/types.ts diff --git a/frontend/src/pages/Deploy/Application/List/index.tsx b/frontend/src/pages/Deploy/Application/List/index.tsx index 64261be5..d87d9e2b 100644 --- a/frontend/src/pages/Deploy/Application/List/index.tsx +++ b/frontend/src/pages/Deploy/Application/List/index.tsx @@ -226,6 +226,16 @@ const ApplicationList: React.FC = () => { header: '应用描述', size: 180, }, + { + id: 'teamCount', + header: '团队数量', + size: 100, + cell: ({ row }) => ( +
+ {row.original.teamCount || 0} +
+ ), + }, { id: 'externalSystem', header: '外部系统', diff --git a/frontend/src/pages/Deploy/Application/List/types.ts b/frontend/src/pages/Deploy/Application/List/types.ts index 742fa017..ceb8af07 100644 --- a/frontend/src/pages/Deploy/Application/List/types.ts +++ b/frontend/src/pages/Deploy/Application/List/types.ts @@ -17,6 +17,7 @@ export interface Application extends BaseResponse { language: DevelopmentLanguageTypeEnum; enabled: boolean; sort: number; + teamCount?: number; categoryId: number; category?: ApplicationCategoryResponse; externalSystemId?: number; diff --git a/frontend/src/pages/Deploy/Team/List/components/DeleteDialog.tsx b/frontend/src/pages/Deploy/Team/List/components/DeleteDialog.tsx new file mode 100644 index 00000000..e516e417 --- /dev/null +++ b/frontend/src/pages/Deploy/Team/List/components/DeleteDialog.tsx @@ -0,0 +1,124 @@ +import React from 'react'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { AlertCircle } from 'lucide-react'; +import { useToast } from '@/components/ui/use-toast'; +import { deleteTeam } from '../service'; +import type { TeamResponse } from '../types'; + +interface DeleteDialogProps { + open: boolean; + record?: TeamResponse; + onOpenChange: (open: boolean) => void; + onSuccess: () => void; +} + +/** + * 团队删除确认对话框 + */ +const DeleteDialog: React.FC = ({ + open, + record, + onOpenChange, + onSuccess, +}) => { + const { toast } = useToast(); + + const handleConfirm = async () => { + if (!record) return; + + try { + await deleteTeam(record.id); + toast({ title: '删除成功', description: `团队 "${record.teamName}" 已删除` }); + onSuccess(); + onOpenChange(false); + } catch (error) { + console.error('删除失败:', error); + toast({ + title: '删除失败', + description: error instanceof Error ? error.message : '未知错误', + variant: 'destructive' + }); + } + }; + + if (!record) return null; + + return ( + + + + + + 确认删除 + + + 此操作将永久删除该团队,该操作无法撤销。 + + + +
+
+
+ 团队编码: + + {record.teamCode} + +
+
+ 团队名称: + {record.teamName} +
+ {record.ownerName && ( +
+ 负责人: + {record.ownerName} +
+ )} + {record.memberCount !== undefined && record.memberCount > 0 && ( +
+ 成员数量: + {record.memberCount} 人 +
+ )} + {record.applicationCount !== undefined && record.applicationCount > 0 && ( +
+ 关联应用: + {record.applicationCount} 个 +
+ )} +
+ + {((record.memberCount !== undefined && record.memberCount > 0) || + (record.applicationCount !== undefined && record.applicationCount > 0)) && ( +
+ +

+ 警告:该团队还有{record.memberCount || 0}名成员和{record.applicationCount || 0}个关联应用,删除后将影响这些关联数据。 +

+
+ )} +
+ + + + + +
+
+ ); +}; + +export default DeleteDialog; + diff --git a/frontend/src/pages/Deploy/Team/List/components/MemberManageDialog.tsx b/frontend/src/pages/Deploy/Team/List/components/MemberManageDialog.tsx new file mode 100644 index 00000000..b7a1e018 --- /dev/null +++ b/frontend/src/pages/Deploy/Team/List/components/MemberManageDialog.tsx @@ -0,0 +1,468 @@ +import React, { useState, useEffect } from 'react'; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { useToast } from "@/components/ui/use-toast"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { DataTablePagination } from '@/components/ui/pagination'; +import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page'; +import { + Plus, + Edit, + Trash2, + Search, + Loader2, + Users, +} from "lucide-react"; +import { + getTeamMembers, + createTeamMember, + updateTeamMember, + deleteTeamMember +} from '../../Member/service'; +import { teamMemberFormSchema, type TeamMemberFormValues } from '../../Member/schema'; +import type { + TeamMemberResponse, + TeamMemberQuery +} from '../../Member/types'; +import type { Page } from '@/types/base'; +import { getUserList } from '@/pages/System/User/service'; +import type { UserResponse } from '@/pages/System/User/types'; + +interface MemberManageDialogProps { + open: boolean; + teamId: number; + teamName: string; + onOpenChange: (open: boolean) => void; + onSuccess?: () => void; +} + +/** + * 团队成员管理对话框 + */ +const MemberManageDialog: React.FC = ({ + open, + teamId, + teamName, + onOpenChange, + onSuccess +}) => { + const { toast } = useToast(); + const [loading, setLoading] = useState(false); + const [data, setData] = useState | null>(null); + const [searchText, setSearchText] = useState(''); + const [editMode, setEditMode] = useState(false); + const [editRecord, setEditRecord] = useState(null); + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const [deleteRecord, setDeleteRecord] = useState(null); + const [users, setUsers] = useState([]); + + // 分页状态 + const [pageNum, setPageNum] = useState(DEFAULT_CURRENT - 1); + const [pageSize] = useState(DEFAULT_PAGE_SIZE); + + const form = useForm({ + resolver: zodResolver(teamMemberFormSchema), + defaultValues: { + teamId: teamId, + userId: undefined, + userName: '', + roleInTeam: '', + } + }); + + // 加载用户列表 + const loadUsers = async () => { + try { + const result = await getUserList(); + setUsers(result || []); + } catch (error) { + console.error('加载用户列表失败:', error); + toast({ + variant: "destructive", + title: "加载用户列表失败", + description: error instanceof Error ? error.message : '未知错误', + }); + } + }; + + // 加载成员列表 + const loadMembers = async () => { + if (!teamId) return; + + setLoading(true); + try { + const query: TeamMemberQuery = { + teamId: teamId, + userName: searchText || undefined, + pageNum, + pageSize, + }; + const result = await getTeamMembers(query); + setData(result); + } catch (error) { + console.error('加载成员失败:', error); + toast({ + variant: "destructive", + title: "加载失败", + description: error instanceof Error ? error.message : '未知错误', + }); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + if (open) { + loadUsers(); + loadMembers(); + } + }, [open, pageNum, pageSize]); + + // 搜索 + const handleSearch = () => { + setPageNum(0); + loadMembers(); + }; + + // 新建成员 + const handleCreate = () => { + setEditRecord(null); + form.reset({ + teamId: teamId, + userId: undefined, + userName: '', + roleInTeam: '', + }); + setEditMode(true); + }; + + // 编辑成员 + const handleEdit = (record: TeamMemberResponse) => { + setEditRecord(record); + form.reset({ + teamId: record.teamId, + userId: record.userId, + userName: record.userName || '', + roleInTeam: record.roleInTeam || '', + }); + setEditMode(true); + }; + + // 删除成员(打开确认对话框) + const handleDeleteClick = (record: TeamMemberResponse) => { + setDeleteRecord(record); + setDeleteDialogOpen(true); + }; + + // 确认删除 + const handleDeleteConfirm = async () => { + if (!deleteRecord) return; + + try { + await deleteTeamMember(deleteRecord.id); + toast({ + title: "删除成功", + description: `成员 "${deleteRecord.userName}" 已移除`, + }); + setDeleteDialogOpen(false); + setDeleteRecord(null); + loadMembers(); + onSuccess?.(); + } catch (error) { + console.error('删除失败:', error); + toast({ + variant: "destructive", + title: "删除失败", + description: error instanceof Error ? error.message : '未知错误', + }); + } + }; + + // 保存表单 + const handleSave = async (values: TeamMemberFormValues) => { + try { + if (editRecord) { + await updateTeamMember(editRecord.id, values); + toast({ + title: "更新成功", + description: `成员 "${values.userName}" 已更新`, + }); + } else { + await createTeamMember(values); + toast({ + title: "添加成功", + description: `成员 "${values.userName}" 已添加`, + }); + } + setEditMode(false); + loadMembers(); + onSuccess?.(); + } catch (error) { + console.error('保存失败:', error); + toast({ + variant: "destructive", + title: "保存失败", + description: error instanceof Error ? error.message : '未知错误', + }); + } + }; + + // 取消编辑 + const handleCancel = () => { + setEditMode(false); + setEditRecord(null); + form.reset(); + }; + + return ( + <> + + + + + + 团队成员管理 - {teamName} + + + + {!editMode ? ( +
+
+ {/* 搜索栏 */} +
+
+ + setSearchText(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleSearch()} + className="pl-10" + /> +
+ +
+ + {/* 表格 */} +
+ + + + 用户ID + 用户名 + 团队角色 + 加入时间 + 操作 + + + + {loading ? ( + + +
+ + 加载中... +
+
+
+ ) : data?.content && data.content.length > 0 ? ( + data.content.map((record) => ( + + + {record.userId} + + + {record.userName || '-'} + + + {record.roleInTeam || '-'} + + + {record.joinTime || '-'} + + +
+ + +
+
+
+ )) + ) : ( + + +
+ +

暂无成员

+
+
+
+ )} +
+
+ + {/* 分页 */} + {data && data.totalElements > 0 && ( +
+ setPageNum(newPage)} + /> +
+ )} +
+
+ ) : ( +
+ + ( + + 选择用户 + + + + )} + /> + + ( + + 团队角色 + + + + + + )} + /> + +
+ + +
+ + + )} + +
+ + {/* 删除确认对话框 */} + + + + 确认删除 + + 确定要将成员 "{deleteRecord?.userName}" 从团队中移除吗?此操作无法撤销。 + + + + setDeleteDialogOpen(false)}> + 取消 + + + 确认删除 + + + + + + ); +}; + +export default MemberManageDialog; + diff --git a/frontend/src/pages/Deploy/Team/List/components/TeamModal.tsx b/frontend/src/pages/Deploy/Team/List/components/TeamModal.tsx new file mode 100644 index 00000000..f352ad59 --- /dev/null +++ b/frontend/src/pages/Deploy/Team/List/components/TeamModal.tsx @@ -0,0 +1,282 @@ +import React, { useEffect } from 'react'; +import type { TeamResponse } from '../types'; +import { createTeam, updateTeam } from '../service'; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Switch } from "@/components/ui/switch"; +import { useToast } from "@/components/ui/use-toast"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { teamFormSchema, type TeamFormValues } from "../schema"; +import { Textarea } from "@/components/ui/textarea"; +import { ScrollArea } from "@/components/ui/scroll-area"; + +interface TeamModalProps { + open: boolean; + onCancel: () => void; + onSuccess: () => void; + initialValues?: TeamResponse; +} + +const TeamModal: React.FC = ({ + open, + onCancel, + onSuccess, + initialValues, +}) => { + const { toast } = useToast(); + const isEdit = !!initialValues?.id; + + const form = useForm({ + resolver: zodResolver(teamFormSchema), + defaultValues: { + teamCode: "", + teamName: "", + description: "", + ownerId: undefined, + ownerName: "", + enabled: true, + sort: 0, + }, + }); + + useEffect(() => { + if (open && initialValues) { + form.reset({ + teamCode: initialValues.teamCode, + teamName: initialValues.teamName, + description: initialValues.description || "", + ownerId: initialValues.ownerId, + ownerName: initialValues.ownerName || "", + enabled: initialValues.enabled, + sort: initialValues.sort, + }); + } else if (open && !initialValues) { + form.reset({ + teamCode: "", + teamName: "", + description: "", + ownerId: undefined, + ownerName: "", + enabled: true, + sort: 0, + }); + } + }, [open, initialValues, form]); + + const handleSubmit = async (values: TeamFormValues) => { + try { + if (isEdit) { + await updateTeam(initialValues!.id, values as any); + toast({ + title: "更新成功", + description: `团队 "${values.teamName}" 已更新`, + duration: 3000, + }); + } else { + await createTeam(values as any); + toast({ + title: "创建成功", + description: `团队 "${values.teamName}" 已创建`, + duration: 3000, + }); + } + onSuccess(); + onCancel(); + } catch (error) { + console.error(error); + toast({ + variant: "destructive", + title: isEdit ? "更新失败" : "创建失败", + description: error instanceof Error ? error.message : "未知错误", + duration: 3000, + }); + } + }; + + return ( + !open && onCancel()}> + + + {isEdit ? '编辑' : '新建'}团队 + +
+ + +
+
+ ( + + 团队编码 + + + + + + )} + /> + + ( + + 团队名称 + + + + + + )} + /> +
+ +
+ ( + + 负责人ID + + { + const value = e.target.value; + field.onChange(value ? Number(value) : undefined); + }} + /> + + + + )} + /> + + ( + + 负责人姓名 + + + + + + )} + /> +
+ + ( + + 团队描述 + +