news 2026/9/28 2:54:06

OpenPencil Vue SDK 属性面板开发指南:从无头原语到变量绑定的完整实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
OpenPencil Vue SDK 属性面板开发指南:从无头原语到变量绑定的完整实战
  • 前端
  • 桌面应用
  • AI 应用
  • MCP 服务

【免费下载链接】open-pencil

AI-native design editor. Open-source Figma alternative.

项目地址:https://gitcode.com/gh_mirrors/op/open-pencil
点击查看免费下载

@open-pencil/vue的属性面板(Property Panels)是设计工具中最复杂的 UI 区域之一:它既要实时反映当前选区的计算结果,又要安全地写回场景图,还要支持变量绑定与多对象同步编辑。本篇指南以@open-pencil/vue官方文档为核心,围绕“composable 优先、无头原语辅助”的架构哲学,讲解如何用usePosition、useFillControls等 composable 构建面板状态,用PropertyListRoot这类无外观(headless)原语组织可复用的列表结构,并深入BindableValueRoot的绑定语义,最终让你能独立搭建一套专业、可维护且尊重设计数据的属性面板。

一、面板架构哲学:Composable 优先,原语补位

@open-pencil/vue的属性面板设计有两个明确分工(见 property-panels.md):

  • 面板主要需要“选区计算值 + 修改动作”时,优先使用 composable。因为面板的核心工作是把x、opacity、fills这类选中节点的属性投影成响应式状态,再把用户的输入写回节点——这正是 composable 的职责。
  • 面板需要可复用的数组/列表结构时,使用无头原语(headless primitive),例如PropertyListRoot。当难点在于协调重复的列表、树或插槽结构时,由原语负责结构编排,外观完全交给调用方。

这一原则在 packages/vue/src/index.ts 的导出结构中可以得到印证:controls/目录集中存放usePosition、useLayout等控制 composable,primitives/目录则存放PropertyList、BindableValue等无头组件。

二、常用控制 Composables:标准属性区块的起点

2.1 单值属性区块

文档列出的五个标准 composable 对应面板中最常见的五个分区:

Composable用途源码位置
usePosition()位置与尺寸(x/y、宽高、旋转、对齐、翻转)controls/position/use.ts
useLayout()自动布局(方向、间距、内边距、网格轨道、尺寸策略)controls/layout/use.ts
useAppearance()可见性、不透明度、圆角半径controls/appearance/use.ts
useTypography()文本排版(字体族、字重、字号等格式化控制)controls/typography/use.ts
useExport()导出相关属性controls/下的导出控制

以 usePosition 为例,源码显示它基于useNodeProps()获取当前选区的nodes、node、active、isMulti状态,然后派生出一组计算属性:

  • x、y、width、height:直接映射到选中(活动)节点,无节点时回退为0;
  • rotation:对旋转角做了Math.round取整,适合直接渲染进数字输入框;
  • 动作层:updateProp(key, value)(即时更新)、commitProp(key, value, previous)(提交到撤销栈)、cancelProp(key)(取消预览);
  • 多选能力:align(axis, pos)调用editor.alignNodes,flip(axis)调用editor.flipNodes,rotate(degrees)调用editor.rotateNodes,全部基于ids(选中节点 id 列表)批量执行。

也就是说,usePosition不只是返回四个数字,它把“单选编辑、多选对齐/翻转/旋转、撤销提交”整套交互都封装好了。类似的,useLayout 内部由createLayoutSelectionState、createPaddingActions、createLayoutActions、createGridTrackActions几个工厂函数组合而成,覆盖自动布局方向、统一/对称/独立内边距、网格轨道增删等能力;useAppearance 则通过createAppearanceState+createAppearanceActions提供可见性与圆角编辑,并带有expandedCornerNodeId状态用于展开独立圆角输入。

2.2 列表型属性区块

对于填充(fills)、描边(strokes)、效果(effects)这类“一组对象”的属性,文档推荐三个 composable:

  • useFillControls()
  • useStrokeControls()
  • useEffectsControls()

以 useFillControls 为例,源码极其简洁但信息量很大:它继承useColorVariableBinding('fills')的全部行为,再额外暴露一个defaultFill——即DEFAULT_SHAPE_FILL(来自@open-pencil/core/constants)。这个默认值正是“添加填充”按钮要用到的素材:用户在面板里点“Add fill”,得到的就是一个符合 SDK 约定的默认填充对象,而不是调用方临时拼出来的结构。

三、示例实战一:位置与尺寸面板

文档给出的位置面板示例完整复刻如下:

