增加团队管理页面
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
|
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}
|
||||||
@ -371,7 +358,9 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
|
|||||||
)}
|
)}
|
||||||
</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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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
|
<MemberManageDialog
|
||||||
open={memberDialogOpen}
|
open={memberDialogOpen}
|
||||||
teamId={currentTeam.id}
|
teamId={currentTeam.id}
|
||||||
teamName={currentTeam.teamName}
|
teamName={currentTeam.teamName}
|
||||||
|
users={users}
|
||||||
onOpenChange={setMemberDialogOpen}
|
onOpenChange={setMemberDialogOpen}
|
||||||
onSuccess={handleSuccess}
|
onSuccess={handleSuccess}
|
||||||
/>
|
/>
|
||||||
|
<ApplicationManageDialog
|
||||||
|
open={applicationDialogOpen}
|
||||||
|
teamId={currentTeam.id}
|
||||||
|
teamName={currentTeam.teamName}
|
||||||
|
applications={applications}
|
||||||
|
onOpenChange={setApplicationDialogOpen}
|
||||||
|
onSuccess={handleSuccess}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user