feat(api, i18n): add agent_node_id to various admin queries and enhance multi-language support
Introduced the agent_node_id field in AdminDrawListQuery, AdminPlayerListQuery, AdminSettlementBatchListQuery, TicketItemsListQuery, and TransferOrderListQuery to improve filtering capabilities. Updated the admin-breadcrumb and admin-sidebar components to include new translations for agent-related terms in English, Nepali, and Chinese, enhancing the overall user experience and multi-language support across the admin interface.
This commit is contained in:
@@ -6,14 +6,50 @@ import { getAdminIntegrationSites } from "@/api/admin-integration-sites";
|
||||
import { adminHasAnyPermission } from "@/lib/admin-permissions";
|
||||
import { PRD_INTEGRATION_ACCESS_ANY } from "@/lib/admin-prd";
|
||||
import { useAdminProfile } from "@/stores/admin-session";
|
||||
import { useAsyncEffect } from "@/hooks/use-async-effect";
|
||||
|
||||
export type AdminSiteCodeOption = {
|
||||
code: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
let cachedSites: AdminSiteCodeOption[] | null = null;
|
||||
let inflightSites: Promise<AdminSiteCodeOption[]> | null = null;
|
||||
|
||||
export function clearCachedAdminSiteCodeOptions(): void {
|
||||
cachedSites = null;
|
||||
inflightSites = null;
|
||||
}
|
||||
|
||||
async function fetchSiteCodeOptions(): Promise<AdminSiteCodeOption[]> {
|
||||
if (cachedSites !== null) {
|
||||
return cachedSites;
|
||||
}
|
||||
if (inflightSites !== null) {
|
||||
return inflightSites;
|
||||
}
|
||||
|
||||
inflightSites = getAdminIntegrationSites()
|
||||
.then((data) => {
|
||||
cachedSites = data.items.map((row) => ({
|
||||
code: row.code,
|
||||
name: row.name,
|
||||
}));
|
||||
return cachedSites;
|
||||
})
|
||||
.catch(() => {
|
||||
cachedSites = [];
|
||||
return [];
|
||||
})
|
||||
.finally(() => {
|
||||
inflightSites = null;
|
||||
});
|
||||
|
||||
return inflightSites;
|
||||
}
|
||||
|
||||
/**
|
||||
* 接入站点下拉(已按当前管理员站点权限过滤)。
|
||||
* 接入站点下拉(已按当前管理员站点权限过滤;模块级缓存避免多页重复 GET)。
|
||||
*/
|
||||
export function useAdminSiteCodeOptions(): {
|
||||
sites: AdminSiteCodeOption[];
|
||||
@@ -24,24 +60,21 @@ export function useAdminSiteCodeOptions(): {
|
||||
const profile = useAdminProfile();
|
||||
const canLoad = adminHasAnyPermission(profile?.permissions, PRD_INTEGRATION_ACCESS_ANY);
|
||||
|
||||
const [sites, setSites] = useState<AdminSiteCodeOption[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [sites, setSites] = useState<AdminSiteCodeOption[]>(cachedSites ?? []);
|
||||
const [loading, setLoading] = useState(canLoad && cachedSites === null);
|
||||
|
||||
const reload = useCallback(async () => {
|
||||
if (!canLoad) {
|
||||
setSites([]);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const data = await getAdminIntegrationSites();
|
||||
setSites(
|
||||
data.items.map((row) => ({
|
||||
code: row.code,
|
||||
name: row.name,
|
||||
})),
|
||||
);
|
||||
clearCachedAdminSiteCodeOptions();
|
||||
const next = await fetchSiteCodeOptions();
|
||||
setSites(next);
|
||||
} catch {
|
||||
setSites([]);
|
||||
} finally {
|
||||
@@ -49,11 +82,24 @@ export function useAdminSiteCodeOptions(): {
|
||||
}
|
||||
}, [canLoad]);
|
||||
|
||||
useEffect(() => {
|
||||
queueMicrotask(() => {
|
||||
void reload();
|
||||
});
|
||||
}, [reload]);
|
||||
useAsyncEffect(() => {
|
||||
if (!canLoad) {
|
||||
setSites([]);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
if (cachedSites !== null) {
|
||||
setSites(cachedSites);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
void (async () => {
|
||||
setLoading(true);
|
||||
const next = await fetchSiteCodeOptions();
|
||||
setSites(next);
|
||||
setLoading(false);
|
||||
})();
|
||||
}, [canLoad]);
|
||||
|
||||
return {
|
||||
sites,
|
||||
|
||||
21
src/hooks/use-async-effect.ts
Normal file
21
src/hooks/use-async-effect.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, type DependencyList } from "react";
|
||||
|
||||
/**
|
||||
* 在依赖变化时执行异步副作用;factory 始终用最新闭包,但不必把 `t` 等不稳定引用放进 deps。
|
||||
*/
|
||||
export function useAsyncEffect(
|
||||
factory: () => void | Promise<void>,
|
||||
deps: DependencyList,
|
||||
): void {
|
||||
const factoryRef = useRef(factory);
|
||||
factoryRef.current = factory;
|
||||
|
||||
useEffect(() => {
|
||||
queueMicrotask(() => {
|
||||
void factoryRef.current();
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- factory 经 ref 同步,deps 仅含真实查询参数
|
||||
}, deps);
|
||||
}
|
||||
43
src/hooks/use-cached-play-type-options.ts
Normal file
43
src/hooks/use-cached-play-type-options.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { getAdminPlayTypes } from "@/api/admin-config";
|
||||
import {
|
||||
getAdminPlayTypesLoadPromise,
|
||||
getCachedAdminPlayTypes,
|
||||
resolveAdminPlayTypeDisplayName,
|
||||
} from "@/lib/admin-play-types";
|
||||
|
||||
export type PlayTypeOption = { code: string; label: string };
|
||||
|
||||
/**
|
||||
* 从全局玩法缓存生成下拉选项;仅首次 miss 时请求 API,语言切换只重算 label。
|
||||
*/
|
||||
export function useCachedPlayTypeOptions(): PlayTypeOption[] {
|
||||
const { i18n } = useTranslation();
|
||||
const [options, setOptions] = useState<PlayTypeOption[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
void (async () => {
|
||||
await getAdminPlayTypesLoadPromise(getAdminPlayTypes);
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
setOptions(
|
||||
getCachedAdminPlayTypes().map((item) => ({
|
||||
code: item.play_code,
|
||||
label:
|
||||
resolveAdminPlayTypeDisplayName(item.play_code, i18n.language, item) || item.play_code,
|
||||
})),
|
||||
);
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [i18n.language]);
|
||||
|
||||
return options;
|
||||
}
|
||||
12
src/hooks/use-translation-ref.ts
Normal file
12
src/hooks/use-translation-ref.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { useRef } from "react";
|
||||
import { useTranslation, type UseTranslationOptions } from "react-i18next";
|
||||
|
||||
/** 稳定引用 i18n `t`,避免放进 useCallback/useEffect 依赖导致重复请求 */
|
||||
export function useTranslationRef(ns?: string | string[], options?: UseTranslationOptions<string>) {
|
||||
const { t } = useTranslation(ns, options);
|
||||
const tRef = useRef(t);
|
||||
tRef.current = t;
|
||||
return tRef;
|
||||
}
|
||||
Reference in New Issue
Block a user