应用开发平台集成表单设计器系列之6——表单构造器集成实战

背景

平台需要实现自定义表单功能,作为低代码开发的一部分,通过技术预研和技术选型,选择form-create和form-create-designer这两个组件进行集成作为实现方案。通过深入了解和技术验证,确认了组件的功能能满足需求,具备良好的开放性和扩展性。
上篇对运行模式下表单集成进行了技术验证,今天来进行实际的集成工作。

初始化表单选项

表单选项指的主要是下图右侧红框标注的部分,包括标签位置、尺寸、标签宽度等
image.png
对应的json数据如下:

{"form": {"labelPosition": "left","size": "mini","labelWidth": "125px","hideRequiredAsterisk": false,"showMessage": true,"inlineMessage": true},"submitBtn": true,"resetBtn": false
}

与平台集成做了初始化设置,如将提交按钮设置为不可见

//初始化表单选项initAdvanceOption() {const option = {form: {labelPosition: 'right',size: 'default',labelWidth: '120px',hideRequiredAsterisk: false,showMessage: true,inlineMessage: false},submitBtn: false}this.$refs.designer.setOption(option)},

这里需要注意的是,关于隐藏提交按钮的实现方式,fcd组件的官方文档没写,fc组件的文档(http://www.form-create.com/v3/element-ui/global#option-submitbtn)里写的数据结构如下:

  • form:表单整体显示规则配置
  • row:表单组件布局配置
  • submitBtn:提交按钮样式配置
  • resetBtn:重置按钮样式配置
  • info:组件提示消息配置
  • wrap: 配置FormItem

即submitBtn是与form平级的,在fc文档中,设置提交按钮隐藏应该如下设置:

form: {labelPosition: 'right',size: 'default',labelWidth: '120px',hideRequiredAsterisk: false,showMessage: true,inlineMessage: false
},
submitBtn: {show:false}

实际测试无效,通过fcd组件的示例程序,获取到的写法是submitBtn: false。

处理操作按钮

fcd组件自带了两个按钮,预览和清空。
image.png
预览功能会弹出对话框,显示当前表单的运行效果。
清空按钮经查看源码,是将表单规则rule置为空数组([]),即重新进行表单配置。
这两个功能都比较实用,予以保留。
此外,我还需要增加自己的按钮“保存”,调用后端服务将配置持久化到数据库。
自己添加的按钮,与内置按钮的样式不一致会不协调,通过查看源码的方式,设置了同样的样式

   <el-button type="primary" plain round size="small" @click="save"><i class="fc-icon icon-select"></i> 保 存</el-button>

注:官方只提了如何添加自定义按钮,并未说明如何移除内置的按钮。经查看fcd源码,没有提供相关的方法来处理,除非修改源码。
还有一种变通的方式就是使用“黑科技”,通过dom来操作可见性。

 //隐藏清空按钮
const designerDom = this.$refs.designer.$el
let clearButton = designerDom.getElementsByClassName('el-button el-button--danger el-button--small is-plain is-round el-tooltip__trigger'
)
clearButton[0].style.display = 'none'

自定义组件的注册异常

官网上的资料肯定是错的,如下所示:

<template><fc-designer ref="designer"/>
</template><script>
export default {name: 'app',data() {return {};},created(){//插入组件规则this.$refs.designer.addComponent(checkbox);//插入拖拽按钮到`main`分类下this.$refs.designer.appendMenuItem('main', {icon: checkbox.icon,name: checkbox.name,label: checkbox.label})}
};
</script>

vue的created事件中,dom元素尚未初始化,不可能通过refs获取到,而只能放到mounted事件中。

更换到mounted事件中后,自定义组件在表单设计器中无法正常显示,如下图所示
image.png
但是一旦点了fcd组件自带的预览按钮,预览页面正常
image.png
关闭预览页面后,表单设计器的点击下左键,自定义组件也能正常显示了
image.png
这现象看上去很奇怪,很像是自定义组件注册引发的问题。

该问题尝试过多种方式,尚未找到解决方式,不过功能上影响也较小,不处理或者点击下“预览”按钮即可正常,暂时搁置,后续集成过程中处理其他问题很可能就顺手解决了。

长文本TextArea的行数设置

对于“备注”这样的字段,控件类型仍为input,属性需要设置为textarea,对于行数量rows,ElementPlus中该属性类型为number,按照该模式设置发现在fcd组件中无效。

property = maker.create('input', item.code, item.name).props({ type: 'textarea', rows: 4 })

image.png
尝试将其按字符串处理时,则fcd组件能识别并正确处理。

property = maker.create(, item.code, item.name).props({ type: 'textarea', rows: '4' })

如何动态绑定组件属性

对于一些组件,如附件上传,在组件中绑定主表单的标识,常规写法如下:

<el-form-item label="附件上传"><AttachmentUploaderentity-type="Notice":entity-id="entityData.id"module-code="support":show-success-files="false"@file-complete="fileComplete"/>
</el-form-item>

其中的entity-id即主表单实体的标识,该标识是调用后端服务来获取的。
使用fcd组件时,做如下处理:

property = maker.create(AttachmentUploaderDesigner.name, item.code, item.name).props({entityId: this.entityData.id,entityType: ENTITY_TYPE,moduleCode: MODULE_CODE
})

在fcd组件中会因为读取不到entityData而报错。
将其先加引号,固化为字符串,生成规则rule,由fc组件解析,在其基础上手工调整,寻求解决方式。

<script>
import { modifyMixin } from '@/mixin/modifyForAdvanceConfigMixin.js'
const MODULE_CODE = 'entityconfig'
const ENTITY_TYPE = 'template'
export default {name: ENTITY_TYPE + '-modify',components: {},mixins: [modifyMixin],data() {return {entityType: ENTITY_TYPE,moduleCode: MODULE_CODE,// eslint-disable-next-line no-evalapi: eval('this.$api.' + MODULE_CODE + '.' + ENTITY_TYPE),pageCode: MODULE_CODE + ':' + ENTITY_TYPE + ':',entityData: { id: '' },//fc组件options: {form: {labelPosition: 'right',size: 'default',labelWidth: '120px',hideRequiredAsterisk: false,showMessage: true,inlineMessage: false},submitBtn: { show: false, innerText: '提交' },resetBtn: { show: false, innerText: '重置' }},rule: [{type: 'FcRow',_fc_drag_tag: 'row',hidden: false,display: true,children: [{type: 'col',props: { span: 24 },_fc_drag_tag: 'col',hidden: false,display: true,children: [{props: {entityId: this.entityData.id,entityType: ENTITY_TYPE,moduleCode: MODULE_CODE},hidden: false,display: true,value: '',type: 'AttachmentUploader',title: '附件上传',field: 'attachmentUpload',name: 'attachmentUpload',_fc_drag_tag: 'AttachmentUploader'}]}]}]}},methods: {}
}
</script>

按照上述方式,浏览器直接报错,this.entityData的未定义id。

主表单实体的标识id,是通过调用后端服务来获取的,而rule规则是页面构建时加载的,这时候就面临一个问题,如何在后端服务执行完成后设置组件的props属性。

查了半天官方文档,找来找去,看上去对于远程调用,官方提供的fetch机制来实现(https://www.form-create.com/v3/guide/attr-fetch),不过这种机制会冲击和影响平台的框架(通过mixin机制混入,新增执行init方法获取带初始化值的实体对象,修改时通过get方法获取实体对象的属性值),同时涉及到需要附加token来实现身份认证,因此不想采用这种方式。

然后尝试寻找是否有api能完成这事,如果有的话,可以在平台框架调用后端服务完成后,将获取到的id值赋值给组件的props属性。
官方文档有如下示例(https://www.form-create.com/v3/guide/custom-component):

fApi.getRule('btn').props.loading = false

在测试过程中发现,按照如下方式,无法获取到fapi的值

<form-create :rule="rule" v-model:api="fApi" :option="options" v-model="formValue" />
setEntityId(entityId) {console.log(this.fApi)this.fApi.getRule('attachmentUpload').props.entityId = entityId
}

将反复测试和验证,fc组件的值,即v-model变化后,绑定的v-model:api属性需要一段时间的解析,因此使用setTimeout方法加了3秒的延迟,对于附件上传这个场景,不会影响到业务功能和用户体验。

 this.api.get(id).then((res) => {this.entityData = res.datathis.formValue = res.datasetTimeout(() => {// 这里放置您要延迟执行的代码this.setEntityId(res.data.id)}, 3000)if (this.afterInit) {this.afterInit()}this.visible = true})

以上虽然测试成功,但是仍有个问题,在于如何识别到哪个组件需要赋值,需要判断当前表单是否有附件上传组件,组件的标识是什么,这会让实现变得更复杂。

换一个角度考虑,在data里新增个变量,附件上传组件时直接绑定该变量就好了,并且该变量可以被多组件复用,如附件上传、附件管理、附件浏览均需要主实体对象的标识值。

 data() {return {……// 增加新属性来保存标识entityId: '',……}},
rule: [{type: 'FcRow',_fc_drag_tag: 'row',hidden: false,display: true,children: [{type: 'col',props: { span: 24 },_fc_drag_tag: 'col',hidden: false,display: true,children: [{props: {//组件来绑定属性entityId: this.entityId,entityType: ENTITY_TYPE,moduleCode: MODULE_CODE},hidden: false,display: true,value: '',type: 'AttachmentUploader',title: '附件上传',field: 'attachmentUpload',name: 'attachmentUpload',_fc_drag_tag: 'AttachmentUploader'}]}]}]
this.api.get(id).then((res) => {this.entityData = res.datathis.formValue = res.data//从后端服务拿到数据后更新属性值this.entityId = res.data.id……})

按照上述实现方式,发现测试无效,上传附件请求后端时,实体对象标识为空,推测是没生效,因此考虑调用fc组件的api,把表单规则给刷新一下。翻着官方文档,尝试了好多方法,都不能实现预期目的。
回过头来一看,实现思路有问题,entityId是data的一个属性,rule同样是data的一个属性,简化下实际是这么回事:

data{a: 1,b: a
}

这种方式明显是行不通的。

接下来就需要转变下思路了,既然官方没有便捷的api,还是得自己想办法来搞定。
比较规范的做法,就是解析rule的json数据,然后根据组件的类型,去给相应的props属性的entityId赋值,这样是可以做,但是rule的数据结构比较复杂,处理起来复杂度是挺高的,并且后面扩展新的自定义组件,也需要相应增加相应的处理。

 rule: [{type: 'FcRow',_fc_drag_tag: 'row',hidden: false,display: true,children: [{type: 'col',props: { span: 24 },_fc_drag_tag: 'col',hidden: false,display: true,children: [{props: {entityId: '@EntityId@',entityType: ENTITY_TYPE,moduleCode: MODULE_CODE},hidden: false,display: true,value: '',type: 'AttachmentUploader',title: '附件上传',field: 'attachmentUpload',name: 'attachmentUpload',_fc_drag_tag: 'AttachmentUploader'}]}]}]

由此想出一个变通的方法,即使用一个约定的标记作为占位符,如@EntityId@

{props: {entityId: '@EntityId@',entityType: ENTITY_TYPE,moduleCode: MODULE_CODE},hidden: false,display: true,value: '',type: 'AttachmentUploader',title: '附件上传',field: 'attachmentUpload',name: 'attachmentUpload',_fc_drag_tag: 'AttachmentUploader'}
//更新标签值
updateData() {const ruleString = JSON.stringify(this.rule).replaceAll('@EntityId@', this.entityId)this.rule = JSON.parse(ruleString)
}

以上方式测试有效。

如何绑定事件

在附件上传场景下,需要组合使用附件管理和附件上传组件,如下图所示:
image.png
当附件上传组件完成某个文件上传后,需要调用附件管理组件的list方法,来刷新列表。

普通实现,给附件管理设置一个ref属性,然后监听附件上传的fileComplete事件即可,如下:

 <el-form-item label="附件列表"><AttachmentManager ref="attachmentManager" :entity-id="entityData.id" /></el-form-item><el-form-item label="附件上传"><AttachmentUploaderentity-type="Notice":entity-id="entityData.id"module-code="support":show-success-files="false"@file-complete="fileComplete"/></el-form-item>// 附件上传完成,刷新管理组件fileComplete() {this.$refs.attachmentManager.list()}

集成form-create组件,将附件上传和附件管理两个封装成自定义组件时,遇到了严重障碍。

首先,如何来触发事件,官方有个upload的例子,在props属性中写了个onSuccess属性

export default {icon: 'icon-upload',label,name,rule({t}) {return {type: name,field: uniqueId(),title: t('components.upload.name'),info: '',$required: false,props: {action: '',onSuccess(res, file) {file.url = res.data.url;}}};},

实际对应的还是Element plus的uploader的on-success属性,但我这边需要加的是事件,不是属性,此路走不通。

看了官方组件联动示例,官方提供了control机制,当A组件某个值发生变化时,调用B组件,该方式同样不使用我们这边的场景。

然后看到官方的事件机制,测试了可用。

props: {entityId: '@EntityId@',entityType: '@EntityType@',moduleCode: '@ModuleCode@'},
//关键通过emit属性来触发事件
emit: ['fileComplete'],
emitPrefix: '',
hidden: false,
display: true,
value: '',
type: 'AttachmentUploader',
title: '附件上传',
field: 'attachmentUpload',
_fc_drag_tag: 'AttachmentUploader'

该事件需要在fc组件上定义,其规则是组件标识+事件名,用横杠来转换

    <form-create:rule="rule"v-model:api="fApi":option="options"v-model="formValue"ref="test"@attachment-upload-file-complete="fileComplete"/>

该方式走得通,但是在调用组件管理的list方法时,完全卡主了。

给附件管理组件的props属性,设置ref属性,实测通过this.$refs读取不出来,如下图所示;

  props: { entityId: '@EntityId@', ref: 'attachmentManager' },hidden: false,display: true,value: '',type: 'AttachmentManager',title: '附件管理',field: 'attachmentManage',_fc_drag_tag: 'AttachmentManager'

查了半天资料,大概原因是使用fc组件封装时使用了v-for循环,这种情况下需要对ref进行额外处理,甚至去翻了下fc组件源码,各种尝试,没找到解决方案。

fc组件内部实现不清楚,那就变更下实现思路吧,我自己把附件管理和附件上传组件封装成一个复合组件。
代码如下:

<template><el-form-item label="附件列表"><AttachmentManager ref="attachmentManager" :entity-id="entityId" /></el-form-item><el-form-item label="附件上传"><AttachmentUploader:entity-type="entityType":entity-id="entityId":module-code="moduleCode"@file-complete="fileComplete"/></el-form-item>
</template><script>
import AttachmentManager from '@/modules/support/view/attachment/attachmentManager.vue'
import AttachmentUploader from '@/modules/support/view/attachment/attachmentUploader.vue'
export default {name: 'AttachmentManagerAndUploader',label: '附件管理上传',components: { AttachmentManager, AttachmentUploader },props: {entityId: {type: String,default: '',required: true},entityType: {type: String,required: true},moduleCode: {type: String,required: true}},data() {return {}},mounted: function () {},methods: {fileComplete() {this.$refs.attachmentManager.list()}}
}
</script>

常规模式效果如下:
image.png
作为fc的自定义组件时,解析的效果则有问题。
image.png

变更下样式,去除外围的form-item标签

<template><AttachmentManager ref="attachmentManager" :entity-id="entityId" /><AttachmentUploader:entity-type="entityType":entity-id="entityId":module-code="moduleCode"@file-complete="fileComplete"/>
</template>

调整后效果如下:
image.png
测试功能正常,该方式还有一个优点在于,如果某个功能需要多个上传附件区域,则可以通过扩展一个附件类型,就能实现,而不会A区域附件上传成功如何去刷新A区域的附件列表的问题。

开源平台资料

平台名称:一二三开发平台
简介: 企业级通用开发平台
设计资料:csdn专栏
开源地址:Gitee
开源协议:MIT
欢迎收藏、点赞、评论,你的支持是我前行的动力。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/293282.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android 手机恢复出厂设置后可以恢复数据吗?

将 Android 手机恢复出厂设置是否会永久删除所有内容&#xff0c;或者您​​仍然可以检索部分数据吗&#xff1f; 如果您无法再使用 Android 手机&#xff0c;唯一的解决方案可能是将其恢复出厂设置。恢复出厂设置&#xff08;也称为硬重置&#xff09;会删除设备中的所有设置…

Qt案例 调用WINDOWS API中的SETUPAPI.H库获取设备管理器中设备的详细信息中的属性值(二)

使用Qt调用windows api中的setupapi.h库中的SetupDiGetDeviceRegistryProperty和SetupDiGetDeviceProperty函数获取设备管理器中的设备详细信息中的属性值&#xff0c;包括设备实例路径&#xff0c;硬件id,驱动inf名称&#xff0c;驱动版本&#xff0c;显示名称&#xff0c;类名…

数据结构——二叉树——堆

前言&#xff1a; 在前面我们已经学习了数据结构的基础操作&#xff1a;顺序表和链表及其相关内容&#xff0c;今天我们来学一点有些难度的知识——数据结构中的二叉树&#xff0c;今天我们先来学习二叉树中堆的知识&#xff0c;这部分内容还是非常有意思的&#xff0c;下面我们…

虚拟机Linux(centos)安装python3.8(超详细)

一、Python下载 下载地址&#xff1a;https://www.python.org/downloads/source/ 输入下面网址即可直接下载&#xff1a; python3.8&#xff1a;https://www.python.org/ftp/python/3.8.0/Python-3.8.0.tgz python3.6&#xff1a;https://www.python.org/ftp/python/3.6.5/…

微信小程序(黑马优购:登录)

1.点击结算进行条件判断 user.js //数据 state: () >({ // address: {} address: JSON.parse(uni.getStorageSync(address) || {}), token: }), my-settle.vue computed: { ...mapGetters(m_cart,[checkedCount,total,checkedGoodsAmount]), …

IP种子是什么?理解和应用

在网络世界中&#xff0c;IP种子是一个广泛应用于文件共享和网络下载领域的概念。它是一种特殊的标识符&#xff0c;用于识别和连接到基于对等网络&#xff08;P2P&#xff09;协议的文件共享网络中的用户或节点。本文将深入探讨IP种子的含义、作用以及其在网络中的应用。 IP地…

【Linux】TCP网络套接字编程+守护进程

文章目录 日志类&#xff08;完成TCP/UDP套接字常见连接过程中的日志打印&#xff09;单进程版本的服务器客户端通信多进程版本和多线程版本守护进程化的多线程服务器 日志类&#xff08;完成TCP/UDP套接字常见连接过程中的日志打印&#xff09; 为了让我们的代码更规范化&…

瑞_23种设计模式_观察者模式

文章目录 1 观察者模式&#xff08;Observer Pattern&#xff09;1.1 介绍1.2 概述1.3 观察者模式的结构1.4 观察者模式的优缺点1.5 观察者模式的使用场景 2 案例一2.1 需求2.2 代码实现 3 案例二3.1 需求3.2 代码实现 4 JDK中提供的观察者模式实现 ★4.1 Observable类4.2 Obse…

Day63-LVS四层负载均衡及结合Nginx7层负载均衡实践

Day63-LVS四层负载均衡及结合Nginx7层负载均衡实践 1. LVS&#xff08;Linux Virtual Server&#xff09;介绍2. IPVS&#xff08;LVS&#xff09;发展史3. IPVS软件工作层次图4. LVS技术点小结5. LVS的4模式原理讲解5.1 NAT(Network AddressTranslation)&#xff0c;中文网络地…

《Retrieval-Augmented Generation for Large Language Models: A Survey》 AI 解读

论文链接&#xff1a;Retrieval-Augmented Generation for Large Language Models: A Survey 论文标题&#xff1a;《Retrieval-Augmented Generation for Large Language Models: A Survey》 一译中文版地址&#xff1a; https://yiyibooks.cn/arxiv/2312.10997v5/index.htm…

PI案例分享--2000A核心电源网络的设计、仿真与验证

目录 摘要 0 引言 1 为什么需要 2000A 的数字电子产品? 2 2000A 的供电电源设计 2.1 "MPM3698 2*MPM3699"的 MPS扩展电源架构 2.2 使用恒定导通时间(COT)模式输出核心电压的原因 2.3 模块化 VRM 的优势 2.4 用步进负载验证2000A的设计难点 2.4.1 电源网络 …

qtcreator的信号槽链接

在ui文件中简单创建一个信号槽连接并保存可以在ui_mainwindow.h下 class Ui_MainWindow 类 void setupUi(QMainWindow *MainWindow)函数 找到对应代码 QObject::connect(pushButton, SIGNAL(clicked()), MainWindow, SLOT(close())); 下拉&#xff0c;由于 class MainWind…

《权力》为什么只为某些人所拥有 - 三余书屋 3ysw.net

权力&#xff1a;为什么只为某些人所拥有 大家好&#xff0c;今天我们解读的书名是《权力》&#xff0c;副标题是“为什么只为某些人所拥有”。该书深入探讨了职场中的权力议题&#xff0c;强调获得权力是关键的职场技能之一。在激烈的职场竞争中&#xff0c;缺乏这一技能将使…

C#(winform) 调用MATLAB函数

测试环境 VisualStudio2022 / .NET Framework 4.7.2 Matlab2021b 参考&#xff1a;C# Matlab 相互调用 Matlab 1、编写Matlab函数 可以没有任何参数单纯定义matlab处理的函数&#xff0c;输出的数据都存在TXT中用以后期读取数据 function [result,m,n] TEST(list) % 计算…

Python 后端 Flask 使用 Flask-SocketIO、前端 Vue3 实现长连接 Websocket 通信详细教程(更新中)

Flask 安装 Flask-Socketio Flask-SocketIO 第三方库使 Flask 应用程序可以实现客户端和服务器之间的低延迟双向通信。客户端应用程序可以使用 Javascript、Python、C、Java 和 Swift 中的任何 SocketIO 客户端库或任何其他兼容客户端来建立与服务器的永久连接。 Flask-Socke…

逐步学习Go-Select多路复用

概述 这里又有多路复用&#xff0c;但是Go中的这个多路复用不同于网络中的多路复用。在Go里&#xff0c;select用于同时等待多个通信操作&#xff08;即多个channel的发送或接收操作&#xff09;。Go中的channel可以参考我的文章&#xff1a;逐步学习Go-并发通道chan(channel)…

使用 Yoda 和 ClickHouse 进行实时欺诈检测

背景 Instacart 是北美领先的在线杂货公司,拥有数百万活跃的客户和购物者。在其平台上打击欺诈和滥用行为不仅对于维护一个值得信赖和安全的环境至关重要,也对保持Instacart的财务健康至关重要。在这篇文章中,将介绍了一个欺诈平台——Yoda,解释了为什么我们选择ClickHous…

【踩坑】荣耀系统Android8.0 system目录Read-only file system

本来以为直接把Charles证书改成系统证书格式&#xff0c;然后通过mt管理器root之后移动到系统证书目录就行了&#xff0c;结果访问baidu仍然显示网络错误&#xff0c;折腾一晚上。后来直接安装为用户证书&#xff0c;与系统证书冲突。 手机型号&#xff1a;荣耀v10 EMUI&…

升级程序到Java21的记录一(在先升级jdk到21)

背景&#xff1a;为了使用Java21的最新特性虚拟线程以及提高程序的整体性能&#xff0c;决定将一个程序A升级到Java21. 备注&#xff1a;程序A有很多文件操作因此使用虚拟线程对提升性能有帮助&#xff0c;如果读者的程序是其他类型&#xff0c;请参考虚拟线程的一些资料决定是…

STM32学习笔记(10_3)- 软件I2C读写MPU6050

无人问津也好&#xff0c;技不如人也罢&#xff0c;都应静下心来&#xff0c;去做该做的事。 最近在学STM32&#xff0c;所以也开贴记录一下主要内容&#xff0c;省的过目即忘。视频教程为江科大&#xff08;改名江协科技&#xff09;&#xff0c;网站jiangxiekeji.com 本期开…