vdi/web-fe/src/pages/images/index.tsx

416 lines
11 KiB
TypeScript
Raw Normal View History

2025-08-06 09:43:46 +00:00
import { ERROR_CODE, IMAGES_TYPE_MAP } from '@/constants/images.constants';
import { getImagesList } from '@/services/images';
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 type { TableProps } from 'antd';
import {
Button,
Checkbox,
Input,
message,
Modal,
Popconfirm,
Popover,
Space,
Table,
Tooltip,
} from 'antd';
import React, { useEffect, useState } from 'react';
import { ModalDetailShow, ImportModal } from './components/modalShow/modalShow';
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
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,
);
const [searchText, setSearchText] = useState<string>('');
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);
const [tableParams, setTableParams] = useState<IMAGES.TableParams>({
pagination: {
current: 1,
pageSize: 10,
},
});
// 列显示控制状态
const [visibleColumns, setVisibleColumns] = useState<Record<string, boolean>>(
{
image_type: true,
storage_path: true,
bt_path: true,
create_time: true,
action: true,
},
);
const [columnSettingsVisible, setColumnSettingsVisible] = useState(false);
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),
JSON.stringify(tableParams.keywords),
]);
const getRandomuserParams = (params: IMAGES.TableParams) => {
const {
pagination,
filters,
sortField,
sortOrder,
keywords,
...restParams
} = params;
const result: Record<string, any> = {};
2025-08-05 01:57:49 +00:00
2025-08-06 09:43:46 +00:00
result.page_size = pagination?.pageSize;
result.page_num = pagination?.current;
if (keywords) {
result.keywords = keywords;
}
if (filters) {
Object.entries(filters).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
result[key] = value;
2025-08-05 01:57:49 +00:00
}
2025-08-06 09:43:46 +00:00
});
}
2025-08-05 01:57:49 +00:00
2025-08-06 09:43:46 +00:00
if (sortField) {
result.orderby = sortField;
result.order = sortOrder === 'ascend' ? 'asc' : 'desc';
}
2025-08-05 01:57:49 +00:00
2025-08-06 09:43:46 +00:00
// 处理其他参数
Object.entries(restParams).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
result[key] = value;
}
});
return result;
2025-08-05 01:57:49 +00:00
};
2025-08-06 09:43:46 +00:00
const params = getRandomuserParams(tableParams);
2025-08-05 01:57:49 +00:00
2025-08-06 09:43:46 +00:00
const loadImages = async () => {
setLoading(true);
try {
const imagesRes = await getImagesList(params);
if (imagesRes.error_code === ERROR_CODE) {
setImages(imagesRes.data.data || []);
setLoading(false);
setTableParams((prev) => ({
...prev,
pagination: {
...prev.pagination,
total: imagesRes.data.paging.total || 0,
},
}));
} else {
message.error(imagesRes.message || '获取镜像列表失败');
setLoading(false);
}
} catch (err) {
message.error('获取镜像列表失败');
setLoading(false);
}
};
2025-08-05 01:57:49 +00:00
2025-08-06 09:43:46 +00:00
// const getStatusTag = (status: string) => {
// const statusMap = {
// active: { color: 'green', text: '可用' },
// inactive: { color: 'red', text: '不可用' },
// building: { color: 'orange', 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-06 09:43:46 +00:00
// TODO: 调用删除接口
setImages(images.filter((img) => img.image_id !== record.image_id));
2025-08-05 01:57:49 +00:00
message.success('删除成功');
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 resetColumns = () => {
setVisibleColumns({
image_type: true,
storage_path: true,
bt_path: true,
create_time: true,
action: true,
});
};
// 列设置内容
const columnSettingsContent = (
<div style={{ padding: '8px 0' }}>
<div style={{ padding: '4px 12px' }}>
<Checkbox
checked={visibleColumns['image_type']}
onChange={(e) => handleColumnChange('image_type', e.target.checked)}
>
</Checkbox>
</div>
<div style={{ padding: '4px 12px' }}>
<Checkbox
checked={visibleColumns['storage_path']}
onChange={(e) => handleColumnChange('storage_path', e.target.checked)}
>
</Checkbox>
</div>
<div style={{ padding: '4px 12px' }}>
<Checkbox
checked={visibleColumns['bt_path']}
onChange={(e) => handleColumnChange('bt_path', e.target.checked)}
>
BT
</Checkbox>
</div>
<div style={{ padding: '4px 12px' }}>
<Checkbox
checked={visibleColumns['create_time']}
onChange={(e) => handleColumnChange('create_time', e.target.checked)}
>
</Checkbox>
</div>
<div style={{ padding: '4px 12px' }}>
<Checkbox
checked={visibleColumns['action']}
onChange={(e) => handleColumnChange('action', e.target.checked)}
>
</Checkbox>
</div>
<div
style={{
padding: '8px 12px',
borderTop: '1px solid #f0f0f0',
marginTop: 8,
}}
>
<Button type="link" onClick={resetColumns} style={{ padding: 0 }}>
</Button>
</div>
</div>
);
// 根据visibleColumns过滤显示的列
const filteredColumns = [
2025-08-05 01:57:49 +00:00
{
title: '镜像名称',
2025-08-06 09:43:46 +00:00
dataIndex: 'image_name',
key: 'image_name',
2025-08-05 01:57:49 +00:00
width: 200,
},
{
2025-08-06 09:43:46 +00:00
title: '桌面类型',
dataIndex: 'image_type',
key: 'image_type',
width: 200,
render: (text: number) => {
const key = text as keyof typeof IMAGES_TYPE_MAP;
return <Tooltip>{IMAGES_TYPE_MAP[key] || '--'}</Tooltip>;
},
...(visibleColumns['image_type'] ? {} : { hidden: true }),
2025-08-05 01:57:49 +00:00
},
{
2025-08-06 09:43:46 +00:00
title: '模板存放路径',
dataIndex: 'storage_path',
key: 'storage_path',
width: 200,
...(visibleColumns['storage_path'] ? {} : { hidden: true }),
2025-08-05 01:57:49 +00:00
},
{
2025-08-06 09:43:46 +00:00
title: 'BT路径',
dataIndex: 'bt_path',
key: 'bt_path',
width: 200,
...(visibleColumns['bt_path'] ? {} : { hidden: true }),
2025-08-05 01:57:49 +00:00
},
{
title: '创建时间',
2025-08-06 09:43:46 +00:00
dataIndex: 'create_time',
key: 'create_time',
2025-08-05 01:57:49 +00:00
width: 180,
2025-08-06 09:43:46 +00:00
...(visibleColumns['create_time'] ? {} : { hidden: true }),
2025-08-05 01:57:49 +00:00
},
{
title: '操作',
key: 'action',
width: 200,
2025-08-06 09:43:46 +00:00
render: (_: any, record: IMAGES.ImageItem) => (
2025-08-05 01:57:49 +00:00
<Space size="small">
<Button
type="text"
icon={<EyeOutlined />}
onClick={() => handleViewDetail(record)}
title="查看详情"
/>
<Popconfirm
title="确定要删除这个镜像吗?"
description="删除后无法恢复,请谨慎操作。"
onConfirm={() => handleDelete(record)}
okText="确定"
cancelText="取消"
>
2025-08-06 09:43:46 +00:00
<Button type="text" icon={<DeleteOutlined />} title="删除" danger />
2025-08-05 01:57:49 +00:00
</Popconfirm>
</Space>
),
2025-08-06 09:43:46 +00:00
...(visibleColumns['action'] ? {} : { hidden: true }),
2025-08-05 01:57:49 +00:00
},
2025-08-06 09:43:46 +00:00
].filter((column) => !column.hidden);
const handleTableChange: TableProps<IMAGES.ImageItem>['onChange'] = (
pagination,
filters,
sorter,
) => {
setTableParams({
pagination: {
current: pagination.current || 1,
pageSize: pagination.pageSize || 10,
},
filters,
sortOrder: Array.isArray(sorter) ? undefined : sorter.order,
sortField: Array.isArray(sorter)
? undefined
: (sorter.field as string | number | undefined),
});
if (pagination.pageSize !== tableParams.pagination?.pageSize) {
setImages([]);
}
};
const handleRefresh = () => {
loadImages();
};
// 导入成功后的回调
const handleImportSuccess = () => {
// 可以在这里添加导入成功后的处理逻辑
// 例如:刷新列表或显示提示信息
setTimeout(() => {
loadImages(); // 一段时间后刷新列表查看是否有新导入的镜像
}, 5000);
};
2025-08-05 01:57:49 +00:00
return (
<div className="image-list">
2025-08-06 09:43:46 +00:00
<div
style={{
marginBottom: 16,
display: 'flex',
justifyContent: 'space-between',
}}
2025-08-05 01:57:49 +00:00
>
2025-08-06 09:43:46 +00:00
<div>
<Button onClick={() => setImportModalVisible(true)}></Button>
</div>
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
<Input.Search
placeholder="镜像名称"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
style={{ width: 300 }}
onSearch={(value) => {
setTableParams((prev) => ({
...prev,
pagination: {
...prev.pagination,
current: 1,
},
keywords: value,
}));
}}
/>
<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>
<Table
columns={filteredColumns}
dataSource={images}
rowKey="image_id"
loading={loading}
pagination={tableParams.pagination}
onChange={handleTableChange}
// pagination={{
// total: images.length,
// pageSize: 10,
// showSizeChanger: true,
// showQuickJumper: true,
// showTotal: (total) => `共 ${total} 条记录`,
// }}
/>
{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;