<script setup lang="ts"> import { usePosition } from '@open-pencil/vue' const { x, y, width, height, updateProp, commitProp } = usePosition() </script> <template> <div class="grid grid-cols-2 gap-2"> <input :value="x" @input="updateProp('x', Number(($event.target as HTMLInputElement).value))" /> <input :value="y" @input="updateProp('y', Number(($event.target as HTMLInputElement).value))" /> <input :value="width" @input="updateProp('width', Number(($event.target as HTMLInputElement).value))" /> <input :value="height" @input="updateProp('height', Number(($event.target as HTMLInputElement).value))" /> </div> </template>

结合源码可以把这个示例的每个细节讲透:

  1. updateProp与commitProp的分工。在 usePosition 中,两者都来自usePropScrub(editor)。updateProp是即时写入选区节点(适合@input拖拽/连续输入时使用),commitProp则把“新值 + 旧值”一起提交给撤销系统(适合输入结束、失焦、@change时调用)。正确的做法是:拖动过程中持续updateProp,松手/失焦时commitProp,这样用户按Ctrl/Cmd+Z能整段回退。
  2. 数字安全。示例里用Number(...)把输入框字符串转成数值。updateProp的key类型是NumericNodeProperty(来自@open-pencil/scene-graph),只接受数字,所以在绑定到input事件时必须自己做转换与过滤。
  3. 多选行为。当isMulti为真时,updateProp/commitProp作用于nodes.value全部节点——这就是多对象同步编辑的实现路径。若选中多个尺寸不同的节点,width/height等计算属性取值自“活动节点”(node.value),面板需要结合prop(属性合并状态)自行呈现“mixed(混合)”提示。

四、示例实战二:填充列表面板

文档给出的填充面板示例是“composable + 无头原语”协同的完整范本:

<script setup lang="ts"> import { PropertyListRoot, useEditorPropertyList, useFillControls } from '@open-pencil/vue' const fillControls = useFillControls() const fills = useEditorPropertyList('fills') </script> <template> <PropertyListRoot prop-key="fills" :items="fills.items.value" :mixed="fills.isMixed.value" @add="fills.actions.add" @remove="fills.actions.remove" v-slot="{ items, actions }" > <div v-for="(fill, index) in items" :key="index"> {{ fill.type }} <button @click="actions.remove(index)">Supprimer</button> </div> <button @click="actions.add(fillControls.defaultFill)">Ajouter un remplissage</button> </PropertyListRoot> </template>

(示例按钮文案按法语原文保留为 “Supprimer / Ajouter un remplissage”,英文版对应 “Remove / Add fill”。)

这里有三层值得展开:

  1. useEditorPropertyList('fills')是列表数据的来源。查看 controls/property-list/use.ts 的源码,它返回items(当前活动节点的fills数组)、isMixed(多选时数组属性是否混合)、isMulti、active,以及一整套actions:

    • add(item):单对象时向节点数组追加(深拷贝structuredClone防共享引用);多对象时通过editor.undo.runBatch(label, apply)把对每个节点的写入包进一个批量撤销操作;
    • remove(index):过滤掉指定下标,多对象同样走批量;
    • update(index, item)/patch(index, changes):通过useUndoBatch的batch.ensure合并连续编辑,patch只合并部分字段变化;
    • toggleVisibility(index):切换单项visible,且每次读取editor.getNode(node.id)获取最新节点,避免闭包里的过期引用;
    • reorder(fromIndex, toIndex):用moveItem在数组内移动元素。

    也就是说,你在模板里拿到的fills.actions.add/remove,底层已经在处理“多选批量写入 + 撤销分组 + 引用隔离”这些脏活。

  2. PropertyListRoot是纯结构协调者。查看 PropertyListRoot.vue:它只负责接收prop-key、items、mixed、disabled,把add/remove/update/patch/toggleVisibility/reorder六个事件转发成 slot 里的actions对象,并通过providePropertyList提供给子组件(PropertyListItem、PropertyListAdd、PropertyListRemove、PropertyListVisibility等配套原语共享同一上下文,见 primitives/PropertyList)。它不渲染任何样式,连默认的disabled保护也只在actions里拦截事件转发。

  3. useFillControls().defaultFill是“添加”按钮的素材。没有它,你得自己拼一个合法的 fill 对象;有了它,actions.add(fillControls.defaultFill)一行就完成了。

五、变量绑定字段:BindableValueRoot 的语义与最佳实践

当面板字段可以引用变量(variable)或外部设计令牌(design token)时,文档要求用BindableValueRoot包住该字段。它在 primitives/BindableValue 下实现,BindableValueRoot.vue的源码展示了完整的“绑定感知”状态机(BindableValueRoot.vue):

  • 它解析provider(providerProp或注入的provideBindingProvider),缺少时直接抛错,保证误用能在开发期暴露;
  • state通过provider.getState(targets)计算,取值包括unbound、bound、unresolved、mixed,并渲染成data-unbound、data-bound、data-unresolved、data-mixed、data-picker-open、data-policy等属性(stateAttrs),让外部样式可以纯靠 CSS 属性选择器区分状态;
  • policy默认值为'detach-on-edit',其余可选值包括readonly-when-bound、edit-variable(见BindableValueRootProps定义)。

