- 人工智能
- AI Agent
- Agent 工作流
- RPA
- 后端
- 前端
- 工具调用
【免费下载链接】astron-agent
An enterprise-ready AI agent development platform combining intelligent RPA, enabling agents to not only think but also act—automating workflows across digital and desktop environments. 这是一个镜像仓库,有issue、pr欢迎去github提交。
在 astron-agent 控制台前端(console/frontend)的空间管理模块中,大量界面(空间列表操作列、成员管理、团队设置等)都需要按用户角色动态决定"编辑""删除""转让"等操作按钮是否显示、是否可用。为此,项目沉淀了一套统一的权限感知按钮组件:ButtonGroup与SpaceButton。它们把「按钮渲染」「模块/资源/自定义三级权限校验」「权限失败后的隐藏或禁用策略」「用户角色的自动获取」封装在一处,使业务页面只需声明式地描述按钮配置,即可完成合规的操作入口渲染。读完本文,你将掌握这套组件的完整 API、权限检查在源码中的调用链、权限失败行为(HIDE/DISABLE)的优先级规则,以及仓库中真实的集成用例与测试验证方式。
组件定位与目录结构
组件位于console/frontend/src/components/button-group/目录下,文件组织如下:
| 文件 | 职责 |
|---|---|
| button-group.tsx | ButtonGroup按钮组容器组件 |
| space-button.tsx | SpaceButton单按钮组件,承载权限控制与状态管理核心逻辑 |
| types.ts | ButtonConfig、UserRole、PermissionConfig等完整 TypeScript 类型定义与PermissionFailureBehavior枚举 |
| index.ts | 统一导出入口(默认导出ButtonGroup,命名导出SpaceButton与全部类型/枚举) |
| button-group.module.scss / space-button.module.scss | CSS Modules 样式,覆盖尺寸变体、垂直排列、分割线与无障碍焦点样式 |
| permission-loading.test.js | 用node:test+ SSR 渲染验证「用户角色加载期间权限按钮保持禁用、可见性受控内容保持隐藏」的行为 |
从源码结构看,权限校验逻辑并未内联在组件内部,而是委托给全局权限体系:SpaceButton从@/permissions/utils引入 hasModulePermission 与 checkResourceRestrictions,用户角色则来自userStore。
提示:组件目录内的 README.md 示例中写的导入路径是
@/components/space/ButtonGroup,但仓库中该组件的实际位置是console/frontend/src/components/button-group/。以实际业务页面(如 space-table)为例,真实导入方式为import ButtonGroup, { ButtonConfig } from '@/components/button-group',本文以下示例均采用该路径。
导入方式
index.ts 暴露了完整的公共 API:
// 导入按钮组(默认导出,推荐) import ButtonGroup from '@/components/button-group'; // 单独导入 SpaceButton 组件 import { SpaceButton } from '@/components/button-group'; // 导入类型定义 import type { ButtonConfig, UserRole, ButtonGroupProps, PermissionConfig, ButtonClickHandler, PermissionChecker, VisibilityChecker, } from '@/components/button-group'; // 导入权限枚举(组件从 @/types/permission 重新导出,便于就近使用) import { SpaceType, RoleType, ModuleType, OperationType, PermissionFailureBehavior, } from '@/components/button-group';这四个权限枚举定义在 src/types/permission.ts 中,是理解整套组件的钥匙:
SpaceType:PERSONAL(个人空间)/ENTERPRISE(企业空间,值为'team');RoleType:OWNER、ADMIN、MEMBER、VISITOR、SUPER_ADMIN、SPACE_ADMIN;ModuleType:SPACE、AGENT、PROMPT_TOOLS、EFFECT_EVALUATION、PUBLISH_MANAGEMENT、MODEL_MANAGEMENT、RESOURCE_MANAGEMENT、API_MANAGEMENT、SPACE_SETTINGS;OperationType:VIEW、CREATE、EDIT、DELETE、PUBLISH、USE、MANAGE,以及一批空间专属操作(ADD_MEMBERS、REMOVE_MEMBERS、SPACE_TRANSFER、INVITATION_MANAGE、APPLY_MANAGE、LEAVE_ENTERPRISE等)。
核心数据模型
types.ts 定义了四个核心结构,也是所有 API 表格的基准。
ButtonConfig 按钮配置
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| key | string | - | 按钮唯一标识符,也是点击回调中回传的key |
| text | string | - | 按钮文本;包含.且非纯数字时会被当作 i18n key 自动翻译(见国际化细节) |
| icon | React.ReactNode? | - | 按钮图标 |
| type | 'primary' \| 'default' \| 'dashed' \| 'link' \| 'text' | 'default' | 按钮类型 |
| size | 'large' \| 'middle' \| 'small' | - | 按钮大小 |
| disabled | boolean | false | 是否禁用 |
| tooltip | string? | - | 提示文本,传入后按钮自动包一层Tooltip |
| danger | boolean | false | 是否为危险按钮(红色语义) |
| loading | boolean | false | 是否显示加载状态 |
| onClick | (key: string, event: React.MouseEvent) => void | - | 单个按钮的点击处理函数,优先级高于ButtonGroup的onButtonClick |
| permission | PermissionConfig? | - | 权限配置 |
| visible | boolean \| ((userRole: UserRole) => boolean) | true | 显示条件,布尔或按角色的函数 |
PermissionConfig 权限配置
| 属性 | 类型 | 说明 |
|---|---|---|
| module | ModuleType? | 模块类型 |
| operation | OperationType? | 操作类型 |
| resourceOwnerId | string? | 资源所有者 ID |
| currentUserId | string? | 当前用户 ID |
| customCheck | (userRole: UserRole) => boolean | 自定义权限检查函数 |
| failureBehavior | PermissionFailureBehavior | 权限失败行为:HIDE(隐藏)/DISABLE(禁用但显示) |
用户角色与点击类型
// 用户角色接口(types.ts) export interface UserRole { spaceType: SpaceType; roleType: RoleType; } // 权限失败行为枚举 export enum PermissionFailureBehavior { HIDE = 'hide', // 隐藏按钮(README 描述的默认行为) DISABLE = 'disable', // 禁用按钮但仍显示 }基础用法
简单按钮组
不配置permission时,ButtonGroup就是一个带统一尺寸、间距与事件分发的普通按钮组:
import React from 'react'; import ButtonGroup from '@/components/button-group'; import { EditOutlined, DeleteOutlined, ShareAltOutlined } from '@ant-design/icons'; const MyComponent = () => { const buttons = [ { key: 'edit', text: '编辑', icon: <EditOutlined />, type: 'primary' as const }, { key: 'share', text: '分享', icon: <ShareAltOutlined />, type: 'default' as const }, { key: 'delete', text: '删除', icon: <DeleteOutlined />, type: 'default' as const, danger: true }, ]; const handleButtonClick = (buttonKey: string, event: React.MouseEvent) => { console.log('按钮点击:', buttonKey); }; return <ButtonGroup buttons={buttons} onButtonClick={handleButtonClick} />; };带权限控制的按钮组
import { ModuleType, OperationType, SpaceType, RoleType } from '@/components/button-group'; import type { ButtonConfig, UserRole } from '@/components/button-group'; const PermissionButtonGroup = () => { // 用户角色信息(也可以不传,组件会自动从 userStore 获取) const userRole: UserRole = { spaceType: SpaceType.ENTERPRISE, roleType: RoleType.ADMIN, }; const buttons: ButtonConfig[] = [ { key: 'edit', text: '编辑', icon: <EditOutlined />, type: 'primary', permission: { module: ModuleType.SPACE, operation: OperationType.EDIT, }, }, { key: 'delete', text: '删除', icon: <DeleteOutlined />, danger: true, permission: { module: ModuleType.SPACE, operation: OperationType.SPACE_DELETE, resourceOwnerId: 'owner123', currentUserId: 'user456', }, }, { key: 'advanced', text: '高级功能', permission: { customCheck: (userRole) => userRole.roleType === RoleType.SUPER_ADMIN, }, }, ]; return ( <ButtonGroup buttons={buttons} userRole={userRole} onButtonClick={(key) => console.log('点击了:', key)} /> ); };注意:
OperationType枚举中的编辑操作是EDIT(值'edit'),仓库枚举里并不存在UPDATE;模块名以 types/permission.ts 中的ModuleType为准(智能体模块为ModuleType.AGENT)。
单独使用 SpaceButton
import { SpaceButton } from '@/components/button-group'; import { PlusOutlined } from '@ant-design/icons'; const SingleButton = () => { const buttonConfig = { key: 'create', text: '创建', icon: <PlusOutlined />, type: 'primary' as const, tooltip: '创建新的资源', }; return <SpaceButton config={buttonConfig} onClick={(key) => console.log('创建操作')} />; };组件 API
ButtonGroup Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| buttons | ButtonConfig[] | - | 按钮配置数组 |
| userRole | UserRole? | - | 用户角色信息,不传时自动从 userStore 获取 |
| className | string? | - | 自定义样式类名 |
| size | 'large' \| 'middle' \| 'small' | 'middle' | 按钮大小 |
| onButtonClick | (key: string, event: React.MouseEvent) => void | - | 统一的按钮点击处理函数 |
| style | React.CSSProperties? | - | 自定义样式 |
| vertical | boolean | false | 是否垂直排列 |
| split | boolean | true | 是否显示分割线(false时切换为独立圆角样式) |
| defaultPermissionFailureBehavior | PermissionFailureBehavior? | 见下文 | 全局权限失败行为,透传给组内每个SpaceButton |
button-group.tsx 的容器逻辑很直接:把每个ButtonConfig交给SpaceButton渲染(inGroup={true}),过滤掉返回null的按钮;若所有按钮都因权限/可见性返回null,整个按钮组也返回null,不渲染空的Button.Group。
SpaceButton Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| config | ButtonConfig | - | 按钮配置 |
| userRole | UserRole? | - | 用户角色信息,不传时自动从 userStore 获取 |
| className | string? | - | 自定义样式类名 |
| style | React.CSSProperties? | - | 自定义样式 |
| size | 'large' \| 'middle' \| 'small' | - | 按钮大小 |
| onClick | (key: string, event: React.MouseEvent) => void | - | 点击事件处理函数(config.onClick优先级更高) |
| inGroup | boolean | false | 是否在按钮组中(影响 CSS Module 类名) |
| defaultPermissionFailureBehavior | PermissionFailureBehavior? | DISABLE | 按钮配置未指定failureBehavior时的兜底行为 |
权限检查机制源码解析
SpaceButton的核心是 checkButtonPermission 函数,它实现了一个清晰的短路检查链:
- 未配置
permission→ 直接放行(返回true); - 拿不到
effectiveUserRole(既未显式传入,userStore也尚未就绪)→ 返回false,即"角色未加载"与"无权限"按同一路径走失败行为; - 配置了
customCheck→ 直接以customCheck(effectiveUserRole)的结果为准,不再做模块校验; - 配置了
module与operation→ 调用 hasModulePermission 做模块权限检查:按spaceType + roleType在权限表PERMISSIONS中查找角色配置,再检查该模块的operations数组是否包含目标操作;失败则立即返回false; - 模块权限通过且同时提供了
resourceOwnerId与currentUserId→ 追加调用 checkResourceRestrictions 做资源级限制检查:若该模块配置了restrictions.ownResourcesOnly(如 enterprise-permissions.ts 中SPACE模块的ownResourcesOnly: true),则要求resourceOwnerId === currentUserId,即"只能操作自己创建的资源"。
权限表本身由 config/index.ts 聚合:PERMISSIONS = [...SHARE_PERMISSIONS, ...ENTERPRISE_PERMISSIONS],分别描述个人空间与企业空间中各角色(SUPER_ADMIN/ADMIN/MEMBER等)在每个模块下允许的操作集合。以 enterprise-permissions.ts 为例,企业空间管理员拥有MANAGE、ADD_MEMBERS、REMOVE_MEMBERS等操作,而成员仅保留MANAGE、VIEW、SPACE_DELETE、LEAVE_ENTERPRISE等基础操作——这正是"删除按钮只对特定角色显示"这类 UI 行为的后端依据。
与之并行的还有 checkButtonVisible 可见性检查:visible缺省为可见;是布尔值则直接采用;是函数时,拿不到角色信息会判定为不可见(返回false),即角色加载期间受visible函数控制的按钮保持隐藏,而不是先闪现再消失。
权限失败行为:HIDE 与 DISABLE
SpaceButton在拿到权限结果后按 failureBehavior 决定渲染策略:
// 获取权限失败行为(优先使用按钮配置,然后使用默认配置) const failureBehavior = permission?.failureBehavior || defaultPermissionFailureBehavior; // HIDE:返回 null,不渲染 if (!hasPermission && failureBehavior === PermissionFailureBehavior.HIDE) return null; // DISABLE:继续渲染,但最终 disabled 计算中纳入无权限条件 const isDisabled = disabled || loading || (!hasPermission && failureBehavior === PermissionFailureBehavior.DISABLE);行为优先级为:按钮级permission.failureBehavior> 组件级defaultPermissionFailureBehavior> 兜底值。需要注意一个细节:ButtonGroup的defaultPermissionFailureBehavior未传入时,SpaceButton解构参数中的兜底默认值是PermissionFailureBehavior.DISABLE,也就是说组件层面的实际兜底是"禁用但显示";而目录 README 将系统默认描述为HIDE。若要严格遵循 README 语义(无权限即隐藏),建议显式传入defaultPermissionFailureBehavior={PermissionFailureBehavior.HIDE},或在每个按钮的permission中显式声明。
一个典型配置示例(结合三种检查方式与两种失败行为):
const buttons = [ { key: 'edit', text: '编辑', // 基础模块权限 permission: { module: ModuleType.SPACE, operation: OperationType.EDIT }, }, { key: 'share', text: '分享', // 无权限时禁用而不是隐藏 permission: { module: ModuleType.SPACE, operation: OperationType.VIEW, failureBehavior: PermissionFailureBehavior.DISABLE, }, }, { key: 'delete', text: '删除', // 资源权限 + 所有者检查 - 无权限时隐藏 permission: { module: ModuleType.SPACE, operation: OperationType.SPACE_DELETE, resourceOwnerId: agent.ownerId, currentUserId: currentUser.id, failureBehavior: PermissionFailureBehavior.HIDE, }, }, { key: 'superAdmin', text: '超级管理', // 自定义权限检查 - 无权限时禁用 permission: { customCheck: userRole => userRole.roleType === RoleType.SUPER_ADMIN, failureBehavior: PermissionFailureBehavior.DISABLE, }, }, ];用户角色加载期间的行为(有测试背书)
permission-loading.test.js 用renderToStaticMarkup做了两条关键断言:
- 权限依赖的按钮在
permissionParams尚未就绪时保持禁用(渲染结果匹配disabled),角色就绪({ spaceType: 'team', roleType: 'admin' })后恢复正常;而没有权限配置的公共按钮在加载期间也不被禁用; - 受
permission/visible函数保护的内容(如SpaceTab)在用户状态加载中保持隐藏(渲染结果为空字符串),角色就绪后才出现。
这套行为保证了首屏不会出现"按钮闪现后被禁用"的权限抖动。
用户角色的自动获取
SpaceButton通过 useUserStoreHook 获取角色:
// space-button.tsx const { permissionParams } = useUserStoreHook(); // 优先使用传入的 userRole,如果没有则从 userStore 获取 let effectiveUserRole: UserRole | undefined = userRole; if (!effectiveUserRole) { effectiveUserRole = permissionParams; }useUserStoreHook内部的permissionParams是一个useMemo派生值:从全局userStore的user对象提取spaceType与roleType,两者任一缺失时返回undefined——这正是上一节"角色未加载"分支的数据来源。因此推荐业务页面直接省略userRole属性,让组件随登录态自动响应;显式传入则主要用于测试或特殊上下文(如替他人视角渲染)。
样式定制与视觉细节
样式采用 CSS Modules,button-group.module.scss 提供了完整的视觉体系:
.spaceButtonGroup { display: inline-flex; align-items: center; gap: 8px; &.vertical { flex-direction: column; .button { width: 100%; } } // vertical 属性 &.noSplit { .button { border-radius: 6px !important; } } // split={false} // 尺寸变体:large 40px / middle 36px / small 32px,间距 16/12/8px &.size-large { gap: 16px; .button { height: 40px; font-size: 16px; } } &.size-middle { gap: 12px; .button { height: 36px; font-size: 14px; } } &.size-small { gap: 8px; .button { height: 32px; font-size: 14px; } } // 无障碍支持 .button:focus-visible { outline: 2px solid var(--primary-color); outline-offset: 2px; border-radius: 4px; } }使用方式:
// 通过 className <ButtonGroup className="my-button-group" buttons={buttons} /> // 通过 style 属性 <ButtonGroup style={{ marginTop: 16 }} buttons={buttons} />// 全局样式覆盖示例 .my-button-group { .ant-btn { border-radius: 8px; margin: 0 4px; } }SpaceButton侧的类名拼接(space-button.tsx)还根据inGroup/standalone、是否带图标、loading、danger、type(映射为ant-btn-{type}修饰类)与尺寸动态组合,type === 'primary'时额外叠加addMemberBtn高亮样式。
按钮文本的国际化细节
getButtonText 实现了一个轻量的 i18n 约定:若text包含.且不是x.y形式的纯数字,则视为 i18n key,通过react-i18next的t()翻译;否则原样渲染。这意味着你可以把多语言文案 key(如space.action.delete)直接写进ButtonConfig.text,而纯中文/英文文案则无需处理。
仓库中的真实集成用例
该组件已在多处空间管理场景中落地,可作为集成参照:
- space-table:空间列表的操作列通过
actionColumn.getActionButtons(...)生成按钮配置后,直接<ButtonGroup buttons={actionButtons} />渲染,不传userRole,完全依赖 store 自动获取角色; - 成员与空间操作弹窗:add-member-modal、delete-space-modal、transfer-ownership-modal、leave-space-modal、space-modal 均引用该组件;
- 企业空间页面:
console/frontend/src/pages/space/enterprise/page-components/下的space-manage、team-settings、member-manage(含member-list、invitation-list、batch-import)以及model-management、resource-management等页面。
从源码结构看,这些页面遵循同一模式:把"每个操作对应的 module/operation/资源归属"声明进ButtonConfig.permission,把"点击后做什么"集中到onButtonClick,从而让权限差异只体现在按钮配置上。
最佳实践
- 权限逻辑集中在
permission,不要混入visible
// ✅ 推荐:清晰的权限配置 const buttons = [ { key: 'edit', text: '编辑', permission: { module: ModuleType.SPACE, operation: OperationType.EDIT }, }, ]; // ❌ 不推荐:把权限判断塞进 visible const buttons = [ { key: 'edit', text: '编辑', visible: userRole => userRole.roleType === RoleType.ADMIN && hasOtherPermission(), }, ];性能:将按钮配置提取到组件外部,避免每次渲染创建新数组与新的 JSX 图标;配置引用稳定后,配合权限检查的纯函数特性可减少无效重渲染。
错误处理:统一在
onButtonClick中做分发与兜底
const handleButtonClick = (key: string, event: React.MouseEvent) => { try { switch (key) { case 'delete': handleDelete(); break; case 'edit': handleEdit(); break; default: console.warn(`未知的按钮操作: ${key}`); } } catch (error) { console.error('按钮操作失败:', error); // 显示错误提示 } };常见问题
Q: 为什么我的按钮没有显示?
依次检查:1) 当前userRole(或 store 中的spaceType/roleType)对应的角色配置在权限表中是否包含目标module + operation;2)visible布尔/函数配置是否正确(注意角色未就绪时visible函数一律返回false);3) 是否被failureBehavior: HIDE命中——权限不通过时按钮返回null,若组内所有按钮均无权限,整个ButtonGroup也不渲染。
Q: 权限检查失败时会发生什么?
取决于failureBehavior:HIDE(显式配置)时按钮返回null不显示;DISABLE时按钮继续渲染但处于禁用态(isDisabled计算中纳入无权限条件,点击事件直接拦截)。组件层面的兜底默认值是DISABLE,与 README 描述的HIDE默认存在出入,建议显式声明所需行为。
Q: 如何自定义按钮样式?
className添加自定义样式类;2)style直接内联;3) 覆盖对应的 SCSS Module 文件(button-group.module.scss/space-button.module.scss)中的尺寸与间距变量。
参考路径汇总
- 组件实现:button-group.tsx、space-button.tsx、types.ts、index.ts
- 权限体系:permissions/utils.ts、permissions/config/index.ts、types/permission.ts
- 角色来源:hooks/use-user-store.ts
- 行为测试:permission-loading.test.js
- 组件文档:button-group/README.md
- 人工智能
- AI Agent
- Agent 工作流
- RPA
- 后端
- 前端
- 工具调用
【免费下载链接】astron-agent
An enterprise-ready AI agent development platform combining intelligent RPA, enabling agents to not only think but also act—automating workflows across digital and desktop environments. 这是一个镜像仓库,有issue、pr欢迎去github提交。
相关推荐
astron-agent 前端 ButtonGroup 与 SpaceButton:基于权限控制的按钮组组件实战指南
astron agent 前端 ButtonGroup 与 SpaceButton:基于权限控制的按钮组组件实战指南 ButtonGroup 与 SpaceBu
人工智能AI AgentAgent 编排RPA后端前端企业应用React Native Elements 按钮组(ButtonGroup)组件详解
React Native Elements 按钮组 ButtonGroup 组件详解 什么是ButtonGroup组件 ButtonGroup是React Na
UI组件移动开发前端react-scanner 路线图与社区生态:从 1.1.0 到未来的无限可能
react scanner 路线图与社区生态:从 1.1.0 到未来的无限可能 react scanner 是一款开源的 React 组件扫描与分析工具 ,能够
开发工具代码质量
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考