From b01e2ddb1a6835a5719d86a0c9c5c5cf37993ecc Mon Sep 17 00:00:00 2001 From: AskaEth Date: Tue, 13 Oct 2026 19:56:32 +0800 Subject: [PATCH] =?UTF-8?q?refactor(admin):=20=E4=B8=BB=E4=BA=BA=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7=E7=BC=96=E8=BE=91=E5=99=A8=E6=8B=86=E5=87=BA=E6=97=A0?= =?UTF-8?q?=20hook=20=E7=9A=84=20OwnerAccountsView?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 界面本体与「分组/选项 memo」分层:OwnerAccountsEditor 只做 memo 组装,OwnerAccountsView 无 hook。本项目没有 jsdom,拆开后测试可以**直接调用**视图、点它自己的按钮,从而钉住「手势 → 草稿 → 重渲染」这条往返(见下一个提交)。行为不变:emit 仍用 flattenAccounts;账号行加 data-account-row 定位标记。 --- .../src/admin/modules/OwnerAccountsEditor.tsx | 47 +++++++++++++++++-- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/frontend/web/src/admin/modules/OwnerAccountsEditor.tsx b/frontend/web/src/admin/modules/OwnerAccountsEditor.tsx index 2ed6ba9..2807b2c 100644 --- a/frontend/web/src/admin/modules/OwnerAccountsEditor.tsx +++ b/frontend/web/src/admin/modules/OwnerAccountsEditor.tsx @@ -13,11 +13,16 @@ import { /** * 渠道账号白名单编辑器(主人 2026-10-05 要求):**一个平台一条**,该平台下用 * 「+ 添加账号 ID」加多行;平台是权威枚举**下拉**;账号 ID 旁给「从该平台已配置 - * 账号里选」的候选。改动经 `onChange` 回到草稿时**立即展平**为 + * 账号里选」的候选。改动经 `onChange` 回到草稿时**写回扁平数组** * `accounts:[{platform,accountId}…]` —— 后端契约与 `owner.yaml` 格式完全不变。 * * 为什么不在这里直接保存:档案的读取/草稿/保存状态由 `Users.tsx` 的 - * `OwnerProfileSection` 统一持有(一次真实 PUT),本组件只管呈现与展平。 + * `OwnerProfileSection` 统一持有(一次真实 PUT),本组件只管呈现与写回草稿。 + * + * 为什么拆成两个函数:`OwnerAccountsEditor` 只留「分组/选项」这层 memo(同一表单里改 + * 别的字段时不必重算),界面本体 `OwnerAccountsView` 是**无 hook** 的 —— 本项目没有 + * jsdom,测试正是**直接调用**它、点它自己的按钮来钉住「手势 → 草稿 → 重渲染」这条往返 + * (见 `ownerAccounts.test.ts` ⑤)。往 View 里加 hook 会让那条测试报 "Invalid hook call"。 */ export function OwnerAccountsEditor({ accounts, @@ -39,9 +44,37 @@ export function OwnerAccountsEditor({ () => platformOptions(platforms, groups.map((g) => g.platform)), [platforms, groups] ); + return ( + + ); +} - // 每次改动都「分组 → 展平 → 回草稿」:草稿里始终是后端要的扁平形态, - // 界面上的分组只是它的投影,两者不会各说各话。 +export interface OwnerAccountsViewProps { + /** 界面上的分组(草稿的投影);手势产生的下一份**草稿**也由它算出来。 */ + groups: OwnerAccountGroup[]; + options: EnumItem[]; + channels: AdminChannelRecord[] | null; + disabled: boolean; + /** 新草稿(扁平数组)。 */ + onChange: (next: OwnerAccount[]) => void; +} + +/** 界面本体(**无 hook**,见文件头):所有手势都走这里 → `onChange(新草稿)`。 */ +export function OwnerAccountsView({ + groups, + options, + channels, + disabled, + onChange, +}: OwnerAccountsViewProps): React.ReactNode { + // 每次改动都「分组 → 改 → 回草稿」:草稿里始终是扁平形态,界面上的分组只是它的投影, + // 两者不会各说各话。 const emit = (next: OwnerAccountGroup[]): void => onChange(flattenAccounts(next)); const patchGroup = (i: number, patch: Partial): void => @@ -156,6 +189,7 @@ function PlatformGroupRow(props: PlatformGroupRowProps): React.ReactNode { {group.accountIds.map((accountId, j) => ( +