AI 驱动的 Vue3 应用开发平台 深入探究(十五):扩展与定制之自定义设置器与属性编辑器

小新 正七品 (知县) 2026-03-17 03:55 1 0 返回 码工码农
小新 正七品 (知县) 楼主
2026-03-17 03:55
第1楼

摘要: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 图标选择

💡 内置 setter 默认使用 Element Plus 组件,确保视觉一致性。name: 'JsonSetter', props: { type: 'Object' } }, 'ExpressionSetter' ], options: ['static', 'api', 'local'] }

SetterWrapper 集成 SetterWrapper 组件连接物料 schemas 和 setter 实现,提供自动类型检测、上下文注入和值规范化。


自定义设置器与属性编辑器

自定义 Setter 和属性编辑器构成了 VTJ 可扩展属性配置系统的基础,使开发者能够为物料组件属性创建专门的输入控件。该系统提供了基于插件的架构,与设计器环境无缝集成,同时为属性编辑场景提供最大的灵活性。

架构概览

Setter 系统通过集中管理模式运行,支持动态注册、基于类型的发现和组件生命周期管理。该架构由三个核心层组成:Setter 管理层、Wrapper 集成层和组件渲染层。

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 管理系统

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() 方法添加自定义 setter
  • 检索:使用 get() 方法按名称获取 setter
  • 修改:使用 set() 方法更新现有 setter 配置
  • 类型发现:使用 getByType() 查询与特定数据类型兼容的 setter

💡 请始终在物料初始化之前注册自定义 setter,以确保在组件渲染期间可用。当需要自定义而不完全替换时,使用 set() 覆盖内置 setter 配置。

Setter 接口

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,涵盖常见的属性编辑场景:

Setter 名称组件类型描述
StringSetterElInputString标准文本输入字段
BooleanSetter-Boolean布尔值的复选框/切换
NumberSetterElInputNumberNumber带步控的数值输入
ColorSetterElColorPickerString颜色选择器
SelectSetterElSelectString选项下拉选择
ExpressionSetter自定义输入Object{{ }} 语法的表达式编辑器
JsonSetter自定义编辑器Object/Array用于复杂数据结构的 JSON 编辑器
FunctionSetter自定义编辑器Function函数体编辑器
IconSetter自定义选择器String图标选择界面
ImageSetter自定义上传器String图片上传/选择
FileSetter自定义上传器String文件上传控件
SliderSetterElSliderNumber范围滑块输入
RadioSetterElRadioString单选按钮组
TagSetter自定义输入String标签管理界面
SizeSetter自定义选择String尺寸预设选择器
SectionSetter自定义 UIString可视化部分分隔符
CssSetter自定义编辑器StringCSS 属性编辑器
VanIconSetter自定义选择器StringVant 图标选择

💡 内置 setter 默认使用 Element Plus 组件,确保视觉一致性。利用 props 配置自定义行为,而无需创建新的 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 实现,提供自动类型检测、上下文注入和值规范化。

组件属性

属性类型默认值描述
contextContextnull用于表达式求值的渲染上下文
currentBlockModelnull当前正在编辑的组件
namestring-属性名称
labelstring-显示标签
titlestring-工具提示/描述
valueanyundefined当前属性值
settersstring|MaterialSetter|Array必填Setter 配置
optionsArray[]SelectSetter/RadioSetter 的选项
variablebooleantrue启用表达式绑定
removablebooleanfalse允许属性移除
disabledbooleanfalse禁用编辑

值流

sequenceDiagram
    participant 物料Schema
    participant SetterWrapper
    participant SetterView
    participant Setter组件
    participant PropModel
    物料Schema->>SetterWrapper: 属性配置
    SetterWrapper->>SetterWrapper: 计算Setters
    SetterWrapper->>SetterWrapper: 根据类型计算默认Setter
    SetterWrapper->>SetterView: 使用上下文渲染
    SetterView->>Setter组件: 传递Props和值
    Setter组件->>Setter组件: 用户交互
    Setter组件->>Setter组件: 验证值
    Setter组件->>SetterView: 发出更改事件
    SetterView->>SetterWrapper: 发出更改
    SetterWrapper->>PropModel: 更新PropModel
    PropModel->>PropModel: 标记为已设置/未设置

最佳实践

性能优化

<script lang="ts" setup>
import { markRaw, defineComponent } from "vue";

// 使用 markRaw 防止不必要的响应式
const setterComponent = markRaw(DateSetter);

// 对于复杂组件,避免深层响应式包装
const props = defineProps<{
  modelValue: any;
  options?: Record<string, any>[];
}>();
</script>

表达式处理

始终检查 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 遵循可访问性指南:

<template>
  <label :for="inputId" class="setter-label">
    {{ label }}
    <input
      :id="inputId"
      v-model="localValue"
      :aria-label="label"
      :aria-invalid="hasError"
      @change="handleChange"
    />
    <span v-if="errorMessage" class="error-message" role="alert">
      {{ errorMessage }}
    </span>
  </label>
</template>

高级场景

条件 Setters

实现根据上下文显示不同 setter 的逻辑:

<template>
  <component
    :is="currentSetter.component"
    v-bind="currentSetter.props"
    :model-value="value"
    @change="handleChange"
  />
</template>

<script lang="ts" setup>
import { computed } from "vue";
import { setterManager } from "@vtj/designer";

const props = defineProps<{
  value: any;
  context: any;
}>();

const currentSetter = computed(() => {
  if (props.context?.isComplex) {
    return setterManager.get("JsonSetter");
  }
  return setterManager.get("StringSetter");
});
</script>

异步数据加载

为 SelectSetter 动态加载选项:

<template>
  <ElSelect v-model="selectedValue" :loading="loading" @change="handleChange">
    <ElOption
      v-for="item in dynamicOptions"
      :key="item.value"
      :label="item.label"
      :value="item.value"
    />
  </ElSelect>
</template>

<script lang="ts" setup>
import { ref, onMounted } from "vue";
import { ElSelect, ElOption } from "element-plus";

const loading = ref(false);
const dynamicOptions = ref<Array<{ label: string; value: any }>>([]);
const selectedValue = ref();

const emit = defineEmits<{
  change: [value: any];
}>();

const loadOptions = async () => {
  loading.value = true;
  try {
    const response = await fetch("/api/options");
    dynamicOptions.value = await response.json();
  } finally {
    loading.value = false;
  }
};

onMounted(loadOptions);

const handleChange = (value: any) => {
  emit("change", value);
};
</script>

故障排除

常见问题

问题原因解决方案
Setter 不显示注册时机在设计器初始化之前注册 setter
值未更新缺少 emit确保 @change 事件发出新值
表达式错误未传递上下文通过 SetterWrapper 提供有效上下文
类型不匹配类型声明不正确验证 type 是否与实际数据类型匹配
Props 未传递组件名称不匹配确保 setter 名称与注册匹配

调试模式

为 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 后,探索以下相关主题:

  • 物料 Schema 配置:学习如何使用 setter 规范配置物料组件
  • 自定义小部件和设计器面板:使用自定义面板和小部件扩展设计器 UI
  • 插件系统开发:将自定义 setter 和扩展打包到插件中
  • 创建自定义物料组件:开发具有属性编辑器的完整物料组件
  • Provider API 参考:了解用于上下文管理的 provider 系统

参考资料

暂无回复,快来抢沙发吧!

  • 1 / 1 页
敬请注意:文中内容观点和各种评论不代表本网立场!若有违规侵权,请联系我们