diff --git a/frontend/src/pages/Deploy/Environment/List/index.tsx b/frontend/src/pages/Deploy/Environment/List/index.tsx
index 0bf11743..1b51cb07 100644
--- a/frontend/src/pages/Deploy/Environment/List/index.tsx
+++ b/frontend/src/pages/Deploy/Environment/List/index.tsx
@@ -77,7 +77,16 @@ const EnvironmentList: React.FC = () => {
{ 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: 'envDesc',
+ title: '环境描述',
+ width: '200px',
+ render: (_, record) => (
+
+ {record.envDesc || '-'}
+
+ ),
+ },
{ key: 'teamCount', title: '团队数', dataIndex: 'teamCount', width: '100px' },
{ key: 'applicationCount', title: '应用数', dataIndex: 'applicationCount', width: '100px' },
{
diff --git a/frontend/src/pages/Deploy/NotificationChannel/List/index.tsx b/frontend/src/pages/Deploy/NotificationChannel/List/index.tsx
index 56b93c6e..4bbb04e6 100644
--- a/frontend/src/pages/Deploy/NotificationChannel/List/index.tsx
+++ b/frontend/src/pages/Deploy/NotificationChannel/List/index.tsx
@@ -1,22 +1,8 @@
-import React, { useEffect, useState } from 'react';
+import React, { useState, useMemo, useRef, useEffect } from 'react';
import { PageContainer } from '@/components/ui/page-container';
import { Button } from '@/components/ui/button';
-import { Input } from '@/components/ui/input';
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from '@/components/ui/select';
-import {
- Table,
- TableBody,
- TableCell,
- TableHead,
- TableHeader,
- TableRow,
-} from '@/components/ui/table';
+import { Badge } from '@/components/ui/badge';
+import { Separator } from '@/components/ui/separator';
import {
Card,
CardContent,
@@ -24,33 +10,21 @@ import {
CardHeader,
CardTitle,
} from '@/components/ui/card';
-import { Badge } from '@/components/ui/badge';
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
import { useToast } from '@/components/ui/use-toast';
-import { DataTablePagination } from '@/components/ui/pagination';
-import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_NUM } from '@/utils/page';
+import { PaginatedTable, type ColumnDef, type SearchFieldDef, type PaginatedTableRef } from '@/components/ui/paginated-table';
import {
Plus,
- Search,
- Loader2,
Edit,
Trash2,
TestTube,
- RefreshCw,
Power,
PowerOff,
Activity,
Database,
Server,
- MoreHorizontal,
+ Loader2,
} from 'lucide-react';
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from '@/components/ui/dropdown-menu';
import type {
NotificationChannel,
NotificationChannelQuery,
@@ -69,32 +43,16 @@ import NotificationChannelDialog from './components/NotificationChannelDialog';
const NotificationChannelList: React.FC = () => {
const { toast } = useToast();
+ const tableRef = useRef>(null);
// 状态管理
- const [channels, setChannels] = useState([]);
const [channelTypes, setChannelTypes] = useState([]);
- const [loading, setLoading] = useState(false);
const [testing, setTesting] = useState(null);
-
- // 分页
- const [pagination, setPagination] = useState({
- pageNum: DEFAULT_PAGE_NUM,
- pageSize: DEFAULT_PAGE_SIZE,
- totalElements: 0,
- });
-
- // 查询条件
- const [query, setQuery] = useState({
- name: '',
- channelType: undefined,
- enabled: undefined,
- });
+ const [stats, setStats] = useState({ total: 0, enabled: 0, disabled: 0 });
// 对话框状态
const [dialogOpen, setDialogOpen] = useState(false);
const [editId, setEditId] = useState();
-
- // 删除确认对话框
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [deleteId, setDeleteId] = useState(null);
@@ -103,11 +61,6 @@ const NotificationChannelList: React.FC = () => {
loadChannelTypes();
}, []);
- // 加载数据
- useEffect(() => {
- loadData();
- }, [pagination.pageNum, pagination.pageSize, query.channelType, query.enabled]);
-
const loadChannelTypes = async () => {
try {
const types = await getChannelTypes();
@@ -121,69 +74,39 @@ const NotificationChannelList: React.FC = () => {
}
};
- const loadData = async () => {
- setLoading(true);
- try {
- const res = await getChannelsPage({
- ...query,
- page: pagination.pageNum,
- size: pagination.pageSize,
- });
-
- setChannels(res.content || []);
- setPagination((prev) => ({
- ...prev,
- totalElements: res.totalElements || 0,
- }));
- } catch (error: any) {
- toast({
- variant: 'destructive',
- title: '加载失败',
- description: error.response?.data?.message || error.message,
- });
- } finally {
- setLoading(false);
- }
- };
-
- // 搜索
- const handleSearch = () => {
- setPagination((prev) => ({ ...prev, pageNum: DEFAULT_PAGE_NUM }));
- loadData();
- };
-
- // 重置搜索
- const handleReset = () => {
- setQuery({
- name: '',
- channelType: undefined,
- enabled: undefined,
+ // 包装 fetchFn,同时更新统计数据
+ const fetchData = async (query: any) => {
+ const result = await getChannelsPage({
+ ...query,
+ page: query.pageNum,
+ size: query.pageSize,
});
- setPagination({
- pageNum: DEFAULT_PAGE_NUM,
- pageSize: DEFAULT_PAGE_SIZE,
- totalElements: 0,
+ // 更新统计
+ const all = result.content || [];
+ setStats({
+ total: result.totalElements || 0,
+ enabled: all.filter((item) => item.enabled).length,
+ disabled: all.filter((item) => !item.enabled).length,
});
+ return result;
};
- // 分页切换
- const handlePageChange = (newPage: number) => {
- setPagination({ ...pagination, pageNum: newPage });
- };
-
- // 创建
- const handleCreate = () => {
+ const handleAdd = () => {
setEditId(undefined);
setDialogOpen(true);
};
- // 编辑
const handleEdit = (id: number) => {
setEditId(id);
setDialogOpen(true);
};
- // 删除
+ const handleSuccess = () => {
+ setDialogOpen(false);
+ setEditId(undefined);
+ tableRef.current?.refresh();
+ };
+
const handleDelete = async () => {
if (!deleteId) return;
await deleteChannel(deleteId);
@@ -199,7 +122,7 @@ const NotificationChannelList: React.FC = () => {
await disableChannel(id);
toast({ title: '已禁用' });
}
- loadData();
+ tableRef.current?.refresh();
} catch (error: any) {
toast({
variant: 'destructive',
@@ -253,25 +176,134 @@ const NotificationChannelList: React.FC = () => {
return {config.label};
};
- // 状态标签
- const getStatusBadge = (enabled: boolean) => {
- if (enabled) {
- return 启用;
- }
- return 禁用;
- };
+ // 搜索字段定义
+ const searchFields: SearchFieldDef[] = useMemo(() => [
+ { key: 'name', type: 'input', placeholder: '搜索渠道名称', width: 'w-[200px]' },
+ {
+ key: 'channelType',
+ type: 'select',
+ placeholder: '渠道类型',
+ width: 'w-[150px]',
+ options: channelTypes
+ .filter((type) => type.code && type.code.trim() !== '')
+ .map((type) => ({ label: type.label, value: type.code })),
+ },
+ ], [channelTypes]);
+
+ // 列定义
+ const columns: ColumnDef[] = useMemo(() => [
+ { key: 'id', title: 'ID', dataIndex: 'id', width: '80px' },
+ { key: 'name', title: '渠道名称', dataIndex: 'name', width: '150px' },
+ {
+ key: 'channelType',
+ title: '渠道类型',
+ width: '120px',
+ render: (_, record) => getChannelTypeBadge(record.channelType),
+ },
+ {
+ key: 'enabled',
+ title: '状态',
+ width: '100px',
+ render: (_, record) => (
+
+ {record.enabled ? '启用' : '禁用'}
+
+ ),
+ },
+ {
+ key: 'description',
+ title: '描述',
+ width: '200px',
+ render: (_, record) => (
+
+ {record.description || '-'}
+
+ ),
+ },
+ { key: 'createTime', title: '创建时间', dataIndex: 'createTime', width: '180px' },
+ {
+ key: 'actions',
+ title: '操作',
+ width: '240px',
+ sticky: true,
+ render: (_, record) => (
+
+
+ {record.enabled ? (
+
+ ) : (
+
+ )}
+
+
+
+ ),
+ },
+ ], [testing, channelTypes]);
+
+ // 工具栏
+ const toolbar = (
+
+ );
return (
{/* 统计卡片 */}
-
+
总渠道数
- {channels.length}
+ {stats.total}
所有渠道
@@ -281,9 +313,7 @@ const NotificationChannelList: React.FC = () => {
-
- {channels.filter(c => c.enabled).length}
-
+ {stats.enabled}
正在使用中
@@ -293,9 +323,7 @@ const NotificationChannelList: React.FC = () => {
-
- {channels.filter(c => !c.enabled).length}
-
+ {stats.disabled}
暂时停用
@@ -304,178 +332,24 @@ const NotificationChannelList: React.FC = () => {
{/* 渠道管理 */}
-
-
- 通知渠道
-
- 管理通知渠道配置,支持企业微信、邮件等多种渠道
-
-
-
+
+ 通知渠道
+
+ 管理通知渠道配置,支持企业微信、邮件等多种渠道
+
-
- {/* 搜索区域 */}
-
-
- setQuery({ ...query, name: e.target.value })}
- onKeyPress={(e) => {
- if (e.key === 'Enter') {
- handleSearch();
- }
- }}
- />
-
-
-
-
-
- {/* 表格 */}
-
-
-
-
- 渠道名称
- 渠道类型
- 状态
- 描述
- 创建时间
- 操作
-
-
-
- {loading ? (
-
-
-
-
-
- ) : channels.length === 0 ? (
-
-
- 暂无数据
-
-
- ) : (
- channels.map((channel) => (
-
- {channel.name}
-
- {getChannelTypeBadge(channel.channelType)}
-
-
-
- {channel.enabled ? "启用" : "禁用"}
-
-
-
- {channel.description || '-'}
-
- {channel.createTime}
-
-
-
- {channel.enabled ? (
-
- ) : (
-
- )}
-
-
-
-
-
- ))
- )}
-
-
-
-
- {/* 分页 */}
-
-
-
+
+
+
+ ref={tableRef}
+ fetchFn={fetchData}
+ columns={columns}
+ searchFields={searchFields}
+ toolbar={toolbar}
+ rowKey="id"
+ minWidth="1200px"
+ />
@@ -485,7 +359,7 @@ const NotificationChannelList: React.FC = () => {
editId={editId}
channelTypes={channelTypes}
onOpenChange={setDialogOpen}
- onSuccess={loadData}
+ onSuccess={handleSuccess}
/>
{/* 删除确认对话框 */}
@@ -499,7 +373,7 @@ const NotificationChannelList: React.FC = () => {
onSuccess={() => {
toast({ title: '删除成功' });
setDeleteId(null);
- loadData();
+ tableRef.current?.refresh();
}}
onCancel={() => setDeleteId(null)}
variant="destructive"
diff --git a/frontend/src/pages/Deploy/NotificationTemplate/List/index.tsx b/frontend/src/pages/Deploy/NotificationTemplate/List/index.tsx
index 6b91aa2b..b7339276 100644
--- a/frontend/src/pages/Deploy/NotificationTemplate/List/index.tsx
+++ b/frontend/src/pages/Deploy/NotificationTemplate/List/index.tsx
@@ -1,25 +1,11 @@
/**
* 通知模板管理页面
*/
-import React, { useState, useEffect } from 'react';
+import React, { useState, useMemo, useRef } from 'react';
import { PageContainer } from '@/components/ui/page-container';
import { Button } from '@/components/ui/button';
-import { Input } from '@/components/ui/input';
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from '@/components/ui/select';
-import {
- Table,
- TableBody,
- TableCell,
- TableHead,
- TableHeader,
- TableRow,
-} from '@/components/ui/table';
+import { Badge } from '@/components/ui/badge';
+import { Separator } from '@/components/ui/separator';
import {
Card,
CardContent,
@@ -27,15 +13,11 @@ import {
CardHeader,
CardTitle,
} from '@/components/ui/card';
-import { Badge } from '@/components/ui/badge';
import { useToast } from '@/components/ui/use-toast';
-import { DataTablePagination } from '@/components/ui/pagination';
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
-import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_NUM } from '@/utils/page';
+import { PaginatedTable, type ColumnDef, type SearchFieldDef, type PaginatedTableRef } from '@/components/ui/paginated-table';
import {
Plus,
- Search,
- Loader2,
Edit,
Trash2,
TestTube,
@@ -44,15 +26,8 @@ import {
Activity,
Database,
Server,
- MoreHorizontal
+ Loader2,
} from 'lucide-react';
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from '@/components/ui/dropdown-menu';
import { NotificationChannelType } from '../../NotificationChannel/List/types';
import type {
NotificationTemplateDTO,
@@ -83,41 +58,19 @@ const CHANNEL_TYPE_OPTIONS: ChannelTypeOption[] = [
const NotificationTemplateList: React.FC = () => {
const { toast } = useToast();
+ const tableRef = useRef>(null);
// 状态管理
- const [loading, setLoading] = useState(false);
- const [list, setList] = useState([]);
- const [pagination, setPagination] = useState({
- pageNum: DEFAULT_PAGE_NUM,
- pageSize: DEFAULT_PAGE_SIZE,
- totalElements: 0,
- });
-
- // 统计数据
- const [stats, setStats] = useState({
- total: 0,
- enabled: 0,
- disabled: 0,
- });
-
- // 操作状态管理
+ const [stats, setStats] = useState({ total: 0, enabled: 0, disabled: 0 });
const [operationLoading, setOperationLoading] = useState({
toggling: null as number | null,
- deleting: null as number | null,
});
- // 查询条件
- const [query, setQuery] = useState({});
-
// 对话框状态
const [modalVisible, setModalVisible] = useState(false);
const [currentTemplate, setCurrentTemplate] = useState();
-
- // 测试模板对话框状态
const [testModalVisible, setTestModalVisible] = useState(false);
const [testTemplate, setTestTemplate] = useState();
-
- // 确认对话框
const [confirmDialog, setConfirmDialog] = useState<{
open: boolean;
title: string;
@@ -130,73 +83,35 @@ const NotificationTemplateList: React.FC = () => {
onConfirm: async () => {},
});
- // 加载数据
- const loadData = async (params?: NotificationTemplateQuery) => {
- setLoading(true);
- try {
- const queryParams: NotificationTemplateQuery = {
- ...query,
- ...params,
- pageNum: pagination.pageNum,
- pageSize: pagination.pageSize,
- };
- const data = await getTemplateList(queryParams);
- setList(data.content || []);
- setPagination({
- ...pagination,
- totalElements: data.totalElements,
- });
-
- // 计算统计数据
- const all = data.content || [];
- setStats({
- total: all.length,
- enabled: all.filter((item) => item.enabled).length,
- disabled: all.filter((item) => !item.enabled).length,
- });
- } catch (error: any) {
- toast({
- variant: 'destructive',
- title: '获取模板列表失败',
- description: error.response?.data?.message || error.message,
- duration: 3000,
- });
- } finally {
- setLoading(false);
- }
- };
-
- // 分页处理
- const handlePageChange = (page: number) => {
- setPagination({
- ...pagination,
- pageNum: page,
+ // 包装 fetchFn,同时更新统计数据
+ const fetchData = async (query: NotificationTemplateQuery) => {
+ const result = await getTemplateList(query);
+ // 更新统计
+ const all = result.content || [];
+ setStats({
+ total: result.totalElements || 0,
+ enabled: all.filter((item) => item.enabled).length,
+ disabled: all.filter((item) => !item.enabled).length,
});
+ return result;
};
- // 初始加载
- useEffect(() => {
- loadData();
- }, [pagination.pageNum, pagination.pageSize]);
-
- // 搜索处理
- const handleSearch = () => {
- setPagination(prev => ({ ...prev, pageNum: DEFAULT_PAGE_NUM }));
- loadData();
- };
-
- // 新建模板
const handleAdd = () => {
setCurrentTemplate(undefined);
setModalVisible(true);
};
- // 编辑模板
const handleEdit = (template: NotificationTemplateDTO) => {
setCurrentTemplate(template);
setModalVisible(true);
};
+ const handleSuccess = () => {
+ setModalVisible(false);
+ setCurrentTemplate(undefined);
+ tableRef.current?.refresh();
+ };
+
// 删除模板
const handleDelete = (template: NotificationTemplateDTO) => {
setConfirmDialog({
@@ -207,7 +122,7 @@ const NotificationTemplateList: React.FC = () => {
try {
await deleteTemplate(template.id);
toast({ title: '删除成功' });
- loadData();
+ tableRef.current?.refresh();
} catch (error: any) {
toast({
variant: 'destructive',
@@ -237,7 +152,7 @@ const NotificationTemplateList: React.FC = () => {
await enableTemplate(template.id);
toast({ title: '已启用' });
}
- loadData();
+ tableRef.current?.refresh();
} catch (error: any) {
toast({
variant: 'destructive',
@@ -255,10 +170,136 @@ const NotificationTemplateList: React.FC = () => {
return option?.label || type;
};
+ // 搜索字段定义
+ const searchFields: SearchFieldDef[] = useMemo(() => [
+ { key: 'name', type: 'input', placeholder: '搜索模板名称或编码', width: 'w-[220px]' },
+ {
+ key: 'channelType',
+ type: 'select',
+ placeholder: '渠道类型',
+ width: 'w-[150px]',
+ options: CHANNEL_TYPE_OPTIONS.map((option) => ({
+ label: option.label,
+ value: option.code,
+ })),
+ },
+ ], []);
+
+ // 列定义
+ const columns: ColumnDef[] = useMemo(() => [
+ { key: 'id', title: 'ID', dataIndex: 'id', width: '80px' },
+ { key: 'name', title: '模板名称', dataIndex: 'name', width: '150px' },
+ { key: 'code', title: '模板编码', dataIndex: 'code', width: '150px' },
+ {
+ key: 'titleTemplate',
+ title: '标题模板',
+ width: '200px',
+ render: (_, record) => (
+
+ {record.titleTemplate || '-'}
+
+ ),
+ },
+ {
+ key: 'channelType',
+ title: '渠道类型',
+ width: '120px',
+ render: (_, record) => (
+ {getChannelTypeLabel(record.channelType)}
+ ),
+ },
+ {
+ key: 'enabled',
+ title: '状态',
+ width: '100px',
+ render: (_, record) => (
+
+ {record.enabled ? '启用' : '禁用'}
+
+ ),
+ },
+ { key: 'createTime', title: '创建时间', dataIndex: 'createTime', width: '180px' },
+ {
+ key: 'actions',
+ title: '操作',
+ width: '240px',
+ sticky: true,
+ render: (_, record) => (
+
+
+
+ {record.enabled ? (
+
+ ) : (
+
+ )}
+
+
+ ),
+ },
+ ], [operationLoading.toggling]);
+
+ // 工具栏
+ const toolbar = (
+
+ );
+
return (
{/* 统计卡片 */}
-
+
总模板数
@@ -294,184 +335,24 @@ const NotificationTemplateList: React.FC = () => {
{/* 模板管理 */}
-
-
- 通知模板
-
- 管理通知消息模板,支持 FreeMarker 语法
-
-
-
+
+ 通知模板
+
+ 管理通知消息模板,支持 FreeMarker 语法
+
-
- {/* 搜索区域 */}
-
-
- setQuery(prev => ({ ...prev, name: e.target.value }))}
- onKeyPress={(e) => {
- if (e.key === 'Enter') {
- handleSearch();
- }
- }}
- />
-
-
-
-
-
- {/* 表格 */}
-
-
-
-
- 模板名称
- 模板编码
- 标题模板
- 渠道类型
- 状态
- 创建时间
- 操作
-
-
-
- {loading ? (
-
-
-
-
-
- ) : list.length === 0 ? (
-
-
- 暂无数据
-
-
- ) : (
- list.map((template) => (
-
- {template.name}
- {template.code}
-
- {template.titleTemplate || '-'}
-
-
-
- {getChannelTypeLabel(template.channelType)}
-
-
-
-
- {template.enabled ? "启用" : "禁用"}
-
-
- {template.createTime}
-
-
-
-
- {template.enabled ? (
-
- ) : (
-
- )}
-
-
-
-
- ))
- )}
-
-
-
-
- {/* 分页 */}
-
-
-
+
+
+
+ ref={tableRef}
+ fetchFn={fetchData}
+ columns={columns}
+ searchFields={searchFields}
+ toolbar={toolbar}
+ rowKey="id"
+ minWidth="1300px"
+ />
@@ -481,7 +362,7 @@ const NotificationTemplateList: React.FC = () => {
editId={currentTemplate?.id}
channelTypes={CHANNEL_TYPE_OPTIONS}
onOpenChange={setModalVisible}
- onSuccess={loadData}
+ onSuccess={handleSuccess}
/>
{/* 测试模板对话框 */}