重构消息通知弹窗
This commit is contained in:
parent
0795570470
commit
6c9df6dd58
@ -1,8 +1,7 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useRef, useMemo } from 'react';
|
||||||
import { PageContainer } from '@/components/ui/page-container';
|
import { PageContainer } from '@/components/ui/page-container';
|
||||||
import {
|
import {
|
||||||
CodeOutlined,
|
CodeOutlined,
|
||||||
GithubOutlined,
|
|
||||||
JavaOutlined,
|
JavaOutlined,
|
||||||
NodeIndexOutlined,
|
NodeIndexOutlined,
|
||||||
PythonOutlined,
|
PythonOutlined,
|
||||||
@ -15,75 +14,37 @@ import type { ApplicationCategoryResponse } from '../Category/types';
|
|||||||
import ApplicationModal from './components/ApplicationModal';
|
import ApplicationModal from './components/ApplicationModal';
|
||||||
import DeleteDialog from './components/DeleteDialog';
|
import DeleteDialog from './components/DeleteDialog';
|
||||||
import CategoryManageDialog from './components/CategoryManageDialog';
|
import CategoryManageDialog from './components/CategoryManageDialog';
|
||||||
import {
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
||||||
Table,
|
|
||||||
TableHeader,
|
|
||||||
TableBody,
|
|
||||||
TableHead,
|
|
||||||
TableRow,
|
|
||||||
TableCell,
|
|
||||||
} from '@/components/ui/table';
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
CardDescription,
|
|
||||||
} from '@/components/ui/card';
|
|
||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from '@/components/ui/select';
|
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { useToast } from '@/components/ui/use-toast';
|
import { PaginatedTable, type ColumnDef, type SearchFieldDef, type PaginatedTableRef } from '@/components/ui/paginated-table';
|
||||||
import { useForm } from 'react-hook-form';
|
import { Plus, Edit, Trash2, Server, Activity, Database, Settings } from 'lucide-react';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
|
||||||
import { searchFormSchema, type SearchFormValues } from './schema';
|
|
||||||
import { DataTablePagination } from '@/components/ui/pagination';
|
|
||||||
import { Plus, Edit, Trash2, Server, Activity, Database, ExternalLink, Settings } from 'lucide-react';
|
|
||||||
|
|
||||||
interface Column {
|
// 获取开发语言信息
|
||||||
accessorKey?: keyof Application;
|
const getLanguageInfo = (language: DevelopmentLanguageTypeEnum) => {
|
||||||
id?: string;
|
switch (language) {
|
||||||
header: string;
|
case DevelopmentLanguageTypeEnum.JAVA:
|
||||||
size: number;
|
return { label: 'Java', icon: <JavaOutlined />, color: '#E76F00' };
|
||||||
cell?: (props: { row: { original: Application } }) => React.ReactNode;
|
case DevelopmentLanguageTypeEnum.NODE_JS:
|
||||||
}
|
return { label: 'NodeJS', icon: <NodeIndexOutlined />, color: '#339933' };
|
||||||
|
case DevelopmentLanguageTypeEnum.PYTHON:
|
||||||
const DEFAULT_PAGE_SIZE = 10;
|
return { label: 'Python', icon: <PythonOutlined />, color: '#3776AB' };
|
||||||
|
case DevelopmentLanguageTypeEnum.GO:
|
||||||
|
return { label: 'Go', icon: <CodeOutlined />, color: '#00ADD8' };
|
||||||
|
default:
|
||||||
|
return { label: language || '未知', icon: <CodeOutlined />, color: '#666666' };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const ApplicationList: React.FC = () => {
|
const ApplicationList: React.FC = () => {
|
||||||
|
const tableRef = useRef<PaginatedTableRef<Application>>(null);
|
||||||
const [modalVisible, setModalVisible] = useState(false);
|
const [modalVisible, setModalVisible] = useState(false);
|
||||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||||
const [categoryDialogOpen, setCategoryDialogOpen] = useState(false);
|
const [categoryDialogOpen, setCategoryDialogOpen] = useState(false);
|
||||||
const [currentApplication, setCurrentApplication] = useState<Application>();
|
const [currentApplication, setCurrentApplication] = useState<Application>();
|
||||||
const [list, setList] = useState<Application[]>([]);
|
|
||||||
const [categories, setCategories] = useState<ApplicationCategoryResponse[]>([]);
|
const [categories, setCategories] = useState<ApplicationCategoryResponse[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [selectedCategoryId, setSelectedCategoryId] = useState<number>();
|
|
||||||
const [pagination, setPagination] = useState({
|
|
||||||
pageNum: 0,
|
|
||||||
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 { toast } = useToast();
|
|
||||||
|
|
||||||
const form = useForm<SearchFormValues>({
|
|
||||||
resolver: zodResolver(searchFormSchema),
|
|
||||||
defaultValues: {
|
|
||||||
appCode: '',
|
|
||||||
appName: '',
|
|
||||||
language: undefined,
|
|
||||||
enabled: undefined,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// 加载分类列表
|
// 加载分类列表
|
||||||
const loadCategories = async () => {
|
const loadCategories = async () => {
|
||||||
@ -99,50 +60,19 @@ const ApplicationList: React.FC = () => {
|
|||||||
loadCategories();
|
loadCategories();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const loadData = async (params?: ApplicationQuery) => {
|
// 包装 fetchFn,同时更新统计数据
|
||||||
setLoading(true);
|
const fetchData = async (query: ApplicationQuery) => {
|
||||||
try {
|
const result = await getApplicationPage(query);
|
||||||
const queryParams: ApplicationQuery = {
|
// 更新统计
|
||||||
...params,
|
const all = result.content || [];
|
||||||
applicationCategoryId: selectedCategoryId,
|
|
||||||
pageNum: pagination.pageNum,
|
|
||||||
pageSize: pagination.pageSize,
|
|
||||||
};
|
|
||||||
const data = await getApplicationPage(queryParams);
|
|
||||||
setList(data.content || []);
|
|
||||||
setPagination({
|
|
||||||
...pagination,
|
|
||||||
totalElements: data.totalElements,
|
|
||||||
});
|
|
||||||
// 计算统计数据
|
|
||||||
const all = data.content || [];
|
|
||||||
setStats({
|
setStats({
|
||||||
total: all.length,
|
total: all.length,
|
||||||
enabled: all.filter((item) => item.enabled).length,
|
enabled: all.filter((item) => item.enabled).length,
|
||||||
disabled: all.filter((item) => !item.enabled).length,
|
disabled: all.filter((item) => !item.enabled).length,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
return result;
|
||||||
toast({
|
|
||||||
variant: 'destructive',
|
|
||||||
title: '获取应用列表失败',
|
|
||||||
duration: 3000,
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePageChange = (page: number) => {
|
|
||||||
setPagination({
|
|
||||||
...pagination,
|
|
||||||
pageNum: page,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
loadData(form.getValues());
|
|
||||||
}, [selectedCategoryId, pagination.pageNum, pagination.pageSize]);
|
|
||||||
|
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
setCurrentApplication(undefined);
|
setCurrentApplication(undefined);
|
||||||
setModalVisible(true);
|
setModalVisible(true);
|
||||||
@ -161,87 +91,71 @@ const ApplicationList: React.FC = () => {
|
|||||||
const handleSuccess = () => {
|
const handleSuccess = () => {
|
||||||
setModalVisible(false);
|
setModalVisible(false);
|
||||||
setCurrentApplication(undefined);
|
setCurrentApplication(undefined);
|
||||||
loadData(form.getValues());
|
tableRef.current?.refresh();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取开发语言信息
|
// 搜索字段定义
|
||||||
const getLanguageInfo = (language: DevelopmentLanguageTypeEnum) => {
|
const searchFields: SearchFieldDef[] = useMemo(() => [
|
||||||
switch (language) {
|
{ key: 'appCode', type: 'input', placeholder: '应用编码', width: 'w-[180px]' },
|
||||||
case DevelopmentLanguageTypeEnum.JAVA:
|
{ key: 'appName', type: 'input', placeholder: '应用名称', width: 'w-[180px]' },
|
||||||
return {
|
{
|
||||||
label: 'Java',
|
key: 'applicationCategoryId',
|
||||||
icon: <JavaOutlined />,
|
type: 'select',
|
||||||
color: '#E76F00',
|
placeholder: '全部分类',
|
||||||
};
|
width: 'w-[180px]',
|
||||||
case DevelopmentLanguageTypeEnum.NODE_JS:
|
options: categories.map(c => ({ label: c.name, value: c.id })),
|
||||||
return {
|
},
|
||||||
label: 'NodeJS',
|
{
|
||||||
icon: <NodeIndexOutlined />,
|
key: 'language',
|
||||||
color: '#339933',
|
type: 'select',
|
||||||
};
|
placeholder: '开发语言',
|
||||||
case DevelopmentLanguageTypeEnum.PYTHON:
|
width: 'w-[150px]',
|
||||||
return {
|
options: [
|
||||||
label: 'Python',
|
{ label: 'Java', value: DevelopmentLanguageTypeEnum.JAVA },
|
||||||
icon: <PythonOutlined />,
|
{ label: 'NodeJS', value: DevelopmentLanguageTypeEnum.NODE_JS },
|
||||||
color: '#3776AB',
|
{ label: 'Python', value: DevelopmentLanguageTypeEnum.PYTHON },
|
||||||
};
|
{ label: 'Go', value: DevelopmentLanguageTypeEnum.GO },
|
||||||
case DevelopmentLanguageTypeEnum.GO:
|
],
|
||||||
return {
|
},
|
||||||
label: 'Go',
|
{
|
||||||
icon: <CodeOutlined />,
|
key: 'enabled',
|
||||||
color: '#00ADD8',
|
type: 'select',
|
||||||
};
|
placeholder: '状态',
|
||||||
default:
|
width: 'w-[120px]',
|
||||||
return {
|
options: [
|
||||||
label: language || '未知',
|
{ label: '启用', value: 'true' },
|
||||||
icon: <CodeOutlined />,
|
{ label: '禁用', value: 'false' },
|
||||||
color: '#666666',
|
],
|
||||||
};
|
},
|
||||||
}
|
], [categories]);
|
||||||
};
|
|
||||||
|
|
||||||
const columns: Column[] = [
|
// 列定义
|
||||||
|
const columns: ColumnDef<Application>[] = useMemo(() => [
|
||||||
{
|
{
|
||||||
id: 'category',
|
key: 'category',
|
||||||
header: '应用分类',
|
title: '应用分类',
|
||||||
size: 120,
|
width: '120px',
|
||||||
cell: ({ row }) => (
|
render: (_, record) => record.applicationCategory?.name || '-',
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span>{row.original.applicationCategory?.name || '-'}</span>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
|
{ key: 'appCode', title: '应用编码', dataIndex: 'appCode', width: '200px' },
|
||||||
|
{ key: 'appName', title: '应用名称', dataIndex: 'appName', width: '130px' },
|
||||||
|
{ key: 'appDesc', title: '应用描述', dataIndex: 'appDesc', width: '180px' },
|
||||||
{
|
{
|
||||||
accessorKey: 'appCode',
|
key: 'teamCount',
|
||||||
header: '应用编码',
|
title: '团队使用数量',
|
||||||
size: 200,
|
width: '120px',
|
||||||
},
|
render: (_, record) => (
|
||||||
{
|
|
||||||
accessorKey: 'appName',
|
|
||||||
header: '应用名称',
|
|
||||||
size: 130,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'appDesc',
|
|
||||||
header: '应用描述',
|
|
||||||
size: 180,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'teamCount',
|
|
||||||
header: '团队使用数量',
|
|
||||||
size: 120,
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<span className="font-medium">{row.original.teamCount || 0}</span>
|
<span className="font-medium">{record.teamCount || 0}</span>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'language',
|
key: 'language',
|
||||||
header: '开发语言',
|
title: '开发语言',
|
||||||
size: 120,
|
width: '120px',
|
||||||
cell: ({ row }) => {
|
render: (_, record) => {
|
||||||
const langInfo = getLanguageInfo(row.original.language);
|
const langInfo = getLanguageInfo(record.language);
|
||||||
return (
|
return (
|
||||||
<Badge variant="outline" className="flex items-center gap-1 inline-flex">
|
<Badge variant="outline" className="flex items-center gap-1 inline-flex">
|
||||||
{langInfo.icon}
|
{langInfo.icon}
|
||||||
@ -251,46 +165,53 @@ const ApplicationList: React.FC = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'enabled',
|
key: 'enabled',
|
||||||
header: '状态',
|
title: '状态',
|
||||||
size: 100,
|
width: '100px',
|
||||||
cell: ({ row }) => (
|
render: (_, record) => (
|
||||||
<Badge variant={row.original.enabled ? 'default' : 'secondary'} className="inline-flex">
|
<Badge variant={record.enabled ? 'default' : 'secondary'} className="inline-flex">
|
||||||
{row.original.enabled ? '启用' : '禁用'}
|
{record.enabled ? '启用' : '禁用'}
|
||||||
</Badge>
|
</Badge>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{ key: 'sort', title: '排序', dataIndex: 'sort', width: '80px' },
|
||||||
{
|
{
|
||||||
accessorKey: 'sort',
|
key: 'actions',
|
||||||
header: '排序',
|
title: '操作',
|
||||||
size: 80,
|
width: '180px',
|
||||||
},
|
sticky: true,
|
||||||
{
|
render: (_, record) => (
|
||||||
id: 'actions',
|
|
||||||
header: '操作',
|
|
||||||
size: 180,
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button variant="ghost" size="sm" onClick={() => handleEdit(row.original)}>
|
<Button variant="ghost" size="sm" onClick={() => handleEdit(record)}>
|
||||||
<Edit className="h-4 w-4 mr-1" />
|
<Edit className="h-4 w-4 mr-1" />编辑
|
||||||
编辑
|
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCurrentApplication(row.original);
|
setCurrentApplication(record);
|
||||||
setDeleteDialogOpen(true);
|
setDeleteDialogOpen(true);
|
||||||
}}
|
}}
|
||||||
className="text-destructive hover:text-destructive"
|
className="text-destructive hover:text-destructive"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-4 w-4 mr-1" />
|
<Trash2 className="h-4 w-4 mr-1" />删除
|
||||||
删除
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
], []);
|
||||||
|
|
||||||
|
// 工具栏
|
||||||
|
const toolbar = (
|
||||||
|
<>
|
||||||
|
<Button variant="outline" onClick={() => setCategoryDialogOpen(true)}>
|
||||||
|
<Settings className="h-4 w-4 mr-2" />分类管理
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleAdd}>
|
||||||
|
<Plus className="h-4 w-4 mr-2" />新建应用
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
@ -331,167 +252,24 @@ const ApplicationList: React.FC = () => {
|
|||||||
{/* 应用管理 */}
|
{/* 应用管理 */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
<div>
|
||||||
<CardTitle>应用管理</CardTitle>
|
<CardTitle>应用管理</CardTitle>
|
||||||
<CardDescription className="mt-2">
|
<CardDescription className="mt-2">
|
||||||
创建和管理应用,支持Git集成和分类管理
|
创建和管理应用,支持Git集成和分类管理
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Button variant="outline" onClick={() => setCategoryDialogOpen(true)}>
|
|
||||||
<Settings className="h-4 w-4 mr-2" />
|
|
||||||
分类管理
|
|
||||||
</Button>
|
|
||||||
<Button onClick={handleAdd}>
|
|
||||||
<Plus className="h-4 w-4 mr-2" />
|
|
||||||
新建应用
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<Separator />
|
<Separator />
|
||||||
<CardContent className="pt-6">
|
<CardContent className="pt-6">
|
||||||
{/* 搜索过滤 */}
|
<PaginatedTable<Application, ApplicationQuery>
|
||||||
<div className="flex items-center gap-4 mb-6">
|
ref={tableRef}
|
||||||
<Input
|
fetchFn={fetchData}
|
||||||
placeholder="应用编码"
|
columns={columns}
|
||||||
value={form.watch('appCode')}
|
searchFields={searchFields}
|
||||||
onChange={(e) => form.setValue('appCode', e.target.value)}
|
toolbar={toolbar}
|
||||||
className="max-w-[200px]"
|
rowKey="id"
|
||||||
|
minWidth="1400px"
|
||||||
/>
|
/>
|
||||||
<Input
|
|
||||||
placeholder="应用名称"
|
|
||||||
value={form.watch('appName')}
|
|
||||||
onChange={(e) => form.setValue('appName', e.target.value)}
|
|
||||||
className="max-w-[200px]"
|
|
||||||
/>
|
|
||||||
<Select
|
|
||||||
value={selectedCategoryId?.toString()}
|
|
||||||
onValueChange={(value) => {
|
|
||||||
if (value === "all") {
|
|
||||||
setSelectedCategoryId(undefined);
|
|
||||||
} else {
|
|
||||||
setSelectedCategoryId(Number(value));
|
|
||||||
}
|
|
||||||
setPagination({ ...pagination, pageNum: 0 });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="max-w-[200px]">
|
|
||||||
<SelectValue placeholder="全部分类" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="all">全部分类</SelectItem>
|
|
||||||
{categories.map((category) => (
|
|
||||||
<SelectItem key={category.id} value={category.id.toString()}>
|
|
||||||
{category.name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<Select
|
|
||||||
value={form.watch('language')}
|
|
||||||
onValueChange={(value) =>
|
|
||||||
form.setValue('language', value as DevelopmentLanguageTypeEnum)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="max-w-[200px]">
|
|
||||||
<SelectValue placeholder="开发语言" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value={DevelopmentLanguageTypeEnum.JAVA}>Java</SelectItem>
|
|
||||||
<SelectItem value={DevelopmentLanguageTypeEnum.NODE_JS}>NodeJS</SelectItem>
|
|
||||||
<SelectItem value={DevelopmentLanguageTypeEnum.PYTHON}>Python</SelectItem>
|
|
||||||
<SelectItem value={DevelopmentLanguageTypeEnum.GO}>Go</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<Select
|
|
||||||
value={form.watch('enabled')?.toString()}
|
|
||||||
onValueChange={(value) => form.setValue('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={() => form.reset()}>
|
|
||||||
重置
|
|
||||||
</Button>
|
|
||||||
<Button onClick={() => loadData(form.getValues())}>搜索</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 数据表格 */}
|
|
||||||
<div className="rounded-md border">
|
|
||||||
<Table minWidth="1400px">
|
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
{columns.map((column) => (
|
|
||||||
<TableHead
|
|
||||||
key={column.accessorKey || column.id}
|
|
||||||
width={column.size ? `${column.size}px` : undefined}
|
|
||||||
sticky={column.id === 'actions'}
|
|
||||||
>
|
|
||||||
{column.header}
|
|
||||||
</TableHead>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{loading ? (
|
|
||||||
<TableRow>
|
|
||||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
|
||||||
<div className="flex items-center justify-center gap-2">
|
|
||||||
<div className="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent" />
|
|
||||||
<span className="text-muted-foreground">加载中...</span>
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
) : list.length === 0 ? (
|
|
||||||
<TableRow>
|
|
||||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
|
||||||
暂无数据
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
) : (
|
|
||||||
list.map((item) => (
|
|
||||||
<TableRow key={item.id}>
|
|
||||||
{columns.map((column) => {
|
|
||||||
let cellContent;
|
|
||||||
if (column.cell) {
|
|
||||||
cellContent = column.cell({ row: { original: item } });
|
|
||||||
} else if (column.accessorKey) {
|
|
||||||
const value = item[column.accessorKey];
|
|
||||||
cellContent = value != null ? String(value) : '';
|
|
||||||
} else {
|
|
||||||
cellContent = '';
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<TableCell
|
|
||||||
key={column.accessorKey || column.id}
|
|
||||||
width={column.size ? `${column.size}px` : undefined}
|
|
||||||
sticky={column.id === 'actions'}
|
|
||||||
>
|
|
||||||
{cellContent}
|
|
||||||
</TableCell>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</TableRow>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
<div className="flex justify-end border-t border-border bg-muted/40">
|
|
||||||
<DataTablePagination
|
|
||||||
pageIndex={pagination.pageNum}
|
|
||||||
pageSize={pagination.pageSize}
|
|
||||||
pageCount={Math.ceil(pagination.totalElements / pagination.pageSize)}
|
|
||||||
onPageChange={handlePageChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user