重构消息通知弹窗
This commit is contained in:
parent
38ebe57a13
commit
7a4f5f37c2
@ -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>
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user