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} /> {/* 测试模板对话框 */}