DirectSurface UIDirectSurface UI
开始使用
文档/API 参考

Commands API 参考

Commands API 用于统一按钮、菜单、快捷键、权限、启用状态和执行入口。复杂业务系统中,同一个动作应注册为一个命令,再由工具栏、菜单、右键菜单或快捷键绑定同一个 command id。

命令不是按钮。按钮、菜单项和快捷键只是命令入口。

导出清单

核心导出:

  • CommandManager
  • CommandScope
  • AppCommand
  • CommandState
  • CommandExecutionContext
  • CommandTrigger
  • CommandPredicate
  • CommandReasonResolver
  • CommandExecutor
  • PermissionService
  • CommandInterceptor
  • CommandExecuteOptions
  • CommandEvaluateOptions
  • CommandInspection
  • CommandScopeDiagnostic
  • RenderCommandScope
  • RenderCommandButton
  • RenderCommandToolbar
  • CommandShortcutController
  • CommandShortcutControllerOptions
  • normalizeShortcut
  • shortcutFromKeyboardEvent
  • commandShortcuts
  • createCommandContextMenu
  • createCommandMenuBarItems
  • resolveCommandMenuEntries
  • executeCommandMenuSelection
  • CommandMenuItem
  • CommandMenuBarItem
  • CommandToolbarGroup
  • CommandToolbarItem
  • commandManagerContextKey

AppCommand

AppCommand<TResult = unknown> 定义一个业务动作。

字段 类型 必填 说明
id string 命令唯一 id。应稳定,便于权限配置、快捷键和审计。
title string 默认显示文本。
description string 说明文本,常用于 tooltip 或诊断面板。
icon string 图标名称。具体可用图标由图标组件决定。
shortcut string | readonly string[] 快捷键,例如 Ctrl+SMeta+S
permission string | readonly string[] 功能权限码。数组语义是全部权限都必须满足。
visible CommandPredicate 是否显示入口。返回 falseenabled 也会是 false
enabled CommandPredicate 当前页面状态下是否可用。
canExecute CommandPredicate 执行前业务条件。执行前会再次求值。
checked CommandPredicate 切换类命令的选中状态。
disabledReason CommandReasonResolver 禁用原因。未提供时默认是 当前状态不可用
execute CommandExecutor<TResult> 执行动作,可同步或异步。

visibleenabledcanExecutecheckeddisabledReason 都接收 CommandExecutionContext。这些函数必须轻量、同步、无副作用;不要在里面请求接口、修改 UI 或扫描大数据。

CommandState

CommandManager.evaluate() 返回 CommandState

字段 类型 说明
id string 命令 id。
title string 当前显示标题。
description string | undefined 当前说明。
icon string | undefined 当前图标。
visible boolean 入口是否可见。
enabled boolean 是否可执行。
checked boolean 是否处于选中状态。
executing boolean 是否正在执行中。
reason string | undefined 禁用原因。
command AppCommand | undefined 原始命令。命令不存在时为空。

命令不存在时,visible=falseenabled=falsereason命令不存在

CommandExecutionContext

命令执行和状态求值都会创建上下文。

字段 类型 说明
appContext AppContextRegistry source 所在 render tree 和应用根收集出的上下文快照。
commandManager CommandManager 当前命令管理器。
source RenderObject | undefined 触发命令的 render 对象。
trigger 'api' | 'click' | 'keyboard' | 'menu' 触发来源。
event unknown 原始事件或调用方附加数据。

appContext 是一次命令求值或执行期间的快照,CommandManager 内部会在求值和执行结束后释放它。业务不要长期持有这个快照。

CommandManager

创建命令管理器:

const commandManager = new CommandManager({  permissionService: {    has(permission, context) {      return permission === 'record.save'    },  },})

构造参数:

参数 类型 默认值 说明
rootScope CommandScope 新建根 scope 应用级命令范围。
permissionService PermissionService 允许全部 权限服务。

属性:

属性 类型 说明
rootScope CommandScope 根命令范围。
version number 命令状态版本号。invalidate() 会递增。
permissionService PermissionService 当前权限服务。
disposed boolean 是否已释放。

方法:

方法 返回值 说明
setPermissionService(service) void 替换权限服务并触发 invalidate()
register(command) DisposeFn 注册应用级命令到 rootScope。返回释放函数。
addInterceptor(interceptor) DisposeFn 注册执行拦截器。
evaluate(commandId, options?) CommandState 求值命令可见、可用、选中和执行中状态。
inspect(commandId, options?) CommandInspection 获取命令、scope 链、权限、快捷键和覆盖关系。
inspectAll(options?) CommandInspection[] 检查当前 scope 可见的所有命令。
execute(commandId, options?) Promise<TResult | undefined> 执行命令。不可见、不可用或不存在时返回 undefined
createExecutionContext(options) CommandExecutionContext 创建执行上下文。一般业务不需要直接调用。
resolveScope(source?) CommandScope 从 source 向上查找最近命令 scope,找不到则返回 root scope。
createAppContextSnapshot(source?) AppContextRegistry 从 source 和应用根创建上下文快照。
subscribe(listener) DisposeFn 订阅命令状态版本变化。
invalidate() void 通知绑定 UI 重新求值命令状态。
dispose() void 释放监听、拦截器、执行状态和 root scope。

