Skip to content

添加自定义属性

渲染器属性允许您为表单渲染器添加自定义的属性处理器,通过属性扩展,您可以处理组件配置、注入自定义逻辑、管理动态样式等。本文将以 级联样式(CSS) 功能作为参考样例,详细说明如何扩展一个新的渲染器属性功能。

渲染器文档中可以查看内置表单组件及其所有可配置参数。

在源码中可以参考现有的属性扩展实现(如 src/utils/css.jssrc/utils/behavior.jssrc/utils/easySlots.js)。

注意

渲染器属性扩展需要在运行态渲染器设计态渲染器中同时注册,确保设计器和运行环境都能正常工作。

扩展渲染器属性要改哪些地方?

在设计器中扩展一个新的渲染器属性功能,通常涉及 3 层:

  • 属性处理器实现(Attribute Handler):定义属性的加载、监听、清理逻辑(如 src/utils/css.js
  • 渲染器注册(formCreate.register):在 PC/移动端渲染器中注册属性处理器(如 src/form/elm.jssrc/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. 属性处理器结构

一个标准的属性处理器需要实现以下接口:

js
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

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 中引入并注册:

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 中引入并注册:

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 中引入并注册:

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

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 里。

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 组件已经在设计器表单中完成组件注册:

js
// 在 designer3.0 的入口文件或组件注册文件中
import DataAttributesInput from './designer/DataAttributesInput.vue';

// 注册到设计器表单
designerForm.component('DataAttributesInput', DataAttributesInput);

7. 补齐多语言

src/locale/zh-cn.js 中新增:

js
// 在 class 节点中
dataAttributes: '数据属性',

// 在 warning 节点中
dataAttributes: '配置的 data-* 属性将添加到组件根元素上,可用于集成第三方库或实现自定义交互。',

并建议同步维护 en.jsjp.js(避免切换语言后配置项缺失)。

使用示例

在规则中直接配置

js
const rules = [
    {
        type: 'input',
        field: 'username',
        title: '用户名',
        // 配置数据属性
        $dataAttributes: {
            'data-track-id': 'user-input',
            'data-analytics': 'true',
            'data-custom': 'my-value',
        },
    },
];

在设计器中配置

  1. 在设计器中选中组件
  2. 在右侧配置面板的样式区域找到"数据属性"按钮
  3. 点击打开配置对话框
  4. 添加需要的 data 属性键值对
  5. 保存后,属性会自动应用到组件上

渲染结果

配置后,组件会渲染为:

html
<input 
    data-track-id="user-input"
    data-analytics="true"
    data-custom="my-value"
    class="el-input__inner"
    ...
/>

常见问题排查

1. 属性配置了但没有生效

  • 检查注册位置:确认在 src/form/elm.jssrc/form/mobile.jssrc/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 等属性