fix(镜像列表): 取消上传+参数逻辑

master
chenyt 2025-08-12 11:46:19 +08:00
parent 0cae7e90bd
commit a1dbc6a736
5 changed files with 157 additions and 78 deletions

View File

@ -11,7 +11,6 @@ import {
import {
getChunkSize,
calculateMD5InChunks,
calculateMD5InChunksWorkers,
formatTime,
} from '@/pages/images/utils/images';
@ -400,7 +399,9 @@ const ImportModal: React.FC<IMAGES.ImportModalProps> = ({
// 如果有正在上传的文件调用后端取消上传API
if (fileId.current) {
try {
await cancelUploadImagesAPI({ file_id: fileId.current });
const params = new URLSearchParams();
params.append('file_id', fileId.current);
await cancelUploadImagesAPI(params);
} catch (error) {
console.error('取消上传API调用失败:', error);
}

View File

@ -4,8 +4,9 @@ import { useCallback, useState } from 'react';
const useTableParams = (
initialParams: IMAGES.TableParams = {
pagination: { current: 1, pageSize: 10 },
filters: {},
filters: {},// 表格的搜索对象
sort: {},
search: {}, // 添加搜索参数对象
},
) => {
const [tableParams, setTableParams] =
@ -14,7 +15,7 @@ const useTableParams = (
const getApiParams = useCallback(() => {
console.log('getApiParams', tableParams);
const { pagination, filters, sort, ...rest } = tableParams;
const { pagination, filters, sort, search, ...rest } = tableParams;
const apiParams: Record<string, any> = {
page_size: pagination?.pageSize,
page_num: pagination?.current,
@ -26,27 +27,52 @@ const useTableParams = (
apiParams.order = sort.order === 'ascend' ? 'asc' : 'desc';
}
// 处理表格搜索参数
Object.entries(filters || {}).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
apiParams[key] = value;
}
});
// 处理搜索参数
Object.entries(search || {}).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== '') {
apiParams[key] = value;
}
});
console.log('getApiParams apiParams', apiParams);
return apiParams;
}, [tableParams]);
const updateParams = useCallback((newParams: Partial<IMAGES.TableParams>) => {
console.log('updateParams', newParams);
setTableParams((prev) => ({
...prev,
...newParams,
pagination: {
...prev.pagination,
...newParams.pagination,
},
}));
}, []);
// 统一的更新方法,可以处理所有参数类型
const updateParams = useCallback(
(
newParams: Partial<IMAGES.TableParams>,
options?: { resetPage?: boolean },
) => {
console.log('updateParams', newParams);
setTableParams((prev) => {
// 如果是搜索或过滤相关的更新,重置到第一页
const shouldResetPage =
options?.resetPage ??
((newParams.search && Object.keys(newParams.search).length > 0) || // 有搜索值
(newParams.filters && Object.keys(newParams.filters).length > 0)); // 有过滤值
return {
...prev,
...newParams,
pagination: {
...prev.pagination,
...newParams.pagination,
...(shouldResetPage ? { current: 1 } : {}), // 根据条件决定是否重置页码
},
};
});
},
[],
);
/**
*
@ -57,9 +83,11 @@ const useTableParams = (
* @returns void
* */
const handleTableChange = useCallback<
NonNullable<TableProps<IMAGES.ImageItem>['onChange']>
NonNullable<TableProps<IMAGES.ImageItem>['onChange']>
>(
(pagination, filters, sorter, extra) => {
console.log('handleTableChange',pagination,filters,sorter,extra);
// 过滤掉空值的filters
const filteredFilters: Record<string, any> = {};
Object.entries(filters || {}).forEach(([key, value]) => {
@ -104,7 +132,7 @@ const useTableParams = (
return {
tableParams,
getApiParams,
updateParams,
updateParams, // 统一的更新方法
handleTableChange,
};
};

View File

@ -9,6 +9,7 @@ import {
Button,
Checkbox,
Input,
Menu,
message,
Modal,
Popconfirm,
@ -38,6 +39,7 @@ type ColumnConfig = {
alwaysVisible?: boolean; // 始终显示的列
filters?: { text: string; value: string }[];
filterMultiple?: boolean; // 是否多选过滤
filterDropdown?: (props: any) => React.ReactNode;
defaultFilteredValue?: string[]; // 默认过滤值
onFilter?: (value: string, record: any) => boolean;
};
@ -52,13 +54,33 @@ type TableColumn = {
hidden?: boolean;
};
// 在组件顶部添加防抖函数
const debounce = (func: Function, delay: number) => {
let timer: NodeJS.Timeout;
return (...args: any[]) => {
clearTimeout(timer);
timer = setTimeout(() => func(...args), delay);
// 在组件顶部添加防抖函数(支持取消)
// 增强版防抖函数
const debounce = (func: Function, delay: number, immediate = false) => {
let timer: NodeJS.Timeout | null = null;
const debounced = (...args: any[]) => {
if (timer) clearTimeout(timer);
if (immediate && !timer) {
func(...args);
}
timer = setTimeout(() => {
if (!immediate) {
func(...args);
}
timer = null;
}, delay);
};
debounced.cancel = () => {
if (timer) {
clearTimeout(timer);
timer = null;
}
};
return debounced;
};
const ImageList: React.FC = () => {
@ -78,6 +100,7 @@ const ImageList: React.FC = () => {
current: 1,
pageSize: 10,
},
search: {}, // 初始化搜索参数
});
// 在组件顶部添加一个 ref 来保存最新的 tableParams
@ -94,7 +117,8 @@ const ImageList: React.FC = () => {
tableParams.pagination?.pageSize,
tableParams?.sortOrder,
tableParams?.sortField,
JSON.stringify(tableParams.filters),
JSON.stringify(tableParams.filters), // 表格搜索参数
JSON.stringify(tableParams.search), // 搜索参数依赖
]);
// 定义所有列的配置
@ -144,23 +168,24 @@ const ImageList: React.FC = () => {
return <Tooltip>{IMAGES_TYPE_MAP[key] || '--'}</Tooltip>;
},
defaultVisible: true,
filters: [
{ text: '全部', value: '全部' },
...Object.entries(IMAGES_TYPE_MAP).map(([key, value]) => ({
text: value,
value: key, // 保持 key 为字符串
})),
],
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm }) => (
<Menu
selectedKeys={selectedKeys.length > 0 ? selectedKeys : ['全部']}
onClick={({ key }) => {
setSelectedKeys(key === '全部' ? [] : [key]);
confirm({ closeDropdown: true }); // 立即触发筛选并关闭下拉菜单
}}
items={[
{ key: '全部', label: '全部' },
...Object.entries(IMAGES_TYPE_MAP).map(([key, value]) => ({
key,
label: value,
})),
]}
/>
),
filterMultiple: false,
defaultFilteredValue: ['全部'],
// onFilter: (value, record) => {
// // 当选择"全部"时显示所有记录
// if (value === '全部') {
// return true;
// }
// // 比较时将字符串 value 转换为数字
// return record.image_type === Number(value);
// },
},
{
key: 'storage_path',
@ -263,14 +288,12 @@ const ImageList: React.FC = () => {
const apiParams = {
...getApiParams(),
};
if (searchInputRef.current) {
apiParams.image_name = searchInputRef.current;
}
console.log('apiParams', apiParams);
const imagesRes = await getImagesList(apiParams);
if (imagesRes.code == CODE) {
setImages(imagesRes.data?.data || []);
setLoading(false);
console.log('imagesRes', imagesRes);
// 正确处理后端返回的分页信息
updateParams({
@ -278,7 +301,7 @@ const ImageList: React.FC = () => {
...tableParams.pagination,
current: imagesRes.data?.page_num || 1,
total: imagesRes.data?.total || 0,
pageSize: tableParams.pagination.pageSize|| 10,
pageSize: tableParams.pagination.pageSize || 10,
},
});
} else {
@ -305,11 +328,13 @@ const ImageList: React.FC = () => {
};
const handleDelete = (record: IMAGES.ImageItem) => {
console.log(record);
Modal.confirm({
title: '确认删除',
content: `确定要删除镜像 "${record.image_name}" 吗?`,
onOk: () => {
delImagesAPI(record.id).then((res) => {
delImagesAPI({id:record.id}).then((res) => {
if (res.code == CODE) {
message.success('删除成功');
loadImages();
@ -399,30 +424,60 @@ const ImageList: React.FC = () => {
const handleSearch = useCallback(
(searchValue: string) => {
console.log('handleSearch', searchValue, tableParams);
// 只有当搜索值变化时才发送请求
// if (searchInputRef.current === searchValue) return;
console.log('实际触发搜索:', searchValue);
// searchInputRef.current = searchValue;
// 使用 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({
search: {
image_name: searchValue,
},
pagination: {
current: 1,
pageSize: currentTableParams.pagination?.pageSize || 10,
},
});
},
[updateParams],
);
// 防抖版本500ms延迟不立即执行
const debouncedSearch = useRef(debounce(handleSearch, 500)).current;
// 立即执行版本(用于清空时立即搜索)
const immediateSearch = useRef(debounce(handleSearch, 0, true)).current;
const handleSearchChange = (value: string) => {
setSearchText(value);
// 取消所有未执行的防抖请求
debouncedSearch.cancel();
immediateSearch.cancel();
console.log('输入变化:', value);
// 清空时立即触发搜索
if (value === '') {
immediateSearch('');
return;
}
// 正常输入时使用防抖
debouncedSearch(value);
};
// 修改回车搜索处理
const handleEnterSearch = (value: string) => {
console.log('回车搜索:', value);
// 回车搜索时取消未执行的防抖
debouncedSearch.cancel();
immediateSearch.cancel();
// 直接执行搜索
handleSearch(value);
};
return (
<div className="image-list">
@ -432,18 +487,9 @@ const ImageList: React.FC = () => {
<Input.Search
placeholder="镜像名称"
value={searchText}
onChange={(e) => {
const value = e.target.value;
setSearchText(value);
// 只有当输入为空时立即触发搜索,否则使用防抖
if (value === '') {
handleSearch('');
} else {
debouncedSearch(value);
}
}}
onChange={(e) => handleSearchChange(e.target.value)}
style={{ width: 300 }}
onSearch={handleSearch}
onSearch={handleEnterSearch}
/>
<Button
onClick={handleRefresh}

View File

@ -17,6 +17,9 @@ export async function delImagesAPI(params:any) {
return request<any>(`${BASE_URL}/image/delete`, {
method: 'POST',
data: params,
headers: {
'Content-Type': 'application/json',
},
});
}
@ -34,5 +37,8 @@ export async function cancelUploadImagesAPI(params:any) {
return request<any>(`${BASE_URL_FILE}/cancel/upload`, {
method: 'POST',
data: params,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
});
}

View File

@ -22,16 +22,14 @@ declare namespace IMAGES {
pageSize: number;
total?: number;
};
image_name?: string;
image_type?: number | string; // 修改为联合类型
filters?: Record<string, any>;
search?: Record<string, any>; // 增加搜索参数
filters?: Record<string, any>; // 表格搜索参数
sort?: {
field?: string;
order?: 'ascend' | 'descend';
};
sortOrder?: 'ascend' | 'descend' | null;
sortField?: string | number | null;
image_status?: string;
}
interface Images_ListInfo {