增加团队管理页面

This commit is contained in:
dengqichen 2025-10-29 10:10:41 +08:00
parent 5eb830fe02
commit 7ab6d3354e
6 changed files with 168 additions and 35 deletions

View File

@ -0,0 +1,18 @@
import { z } from 'zod';
/**
*
*/
export const teamApplicationFormSchema = z.object({
teamId: z.number({
required_error: '团队ID不能为空',
invalid_type_error: '团队ID必须是数字',
}),
applicationId: z.number({
required_error: '请选择应用',
invalid_type_error: '应用ID必须是数字',
}),
});
export type TeamApplicationFormValues = z.infer<typeof teamApplicationFormSchema>;

View File

@ -0,0 +1,40 @@
import request from '@/utils/request';
import type { Page } from '@/types/base';
import type {
TeamApplicationQuery,
TeamApplicationResponse,
TeamApplicationRequest,
} from './types';
const BASE_URL = '/api/v1/team-applications';
/**
*
*/
export const getTeamApplications = (params?: TeamApplicationQuery) =>
request.get<Page<TeamApplicationResponse>>(`${BASE_URL}/page`, { params });
/**
* ID获取团队应用关联
*/
export const getTeamApplicationById = (id: number) =>
request.get<TeamApplicationResponse>(`${BASE_URL}/${id}`);
/**
*
*/
export const createTeamApplication = (data: TeamApplicationRequest) =>
request.post<TeamApplicationResponse>(BASE_URL, data);
/**
*
*/
export const updateTeamApplication = (id: number, data: TeamApplicationRequest) =>
request.put<TeamApplicationResponse>(`${BASE_URL}/${id}`, data);
/**
*
*/
export const deleteTeamApplication = (id: number) =>
request.delete<void>(`${BASE_URL}/${id}`);

View File

@ -0,0 +1,30 @@
import type { BaseResponse } from '@/types/base';
/**
*
*/
export interface TeamApplicationQuery {
teamId?: number;
applicationId?: number;
pageNum?: number;
pageSize?: number;
}
/**
*
*/
export interface TeamApplicationResponse extends BaseResponse {
teamId: number;
applicationId: number;
applicationName?: string;
applicationCode?: string;
}
/**
*
*/
export interface TeamApplicationRequest {
teamId: number;
applicationId: number;
}

View File

@ -65,13 +65,13 @@ import type {
TeamMemberQuery TeamMemberQuery
} from '../../Member/types'; } from '../../Member/types';
import type { Page } from '@/types/base'; import type { Page } from '@/types/base';
import { getUserList } from '@/pages/System/User/service';
import type { UserResponse } from '@/pages/System/User/types'; import type { UserResponse } from '@/pages/System/User/types';
interface MemberManageDialogProps { interface MemberManageDialogProps {
open: boolean; open: boolean;
teamId: number; teamId: number;
teamName: string; teamName: string;
users: UserResponse[];
onOpenChange: (open: boolean) => void; onOpenChange: (open: boolean) => void;
onSuccess?: () => void; onSuccess?: () => void;
} }
@ -83,6 +83,7 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
open, open,
teamId, teamId,
teamName, teamName,
users,
onOpenChange, onOpenChange,
onSuccess onSuccess
}) => { }) => {
@ -94,7 +95,6 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
const [editRecord, setEditRecord] = useState<TeamMemberResponse | null>(null); const [editRecord, setEditRecord] = useState<TeamMemberResponse | null>(null);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [deleteRecord, setDeleteRecord] = useState<TeamMemberResponse | null>(null); const [deleteRecord, setDeleteRecord] = useState<TeamMemberResponse | null>(null);
const [users, setUsers] = useState<UserResponse[]>([]);
// 分页状态 // 分页状态
const [pageNum, setPageNum] = useState(DEFAULT_CURRENT - 1); const [pageNum, setPageNum] = useState(DEFAULT_CURRENT - 1);
@ -110,20 +110,6 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
} }
}); });
// 加载用户列表
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 () => { const loadMembers = async () => {
@ -153,7 +139,6 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
useEffect(() => { useEffect(() => {
if (open) { if (open) {
loadUsers();
loadMembers(); loadMembers();
} }
}, [open, pageNum, pageSize]); }, [open, pageNum, pageSize]);
@ -287,7 +272,8 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
</div> </div>
{/* 表格 */} {/* 表格 */}
<div className="overflow-x-auto"> <div className="border rounded-lg overflow-hidden">
<div className="overflow-x-auto max-h-[50vh]">
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow> <TableRow>
@ -357,10 +343,11 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
)} )}
</TableBody> </TableBody>
</Table> </Table>
</div>
{/* 分页 */} {/* 分页 */}
{data && data.totalElements > 0 && ( {data && data.totalElements > 0 && (
<div className="flex justify-end py-4"> <div className="flex justify-end py-3 px-4 border-t bg-muted/40">
<DataTablePagination <DataTablePagination
pageIndex={pageNum} pageIndex={pageNum}
pageSize={pageSize} pageSize={pageSize}
@ -370,8 +357,10 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
</div> </div>
)} )}
</div> </div>
</div>
</div> </div>
) : ( ) : (
<div className="px-6 pb-6">
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(handleSave)} className="space-y-4"> <form onSubmit={form.handleSubmit(handleSave)} className="space-y-4">
<FormField <FormField
@ -437,6 +426,7 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
</div> </div>
</form> </form>
</Form> </Form>
</div>
)} )}
</DialogContent> </DialogContent>
</Dialog> </Dialog>

