c3232f73c8
- Created useScopeEffect hook that auto-triggers reload on scope change - Replaced useEffect([], []) with useScopeEffect in 20+ pages - Risks/Reports/Dashboard also use useScopeEffect - Removed redundant per-page useCompanyScope where only effect was needed
110 lines
3.2 KiB
TypeScript
110 lines
3.2 KiB
TypeScript
/** 企业视角切换 Context — 全局企业筛选器。 */
|
||
|
||
"use client";
|
||
|
||
import { createContext, useContext, useEffect, useState, type ReactNode } from "react";
|
||
import { listCompanies, type Company } from "@/lib/companies";
|
||
|
||
interface CompanyScopeValue {
|
||
/** 选中的企业 ID,null 表示"全部企业" */
|
||
companyId: string | null;
|
||
/** 选中的企业名称,null 表示"全部企业" */
|
||
companyName: string | null;
|
||
/** 企业列表 */
|
||
companies: Company[];
|
||
/** 切换企业 */
|
||
setCompanyId: (id: string | null) => void;
|
||
/** 是否加载中 */
|
||
isLoading: boolean;
|
||
}
|
||
|
||
const CompanyScopeContext = createContext<CompanyScopeValue | undefined>(undefined);
|
||
|
||
/**
|
||
* 企业视角 Provider。
|
||
*
|
||
* 在侧边栏顶部提供企业选择器,选中后所有页面自动按该企业过滤数据。
|
||
* 选择"全部企业"时恢复全局视图。
|
||
*/
|
||
export function CompanyScopeProvider({ children }: { children: ReactNode }) {
|
||
const [companyId, setCompanyIdState] = useState<string | null>(null);
|
||
const [companies, setCompanies] = useState<Company[]>([]);
|
||
const [isLoading, setIsLoading] = useState(true);
|
||
|
||
// 加载企业列表
|
||
useEffect(() => {
|
||
listCompanies({ page_size: 100 })
|
||
.then((resp) => {
|
||
if (resp.data?.items) setCompanies(resp.data.items);
|
||
})
|
||
.catch(() => {
|
||
// ignore
|
||
})
|
||
.finally(() => setIsLoading(false));
|
||
}, []);
|
||
|
||
// 从 localStorage 恢复选择
|
||
useEffect(() => {
|
||
const saved = localStorage.getItem("company_scope_id");
|
||
if (saved && saved !== "all") {
|
||
setCompanyIdState(saved);
|
||
}
|
||
}, []);
|
||
|
||
// 持久化选择 + 派发事件通知各页面重新加载
|
||
function setCompanyId(id: string | null) {
|
||
setCompanyIdState(id);
|
||
if (id) {
|
||
localStorage.setItem("company_scope_id", id);
|
||
} else {
|
||
localStorage.setItem("company_scope_id", "all");
|
||
}
|
||
// 派发全局事件,让监听了 useScopeEffect 的页面重新加载数据
|
||
window.dispatchEvent(new CustomEvent("company-scope-change", { detail: { companyId: id } }));
|
||
}
|
||
|
||
const companyName = companyId
|
||
? companies.find((c) => c.id === companyId)?.name ?? null
|
||
: null;
|
||
|
||
return (
|
||
<CompanyScopeContext.Provider
|
||
value={{ companyId, companyName, companies, setCompanyId, isLoading }}
|
||
>
|
||
{children}
|
||
</CompanyScopeContext.Provider>
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 使用企业视角 Context。
|
||
*
|
||
* 返回当前选中的企业 ID,用于各页面过滤数据。
|
||
*/
|
||
export function useCompanyScope() {
|
||
const ctx = useContext(CompanyScopeContext);
|
||
if (!ctx) {
|
||
throw new Error("useCompanyScope 必须在 CompanyScopeProvider 内使用");
|
||
}
|
||
return ctx;
|
||
}
|
||
|
||
/**
|
||
* 监听企业视角变化,自动触发回调重新加载数据。
|
||
*
|
||
* 各页面在 useEffect 中调用此 hook,当全局企业选择变化时,
|
||
* 会自动触发回调重新加载(apiFetch 会自动注入新的 company_id)。
|
||
*
|
||
* @param callback 数据加载回调
|
||
* @param deps 额外依赖(如分页、筛选状态)
|
||
*/
|
||
export function useScopeEffect(callback: () => void, deps: unknown[] = []) {
|
||
const { companyId } = useCompanyScope();
|
||
|
||
useEffect(() => {
|
||
callback();
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||
}, [companyId, ...deps]);
|
||
}
|
||
|