摘要:graph TD
A[物料组件] --> B[物料 Schema]
B --> C[Setter 配置]
C --> D[SetterManager]
D --> E[Setter 注册表]
D --> F[SetterWrapper]
F --> G[SetterView]
G --> H[自定义 Setter 组件]
H --> I[值发送]
I --&RadioSetterElRadioString单选按钮组TagSetter自定义输入String标签管理界面SizeSetter自定义选择String尺寸预设选择器SectionSetter自定义 UIString可视化部分分隔符CssSetter自定义编辑器StringCSS 属性编辑器VanIconSetter自定义选择器StringVant 图标选择
graph TD
A[物料组件] --> B[物料 Schema]
B --> C[Setter 配置]
C --> D[SetterManager]
D --> E[Setter 注册表]
D --> F[SetterWrapper]
F --> G[SetterView]
G --> H[自定义 Setter 组件]
H --> I[值发送]
I --> J[PropModel 更新]
D --> K[自定义 Setter 注册]
K -.->|注册| D
D -.->|根据类型获取| K
D -.->|获取| F
💡 内置 setter 默认使用 Element Plus 组件,确保视觉一致性。name: 'JsonSetter', props: { type: 'Object' } }, 'ExpressionSetter' ], options: ['static', 'api', 'local'] }
SetterWrapper 集成 SetterWrapper 组件连接物料 schemas 和 setter 实现,提供自动类型检测、上下文注入和值规范化。
自定义设置器与属性编辑器
自定义 Setter 和属性编辑器构成了 VTJ 可扩展属性配置系统的基础,使开发者能够为物料组件属性创建专门的输入控件。该系统提供了基于插件的架构,与设计器环境无缝集成,同时为属性编辑场景提供最大的灵活性。
架构概览
Setter 系统通过集中管理模式运行,支持动态注册、基于类型的发现和组件生命周期管理。该架构由三个核心层组成:Setter 管理层、Wrapper 集成层和组件渲染层。
Setter 管理系统
SetterManager
SetterManager作为所有属性编辑器的中央注册表,管理 Setter 生命周期并提供注册接口。class SetterManager { private setters: Record<string, Setter> = {}; public defaultSetter: Setter = defaultSetter; // 核心方法 register(setter: Setter); get(name: string): Setter; set(name: string, setter: Partial<Setter>); getByType(type: BlockPropDataType): string[]; }主要职责:
register()方法添加自定义 setterget()方法按名称获取 setterset()方法更新现有 setter 配置getByType()查询与特定数据类型兼容的 setterSetter 接口
Setter协议定义了所有属性编辑器的契约:interface Setter { name: string; component: VueComponent; type: BlockPropDataType; props?: Record<string, any>; }属性:
name:Setter 的唯一标识符component:实现编辑器 UI 的 Vue 组件type:Setter 处理的数据类型(String, Boolean, Number, Object, Array, Function)props:传递给组件的默认配置内置 Setters
VTJ 提供了一套全面的预配置 setter,涵盖常见的属性编辑场景:
{{ }}语法的表达式编辑器创建自定义 Setters
基本 Setter 实现
通过实现一个 Vue 组件来创建自定义 setter,该组件通过
v-model或显式 props 接收值,并通过事件发出更改。示例:日期 Setter
<template> <ElDatePicker v-model="localValue" type="date" format="YYYY-MM-DD" value-format="YYYY-MM-DD" @change="handleChange" /> </template> <script lang="ts" setup> import { ref, watch } from "vue"; import { ElDatePicker } from "element-plus"; import { isJSExpression } from "@vtj/renderer"; export interface Props { modelValue?: string | any; placeholder?: string; disabled?: boolean; } const props = withDefaults(defineProps<Props>(), { placeholder: "Select date", disabled: false, }); const emit = defineEmits<{ change: [value: string]; }>(); const localValue = ref<string | undefined>( isJSExpression(props.modelValue) ? undefined : props.modelValue, ); watch( () => props.modelValue, (v) => { localValue.value = isJSExpression(v) ? undefined : (v as string); }, ); const handleChange = (value: string) => { emit("change", value); }; defineOptions({ name: "DateSetter", }); </script>示例:富文本 Setter
<template> <div class="rich-text-setter"> <ElButton v-if="!isEditing" @click="startEdit" :disabled="disabled"> Edit Content </ElButton> <div v-else class="editor-container"> <textarea v-model="editorValue" :disabled="disabled" rows="10" /> <div class="actions"> <ElButton @click="cancelEdit">Cancel</ElButton> <ElButton type="primary" @click="saveEdit">Save</ElButton> </div> </div> </div> </template> <script lang="ts" setup> import { ref, computed } from "vue"; import { ElButton } from "element-plus"; import { isJSExpression } from "@vtj/renderer"; export interface Props { modelValue?: string; disabled?: boolean; } const props = withDefaults(defineProps<Props>(), { disabled: false, }); const emit = defineEmits<{ change: [value: string]; }>(); const isEditing = ref(false); const editorValue = ref(""); const startEdit = () => { const value = props.modelValue; editorValue.value = isJSExpression(value) ? "" : (value as string) || ""; isEditing.value = true; }; const saveEdit = () => { emit("change", editorValue.value); isEditing.value = false; }; const cancelEdit = () => { isEditing.value = false; }; defineOptions({ name: "RichTextSetter", }); </script>具有上下文访问的高级 Setter
高级 setter 可以访问渲染上下文以提供变量绑定、表达式求值和项目元数据等功能。
<template> <div class="advanced-expression-setter"> <ElInput v-model="textValue" @change="handleChange" :disabled="disabled"> <template #prefix>{{ prefix }}</template> <template #suffix>{{ suffix }}</template> </ElInput> <ElButton v-if="context && showBindButton" @click="showVariablePicker" size="small" text > Bind Variable </ElButton> </div> </template> <script lang="ts" setup> import { ref, watch, computed } from "vue"; import { ElInput, ElButton } from "element-plus"; import { type JSExpression } from "@vtj/core"; import { isJSExpression } from "@vtj/renderer"; import { type Context } from "@vtj/renderer"; import { expressionValidate } from "../../utils"; export interface Props { modelValue?: JSExpression | string; context?: Context; disabled?: boolean; } const props = withDefaults(defineProps<Props>(), { disabled: false, }); const emit = defineEmits<{ change: [value: JSExpression]; }>(); const prefix = `{{`; const suffix = `}}`; const showBindButton = computed(() => { return isJSExpression(props.modelValue) || !props.modelValue; }); const createValue = (value: JSExpression | string = "") => { return { type: "JSExpression", value: isJSExpression(value) ? value.value : value, } as JSExpression; }; const textValue = ref(createValue(props.modelValue).value); watch( () => props.modelValue, (v) => { textValue.value = createValue(v).value; }, { immediate: true }, ); const validate = (value: JSExpression) => { return expressionValidate(value, props.context, true); }; const handleChange = (value: string) => { const expression: JSExpression = { type: "JSExpression", value, }; if (validate(expression)) { emit("change", expression); } }; const showVariablePicker = () => { // 实现变量选择器对话框 console.log("Show variable picker", props.context); }; defineOptions({ name: "AdvancedExpressionSetter", }); </script>Setter 注册
注册方法
方法 1:通过 SetterManager 直接注册
import { setterManager } from "@vtj/designer"; import DateSetter from "./setters/DateSetter.vue"; setterManager.register({ name: "DateSetter", component: DateSetter, type: "String", });方法 2:基于插件的注册
import type { Plugin } from "@vtj/designer"; import { setterManager } from "@vtj/designer"; import DateSetter from "./setters/DateSetter.vue"; const customSettersPlugin: Plugin = { name: "CustomSettersPlugin", setup(ctx) { setterManager.register({ name: "DateSetter", component: DateSetter, type: "String", props: { format: "YYYY-MM-DD", }, }); return { // 插件返回值 }; }, }; export default customSettersPlugin;动态 Setter 配置
使用
set()方法在运行时修改现有 setter 配置:setterManager.set("DateSetter", { props: { format: "DD/MM/YYYY", disabled: false, }, });物料 Schema 集成
在物料 Schema 中配置 Setters
Setter 在物料组件属性 schemas 中使用
setter字段进行配置:{ componentName: 'MyCustomComponent', props: [ { name: 'title', title: 'Title', type: 'String', defaultValue: 'Default Title', setter: { name: 'StringSetter', props: { maxlength: 50, showWordLimit: true } } }, { name: 'themeColor', title: 'Theme Color', type: 'String', setter: 'ColorSetter' }, { name: 'publishDate', title: 'Publish Date', type: 'String', setter: { name: 'DateSetter', label: 'Select Date' } }, { name: 'content', title: 'Content', type: 'Object', setter: [ 'StringSetter', { name: 'RichTextSetter', label: 'Rich Text' }, 'ExpressionSetter' ] } ] }多 Setter 支持
属性可以支持多种 setter 类型,允许用户在不同的编辑模式之间切换:
{ name: 'dataSource', title: 'Data Source', type: 'String', setter: [ 'SelectSetter', { name: 'JsonSetter', props: { type: 'Object' } }, 'ExpressionSetter' ], options: ['static', 'api', 'local'] }SetterWrapper 集成
SetterWrapper组件连接物料 schemas 和 setter 实现,提供自动类型检测、上下文注入和值规范化。组件属性
值流
最佳实践
性能优化
表达式处理
始终检查
JSExpression类型,以防止当值包含绑定表达式时出现运行时错误:import { isJSExpression } from "@vtj/renderer"; const computedValue = computed(() => { if (isJSExpression(props.modelValue)) { return undefined; // 或显示表达式指示器 } return props.modelValue; });类型安全
导出 setter props 的 TypeScript 接口以启用类型检查:
export interface DateSetterProps { modelValue?: string; placeholder?: string; format?: string; disabled?: boolean; minDate?: Date; maxDate?: Date; } const props = withDefaults(defineProps<DateSetterProps>(), { placeholder: "Select date", format: "YYYY-MM-DD", });可访问性
确保自定义 setter 遵循可访问性指南:
高级场景
条件 Setters
实现根据上下文显示不同 setter 的逻辑:
异步数据加载
为 SelectSetter 动态加载选项:
故障排除
常见问题
@change事件发出新值type是否与实际数据类型匹配调试模式
为 setter 操作启用详细日志记录:
import { logger } from "@vtj/utils"; // 记录 setter 注册 setterManager.register({ name: "CustomSetter", component: CustomSetterComponent, type: "String", }); logger.info("CustomSetter registered"); // 记录 setter 检索 const setter = setterManager.get("CustomSetter"); logger.debug("Setter retrieved:", setter);后续步骤
掌握自定义 setter 后,探索以下相关主题:
参考资料