init datamate

This commit is contained in:
Dallas98
2025-10-21 23:00:48 +08:00
commit 1c97afed7d
692 changed files with 135442 additions and 0 deletions

View 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]);
}

View 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,
};
}

View 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,
};
}

View 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]);
}

View 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;