CommandExecuteOptionsCommandEvaluateOptions

字段 类型 默认值 说明
source RenderObject undefined 触发源。用于解析局部 command scope 和 app context。
trigger CommandTrigger api 触发来源。
event unknown undefined 原始事件或附加数据。

CommandScope

CommandScope 是命令的分层注册表。

构造参数:

const pageScope = new CommandScope(commandManager.rootScope, 'record-page')
参数 类型 说明
parent CommandScope | undefined 父命令范围。
debugLabel string | undefined 诊断标签。

属性和方法:

成员 返回值 说明
debugId number 内部诊断 id。
debugLabel string | undefined 诊断标签,可修改。
disposed boolean 是否已释放。
register(command) DisposeFn 注册命令。相同 id 会覆盖当前 scope 的旧命令。
unregister(commandId) boolean 删除当前 scope 的命令。
get(commandId) AppCommand | undefined 从当前 scope 向父级查找。
getOwn(commandId) AppCommand | undefined 只读取当前 scope。
hasOwn(commandId) boolean 当前 scope 是否有该命令。
ids() IterableIterator<string> 当前 scope 自己注册的命令 id。
ownCommands() AppCommand[] 当前 scope 自己注册的命令。
commands() AppCommand[] 当前 scope 和父级合并后的可见命令,局部覆盖父级。
dispose() void 释放当前 scope 的命令。不会释放父 scope。

RenderCommandScope

RenderCommandScope 把命令 scope 挂到 render tree。子组件触发命令时,会优先解析到最近的 RenderCommandScope

构造参数:

参数 类型 默认值 说明
commandManager CommandManager undefined 用于注册后触发 invalidate(),也用于创建默认父 scope。
scope CommandScope 新建 scope 外部已有命令范围。
debugLabel string undefined 诊断标签。
commands AppCommand[] [] 初始化命令。
child RenderBox undefined 被包裹的页面或组件根。
disposeScopeOnDispose boolean 未传 scope 时为 true 当前 render 释放时是否释放 command scope。

方法:

方法 返回值 说明
register(command) DisposeFn 注册局部命令并触发 manager 状态刷新。
setChild(child?) void 替换子节点。
dispose() void disposeScopeOnDispose 决定是否释放 scope。

页面或组件命令应优先放在 RenderCommandScope。这样页面关闭、tab 销毁或组件释放时,局部命令会自动失效。

RenderCommandButton

RenderCommandButton 是绑定单个命令的按钮。

构造参数:

参数 类型 默认值 说明
commandId string 必填 绑定的命令 id。
commandManager CommandManager 从 app context 读取 命令管理器。
label string 命令 title 覆盖按钮文本。
tooltip string 命令禁用原因或说明 覆盖 tooltip。
variant ButtonVariant Button 默认值 按钮风格。

成员:

成员 返回值 说明
commandId string 当前绑定的命令 id。
commandState CommandState | undefined 最近一次命令状态。读取时会刷新。
executeCommand(trigger?, event?) Promise<unknown> 手动执行命令。
refreshCommandState(force?) boolean 刷新按钮状态,返回当前是否可见且可用。

按钮会订阅 CommandManager,manager invalidate() 后会重新求值并更新文本、禁用、loading 和 tooltip。

RenderCommandToolbar

RenderCommandToolbar 把一组命令转换为普通 RenderToolbar 项。

构造参数:

参数 类型 默认值 说明
groups CommandToolbarGroup[] 必填 工具栏分组。
commandManager CommandManager 从 app context 读取 命令管理器。

CommandToolbarGroup

字段 类型 说明
id string 分组 id。
slot ToolbarSlot 分组位置。
visible boolean | undefined 分组是否显示。
items CommandToolbarItem[] 分组项。

CommandToolbarItem

kind 字段 说明
command commandIdid?label?tooltip?showLabel? 绑定命令按钮。
separator id? 分隔线。

方法:

方法 返回值 说明
setCommandGroups(groups) void 替换命令分组。

Toolbar 会隐藏 state.visible=false 的命令项,禁用 state.enabled=false 的命令项。

菜单适配

命令菜单工具把命令配置转换为 ContextMenu 或 MenuBar 需要的数据。

CommandMenuItem

kind 字段 说明
command commandIdkey?label?icon?shortcut?danger?disabled? 命令菜单项。
submenu keylabelitemsicon?disabled?visible? 子菜单。
separator 分隔线。

CommandMenuBarItem

