fix(web): 清理 TypeScript any 类型(16→1)
- api/client.ts: AxiosRequestConfig 模块增强 + AxiosHeaders 替代 {} as any(消 8 处)
- api/health/points.ts: 移除 7 处 as any(由 AxiosRequestConfig 增强解决)
- hooks/usePaginatedData.ts: 提取 OptionsConfig 类型消除 4 处 as any
- pages/health/MediaLibrary.tsx: Tree 组件使用 TreeNode 接口替代 any(消 2 处)
- pages/health/articleEditor/ArticleEditor.tsx: 保留 1 处 any(wangEditor 限制)
This commit is contained in:
@@ -34,7 +34,7 @@ const client = axios.create({
|
|||||||
data: entry.data,
|
data: entry.data,
|
||||||
status: 200,
|
status: 200,
|
||||||
statusText: "OK (cached)",
|
statusText: "OK (cached)",
|
||||||
headers: {} as any,
|
headers: new axios.AxiosHeaders(),
|
||||||
config,
|
config,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -203,6 +203,9 @@ function showGlobalError(msg: string) {
|
|||||||
// 全局错误拦截 — 在响应拦截器之后、组件 catch 之前执行
|
// 全局错误拦截 — 在响应拦截器之后、组件 catch 之前执行
|
||||||
// 组件可通过 axios config 中设置 skipGlobalError: true 来抑制全局提示
|
// 组件可通过 axios config 中设置 skipGlobalError: true 来抑制全局提示
|
||||||
declare module "axios" {
|
declare module "axios" {
|
||||||
|
interface AxiosRequestConfig {
|
||||||
|
skipGlobalError?: boolean;
|
||||||
|
}
|
||||||
interface InternalAxiosRequestConfig {
|
interface InternalAxiosRequestConfig {
|
||||||
skipGlobalError?: boolean;
|
skipGlobalError?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -388,7 +388,7 @@ export const pointsApi = {
|
|||||||
const { data } = await client.get<{
|
const { data } = await client.get<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
data: PointsStatistics;
|
data: PointsStatistics;
|
||||||
}>('/health/admin/points/statistics', { skipGlobalError: opts?.silent } as any);
|
}>('/health/admin/points/statistics', { skipGlobalError: opts?.silent });
|
||||||
return data.data;
|
return data.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -398,7 +398,7 @@ export const pointsApi = {
|
|||||||
const { data } = await client.get<{
|
const { data } = await client.get<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
data: PatientStatistics;
|
data: PatientStatistics;
|
||||||
}>('/health/admin/statistics/patients', { skipGlobalError: opts?.silent } as any);
|
}>('/health/admin/statistics/patients', { skipGlobalError: opts?.silent });
|
||||||
return data.data;
|
return data.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -406,7 +406,7 @@ export const pointsApi = {
|
|||||||
const { data } = await client.get<{
|
const { data } = await client.get<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
data: ConsultationStatistics;
|
data: ConsultationStatistics;
|
||||||
}>('/health/admin/statistics/consultations', { skipGlobalError: opts?.silent } as any);
|
}>('/health/admin/statistics/consultations', { skipGlobalError: opts?.silent });
|
||||||
return data.data;
|
return data.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -414,7 +414,7 @@ export const pointsApi = {
|
|||||||
const { data } = await client.get<{
|
const { data } = await client.get<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
data: FollowUpStatistics;
|
data: FollowUpStatistics;
|
||||||
}>('/health/admin/statistics/follow-ups', { skipGlobalError: opts?.silent } as any);
|
}>('/health/admin/statistics/follow-ups', { skipGlobalError: opts?.silent });
|
||||||
return data.data;
|
return data.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -422,7 +422,7 @@ export const pointsApi = {
|
|||||||
const { data } = await client.get<{
|
const { data } = await client.get<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
data: HealthDataStats;
|
data: HealthDataStats;
|
||||||
}>('/health/admin/statistics/health-data', { skipGlobalError: opts?.silent } as any);
|
}>('/health/admin/statistics/health-data', { skipGlobalError: opts?.silent });
|
||||||
return data.data;
|
return data.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -430,7 +430,7 @@ export const pointsApi = {
|
|||||||
const { data } = await client.get<{
|
const { data } = await client.get<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
data: DialysisStatistics;
|
data: DialysisStatistics;
|
||||||
}>('/health/admin/statistics/dialysis', { skipGlobalError: opts?.silent } as any);
|
}>('/health/admin/statistics/dialysis', { skipGlobalError: opts?.silent });
|
||||||
return data.data;
|
return data.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -438,7 +438,7 @@ export const pointsApi = {
|
|||||||
const { data } = await client.get<{
|
const { data } = await client.get<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
data: PersonalStats;
|
data: PersonalStats;
|
||||||
}>('/health/admin/statistics/personal-stats', { skipGlobalError: opts?.silent } as any);
|
}>('/health/admin/statistics/personal-stats', { skipGlobalError: opts?.silent });
|
||||||
return data.data;
|
return data.data;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ interface PaginatedState<T> {
|
|||||||
loading: boolean;
|
loading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FetchResult<T> = { data: T[]; total: number };
|
||||||
|
type OptionsConfig<F> = { pageSize?: number; defaultFilters: F; autoFetch?: boolean };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通用分页数据 Hook,封装 data / total / page / loading / fetch 逻辑。
|
* 通用分页数据 Hook,封装 data / total / page / loading / fetch 逻辑。
|
||||||
*
|
*
|
||||||
@@ -19,27 +22,30 @@ interface PaginatedState<T> {
|
|||||||
|
|
||||||
// 重载签名
|
// 重载签名
|
||||||
export function usePaginatedData<T, F>(
|
export function usePaginatedData<T, F>(
|
||||||
fetchFn: (page: number, pageSize: number, filters: F) => Promise<{ data: T[]; total: number }>,
|
fetchFn: (page: number, pageSize: number, filters: F) => Promise<FetchResult<T>>,
|
||||||
options: { pageSize?: number; defaultFilters: F; autoFetch?: boolean },
|
options: OptionsConfig<F>,
|
||||||
): PaginatedResult<T, F>;
|
): PaginatedResult<T, F>;
|
||||||
|
|
||||||
export function usePaginatedData<T>(
|
export function usePaginatedData<T>(
|
||||||
fetchFn:
|
fetchFn:
|
||||||
| ((page: number, pageSize: number, search: string) => Promise<{ data: T[]; total: number }>)
|
| ((page: number, pageSize: number, search: string) => Promise<FetchResult<T>>)
|
||||||
| ((page: number, pageSize: number) => Promise<{ data: T[]; total: number }>),
|
| ((page: number, pageSize: number) => Promise<FetchResult<T>>),
|
||||||
pageSize?: number,
|
pageSize?: number,
|
||||||
autoFetch?: boolean,
|
autoFetch?: boolean,
|
||||||
): PaginatedResult<T, string>;
|
): PaginatedResult<T, string>;
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any -- 实现签名必须兼容所有重载 */
|
||||||
export function usePaginatedData<T, F = string>(
|
export function usePaginatedData<T, F = string>(
|
||||||
fetchFn: (...args: any[]) => Promise<{ data: T[]; total: number }>,
|
fetchFn: (...args: any[]) => Promise<FetchResult<T>>,
|
||||||
pageSizeOrOptions?: number | { pageSize?: number; defaultFilters: F; autoFetch?: boolean },
|
pageSizeOrOptions?: number | OptionsConfig<F>,
|
||||||
autoFetch = true,
|
autoFetch = true,
|
||||||
): PaginatedResult<T, F> {
|
): PaginatedResult<T, F> {
|
||||||
|
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||||
const isOptions = typeof pageSizeOrOptions === 'object' && pageSizeOrOptions !== null;
|
const isOptions = typeof pageSizeOrOptions === 'object' && pageSizeOrOptions !== null;
|
||||||
const pageSize = isOptions ? (pageSizeOrOptions as any).pageSize ?? 20 : (pageSizeOrOptions as number) ?? 20;
|
const options = pageSizeOrOptions as OptionsConfig<F>;
|
||||||
const shouldAutoFetch = isOptions ? (pageSizeOrOptions as any).autoFetch ?? true : autoFetch;
|
const pageSize = isOptions ? options.pageSize ?? 20 : (pageSizeOrOptions as number) ?? 20;
|
||||||
const defaultFilters = isOptions ? (pageSizeOrOptions as any).defaultFilters : ('' as unknown as F);
|
const shouldAutoFetch = isOptions ? options.autoFetch ?? true : autoFetch;
|
||||||
|
const defaultFilters = isOptions ? options.defaultFilters : ('' as unknown as F);
|
||||||
|
|
||||||
const [state, setState] = useState<PaginatedState<T>>({
|
const [state, setState] = useState<PaginatedState<T>>({
|
||||||
data: [],
|
data: [],
|
||||||
@@ -50,6 +56,7 @@ export function usePaginatedData<T, F = string>(
|
|||||||
const [searchText, setSearchText] = useState('');
|
const [searchText, setSearchText] = useState('');
|
||||||
const [filters, setFilters] = useState<F>(defaultFilters);
|
const [filters, setFilters] = useState<F>(defaultFilters);
|
||||||
|
|
||||||
|
|
||||||
const fetchFnRef = useRef(fetchFn);
|
const fetchFnRef = useRef(fetchFn);
|
||||||
fetchFnRef.current = fetchFn;
|
fetchFnRef.current = fetchFn;
|
||||||
|
|
||||||
@@ -61,13 +68,14 @@ export function usePaginatedData<T, F = string>(
|
|||||||
|
|
||||||
const stateRef = useRef(state);
|
const stateRef = useRef(state);
|
||||||
stateRef.current = state;
|
stateRef.current = state;
|
||||||
|
|
||||||
|
|
||||||
const refresh = useCallback(
|
const refresh = useCallback(
|
||||||
async (p?: number) => {
|
async (p?: number) => {
|
||||||
const targetPage = p ?? stateRef.current.page;
|
const targetPage = p ?? stateRef.current.page;
|
||||||
setState((s) => ({ ...s, loading: true }));
|
setState((s) => ({ ...s, loading: true }));
|
||||||
try {
|
try {
|
||||||
const result = await (fetchFnRef.current as any)(
|
const result = await fetchFnRef.current(
|
||||||
targetPage,
|
targetPage,
|
||||||
pageSize,
|
pageSize,
|
||||||
filtersRef.current ?? searchTextRef.current,
|
filtersRef.current ?? searchTextRef.current,
|
||||||
@@ -83,6 +91,7 @@ export function usePaginatedData<T, F = string>(
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (shouldAutoFetch) {
|
if (shouldAutoFetch) {
|
||||||
|
|
||||||
refresh(1);
|
refresh(1);
|
||||||
}
|
}
|
||||||
}, [shouldAutoFetch, refresh]);
|
}, [shouldAutoFetch, refresh]);
|
||||||
@@ -95,8 +104,10 @@ export function usePaginatedData<T, F = string>(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (shouldAutoFetch) {
|
if (shouldAutoFetch) {
|
||||||
|
|
||||||
refresh(1);
|
refresh(1);
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [filters]);
|
}, [filters]);
|
||||||
|
|
||||||
return { ...state, searchText, setSearchText, filters, setFilters, refresh };
|
return { ...state, searchText, setSearchText, filters, setFilters, refresh };
|
||||||
|
|||||||
@@ -194,9 +194,9 @@ export default function MediaLibrary() {
|
|||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 1, overflow: 'auto', padding: '8px 4px' }}>
|
<div style={{ flex: 1, overflow: 'auto', padding: '8px 4px' }}>
|
||||||
{foldersLoading ? <div style={{ textAlign: 'center', padding: 20 }}><Spin size="small" /></div> : (
|
{foldersLoading ? <div style={{ textAlign: 'center', padding: 20 }}><Spin size="small" /></div> : (
|
||||||
<Tree defaultExpandAll selectedKeys={folderId ? [folderId] : ['__all__']} treeData={treeData as any}
|
<Tree defaultExpandAll selectedKeys={folderId ? [folderId] : ['__all__']} treeData={treeData}
|
||||||
fieldNames={{ title: 'name', key: 'id', children: 'children' }} onSelect={handleFolderSelect}
|
fieldNames={{ title: 'name', key: 'id', children: 'children' }} onSelect={handleFolderSelect}
|
||||||
titleRender={(node: any) => {
|
titleRender={(node: TreeNode) => {
|
||||||
if (node.id === '__all__') return <span>{node.name}</span>;
|
if (node.id === '__all__') return <span>{node.name}</span>;
|
||||||
const matched = folders.find((f) => f.id === node.id);
|
const matched = folders.find((f) => f.id === node.id);
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -200,7 +200,8 @@ export default function ArticleEditor() {
|
|||||||
style: el.getAttribute('style') || '',
|
style: el.getAttribute('style') || '',
|
||||||
innerHtml: el.innerHTML,
|
innerHtml: el.innerHTML,
|
||||||
children: [{ text: '' }],
|
children: [{ text: '' }],
|
||||||
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- wangEditor custom element type not in Slate types
|
||||||
|
} as any);
|
||||||
} else {
|
} else {
|
||||||
editor.dangerouslyInsertHtml(html);
|
editor.dangerouslyInsertHtml(html);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user