增加团队管理页面
This commit is contained in:
parent
5eb830fe02
commit
7ab6d3354e
18
frontend/src/pages/Deploy/Team/Application/schema.ts
Normal file
18
frontend/src/pages/Deploy/Team/Application/schema.ts
Normal 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>;
|
||||
|
||||
40
frontend/src/pages/Deploy/Team/Application/service.ts
Normal file
40
frontend/src/pages/Deploy/Team/Application/service.ts
Normal 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}`);
|
||||
|
||||
30
frontend/src/pages/Deploy/Team/Application/types.ts
Normal file
30
frontend/src/pages/Deploy/Team/Application/types.ts
Normal 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;
|
||||
}
|
||||
|
||||
@ -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}
|
||||
@ -370,8 +357,10 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
|
||||
</div>
|
||||
)}
|
||||
</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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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}
|
||||
onOpenChange={setMemberDialogOpen}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
<>
|
||||
<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>
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user