diff --git a/public/entry/image1.png b/public/entry/image1.png new file mode 100644 index 0000000..241cf4e Binary files /dev/null and b/public/entry/image1.png differ diff --git a/src/components/admin/admin-subnav.tsx b/src/components/admin/admin-subnav.tsx index bdf12ec..495c5ba 100644 --- a/src/components/admin/admin-subnav.tsx +++ b/src/components/admin/admin-subnav.tsx @@ -97,7 +97,7 @@ export function AdminSubnavButton({ className={cn(adminSubnavItemClassName(active, disabled), className)} > {children} - {count !== undefined && count > 0 ? ( + {count !== undefined ? ( ; } + const inputClass = + "h-9 rounded-xl border-gray-200 bg-white pl-10 shadow-none transition-shadow focus-visible:ring-2 focus-visible:ring-red-600/20 xl:h-10"; + const fieldIconClass = + "pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-gray-400"; + return ( -
+
-
-
-
- -
- -
-
- +
+
+
- - -
- +
+
+
+ +
-
- + +
+

{t("loginTitle", { defaultValue: authModuleMeta.title })} - -

+

+

{t("loginSubtitle")}

-
- -
- -
- - setAccount(ev.target.value)} - placeholder={t("accountPlaceholder")} - required - disabled={submitting} - className="h-11 transition-shadow focus-visible:ring-2 focus-visible:ring-primary/25" - /> -
-
- - setPassword(ev.target.value)} - placeholder={t("passwordPlaceholder")} - required - disabled={submitting} - className="h-11 transition-shadow focus-visible:ring-2 focus-visible:ring-primary/25" - /> -
-
- -
- setCaptchaCode(ev.target.value)} - placeholder={t("captchaPlaceholder")} - maxLength={32} - required - disabled={submitting} - className="h-11 min-w-0 flex-1 transition-shadow focus-visible:ring-2 focus-visible:ring-primary/25 sm:max-w-[12rem]" - /> - + + +
+ +
+ + setAccount(ev.target.value)} + placeholder={t("accountPlaceholder")} + required + disabled={submitting} + className={inputClass} + /> +
-
- - - - - - + +
+ +
+ + setPassword(ev.target.value)} + placeholder={t("passwordPlaceholder")} + required + disabled={submitting} + className={inputClass} + /> +
+
+ +
+ +
+
+ + setCaptchaCode(ev.target.value)} + placeholder={t("captchaPlaceholder")} + maxLength={32} + required + disabled={submitting} + className={inputClass} + /> +
+ +
+
+ + + +
+
); diff --git a/src/modules/agents/agent-line-detail-panel.tsx b/src/modules/agents/agent-line-detail-panel.tsx index 4c4add0..84ebd4a 100644 --- a/src/modules/agents/agent-line-detail-panel.tsx +++ b/src/modules/agents/agent-line-detail-panel.tsx @@ -34,6 +34,8 @@ export type AgentLineDetailPanelProps = { profileError?: string | null; childAgents: AgentNodeRow[]; childCountById: Map; + directPlayerCount?: number; + onDirectPlayerCountChange?: (count: number) => void; siteCode: string; parentName: string | null; detailTab: AgentDetailTab; @@ -64,6 +66,8 @@ export function AgentLineDetailPanel({ profileError = null, childAgents, childCountById, + directPlayerCount, + onDirectPlayerCountChange, siteCode, parentName, detailTab, @@ -116,6 +120,7 @@ export function AgentLineDetailPanel({ { key: "players", label: t("lineUi.tabPlayers", { defaultValue: "直属玩家" }), + count: directPlayerCount, visible: canViewPlayersTab, }, ]; @@ -224,6 +229,7 @@ export function AgentLineDetailPanel({ agentNodeId={node.id} allowCreatePlayer={canCreatePlayerAction} embedded + onTotalChange={onDirectPlayerCountChange} /> ) : null}
diff --git a/src/modules/agents/agents-console.tsx b/src/modules/agents/agents-console.tsx index de58e97..422931b 100644 --- a/src/modules/agents/agents-console.tsx +++ b/src/modules/agents/agents-console.tsx @@ -524,6 +524,7 @@ export function AgentsConsole(): React.ReactElement { const [directPlayerCountById, setDirectPlayerCountById] = useState>( () => new Map(), ); + const [selectedDirectPlayerCount, setSelectedDirectPlayerCount] = useState(0); const prevSelectedNodeIdRef = useRef(null); @@ -590,6 +591,41 @@ export function AgentsConsole(): React.ReactElement { }); }, [canShowDownlineTab, canShowPlayersTab, canViewProfileTab, selectedNodeId]); + useEffect(() => { + if (selectedNodeId === null || activeSiteCode.trim() === "") { + setSelectedDirectPlayerCount(0); + return; + } + + let cancelled = false; + void getAdminPlayers({ + site_code: activeSiteCode.trim(), + agent_node_id: selectedNodeId, + page: 1, + per_page: 1, + }) + .then((data) => { + if (cancelled) { + return; + } + setSelectedDirectPlayerCount(data.meta.total); + setDirectPlayerCountById((prev) => { + const next = new Map(prev); + next.set(selectedNodeId, data.meta.total); + return next; + }); + }) + .catch(() => { + if (!cancelled) { + setSelectedDirectPlayerCount(0); + } + }); + + return () => { + cancelled = true; + }; + }, [activeSiteCode, selectedNodeId]); + useEffect(() => { if (detailTab !== "downline" || activeSiteCode.trim() === "") { return; @@ -1077,6 +1113,8 @@ export function AgentsConsole(): React.ReactElement { profileError={selectedProfileErr} childAgents={selectedChildAgents} childCountById={childCountById} + directPlayerCount={selectedDirectPlayerCount} + onDirectPlayerCountChange={setSelectedDirectPlayerCount} siteCode={activeSiteCode} parentName={ selectedNode?.parent_id !== null && selectedNode?.parent_id !== undefined diff --git a/src/modules/agents/agents-players-panel.tsx b/src/modules/agents/agents-players-panel.tsx index cab2762..4945bac 100644 --- a/src/modules/agents/agents-players-panel.tsx +++ b/src/modules/agents/agents-players-panel.tsx @@ -146,6 +146,8 @@ type AgentsPlayersPanelProps = { allowCreatePlayer?: boolean; /** 嵌入代理线路详情 Tab 时使用紧凑顶栏 */ embedded?: boolean; + /** 列表总数变化时同步 Tab 角标等 */ + onTotalChange?: (total: number) => void; }; export function AgentsPlayersPanel({ @@ -153,6 +155,7 @@ export function AgentsPlayersPanel({ agentNodeId, allowCreatePlayer, embedded = false, + onTotalChange, }: AgentsPlayersPanelProps): React.ReactElement { const { t } = useTranslation(["agents", "players", "common"]); const router = useRouter(); @@ -269,6 +272,10 @@ export function AgentsPlayersPanel({ void load(); }, [load]); + useEffect(() => { + onTotalChange?.(total); + }, [onTotalChange, total]); + async function savePlayer(): Promise { if (siteCode.trim() === "") { toast.error(t("players:siteCodeRequired", { defaultValue: "请填写主站编号" }));