canvas-editor 控件命令(Control Commands)API 全解析:从 getControlValue 到 executeJumpControl 的实战指南
canvas-editor 控件命令(Control Commands)API 全解析:从 getControlValue 到 executeJumpControl 的实战指南
导读
canvas-editor 是一套基于 Canvas/SVG 的富文本编辑器,其「控件」体系(文本、下拉、复选、单选、日期、数字等交互单元)是构建表单式文档的核心能力。本篇技术指南以官方文档 docs/en/guide/control/command.md 为主体骨架,逐条拆解 instance.command 上全部 12 个控件命令的用法、参数、返回值与底层实现,并结合仓库源码与单元测试给出可验证的依据。读完本文,你将能熟练完成控件值的读写、批量赋值、删除、定位、高亮与跳转,独立编写一套可靠的控件数据操作代码。
一、控件命令总览:Command 门面与 CommandAdapt 适配层
在 canvas-editor 中,所有对外命令统一挂在 instance.command 上。控件相关命令集中定义在 Command.ts,它们并非直接实现,而是通过 CommandAdapt 中转绑定,避免外部直接接触编辑器内部上下文(源码注释明确写有“通过 CommandAdapt 中转避免直接暴露编辑器上下文”)。
从 Command.ts 可以看到,每个命令在构造时还会经过 wrap 包装:若开发者通过 setInterceptor 注册了拦截器,则每次调用命令前都会触发 interceptor(name, args) 回调,可用于命令埋点与审计。控件命令因此天然支持统一的调用追踪。
控件命令可分成两组:
| 分组 | 命令 |
|---|---|
| 获取数据(Get Data) | getControlValue、getControlList |
| 执行动作(Execute) | executeRemoveControl、executeSetControlValue、executeSetControlValueList、executeSetControlExtension、executeSetControlExtensionList、executeSetControlProperties、executeSetControlPropertiesList、executeSetControlHighlight、executeLocationControl、executeInsertControl、executeJumpControl |
所有动作型命令在 CommandAdapt.ts 中都有对应实现,且最终统一收敛到绘制层管理器 Control.ts 的 getValueById / setValueListById / setExtensionListById / setPropertiesListById / setHighlightList / getList / initNextControl 等方法上。下面按官方文档顺序逐一展开。
二、获取数据(Get Data)
2.1 getControlValue:获取控件值
官方用法(与 接口定义 对齐后):
const result = await instance.command.getControlValue(payload: IGetControlValueOption)
调用后返回一个结果数组(IGetControlValueResult),每一项包含:
{
// 控件除 value 外的完整配置(type、placeholder、conceptId、groupId、extension……)
...control,
value: string | null // 控件“值”
innerText: string | null // 控件“内部文本”(人眼可读内容)
zone: EditorZone // 所在区域:HEADER / MAIN / FOOTER
elementList?: IElement[] // 文本类控件对应的元素列表(TEXT/DATE/NUMBER 才有)
}
IGetControlValueOption 支持四个定位维度,均为可选:
| 参数 | 类型 | 说明 |
|---|---|---|
id |
string |
控件唯一 id,精确匹配单个控件 |
groupId |
string |
控件组 id,批量命中一组控件 |
conceptId |
string |
语义概念 id,跨 id 批量命中 |
areaId |
string |
区域 id,按区域过滤 |
实现细节:CommandAdapt.getControlValue 直接委托给 draw.getControl().getValueById(payload)(见 CommandAdapt.ts)。而 Control.ts 的 getValueById 会同时遍历页眉(HEADER)、正文(MAIN)、页脚(FOOTER)三块数据,并递归下钻表格单元格(trList → tdList → td.value),因此表格内的控件也能被正确查到。
返回值按控件类型有所区分(源码 L1243-L1274):
- TEXT / DATE / NUMBER:
value与innerText均为拼接后的文本,elementList返回控件值对应的元素数组; - SELECT / CHECKBOX / RADIO:
value为以逗号分隔的 code 字符串(如"a,b"),innerText由valueSets中匹配到的显示文本拼接而成,不返回elementList。
测试佐证:control.test.ts 中通过 getControlValue({ conceptId }) 断言 isOverwrite: false 场景下旧值不被覆盖、空控件被填入默认值;该文件 L221-L225 还验证了留痕开启时日期控件连续两次 executeSetControlValue 后 getControlValue 返回最新值 2026-03-03。
2.2 getControlList:获取所有控件
官方用法:
const controlList = await instance.command.getControlList()
返回 IElement<a href="https://link.gitcode.com/i/00ce4582240214962bd3a78741f922fd" target="_blank">],即文档中所有控件元素(含 control 配置与 controlId)的列表。底层实现见 [CommandAdapt.ts,调用 draw.getControl().getList()。与 getControlValue 的区别在于:getControlList 返回的是控件“元素本身”,适合遍历后再用 getControlValue 按 id 精确取值,或直接读取 element.control 配置。
三、执行动作(Execute)
3.1 executeRemoveControl:删除控件
官方用法:
instance.command.executeRemoveControl(payload?: IRemoveControlOption)
IRemoveControlOption 为 { id?: string; conceptId?: string }。实现分两条路径(见 CommandAdapt.ts):
- 传入
id或conceptId:从页眉、正文、页脚三块数据中递归查找匹配控件(同样会下钻表格),命中后调用draw.deleteElementList删除并重渲染; - 不传参:删除当前光标所在位置的控件——要求光标为折叠态(
startIndex === endIndex),且光标处元素必须带controlId,随后通过control.removeControl(startIndex)删除并重新定位光标。
空参且不在控件上时命令静默返回,不会抛错,适合无脑兜底调用。
3.2 executeSetControlValue:设置控件值
官方用法:
instance.command.executeSetControlValue(payload: ISetControlValueOption)
ISetControlValueOption(接口定义):
| 参数 | 类型 | 说明 |
|---|---|---|
id / groupId / conceptId / areaId |
string |
控件定位维度(同 getControlValue) |
value |
string | IElement[] | null |
新值;传字符串时按类型解析,传 IElement[] 可携带格式(如加粗文本),传 null 清空 |
isSubmitHistory |
boolean |
是否提交撤销历史,默认按内部逻辑 |
isOverwrite |
boolean |
是否覆盖已有值;false 时已有值不被改写,仅填充空控件 |
实现细节:CommandAdapt.setControlValue 将单个 payload 包装成数组后调用 setValueListById(<a href="https://link.gitcode.com/i/16ab0820e7c387b29378905dd947de89" target="_blank">payload])([CommandAdapt.ts),即单条赋值是批量赋值的特例。
测试佐证:
- 对不存在的
id调用不抛错(control.test.ts); isOverwrite: false时,已有值控件保持“旧值”,空控件写入“默认值”(control.test.ts);- 空值清空控件时占位符(placeholder)只替换不叠加,该用例直接对应 issue #1473 的回归修复(control.test.ts)。
3.3 executeSetControlValueList:批量设置控件值
官方用法:
instance.command.executeSetControlValueList(payload: ISetControlValueOption[])
与单条版本共用同一套 ISetControlValueOption,一次调用可混合使用 id / groupId / conceptId / areaId 命中多个控件并分别赋值。底层直接透传 setValueListById(payload)(CommandAdapt.ts),在一次渲染周期内完成,适合表单回填等需要“一键批量赋值”的场景。isOverwrite: false 的批量语义同样生效,见上文测试用例。
3.4 executeSetControlExtension / executeSetControlExtensionList:设置/批量设置控件扩展值
官方用法:
instance.command.executeSetControlExtension(payload: ISetControlExtensionOption)
instance.command.executeSetControlExtensionList(payload: ISetControlExtensionOption[])
ISetControlExtensionOption(接口定义)为 { id?, groupId?, conceptId?, areaId?, extension: unknown }。extension 是控件配置上的自由扩展字段(IControlBasic.extension?: unknown,见 Control.ts 接口),可用于挂载任意业务元数据(如审批状态、来源单据号),不影响控件渲染与取值。
实现细节:单条/批量分别调用 setExtensionListById(<a href="https://link.gitcode.com/i/bebf00b66b4bbc134279e71b277b616a" target="_blank">payload]) 与 setExtensionListById(payload)([CommandAdapt.ts),在 Control.ts 的 setExtensionListById 中完成查找与回写。
3.5 executeSetControlProperties / executeSetControlPropertiesList:设置/批量设置控件属性
官方用法:
instance.command.executeSetControlProperties(payload: ISetControlProperties)
instance.command.executeSetControlPropertiesList(payload: ISetControlProperties[])
ISetControlProperties(接口定义):
{
id?: string
groupId?: string
conceptId?: string
areaId?: string
properties: Partial<Omit<IControl, 'value'>> // 除 value 外的任意控件配置
isSubmitHistory?: boolean
}
properties 可携带 IControl 中除 value 外的全部字段,例如 placeholder、disabled、required、underline、border、minWidth、prefix/postfix、validation 等(完整字段见 IControlBasic/IControlRule 接口)。这使其成为「动态改控件规则」的入口:例如将 disabled 置为 true 冻结输入、切换 placeholder 提示文案。
实现细节:分别调用 setPropertiesListById(<a href="https://link.gitcode.com/i/f7588089e72fa55ab58f9c281baf2a2a" target="_blank">payload]) 与 setPropertiesListById(payload)([CommandAdapt.ts)。
3.6 executeSetControlHighlight:设置控件高亮(按关键词)
官方用法:
instance.command.executeSetControlHighlight(payload: ISetControlHighlightOption)
ISetControlHighlightOption 即 IControlHighlight<a href="https://link.gitcode.com/i/a6162ac3bbd1616ac50959592e92693a" target="_blank">]([接口定义),结构与关键词规则相关:
{
ruleList: {
keyword: string // 要高亮的关键词
alpha?: number // 背景透明度
backgroundColor?: string // 背景色
}[]
id?: string // 限定到某个控件
conceptId?: string // 或按语义概念批量命中
}
实现细节:CommandAdapt.setControlHighlight 调用 draw.getControl().setHighlightList(payload) 后以 isSubmitHistory: false 重渲染(CommandAdapt.ts),即高亮属于“非历史”的视觉状态,不进入撤销栈。底层由控件搜索模块 ControlSearch.ts 维护高亮规则(setHighlightList)并负责 Canvas 上的匹配与绘制(computeHighlightList / renderHighlightList,见 Control.ts)。
测试佐证:control.test.ts 验证了无任何控件时调用 executeSetControlHighlight([{ ruleList: [{ keyword: 'test' }] }]) 不抛错。
3.7 executeLocationControl:定位并激活控件
官方用法:
instance.command.executeLocationControl(controlId: string, options?: ILocationControlOption)
ILocationControlOption 为 { position: LocationPosition },LocationPosition 枚举定义于 Common.ts:
| 取值 | 含义 |
|---|---|
before |
控件内部最前 |
after |
控件内部最后 |
outer-before |
控件外面最前 |
outer-after |
控件外面最后 |
实现细节:CommandAdapt.locationControl 按「页眉 → 正文 → 页脚」顺序查找首个 controlId 匹配的控件(表格内同样递归下钻),随后依次:切换所在区域(setZone)→ 写入位置上下文(position.setPositionContext)→ 用 range.replaceRange 折叠光标到目标位置 → 重渲染。因此调用后光标会精确落到控件内部或外部指定位置,相当于“编程式聚焦控件”,常用于表单校验后定位到出错的控件。
3.8 executeInsertControl:插入控件
官方用法:
instance.command.executeInsertControl(payload: IElement)
payload 是一个完整的控件元素,典型结构:
{
value: '',
control: {
type: ControlType.TEXT, // TEXT / SELECT / CHECKBOX / RADIO / DATE / NUMBER
value: null, // 初始值;也可传入 [{ value: '控件值' }]
placeholder: '请输入……',
// 其他 IControl 配置(conceptId、groupId、prefix、postfix、validation……)
}
}
实现细节与限制(CommandAdapt.ts):
- 编辑器处于只读(readonly)或禁用(disabled)状态时直接返回,不执行插入;
- 对 payload 做深拷贝,并从光标所在锚点元素复制表格、行、列表、区域等上下文属性,保证插入后排版上下文正确;
- 一个重要的嵌套约束:仅允许 TEXT 控件作为外层嵌套其他控件。若当前光标位于非 TEXT 控件内部又试图插入控件元素,会被拒绝——这是源码明确实现的校验逻辑(L2711-L2718)。
测试佐证:control.test.ts 覆盖了插入空值文本控件与插入带值控件(value: [{ value: '控件值' }])两种路径,均断言不抛错。
3.9 executeJumpControl:跳转到上/下一个控件
官方用法:
instance.command.executeJumpControl(payload?: { direction?: MoveDirection })
MoveDirection 枚举定义于 Observer.ts:
export enum MoveDirection {
UP = 'top',
DOWN = 'down',
LEFT = 'left',
RIGHT = 'right'
}
实现细节:CommandAdapt.jumpControl 调用 draw.getControl().initNextControl({ direction })(CommandAdapt.ts),由 Control.ts 的 initNextControl 依据当前光标上下文计算下一个控件的定位信息并激活。该命令非常适合实现“按 Enter/Tab 在多个控件间流转”的表单导航体验——例如在最后一个控件按下回车自动跳到下一个空控件。
四、命令速查表
| 命令 | 签名 | 核心参数 | 返回 |
|---|---|---|---|
getControlValue |
(payload: IGetControlValueOption) => Promise<IGetControlValueResult> |
id / groupId / conceptId / areaId |
{ value, innerText, zone, elementList? }[] |
getControlList |
() => Promise<IElement[]> |
无 | 控件元素数组 |
executeRemoveControl |
(payload?: IRemoveControlOption) => void |
id / conceptId(空参删光标处控件) |
— |
executeSetControlValue |
(payload: ISetControlValueOption) => void |
value、isSubmitHistory、isOverwrite |
— |
executeSetControlValueList |
(payload: ISetControlValueOption[]) => void |
同上,数组批量 | — |
executeSetControlExtension |
(payload: ISetControlExtensionOption) => void |
extension: unknown |
— |
executeSetControlExtensionList |
(payload: ISetControlExtensionOption[]) => void |
同上,数组批量 | — |
executeSetControlProperties |
(payload: ISetControlProperties) => void |
properties: Partial<Omit<IControl, 'value'>>、isSubmitHistory |
— |
executeSetControlPropertiesList |
(payload: ISetControlProperties[]) => void |
同上,数组批量 | — |
executeSetControlHighlight |
(payload: ISetControlHighlightOption) => void |
ruleList[{keyword, alpha?, backgroundColor?}]、id / conceptId |
— |
executeLocationControl |
(controlId: string, options?: ILocationControlOption) => void |
position: before/after/outer-before/outer-after |
— |
executeInsertControl |
(payload: IElement) => void |
控件元素(仅 TEXT 可作外层嵌套) | — |
executeJumpControl |
(payload?: { direction?: MoveDirection }) => void |
top/down/left/right |
— |
五、组合实战:一套完整的控件数据流
结合以上 API,可轻松实现“表单回填 → 校验定位 → 流转导航”的完整链路:
const editor = new Editor(container, options)
// 1. 批量回填表单(不覆盖已有值)
editor.command.executeSetControlValueList([
{ conceptId: 'OutpatientId', value: 'OP-2026-001', isOverwrite: false },
{ conceptId: 'ChestPainType', value: 'a,b' }, // SELECT/CHECKBOX 按 code 写入
{ groupId: 'ExamGroup', value: [{ value: '正常', bold: true }] } // 带格式写入
])
// 2. 读取回填结果(含所在区域与内部文本)
const result = await editor.command.getControlValue({ groupId: 'ExamGroup' })
console.log(result[0].value, result[0].innerText, result[0].zone)
// 3. 校验失败时定位到出错的控件(光标落入控件内部最前)
editor.command.executeLocationControl(errorControlId, {
position: 'before'
})
// 4. 按回车在控件间流转
document.addEventListener('keydown', (evt) => {
if (evt.key === 'Enter') {
editor.command.executeJumpControl({ direction: 'down' })
}
})
几点实战提示:
- 定位维度:
id精确、conceptId语义化批量、groupId分组、areaId限定区域,四者可在一次调用中组合使用,实现“按区域 + 按概念”的交叉过滤; - 历史记录:赋值类命令通过
isSubmitHistory控制是否进入撤销栈;高亮、定位类命令默认不写入历史,避免污染撤销链路; - 只读保护:
executeInsertControl在只读/禁用状态下静默返回,业务侧如需提示可先通过getOptions检查当前编辑状态; - 与级联/校验协同:
executeSetControlProperties可动态修改required、disabled、hide等规则,与文档同目录下的 级联规则、校验规则 配合,可实现“控件 A 的值变化 → 动态隐藏/必填控件 B → 整体校验”的联动表单;控件的基础配置与外观选项可进一步参考 控件选项。
六、结语
控件命令是 canvas-editor 面向业务集成的关键入口:getControlValue / getControlList 负责读取,executeSetControlValue(List) / executeSetControlExtension(List) / executeSetControlProperties(List) 负责按值、扩展字段、配置属性三个粒度写入,executeRemoveControl / executeInsertControl / executeJumpControl / executeLocationControl / executeSetControlHighlight 负责结构、导航与视觉状态管理。所有命令均可在 Command.ts 与 CommandAdapt.ts 中溯源,并由 control.test.ts 提供行为级回归保障。开发者可直接将上文的示例代码接入自己的表单场景,实现稳定、可追踪的控件数据驱动。