2025-08-08 10:23:23 +00:00
|
|
|
import { CODE, IMAGES_TYPE_MAP } from '@/constants/images.constants';
|
2025-08-12 01:44:46 +00:00
|
|
|
import { delImagesAPI, getImagesList } from '@/services/images';
|
2025-08-06 09:43:46 +00:00
|
|
|
import {
|
|
|
|
|
DeleteOutlined,
|
|
|
|
|
EyeOutlined,
|
|
|
|
|
SettingOutlined,
|
2025-08-05 01:57:49 +00:00
|
|
|
} from '@ant-design/icons';
|
2025-08-06 09:43:46 +00:00
|
|
|
import {
|
|
|
|
|
Button,
|
|
|
|
|
Checkbox,
|
|
|
|
|
Input,
|
|
|
|
|
message,
|
|
|
|
|
Modal,
|
|
|
|
|
Popconfirm,
|
|
|
|
|
Popover,
|
|
|
|
|
Space,
|
|
|
|
|
Table,
|
2025-08-08 10:23:23 +00:00
|
|
|
Tag,
|
2025-08-12 01:44:46 +00:00
|
|
|
Tooltip,
|
2025-08-06 09:43:46 +00:00
|
|
|
} from 'antd';
|
2025-08-12 01:44:46 +00:00
|
|
|
import dayjs from 'dayjs';
|
|
|
|
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
2025-08-07 03:28:17 +00:00
|
|
|
import { ImportModal, ModalDetailShow } from './components/modalShow/modalShow';
|
2025-08-12 01:44:46 +00:00
|
|
|
import useTableParams from './hook/hook';
|
2025-08-05 01:57:49 +00:00
|
|
|
import './index.less';
|
|
|
|
|
|
2025-08-06 09:43:46 +00:00
|
|
|
import { ReactComponent as RefreshIcon } from '@/assets/icons/refresh.svg';
|
2025-08-05 01:57:49 +00:00
|
|
|
|
2025-08-08 10:23:23 +00:00
|
|
|
// 列配置定义
|
|
|
|
|
type ColumnConfig = {
|
|
|
|
|
key: string;
|
|
|
|
|
title: string;
|
|
|
|
|
dataIndex?: string;
|
|
|
|
|
width: number;
|
|
|
|
|
render?: (text: any, record: any, index: number) => React.ReactNode;
|
|
|
|
|
fixed?: 'left' | 'right';
|
|
|
|
|
defaultVisible: boolean; // 默认是否显示
|
|
|
|
|
alwaysVisible?: boolean; // 始终显示的列
|
2025-08-12 01:44:46 +00:00
|
|
|
filters?: { text: string; value: string }[];
|
|
|
|
|
filterMultiple?: boolean; // 是否多选过滤
|
|
|
|
|
defaultFilteredValue?: string[]; // 默认过滤值
|
|
|
|
|
onFilter?: (value: string, record: any) => boolean;
|
2025-08-08 10:23:23 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type TableColumn = {
|
|
|
|
|
title: string;
|
|
|
|
|
dataIndex?: string;
|
|
|
|
|
key: string;
|
|
|
|
|
width: number;
|
|
|
|
|
render?: any;
|
|
|
|
|
fixed?: 'left' | 'right';
|
|
|
|
|
hidden?: boolean;
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-12 01:44:46 +00:00
|
|
|
// 在组件顶部添加防抖函数
|
|
|
|
|
const debounce = (func: Function, delay: number) => {
|
|
|
|
|
let timer: NodeJS.Timeout;
|
|
|
|
|
return (...args: any[]) => {
|
|
|
|
|
clearTimeout(timer);
|
|
|
|
|
timer = setTimeout(() => func(...args), delay);
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-05 01:57:49 +00:00
|
|
|
const ImageList: React.FC = () => {
|
2025-08-06 09:43:46 +00:00
|
|
|
const [images, setImages] = useState<IMAGES.ImageItem[]>([]);
|
2025-08-05 01:57:49 +00:00
|
|
|
const [loading, setLoading] = useState(false);
|
2025-08-06 09:43:46 +00:00
|
|
|
const [selectedImage, setSelectedImage] = useState<IMAGES.ImageItem | null>(
|
|
|
|
|
null,
|
|
|
|
|
);
|
2025-08-05 01:57:49 +00:00
|
|
|
const [detailVisible, setDetailVisible] = useState(false);
|
2025-08-06 09:43:46 +00:00
|
|
|
const [importModalVisible, setImportModalVisible] = useState(false);
|
2025-08-12 01:44:46 +00:00
|
|
|
const [searchText, setSearchText] = useState<string>(''); // 添加本地搜索状态
|
|
|
|
|
const searchInputRef = useRef<string>(''); // 保存已发送请求的搜索文本
|
|
|
|
|
|
|
|
|
|
const { tableParams, getApiParams, updateParams, handleTableChange } =
|
|
|
|
|
useTableParams({
|
|
|
|
|
pagination: {
|
|
|
|
|
current: 1,
|
|
|
|
|
pageSize: 10,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 在组件顶部添加一个 ref 来保存最新的 tableParams
|
|
|
|
|
const tableParamsRef = useRef(tableParams);
|
|
|
|
|
tableParamsRef.current = tableParams; // 每次渲染时更新 ref 的值
|
2025-08-06 09:43:46 +00:00
|
|
|
|
|
|
|
|
const [columnSettingsVisible, setColumnSettingsVisible] = useState(false);
|
2025-08-05 01:57:49 +00:00
|
|
|
|
2025-08-08 10:23:23 +00:00
|
|
|
// 表格参数变化 获取镜像列表
|
2025-08-05 01:57:49 +00:00
|
|
|
useEffect(() => {
|
|
|
|
|
loadImages();
|
2025-08-06 09:43:46 +00:00
|
|
|
}, [
|
|
|
|
|
tableParams.pagination?.current,
|
|
|
|
|
tableParams.pagination?.pageSize,
|
|
|
|
|
tableParams?.sortOrder,
|
|
|
|
|
tableParams?.sortField,
|
|
|
|
|
JSON.stringify(tableParams.filters),
|
|
|
|
|
]);
|
2025-08-07 03:28:17 +00:00
|
|
|
|
2025-08-08 10:23:23 +00:00
|
|
|
// 定义所有列的配置
|
|
|
|
|
const columnConfigs: ColumnConfig[] = [
|
|
|
|
|
{
|
|
|
|
|
key: 'index',
|
|
|
|
|
title: '序号',
|
2025-08-12 01:44:46 +00:00
|
|
|
width: 60,
|
2025-08-08 10:23:23 +00:00
|
|
|
render: (text: any, row: any, index: number) =>
|
|
|
|
|
(tableParams.pagination?.current - 1) *
|
|
|
|
|
tableParams.pagination?.pageSize +
|
|
|
|
|
index +
|
|
|
|
|
1,
|
|
|
|
|
defaultVisible: true,
|
|
|
|
|
alwaysVisible: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'image_name',
|
|
|
|
|
title: '镜像名称',
|
|
|
|
|
dataIndex: 'image_name',
|
2025-08-12 01:44:46 +00:00
|
|
|
width: 200,
|
2025-08-08 10:23:23 +00:00
|
|
|
defaultVisible: true,
|
|
|
|
|
alwaysVisible: true,
|
2025-08-12 01:44:46 +00:00
|
|
|
render: (text: string) => (
|
|
|
|
|
<Tooltip title={text}>
|
|
|
|
|
<span
|
|
|
|
|
style={{
|
|
|
|
|
overflow: 'hidden',
|
|
|
|
|
textOverflow: 'ellipsis',
|
|
|
|
|
whiteSpace: 'nowrap',
|
|
|
|
|
display: 'inline-block',
|
|
|
|
|
width: '100%',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{text || '--'}
|
|
|
|
|
</span>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
),
|
2025-08-08 10:23:23 +00:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'image_type',
|
|
|
|
|
title: '桌面类型',
|
|
|
|
|
dataIndex: 'image_type',
|
2025-08-12 01:44:46 +00:00
|
|
|
width: 100,
|
2025-08-08 10:23:23 +00:00
|
|
|
render: (text: number) => {
|
|
|
|
|
const key = text as keyof typeof IMAGES_TYPE_MAP;
|
|
|
|
|
return <Tooltip>{IMAGES_TYPE_MAP[key] || '--'}</Tooltip>;
|
|
|
|
|
},
|
|
|
|
|
defaultVisible: true,
|
2025-08-12 01:44:46 +00:00
|
|
|
filters: [
|
|
|
|
|
{ text: '全部', value: '全部' },
|
|
|
|
|
...Object.entries(IMAGES_TYPE_MAP).map(([key, value]) => ({
|
|
|
|
|
text: value,
|
|
|
|
|
value: key, // 保持 key 为字符串
|
|
|
|
|
})),
|
|
|
|
|
],
|
|
|
|
|
filterMultiple: false,
|
|
|
|
|
defaultFilteredValue: ['全部'],
|
|
|
|
|
// onFilter: (value, record) => {
|
|
|
|
|
// // 当选择"全部"时显示所有记录
|
|
|
|
|
// if (value === '全部') {
|
|
|
|
|
// return true;
|
|
|
|
|
// }
|
|
|
|
|
// // 比较时将字符串 value 转换为数字
|
|
|
|
|
// return record.image_type === Number(value);
|
|
|
|
|
// },
|
2025-08-08 10:23:23 +00:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'storage_path',
|
|
|
|
|
title: '模板存放路径',
|
|
|
|
|
dataIndex: 'storage_path',
|
|
|
|
|
width: 140,
|
|
|
|
|
defaultVisible: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'bt_path',
|
|
|
|
|
title: 'BT路径',
|
|
|
|
|
dataIndex: 'bt_path',
|
|
|
|
|
width: 140,
|
|
|
|
|
defaultVisible: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'image_version',
|
|
|
|
|
title: '镜像版本',
|
|
|
|
|
dataIndex: 'image_version',
|
|
|
|
|
width: 100,
|
|
|
|
|
defaultVisible: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'os_version',
|
|
|
|
|
title: '操作系统',
|
|
|
|
|
dataIndex: 'os_version',
|
|
|
|
|
width: 100,
|
|
|
|
|
defaultVisible: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'image_status',
|
|
|
|
|
title: '镜像状态',
|
|
|
|
|
dataIndex: 'image_status',
|
|
|
|
|
width: 80,
|
|
|
|
|
render: (text: number) => <Tooltip>{getStatusTag(text)}</Tooltip>,
|
|
|
|
|
defaultVisible: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'create_time',
|
|
|
|
|
title: '创建时间',
|
|
|
|
|
dataIndex: 'create_time',
|
|
|
|
|
width: 180,
|
|
|
|
|
render: (text: string) => (
|
|
|
|
|
<Tooltip>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</Tooltip>
|
|
|
|
|
),
|
|
|
|
|
defaultVisible: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'action',
|
|
|
|
|
title: '操作',
|
|
|
|
|
width: 100,
|
|
|
|
|
fixed: 'right' as 'right',
|
|
|
|
|
render: (_: any, record: IMAGES.ImageItem) => (
|
|
|
|
|
<Space size="small">
|
|
|
|
|
<Button
|
|
|
|
|
type="text"
|
|
|
|
|
icon={<EyeOutlined />}
|
|
|
|
|
onClick={() => handleViewDetail(record)}
|
|
|
|
|
title="查看详情"
|
|
|
|
|
/>
|
|
|
|
|
<Popconfirm
|
|
|
|
|
title="确定要删除这个镜像吗?"
|
|
|
|
|
description="删除后无法恢复,请谨慎操作。"
|
|
|
|
|
onConfirm={() => handleDelete(record)}
|
|
|
|
|
okText="确定"
|
|
|
|
|
cancelText="取消"
|
|
|
|
|
>
|
|
|
|
|
<Button type="text" icon={<DeleteOutlined />} title="删除" danger />
|
|
|
|
|
</Popconfirm>
|
|
|
|
|
</Space>
|
|
|
|
|
),
|
|
|
|
|
defaultVisible: true,
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// 初始化 visibleColumns 状态
|
|
|
|
|
const initialVisibleColumns = columnConfigs.reduce<Record<string, boolean>>(
|
|
|
|
|
(acc, column) => {
|
|
|
|
|
if (!column.alwaysVisible) {
|
|
|
|
|
acc[column.key] = column.defaultVisible;
|
|
|
|
|
}
|
|
|
|
|
return acc;
|
|
|
|
|
},
|
|
|
|
|
{},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const [visibleColumns, setVisibleColumns] = useState<Record<string, boolean>>(
|
|
|
|
|
initialVisibleColumns,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 重置列设置
|
|
|
|
|
const resetColumns = () => {
|
|
|
|
|
setVisibleColumns(initialVisibleColumns);
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-06 09:43:46 +00:00
|
|
|
const loadImages = async () => {
|
|
|
|
|
setLoading(true);
|
|
|
|
|
try {
|
2025-08-12 01:44:46 +00:00
|
|
|
// 将搜索文本合并到API参数中
|
|
|
|
|
const apiParams = {
|
|
|
|
|
...getApiParams(),
|
|
|
|
|
};
|
|
|
|
|
if (searchInputRef.current) {
|
|
|
|
|
apiParams.image_name = searchInputRef.current;
|
|
|
|
|
}
|
|
|
|
|
const imagesRes = await getImagesList(apiParams);
|
2025-08-08 10:23:23 +00:00
|
|
|
if (imagesRes.code == CODE) {
|
2025-08-12 01:44:46 +00:00
|
|
|
setImages(imagesRes.data?.data || []);
|
2025-08-06 09:43:46 +00:00
|
|
|
setLoading(false);
|
2025-08-12 01:44:46 +00:00
|
|
|
console.log('imagesRes', imagesRes);
|
|
|
|
|
|
|
|
|
|
// 正确处理后端返回的分页信息
|
|
|
|
|
updateParams({
|
2025-08-06 09:43:46 +00:00
|
|
|
pagination: {
|
2025-08-12 01:44:46 +00:00
|
|
|
...tableParams.pagination,
|
|
|
|
|
current: imagesRes.data?.page_num || 1,
|
|
|
|
|
total: imagesRes.data?.total || 0,
|
|
|
|
|
pageSize: tableParams.pagination.pageSize|| 10,
|
2025-08-06 09:43:46 +00:00
|
|
|
},
|
2025-08-12 01:44:46 +00:00
|
|
|
});
|
2025-08-06 09:43:46 +00:00
|
|
|
} else {
|
|
|
|
|
message.error(imagesRes.message || '获取镜像列表失败');
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
message.error('获取镜像列表失败');
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
2025-08-08 10:23:23 +00:00
|
|
|
const getStatusTag = (status: number) => {
|
|
|
|
|
const statusMap = {
|
|
|
|
|
1: { color: 'green', text: '成功' },
|
|
|
|
|
2: { color: 'red', text: '失败' },
|
|
|
|
|
};
|
|
|
|
|
const config = statusMap[status as keyof typeof statusMap];
|
|
|
|
|
return <Tag color={config.color}>{config.text}</Tag>;
|
|
|
|
|
};
|
2025-08-05 01:57:49 +00:00
|
|
|
|
2025-08-06 09:43:46 +00:00
|
|
|
const handleViewDetail = (record: IMAGES.ImageItem) => {
|
|
|
|
|
setSelectedImage(record);
|
|
|
|
|
setDetailVisible(true);
|
2025-08-05 01:57:49 +00:00
|
|
|
};
|
|
|
|
|
|
2025-08-06 09:43:46 +00:00
|
|
|
const handleDelete = (record: IMAGES.ImageItem) => {
|
2025-08-05 01:57:49 +00:00
|
|
|
Modal.confirm({
|
|
|
|
|
title: '确认删除',
|
2025-08-06 09:43:46 +00:00
|
|
|
content: `确定要删除镜像 "${record.image_name}" 吗?`,
|
2025-08-05 01:57:49 +00:00
|
|
|
onOk: () => {
|
2025-08-12 01:44:46 +00:00
|
|
|
delImagesAPI(record.id).then((res) => {
|
|
|
|
|
if (res.code == CODE) {
|
|
|
|
|
message.success('删除成功');
|
|
|
|
|
loadImages();
|
|
|
|
|
} else {
|
|
|
|
|
message.error(res.message || '删除失败');
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-08-06 09:43:46 +00:00
|
|
|
},
|
2025-08-05 01:57:49 +00:00
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-06 09:43:46 +00:00
|
|
|
// 列设置相关函数
|
|
|
|
|
const handleColumnChange = (columnKey: string, checked: boolean) => {
|
|
|
|
|
setVisibleColumns((prev) => ({
|
|
|
|
|
...prev,
|
|
|
|
|
[columnKey]: checked,
|
|
|
|
|
}));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 列设置内容
|
|
|
|
|
const columnSettingsContent = (
|
|
|
|
|
<div style={{ padding: '8px 0' }}>
|
2025-08-08 10:23:23 +00:00
|
|
|
{columnConfigs
|
|
|
|
|
.filter((config) => !config.alwaysVisible) // 只显示可控制的列
|
|
|
|
|
.map((config) => (
|
|
|
|
|
<div key={config.key} style={{ padding: '4px 12px' }}>
|
|
|
|
|
<Checkbox
|
|
|
|
|
checked={visibleColumns[config.key]}
|
|
|
|
|
onChange={(e) => handleColumnChange(config.key, e.target.checked)}
|
|
|
|
|
>
|
|
|
|
|
{config.title}
|
|
|
|
|
</Checkbox>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
2025-08-06 09:43:46 +00:00
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
padding: '8px 12px',
|
|
|
|
|
borderTop: '1px solid #f0f0f0',
|
|
|
|
|
marginTop: 8,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<Button type="link" onClick={resetColumns} style={{ padding: 0 }}>
|
|
|
|
|
重置
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 根据visibleColumns过滤显示的列
|
2025-08-08 10:23:23 +00:00
|
|
|
const filteredColumns = columnConfigs
|
|
|
|
|
.map((config) => {
|
|
|
|
|
// 对于始终显示的列
|
|
|
|
|
if (config.alwaysVisible) {
|
|
|
|
|
return {
|
2025-08-12 01:44:46 +00:00
|
|
|
...config,
|
2025-08-08 10:23:23 +00:00
|
|
|
hidden: undefined,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 对于可控制显示/隐藏的列
|
|
|
|
|
return {
|
2025-08-12 01:44:46 +00:00
|
|
|
...config,
|
2025-08-08 10:23:23 +00:00
|
|
|
...(visibleColumns[config.key] ? {} : { hidden: true }),
|
|
|
|
|
};
|
|
|
|
|
})
|
|
|
|
|
.filter((column) => !column.hidden) as TableColumn[];
|
2025-08-06 09:43:46 +00:00
|
|
|
|
|
|
|
|
const handleRefresh = () => {
|
|
|
|
|
loadImages();
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-07 03:28:17 +00:00
|
|
|
// 导入镜像成功后的回调
|
2025-08-06 09:43:46 +00:00
|
|
|
const handleImportSuccess = () => {
|
|
|
|
|
setTimeout(() => {
|
2025-08-07 03:28:17 +00:00
|
|
|
loadImages();
|
2025-08-06 09:43:46 +00:00
|
|
|
}, 5000);
|
|
|
|
|
};
|
2025-08-05 01:57:49 +00:00
|
|
|
|
2025-08-07 03:28:17 +00:00
|
|
|
// 自定义分页配置
|
|
|
|
|
const paginationConfig = {
|
|
|
|
|
...tableParams.pagination,
|
|
|
|
|
showTotal: (total: number) => `共 ${total} 条记录`,
|
|
|
|
|
showSizeChanger: true,
|
|
|
|
|
showQuickJumper: true,
|
|
|
|
|
pageSizeOptions: ['10', '20', '50', '100'],
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-12 01:44:46 +00:00
|
|
|
const handleSearch = useCallback(
|
|
|
|
|
(searchValue: string) => {
|
|
|
|
|
console.log('handleSearch', searchValue, tableParams);
|
|
|
|
|
|
|
|
|
|
// 使用 ref 中的最新值
|
|
|
|
|
const currentTableParams = tableParamsRef.current;
|
|
|
|
|
|
|
|
|
|
// 只有当搜索值变化时才更新参数和触发请求
|
|
|
|
|
if (searchInputRef.current !== searchValue) {
|
|
|
|
|
searchInputRef.current = searchValue;
|
|
|
|
|
updateParams({
|
|
|
|
|
pagination: {
|
|
|
|
|
current: 1,
|
|
|
|
|
pageSize: currentTableParams.pagination?.pageSize || 10,
|
|
|
|
|
},
|
|
|
|
|
filters: {
|
|
|
|
|
...currentTableParams.filters,
|
|
|
|
|
image_name: searchValue ? searchValue : undefined,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[updateParams],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const debouncedSearch = useRef(debounce(handleSearch, 500)).current;
|
|
|
|
|
|
2025-08-05 01:57:49 +00:00
|
|
|
return (
|
|
|
|
|
<div className="image-list">
|
2025-08-07 03:28:17 +00:00
|
|
|
<div className="search-box">
|
|
|
|
|
<Button onClick={() => setImportModalVisible(true)}>导入</Button>
|
|
|
|
|
<div className="search-input">
|
2025-08-06 09:43:46 +00:00
|
|
|
<Input.Search
|
|
|
|
|
placeholder="镜像名称"
|
|
|
|
|
value={searchText}
|
2025-08-12 01:44:46 +00:00
|
|
|
onChange={(e) => {
|
|
|
|
|
const value = e.target.value;
|
|
|
|
|
setSearchText(value);
|
|
|
|
|
// 只有当输入为空时立即触发搜索,否则使用防抖
|
|
|
|
|
if (value === '') {
|
|
|
|
|
handleSearch('');
|
|
|
|
|
} else {
|
|
|
|
|
debouncedSearch(value);
|
|
|
|
|
}
|
2025-08-06 09:43:46 +00:00
|
|
|
}}
|
2025-08-12 01:44:46 +00:00
|
|
|
style={{ width: 300 }}
|
|
|
|
|
onSearch={handleSearch}
|
2025-08-06 09:43:46 +00:00
|
|
|
/>
|
|
|
|
|
<Button
|
|
|
|
|
onClick={handleRefresh}
|
|
|
|
|
loading={loading}
|
|
|
|
|
icon={<RefreshIcon style={{ width: 13, height: 13 }} />}
|
|
|
|
|
></Button>
|
|
|
|
|
<Popover
|
|
|
|
|
content={columnSettingsContent}
|
|
|
|
|
title="列设置"
|
|
|
|
|
trigger="click"
|
|
|
|
|
open={columnSettingsVisible}
|
|
|
|
|
onOpenChange={setColumnSettingsVisible}
|
|
|
|
|
placement="bottomRight"
|
|
|
|
|
>
|
|
|
|
|
<Button icon={<SettingOutlined />}></Button>
|
|
|
|
|
</Popover>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-08-07 03:28:17 +00:00
|
|
|
<div className="images-list-container">
|
|
|
|
|
<div className="images-list-table">
|
|
|
|
|
<Table
|
|
|
|
|
columns={filteredColumns}
|
|
|
|
|
dataSource={images}
|
2025-08-12 01:44:46 +00:00
|
|
|
rowKey="id"
|
2025-08-07 03:28:17 +00:00
|
|
|
loading={loading}
|
|
|
|
|
pagination={paginationConfig}
|
|
|
|
|
onChange={handleTableChange}
|
2025-08-12 01:44:46 +00:00
|
|
|
scroll={{
|
|
|
|
|
y: 'max-content', // 关键:允许内容决定高度
|
|
|
|
|
}}
|
|
|
|
|
style={{
|
|
|
|
|
height: '100%',
|
|
|
|
|
display: 'flex',
|
|
|
|
|
flexDirection: 'column',
|
|
|
|
|
}}
|
2025-08-07 03:28:17 +00:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-08-06 09:43:46 +00:00
|
|
|
|
|
|
|
|
{detailVisible ? (
|
|
|
|
|
<ModalDetailShow
|
|
|
|
|
title="镜像详情"
|
|
|
|
|
detailVisible={detailVisible}
|
|
|
|
|
setDetailVisible={setDetailVisible}
|
|
|
|
|
selectedImage={selectedImage}
|
|
|
|
|
/>
|
|
|
|
|
) : null}
|
|
|
|
|
{/* 导入弹窗 */}
|
|
|
|
|
<ImportModal
|
|
|
|
|
visible={importModalVisible}
|
|
|
|
|
onCancel={() => setImportModalVisible(false)}
|
|
|
|
|
onImportSuccess={handleImportSuccess}
|
|
|
|
|
/>
|
2025-08-05 01:57:49 +00:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-06 09:43:46 +00:00
|
|
|
export default ImageList;
|