重构消息通知弹窗
This commit is contained in:
parent
0a10f1def1
commit
69fc7fe163
@ -197,104 +197,96 @@ export const ApplicationCard: React.FC<ApplicationCardProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 最近部署记录 */}
|
||||
{/* 最近部署记录 - 固定显示2条槽位 */}
|
||||
<div className="mt-2 space-y-2">
|
||||
<div className="flex items-center gap-1 text-[10px] font-medium text-muted-foreground mb-1">
|
||||
<History className="h-3 w-3" />
|
||||
<span>最近记录</span>
|
||||
</div>
|
||||
{app.recentDeployRecords && app.recentDeployRecords.length > 0 ? (
|
||||
<>
|
||||
{/* 显示实际记录(最多2条) */}
|
||||
{app.recentDeployRecords.slice(0, 2).map((record) => {
|
||||
const { icon: StatusIcon, color } = getStatusIcon(record.status);
|
||||
return (
|
||||
<div key={record.id} className="p-1.5 rounded-md bg-muted/30 border border-muted/50 space-y-1">
|
||||
{/* 第一行:状态、编号、部署人 */}
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
{/* 始终显示2条记录槽位,确保卡片高度一致 */}
|
||||
{Array.from({ length: 2 }).map((_, index) => {
|
||||
const record = app.recentDeployRecords?.[index];
|
||||
|
||||
if (record) {
|
||||
// 显示实际记录
|
||||
const { icon: StatusIcon, color } = getStatusIcon(record.status);
|
||||
return (
|
||||
<div key={record.id} className="p-1.5 rounded-md bg-muted/30 border border-muted/50 space-y-1 h-[60px]">
|
||||
{/* 第一行:状态、编号、部署人 */}
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<div className="flex items-center gap-1">
|
||||
<StatusIcon className={cn("h-3.5 w-3.5 shrink-0", color, record.status === 'RUNNING' && "animate-spin")} />
|
||||
<span className={cn("text-[10px] font-semibold", color)}>{getStatusText(record.status)}</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleDeployRecordClick(record)}
|
||||
className="flex items-center gap-1 px-1.5 py-0.5 rounded bg-background/50 hover:bg-background/80 transition-colors cursor-pointer"
|
||||
title="点击查看工作流详情"
|
||||
>
|
||||
<Hash className="h-2.5 w-2.5 text-muted-foreground" />
|
||||
<span className="text-[10px] text-muted-foreground font-mono hover:text-primary transition-colors">
|
||||
{record.id}
|
||||
</span>
|
||||
</button>
|
||||
{record.deployBy && (
|
||||
<div className="flex items-center gap-1">
|
||||
<StatusIcon className={cn("h-3.5 w-3.5 shrink-0", color, record.status === 'RUNNING' && "animate-spin")} />
|
||||
<span className={cn("text-[10px] font-semibold", color)}>{getStatusText(record.status)}</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleDeployRecordClick(record)}
|
||||
className="flex items-center gap-1 px-1.5 py-0.5 rounded bg-background/50 hover:bg-background/80 transition-colors cursor-pointer"
|
||||
title="点击查看工作流详情"
|
||||
>
|
||||
<Hash className="h-2.5 w-2.5 text-muted-foreground" />
|
||||
<span className="text-[10px] text-muted-foreground font-mono hover:text-primary transition-colors">
|
||||
{record.id}
|
||||
</span>
|
||||
</button>
|
||||
{record.deployBy && (
|
||||
<div className="flex items-center gap-1">
|
||||
<User className="h-2.5 w-2.5 text-muted-foreground" />
|
||||
<span className="text-[10px] text-muted-foreground">{record.deployBy}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 第二行:时间信息(一行显示) */}
|
||||
{(record.startTime || record.endTime || record.duration) && (
|
||||
<div className="flex items-center gap-1.5 text-[10px] text-muted-foreground flex-nowrap overflow-hidden">
|
||||
<Clock className="h-2.5 w-2.5 shrink-0" />
|
||||
<div className="flex items-center gap-1 flex-nowrap min-w-0">
|
||||
{record.startTime && (
|
||||
<span className="whitespace-nowrap">{formatTime(record.startTime)}</span>
|
||||
)}
|
||||
{record.endTime && (
|
||||
<>
|
||||
<span className="px-0.5 shrink-0">→</span>
|
||||
<span className="whitespace-nowrap">{formatTime(record.endTime)}</span>
|
||||
</>
|
||||
)}
|
||||
{record.duration && (
|
||||
<>
|
||||
<span className="px-0.5 text-muted-foreground/50 shrink-0">•</span>
|
||||
<span className="font-medium whitespace-nowrap">{formatDuration(record.duration)}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 第三行:备注 */}
|
||||
{record.deployRemark && (
|
||||
<div className="flex items-center gap-1 text-[10px] text-muted-foreground pt-0.5 border-t border-muted/30">
|
||||
<FileText className="h-2.5 w-2.5 shrink-0" />
|
||||
<span className="truncate">{record.deployRemark}</span>
|
||||
<User className="h-2.5 w-2.5 text-muted-foreground" />
|
||||
<span className="text-[10px] text-muted-foreground">{record.deployBy}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{/* 如果记录少于2条,用骨架屏补充 */}
|
||||
{app.recentDeployRecords.length < 2 && (
|
||||
Array.from({ length: 2 - app.recentDeployRecords.length }).map((_, index) => (
|
||||
<div key={`skeleton-${index}`} className="p-1.5 rounded-md bg-muted/30 border border-muted/50 space-y-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<Skeleton className="h-3.5 w-16" />
|
||||
<Skeleton className="h-4 w-12" />
|
||||
<Skeleton className="h-3.5 w-12 ml-auto" />
|
||||
|
||||
{/* 第二行:时间信息(一行显示) */}
|
||||
{(record.startTime || record.endTime || record.duration) && (
|
||||
<div className="flex items-center gap-1.5 text-[10px] text-muted-foreground flex-nowrap overflow-hidden">
|
||||
<Clock className="h-2.5 w-2.5 shrink-0" />
|
||||
<div className="flex items-center gap-1 flex-nowrap min-w-0">
|
||||
{record.startTime && (
|
||||
<span className="whitespace-nowrap">{formatTime(record.startTime)}</span>
|
||||
)}
|
||||
{record.endTime && (
|
||||
<>
|
||||
<span className="px-0.5 shrink-0">→</span>
|
||||
<span className="whitespace-nowrap">{formatTime(record.endTime)}</span>
|
||||
</>
|
||||
)}
|
||||
{record.duration && (
|
||||
<>
|
||||
<span className="px-0.5 text-muted-foreground/50 shrink-0">•</span>
|
||||
<span className="font-medium whitespace-nowrap">{formatDuration(record.duration)}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Skeleton className="h-3 w-32" />
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
/* 如果没有记录,显示2条骨架记录 */
|
||||
Array.from({ length: 2 }).map((_, index) => (
|
||||
<div key={`skeleton-${index}`} className="p-1.5 rounded-md bg-muted/30 border border-muted/50 space-y-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<Skeleton className="h-3.5 w-16" />
|
||||
<Skeleton className="h-4 w-12" />
|
||||
<Skeleton className="h-3.5 w-12 ml-auto" />
|
||||
)}
|
||||
|
||||
{/* 第三行:备注 */}
|
||||
{record.deployRemark && (
|
||||
<div className="flex items-center gap-1 text-[10px] text-muted-foreground pt-0.5 border-t border-muted/30">
|
||||
<FileText className="h-2.5 w-2.5 shrink-0" />
|
||||
<span className="truncate">{record.deployRemark}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Skeleton className="h-3 w-32" />
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
);
|
||||
} else {
|
||||
// 显示骨架屏占位 - 3条线模拟实际记录的3行
|
||||
return (
|
||||
<div key={`skeleton-${index}`} className="p-1.5 rounded-md bg-muted/30 border border-muted/50 space-y-1 h-[60px]">
|
||||
{/* 第一行:状态、编号、部署人 */}
|
||||
<div className="flex items-center gap-2">
|
||||
<Skeleton className="h-3.5 w-16" />
|
||||
<Skeleton className="h-4 w-12" />
|
||||
<Skeleton className="h-3.5 w-12 ml-auto" />
|
||||
</div>
|
||||
{/* 第二行:时间信息 */}
|
||||
<Skeleton className="h-3 w-32" />
|
||||
{/* 第三行:备注 */}
|
||||
<Skeleton className="h-3 w-24" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
372
frontend/src/pages/Resource/External/List/index.tsx
vendored
372
frontend/src/pages/Resource/External/List/index.tsx
vendored
@ -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<ExternalSystemResponse[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [query, setQuery] = useState<ExternalSystemQuery>({
|
||||
pageNum: 0,
|
||||
pageSize: DEFAULT_PAGE_SIZE,
|
||||
});
|
||||
const [total, setTotal] = useState(0);
|
||||
const tableRef = useRef<PaginatedTableRef<ExternalSystemResponse>>(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<ExternalSystemResponse>();
|
||||
|
||||
// 加载数据
|
||||
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<ExternalSystemResponse>[] = 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 <Badge variant={typeInfo.variant}>{typeInfo.label}</Badge>;
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'url',
|
||||
title: '系统地址',
|
||||
width: '250px',
|
||||
render: (_, item) => (
|
||||
<a
|
||||
href={item.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-600 hover:underline flex items-center gap-1"
|
||||
onClick={() => {
|
||||
setTimeout(() => tableRef.current?.refresh(), 100);
|
||||
}}
|
||||
>
|
||||
<Link className="h-3 w-3" />
|
||||
{item.url}
|
||||
</a>
|
||||
),
|
||||
},
|
||||
{
|
||||
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) => (
|
||||
<Switch
|
||||
checked={item.enabled}
|
||||
onCheckedChange={(checked) => handleStatusChange(item.id, checked)}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'actions',
|
||||
title: '操作',
|
||||
width: '250px',
|
||||
sticky: true,
|
||||
render: (_, item) => (
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 px-2"
|
||||
onClick={() => {
|
||||
setCurrentRecord(item);
|
||||
setEditDialogOpen(true);
|
||||
}}
|
||||
>
|
||||
<Edit className="h-3.5 w-3.5 mr-1" />
|
||||
编辑
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 px-2"
|
||||
onClick={() => handleTestConnection(item.id)}
|
||||
>
|
||||
<Link className="h-3.5 w-3.5 mr-1" />
|
||||
测试连接
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 px-2 text-destructive hover:text-destructive"
|
||||
onClick={() => {
|
||||
setCurrentRecord(item);
|
||||
setDeleteDialogOpen(true);
|
||||
}}
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5 mr-1" />
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
], []);
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
{/* 页面标题 */}
|
||||
@ -179,160 +278,21 @@ const ExternalPage: React.FC = () => {
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* 搜索过滤 */}
|
||||
<Card>
|
||||
<div className="p-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<Input
|
||||
placeholder="系统名称"
|
||||
value={query.name || ''}
|
||||
onChange={(e) => setQuery({ ...query, name: e.target.value })}
|
||||
className="max-w-[200px]"
|
||||
/>
|
||||
<Select
|
||||
value={query.type}
|
||||
onValueChange={(value) => setQuery({ ...query, type: value as SystemType })}
|
||||
>
|
||||
<SelectTrigger className="max-w-[200px]">
|
||||
<SelectValue placeholder="系统类型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="JENKINS">Jenkins</SelectItem>
|
||||
<SelectItem value="GIT">Git</SelectItem>
|
||||
<SelectItem value="ZENTAO">禅道</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Select
|
||||
value={query.enabled?.toString()}
|
||||
onValueChange={(value) => setQuery({ ...query, enabled: value === 'true' })}
|
||||
>
|
||||
<SelectTrigger className="max-w-[200px]">
|
||||
<SelectValue placeholder="状态" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="true">已启用</SelectItem>
|
||||
<SelectItem value="false">已禁用</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button variant="outline" onClick={handleReset}>
|
||||
重置
|
||||
</Button>
|
||||
<Button onClick={handleSearch}>
|
||||
搜索
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* 数据表格 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>系统列表</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="rounded-md border">
|
||||
<Table minWidth="1120px">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead width="150px">系统名称</TableHead>
|
||||
<TableHead width="100px">系统类型</TableHead>
|
||||
<TableHead width="250px">系统地址</TableHead>
|
||||
<TableHead width="120px">认证方式</TableHead>
|
||||
<TableHead width="150px">最后连接时间</TableHead>
|
||||
<TableHead width="100px">状态</TableHead>
|
||||
<TableHead width="250px" sticky>操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{list.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={7} className="h-24 text-center">
|
||||
暂无数据
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
list.map((item) => (
|
||||
<TableRow key={item.id}>
|
||||
<TableCell width="150px" className="font-medium">{item.name}</TableCell>
|
||||
<TableCell width="100px">
|
||||
<Badge variant={getSystemTypeLabel(item.type).variant}>
|
||||
{getSystemTypeLabel(item.type).label}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell width="250px">
|
||||
<a
|
||||
href={item.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-600 hover:underline flex items-center gap-1"
|
||||
onClick={() => {
|
||||
// 点击链接后刷新列表
|
||||
setTimeout(() => loadData(), 100);
|
||||
}}
|
||||
>
|
||||
<Link className="h-3 w-3" />
|
||||
{item.url}
|
||||
</a>
|
||||
</TableCell>
|
||||
<TableCell width="120px">{getAuthTypeLabel(item.authType)}</TableCell>
|
||||
<TableCell width="150px" className="text-muted-foreground">
|
||||
{item.lastConnectTime || '-'}
|
||||
</TableCell>
|
||||
<TableCell width="100px">
|
||||
<Switch
|
||||
checked={item.enabled}
|
||||
onCheckedChange={(checked) => handleStatusChange(item.id, checked)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell width="250px" 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={() => handleTestConnection(item.id)}
|
||||
>
|
||||
<Link 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>
|
||||
<PaginatedTable<ExternalSystemResponse, ExternalSystemQuery>
|
||||
ref={tableRef}
|
||||
fetchFn={fetchData}
|
||||
columns={columns}
|
||||
searchFields={searchFields}
|
||||
rowKey="id"
|
||||
minWidth="1120px"
|
||||
pageSize={DEFAULT_PAGE_SIZE}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@ -341,13 +301,13 @@ const ExternalPage: 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