文档同时给出了五条绑定交互的硬性规范,这是构建“尊重用户数据”的面板必须遵守的:

  1. 空闲时显示变量身份,解析值放在辅助 UI:字段非编辑态应显示变量名(OpenPencil 应用皮肤里是紫色的变量名胶囊),解析出的计算值通过 tooltip 等支持性 UI 暴露;
  2. 聚焦或打开变量选择器绝不能破坏绑定:把焦点移入字段不等于用户要编辑,因此“聚焦即分离”是禁止的;
  3. 只在真正发生修改时才应用detach-on-edit/readonly-when-bound/edit-variable:这三种策略都绑定在“用户实际改动值”这一事件上,而不是“字段获得焦点”上;
  4. 显式的解除绑定动作放在选择器内部,而不是放在字段旁边一个容易误触的一次性图标按钮上;
  5. 把“替换绑定、编辑时分离、多对象更新”放进同一次 provider 批量操作:源码里beginProviderBatch/commitProviderBatch/rollbackProviderBatch的交互批处理(supportsInteractionBatch)正是为此设计——一次交互要么整体生效,要么整体回滚,配合batchLabel(默认'Edit bound value')进入撤销栈。

关于第 1 条的“紫色胶囊”,文档特别注明这是 OpenPencil 应用皮肤的实现选择:字段空闲时显示紫色变量名,NumberField进入编辑模式时才揭示解析后的数字值。BindableValueRoot本身是无外观的,自定义编辑器外壳可以完全用不同的方式呈现同一套 headless 状态。

六、如何选择 API:一句经验法则

文档最后给出了一条可操作的判断标准:

  • 需要直接控制逻辑(状态 + 动作)→ 用 composable。典型场景:位置、尺寸、透明度、圆角、排版、导出等标准分区,直接用usePosition、useAppearance、useTypography等;
  • 难点在重复的列表/树/插槽协调 → 用结构原语。典型场景:填充、描边、效果这类可增删、可排序、可见性可切换的数组属性,用PropertyListRoot配合useEditorPropertyList;
  • 两者不是互斥的:填充面板示例就是“composable(useFillControls、useEditorPropertyList)提供数据与动作,原语(PropertyListRoot)负责结构”的组合用法。实践中,一个复杂面板通常是若干 composable 加上若干原语的混合体。

七、相关 API 速查

围绕本文主题,@open-pencil/vue还提供以下 API 供深入阅读:

  • usePosition
  • useLayout
  • useAppearance
  • useTypography
  • useFillControls
  • useStrokeControls
  • useEffectsControls
  • PropertyListRoot

如需了解这些 composable 与编辑器实例的关系(如useEditor、useNodeProps、useUndoBatch),可进一步阅读 SDK 架构文档 与 入门指南。

结语

OpenPencil 的属性面板 API 把“设计工具的脏活”封装成了清晰的层次:composable 承载选区状态与写入动作,无头原语承载列表/绑定结构,BindableValueRoot则用一套严谨的交互规范保护变量绑定不被误伤。掌握“先看状态来自哪个 composable、再看结构该用哪个原语”的判断方法,你就能在自定义编辑器外壳中快速复刻出专业级属性面板,同时保持代码的可测试性与可复用性。

  • 前端
  • 桌面应用
  • AI 应用
  • MCP 服务

【免费下载链接】open-pencil

AI-native design editor. Open-source Figma alternative.

项目地址:https://gitcode.com/gh_mirrors/op/open-pencil
点击查看免费下载

相关推荐

上一篇:CSS Scope Inline与Surreal:构建无构建前端生态的最佳实践
下一篇:nix-darwin 高级用法:自定义模块开发与扩展指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 2:52:10

异次元发卡网插件化架构与强制登录实战指南

简介&#xff1a;这是一套基于原生PHP开发的异次元发卡网完整源码&#xff0c;面向中小型数字商品经营者、独立开发者及二次开发需求者&#xff0c;解决在线虚拟商品&#xff08;如账号、卡密、API服务&#xff09;快速上架、安全交付与多渠道收款等核心问题。资源包共2000个文…

作者头像 李华
网站建设 2026/9/28 2:49:53

Woodpecker Workflow 语法完全指南:steps、条件执行与依赖编排实战

CI/CDDevOps 【免费下载链接】woodpecker Woodpecker is a simple, yet powerful CI/CD engine with great extensibility. 项目地址&#xff1a; https://gitcode.com/gh_mirrors/wo/woodpecker 点击查看 免费下载 本篇指南以 Woodpecker CI/CD 引擎的 workflow 配置文件语法…

作者头像 李华