增加团队管理页面

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
} 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;
users: UserResponse[];
onOpenChange: (open: boolean) => void;
onSuccess?: () => void;
}
@ -83,6 +83,7 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
open,
teamId,
teamName,
users,
onOpenChange,
onSuccess
}) => {
@ -94,7 +95,6 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
const [editRecord, setEditRecord] = useState<TeamMemberResponse | null>(null);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [deleteRecord, setDeleteRecord] = useState<TeamMemberResponse | null>(null);
const [users, setUsers] = useState<UserResponse[]>([]);
// 分页状态
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 () => {
@ -153,7 +139,6 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
useEffect(() => {
if (open) {
loadUsers();
loadMembers();
}
}, [open, pageNum, pageSize]);
@ -287,7 +272,8 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
</div>
{/* 表格 */}
<div className="overflow-x-auto">
<div className="border rounded-lg overflow-hidden">
<div className="overflow-x-auto max-h-[50vh]">
<Table>
<TableHeader>
<TableRow>
@ -357,10 +343,11 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
)}
</TableBody>
</Table>
</div>
{/* 分页 */}
{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
pageIndex={pageNum}
pageSize={pageSize}
@ -371,7 +358,9 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
)}
</div>
</div>
</div>
) : (
<div className="px-6 pb-6">
<Form {...form}>
<form onSubmit={form.handleSubmit(handleSave)} className="space-y-4">
<FormField
@ -437,6 +426,7 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
</div>
</form>
</Form>
</div>
)}
</DialogContent>
</Dialog>

View File

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

View File

@ -5,6 +5,11 @@ import type { TeamResponse, TeamQuery } from './types';
import TeamModal from './components/TeamModal';
import DeleteDialog from './components/DeleteDialog';
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 {
Table,
TableHeader,
@ -45,6 +50,7 @@ import {
Activity,
Server,
Database,
Package,
} from 'lucide-react';
type Column = {
@ -67,6 +73,9 @@ const TeamList: React.FC = () => {
const [currentTeam, setCurrentTeam] = useState<TeamResponse | undefined>();
const [deleteDialogOpen, setDeleteDialogOpen] = 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>({
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(() => {
loadData();
}, [pagination.pageNum, pagination.pageSize]);
@ -138,6 +175,11 @@ const TeamList: React.FC = () => {
setMemberDialogOpen(true);
};
const handleManageApplications = (record: TeamResponse) => {
setCurrentTeam(record);
setApplicationDialogOpen(true);
};
const handleModalClose = () => {
setModalVisible(false);
setCurrentTeam(undefined);
@ -214,6 +256,10 @@ const TeamList: React.FC = () => {
<Users className="h-4 w-4 mr-1" />
</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)}>
<Edit className="h-4 w-4 mr-1" />
@ -394,13 +440,24 @@ const TeamList: React.FC = () => {
onSuccess={handleSuccess}
/>
{currentTeam && (
<>
<MemberManageDialog
open={memberDialogOpen}
teamId={currentTeam.id}
teamName={currentTeam.teamName}
users={users}
onOpenChange={setMemberDialogOpen}
onSuccess={handleSuccess}
/>
<ApplicationManageDialog
open={applicationDialogOpen}
teamId={currentTeam.id}
teamName={currentTeam.teamName}
applications={applications}
onOpenChange={setApplicationDialogOpen}
onSuccess={handleSuccess}
/>
</>
)}
</PageContainer>
);