From f13a1bb7feb271e02a85cf545b8b3e4e376ef32a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=91=A3=E5=AE=8F=E5=87=AF?= Date: Sun, 21 Jun 2026 17:17:19 +0800 Subject: [PATCH] =?UTF-8?q?feat(roundtable):=20=E4=B8=9A=E5=8A=A1=E9=93=BE?= =?UTF-8?q?=E6=9D=A1=E9=85=8D=E7=BD=AE=E5=A2=9E=E5=8A=A0=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=91=98=E6=9D=83=E9=99=90=EF=BC=8C=E7=AE=A1=E7=90=86=E5=91=98?= =?UTF-8?q?=E5=8F=AF=E9=85=8D=E6=89=80=E6=9C=89=E9=93=BE=E6=9D=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 后端 store/路由:scope=all(仅管理员)列全量;get/update/delete 加 is_admin 旁路,管理员可操作任意 owner 链条且不转移归属;非管理员请求 all 退化为 mine - 前端列表页/会商弹窗:管理员多「全部链条」筛选 tab,canManage=isAdmin||isMine 放开发布/设置/删除 - 编辑器:管理员可见发布按钮;按 chainId 找不到本地列表时直接 getChain 拉取(修复管理员从「全部链条」进编辑器空白),并修掉自身无链条时加载卡死的边界 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/roundtable-planning/api/chains.ts | 11 ++++-- .../components/BusinessChainListDialog.tsx | 24 ++++++++----- .../pages/BusinessChainEditorPage.tsx | 29 +++++++++++---- .../pages/BusinessChainListPage.tsx | 23 ++++++++---- .../app/gateway/routers/roundtable_chains.py | 35 ++++++++++++++---- .../persistence/roundtable_chains/sql.py | 36 +++++++++++-------- 6 files changed, 113 insertions(+), 45 deletions(-) diff --git a/frontend-web/src/roundtable-planning/api/chains.ts b/frontend-web/src/roundtable-planning/api/chains.ts index 6138efef..b57bf26d 100644 --- a/frontend-web/src/roundtable-planning/api/chains.ts +++ b/frontend-web/src/roundtable-planning/api/chains.ts @@ -54,8 +54,13 @@ export interface ChainUpdatePayload { isPublic?: boolean; } -/** 列表筛选范围:`mine` = 我的;`public` = 所有已发布的公共链条。 */ -export type ChainScope = "mine" | "public"; +/** + * 列表筛选范围: + * - `mine` = 我的; + * - `public` = 所有已发布的公共链条; + * - `all` = 所有账号的全部链条(**仅管理员**;后端对非管理员请求 `all` 退化为 `mine`)。 + */ +export type ChainScope = "mine" | "public" | "all"; const BASE = () => `${getBackendBaseURL()}/api/roundtable-chains`; @@ -146,7 +151,7 @@ function fromUpdate(p: ChainUpdatePayload) { * - `"public"`:所有已发布的公共链条(任何账号可见 / 可选入研讨)。 */ export async function listChains(scope: ChainScope = "mine"): Promise { - const url = scope === "public" ? `${BASE()}?scope=public` : BASE(); + const url = scope === "mine" ? BASE() : `${BASE()}?scope=${scope}`; const res = await apiFetch(url, { method: "GET" }); const data = await readJson<{ chains: ServerChain[] }>(res, "list chains"); return (data.chains ?? []).map(toChain); diff --git a/frontend-web/src/roundtable-planning/components/BusinessChainListDialog.tsx b/frontend-web/src/roundtable-planning/components/BusinessChainListDialog.tsx index 1b813bda..04353b30 100644 --- a/frontend-web/src/roundtable-planning/components/BusinessChainListDialog.tsx +++ b/frontend-web/src/roundtable-planning/components/BusinessChainListDialog.tsx @@ -31,6 +31,7 @@ import { import { cn } from "@/lib/utils"; import { useDesensitizeSubtree } from "@/roundtable-planning/lib/use-desensitize-subtree"; +import { getStoredAuth } from "@/core/auth"; import { listAgents } from "@/core/agents"; import { listChains, setChainPublic, type ChainScope } from "../api/chains"; @@ -71,7 +72,9 @@ export function BusinessChainListBody({ const [chains, setChains] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - // 筛选:我的 / 公共(已发布)。 + // 管理员:额外多一个「全部」筛选,且可编辑 / 发布任意账号的链条。 + const isAdmin = getStoredAuth()?.system_role === "admin"; + // 筛选:我的 / 公共(已发布)/(管理员)全部。 const [scope, setScope] = useState("mine"); const [publishingId, setPublishingId] = useState(null); // 实时名称表(id 小写 → 最新 name):用于把席位的落库快照名刷新成最新(agent 改名同步)。 @@ -147,9 +150,9 @@ export function BusinessChainListBody({ return ( <> - {/* 筛选:我的 / 公共 */} + {/* 筛选:我的 / 公共 /(管理员)全部 */}
- {(["mine", "public"] as const).map((s) => ( + {((isAdmin ? ["mine", "public", "all"] : ["mine", "public"]) as ChainScope[]).map((s) => ( ))}
@@ -195,6 +198,7 @@ export function BusinessChainListBody({ onConfigure(chain.id)} @@ -245,18 +249,20 @@ export function BusinessChainListDialog({ function ChainRow({ chain, + canManage, nameById, publishing, onSettings, onTogglePublic, }: { chain: BusinessChain; + /** 能否编辑 / 发布(本人链条 或 管理员)。 */ + canManage: boolean; nameById: Map; publishing: boolean; onSettings: () => void; onTogglePublic: () => void; }) { - const isMine = chain.isMine !== false; // 配置者用户名:后端返回 email,展示时取 @ 前的本地部分更像「用户名」。 const ownerName = chain.ownerName ? chain.ownerName.split("@")[0] : ""; return ( @@ -274,8 +280,8 @@ function ChainRow({ )}
- {/* 发布 / 取消发布:仅本人的链条可操作。 */} - {isMine && ( + {/* 发布 / 取消发布:本人的链条或管理员可操作。 */} + {canManage && (
- {/* 操作按钮:仅本人链条可发布 / 设置 / 删除(公共里别人的链条只读浏览)。 */} - {isMine && ( + {/* 操作按钮:本人链条或管理员可发布 / 设置 / 删除(普通用户看别人的链条只读浏览)。 */} + {canManage && (