自定义组件开发
自定义组件开发
内置组件之外,你可以注册自己的组件类型,让它像内置组件一样被拖拽、调参、绑定数据、参与交互与导出。
开发所需 API 均由 @vp/sdk 直接导出(二次开发工程包内同样可用):VpWidget 基类、useWidget() / useWidgetSafe()(组件内取模型实例)、类型 VpWidgetModule / VpOptionGroupDef。完整可运行范例:web/apps/sdk-demo/src/park/widgets/alarmTicker.ts(告警跑马灯:选项蓝图 + field 绑定 + 响应式取数 + 挂载注册)、web/apps/safety-demo/src/widgets/riskMatrix.ts(5×5 风险矩阵:纯函数聚合内核与渲染分离,npm 消费形态下从编译产物导入)。
组件模块的形状
一个组件 = 一个 VpWidgetModule:模型类 + Vue 渲染组件 + 元信息。以内置文本组件为例:
import type { VpWidgetModule } from '@vp/sdk'
import VpText from './VpText.vue'
import { VpTextWidget } from './VpTextWidget'
export const textModule: VpWidgetModule = {
TheWidget: VpTextWidget, // 模型类:继承 VpWidget,静态 defineOptions() 声明所有选项
component: VpText, // Vue 渲染组件(读 options 出画面)
meta: {
type: 'basic-text',
name: '文本',
category: 'text',
defaultSize: [300, 60],
defaultOptions: { text: '文本' },
},
}| 字段 | 说明 |
|---|---|
TheWidget | 模型类,继承 VpWidget;静态 defineOptions() 定义选项蓝图,实例方法可定义 defaultName 等 |
component | Vue 组件,负责渲染(通过注入拿到组件模型,读 options) |
meta.type | 全局唯一类型串(工程 JSON 里 widget.type 用它引用) |
meta.name / category | 显示名 / 分类(组件库分组);type / name / category 三项必填 |
meta.defaultSize | 默认尺寸 [宽, 高] |
meta.defaultOptions | 拖入时的初始 options(可选) |
defineOptions():声明选项蓝图
defineOptions() 返回一棵分组树:style.* 下是样式选项、data.* 下是数据 / 绑定选项。叶子项形如 { name, alias, type, default };type 以 field(...) 开头的即数据绑定项(值为三元组数组)。最后 ...VpWidget.defineOptions() 把基类通用选项铺在末尾。
import { VpWidget, type VpOptionGroupDef } from '@vp/sdk'
export class VpTextWidget extends VpWidget {
get defaultName(): string {
return (this.getOption('text') as string) || '文本'
}
static defineOptions(): VpOptionGroupDef[] {
return [
{
style: {
basic: { children: [
{ name: 'text', alias: '文本', type: 'string', default: '文本' },
{ name: 'text-arrangement', alias: '文本排列', type: 'select(radioGroup)', default: 'horizontal',
selectChoices: [
{ value: 'horizontal', label: '水平排列' },
{ value: 'vertical', label: '垂直排列' },
] },
] },
transform: { children: [{ name: 'size', default: [300, 60] }] }, // 覆盖基类默认尺寸
},
data: {
binding: { alias: '字段映射', children: [
{ name: 'value-field', alias: '动态文本', type: 'field(recommend=string,min=0,max=1)',
tip: '绑定一个字段,显示其首行值' },
] },
},
},
...VpWidget.defineOptions(), // 铺底基类通用选项(position/size/opacity/背景/边框/联动…)
]
}
}- 选项类型
type:常见string/number/boolean/color/select(...)/font/vector<...>/field(...)(绑定)等,与属性面板控件一一对应。 - 绑定约束:
field(recommend=number,min=1,max=1)——recommend推荐字段类型,min/max可绑字段个数。 - 运行期:
defineOptions()的分组树经@vp/core的mergeGroups深合并(base 铺底)→buildBlueprint压平成{ 选项名: { type, default, group, alias, ... } },属性面板与取值都据此工作。
只做「一次性、外观简单」的组件,
TheWidget可省略,meta+ 一个 Vuecomponent即可(见下最小示例)。要参与调参 / 绑定 / 导出的正式组件,建议提供完整模型类与defineOptions()。
渲染组件内取模型与数据:useWidget()
Vue 渲染组件在 setup 里调 useWidget() 拿到当前组件模型实例(响应式):读选项用 getOption,读绑定数据用 getBoundFields + afterLinkageAndFilterRows(内部带失效缓存,可直接放 computed,数据变化自动重渲):
import { VpWidget, useWidget, type VpFieldRef } from '@vp/sdk'
import { computed, defineComponent, h } from 'vue'
export const MyList = defineComponent({
setup() {
const widget = useWidget()
const rows = computed(() => {
const fields = widget.getBoundFields('value-field') as VpFieldRef[]
return fields.length ? (widget.afterLinkageAndFilterRows(fields) ?? []) : []
})
return () => h('div', rows.value.map((r, i) => h('div', { key: i }, JSON.stringify(r))))
},
})注册组件
两种方式(择一):
① 通过 SDK 挂载选项 widgets(推荐,见二次开发工程包):
import { defineComponent, h } from 'vue'
import { createVpViewer, type VpMountOptions } from '@vp/sdk'
const Gauge = defineComponent({
name: 'Gauge',
setup: () => () => h('div', { style: 'color:#5cc8ff' }, '自定义仪表盘'),
})
const options: VpMountOptions = {
// 最小组件模块:TheWidget 可省略(缺省用 VpWidget 基类),component + meta 即可
// meta 的 type / name / category 三项必填
widgets: [{ type: 'my-gauge', module: { component: Gauge, meta: { type: 'my-gauge', name: '仪表盘', category: 'chart' } } }],
async onReady(ctx) {
await ctx.addWidget('my-gauge') // 登记后即可程序化创建,或在工程 JSON 里以该 type 引用
},
}
createVpViewer('#app', body, options)② 直接调用 registerWidgetType(在挂载前):
import { registerWidgetType } from '@vp/sdk'
registerWidgetType('my-gauge', { component: Gauge, meta: { type: 'my-gauge', name: '仪表盘', category: 'chart' } })注册后,该 type 就能:在工程 JSON 里被引用、被 ctx.addWidget(type) 创建、出现在组件序列化 / 导出里。
维护与内置组件同步
若你是在本仓库内新增内置组件,除了加组件模块,还要:在 web/packages/widgets/src/index.ts 加 registerWidgetType('类型串', xxxModule);并重跑组件 schema 导出脚本、更新组件手册。详细清单见仓库 .cursor/skills/generate-vp-project/reference/maintenance.md。
延伸阅读:SDK 与扩展开发(widgets / registerWidgetType)· 发布与部署(二次开发工程包)· 组件手册(内置组件的参数蓝图作参考)· 二次开发脚本。