重构消息通知弹窗
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 { PageContainer } from '@/components/ui/page-container';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
|
CardDescription,
|
||||||
} from '@/components/ui/card';
|
} from '@/components/ui/card';
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from '@/components/ui/table';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { useToast } from '@/components/ui/use-toast';
|
import { Separator } from '@/components/ui/separator';
|
||||||
import { DataTablePagination } from '@/components/ui/pagination';
|
import { PaginatedTable, type ColumnDef, type SearchFieldDef, type PaginatedTableRef } from '@/components/ui/paginated-table';
|
||||||
import { Plus, Edit, Trash2, Server, Activity, Database } from 'lucide-react';
|
import { Plus, Edit, Trash2, Server, Activity, Database } from 'lucide-react';
|
||||||
import EditDialog from './components/EditDialog';
|
import EditDialog from './components/EditDialog';
|
||||||
import DeleteDialog from './components/DeleteDialog';
|
import DeleteDialog from './components/DeleteDialog';
|
||||||
import { getEnvironmentPage, deleteEnvironment } from './service';
|
import { getEnvironmentPage } from './service';
|
||||||
import type { Environment, EnvironmentQueryParams } from './types';
|
import type { Environment, EnvironmentQueryParams } from './types';
|
||||||
|
|
||||||
const DEFAULT_PAGE_SIZE = 10;
|
|
||||||
|
|
||||||
const EnvironmentList: React.FC = () => {
|
const EnvironmentList: React.FC = () => {
|
||||||
const { toast } = useToast();
|
const tableRef = useRef<PaginatedTableRef<Environment>>(null);
|
||||||
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 [stats, setStats] = useState({ total: 0, enabled: 0, disabled: 0 });
|
const [stats, setStats] = useState({ total: 0, enabled: 0, disabled: 0 });
|
||||||
|
|
||||||
// 对话框状态
|
// 对话框状态
|
||||||
@ -43,59 +26,107 @@ const EnvironmentList: React.FC = () => {
|
|||||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||||
const [currentRecord, setCurrentRecord] = useState<Environment>();
|
const [currentRecord, setCurrentRecord] = useState<Environment>();
|
||||||
|
|
||||||
// 加载数据
|
// 包装 fetchFn,同时更新统计数据
|
||||||
const loadData = async () => {
|
const fetchData = async (query: EnvironmentQueryParams) => {
|
||||||
setLoading(true);
|
const result = await getEnvironmentPage(query);
|
||||||
try {
|
// 更新统计
|
||||||
const response = await getEnvironmentPage(query);
|
const all = result.content || [];
|
||||||
if (response) {
|
setStats({
|
||||||
setList(response.content || []);
|
total: result.totalElements || 0,
|
||||||
setTotal(response.totalElements || 0);
|
enabled: all.filter((item) => item.enabled).length,
|
||||||
// 计算统计数据
|
disabled: all.filter((item) => !item.enabled).length,
|
||||||
const all = response.content || [];
|
});
|
||||||
setStats({
|
return result;
|
||||||
total: all.length,
|
|
||||||
enabled: all.filter((item) => item.enabled).length,
|
|
||||||
disabled: all.filter((item) => !item.enabled).length,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
toast({ title: '加载失败', description: '无法加载环境列表', variant: 'destructive' });
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleAdd = () => {
|
||||||
loadData();
|
setCurrentRecord(undefined);
|
||||||
}, [query.pageNum, query.pageSize]);
|
setEditDialogOpen(true);
|
||||||
|
|
||||||
// 搜索
|
|
||||||
const handleSearch = () => {
|
|
||||||
setQuery({ ...query, pageNum: 0 });
|
|
||||||
loadData();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 重置
|
const handleEdit = (environment: Environment) => {
|
||||||
const handleReset = () => {
|
setCurrentRecord(environment);
|
||||||
setQuery({ pageNum: 0, pageSize: DEFAULT_PAGE_SIZE });
|
setEditDialogOpen(true);
|
||||||
loadData();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
{/* 页面标题 */}
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<h2 className="text-3xl font-bold tracking-tight">环境管理</h2>
|
|
||||||
<Button onClick={() => {
|
|
||||||
setCurrentRecord(undefined);
|
|
||||||
setEditDialogOpen(true);
|
|
||||||
}}>
|
|
||||||
<Plus className="h-4 w-4 mr-2" />
|
|
||||||
新建环境
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 统计卡片 */}
|
{/* 统计卡片 */}
|
||||||
<div className="grid gap-4 md:grid-cols-3">
|
<div className="grid gap-4 md:grid-cols-3">
|
||||||
<Card>
|
<Card>
|
||||||
@ -130,128 +161,27 @@ const EnvironmentList: React.FC = () => {
|
|||||||
</Card>
|
</Card>
|
||||||
</div>
|
</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>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>环境列表</CardTitle>
|
<div>
|
||||||
</CardHeader>
|
<CardTitle>环境管理</CardTitle>
|
||||||
<CardContent>
|
<CardDescription className="mt-2">
|
||||||
<div className="rounded-md border">
|
创建和管理部署环境,配置环境参数
|
||||||
<Table minWidth="1150px">
|
</CardDescription>
|
||||||
<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 })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<Separator />
|
||||||
|
<CardContent className="pt-6">
|
||||||
|
<PaginatedTable<Environment, EnvironmentQueryParams>
|
||||||
|
ref={tableRef}
|
||||||
|
fetchFn={fetchData}
|
||||||
|
columns={columns}
|
||||||
|
searchFields={searchFields}
|
||||||
|
toolbar={toolbar}
|
||||||
|
rowKey="id"
|
||||||
|
minWidth="1200px"
|
||||||
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
@ -260,13 +190,13 @@ const EnvironmentList: React.FC = () => {
|
|||||||
open={editDialogOpen}
|
open={editDialogOpen}
|
||||||
record={currentRecord}
|
record={currentRecord}
|
||||||
onOpenChange={setEditDialogOpen}
|
onOpenChange={setEditDialogOpen}
|
||||||
onSuccess={loadData}
|
onSuccess={handleSuccess}
|
||||||
/>
|
/>
|
||||||
<DeleteDialog
|
<DeleteDialog
|
||||||
open={deleteDialogOpen}
|
open={deleteDialogOpen}
|
||||||
record={currentRecord}
|
record={currentRecord}
|
||||||
onOpenChange={setDeleteDialogOpen}
|
onOpenChange={setDeleteDialogOpen}
|
||||||
onSuccess={loadData}
|
onSuccess={handleSuccess}
|
||||||
/>
|
/>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user