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

View File

@ -4,8 +4,9 @@ import { useCallback, useState } from 'react';
const useTableParams = ( const useTableParams = (
initialParams: IMAGES.TableParams = { initialParams: IMAGES.TableParams = {
pagination: { current: 1, pageSize: 10 }, pagination: { current: 1, pageSize: 10 },
filters: {}, filters: {},// 表格的搜索对象
sort: {}, sort: {},
search: {}, // 添加搜索参数对象
}, },
) => { ) => {
const [tableParams, setTableParams] = const [tableParams, setTableParams] =
@ -13,8 +14,8 @@ const useTableParams = (
const getApiParams = useCallback(() => { const getApiParams = useCallback(() => {
console.log('getApiParams', tableParams); console.log('getApiParams', tableParams);
const { pagination, filters, sort, ...rest } = tableParams; const { pagination, filters, sort, search, ...rest } = tableParams;
const apiParams: Record<string, any> = { const apiParams: Record<string, any> = {
page_size: pagination?.pageSize, page_size: pagination?.pageSize,
page_num: pagination?.current, page_num: pagination?.current,
@ -26,27 +27,52 @@ const useTableParams = (
apiParams.order = sort.order === 'ascend' ? 'asc' : 'desc'; apiParams.order = sort.order === 'ascend' ? 'asc' : 'desc';
} }
// 处理表格搜索参数
Object.entries(filters || {}).forEach(([key, value]) => { Object.entries(filters || {}).forEach(([key, value]) => {
if (value !== undefined && value !== null) { if (value !== undefined && value !== null) {
apiParams[key] = value; apiParams[key] = value;
} }
}); });
// 处理搜索参数
Object.entries(search || {}).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== '') {
apiParams[key] = value;
}
});
console.log('getApiParams apiParams', apiParams); console.log('getApiParams apiParams', apiParams);
return apiParams; return apiParams;
}, [tableParams]); }, [tableParams]);
const updateParams = useCallback((newParams: Partial<IMAGES.TableParams>) => { // 统一的更新方法,可以处理所有参数类型
console.log('updateParams', newParams); const updateParams = useCallback(
setTableParams((prev) => ({ (
...prev, newParams: Partial<IMAGES.TableParams>,
...newParams, options?: { resetPage?: boolean },
pagination: { ) => {
...prev.pagination, console.log('updateParams', newParams);
...newParams.pagination,
}, 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 * @returns void
* */ * */
const handleTableChange = useCallback< const handleTableChange = useCallback<
NonNullable<TableProps<IMAGES.ImageItem>['onChange']> NonNullable<TableProps<IMAGES.ImageItem>['onChange']>
>( >(
(pagination, filters, sorter, extra) => { (pagination, filters, sorter, extra) => {
console.log('handleTableChange',pagination,filters,sorter,extra);
// 过滤掉空值的filters // 过滤掉空值的filters
const filteredFilters: Record<string, any> = {}; const filteredFilters: Record<string, any> = {};
Object.entries(filters || {}).forEach(([key, value]) => { Object.entries(filters || {}).forEach(([key, value]) => {
@ -104,7 +132,7 @@ const useTableParams = (
return { return {
tableParams, tableParams,
getApiParams, getApiParams,
updateParams, updateParams, // 统一的更新方法
handleTableChange, handleTableChange,
}; };
}; };

View File

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

View File

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

View File

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