字段 类型 说明
key string 一级菜单 key。
label string 一级菜单文本。
items CommandMenuItem[] 子项。
disabled boolean | undefined 是否禁用。
visible boolean | undefined 是否显示。

函数:

函数 返回值 说明
createCommandContextMenu(manager, source, items) { entries; onSelect } 生成 ContextMenu 条目和选择处理函数。
createCommandMenuBarItems(manager, source, menus) { items; onSelect } 生成 MenuBar 条目和选择处理函数。
resolveCommandMenuEntries(manager, source, items, selectionMap?, path?) ContextMenuEntry[] 只生成菜单条目。
executeCommandMenuSelection(manager, source, selectionMap, key) void 根据选择 key 执行命令。

菜单适配会自动过滤不可见命令,并把命令 shortcut 显示到菜单项。

快捷键

CommandShortcutController 把键盘事件映射到当前 scope 可见命令。

构造参数:

参数 类型 默认值 说明
commandManager CommandManager 必填 命令管理器。
source RenderObject | (() => RenderObject | undefined) undefined 当前快捷键作用源。
priority number 0 键盘绑定优先级。

方法和工具函数:

成员 返回值 说明
dispose() void 注销键盘绑定。
commandShortcuts(command) string[] 获取命令标准化后的快捷键数组。
normalizeShortcut(shortcut) string | undefined 标准化快捷键字符串。
shortcutFromKeyboardEvent(event) string | undefined 从键盘事件生成标准化快捷键。

快捷键会从 source 解析当前命令 scope,只执行可见且可用的第一个匹配命令,并调用 event.preventDefault()

权限服务和拦截器

PermissionService

方法 返回值 说明
has(permission, context) boolean 判断当前上下文是否拥有某权限。

CommandInterceptor

方法 返回值 说明
beforeExecute(command, context) boolean | void | Promise<boolean | void> 执行前拦截。返回 false 会取消执行。
afterExecute(command, context, result) void | Promise<void> 执行成功后调用。
onError(command, context, error) void | Promise<void> 执行抛错时调用。

拦截器适合做统一确认、埋点、审计和错误处理。它不是业务状态存储系统。

命令检查

inspect()inspectAll() 用于调试命令注册、覆盖、权限和快捷键。

CommandInspection

字段 类型 说明
commandId string 命令 id。
exists boolean 是否存在命令。
state CommandState 当前求值状态。
command AppCommand | undefined 命令定义。
sourceScopeId number 触发源解析到的 scope id。
sourceScopeLabel string 触发源 scope 标签。
ownerScopeId number | undefined 实际拥有该命令的 scope id。
ownerScopeLabel string | undefined 实际拥有该命令的 scope 标签。
scopeChain CommandScopeDiagnostic[] 从 source scope 到 root 的链路。
permissions string[] 命令权限码。
shortcuts string[] 命令快捷键。
overridesParent boolean 当前命令是否覆盖了父级同 id 命令。

CommandScopeDiagnostic

字段 类型 说明
id number scope id。
label string scope 标签。
ownCommandIds string[] 当前 scope 自己注册的命令。
activeCommandIds string[] 当前 scope 和父级合并后的可见命令。
isSourceScope boolean 是否是 source 解析到的 scope。
isOwnerScope boolean 是否是命令实际拥有者。

使用示例

import {  CommandManager,  RenderCommandButton,  RenderCommandScope,  RenderStackPanel,  createAppContextKey,} from 'ds-ui' const exampleCurrentRecordKey = createAppContextKey<{ id: string }>('record.current')let exampleDirty = true const exampleCommandManager = new CommandManager({  permissionService: {    has: permission => permission === 'record.save',  },}) const exampleContent = new RenderStackPanel({ spacing: 8 })const exampleScope = new RenderCommandScope({  commandManager: exampleCommandManager,  debugLabel: 'record-page',  commands: [    {      id: 'record.save',      title: '保存',      icon: 'save',      shortcut: ['Ctrl+S', 'Meta+S'],      permission: 'record.save',      canExecute: context => exampleDirty && Boolean(context.appContext.get(exampleCurrentRecordKey)),      execute: () => {        exampleDirty = false        exampleCommandManager.invalidate()      },    },  ],  child: exampleContent,}) exampleContent.addChild(new RenderCommandButton({  commandId: 'record.save',  commandManager: exampleCommandManager,})) exampleScope

使用边界

  • 页面命令优先注册在页面级 RenderCommandScope,不要注册到 root scope。
  • source 很重要。没有 source 时,只能解析 root scope 和应用级上下文。
  • 业务状态变化后调用 commandManager.invalidate(),不要在 hover 或 paint 中频繁调用。
  • execute() 会在执行前重新求值权限和可用状态,不要只依赖按钮禁用状态。
  • dispose() 后继续注册、求值或执行会抛错。
  • 命令适合业务动作,不适合低层 pointer move、hover、输入字符等高频事件。

相关文档