From 748fc43455ce3d094641811bdb278500f7e50d14 Mon Sep 17 00:00:00 2001 From: Kinyoo Date: Wed, 12 Aug 2026 17:40:10 +0800 Subject: [PATCH 1/2] refactor(permission): one level menu and one dialog shell across the surfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The member list, the create-page draft and the two "新增授权" dialogs had each grown their own version of the same two things — so the level control looked different depending on where you opened it, and the draft picker was a look-alike of the share dialog rather than the same dialog. - PermissionLevelMenu extracts the list's dropdown (levels, separator, destructive 移除) and the draft editor adopts it, dropping its RelationSelect + separate delete icon button. Removal now always lives inside the menu, never as a button beside it; a row with neither permission renders the plain label at the same width, so the column never jitters. - permissionDialogStyles holds the share dialog's chrome verbatim — shell sizing (80vh card / full-screen under 768px), subject tabs, 包含子部门 toggle, footer captions — and the draft picker consumes it instead of re-approximating it. - Empty authorization lists get their own copy (new key, three languages) instead of borrowing the search-result empty text. Co-Authored-By: Claude Opus 5 --- .../permission/PermissionDraftEditor.tsx | 42 +++---- .../permission/PermissionDraftPanel.tsx | 23 ++-- .../PermissionDraftPickerDialog.tsx | 55 ++++++--- .../permission/PermissionLevelMenu.tsx | 106 ++++++++++++++++++ .../permission/PermissionListTab.tsx | 79 +++---------- .../permission/UnifiedPermissionControls.tsx | 2 +- .../permission/permissionDialogStyles.ts | 42 +++++++ .../client/src/locales/en/translation.json | 1 + .../client/src/locales/ja/translation.json | 1 + .../src/locales/zh-Hans/translation.json | 1 + .../ChannelBusinessSettings.tsx | 12 +- .../SpaceDetail/KnowledgeSpaceShareDialog.tsx | 30 +++-- .../KnowledgeSpaceSettingsPage.tsx | 2 +- 13 files changed, 266 insertions(+), 130 deletions(-) create mode 100644 src/frontend/client/src/components/permission/PermissionLevelMenu.tsx create mode 100644 src/frontend/client/src/components/permission/permissionDialogStyles.ts diff --git a/src/frontend/client/src/components/permission/PermissionDraftEditor.tsx b/src/frontend/client/src/components/permission/PermissionDraftEditor.tsx index 353a738d36..baba3f4bf4 100644 --- a/src/frontend/client/src/components/permission/PermissionDraftEditor.tsx +++ b/src/frontend/client/src/components/permission/PermissionDraftEditor.tsx @@ -1,8 +1,5 @@ -import { Button } from "@bisheng/ui"; -import { Outlined } from "bisheng-icons"; import { useLocalize } from "~/hooks"; -import { Tooltip, TooltipContent, TooltipTrigger } from "~/components/ui/Tooltip2"; -import { RelationSelect } from "./RelationSelect"; +import { PermissionLevelMenu } from "./PermissionLevelMenu"; import type { RelationModelOption } from "./RelationSelect"; import { getPermissionDraftRowKey, @@ -58,6 +55,10 @@ export function PermissionDraftEditor({ && capabilities.canChangeRelation && relationModels.length > 0; const canRemove = !row.immutableCreator && capabilities.canRemove; + const activeModelId = row.modelId ?? row.relation; + const relationLabel = + capabilities.relationModels.find((model) => model.id === activeModelId)?.name + ?? localize(`com_permission.level_${row.relation}`); return (
@@ -68,34 +69,19 @@ export function PermissionDraftEditor({ {row.subjectName}
{row.immutableCreator ? ( - {localize("creator")} + + {localize("creator")} + ) : ( - handleRelationChange(row, modelId)} + handleRelationChange(row, modelId)} + onRemove={canRemove ? () => handleRemove(row) : undefined} /> )} - {canRemove && ( - - - - - {localize("com_permission.remove")} - - )} ); })} diff --git a/src/frontend/client/src/components/permission/PermissionDraftPanel.tsx b/src/frontend/client/src/components/permission/PermissionDraftPanel.tsx index 6010c933d5..361659a4c6 100644 --- a/src/frontend/client/src/components/permission/PermissionDraftPanel.tsx +++ b/src/frontend/client/src/components/permission/PermissionDraftPanel.tsx @@ -3,6 +3,12 @@ import type { SubjectType } from "~/api/permission"; import { EmptyStateIllustration } from "~/components/illustrations"; import { useLocalize } from "~/hooks"; import { PermissionDraftEditor, type PermissionDraftEditorCapabilities } from "./PermissionDraftEditor"; +import { + SUBJECT_TAB_BUTTON_ACTIVE_CLASS, + SUBJECT_TAB_BUTTON_CLASS, + SUBJECT_TAB_BUTTON_INACTIVE_CLASS, + SUBJECT_TAB_LIST_CLASS, +} from "./permissionDialogStyles"; import { getPermissionDraftRowKey, type PermissionDraftRow } from "./usePermissionDraft"; const SUBJECT_TYPES: SubjectType[] = ["user", "department", "user_group"]; @@ -43,15 +49,15 @@ export function PermissionDraftPanel({ {localize("com_unified_permission.authorization")}
-
+
{SUBJECT_TYPES.map((type) => (
{visibleRows.length === 0 ? ( -
+
+

+ {localize("com_unified_permission.authorization_empty")} +

) : ( - - - {localize("com_unified_permission.add_authorization")} + + + + {localize("com_unified_permission.add_authorization")} + -
- - {localize("com_permission.subject_user")} - +
+ + + {localize("com_permission.subject_user")} + + {localize("com_permission.subject_department")} - + {localize("com_permission.subject_user_group")} {subjectType === "department" && ( -
- + - + - + - -
- {localize("com_permission.uniform_grant")} + +
+ + {localize("com_permission.uniform_grant")} +
); diff --git a/src/frontend/client/src/components/permission/UnifiedPermissionControls.tsx b/src/frontend/client/src/components/permission/UnifiedPermissionControls.tsx index ac75958c47..3ddb87a842 100644 --- a/src/frontend/client/src/components/permission/UnifiedPermissionControls.tsx +++ b/src/frontend/client/src/components/permission/UnifiedPermissionControls.tsx @@ -26,7 +26,7 @@ export function SettingsSectionHeader({ }: SettingsSectionHeaderProps) { const Icon = SECTION_ICONS[kind]; return ( -
+
{title}
diff --git a/src/frontend/client/src/components/permission/permissionDialogStyles.ts b/src/frontend/client/src/components/permission/permissionDialogStyles.ts new file mode 100644 index 0000000000..429c313f33 --- /dev/null +++ b/src/frontend/client/src/components/permission/permissionDialogStyles.ts @@ -0,0 +1,42 @@ +/** + * Shared chrome for the permission dialogs. + * + * Extracted verbatim from KnowledgeSpaceShareDialog, which shipped the original + * "新增授权" dialog. The unified-permission draft picker reuses these so both + * dialogs stay pixel-identical instead of drifting into two look-alikes. + */ + +/** Dialog shell: fixed 80vh card on desktop, full-screen sheet under 768px. */ +export const PERMISSION_DIALOG_CONTENT_CLASS = + "!flex h-[80vh] max-h-[800px] w-[calc(100vw-80px)] max-w-[800px] min-w-0 flex-col gap-0 overflow-hidden p-5 max-[768px]:fixed max-[768px]:inset-0 max-[768px]:h-[100dvh] max-[768px]:max-h-[100dvh] max-[768px]:w-full max-[768px]:max-w-none max-[768px]:translate-x-0 max-[768px]:translate-y-0 max-[768px]:rounded-none max-[768px]:p-4"; + +/** Subject-type switcher: bordered pill group, brand-tinted active segment. */ +export const SUBJECT_TAB_LIST_CLASS = + "w-fit shrink-0 rounded-md border border-[#ECECEC] bg-white p-[3px] shadow-none"; + +export const SUBJECT_TAB_TRIGGER_CLASS = + "min-w-0 rounded-[4px] px-3 py-0.5 text-[14px] font-normal leading-[22px] text-[#818181] shadow-none data-[state=active]:bg-[rgb(var(--brand-500)/0.15)] data-[state=active]:font-medium data-[state=active]:text-blue-500 data-[state=active]:shadow-none"; + +/** + * Same switcher rendered with plain buttons instead of Radix Tabs — used where + * the active segment is driven by external state. Wrap them in + * `inline-flex items-center justify-center ${SUBJECT_TAB_LIST_CLASS}`. + */ +export const SUBJECT_TAB_BUTTON_CLASS = + "min-w-0 rounded-[4px] px-3 py-0.5 text-[14px] leading-[22px] transition-colors"; + +export const SUBJECT_TAB_BUTTON_ACTIVE_CLASS = + "bg-[rgb(var(--brand-500)/0.15)] font-medium text-blue-500"; + +export const SUBJECT_TAB_BUTTON_INACTIVE_CLASS = "font-normal text-[#818181]"; + +/** "包含子部门" toggle sitting next to the tab group. */ +export const INCLUDE_CHILDREN_LABEL_CLASS = + "flex shrink-0 cursor-pointer items-center gap-2 text-[14px] leading-[22px] text-[#212121]"; + +export const INCLUDE_CHILDREN_CHECKBOX_CLASS = + "border-[#D9D9D9] data-[state=checked]:border-primary data-[state=indeterminate]:border-primary"; + +/** Muted caption used by the footer labels ("已选用户:", "统一授权:"). */ +export const PERMISSION_FOOTER_LABEL_CLASS = + "shrink-0 text-[14px] font-normal leading-[22px] text-[#999999]"; diff --git a/src/frontend/client/src/locales/en/translation.json b/src/frontend/client/src/locales/en/translation.json index f7ef802cad..20d7824b52 100644 --- a/src/frontend/client/src/locales/en/translation.json +++ b/src/frontend/client/src/locales/en/translation.json @@ -1867,6 +1867,7 @@ "add_authorization": "Add permission", "advanced_settings": "Advanced settings", "authorization": "Permissions", + "authorization_empty": "No authorized subjects for this type yet", "basic_settings": "Basic settings", "cancel": "Cancel", "confirm_create": "Create", diff --git a/src/frontend/client/src/locales/ja/translation.json b/src/frontend/client/src/locales/ja/translation.json index 5e72ae11bb..8018677f15 100644 --- a/src/frontend/client/src/locales/ja/translation.json +++ b/src/frontend/client/src/locales/ja/translation.json @@ -1790,6 +1790,7 @@ "add_authorization": "権限を追加", "advanced_settings": "詳細設定", "authorization": "権限", + "authorization_empty": "現在のタイプに権限対象はまだありません", "basic_settings": "基本設定", "cancel": "キャンセル", "confirm_create": "作成を確定", diff --git a/src/frontend/client/src/locales/zh-Hans/translation.json b/src/frontend/client/src/locales/zh-Hans/translation.json index ebed23da2c..b50722f3c5 100644 --- a/src/frontend/client/src/locales/zh-Hans/translation.json +++ b/src/frontend/client/src/locales/zh-Hans/translation.json @@ -1796,6 +1796,7 @@ "add_authorization": "新增授权", "advanced_settings": "高级设置", "authorization": "授权", + "authorization_empty": "当前类型下暂无授权对象", "basic_settings": "基础设置", "cancel": "取消", "confirm_create": "确认创建", diff --git a/src/frontend/client/src/pages/Subscription/ChannelSettings/ChannelBusinessSettings.tsx b/src/frontend/client/src/pages/Subscription/ChannelSettings/ChannelBusinessSettings.tsx index 90e0f6beb3..7c633bb40f 100644 --- a/src/frontend/client/src/pages/Subscription/ChannelSettings/ChannelBusinessSettings.tsx +++ b/src/frontend/client/src/pages/Subscription/ChannelSettings/ChannelBusinessSettings.tsx @@ -1,4 +1,5 @@ -import { Outlined } from "bisheng-icons"; +// bisheng-icons has no boxed plus; SquarePlus matches the sibling "add condition" button. +import { SquarePlus } from "lucide-react"; import type { ComponentProps } from "react"; import { NotificationSeverity } from "~/common"; import { @@ -183,7 +184,7 @@ export function ChannelBusinessSettings({ /> {form.createSubChannel && (
{form.subChannels.map((subChannel) => ( @@ -219,9 +220,12 @@ export function ChannelBusinessSettings({ )} diff --git a/src/frontend/client/src/pages/knowledge/SpaceDetail/KnowledgeSpaceShareDialog.tsx b/src/frontend/client/src/pages/knowledge/SpaceDetail/KnowledgeSpaceShareDialog.tsx index 93575aa52e..d9970c418c 100644 --- a/src/frontend/client/src/pages/knowledge/SpaceDetail/KnowledgeSpaceShareDialog.tsx +++ b/src/frontend/client/src/pages/knowledge/SpaceDetail/KnowledgeSpaceShareDialog.tsx @@ -1,4 +1,14 @@ import { useCallback, useEffect, useMemo, useState } from "react"; +import { + INCLUDE_CHILDREN_CHECKBOX_CLASS, + INCLUDE_CHILDREN_LABEL_CLASS, + PERMISSION_DIALOG_CONTENT_CLASS, + SUBJECT_TAB_BUTTON_ACTIVE_CLASS, + SUBJECT_TAB_BUTTON_CLASS, + SUBJECT_TAB_BUTTON_INACTIVE_CLASS, + SUBJECT_TAB_LIST_CLASS, + SUBJECT_TAB_TRIGGER_CLASS, +} from "~/components/permission/permissionDialogStyles"; import { PermissionGrantTab } from "~/components/permission/PermissionGrantTab"; import { PermissionListTab } from "~/components/permission/PermissionListTab"; import { @@ -113,12 +123,12 @@ export function KnowledgeSpaceShareDialog({ className="flex min-h-0 flex-1 flex-col" >
- + {SUBJECT_TABS.map((tab) => ( {localize(tab.labelKey)} @@ -160,7 +170,7 @@ export function KnowledgeSpaceShareDialog({ return ( <> - + {dialogTitle} @@ -172,7 +182,7 @@ export function KnowledgeSpaceShareDialog({ - + {localize("com_permission.tab_grant")} - {resourceName} @@ -181,16 +191,16 @@ export function KnowledgeSpaceShareDialog({
-
+
{SUBJECT_TABS.map((tab) => (
{grantSubjectType === "department" && ( -