refactor(web): 编辑角色主操作移入详情页头并移除页脚
ci / 质量门禁 (push) Canceled after 0s

背景:角色详情页把「编辑角色」放在右下角页脚,与页头的复制/停用分离,主操作位置突兀;页脚只剩一句备注也浪费垂直空间。

改动:
- 页头操作顺序调整为:复制 → 停用/启用(系统角色为改名)→ 编辑角色(实心主按钮);
- 只读用户显示「查看角色」,系统角色也显示「查看角色」(仅名称可改,避免编辑语义过强);
- 移除详情页脚及编辑按钮,权限摘要与绑定用户获得更多高度;
- 更新只读态单测断言。

验证:format:check / lint / typecheck / test 全绿;5173 实测页头操作与主按钮位置正确。
This commit is contained in:
Stev_Wang committed 2026-11-03 14:16:39 +08:00
1 parent f5a8afe835
commit f4da1f3c02
3 files changed
+65 -73

No files matched your search

@@ -160,7 +160,8 @@ describe('角色详情面板', () => {
renderPanel({ canManage: false }); renderPanel({ canManage: false });
expect(screen.queryByText('复制')).toBeNull(); expect(screen.queryByText('复制')).toBeNull();
expect(screen.queryByText('停用')).toBeNull(); expect(screen.queryByText('停用')).toBeNull();
expect(screen.getByText('编辑角色')).toBeTruthy(); expect(screen.queryByText('编辑角色')).toBeNull();
expect(screen.getByText('查看角色')).toBeTruthy();
}); });
it('未选择角色时展示空态', () => { it('未选择角色时展示空态', () => {
@@ -2,7 +2,15 @@
import { Button } from '@douyinfe/semi-ui-19'; import { Button } from '@douyinfe/semi-ui-19';
import type { PermissionTree, RoleDetail } from '@lingxi/shared'; import type { PermissionTree, RoleDetail } from '@lingxi/shared';
import { ErrorState, PageEmpty, PageSkeleton } from '@lingxi/ui'; import { ErrorState, PageEmpty, PageSkeleton } from '@lingxi/ui';
import { IconCopy, IconEdit, IconPause, IconPlay, IconShield, IconStar } from '@lingxi/ui/icons'; import {
IconCopy,
IconEdit,
IconPause,
IconPlay,
IconShield,
IconStar,
IconView,
} from '@lingxi/ui/icons';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { useState } from 'react'; import { useState } from 'react';
@@ -47,11 +55,15 @@ function RoleAvatar({ status }: { status: 'active' | 'disabled' }): ReactNode {
function DetailHead({ function DetailHead({
role, role,
canManage, canManage,
onEdit,
onCopy, onCopy,
onRename, onRename,
onDisable, onDisable,
onEnable, onEnable,
}: Pick<RoleDetailPanelProps, 'canManage' | 'onCopy' | 'onRename' | 'onDisable' | 'onEnable'> & { }: Pick<
RoleDetailPanelProps,
'canManage' | 'onEdit' | 'onCopy' | 'onRename' | 'onDisable' | 'onEnable'
> & {
role: RoleDetail; role: RoleDetail;
}): ReactNode { }): ReactNode {
return ( return (
@@ -65,47 +77,59 @@ function DetailHead({
</div> </div>
<p className="lx-role-detail__desc">{role.description}</p> <p className="lx-role-detail__desc">{role.description}</p>
</div> </div>
{canManage ? ( <div className="lx-role-detail__actions">
<div className="lx-role-detail__actions"> {canManage ? (
<Button <>
icon={<IconCopy size={14} />}
onClick={() => {
onCopy(role);
}}
>
复制
</Button>
{role.isSystem ? (
<Button <Button
icon={<IconEdit size={14} />} icon={<IconCopy size={14} />}
onClick={() => { onClick={() => {
onRename(role); onCopy(role);
}} }}
> >
改名 复制
</Button> </Button>
) : role.status === 'active' ? ( {role.isSystem ? (
<Button <Button
type="danger" icon={<IconEdit size={14} />}
icon={<IconPause size={14} />} onClick={() => {
onClick={() => { onRename(role);
onDisable(role); }}
}} >
> 改名
停用 </Button>
</Button> ) : role.status === 'active' ? (
) : ( <Button
<Button type="danger"
icon={<IconPlay size={14} />} icon={<IconPause size={14} />}
onClick={() => { onClick={() => {
onEnable(role); onDisable(role);
}} }}
> >
启用 停用
</Button> </Button>
)} ) : (
</div> <Button
) : null} icon={<IconPlay size={14} />}
onClick={() => {
onEnable(role);
}}
>
启用
</Button>
)}
</>
) : null}
<Button
theme="solid"
type="primary"
icon={canManage && !role.isSystem ? <IconEdit size={14} /> : <IconView size={14} />}
onClick={() => {
onEdit(role.id);
}}
>
{canManage && !role.isSystem ? '编辑角色' : '查看角色'}
</Button>
</div>
</header> </header>
); );
} }
@@ -300,6 +324,7 @@ export function RoleDetailPanel(props: RoleDetailPanelProps): ReactNode {
<DetailHead <DetailHead
role={detail} role={detail}
canManage={props.canManage} canManage={props.canManage}
onEdit={props.onEdit}
onCopy={props.onCopy} onCopy={props.onCopy}
onRename={props.onRename} onRename={props.onRename}
onDisable={props.onDisable} onDisable={props.onDisable}
@@ -324,21 +349,6 @@ export function RoleDetailPanel(props: RoleDetailPanelProps): ReactNode {
<ScopeAndSpecial role={detail} tree={props.tree} /> <ScopeAndSpecial role={detail} tree={props.tree} />
<BoundUsers role={detail} /> <BoundUsers role={detail} />
</div> </div>
<footer className="lx-role-detail__foot">
<span className="lx-role-detail__foot-note">
{detail.isSystem ? '系统内置角色,不可停用或删除' : '自定义角色,可复制、停用或删除'}
</span>
<Button
theme="solid"
type="primary"
icon={<IconEdit size={14} />}
onClick={() => {
props.onEdit(detail.id);
}}
>
编辑角色
</Button>
</footer>
</> </>
) : ( ) : (
<div className="lx-role-detail__empty"> <div className="lx-role-detail__empty">
-19
View File
@@ -711,25 +711,6 @@
white-space: nowrap; white-space: nowrap;
} }
.lx-role-detail__foot {
display: flex;
height: 56px;
flex: 0 0 56px;
align-items: center;
justify-content: space-between;
gap: var(--lx-space-3);
padding: 0 18px;
border-top: 1px solid var(--lx-color-border);
}
.lx-role-detail__foot-note {
overflow: hidden;
font-size: var(--lx-font-size-xs);
color: var(--lx-color-text-tertiary);
text-overflow: ellipsis;
white-space: nowrap;
}
/* ---------- 角色模板(新建页左列) ---------- */ /* ---------- 角色模板(新建页左列) ---------- */
.lx-role-templates { .lx-role-templates {