+ {columnConfigs
+ .filter((config) => !config.alwaysVisible) // 只显示可控制的列
+ .map((config) => (
+
{
);
// 根据visibleColumns过滤显示的列
- const filteredColumns = [
- {
- title: '镜像名称',
- dataIndex: 'image_name',
- key: 'image_name',
- width: 200,
- },
- {
- title: '桌面类型',
- dataIndex: 'image_type',
- key: 'image_type',
- width: 200,
- render: (text: number) => {
- const key = text as keyof typeof IMAGES_TYPE_MAP;
- return
{IMAGES_TYPE_MAP[key] || '--'};
- },
- ...(visibleColumns['image_type'] ? {} : { hidden: true }),
- },
- {
- title: '模板存放路径',
- dataIndex: 'storage_path',
- key: 'storage_path',
- width: 200,
- ...(visibleColumns['storage_path'] ? {} : { hidden: true }),
- },
- {
- title: 'BT路径',
- dataIndex: 'bt_path',
- key: 'bt_path',
- width: 200,
- ...(visibleColumns['bt_path'] ? {} : { hidden: true }),
- },
- {
- title: '创建时间',
- dataIndex: 'create_time',
- key: 'create_time',
- width: 180,
- render:(text:string)=>
{dayjs(text).format('YYYY-MM-DD HH:mm:ss')},
- ...(visibleColumns['create_time'] ? {} : { hidden: true }),
- },
- {
- title: '操作',
- key: 'action',
- width: 200,
- render: (_: any, record: IMAGES.ImageItem) => (
-
- }
- onClick={() => handleViewDetail(record)}
- title="查看详情"
- />
- handleDelete(record)}
- okText="确定"
- cancelText="取消"
- >
- } title="删除" danger />
-
-
- ),
- ...(visibleColumns['action'] ? {} : { hidden: true }),
- },
- ].filter((column) => !column.hidden);
+ const filteredColumns = columnConfigs
+ .map((config) => {
+ // 对于始终显示的列
+ if (config.alwaysVisible) {
+ return {
+ title: config.title,
+ dataIndex: config.dataIndex,
+ key: config.key,
+ width: config.width,
+ render: config.render,
+ fixed: config.fixed,
+ hidden: undefined,
+ };
+ }
+
+ // 对于可控制显示/隐藏的列
+ return {
+ title: config.title,
+ dataIndex: config.dataIndex,
+ key: config.key,
+ width: config.width,
+ render: config.render,
+ fixed: config.fixed,
+ ...(visibleColumns[config.key] ? {} : { hidden: true }),
+ };
+ })
+ .filter((column) => !column.hidden) as TableColumn[];
// 处理表格分页、过滤和排序变化
const handleTableChange: TableProps
['onChange'] = (
@@ -368,7 +430,7 @@ const ImageList: React.FC = () => {
...prev.pagination,
current: 1,
},
- keywords: value,
+ image_name: value,
}));
}}
/>
diff --git a/web-fe/src/services/images.ts b/web-fe/src/services/images.ts
index f3dfbed..39d2f54 100644
--- a/web-fe/src/services/images.ts
+++ b/web-fe/src/services/images.ts
@@ -1,6 +1,6 @@
import { request } from '@umijs/max';
-const BASE_URL = '/api/v1/images';
+const BASE_URL = '/api/files';
// 根据终端序列号查询镜像列表
export async function getImagesList(params:any) {
@@ -15,7 +15,10 @@ export async function getImagesList(params:any) {
}
export async function uploadChunkAPI(formData: FormData, signal?: AbortSignal) {
- return request(`${BASE_URL}/file/chunk/upload`, {
+ console.log('formData', formData);
+
+ // return request(`/api/v1/images/file/chunk/upload`, {
+ return request(`${BASE_URL}/upload-chunk`, {
method: 'POST',
data: formData,
signal, // 添加 signal 支持
diff --git a/web-fe/src/types/images.d.ts b/web-fe/src/types/images.d.ts
index dbe77d3..c988857 100644
--- a/web-fe/src/types/images.d.ts
+++ b/web-fe/src/types/images.d.ts
@@ -6,6 +6,9 @@ declare namespace IMAGES {
image_type: number;
storage_path: string;
bt_path: string;
+ image_version: string;
+ os_version: string;
+ image_status: number;
create_time: string;
description?: string;
desktopType?: string;
@@ -22,25 +25,40 @@ declare namespace IMAGES {
filters?: Record;
sortOrder?: 'ascend' | 'descend' | null;
sortField?: string | number | null;
- keywords?: string; // 添加关键词字段
+ image_name?: string; // 添加关键词字段
+ image_status?: string;
}
- // interface DeviceParams{
- // device_id: string;
- // }
-
interface Images_ListInfo {
- error_code: string;
+ code: string;
message: string;
data: {
paging: {
total: number;
- total_num?: number;
page_num: number;
page_size: number;
};
data: ImageItem[];
};
}
+ /**上传文件分片 */
+ interface ImportModalProps {
+ visible: boolean;
+ onCancel: () => void;
+ onImportSuccess?: () => void;
+ }
+ type ImageDetailFieldWithoutRender = {
+ label: string;
+ key: keyof ImageType; // 假设 ImageType 是 selectedImage 的类型
+ };
+ type ImageDetailFieldWithRender = {
+ label: string;
+ key: keyof ImageType;
+ render: (value: any) => React.ReactNode;
+ };
+
+ export type ImageDetailField =
+ | ImageDetailFieldWithoutRender
+ | ImageDetailFieldWithRender;
}
diff --git a/web-fe/src/utils/images.ts b/web-fe/src/utils/images.ts
new file mode 100644
index 0000000..f89e2cc
--- /dev/null
+++ b/web-fe/src/utils/images.ts
@@ -0,0 +1,106 @@
+import {
+ CHUNK_SIZE_CONFIG,
+ MD5_CHUNK_SIZE_CONFIG,
+ TIME_FORMAT_CONFIG
+} from '@/constants/images.constants';
+import SparkMD5 from 'spark-md5';
+
+/**
+ * 根据文件大小获取网络传输分片大小
+ * @param fileSize 文件大小(字节)
+ * @returns 分片大小(字节)
+ */
+export const getChunkSize = (fileSize: number): number => {
+ for (const config of CHUNK_SIZE_CONFIG) {
+ if (fileSize <= config.maxSize) {
+ return config.chunkSize;
+ }
+ }
+ return CHUNK_SIZE_CONFIG[CHUNK_SIZE_CONFIG.length - 1].chunkSize; // 默认返回最大配置
+};
+
+/**
+ * 根据文件大小获取MD5计算内存分块大小
+ * @param fileSize 文件大小(字节)
+ * @returns MD5分块大小(字节)
+ */
+export const getMD5ChunkSize = (fileSize: number): number => {
+ for (const config of MD5_CHUNK_SIZE_CONFIG) {
+ if (fileSize <= config.maxSize) {
+ return config.chunkSize;
+ }
+ }
+ return MD5_CHUNK_SIZE_CONFIG[MD5_CHUNK_SIZE_CONFIG.length - 1].chunkSize; // 默认返回最大配置
+};
+
+/**
+ * 分块计算文件MD5
+ * @param file 文件对象
+ * @param fileSize 文件大小
+ * @param chunkSize 分块大小
+ * @returns Promise MD5值
+ */
+export const calculateMD5InChunks = (
+ file: Blob,
+ fileSize: number,
+ chunkSize: number = getMD5ChunkSize(fileSize)
+): Promise => {
+ return new Promise((resolve, reject) => {
+ const spark = new SparkMD5.ArrayBuffer();
+ const fileReader = new FileReader();
+ let currentChunk = 0;
+ const chunks = Math.ceil(file.size / chunkSize);
+
+ fileReader.onload = (e) => {
+ try {
+ spark.append(e.target?.result as ArrayBuffer);
+ currentChunk++;
+
+ if (currentChunk < chunks) {
+ loadNext();
+ } else {
+ const hash = spark.end();
+ spark.destroy(); // 释放内存
+ resolve(hash);
+ }
+ } catch (error) {
+ spark.destroy();
+ reject(error);
+ }
+ };
+
+ fileReader.onerror = () => {
+ spark.destroy();
+ reject(new Error('计算MD5失败'));
+ };
+
+ const loadNext = () => {
+ const start = currentChunk * chunkSize;
+ const end = Math.min(start + chunkSize, file.size);
+ fileReader.readAsArrayBuffer(file.slice(start, end));
+ };
+
+ loadNext();
+ });
+};
+
+/**
+ * 格式化时间显示
+ * @param seconds 秒数
+ * @returns 格式化后的时间字符串
+ */
+export const formatTime = (seconds: number): string => {
+ const { HOUR_LABEL, MINUTE_LABEL, SECOND_LABEL } = TIME_FORMAT_CONFIG;
+
+ const h = Math.floor(seconds / 3600);
+ const m = Math.floor((seconds % 3600) / 60);
+ const s = seconds % 60;
+
+ if (h > 0) {
+ return `${h}${HOUR_LABEL}${m}${MINUTE_LABEL}${s}${SECOND_LABEL}`;
+ } else if (m > 0) {
+ return `${m}${MINUTE_LABEL}${s}${SECOND_LABEL}`;
+ } else {
+ return `${s}${SECOND_LABEL}`;
+ }
+};
\ No newline at end of file