From a3419cf5da7275deda9c34fe343fbf62da1bf2d5 Mon Sep 17 00:00:00 2001 From: Stev_Wang <304865932@qq.com> Date: Tue, 3 Nov 2026 13:56:35 +0800 Subject: [PATCH] =?UTF-8?q?refactor(web):=20=E6=94=B6=E6=95=9B=E8=A7=92?= =?UTF-8?q?=E8=89=B2=E9=A1=B5=E6=9D=83=E9=99=90=E7=82=B9=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 背景:列表页页头「权限点目录」与详情面板「查看权限点目录」和编辑器权限点 Tab 信息重复,抽屉只读、无搜索、无操作,且页头出现两个并列动作。 改动: - 移除列表页页头目录按钮与 PermissionCatalogDrawer 组件及对应样式; - 详情面板删除「查看权限点目录」入口,保留「已选 n / 30」信息; - 权限查看统一走编辑器「权限点」Tab(只读用户也可进入查看); - 同步 A2 实施报告与设计说明。 验证:pnpm format:check / lint / typecheck / test 全绿;5173 实测页头仅保留「新建角色」,详情面板无目录入口。 --- .../system/roles/PermissionCatalogDrawer.tsx | 97 ------------- .../system/roles/RoleDetailPanel.spec.tsx | 1 - .../pages/system/roles/RoleDetailPanel.tsx | 14 +- apps/web/src/pages/system/roles/RolesPage.tsx | 23 +--- apps/web/src/styles/roles.css | 129 ------------------ ...灵犀_A2角色权限与数据范围_实施报告_V0.1.md | 2 +- 6 files changed, 3 insertions(+), 263 deletions(-) delete mode 100644 apps/web/src/pages/system/roles/PermissionCatalogDrawer.tsx diff --git a/apps/web/src/pages/system/roles/PermissionCatalogDrawer.tsx b/apps/web/src/pages/system/roles/PermissionCatalogDrawer.tsx deleted file mode 100644 index e46ed8c..0000000 --- a/apps/web/src/pages/system/roles/PermissionCatalogDrawer.tsx +++ /dev/null @@ -1,97 +0,0 @@ -/* eslint-disable max-lines-per-function -- 单组件按原型区块展开,拆分反而降低可读性 */ -import { Button, SideSheet } from '@douyinfe/semi-ui-19'; -import type { PermissionTree, RoleDetail } from '@lingxi/shared'; -import { IconCheck, IconStar } from '@lingxi/ui/icons'; -import type { ReactNode } from 'react'; - -import { scopeLabelOf } from './role-display'; - -export interface PermissionCatalogDrawerProps { - visible: boolean; - tree: PermissionTree | undefined; - role: RoleDetail | null; - onClose: () => void; -} - -/** 权限点目录抽屉:只读展示 30 个权限点与当前角色的持有情况 */ -export function PermissionCatalogDrawer({ - visible, - tree, - role, - onClose, -}: PermissionCatalogDrawerProps): ReactNode { - const granted = new Set([ - ...(role?.permissions ?? []), - ...(role?.specialPermissions ?? []), - ]); - return ( - - 权限点变更即时对绑定用户生效 - - - } - > -
-

- {tree?.totalPermissions ?? 30} 个权限点 · {tree?.specialPermissions.length ?? 6}{' '} - 个特殊权限;★ 默认关闭、需单独授予。 - {role === null - ? ' 当前角色尚未加载。' - : ` 当前勾选为「${role.name}」持有(${String(role.permissionCount + role.specialPermissionCount)} 个)。`} -

- {tree === undefined ? ( -

权限点目录加载中…

- ) : ( - tree.modules.map((module) => ( -
-
- {module.moduleLabel} - - {module.permissions.filter((permission) => granted.has(permission.code)).length} /{' '} - {module.permissions.length} - -
- {module.permissions.map((permission) => { - const owned = granted.has(permission.code); - return ( -
- - - - {permission.name} - {permission.isSpecial ? ( - ★ 特殊 - ) : null} - - {permission.description} - - {permission.code} -
- ); - })} -
- )) - )} -
- - - 特殊权限 6 项与普通权限分开授予,不随角色复制带入;当前角色数据范围为「 - {role === null ? '—' : scopeLabelOf(role.dataScope.type)}」。 - -
-
-
- ); -} diff --git a/apps/web/src/pages/system/roles/RoleDetailPanel.spec.tsx b/apps/web/src/pages/system/roles/RoleDetailPanel.spec.tsx index 7ba185a..5255e43 100644 --- a/apps/web/src/pages/system/roles/RoleDetailPanel.spec.tsx +++ b/apps/web/src/pages/system/roles/RoleDetailPanel.spec.tsx @@ -125,7 +125,6 @@ function renderPanel(overrides: Partial[0]> = onRename={vi.fn()} onDisable={vi.fn()} onEnable={vi.fn()} - onOpenCatalog={vi.fn()} {...overrides} />, ); diff --git a/apps/web/src/pages/system/roles/RoleDetailPanel.tsx b/apps/web/src/pages/system/roles/RoleDetailPanel.tsx index cefe861..790b7cc 100644 --- a/apps/web/src/pages/system/roles/RoleDetailPanel.tsx +++ b/apps/web/src/pages/system/roles/RoleDetailPanel.tsx @@ -29,7 +29,6 @@ export interface RoleDetailPanelProps { onRename: (role: RoleDetail) => void; onDisable: (role: RoleDetail) => void; onEnable: (role: RoleDetail) => void; - onOpenCatalog: (role: RoleDetail) => void; } function RoleAvatar({ status }: { status: 'active' | 'disabled' }): ReactNode { @@ -114,11 +113,9 @@ function DetailHead({ function PermissionSummary({ role, tree, - onOpenCatalog, }: { role: RoleDetail; tree: PermissionTree | undefined; - onOpenCatalog: () => void; }): ReactNode { const selected = [...role.permissions, ...role.specialPermissions]; const modules = tree === undefined ? [] : summarizePermissionModules(tree, selected); @@ -130,9 +127,6 @@ function PermissionSummary({ 已选 {role.permissionCount + role.specialPermissionCount} /{' '} {tree?.totalPermissions ?? '—'} - {modules.length === 0 ? (

权限点目录加载中…

@@ -326,13 +320,7 @@ export function RoleDetailPanel(props: RoleDetailPanelProps): ReactNode { {formatRoleDateTime(detail.updatedAt)} - { - props.onOpenCatalog(detail); - }} - /> + diff --git a/apps/web/src/pages/system/roles/RolesPage.tsx b/apps/web/src/pages/system/roles/RolesPage.tsx index 152a9e3..45c8d88 100644 --- a/apps/web/src/pages/system/roles/RolesPage.tsx +++ b/apps/web/src/pages/system/roles/RolesPage.tsx @@ -2,7 +2,7 @@ import { Button, Input, Switch } from '@douyinfe/semi-ui-19'; import { Permission, type RoleDetail, type RoleListItem } from '@lingxi/shared'; import { notifySuccess } from '@lingxi/ui'; -import { IconAdd, IconSearch, IconShield } from '@lingxi/ui/icons'; +import { IconAdd, IconSearch } from '@lingxi/ui/icons'; import type { ReactNode } from 'react'; import { useMemo, useState } from 'react'; import { useNavigate } from 'react-router'; @@ -19,7 +19,6 @@ import { import { usePermission } from '@/hooks/use-permission'; import { roleDetailPath, ROUTES } from '@/routes'; -import { PermissionCatalogDrawer } from './PermissionCatalogDrawer'; import { CopyRoleDialog, DeleteRoleDialog, @@ -129,7 +128,6 @@ export function RolesPage(): ReactNode { const [filters, setFilters] = useState(DEFAULT_ROLE_LIST_FILTERS); const [selectedId, setSelectedId] = useState(null); const [dialog, setDialog] = useState(null); - const [catalogOpen, setCatalogOpen] = useState(false); const filteredRoles = useMemo(() => filterRoles(roles, filters), [roles, filters]); const stats = useMemo(() => summarizeRoles(roles), [roles]); const effectiveSelectedId = filteredRoles.some((role) => role.id === selectedId) @@ -176,14 +174,6 @@ export function RolesPage(): ReactNode {

配置功能权限、数据范围与特殊权限,变更即时生效并留痕

-
@@ -383,14 +370,6 @@ export function RolesPage(): ReactNode { ); }} /> - { - setCatalogOpen(false); - }} - /> ); } diff --git a/apps/web/src/styles/roles.css b/apps/web/src/styles/roles.css index 499144e..40b0aeb 100644 --- a/apps/web/src/styles/roles.css +++ b/apps/web/src/styles/roles.css @@ -1122,135 +1122,6 @@ word-break: break-all; } -/* ---------- 权限点目录抽屉 ---------- */ - -.lx-role-catalog { - display: flex; - flex-direction: column; - gap: var(--lx-space-3); - padding-bottom: var(--lx-space-4); -} - -.lx-role-catalog__sub { - font-size: var(--lx-font-size-xs); - line-height: 18px; - color: var(--lx-color-text-secondary); -} - -.lx-role-catalog__module { - padding-bottom: 8px; - border-bottom: 1px solid var(--lx-color-border); -} - -.lx-role-catalog__module:last-of-type { - border-bottom: none; -} - -.lx-role-catalog__module-head { - display: flex; - align-items: center; - justify-content: space-between; - padding: 8px 0 4px; - font-size: var(--lx-font-size-sm); - font-weight: 600; - color: var(--lx-color-text-primary); -} - -.lx-role-catalog__module-head span { - font-size: var(--lx-font-size-xs); - font-weight: 400; - color: var(--lx-color-text-tertiary); - font-variant-numeric: tabular-nums; -} - -.lx-role-catalog__row { - display: flex; - align-items: flex-start; - gap: var(--lx-space-2); - padding: 7px 0; - border-bottom: 1px solid var(--lx-color-border); -} - -.lx-role-catalog__row:last-child { - border-bottom: none; -} - -.lx-role-catalog__check { - display: inline-flex; - width: 15px; - height: 15px; - flex: 0 0 15px; - align-items: center; - justify-content: center; - margin-top: 2px; - border: 1px solid var(--lx-color-border-strong); - border-radius: 3px; - color: var(--lx-color-on-brand); -} - -.lx-role-catalog__row.is-granted .lx-role-catalog__check { - border-color: var(--lx-color-brand); - background: var(--lx-color-brand); -} - -.lx-role-catalog__copy { - display: flex; - min-width: 0; - flex: 1; - flex-direction: column; - gap: 1px; -} - -.lx-role-catalog__name { - display: flex; - align-items: center; - gap: 6px; - font-size: var(--lx-font-size-sm); - color: var(--lx-color-text-primary); -} - -.lx-role-catalog__desc { - font-size: var(--lx-font-size-xs); - color: var(--lx-color-text-tertiary); -} - -.lx-role-catalog__code { - flex: 0 0 auto; - padding-top: 2px; - font-family: var(--lx-font-mono); - font-size: 11px; - color: var(--lx-color-text-tertiary); - white-space: nowrap; -} - -.lx-role-catalog__note { - display: flex; - align-items: flex-start; - gap: var(--lx-space-2); - padding: 10px 12px; - border-radius: var(--lx-radius-md); - background: var(--lx-color-bg-subtle); - font-size: var(--lx-font-size-xs); - line-height: 18px; - color: var(--lx-color-text-secondary); -} - -.lx-role-catalog__foot { - display: flex; - width: 100%; - align-items: center; - justify-content: space-between; - font-size: var(--lx-font-size-xs); - color: var(--lx-color-text-tertiary); -} - -.lx-role-catalog__empty { - padding: var(--lx-space-5); - font-size: var(--lx-font-size-xs); - color: var(--lx-color-text-tertiary); - text-align: center; -} - /* ---------- 响应式 ---------- */ @media (width <= 1280px) { diff --git a/docs/开发计划/灵犀_A2角色权限与数据范围_实施报告_V0.1.md b/docs/开发计划/灵犀_A2角色权限与数据范围_实施报告_V0.1.md index 6e7ff95..2167703 100644 --- a/docs/开发计划/灵犀_A2角色权限与数据范围_实施报告_V0.1.md +++ b/docs/开发计划/灵犀_A2角色权限与数据范围_实施报告_V0.1.md @@ -56,7 +56,7 @@ H1 权限门口径(用户确认):权限点保留现有 `read/write` 编码 - 路由:`/system/roles`(`role:view`,左 400 角色列表 + 右详情)、`/system/roles/:id`(编辑器,Tab:基本信息/权限点/数据范围/特殊权限 + 底部唯一保存)、`/system/roles/new`(`role:manage`,左模板 + 右表单);导航 `/system/roles` 权限由 `ROLE_MANAGE` 调整为 `ROLE_VIEW`;面包屑补角色三页。 - API/Query:`api/roles.ts`、`hooks/queries/use-roles.ts`、`query-keys`、`endpoints`;全部用 shared Zod schema 解析。 -- 页面能力:权限树 10 模块/30 点三态 + 搜索 + 展开;特殊权限 6 项二次确认;数据范围六类单选 + 客户组空态;模板预填(特殊不带入);复制/停用/删除/系统角色改名;保存前影响确认;SoD 实时冲突与 40304 回填;未保存离开拦截;权限点目录抽屉;只读态;加载/空/错误态;1024 适配。 +- 页面能力:权限树 10 模块/30 点三态 + 搜索 + 展开;特殊权限 6 项二次确认;数据范围六类单选 + 客户组空态;模板预填(特殊不带入);复制/停用/删除/系统角色改名;保存前影响确认;SoD 实时冲突与 40304 回填;未保存离开拦截;只读态;加载/空/错误态;1024 适配。 - 样式:`styles/roles.css`、`styles/role-editor.css`,复用 `--lx-*` token 与现有 `.lx-*` 组件类;未新增依赖、未内联 SVG。 ### 2.5 UI 原型与决策