From 0968545b5a1b8ff05d9e87547f56e35f2196c596 Mon Sep 17 00:00:00 2001 From: ChickenPige0n <2336983354@qq.com> Date: Sat, 11 Jul 2026 12:46:54 +0800 Subject: [PATCH] refactor(admin-web): polish Chinese UI and bits-ui controls Map roles to Chinese labels, remove ADR/spec wording from the surface, and replace native selects/checkboxes/modals with bits-ui Select, Checkbox, Switch, Dialog, Label, and Collapsible. --- .../src/lib/components/CheckboxControl.svelte | 32 ++++ hub/admin-web/src/lib/components/Modal.svelte | 38 ++--- .../src/lib/components/RoleCard.svelte | 80 ++++----- .../src/lib/components/SelectField.svelte | 71 ++++++++ .../src/lib/components/SwitchControl.svelte | 27 +++ hub/admin-web/src/lib/constants.ts | 38 ++++- hub/admin-web/src/lib/format.ts | 23 +++ hub/admin-web/src/routes/+layout.svelte | 66 ++++---- .../src/routes/admin/org/[slug]/+page.svelte | 31 ++-- .../admin/org/[slug]/members/+page.svelte | 34 ++-- .../admin/org/[slug]/models/+page.svelte | 17 +- .../admin/org/[slug]/projects/+page.svelte | 36 ++-- .../[slug]/projects/[projectId]/+page.svelte | 71 ++++---- .../admin/org/[slug]/provider/+page.svelte | 41 +++-- .../admin/org/[slug]/roles/+page.svelte | 8 +- .../admin/org/[slug]/teams/+page.svelte | 49 +++--- hub/admin-web/src/routes/app.css | 159 +++++++++++++++++- 17 files changed, 586 insertions(+), 235 deletions(-) create mode 100644 hub/admin-web/src/lib/components/CheckboxControl.svelte create mode 100644 hub/admin-web/src/lib/components/SelectField.svelte create mode 100644 hub/admin-web/src/lib/components/SwitchControl.svelte diff --git a/hub/admin-web/src/lib/components/CheckboxControl.svelte b/hub/admin-web/src/lib/components/CheckboxControl.svelte new file mode 100644 index 0000000..0cab84c --- /dev/null +++ b/hub/admin-web/src/lib/components/CheckboxControl.svelte @@ -0,0 +1,32 @@ + + + { + checked = next; + onchange?.(next); + }} +> + {#snippet children({ checked: isChecked })} + {#if isChecked} + + {/if} + {/snippet} + diff --git a/hub/admin-web/src/lib/components/Modal.svelte b/hub/admin-web/src/lib/components/Modal.svelte index a8066ac..c089d90 100644 --- a/hub/admin-web/src/lib/components/Modal.svelte +++ b/hub/admin-web/src/lib/components/Modal.svelte @@ -1,5 +1,6 @@ -{#if open} - -{/if} + + + diff --git a/hub/admin-web/src/lib/components/RoleCard.svelte b/hub/admin-web/src/lib/components/RoleCard.svelte index 8667bf9..968b11c 100644 --- a/hub/admin-web/src/lib/components/RoleCard.svelte +++ b/hub/admin-web/src/lib/components/RoleCard.svelte @@ -1,7 +1,12 @@
@@ -79,60 +81,58 @@
- + 角色 ID
- + 显示名
- - +

默认模型

+
工具白名单
- {#each Object.entries(groupedTools) as [group, tools]} -
-

{group}

-
- {#each tools as t} - - {/each} + + {#each Object.entries(groupedTools) as [group, tools]} +
+

{group}

+
+ {#each tools as t} + + {/each} +
-
- {/each} + {/each} +
- + 系统提示词
diff --git a/hub/admin-web/src/lib/components/SelectField.svelte b/hub/admin-web/src/lib/components/SelectField.svelte new file mode 100644 index 0000000..5013aa3 --- /dev/null +++ b/hub/admin-web/src/lib/components/SelectField.svelte @@ -0,0 +1,71 @@ + + + { + value = next; + onchange?.(next); + }} +> + + + + + + + + + + {#each items as item (item.value)} + + {#snippet children({ selected })} + {item.label} + {#if selected} + + {/if} + {/snippet} + + {/each} + + + + diff --git a/hub/admin-web/src/lib/components/SwitchControl.svelte b/hub/admin-web/src/lib/components/SwitchControl.svelte new file mode 100644 index 0000000..6eee458 --- /dev/null +++ b/hub/admin-web/src/lib/components/SwitchControl.svelte @@ -0,0 +1,27 @@ + + + { + checked = next; + onchange?.(next); + }} +> + + diff --git a/hub/admin-web/src/lib/constants.ts b/hub/admin-web/src/lib/constants.ts index e17e489..8eda0a3 100644 --- a/hub/admin-web/src/lib/constants.ts +++ b/hub/admin-web/src/lib/constants.ts @@ -5,18 +5,40 @@ export interface ToolOption { } export const TOOL_OPTIONS: ToolOption[] = [ - { id: 'read_file', label: '读取文件 (Read)', group: '文件' }, - { id: 'write_file', label: '写入文件 (Write)', group: '文件' }, - { id: 'list_files', label: '列目录 (Glob)', group: '文件' }, - { id: 'search_files', label: '搜索 (Grep)', group: '文件' }, + { id: 'read_file', label: '读取文件', group: '文件' }, + { id: 'write_file', label: '写入文件', group: '文件' }, + { id: 'list_files', label: '列目录', group: '文件' }, + { id: 'search_files', label: '搜索', group: '文件' }, { id: 'bash', label: 'Bash 命令', group: 'Shell' }, { id: 'cph_check', label: 'cph check', group: 'CPH' }, { id: 'cph_build', label: 'cph build', group: 'CPH' }, - { id: 'send_file', label: '发送文件 (飞书)', group: '飞书 MCP' }, - { id: 'feishu_read_context', label: '读飞书上下文', group: '飞书 MCP' }, - { id: 'feishu_download_resource', label: '下载飞书资源', group: '飞书 MCP' }, - { id: 'request_approval', label: '请求审批', group: '飞书 MCP' } + { id: 'send_file', label: '发送文件(飞书)', group: '飞书' }, + { id: 'feishu_read_context', label: '读飞书上下文', group: '飞书' }, + { id: 'feishu_download_resource', label: '下载飞书资源', group: '飞书' }, + { id: 'request_approval', label: '请求审批', group: '飞书' } ]; +/** 组织成员角色(接口枚举保持英文,界面用 orgRoleLabel) */ export const ORG_ROLES = ['OWNER', 'ADMIN', 'MEMBER'] as const; +export type OrgRole = (typeof ORG_ROLES)[number]; + +export const ORG_ROLE_LABELS: Record = { + OWNER: '所有者', + ADMIN: '管理员', + MEMBER: '成员' +}; + +/** 项目团队授权角色(接口枚举保持英文,界面用 permissionRoleLabel) */ export const PERMISSION_ROLES = ['READ', 'EDIT', 'MANAGE'] as const; +export type PermissionRole = (typeof PERMISSION_ROLES)[number]; + +export const PERMISSION_ROLE_LABELS: Record = { + READ: '只读', + EDIT: '编辑', + MANAGE: '管理' +}; + +export const PROVIDER_MODES = [ + { value: 'PLATFORM_MANAGED', label: '平台托管' }, + { value: 'BYOK', label: '自带密钥' } +] as const; diff --git a/hub/admin-web/src/lib/format.ts b/hub/admin-web/src/lib/format.ts index 2c6ff9c..80160da 100644 --- a/hub/admin-web/src/lib/format.ts +++ b/hub/admin-web/src/lib/format.ts @@ -1,3 +1,10 @@ +import { + ORG_ROLE_LABELS, + PERMISSION_ROLE_LABELS, + type OrgRole, + type PermissionRole +} from './constants'; + export function fmtDate(iso: string): string { if (!iso) return '—'; const d = new Date(iso); @@ -26,3 +33,19 @@ export function fmtCost(usd: number | null): string { export function fmtNum(n: number): string { return n.toLocaleString(); } + +export function orgRoleLabel(role: string): string { + const key = role.toUpperCase() as OrgRole; + return ORG_ROLE_LABELS[key] ?? role; +} + +export function permissionRoleLabel(role: string): string { + const key = role.toUpperCase() as PermissionRole; + return PERMISSION_ROLE_LABELS[key] ?? role; +} + +export function providerModeLabel(mode: string): string { + if (mode === 'BYOK') return '自带密钥'; + if (mode === 'PLATFORM_MANAGED') return '平台托管'; + return mode; +} diff --git a/hub/admin-web/src/routes/+layout.svelte b/hub/admin-web/src/routes/+layout.svelte index 128fa40..5b97246 100644 --- a/hub/admin-web/src/routes/+layout.svelte +++ b/hub/admin-web/src/routes/+layout.svelte @@ -5,8 +5,10 @@ import { page } from '$app/state'; import { session, loadSession, logout, redirectToLogin } from '$lib/session'; import type { OrgMembership } from '$lib/api'; + import { orgRoleLabel } from '$lib/format'; import Icon from '$lib/components/Icon.svelte'; import ToastHost from '$lib/components/ToastHost.svelte'; + import SelectField from '$lib/components/SelectField.svelte'; let { children } = $props(); let mobileNavOpen = $state(false); @@ -23,7 +25,7 @@ { key: 'projects', label: '项目', icon: 'projects' as const }, { key: 'models', label: '模型', icon: 'models' as const }, { key: 'roles', label: '角色', icon: 'roles' as const }, - { key: 'provider', label: 'Provider', icon: 'provider' as const } + { key: 'provider', label: '供应方', icon: 'provider' as const } ]; function isAdmin(org: OrgMembership): boolean { @@ -47,7 +49,6 @@ return memberships().filter(isAdmin); } - /** Org matching the URL slug (if any). */ function currentOrg(): OrgMembership | null { const slug = orgSlugFromPath(); if (!slug) return null; @@ -68,7 +69,7 @@ function navHref(key: string): string { const slug = currentOrg()?.slug ?? pickHomeOrg()?.slug; - if (!slug) return '/'; + if (!slug) return '/'; if (key === 'overview') return `/admin/org/${slug}`; return `/admin/org/${slug}/${key}`; } @@ -79,9 +80,9 @@ return navItems.find((i) => i.key === key)?.label ?? '管理后台'; } - function onOrgSwitch(e: Event) { - const sel = e.target as HTMLSelectElement; - void goto(`/admin/org/${sel.value}`); + function switchOrg(nextSlug: string) { + if (!nextSlug || nextSlug === currentOrg()?.slug) return; + void goto(`/admin/org/${nextSlug}`); } function handleLogout(e: Event) { @@ -89,14 +90,18 @@ logout(); } - // Close mobile nav on route change. + function orgSelectItems(list: OrgMembership[]) { + return list.map((o) => ({ + value: o.slug, + label: `${o.name} · ${orgRoleLabel(o.role)}` + })); + } + $effect(() => { page.url.pathname; mobileNavOpen = false; }); - // If the user has admin rights but the URL is not under a valid org slug, - // send them to their home org. Fixes false "未加入组织" on / or wrong paths. $effect(() => { if ($session.loading || !$session.me) return; const admins = adminOrgs(); @@ -109,8 +114,6 @@ return; } - // Only auto-redirect when URL is not already a matched *member* org - // (member orgs keep their own denied screen). Missing / unknown slug → home. if (!matched) { const target = `/admin/org/${admins[0].slug}`; if (page.url.pathname !== target && !page.url.pathname.startsWith(`${target}/`)) { @@ -179,21 +182,21 @@ CPH
-
Org Admin
+
组织后台
Curriculum Hub
-