重构消息通知弹窗

This commit is contained in:
dengqichen 2025-11-28 16:52:43 +08:00
parent 38ebe57a13
commit 7a4f5f37c2

View File

@ -1,41 +1,24 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useMemo, useRef } from 'react';
import { PageContainer } from '@/components/ui/page-container';
import {
Card,
CardContent,
CardHeader,
CardTitle,
CardDescription,
} from '@/components/ui/card';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Badge } from '@/components/ui/badge';
import { useToast } from '@/components/ui/use-toast';
import { DataTablePagination } from '@/components/ui/pagination';
import { Separator } from '@/components/ui/separator';
import { PaginatedTable, type ColumnDef, type SearchFieldDef, type PaginatedTableRef } from '@/components/ui/paginated-table';
import { Plus, Edit, Trash2, Server, Activity, Database } from 'lucide-react';
import EditDialog from './components/EditDialog';
import DeleteDialog from './components/DeleteDialog';
import { getEnvironmentPage, deleteEnvironment } from './service';
import { getEnvironmentPage } from './service';
import type { Environment, EnvironmentQueryParams } from './types';
const DEFAULT_PAGE_SIZE = 10;
const EnvironmentList: React.FC = () => {
const { toast } = useToast();
const [list, setList] = useState<Environment[]>([]);
const [loading, setLoading] = useState(false);
const [query, setQuery] = useState<EnvironmentQueryParams>({
pageNum: 0,
pageSize: DEFAULT_PAGE_SIZE,
});
const [total, setTotal] = useState(0);
const tableRef = useRef<PaginatedTableRef<Environment>>(null);
const [stats, setStats] = useState({ total: 0, enabled: 0, disabled: 0 });
// 对话框状态
@ -43,59 +26,107 @@ const EnvironmentList: React.FC = () => {
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [currentRecord, setCurrentRecord] = useState<Environment>();
// 加载数据
const loadData = async () => {
setLoading(true);
try {
const response = await getEnvironmentPage(query);
if (response) {
setList(response.content || []);
setTotal(response.totalElements || 0);
// 计算统计数据
const all = response.content || [];
// 包装 fetchFn同时更新统计数据
const fetchData = async (query: EnvironmentQueryParams) => {
const result = await getEnvironmentPage(query);
// 更新统计
const all = result.content || [];
setStats({
total: all.length,
total: result.totalElements || 0,
enabled: all.filter((item) => item.enabled).length,
disabled: all.filter((item) => !item.enabled).length,
});
}
} catch (error) {
toast({ title: '加载失败', description: '无法加载环境列表', variant: 'destructive' });
} finally {
setLoading(false);
}
return result;
};
useEffect(() => {
loadData();
}, [query.pageNum, query.pageSize]);
// 搜索
const handleSearch = () => {
setQuery({ ...query, pageNum: 0 });
loadData();
};
// 重置
const handleReset = () => {
setQuery({ pageNum: 0, pageSize: DEFAULT_PAGE_SIZE });
loadData();
};
return (
<PageContainer>
{/* 页面标题 */}
<div className="flex items-center justify-between">
<h2 className="text-3xl font-bold tracking-tight"></h2>
<Button onClick={() => {
const handleAdd = () => {
setCurrentRecord(undefined);
setEditDialogOpen(true);
}}>
};
const handleEdit = (environment: Environment) => {
setCurrentRecord(environment);
setEditDialogOpen(true);
};
const handleSuccess = () => {
setEditDialogOpen(false);
setDeleteDialogOpen(false);
setCurrentRecord(undefined);
tableRef.current?.refresh();
};
// 搜索字段定义
const searchFields: SearchFieldDef[] = useMemo(() => [
{ key: 'envCode', type: 'input', placeholder: '环境编码', width: 'w-[180px]' },
{ key: 'envName', type: 'input', placeholder: '环境名称', width: 'w-[180px]' },
{
key: 'enabled',
type: 'select',
placeholder: '状态',
width: 'w-[120px]',
options: [
{ label: '启用', value: 'true' },
{ label: '禁用', value: 'false' },
],
},
], []);
// 列定义
const columns: ColumnDef<Environment>[] = useMemo(() => [
{ key: 'id', title: 'ID', dataIndex: 'id', width: '80px' },
{ key: 'envCode', title: '环境编码', dataIndex: 'envCode', width: '150px' },
{ key: 'envName', title: '环境名称', dataIndex: 'envName', width: '150px' },
{ key: 'envDesc', title: '环境描述', dataIndex: 'envDesc', width: '200px' },
{ key: 'teamCount', title: '团队数', dataIndex: 'teamCount', width: '100px' },
{ key: 'applicationCount', title: '应用数', dataIndex: 'applicationCount', width: '100px' },
{
key: 'enabled',
title: '状态',
width: '100px',
render: (_, record) => (
<Badge variant={record.enabled ? 'default' : 'secondary'}>
{record.enabled ? '启用' : '禁用'}
</Badge>
),
},
{ key: 'sort', title: '排序', dataIndex: 'sort', width: '80px' },
{
key: 'actions',
title: '操作',
width: '160px',
sticky: true,
render: (_, record) => (
<div className="flex items-center gap-2">
<Button variant="ghost" size="sm" onClick={() => handleEdit(record)}>
<Edit className="h-4 w-4 mr-1" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => {
setCurrentRecord(record);
setDeleteDialogOpen(true);
}}
className="text-destructive hover:text-destructive"
>
<Trash2 className="h-4 w-4 mr-1" />
</Button>
</div>
),
},
], []);
// 工具栏
const toolbar = (
<Button onClick={handleAdd}>
<Plus className="h-4 w-4 mr-2" />
</Button>
</div>
);
return (
<PageContainer>
{/* 统计卡片 */}
<div className="grid gap-4 md:grid-cols-3">
<Card>
@ -130,128 +161,27 @@ const EnvironmentList: React.FC = () => {
</Card>
</div>
{/* 搜索过滤 */}
<Card>
<div className="p-6">
<div className="flex items-center gap-4">
<Input
placeholder="环境编码"
value={query.envCode || ''}
onChange={(e) => setQuery({ ...query, envCode: e.target.value })}
className="max-w-[200px]"
/>
<Input
placeholder="环境名称"
value={query.envName || ''}
onChange={(e) => setQuery({ ...query, envName: e.target.value })}
className="max-w-[200px]"
/>
<Button variant="outline" onClick={handleReset}>
</Button>
<Button onClick={handleSearch}>
</Button>
</div>
</div>
</Card>
{/* 数据表格 */}
{/* 环境管理 */}
<Card>
<CardHeader>
<CardTitle></CardTitle>
<div>
<CardTitle></CardTitle>
<CardDescription className="mt-2">
</CardDescription>
</div>
</CardHeader>
<CardContent>
<div className="rounded-md border">
<Table minWidth="1150px">
<TableHeader>
<TableRow>
<TableHead width="120px"></TableHead>
<TableHead width="150px"></TableHead>
<TableHead width="250px"></TableHead>
<TableHead width="100px"></TableHead>
<TableHead width="100px"></TableHead>
<TableHead width="80px"></TableHead>
<TableHead width="100px"></TableHead>
<TableHead width="200px" sticky></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{list.length === 0 ? (
<TableRow>
<TableCell colSpan={8} className="h-24 text-center">
</TableCell>
</TableRow>
) : (
list.map((item) => {
return (
<TableRow key={item.id}>
<TableCell width="120px" className="font-medium">{item.envCode}</TableCell>
<TableCell width="150px">{item.envName}</TableCell>
<TableCell width="250px" className="text-muted-foreground">{item.envDesc || '-'}</TableCell>
<TableCell width="100px">
<div className="text-center">
<Badge variant="outline" className="font-medium">
{item.teamCount || 0}
</Badge>
</div>
</TableCell>
<TableCell width="100px">
<div className="text-center">
<Badge variant="outline" className="font-medium">
{item.applicationCount || 0}
</Badge>
</div>
</TableCell>
<TableCell width="80px">{item.sort}</TableCell>
<TableCell width="100px">
<Badge variant={item.enabled ? "default" : "secondary"} className="inline-flex">
{item.enabled ? '启用' : '禁用'}
</Badge>
</TableCell>
<TableCell width="200px" sticky>
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="sm"
onClick={() => {
setCurrentRecord(item);
setEditDialogOpen(true);
}}
>
<Edit className="h-4 w-4 mr-1" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => {
setCurrentRecord(item);
setDeleteDialogOpen(true);
}}
className="text-destructive hover:text-destructive"
>
<Trash2 className="h-4 w-4 mr-1" />
</Button>
</div>
</TableCell>
</TableRow>
);
})
)}
</TableBody>
</Table>
<div className="flex justify-end border-t border-border bg-muted/40">
<DataTablePagination
pageIndex={query.pageNum || 0}
pageSize={query.pageSize || DEFAULT_PAGE_SIZE}
pageCount={Math.ceil(total / (query.pageSize || DEFAULT_PAGE_SIZE))}
onPageChange={(page) => setQuery({ ...query, pageNum: page })}
<Separator />
<CardContent className="pt-6">
<PaginatedTable<Environment, EnvironmentQueryParams>
ref={tableRef}
fetchFn={fetchData}
columns={columns}
searchFields={searchFields}
toolbar={toolbar}
rowKey="id"
minWidth="1200px"
/>
</div>
</div>
</CardContent>
</Card>
@ -260,13 +190,13 @@ const EnvironmentList: React.FC = () => {
open={editDialogOpen}
record={currentRecord}
onOpenChange={setEditDialogOpen}
onSuccess={loadData}
onSuccess={handleSuccess}
/>
<DeleteDialog
open={deleteDialogOpen}
record={currentRecord}
onOpenChange={setDeleteDialogOpen}
onSuccess={loadData}
onSuccess={handleSuccess}
/>
</PageContainer>
);