View File

@ -6,7 +6,6 @@ import {
DialogContent, DialogContent,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
DialogFooter,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
@ -24,7 +23,6 @@ import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { teamFormSchema, type TeamFormValues } from "../schema"; import { teamFormSchema, type TeamFormValues } from "../schema";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { ScrollArea } from "@/components/ui/scroll-area";
interface TeamModalProps { interface TeamModalProps {
open: boolean; open: boolean;
@ -111,14 +109,14 @@ const TeamModal: React.FC<TeamModalProps> = ({
return ( return (
<Dialog open={open} onOpenChange={(open) => !open && onCancel()}> <Dialog open={open} onOpenChange={(open) => !open && onCancel()}>
<DialogContent className="max-w-[600px] max-h-[85vh] flex flex-col"> <DialogContent className="max-w-[600px] max-h-[85vh] flex flex-col p-0">
<DialogHeader> <DialogHeader className="px-6 pt-6 pb-4">
<DialogTitle>{isEdit ? '编辑' : '新建'}</DialogTitle> <DialogTitle>{isEdit ? '编辑' : '新建'}</DialogTitle>
</DialogHeader> </DialogHeader>
<Form {...form}> <Form {...form}>
<form className="flex flex-col flex-1 overflow-hidden"> <form className="flex flex-col flex-1 overflow-hidden">
<ScrollArea className="flex-1 px-6"> <div className="flex-1 overflow-y-auto px-6">
<div className="space-y-4 pr-4"> <div className="space-y-4 pb-4">
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<FormField <FormField
control={form.control} control={form.control}
@ -254,8 +252,8 @@ const TeamModal: React.FC<TeamModalProps> = ({
<div className="h-6" /> <div className="h-6" />
</div> </div>
</ScrollArea> </div>
<DialogFooter className="px-6 py-4 border-t mt-0"> <div className="px-6 py-4 border-t bg-muted/30 flex justify-end gap-2">
<Button type="button" variant="outline" onClick={onCancel}> <Button type="button" variant="outline" onClick={onCancel}>
</Button> </Button>
@ -270,7 +268,7 @@ const TeamModal: React.FC<TeamModalProps> = ({
> >
</Button> </Button>
</DialogFooter> </div>
</form> </form>
</Form> </Form>
</DialogContent> </DialogContent>

View File

@ -5,6 +5,11 @@ import type { TeamResponse, TeamQuery } from './types';
import TeamModal from './components/TeamModal'; import TeamModal from './components/TeamModal';
import DeleteDialog from './components/DeleteDialog'; import DeleteDialog from './components/DeleteDialog';
import MemberManageDialog from './components/MemberManageDialog'; import MemberManageDialog from './components/MemberManageDialog';
import ApplicationManageDialog from './components/ApplicationManageDialog';
import { getUserList } from '@/pages/System/User/service';
import type { UserResponse } from '@/pages/System/User/types';
import { getApplicationList } from '@/pages/Deploy/Application/List/service';
import type { Application } from '@/pages/Deploy/Application/List/types';
import { import {
Table, Table,
TableHeader, TableHeader,
@ -45,6 +50,7 @@ import {
Activity, Activity,
Server, Server,
Database, Database,
Package,
} from 'lucide-react'; } from 'lucide-react';
type Column = { type Column = {
@ -67,6 +73,9 @@ const TeamList: React.FC = () => {
const [currentTeam, setCurrentTeam] = useState<TeamResponse | undefined>(); const [currentTeam, setCurrentTeam] = useState<TeamResponse | undefined>();
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [memberDialogOpen, setMemberDialogOpen] = useState(false); const [memberDialogOpen, setMemberDialogOpen] = useState(false);
const [applicationDialogOpen, setApplicationDialogOpen] = useState(false);
const [users, setUsers] = useState<UserResponse[]>([]);
const [applications, setApplications] = useState<Application[]>([]);
const form = useForm<SearchFormValues>({ const form = useForm<SearchFormValues>({
resolver: zodResolver(searchFormSchema), resolver: zodResolver(searchFormSchema),
@ -110,6 +119,34 @@ const TeamList: React.FC = () => {
} }
}; };
// 加载用户列表和应用列表(仅一次)
useEffect(() => {
loadUsers();
loadApplications();
}, []);
const loadUsers = async () => {
try {
const response = await getUserList();
if (response) {
setUsers(response);
}
} catch (error) {
console.error('加载用户列表失败:', error);
}
};
const loadApplications = async () => {
try {
const response = await getApplicationList();
if (response) {
setApplications(response);
}
} catch (error) {
console.error('加载应用列表失败:', error);
}
};
useEffect(() => { useEffect(() => {
loadData(); loadData();
}, [pagination.pageNum, pagination.pageSize]); }, [pagination.pageNum, pagination.pageSize]);
@ -138,6 +175,11 @@ const TeamList: React.FC = () => {
setMemberDialogOpen(true); setMemberDialogOpen(true);
}; };
const handleManageApplications = (record: TeamResponse) => {
setCurrentTeam(record);
setApplicationDialogOpen(true);
};
const handleModalClose = () => { const handleModalClose = () => {
setModalVisible(false); setModalVisible(false);
setCurrentTeam(undefined); setCurrentTeam(undefined);
@ -214,6 +256,10 @@ const TeamList: React.FC = () => {
<Users className="h-4 w-4 mr-1" /> <Users className="h-4 w-4 mr-1" />
</Button> </Button>
<Button variant="ghost" size="sm" onClick={() => handleManageApplications(row.original)}>
<Package className="h-4 w-4 mr-1" />
</Button>
<Button variant="ghost" size="sm" onClick={() => handleEdit(row.original)}> <Button variant="ghost" size="sm" onClick={() => handleEdit(row.original)}>
<Edit className="h-4 w-4 mr-1" /> <Edit className="h-4 w-4 mr-1" />
@ -394,13 +440,24 @@ const TeamList: React.FC = () => {
onSuccess={handleSuccess} onSuccess={handleSuccess}
/> />
{currentTeam && ( {currentTeam && (
<MemberManageDialog <>
open={memberDialogOpen} <MemberManageDialog
teamId={currentTeam.id} open={memberDialogOpen}
teamName={currentTeam.teamName} teamId={currentTeam.id}
onOpenChange={setMemberDialogOpen} teamName={currentTeam.teamName}
onSuccess={handleSuccess} users={users}
/> onOpenChange={setMemberDialogOpen}
onSuccess={handleSuccess}
/>
<ApplicationManageDialog
open={applicationDialogOpen}
teamId={currentTeam.id}
teamName={currentTeam.teamName}
applications={applications}
onOpenChange={setApplicationDialogOpen}
onSuccess={handleSuccess}
/>
</>
)} )}
</PageContainer> </PageContainer>
); );