You've already forked DataMate
* feat: Update site name to DataMate and refine text for AI data processing * feat: Refactor settings page and implement model access functionality - Created a new ModelAccess component for managing model configurations. - Removed the old Settings component and replaced it with a new SettingsPage component that integrates ModelAccess, SystemConfig, and WebhookConfig. - Added SystemConfig component for managing system settings. - Implemented WebhookConfig component for managing webhook configurations. - Updated API functions for model management in settings.apis.ts. - Adjusted routing to point to the new SettingsPage component. * feat: Implement Data Collection Page with Task Management and Execution Log - Created DataCollectionPage component to manage data collection tasks. - Added TaskManagement and ExecutionLog components for task handling and logging. - Integrated task operations including start, stop, edit, and delete functionalities. - Implemented filtering and searching capabilities in task management. - Introduced SimpleCronScheduler for scheduling tasks with cron expressions. - Updated CreateTask component to utilize new scheduling and template features. - Enhanced BasicInformation component to conditionally render fields based on visibility settings. - Refactored ImportConfiguration component to remove NAS import section. * feat: Update task creation API endpoint and enhance task creation form with new fields and validation * Refactor file upload and operator management components - Removed unnecessary console logs from file download and export functions. - Added size property to TaskItem interface for better task management. - Simplified TaskUpload component by utilizing useFileSliceUpload hook for file upload logic. - Enhanced OperatorPluginCreate component to handle file uploads and parsing more efficiently. - Updated ConfigureStep component to use Ant Design Form for better data handling and validation. - Improved PreviewStep component to navigate back to the operator market. - Added support for additional file types in UploadStep component. - Implemented delete operator functionality in OperatorMarketPage with confirmation prompts. - Cleaned up unused API functions in operator.api.ts to streamline the codebase. - Fixed number formatting utility to handle zero values correctly.
226 lines
6.0 KiB
TypeScript
226 lines
6.0 KiB
TypeScript
// 首页数据获取
|
|
// 支持轮询功能,使用示例:
|
|
// const { fetchData, startPolling, stopPolling, isPolling } = useFetchData(
|
|
// fetchFunction,
|
|
// mapFunction,
|
|
// 5000, // 5秒轮询一次,默认30秒
|
|
// true, // 是否自动开始轮询,默认 true
|
|
// [fetchStatistics, fetchOtherData] // 额外的轮询函数数组
|
|
// );
|
|
//
|
|
// startPolling(); // 开始轮询
|
|
// stopPolling(); // 停止轮询
|
|
// 手动调用 fetchData() 时,如果正在轮询,会重新开始轮询计时
|
|
// 轮询时会同时执行主要的 fetchFunction 和所有额外的轮询函数
|
|
import { useState, useRef, useEffect, useCallback } from "react";
|
|
import { useDebouncedEffect } from "./useDebouncedEffect";
|
|
import Loading from "@/utils/loading";
|
|
import { App } from "antd";
|
|
|
|
export default function useFetchData<T>(
|
|
fetchFunc: (params?: any) => Promise<any>,
|
|
mapDataFunc: (data: Partial<T>) => T = (data) => data as T,
|
|
pollingInterval: number = 30000, // 默认30秒轮询一次
|
|
autoRefresh: boolean = true,
|
|
additionalPollingFuncs: (() => Promise<any>)[] = [], // 额外的轮询函数
|
|
pageOffset: number = 1
|
|
) {
|
|
const { message } = App.useApp();
|
|
|
|
// 轮询相关状态
|
|
const [isPolling, setIsPolling] = useState(false);
|
|
const pollingTimerRef = useRef<NodeJS.Timeout | null>(null);
|
|
|
|
// 表格数据
|
|
const [tableData, setTableData] = useState<T[]>([]);
|
|
// 设置加载状态
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
// 搜索参数
|
|
const [searchParams, setSearchParams] = useState({
|
|
keyword: "",
|
|
filter: {
|
|
type: [] as string[],
|
|
status: [] as string[],
|
|
tags: [] as string[],
|
|
},
|
|
current: 1,
|
|
pageSize: 12,
|
|
});
|
|
|
|
// 分页配置
|
|
const [pagination, setPagination] = useState({
|
|
total: 0,
|
|
showSizeChanger: true,
|
|
pageSizeOptions: ["12", "24", "48"],
|
|
showTotal: (total: number) => `共 ${total} 条`,
|
|
onChange: (current: number, pageSize?: number) => {
|
|
setSearchParams((prev) => ({
|
|
...prev,
|
|
current,
|
|
pageSize: pageSize || prev.pageSize,
|
|
}));
|
|
},
|
|
});
|
|
|
|
const handleFiltersChange = (searchFilters: { [key: string]: string[] }) => {
|
|
setSearchParams({
|
|
...searchParams,
|
|
current: 1,
|
|
filter: { ...searchParams.filter, ...searchFilters },
|
|
});
|
|
};
|
|
|
|
function getFirstOfArray(arr: string[]) {
|
|
if (!arr || arr.length === 0 || !Array.isArray(arr)) return undefined;
|
|
if (arr[0] === "all") return undefined;
|
|
return arr[0];
|
|
}
|
|
|
|
// 清除轮询定时器
|
|
const clearPollingTimer = useCallback(() => {
|
|
if (pollingTimerRef.current) {
|
|
clearTimeout(pollingTimerRef.current);
|
|
pollingTimerRef.current = null;
|
|
}
|
|
}, []);
|
|
|
|
const fetchData = useCallback(
|
|
async (extraParams = {}, skipPollingRestart = false) => {
|
|
const { keyword, filter, current, pageSize } = searchParams;
|
|
if (!skipPollingRestart) {
|
|
Loading.show();
|
|
setLoading(true);
|
|
}
|
|
|
|
// 如果正在轮询且不是轮询触发的调用,先停止当前轮询
|
|
const wasPolling = isPolling && !skipPollingRestart;
|
|
if (wasPolling) {
|
|
clearPollingTimer();
|
|
}
|
|
|
|
try {
|
|
// 同时执行主要数据获取和额外的轮询函数
|
|
const promises = [
|
|
fetchFunc({
|
|
...filter,
|
|
...extraParams,
|
|
keyword,
|
|
type: getFirstOfArray(filter?.type) || undefined,
|
|
status: getFirstOfArray(filter?.status) || undefined,
|
|
tags: filter?.tags?.length ? filter.tags.join(",") : undefined,
|
|
page: current - pageOffset,
|
|
size: pageSize,
|
|
}),
|
|
...additionalPollingFuncs.map((func) => func()),
|
|
];
|
|
|
|
const results = await Promise.all(promises);
|
|
const { data } = results[0]; // 主要数据结果
|
|
|
|
setPagination((prev) => ({
|
|
...prev,
|
|
total: data?.totalElements || 0,
|
|
}));
|
|
let result = [];
|
|
if (mapDataFunc) {
|
|
result = data?.content.map(mapDataFunc) ?? [];
|
|
}
|
|
setTableData(result);
|
|
|
|
// 如果之前正在轮询且不是轮询触发的调用,重新开始轮询
|
|
if (wasPolling) {
|
|
const poll = () => {
|
|
pollingTimerRef.current = setTimeout(() => {
|
|
fetchData({}, true).then(() => {
|
|
if (pollingTimerRef.current) {
|
|
poll();
|
|
}
|
|
});
|
|
}, pollingInterval);
|
|
};
|
|
poll();
|
|
}
|
|
} catch (error) {
|
|
console.error(error);
|
|
message.error("数据获取失败,请稍后重试");
|
|
} finally {
|
|
Loading.hide();
|
|
setLoading(false);
|
|
}
|
|
},
|
|
[
|
|
searchParams,
|
|
fetchFunc,
|
|
mapDataFunc,
|
|
isPolling,
|
|
clearPollingTimer,
|
|
pollingInterval,
|
|
message,
|
|
additionalPollingFuncs,
|
|
]
|
|
);
|
|
|
|
// 开始轮询
|
|
const startPolling = useCallback(() => {
|
|
clearPollingTimer();
|
|
setIsPolling(true);
|
|
|
|
const poll = () => {
|
|
pollingTimerRef.current = setTimeout(() => {
|
|
fetchData({}, true).then(() => {
|
|
if (pollingTimerRef.current) {
|
|
poll();
|
|
}
|
|
});
|
|
}, pollingInterval);
|
|
};
|
|
|
|
poll();
|
|
}, [pollingInterval, clearPollingTimer, fetchData]);
|
|
|
|
// 停止轮询
|
|
const stopPolling = useCallback(() => {
|
|
clearPollingTimer();
|
|
setIsPolling(false);
|
|
}, [clearPollingTimer]);
|
|
|
|
// 搜索参数变化时,自动刷新数据
|
|
// keyword 变化时,防抖500ms后刷新
|
|
useDebouncedEffect(
|
|
() => {
|
|
fetchData();
|
|
},
|
|
[searchParams],
|
|
searchParams?.keyword ? 500 : 0
|
|
);
|
|
|
|
// 组件卸载时清理轮询
|
|
useEffect(() => {
|
|
if (autoRefresh) {
|
|
startPolling();
|
|
}
|
|
return () => {
|
|
clearPollingTimer();
|
|
};
|
|
}, [clearPollingTimer]);
|
|
|
|
return {
|
|
loading,
|
|
tableData,
|
|
pagination: {
|
|
...pagination,
|
|
current: searchParams.current,
|
|
pageSize: searchParams.pageSize,
|
|
},
|
|
searchParams,
|
|
setSearchParams,
|
|
setPagination,
|
|
handleFiltersChange,
|
|
fetchData,
|
|
isPolling,
|
|
startPolling,
|
|
stopPolling,
|
|
};
|
|
}
|