增加团队管理页面

This commit is contained in:
dengqichen 2025-10-29 10:32:02 +08:00
parent 7ab6d3354e
commit 0a5c60b888
6 changed files with 580 additions and 180 deletions

View File

@ -74,7 +74,7 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
appCode: "", appCode: "",
appName: "", appName: "",
appDesc: "", appDesc: "",
categoryId: undefined, applicationCategoryId: undefined,
language: DevelopmentLanguageTypeEnum.JAVA, language: DevelopmentLanguageTypeEnum.JAVA,
enabled: true, enabled: true,
sort: 0, sort: 0,
@ -131,7 +131,7 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
appCode: initialValues.appCode, appCode: initialValues.appCode,
appName: initialValues.appName, appName: initialValues.appName,
appDesc: initialValues.appDesc || "", appDesc: initialValues.appDesc || "",
categoryId: initialValues.categoryId, applicationCategoryId: initialValues.applicationCategoryId,
language: initialValues.language, language: initialValues.language,
enabled: initialValues.enabled, enabled: initialValues.enabled,
sort: initialValues.sort, sort: initialValues.sort,
@ -221,7 +221,7 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
<div className="grid grid-cols-3 gap-4"> <div className="grid grid-cols-3 gap-4">
<FormField <FormField
control={form.control} control={form.control}
name="categoryId" name="applicationCategoryId"
render={({field}) => ( render={({field}) => (
<FormItem> <FormItem>
<FormLabel></FormLabel> <FormLabel></FormLabel>

View File

@ -104,7 +104,7 @@ const ApplicationList: React.FC = () => {
try { try {
const queryParams: ApplicationQuery = { const queryParams: ApplicationQuery = {
...params, ...params,
categoryId: selectedCategoryId, applicationCategoryId: selectedCategoryId,
pageNum: pagination.pageNum, pageNum: pagination.pageNum,
pageSize: pagination.pageSize, pageSize: pagination.pageSize,
}; };

View File

@ -12,7 +12,7 @@ export const applicationFormSchema = z.object({
appCode: z.string().min(1, '应用编码不能为空'), appCode: z.string().min(1, '应用编码不能为空'),
appName: z.string().min(1, '应用名称不能为空'), appName: z.string().min(1, '应用名称不能为空'),
appDesc: z.string().max(200, "应用描述不能超过200个字符").optional(), appDesc: z.string().max(200, "应用描述不能超过200个字符").optional(),
categoryId: z.number({ applicationCategoryId: z.number({
required_error: '请选择应用分类', required_error: '请选择应用分类',
invalid_type_error: '应用分类必须是数字', invalid_type_error: '应用分类必须是数字',
}), }),

View File

@ -18,7 +18,7 @@ export interface Application extends BaseResponse {
enabled: boolean; enabled: boolean;
sort: number; sort: number;
teamCount?: number; teamCount?: number;
categoryId: number; applicationCategoryId: number;
category?: ApplicationCategoryResponse; category?: ApplicationCategoryResponse;
externalSystemId?: number; externalSystemId?: number;
externalSystem?: ExternalSystemResponse; externalSystem?: ExternalSystemResponse;
@ -66,7 +66,7 @@ export type ApplicationFormValues = CreateApplicationRequest;
export interface ApplicationQuery extends BaseQuery { export interface ApplicationQuery extends BaseQuery {
appCode?: string; appCode?: string;
appName?: string; appName?: string;
categoryId?: number; applicationCategoryId?: number;
enabled?: boolean; enabled?: boolean;
language?: string; language?: string;
} }

View File

@ -0,0 +1,427 @@
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 {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
} from "@/components/ui/command";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Checkbox } from "@/components/ui/checkbox";
import { Badge } from "@/components/ui/badge";
import { useToast } from "@/components/ui/use-toast";
import { DataTablePagination } from '@/components/ui/pagination';
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
import {
Plus,
Trash2,
Search,
Loader2,
Package,
Check,
ChevronsUpDown,
} from "lucide-react";
import {
getTeamApplications,
createTeamApplication,
deleteTeamApplication
} from '../../Application/service';
import type {
TeamApplicationResponse,
TeamApplicationQuery
} from '../../Application/types';
import type { Page } from '@/types/base';
import type { Application } from '@/pages/Deploy/Application/List/types';
interface ApplicationManageDialogProps {
open: boolean;
teamId: number;
teamName: string;
applications: Application[];
onOpenChange: (open: boolean) => void;
onSuccess?: () => void;
}
/**
*
*/
const ApplicationManageDialog: React.FC<ApplicationManageDialogProps> = ({
open,
teamId,
teamName,
applications,
onOpenChange,
onSuccess
}) => {
const { toast } = useToast();
const [loading, setLoading] = useState(false);
const [data, setData] = useState<Page<TeamApplicationResponse> | null>(null);
const [searchText, setSearchText] = useState('');
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [deleteRecord, setDeleteRecord] = useState<TeamApplicationResponse | null>(null);
const [selectedAppIds, setSelectedAppIds] = useState<number[]>([]);
const [popoverOpen, setPopoverOpen] = useState(false);
const [adding, setAdding] = useState(false);
// 分页状态
const [pageNum, setPageNum] = useState(DEFAULT_CURRENT - 1);
const [pageSize] = useState(DEFAULT_PAGE_SIZE);
// 加载应用列表
const loadApplications = async () => {
if (!teamId) return;
setLoading(true);
try {
const query: TeamApplicationQuery = {
teamId: teamId,
pageNum,
pageSize,
};
const result = await getTeamApplications(query);
setData(result);
} catch (error) {
console.error('加载应用失败:', error);
toast({
variant: "destructive",
title: "加载失败",
description: error instanceof Error ? error.message : '未知错误',
});
} finally {
setLoading(false);
}
};
useEffect(() => {
if (open) {
loadApplications();
}
}, [open, pageNum, pageSize]);
// 搜索
const handleSearch = () => {
setPageNum(0);
loadApplications();
};
// 切换应用选择
const toggleAppSelection = (appId: number) => {
setSelectedAppIds(prev =>
prev.includes(appId)
? prev.filter(id => id !== appId)
: [...prev, appId]
);
};
// 批量添加应用
const handleBatchAdd = async () => {
if (selectedAppIds.length === 0) {
toast({
variant: "destructive",
title: "请选择应用",
description: "至少选择一个应用进行添加",
});
return;
}
setAdding(true);
try {
// 并行创建所有关联
await Promise.all(
selectedAppIds.map(appId =>
createTeamApplication({
teamId,
applicationId: appId,
})
)
);
toast({
title: "添加成功",
description: `已成功添加 ${selectedAppIds.length} 个应用`,
});
setSelectedAppIds([]);
setPopoverOpen(false);
loadApplications();
onSuccess?.();
} catch (error) {
console.error('批量添加失败:', error);
toast({
variant: "destructive",
title: "添加失败",
description: error instanceof Error ? error.message : '未知错误',
});
} finally {
setAdding(false);
}
};
// 删除应用关联(打开确认对话框)
const handleDeleteClick = (record: TeamApplicationResponse) => {
setDeleteRecord(record);
setDeleteDialogOpen(true);
};
// 确认删除
const handleDeleteConfirm = async () => {
if (!deleteRecord) return;
try {
await deleteTeamApplication(deleteRecord.id);
toast({
title: "删除成功",
description: `应用 "${deleteRecord.applicationName}" 已移除`,
});
setDeleteDialogOpen(false);
setDeleteRecord(null);
loadApplications();
onSuccess?.();
} catch (error) {
console.error('删除失败:', error);
toast({
variant: "destructive",
title: "删除失败",
description: error instanceof Error ? error.message : '未知错误',
});
}
};
// 过滤出未关联的应用
const availableApplications = applications.filter(app =>
!data?.content?.some(item => item.applicationId === app.id)
);
return (
<>
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-4xl max-h-[85vh] flex flex-col p-0">
<DialogHeader className="px-6 pt-6 pb-4">
<DialogTitle className="flex items-center gap-2">
<Package className="h-5 w-5" />
- {teamName}
</DialogTitle>
</DialogHeader>
<div className="flex-1 overflow-hidden flex flex-col px-6 pb-6">
<div className="space-y-4 flex-1 overflow-y-auto">
{/* 操作栏 */}
<div className="flex items-center gap-2">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<Input
placeholder="搜索应用名称..."
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
className="pl-10"
/>
</div>
{/* 多选下拉框 */}
<Popover open={popoverOpen} onOpenChange={setPopoverOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={popoverOpen}
className="w-[280px] justify-between"
disabled={availableApplications.length === 0}
>
{selectedAppIds.length > 0 ? (
<span className="flex items-center gap-1">
<Badge variant="secondary" className="ml-1">{selectedAppIds.length}</Badge>
</span>
) : (
"选择应用..."
)}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[280px] p-0">
<Command>
<CommandInput placeholder="搜索应用..." />
<CommandEmpty></CommandEmpty>
<CommandGroup className="max-h-[300px] overflow-auto">
{availableApplications.map((app) => (
<CommandItem
key={app.id}
onSelect={() => toggleAppSelection(app.id)}
className="cursor-pointer"
>
<Checkbox
checked={selectedAppIds.includes(app.id)}
className="mr-2"
/>
<div className="flex flex-col flex-1">
<span className="font-medium">{app.appName}</span>
<span className="text-xs text-muted-foreground">{app.appCode}</span>
</div>
{selectedAppIds.includes(app.id) && (
<Check className="ml-auto h-4 w-4" />
)}
</CommandItem>
))}
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
{/* 批量添加按钮 */}
<Button
onClick={handleBatchAdd}
disabled={selectedAppIds.length === 0 || adding}
>
{adding ? (
<>
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
...
</>
) : (
<>
<Plus className="h-4 w-4 mr-2" />
</>
)}
</Button>
</div>
{/* 表格 */}
<div className="border rounded-lg overflow-hidden">
<div className="overflow-x-auto max-h-[50vh]">
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[100px]">ID</TableHead>
<TableHead className="w-[150px]"></TableHead>
<TableHead className="w-[200px]"></TableHead>
<TableHead className="w-[160px]"></TableHead>
<TableHead className="w-[80px] text-right"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{loading ? (
<TableRow>
<TableCell colSpan={5} className="text-center py-8">
<div className="flex items-center justify-center">
<Loader2 className="h-6 w-6 animate-spin mr-2" />
<span className="text-sm text-muted-foreground">...</span>
</div>
</TableCell>
</TableRow>
) : data?.content && data.content.length > 0 ? (
data.content.map((record) => (
<TableRow key={record.id}>
<TableCell className="font-medium">
{record.applicationId}
</TableCell>
<TableCell>
{record.applicationCode || '-'}
</TableCell>
<TableCell>
{record.applicationName || '-'}
</TableCell>
<TableCell>
{record.createTime || '-'}
</TableCell>
<TableCell className="text-right">
<div className="flex items-center justify-end gap-1">
<Button
variant="ghost"
size="sm"
className="h-8 w-8 p-0"
onClick={() => handleDeleteClick(record)}
>
<Trash2 className="h-3.5 w-3.5 text-destructive" />
</Button>
</div>
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={5} className="text-center py-12 text-muted-foreground">
<div className="flex flex-col items-center gap-3">
<Package className="h-12 w-12 opacity-20" />
<p className="text-sm"></p>
</div>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
{/* 分页 */}
{data && data.totalElements > 0 && (
<div className="flex justify-end py-3 px-4 border-t bg-muted/40">
<DataTablePagination
pageIndex={pageNum}
pageSize={pageSize}
pageCount={Math.ceil((data.totalElements || 0) / pageSize)}
onPageChange={(newPage) => setPageNum(newPage)}
/>
</div>
)}
</div>
</div>
</div>
</DialogContent>
</Dialog>
{/* 删除确认对话框 */}
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle></AlertDialogTitle>
<AlertDialogDescription>
"{deleteRecord?.applicationName}"
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel onClick={() => setDeleteDialogOpen(false)}>
</AlertDialogCancel>
<AlertDialogAction onClick={handleDeleteConfirm} className="bg-destructive text-destructive-foreground hover:bg-destructive/90">
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
);
};
export default ApplicationManageDialog;

View File

@ -17,13 +17,6 @@ import {
} from "@/components/ui/alert-dialog"; } from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { import {
Table, Table,
TableBody, TableBody,
@ -33,33 +26,36 @@ import {
TableRow, TableRow,
} from "@/components/ui/table"; } from "@/components/ui/table";
import { import {
Form, Command,
FormControl, CommandEmpty,
FormField, CommandGroup,
FormItem, CommandInput,
FormLabel, CommandItem,
FormMessage, } from "@/components/ui/command";
} from "@/components/ui/form"; import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Checkbox } from "@/components/ui/checkbox";
import { Badge } from "@/components/ui/badge";
import { useToast } from "@/components/ui/use-toast"; 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 { DataTablePagination } from '@/components/ui/pagination';
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page'; import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
import { import {
Plus, Plus,
Edit,
Trash2, Trash2,
Search, Search,
Loader2, Loader2,
Users, Users,
Check,
ChevronsUpDown,
} from "lucide-react"; } from "lucide-react";
import { import {
getTeamMembers, getTeamMembers,
createTeamMember, createTeamMember,
updateTeamMember,
deleteTeamMember deleteTeamMember
} from '../../Member/service'; } from '../../Member/service';
import { teamMemberFormSchema, type TeamMemberFormValues } from '../../Member/schema';
import type { import type {
TeamMemberResponse, TeamMemberResponse,
TeamMemberQuery TeamMemberQuery
@ -91,25 +87,16 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [data, setData] = useState<Page<TeamMemberResponse> | null>(null); const [data, setData] = useState<Page<TeamMemberResponse> | null>(null);
const [searchText, setSearchText] = useState(''); const [searchText, setSearchText] = useState('');
const [editMode, setEditMode] = useState(false);
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 [selectedUserIds, setSelectedUserIds] = useState<number[]>([]);
const [popoverOpen, setPopoverOpen] = useState(false);
const [adding, setAdding] = useState(false);
// 分页状态 // 分页状态
const [pageNum, setPageNum] = useState(DEFAULT_CURRENT - 1); const [pageNum, setPageNum] = useState(DEFAULT_CURRENT - 1);
const [pageSize] = useState(DEFAULT_PAGE_SIZE); const [pageSize] = useState(DEFAULT_PAGE_SIZE);
const form = useForm<TeamMemberFormValues>({
resolver: zodResolver(teamMemberFormSchema),
defaultValues: {
teamId: teamId,
userId: undefined,
userName: '',
roleInTeam: '',
}
});
// 加载成员列表 // 加载成员列表
const loadMembers = async () => { const loadMembers = async () => {
@ -149,28 +136,60 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
loadMembers(); loadMembers();
}; };
// 新建成员 // 切换用户选择
const handleCreate = () => { const toggleUserSelection = (userId: number) => {
setEditRecord(null); setSelectedUserIds(prev =>
form.reset({ prev.includes(userId)
teamId: teamId, ? prev.filter(id => id !== userId)
userId: undefined, : [...prev, userId]
userName: '', );
roleInTeam: '',
});
setEditMode(true);
}; };
// 编辑成员 // 批量添加成员
const handleEdit = (record: TeamMemberResponse) => { const handleBatchAdd = async () => {
setEditRecord(record); if (selectedUserIds.length === 0) {
form.reset({ toast({
teamId: record.teamId, variant: "destructive",
userId: record.userId, title: "请选择成员",
userName: record.userName || '', description: "至少选择一个用户进行添加",
roleInTeam: record.roleInTeam || '',
}); });
setEditMode(true); return;
}
setAdding(true);
try {
// 并行创建所有成员关联
await Promise.all(
selectedUserIds.map(userId => {
const user = users.find(u => u.id === userId);
return createTeamMember({
teamId,
userId,
userName: user?.nickname || user?.username || '',
roleInTeam: '', // 批量添加时角色为空
});
})
);
toast({
title: "添加成功",
description: `已成功添加 ${selectedUserIds.length} 个成员`,
});
setSelectedUserIds([]);
setPopoverOpen(false);
loadMembers();
onSuccess?.();
} catch (error) {
console.error('批量添加失败:', error);
toast({
variant: "destructive",
title: "添加失败",
description: error instanceof Error ? error.message : '未知错误',
});
} finally {
setAdding(false);
}
}; };
// 删除成员(打开确认对话框) // 删除成员(打开确认对话框)
@ -203,41 +222,10 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
} }
}; };
// 保存表单 // 过滤出未加入的用户
const handleSave = async (values: TeamMemberFormValues) => { const availableUsers = users.filter(user =>
try { !data?.content?.some(member => member.userId === user.id)
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 ( return (
<> <>
@ -250,10 +238,9 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
</DialogTitle> </DialogTitle>
</DialogHeader> </DialogHeader>
{!editMode ? (
<div className="flex-1 overflow-hidden flex flex-col px-6 pb-6"> <div className="flex-1 overflow-hidden flex flex-col px-6 pb-6">
<div className="space-y-4 flex-1 overflow-y-auto"> <div className="space-y-4 flex-1 overflow-y-auto">
{/* 搜索栏 */} {/* 操作栏 */}
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="relative flex-1"> <div className="relative flex-1">
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" /> <Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
@ -265,9 +252,72 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
className="pl-10" className="pl-10"
/> />
</div> </div>
<Button onClick={handleCreate}>
{/* 多选下拉框 */}
<Popover open={popoverOpen} onOpenChange={setPopoverOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={popoverOpen}
className="w-[280px] justify-between"
disabled={availableUsers.length === 0}
>
{selectedUserIds.length > 0 ? (
<span className="flex items-center gap-1">
<Badge variant="secondary" className="ml-1">{selectedUserIds.length}</Badge>
</span>
) : (
"选择成员..."
)}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[280px] p-0">
<Command>
<CommandInput placeholder="搜索用户..." />
<CommandEmpty></CommandEmpty>
<CommandGroup className="max-h-[300px] overflow-auto">
{availableUsers.map((user) => (
<CommandItem
key={user.id}
onSelect={() => toggleUserSelection(user.id)}
className="cursor-pointer"
>
<Checkbox
checked={selectedUserIds.includes(user.id)}
className="mr-2"
/>
<div className="flex flex-col flex-1">
<span className="font-medium">{user.nickname || user.username}</span>
<span className="text-xs text-muted-foreground">{user.username}</span>
</div>
{selectedUserIds.includes(user.id) && (
<Check className="ml-auto h-4 w-4" />
)}
</CommandItem>
))}
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
{/* 批量添加按钮 */}
<Button
onClick={handleBatchAdd}
disabled={selectedUserIds.length === 0 || adding}
>
{adding ? (
<>
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
...
</>
) : (
<>
<Plus className="h-4 w-4 mr-2" /> <Plus className="h-4 w-4 mr-2" />
</>
)}
</Button> </Button>
</div> </div>
@ -280,8 +330,8 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
<TableHead className="w-[100px]">ID</TableHead> <TableHead className="w-[100px]">ID</TableHead>
<TableHead className="w-[150px]"></TableHead> <TableHead className="w-[150px]"></TableHead>
<TableHead className="w-[150px]"></TableHead> <TableHead className="w-[150px]"></TableHead>
<TableHead className="w-[180px]"></TableHead> <TableHead className="w-[160px]"></TableHead>
<TableHead className="w-[100px] text-right"></TableHead> <TableHead className="w-[80px] text-right"></TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
@ -311,14 +361,6 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
</TableCell> </TableCell>
<TableCell className="text-right"> <TableCell className="text-right">
<div className="flex items-center justify-end gap-1"> <div className="flex items-center justify-end gap-1">
<Button
variant="ghost"
size="sm"
className="h-8 w-8 p-0"
onClick={() => handleEdit(record)}
>
<Edit className="h-3.5 w-3.5" />
</Button>
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
@ -359,75 +401,6 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
</div> </div>
</div> </div>
</div> </div>
) : (
<div className="px-6 pb-6">
<Form {...form}>
<form onSubmit={form.handleSubmit(handleSave)} className="space-y-4">
<FormField
control={form.control}
name="userId"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<Select
onValueChange={(value) => {
const userId = Number(value);
const selectedUser = users.find(u => u.id === userId);
field.onChange(userId);
if (selectedUser) {
form.setValue('userName', selectedUser.nickname || selectedUser.username);
}
}}
value={field.value?.toString()}
disabled={!!editRecord}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="请选择用户" />
</SelectTrigger>
</FormControl>
<SelectContent>
{users.map((user) => (
<SelectItem key={user.id} value={user.id.toString()}>
{user.nickname || user.username} ({user.username})
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="roleInTeam"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Input
placeholder="请输入团队角色,如:开发、测试、产品经理等"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end gap-2 pt-4">
<Button type="button" variant="outline" onClick={handleCancel}>
</Button>
<Button type="submit">
{editRecord ? '更新' : '添加'}
</Button>
</div>
</form>
</Form>
</div>
)}
</DialogContent> </DialogContent>
</Dialog> </Dialog>