From 4450f342d8499ac6380f7f0affeb2fe468be5e72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=91=A3=E5=AE=8F=E5=87=AF?= Date: Wed, 29 Jul 2026 21:53:32 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E5=B5=8C=E5=A5=97=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend-web/src/components/page-sidebar.tsx | 15 + .../workspace/workspace-nav-menu.tsx | 8 + frontend-web/src/core/auth/route-key-login.ts | 43 ++- frontend-web/src/core/embed/frame-context.ts | 24 ++ .../page-layout/chats-iframe-route.test.mjs | 147 +++++-- .../core/page-layout/chats-iframe-route.ts | 220 ++++++++++- .../src/core/page-layout/sidebar-menu.ts | 7 + .../pages/EightBfRoundtablePage.tsx | 20 +- frontend-web/src/env.ts | 7 + frontend-web/src/pages/IframeChatPage.tsx | 104 +---- frontend-web/src/pages/LoginPage.tsx | 98 ++++- frontend-web/src/pages/PositionRolesPage.tsx | 362 ++++++++++++++++++ frontend-web/src/pages/StrategyRoutes.tsx | 11 + frontend-web/src/pages/WorkspaceRoutes.tsx | 131 +++++-- .../src/position-roundtable/README.md | 10 +- .../src/position-roundtable/lib/positions.ts | 67 +++- .../pages/PositionRoundtablePage.tsx | 94 +++-- .../roundtable-planning/lib/business-chain.ts | 4 +- .../strategy-components/api/position-roles.ts | 181 +++++++++ offline-backend-20260512/backend/CLAUDE.md | 21 + offline-backend-20260512/backend/README.md | 21 +- .../backend/app/gateway/app.py | 6 +- .../backend/app/gateway/deps.py | 8 + .../app/gateway/position_role_defaults.py | 47 +++ .../app/gateway/routers/business_mapping.py | 30 +- .../backend/app/gateway/routers/intent.py | 27 +- .../app/gateway/routers/position_roles.py | 148 +++++++ .../gateway/routers/position_roundtable.py | 91 ++++- .../app/gateway/routers/roundtable_chains.py | 7 +- .../versions/20260729_01_position_roles.py | 45 +++ .../deerflow/persistence/models/__init__.py | 38 +- .../persistence/position_roles/__init__.py | 27 ++ .../persistence/position_roles/base.py | 30 ++ .../persistence/position_roles/memory.py | 45 +++ .../persistence/position_roles/model.py | 37 ++ .../persistence/position_roles/sql.py | 66 ++++ .../backend/tests/test_business_mapping.py | 44 ++- .../test_position_roundtable_sessions.py | 38 +- 38 files changed, 2020 insertions(+), 309 deletions(-) create mode 100644 frontend-web/src/core/embed/frame-context.ts create mode 100644 frontend-web/src/pages/PositionRolesPage.tsx create mode 100644 frontend-web/src/strategy-components/api/position-roles.ts create mode 100644 offline-backend-20260512/backend/app/gateway/position_role_defaults.py create mode 100644 offline-backend-20260512/backend/app/gateway/routers/position_roles.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/migrations/versions/20260729_01_position_roles.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/position_roles/__init__.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/position_roles/base.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/position_roles/memory.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/position_roles/model.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/position_roles/sql.py diff --git a/frontend-web/src/components/page-sidebar.tsx b/frontend-web/src/components/page-sidebar.tsx index 2d5375ec..eb1f0bd1 100644 --- a/frontend-web/src/components/page-sidebar.tsx +++ b/frontend-web/src/components/page-sidebar.tsx @@ -10,6 +10,7 @@ import { Gauge, ListTree, MousePointerClick, + BriefcaseBusiness, Settings, ShieldAlert, ShieldCheck, @@ -235,6 +236,14 @@ function SecondaryNavSidebar({ headerHidden }: { headerHidden: boolean }) { 按钮管理 )} + {isAdmin && ( + navigate("/page/strategy/admin/position-roles")} + > + + 岗位角色配置 + + )} {isAdmin && ( navigate("/page/strategy/admin/login-whitelist")} @@ -540,6 +549,12 @@ function LegacyAccordionSidebar({ 按钮管理 )} + {isAdmin && ( + navigate("/page/strategy/admin/position-roles")}> + + 岗位角色配置 + + )} {isAdmin && ( navigate("/page/strategy/admin/login-whitelist")}> diff --git a/frontend-web/src/components/workspace/workspace-nav-menu.tsx b/frontend-web/src/components/workspace/workspace-nav-menu.tsx index 69f95724..8bc0e1a1 100644 --- a/frontend-web/src/components/workspace/workspace-nav-menu.tsx +++ b/frontend-web/src/components/workspace/workspace-nav-menu.tsx @@ -234,6 +234,14 @@ export function WorkspaceNavMenu() { 岗位管理 )} + {isAdmin && ( + navigate("/page/workspace/admin/position-roles")} + > + + 岗位角色配置 + + )} {isAdmin && ( diff --git a/frontend-web/src/core/auth/route-key-login.ts b/frontend-web/src/core/auth/route-key-login.ts index 7c7601ff..0b8b157a 100644 --- a/frontend-web/src/core/auth/route-key-login.ts +++ b/frontend-web/src/core/auth/route-key-login.ts @@ -5,11 +5,25 @@ import { readRuntimeEnv } from "@/env"; * * Add a new entry here when an external system needs a short ``routeKey``. * Targets must remain internal SPA routes; LoginPage validates them again - * before navigating. + * before navigating. The optional display settings are converted into the + * existing route parameters on the destination URL. */ -export const ROUTE_KEY_MAPPINGS: Readonly> = { - a: "/page/canvas/ai-writing", - "8bf": "/page/strategy/qa/8bf-roundtable", +export interface RouteKeyMapping { + /** Internal SPA route to open after sign-in. */ + path: string; + /** Add `hideChrome=1` to hide the global header and page sidebar. */ + hideChrome?: boolean; + /** Add the existing `theme` route parameter and apply it during sign-in. */ + theme?: string; +} + +export const ROUTE_KEY_MAPPINGS: Readonly> = { + a: { path: "/page/canvas/ai-writing" }, + "8bf": { + path: "/page/strategy/qa/8bf-roundtable", + hideChrome: true, + theme: "dark-blue", + }, }; /** Login URL parameter that selects a route from ``ROUTE_KEY_MAPPINGS``. */ @@ -26,8 +40,23 @@ export function getRouteKeyLoginPassword(): string { return readRuntimeEnv("VITE_ROUTE_LOGIN_PASSWORD") ?? ""; } -export function resolveRouteKey(routeKey: string | null | undefined): string { +export function resolveRouteKeyMapping( + routeKey: string | null | undefined, +): RouteKeyMapping | undefined { const key = routeKey?.trim(); - if (!key) return ""; - return ROUTE_KEY_MAPPINGS[key] ?? ""; + if (!key) return undefined; + return ROUTE_KEY_MAPPINGS[key]; +} + +export function resolveRouteKey(routeKey: string | null | undefined): string { + const mapping = resolveRouteKeyMapping(routeKey); + if (!mapping) return ""; + + const [path, ownQuery = ""] = mapping.path.split("?"); + const query = new URLSearchParams(ownQuery); + if (mapping.hideChrome) query.set("hideChrome", "1"); + if (mapping.theme) query.set("theme", mapping.theme); + + const serializedQuery = query.toString(); + return serializedQuery ? `${path}?${serializedQuery}` : path; } diff --git a/frontend-web/src/core/embed/frame-context.ts b/frontend-web/src/core/embed/frame-context.ts new file mode 100644 index 00000000..3223cc2a --- /dev/null +++ b/frontend-web/src/core/embed/frame-context.ts @@ -0,0 +1,24 @@ +/** + * Whether the application is currently hosted by another page. + * + * This intentionally covers both a native `