添加自定义属性
渲染器属性允许您为表单渲染器添加自定义的属性处理器,通过属性扩展,您可以处理组件配置、注入自定义逻辑、管理动态样式等。本文将以 级联样式(CSS) 功能作为参考样例,详细说明如何扩展一个新的渲染器属性功能。
在渲染器文档中可以查看内置表单组件及其所有可配置参数。
在源码中可以参考现有的属性扩展实现(如 src/utils/css.js、src/utils/behavior.js、src/utils/easySlots.js)。
注意
渲染器属性扩展需要在运行态渲染器和设计态渲染器中同时注册,确保设计器和运行环境都能正常工作。
扩展渲染器属性要改哪些地方?
在设计器中扩展一个新的渲染器属性功能,通常涉及 3 层:
- 属性处理器实现(Attribute Handler):定义属性的加载、监听、清理逻辑(如
src/utils/css.js) - 渲染器注册(formCreate.register):在 PC/移动端渲染器中注册属性处理器(如
src/form/elm.js、src/form/mobile.js) - 设计器配置组件(可选):如果需要可视化配置界面,需要创建配置组件(如
src/designer/style/CssInput.vue)
参考样例:级联样式(CSS)的接入链路
级联样式功能是一个典型的"属性扩展 + 可视化配置"的例子。相关文件一般可以在以下位置找到:
- 属性处理器:
src/utils/css.js- 处理 CSS 样式注入和清理 - 设计器配置组件:
src/designer/style/CssInput.vue- 提供可视化 CSS 编辑界面 - PC 渲染器注册:
src/form/elm.js- 注册css属性处理器 - 移动端渲染器注册:
src/form/mobile.js- 注册css属性处理器 - 表单工具注册:
src/utils/form.js- 同时注册到运行态和设计态渲染器
1. 属性处理器结构
一个标准的属性处理器需要实现以下接口:
export default function (fc) {
const attr = {
name: 'yourAttrName', // 属性名称,对应 rule.$yourAttrName
load(attr, rule, api) {
// 属性加载时的处理逻辑
// attr: 属性对象,可通过 attr.getValue() 获取值
// rule: 当前规则对象
// api: 表单 API 对象
},
watch(attr, rule, api) {
// 属性值变化时的处理逻辑(可选,默认使用 load)
},
deleted(attr) {
// 属性被删除时的清理逻辑(可选)
},
};
return attr;
}2. CSS 属性处理器要点
以 CSS 样式功能为参照,推荐你的属性处理器至少具备:
- 动态样式注入:通过创建
<style>标签动态注入样式 - 作用域隔离:为每个组件生成唯一的类名,避免样式冲突
- 生命周期管理:在组件删除时自动清理相关样式
- 值变化监听:支持样式值变化时实时更新
3. 设计器配置组件
如果需要在设计器右侧面板提供可视化配置界面,可以创建配置组件:
- 组件位置:
src/designer/style/YourAttrInput.vue - 通过
inject: ['designer']获取设计器上下文 - 通过
designer.setupState.activeRule.$yourAttr访问和修改属性值
扩展示例:新增一个 dataAttributes 数据属性扩展功能
下面给出一个可直接照抄的示例:新增一个 dataAttributes 属性扩展,允许为组件动态添加 HTML data 属性,用于集成第三方库或实现自定义交互。
1. 创建属性处理器
新建 src/utils/dataAttributes.js:
/**
* 数据属性扩展处理器
* 允许为组件动态添加 HTML data-* 属性
*/
export default function (fc) {
const dataAttributes = {
name: 'dataAttributes',
/**
* 属性加载时的处理逻辑
* @param {Object} attr - 属性对象
* @param {Object} rule - 当前规则对象
* @param {Object} api - 表单 API 对象
*/
load(attr, rule, api) {
const value = attr.getValue();
const prop = attr.getProp();
// 如果之前有设置过 data 属性,先清理
if (prop._dataAttributes) {
dataAttributes.deleted(attr);
}
// 如果有值,将 data 属性合并到组件的 props 中
if (value && typeof value === 'object') {
const dataProps = {};
Object.keys(value).forEach(key => {
// 确保 key 符合 data-* 属性命名规范
const dataKey = key.startsWith('data-') ? key : `data-${key}`;
dataProps[dataKey] = value[key];
});
// 将 data 属性合并到组件的 props 中
if (!prop.props) {
prop.props = {};
}
Object.assign(prop.props, dataProps);
// 保存原始值,用于后续清理
prop._dataAttributes = value;
// 同步规则变化
api.sync(rule);
}
},
/**
* 属性值变化时的处理逻辑
*/
watch(attr, rule, api) {
// 重新加载以应用新值
dataAttributes.load(attr, rule, api);
},
/**
* 属性被删除时的清理逻辑
*/
deleted(attr) {
const prop = attr.getProp();
if (prop._dataAttributes && prop.props) {
// 移除之前添加的 data 属性
Object.keys(prop._dataAttributes).forEach(key => {
const dataKey = key.startsWith('data-') ? key : `data-${key}`;
delete prop.props[dataKey];
});
delete prop._dataAttributes;
}
},
};
return dataAttributes;
}2. 注册属性处理器(PC 端)
在 src/form/elm.js 中引入并注册:
import cssAttr from '../utils/css';
import dataAttributesAttr from '../utils/dataAttributes'; // 新增
// ... 其他代码 ...
export function useAdvanced(formCreate) {
// ... 其他注册 ...
formCreate.register('css', cssAttr);
formCreate.register('dataAttributes', dataAttributesAttr); // 新增
}3. 注册属性处理器(移动端)
在 src/form/mobile.js 中引入并注册:
import cssAttr from '../utils/css';
import dataAttributesAttr from '../utils/dataAttributes'; // 新增
// ... 其他代码 ...
export function useAdvanced(formCreate) {
// ... 其他注册 ...
formCreate.register('css', cssAttr);
formCreate.register('dataAttributes', dataAttributesAttr); // 新增
}4. 注册到表单工具
在 src/utils/form.js 中引入并注册:
import cssAttr from './css';
import dataAttributesAttr from './dataAttributes'; // 新增
// ... 其他代码 ...
viewForm.register('css', cssAttr);
viewForm.register('dataAttributes', dataAttributesAttr); // 新增
designerForm.register('css', cssAttr);
designerForm.register('dataAttributes', dataAttributesAttr); // 新增5. 创建设计器配置组件(可选)
如果需要可视化配置界面,新建 src/designer/DataAttributesInput.vue:
<template>
<div class="_fd-data-attributes-input">
<el-badge type="info" is-dot :hidden="!configured">
<div class="_fd-data-attributes-btn" @click="visible = true">
<i class="fc-icon icon-setting"></i>
{{ t('class.dataAttributes') }}
</div>
</el-badge>
<el-dialog
class="_fd-data-attributes-input-dialog _fd-config-dialog"
v-model="visible"
destroy-on-close
:close-on-click-modal="false"
append-to-body
width="600px"
>
<template #header>
{{ t('class.dataAttributes') }}
<Warning :tooltip="t('warning.dataAttributes')"></Warning>
</template>
<el-form :model="formData" label-width="100px">
<el-form-item
v-for="(item, index) in formData.items"
:key="index"
:label="`属性 ${index + 1}`"
>
<div style="display: flex; gap: 8px; width: 100%;">
<el-input
v-model="item.key"
placeholder="属性名(如:track-id)"
style="flex: 1;"
/>
<el-input
v-model="item.value"
placeholder="属性值"
style="flex: 1;"
/>
<el-button
type="danger"
:icon="Delete"
@click="removeItem(index)"
circle
/>
</div>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Plus" @click="addItem">
添加属性
</el-button>
</el-form-item>
</el-form>
<template #footer>
<div>
<el-button @click="visible = false" size="default">
{{ t('props.cancel') }}
</el-button>
<el-button type="primary" @click="onOk" size="default">
{{ t('props.ok') }}
</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script>
import { defineComponent } from 'vue';
import { Plus, Delete } from '@element-plus/icons-vue';
import Warning from '../Warning.vue';
export default defineComponent({
name: 'DataAttributesInput',
components: {
Warning,
},
inject: ['designer'],
computed: {
t() {
return this.designer.setupState.t;
},
activeRule() {
return this.designer.setupState.activeRule;
},
configured() {
const attrs = this.activeRule?.$dataAttributes;
return attrs && Object.keys(attrs).length > 0;
},
},
data() {
return {
visible: false,
formData: {
items: [],
},
};
},
watch: {
visible(n) {
if (n) {
// 打开对话框时,将当前值转换为表单数据
const attrs = this.activeRule?.$dataAttributes || {};
this.formData.items = Object.keys(attrs).map(key => ({
key: key.replace(/^data-/, ''), // 移除 data- 前缀用于编辑
value: attrs[key],
}));
// 如果没有数据,至少显示一个空行
if (this.formData.items.length === 0) {
this.formData.items.push({ key: '', value: '' });
}
}
},
},
methods: {
addItem() {
this.formData.items.push({ key: '', value: '' });
},
removeItem(index) {
this.formData.items.splice(index, 1);
},
onOk() {
// 将表单数据转换为对象格式
const attrs = {};
this.formData.items.forEach(item => {
if (item.key && item.value !== undefined) {
// 确保 key 符合 data-* 命名规范
const key = item.key.startsWith('data-')
? item.key
: `data-${item.key}`;
attrs[key] = item.value;
}
});
// 更新规则属性
this.activeRule.$dataAttributes = Object.keys(attrs).length > 0 ? attrs : undefined;
this.visible = false;
},
},
});
</script>
<style scoped>
._fd-data-attributes-input .el-badge {
width: 100%;
}
._fd-data-attributes-btn {
margin-left: 6px;
background: var(--fc-style-bg-color-1);
border-radius: 5px;
color: var(--fc-style-color-1);
display: flex;
align-items: center;
font-size: 12px;
padding: 2px 6px;
cursor: pointer;
}
._fd-data-attributes-input-dialog .el-dialog__body {
padding: 20px;
}
</style>6. 集成到设计器配置面板(可选)
如果创建了配置组件,需要将其添加到设计器的基础配置中。该项目的配置面板项需要加在 /src/config/base/field.js 里。
import { localeOptions } from '../../utils';
export default function field({ t }) {
return [
// ... 其他配置项 ...
{
type: 'FieldInput',
field: 'field',
value: '',
title: t('form.field'),
warning: t('warning.field'),
},
// ... 其他配置项 ...
// 添加数据属性配置组件
{
type: 'DataAttributesInput',
field: '$dataAttributes',
title: '',
wrap: { show: false },
col: { show: false },
},
];
}说明:
type: 'DataAttributesInput':使用你创建的配置组件类型field: '$dataAttributes':对应规则中的属性字段(以$开头)wrap: { show: false }和col: { show: false }:隐藏表单项的包装,让组件独立显示title: '':不显示标题,因为组件内部已经包含了标题和按钮
注意: 确保 DataAttributesInput 组件已经在设计器表单中完成组件注册:
// 在 designer3.0 的入口文件或组件注册文件中
import DataAttributesInput from './designer/DataAttributesInput.vue';
// 注册到设计器表单
designerForm.component('DataAttributesInput', DataAttributesInput);7. 补齐多语言
在 src/locale/zh-cn.js 中新增:
// 在 class 节点中
dataAttributes: '数据属性',
// 在 warning 节点中
dataAttributes: '配置的 data-* 属性将添加到组件根元素上,可用于集成第三方库或实现自定义交互。',并建议同步维护 en.js、jp.js(避免切换语言后配置项缺失)。
使用示例
在规则中直接配置
const rules = [
{
type: 'input',
field: 'username',
title: '用户名',
// 配置数据属性
$dataAttributes: {
'data-track-id': 'user-input',
'data-analytics': 'true',
'data-custom': 'my-value',
},
},
];在设计器中配置
- 在设计器中选中组件
- 在右侧配置面板的样式区域找到"数据属性"按钮
- 点击打开配置对话框
- 添加需要的 data 属性键值对
- 保存后,属性会自动应用到组件上
渲染结果
配置后,组件会渲染为:
<input
data-track-id="user-input"
data-analytics="true"
data-custom="my-value"
class="el-input__inner"
...
/>常见问题排查
1. 属性配置了但没有生效
- 检查注册位置:确认在
src/form/elm.js、src/form/mobile.js和src/utils/form.js中都注册了属性处理器 - 检查属性名称:确认规则中使用的是
$dataAttributes(以$开头) - 检查属性值格式:确认属性值是对象格式,且键值对正确
2. 移动端和 PC 端表现不一致
- 检查双端注册:确认在 PC 端和移动端渲染器中都注册了相同的属性处理器
- 检查平台差异:某些属性在不同平台可能有不同的处理方式,需要做平台判断
3. 对已有规则的影响
- 新增的属性扩展功能只对新配置的规则生效
- 已保存的规则不会自动应用新属性,需要手动编辑规则或重新配置
版本兼容
- Vue2 版本:属性处理器的实现方式相同,但需要注意 Vue2 和 Vue3 的 API 差异
- Vue3 版本:使用 Composition API 时,需要确保属性处理器兼容新的响应式系统
附录
相关链接
术语表
- 属性处理器(Attribute Handler):处理规则中自定义属性的加载、监听、清理逻辑的函数
- 运行态渲染器(Runtime Renderer):用于实际表单渲染的 form-create 实例
- 设计态渲染器(Designer Renderer):用于设计器画布渲染的 form-create 实例
- 规则(Rule):定义表单组件配置的对象,包含 type、field、props 等属性


