重构消息通知弹窗
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 {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@ -8,14 +8,7 @@ import {
|
|||||||
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
|
import { ConfirmDialog } from '@/components/ui/confirm-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 {
|
import { PaginatedTable, type ColumnDef, type SearchFieldDef, type PaginatedTableRef } from '@/components/ui/paginated-table';
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from "@/components/ui/table";
|
|
||||||
import {
|
import {
|
||||||
Command,
|
Command,
|
||||||
CommandEmpty,
|
CommandEmpty,
|
||||||
@ -31,13 +24,10 @@ import {
|
|||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
import { DataTablePagination } from '@/components/ui/pagination';
|
|
||||||
import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_NUM } from '@/utils/page';
|
|
||||||
import {
|
import {
|
||||||
Plus,
|
Plus,
|
||||||
Edit,
|
Edit,
|
||||||
Trash2,
|
Trash2,
|
||||||
Search,
|
|
||||||
Loader2,
|
Loader2,
|
||||||
Users,
|
Users,
|
||||||
Check,
|
Check,
|
||||||
@ -53,7 +43,6 @@ import type {
|
|||||||
TeamMemberResponse,
|
TeamMemberResponse,
|
||||||
TeamMemberQuery
|
TeamMemberQuery
|
||||||
} from '../../Member/types';
|
} from '../../Member/types';
|
||||||
import type { Page } from '@/types/base';
|
|
||||||
import type { UserResponse } from '@/pages/System/User/List/types';
|
import type { UserResponse } from '@/pages/System/User/List/types';
|
||||||
|
|
||||||
interface MemberManageDialogProps {
|
interface MemberManageDialogProps {
|
||||||
@ -79,9 +68,8 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
|
|||||||
onSuccess
|
onSuccess
|
||||||
}) => {
|
}) => {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const [loading, setLoading] = useState(false);
|
const tableRef = useRef<PaginatedTableRef<TeamMemberResponse>>(null);
|
||||||
const [data, setData] = useState<Page<TeamMemberResponse> | null>(null);
|
const [memberData, setMemberData] = useState<TeamMemberResponse[]>([]);
|
||||||
const [searchText, setSearchText] = useState('');
|
|
||||||
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 [selectedUserIds, setSelectedUserIds] = useState<number[]>([]);
|
||||||
@ -90,48 +78,16 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
|
|||||||
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
||||||
const [editRecord, setEditRecord] = useState<TeamMemberResponse | null>(null);
|
const [editRecord, setEditRecord] = useState<TeamMemberResponse | null>(null);
|
||||||
const [editRole, setEditRole] = useState('');
|
const [editRole, setEditRole] = useState('');
|
||||||
|
|
||||||
// 分页状态
|
|
||||||
const [pageNum, setPageNum] = useState(DEFAULT_PAGE_NUM);
|
|
||||||
const [pageSize] = useState(DEFAULT_PAGE_SIZE);
|
|
||||||
|
|
||||||
|
// 包装 fetchFn
|
||||||
// 加载成员列表
|
const fetchData = async (query: TeamMemberQuery) => {
|
||||||
const loadMembers = async () => {
|
const result = await getTeamMembers({
|
||||||
if (!teamId) return;
|
...query,
|
||||||
|
teamId: teamId,
|
||||||
setLoading(true);
|
userName: searchUserName || undefined,
|
||||||
try {
|
});
|
||||||
const query: TeamMemberQuery = {
|
setMemberData(result.content || []);
|
||||||
teamId: teamId,
|
return result;
|
||||||
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();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 切换用户选择
|
// 切换用户选择
|
||||||
@ -176,7 +132,7 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
|
|||||||
|
|
||||||
setSelectedUserIds([]);
|
setSelectedUserIds([]);
|
||||||
setPopoverOpen(false);
|
setPopoverOpen(false);
|
||||||
loadMembers();
|
tableRef.current?.refresh();
|
||||||
onSuccess?.();
|
onSuccess?.();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('批量添加失败:', error);
|
console.error('批量添加失败:', error);
|
||||||
@ -215,7 +171,7 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
|
|||||||
setEditDialogOpen(false);
|
setEditDialogOpen(false);
|
||||||
setEditRecord(null);
|
setEditRecord(null);
|
||||||
setEditRole('');
|
setEditRole('');
|
||||||
loadMembers();
|
tableRef.current?.refresh();
|
||||||
onSuccess?.();
|
onSuccess?.();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('更新失败:', error);
|
console.error('更新失败:', error);
|
||||||
@ -241,9 +197,46 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
|
|||||||
|
|
||||||
// 过滤出未加入的用户(排除负责人和已加入的成员)
|
// 过滤出未加入的用户(排除负责人和已加入的成员)
|
||||||
const availableUsers = users.filter(user =>
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<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="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">
|
{/* 搜索框 */}
|
||||||
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
<Input
|
||||||
<Input
|
placeholder="搜索成员姓名..."
|
||||||
placeholder="搜索成员姓名..."
|
value={searchUserName}
|
||||||
value={searchText}
|
onChange={(e) => setSearchUserName(e.target.value)}
|
||||||
onChange={(e) => setSearchText(e.target.value)}
|
onKeyDown={(e) => {
|
||||||
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
|
if (e.key === 'Enter') {
|
||||||
className="pl-10"
|
tableRef.current?.refresh();
|
||||||
/>
|
}
|
||||||
</div>
|
}}
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
|
||||||
{/* 多选下拉框 */}
|
{/* 多选下拉框 */}
|
||||||
<Popover open={popoverOpen} onOpenChange={setPopoverOpen}>
|
<Popover open={popoverOpen} onOpenChange={setPopoverOpen}>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
@ -277,7 +272,7 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
role="combobox"
|
role="combobox"
|
||||||
aria-expanded={popoverOpen}
|
aria-expanded={popoverOpen}
|
||||||
className="w-[280px] justify-between"
|
className="w-[200px] justify-between"
|
||||||
disabled={availableUsers.length === 0}
|
disabled={availableUsers.length === 0}
|
||||||
>
|
>
|
||||||
{selectedUserIds.length > 0 ? (
|
{selectedUserIds.length > 0 ? (
|
||||||
@ -340,89 +335,14 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
|
|||||||
|
|
||||||
{/* 表格 */}
|
{/* 表格 */}
|
||||||
<div className="border rounded-lg overflow-hidden">
|
<div className="border rounded-lg overflow-hidden">
|
||||||
<div className="max-h-[50vh] overflow-y-auto">
|
<PaginatedTable<TeamMemberResponse, TeamMemberQuery>
|
||||||
<Table minWidth="680px">
|
ref={tableRef}
|
||||||
<TableHeader>
|
fetchFn={fetchData}
|
||||||
<TableRow>
|
columns={columns}
|
||||||
<TableHead width="100px">用户ID</TableHead>
|
rowKey="id"
|
||||||
<TableHead width="150px">用户名</TableHead>
|
minWidth="680px"
|
||||||
<TableHead width="150px">团队角色</TableHead>
|
pageSize={10}
|
||||||
<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>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -474,7 +394,7 @@ const MemberManageDialog: React.FC<MemberManageDialogProps> = ({
|
|||||||
description: `成员 "${deleteRecord?.userName}" 已移除`,
|
description: `成员 "${deleteRecord?.userName}" 已移除`,
|
||||||
});
|
});
|
||||||
setDeleteRecord(null);
|
setDeleteRecord(null);
|
||||||
loadMembers();
|
tableRef.current?.refresh();
|
||||||
onSuccess?.();
|
onSuccess?.();
|
||||||
}}
|
}}
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user