重构消息通知弹窗
This commit is contained in:
parent
cb5741f487
commit
763d14f3d3
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useMemo, useRef } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@ -8,14 +8,7 @@ import {
|
||||
import { ConfirmDialog } from '@/components/ui/confirm-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 { PaginatedTable, type ColumnDef, type SearchFieldDef, type PaginatedTableRef } from '@/components/ui/paginated-table';
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
@ -31,13 +24,10 @@ import {
|
||||
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_PAGE_NUM } from '@/utils/page';
|
||||
import {
|
||||
Plus,
|
||||
Edit,
|
||||
Trash2,
|
||||
Search,
|
||||
Loader2,
|
||||
Users,
|
||||
Check,
|
||||
@ -53,7 +43,6 @@ import type {
|
||||
TeamMemberResponse,
|
||||
TeamMemberQuery
|
||||
} from '../../Member/types';
|
||||
import type { Page } from '@/types/base';
|
||||
import type { UserResponse } from '@/pages/System/User/List/types';
|
||||
|
||||
interface MemberManageDialogProps {
|
||||
@ -79,9 +68,8 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
|
||||
onSuccess
|
||||
}) => {
|
||||
const { toast } = useToast();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState<Page<TeamMemberResponse> | null>(null);
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const tableRef = useRef<PaginatedTableRef<TeamMemberResponse>>(null);
|
||||
const [memberData, setMemberData] = useState<TeamMemberResponse[]>([]);
|
||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||
const [deleteRecord, setDeleteRecord] = useState<TeamMemberResponse | null>(null);
|
||||
const [selectedUserIds, setSelectedUserIds] = useState<number[]>([]);
|
||||
@ -90,48 +78,16 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
|
||||
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
||||
const [editRecord, setEditRecord] = useState<TeamMemberResponse | null>(null);
|
||||
const [editRole, setEditRole] = useState('');
|
||||
|
||||
// 分页状态
|
||||
const [pageNum, setPageNum] = useState(DEFAULT_PAGE_NUM);
|
||||
const [pageSize] = useState(DEFAULT_PAGE_SIZE);
|
||||
|
||||
|
||||
// 加载成员列表
|
||||
const loadMembers = async () => {
|
||||
if (!teamId) return;
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const query: TeamMemberQuery = {
|
||||
teamId: teamId,
|
||||
userName: searchText || undefined,
|
||||
pageNum,
|
||||
pageSize,
|
||||
};
|
||||
const result = await getTeamMembers(query);
|
||||
setData(result);
|
||||
} catch (error) {
|
||||
console.error('加载成员失败:', error);
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "加载失败",
|
||||
description: error instanceof Error ? error.message : '未知错误',
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
loadMembers();
|
||||
}
|
||||
}, [open, pageNum, pageSize]);
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
setPageNum(0);
|
||||
loadMembers();
|
||||
// 包装 fetchFn
|
||||
const fetchData = async (query: TeamMemberQuery) => {
|
||||
const result = await getTeamMembers({
|
||||
...query,
|
||||
teamId: teamId,
|
||||
userName: searchUserName || undefined,
|
||||
});
|
||||
setMemberData(result.content || []);
|
||||
return result;
|
||||
};
|
||||
|
||||
// 切换用户选择
|
||||
@ -176,7 +132,7 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
|
||||
|
||||
setSelectedUserIds([]);
|
||||
setPopoverOpen(false);
|
||||
loadMembers();
|
||||
tableRef.current?.refresh();
|
||||
onSuccess?.();
|
||||
} catch (error) {
|
||||
console.error('批量添加失败:', error);
|
||||
@ -215,7 +171,7 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
|
||||
setEditDialogOpen(false);
|
||||
setEditRecord(null);
|
||||
setEditRole('');
|
||||
loadMembers();
|
||||
tableRef.current?.refresh();
|
||||
onSuccess?.();
|
||||
} catch (error) {
|
||||
console.error('更新失败:', error);
|
||||
@ -241,9 +197,46 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
|
||||
|
||||
// 过滤出未加入的用户(排除负责人和已加入的成员)
|
||||
const availableUsers = users.filter(user =>
|
||||
user.id !== ownerId && !data?.content?.some(member => member.userId === user.id)
|
||||
user.id !== ownerId && !memberData.some(member => member.userId === user.id)
|
||||
);
|
||||
|
||||
// 搜索状态
|
||||
const [searchUserName, setSearchUserName] = useState('');
|
||||
|
||||
// 列定义
|
||||
const columns: ColumnDef<TeamMemberResponse>[] = useMemo(() => [
|
||||
{ key: 'userId', title: '用户ID', dataIndex: 'userId', width: '100px' },
|
||||
{ key: 'userName', title: '用户名', dataIndex: 'userName', width: '150px' },
|
||||
{ key: 'roleInTeam', title: '团队角色', dataIndex: 'roleInTeam', width: '150px' },
|
||||
{ key: 'joinTime', title: '加入时间', dataIndex: 'joinTime', width: '180px' },
|
||||
{
|
||||
key: 'actions',
|
||||
title: '操作',
|
||||
width: '120px',
|
||||
sticky: true,
|
||||
render: (_, record) => (
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 w-8 p-0"
|
||||
onClick={() => handleEditClick(record)}
|
||||
>
|
||||
<Edit className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<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>
|
||||
),
|
||||
},
|
||||
], []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
@ -259,17 +252,19 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
|
||||
<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>
|
||||
|
||||
{/* 搜索框 */}
|
||||
<Input
|
||||
placeholder="搜索成员姓名..."
|
||||
value={searchUserName}
|
||||
onChange={(e) => setSearchUserName(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
tableRef.current?.refresh();
|
||||
}
|
||||
}}
|
||||
className="flex-1"
|
||||
/>
|
||||
|
||||
{/* 多选下拉框 */}
|
||||
<Popover open={popoverOpen} onOpenChange={setPopoverOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
@ -277,7 +272,7 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={popoverOpen}
|
||||
className="w-[280px] justify-between"
|
||||
className="w-[200px] justify-between"
|
||||
disabled={availableUsers.length === 0}
|
||||
>
|
||||
{selectedUserIds.length > 0 ? (
|
||||
@ -340,89 +335,14 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
|
||||
|
||||
{/* 表格 */}
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
<div className="max-h-[50vh] overflow-y-auto">
|
||||
<Table minWidth="680px">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead width="100px">用户ID</TableHead>
|
||||
<TableHead width="150px">用户名</TableHead>
|
||||
<TableHead width="150px">团队角色</TableHead>
|
||||
<TableHead width="160px">加入时间</TableHead>
|
||||
<TableHead width="120px" sticky>操作</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 width="100px" className="font-medium">
|
||||
{record.userId}
|
||||
</TableCell>
|
||||
<TableCell width="150px">
|
||||
{record.userName || '-'}
|
||||
</TableCell>
|
||||
<TableCell width="150px">
|
||||
{record.roleInTeam || '-'}
|
||||
</TableCell>
|
||||
<TableCell width="160px">
|
||||
{record.joinTime || '-'}
|
||||
</TableCell>
|
||||
<TableCell width="120px" sticky>
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 w-8 p-0"
|
||||
onClick={() => handleEditClick(record)}
|
||||
>
|
||||
<Edit className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<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">
|
||||
<Users 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>
|
||||
)}
|
||||
<PaginatedTable<TeamMemberResponse, TeamMemberQuery>
|
||||
ref={tableRef}
|
||||
fetchFn={fetchData}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
minWidth="680px"
|
||||
pageSize={10}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -474,7 +394,7 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
|
||||
description: `成员 "${deleteRecord?.userName}" 已移除`,
|
||||
});
|
||||
setDeleteRecord(null);
|
||||
loadMembers();
|
||||
tableRef.current?.refresh();
|
||||
onSuccess?.();
|
||||
}}
|
||||
variant="destructive"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user