You've already forked DataMate
init datamate
This commit is contained in:
17
frontend/src/hooks/useDebouncedEffect.ts
Normal file
17
frontend/src/hooks/useDebouncedEffect.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { useEffect } from "react";
|
||||
|
||||
export function useDebouncedEffect(
|
||||
cb: () => void,
|
||||
deps: any[] = [],
|
||||
delay: number = 300
|
||||
) {
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
cb();
|
||||
}, delay);
|
||||
|
||||
return () => {
|
||||
clearTimeout(handler);
|
||||
};
|
||||
}, [...(deps || []), delay]);
|
||||
}
|
||||
113
frontend/src/hooks/useFetchData.ts
Normal file
113
frontend/src/hooks/useFetchData.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
// 首页数据获取
|
||||
import { useState } 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: any) => T = (data) => data as T
|
||||
) {
|
||||
const { message } = App.useApp();
|
||||
// 表格数据
|
||||
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];
|
||||
}
|
||||
|
||||
async function fetchData(extraParams = {}) {
|
||||
const { keyword, filter, current, pageSize } = searchParams;
|
||||
Loading.show();
|
||||
setLoading(true);
|
||||
try {
|
||||
const { data } = await fetchFunc({
|
||||
...filter,
|
||||
...extraParams,
|
||||
keyword,
|
||||
type: getFirstOfArray(filter?.type) || undefined,
|
||||
status: getFirstOfArray(filter?.status) || undefined,
|
||||
tags: filter?.tags?.length ? filter.tags.join(",") : undefined,
|
||||
page: current - 1,
|
||||
size: pageSize,
|
||||
});
|
||||
setPagination((prev) => ({
|
||||
...prev,
|
||||
total: data?.totalElements || 0,
|
||||
}));
|
||||
let result = [];
|
||||
if (mapDataFunc) {
|
||||
result = data?.content.map(mapDataFunc) ?? [];
|
||||
}
|
||||
setTableData(result);
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
message.error("数据获取失败,请稍后重试");
|
||||
} finally {
|
||||
Loading.hide();
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
useDebouncedEffect(
|
||||
() => {
|
||||
fetchData();
|
||||
},
|
||||
[searchParams],
|
||||
searchParams?.keyword ? 500 : 0
|
||||
);
|
||||
|
||||
return {
|
||||
loading,
|
||||
tableData,
|
||||
pagination: {
|
||||
...pagination,
|
||||
current: searchParams.current,
|
||||
pageSize: searchParams.pageSize,
|
||||
},
|
||||
searchParams,
|
||||
setSearchParams,
|
||||
setPagination,
|
||||
handleFiltersChange,
|
||||
fetchData,
|
||||
};
|
||||
}
|
||||
52
frontend/src/hooks/useLeavePrompt.ts
Normal file
52
frontend/src/hooks/useLeavePrompt.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router";
|
||||
|
||||
// 自定义hook:页面离开前提示
|
||||
export function useLeavePrompt(shouldPrompt: boolean) {
|
||||
const navigate = useNavigate();
|
||||
const [showPrompt, setShowPrompt] = useState(false);
|
||||
const [nextPath, setNextPath] = useState<string | null>(null);
|
||||
|
||||
// 浏览器刷新/关闭
|
||||
useEffect(() => {
|
||||
const handler = (e: BeforeUnloadEvent) => {
|
||||
if (shouldPrompt) {
|
||||
e.preventDefault();
|
||||
e.returnValue = "";
|
||||
return "";
|
||||
}
|
||||
};
|
||||
window.addEventListener("beforeunload", handler);
|
||||
return () => window.removeEventListener("beforeunload", handler);
|
||||
}, [shouldPrompt]);
|
||||
|
||||
// 路由切换拦截
|
||||
useEffect(() => {
|
||||
const unblock = (window as any).__REACT_ROUTER_DOM_HISTORY__?.block?.(
|
||||
(tx: any) => {
|
||||
if (shouldPrompt) {
|
||||
setShowPrompt(true);
|
||||
setNextPath(tx.location.pathname);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
);
|
||||
return () => {
|
||||
if (unblock) unblock();
|
||||
};
|
||||
}, [shouldPrompt]);
|
||||
|
||||
const confirmLeave = useCallback(() => {
|
||||
setShowPrompt(false);
|
||||
if (nextPath) {
|
||||
navigate(nextPath, { replace: true });
|
||||
}
|
||||
}, [nextPath, navigate]);
|
||||
|
||||
return {
|
||||
showPrompt,
|
||||
setShowPrompt,
|
||||
confirmLeave,
|
||||
};
|
||||
}
|
||||
18
frontend/src/hooks/useSearchParams.tsx
Normal file
18
frontend/src/hooks/useSearchParams.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { useMemo } from "react";
|
||||
import { useLocation } from "react-router";
|
||||
|
||||
interface AnyObject {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export function useSearchParams(): AnyObject {
|
||||
const { search } = useLocation();
|
||||
return useMemo(() => {
|
||||
const urlParams = new URLSearchParams(search);
|
||||
const params: AnyObject = {};
|
||||
for (const [key, value] of urlParams.entries()) {
|
||||
params[key] = value;
|
||||
}
|
||||
return params;
|
||||
}, [search]);
|
||||
}
|
||||
20
frontend/src/hooks/useStyle.ts
Normal file
20
frontend/src/hooks/useStyle.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { createStyles } from "antd-style";
|
||||
|
||||
const useStyle = createStyles(({ css, token }) => {
|
||||
const { antCls } = token;
|
||||
return {
|
||||
customTable: css`
|
||||
${antCls}-table {
|
||||
${antCls}-table-container {
|
||||
${antCls}-table-body, ${antCls}-table-content {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: ${token.colorBorder} transparent;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
||||
export default useStyle;
|
||||
Reference in New Issue
Block a user