From 69fc7fe163585d3c2168551dde3ebcf6f493cecf Mon Sep 17 00:00:00 2001 From: dengqichen Date: Fri, 28 Nov 2025 17:51:29 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E6=B6=88=E6=81=AF=E9=80=9A?= =?UTF-8?q?=E7=9F=A5=E5=BC=B9=E7=AA=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Dashboard/components/ApplicationCard.tsx | 166 ++++---- .../pages/Resource/External/List/index.tsx | 372 ++++++++---------- 2 files changed, 245 insertions(+), 293 deletions(-) diff --git a/frontend/src/pages/Dashboard/components/ApplicationCard.tsx b/frontend/src/pages/Dashboard/components/ApplicationCard.tsx index 03528b23..7e9df690 100644 --- a/frontend/src/pages/Dashboard/components/ApplicationCard.tsx +++ b/frontend/src/pages/Dashboard/components/ApplicationCard.tsx @@ -197,104 +197,96 @@ export const ApplicationCard: React.FC = ({ )} - {/* 最近部署记录 */} + {/* 最近部署记录 - 固定显示2条槽位 */}
最近记录
- {app.recentDeployRecords && app.recentDeployRecords.length > 0 ? ( - <> - {/* 显示实际记录(最多2条) */} - {app.recentDeployRecords.slice(0, 2).map((record) => { - const { icon: StatusIcon, color } = getStatusIcon(record.status); - return ( -
- {/* 第一行:状态、编号、部署人 */} -
+ {/* 始终显示2条记录槽位,确保卡片高度一致 */} + {Array.from({ length: 2 }).map((_, index) => { + const record = app.recentDeployRecords?.[index]; + + if (record) { + // 显示实际记录 + const { icon: StatusIcon, color } = getStatusIcon(record.status); + return ( +
+ {/* 第一行:状态、编号、部署人 */} +
+
+ + {getStatusText(record.status)} +
+ + {record.deployBy && (
- - {getStatusText(record.status)} -
- - {record.deployBy && ( -
- - {record.deployBy} -
- )} -
- - {/* 第二行:时间信息(一行显示) */} - {(record.startTime || record.endTime || record.duration) && ( -
- -
- {record.startTime && ( - {formatTime(record.startTime)} - )} - {record.endTime && ( - <> - - {formatTime(record.endTime)} - - )} - {record.duration && ( - <> - - {formatDuration(record.duration)} - - )} -
-
- )} - - {/* 第三行:备注 */} - {record.deployRemark && ( -
- - {record.deployRemark} + + {record.deployBy}
)}
- ); - })} - {/* 如果记录少于2条,用骨架屏补充 */} - {app.recentDeployRecords.length < 2 && ( - Array.from({ length: 2 - app.recentDeployRecords.length }).map((_, index) => ( -
-
- - - + + {/* 第二行:时间信息(一行显示) */} + {(record.startTime || record.endTime || record.duration) && ( +
+ +
+ {record.startTime && ( + {formatTime(record.startTime)} + )} + {record.endTime && ( + <> + + {formatTime(record.endTime)} + + )} + {record.duration && ( + <> + + {formatDuration(record.duration)} + + )} +
- -
- )) - )} - - ) : ( - /* 如果没有记录,显示2条骨架记录 */ - Array.from({ length: 2 }).map((_, index) => ( -
-
- - - + )} + + {/* 第三行:备注 */} + {record.deployRemark && ( +
+ + {record.deployRemark} +
+ )}
- -
- )) - )} + ); + } else { + // 显示骨架屏占位 - 3条线模拟实际记录的3行 + return ( +
+ {/* 第一行:状态、编号、部署人 */} +
+ + + +
+ {/* 第二行:时间信息 */} + + {/* 第三行:备注 */} + +
+ ); + } + })}
diff --git a/frontend/src/pages/Resource/External/List/index.tsx b/frontend/src/pages/Resource/External/List/index.tsx index 3dd443e0..0c675486 100644 --- a/frontend/src/pages/Resource/External/List/index.tsx +++ b/frontend/src/pages/Resource/External/List/index.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useMemo, useRef } from 'react'; import { PageContainer } from '@/components/ui/page-container'; import { Card, @@ -6,21 +6,11 @@ import { CardHeader, CardTitle, } 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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Switch } from '@/components/ui/switch'; import { useToast } from '@/components/ui/use-toast'; -import { DataTablePagination } from '@/components/ui/pagination'; +import { PaginatedTable, type ColumnDef, type SearchFieldDef, type PaginatedTableRef } from '@/components/ui/paginated-table'; import { Plus, Edit, Trash2, Link, Server, Activity, Database } from 'lucide-react'; import EditDialog from './components/EditDialog'; import DeleteDialog from './components/DeleteDialog'; @@ -31,13 +21,7 @@ const DEFAULT_PAGE_SIZE = 10; const ExternalPage: React.FC = () => { const { toast } = useToast(); - const [list, setList] = useState([]); - const [loading, setLoading] = useState(false); - const [query, setQuery] = useState({ - pageNum: 0, - pageSize: DEFAULT_PAGE_SIZE, - }); - const [total, setTotal] = useState(0); + const tableRef = useRef>(null); const [stats, setStats] = useState({ total: 0, enabled: 0, disabled: 0 }); // 对话框状态 @@ -45,43 +29,21 @@ const ExternalPage: React.FC = () => { const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [currentRecord, setCurrentRecord] = useState(); - // 加载数据 - const loadData = async () => { - setLoading(true); - try { - const response = await service.getExternalSystemsPage(query); - if (response) { - setList(response.content || []); - setTotal(response.totalElements || 0); - // 计算统计数据 - const all = response.content || []; - setStats({ - 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); - } + // 包装 fetchFn + const fetchData = async (query: ExternalSystemQuery) => { + const response = await service.getExternalSystemsPage(query); + // 计算统计数据 + const all = response.content || []; + setStats({ + total: response.totalElements || 0, + enabled: all.filter((item) => item.enabled).length, + disabled: all.filter((item) => !item.enabled).length, + }); + return response; }; - useEffect(() => { - loadData(); - }, [query.pageNum, query.pageSize]); - - // 搜索 - const handleSearch = () => { - setQuery({ ...query, pageNum: 0 }); - loadData(); - }; - - // 重置 - const handleReset = () => { - setQuery({ pageNum: 0, pageSize: DEFAULT_PAGE_SIZE }); - loadData(); + const handleSuccess = () => { + tableRef.current?.refresh(); }; // 测试连接 @@ -94,7 +56,7 @@ const ExternalPage: React.FC = () => { variant: success ? 'default' : 'destructive', }); // 刷新列表以更新最后连接时间等信息 - loadData(); + tableRef.current?.refresh(); } catch (error) { toast({ title: '测试失败', description: '无法测试连接', variant: 'destructive' }); } @@ -105,7 +67,7 @@ const ExternalPage: React.FC = () => { try { await service.updateStatus(id, enabled); toast({ title: '更新成功', description: `系统状态已${enabled ? '启用' : '禁用'}` }); - loadData(); + tableRef.current?.refresh(); } catch (error) { toast({ title: '更新失败', description: '无法更新系统状态', variant: 'destructive' }); } @@ -131,6 +93,143 @@ const ExternalPage: React.FC = () => { return authTypeMap[authType]; }; + // 搜索字段定义 + const searchFields: SearchFieldDef[] = useMemo(() => [ + { key: 'name', type: 'input', placeholder: '系统名称', width: 'w-[200px]' }, + { + key: 'type', + type: 'select', + placeholder: '系统类型', + width: 'w-[200px]', + options: [ + { label: 'Jenkins', value: 'JENKINS' }, + { label: 'Git', value: 'GIT' }, + { label: '禅道', value: 'ZENTAO' }, + ], + }, + { + key: 'enabled', + type: 'select', + placeholder: '状态', + width: 'w-[200px]', + options: [ + { label: '已启用', value: 'true' }, + { label: '已禁用', value: 'false' }, + ], + }, + ], []); + + // 列定义 + const columns: ColumnDef[] = useMemo(() => [ + { + key: 'id', + title: 'ID', + dataIndex: 'id', + width: '80px', + }, + { + key: 'name', + title: '系统名称', + dataIndex: 'name', + width: '150px', + }, + { + key: 'type', + title: '系统类型', + width: '100px', + render: (_, item) => { + const typeInfo = getSystemTypeLabel(item.type); + return {typeInfo.label}; + }, + }, + { + key: 'url', + title: '系统地址', + width: '250px', + render: (_, item) => ( + { + setTimeout(() => tableRef.current?.refresh(), 100); + }} + > + + {item.url} + + ), + }, + { + key: 'authType', + title: '认证方式', + width: '120px', + render: (_, item) => getAuthTypeLabel(item.authType), + }, + { + key: 'lastConnectTime', + title: '最后连接时间', + dataIndex: 'lastConnectTime', + width: '150px', + render: (value) => value || '-', + }, + { + key: 'enabled', + title: '状态', + width: '100px', + render: (_, item) => ( + handleStatusChange(item.id, checked)} + /> + ), + }, + { + key: 'actions', + title: '操作', + width: '250px', + sticky: true, + render: (_, item) => ( +
+ + + +
+ ), + }, + ], []); + return ( {/* 页面标题 */} @@ -179,160 +278,21 @@ const ExternalPage: React.FC = () => {
- {/* 搜索过滤 */} - -
-
- setQuery({ ...query, name: e.target.value })} - className="max-w-[200px]" - /> - - - - -
-
-
- {/* 数据表格 */} 系统列表 -
- - - - 系统名称 - 系统类型 - 系统地址 - 认证方式 - 最后连接时间 - 状态 - 操作 - - - - {list.length === 0 ? ( - - - 暂无数据 - - - ) : ( - list.map((item) => ( - - {item.name} - - - {getSystemTypeLabel(item.type).label} - - - - { - // 点击链接后刷新列表 - setTimeout(() => loadData(), 100); - }} - > - - {item.url} - - - {getAuthTypeLabel(item.authType)} - - {item.lastConnectTime || '-'} - - - handleStatusChange(item.id, checked)} - /> - - -
- - - -
-
-
- )) - )} -
-
-
- setQuery({ ...query, pageNum: page })} - /> -
-
+ + ref={tableRef} + fetchFn={fetchData} + columns={columns} + searchFields={searchFields} + rowKey="id" + minWidth="1120px" + pageSize={DEFAULT_PAGE_SIZE} + />
@@ -341,13 +301,13 @@ const ExternalPage: React.FC = () => { open={editDialogOpen} record={currentRecord} onOpenChange={setEditDialogOpen} - onSuccess={loadData} + onSuccess={handleSuccess} /> );