背景
平台需要实现自定义表单功能,作为低代码开发的一部分,通过技术预研和技术选型,选择form-create和form-create-designer这两个组件进行集成作为实现方案。通过深入了解和技术验证,确认了组件的功能能满足需求,具备良好的开放性和扩展性。
上篇对运行模式下表单集成进行了技术验证,今天来进行实际的集成工作。
初始化表单选项
表单选项指的主要是下图右侧红框标注的部分,包括标签位置、尺寸、标签宽度等
对应的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组件自带了两个按钮,预览和清空。
预览功能会弹出对话框,显示当前表单的运行效果。
清空按钮经查看源码,是将表单规则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事件中后,自定义组件在表单设计器中无法正常显示,如下图所示
但是一旦点了fcd组件自带的预览按钮,预览页面正常
关闭预览页面后,表单设计器的点击下左键,自定义组件也能正常显示了
这现象看上去很奇怪,很像是自定义组件注册引发的问题。
该问题尝试过多种方式,尚未找到解决方式,不过功能上影响也较小,不处理或者点击下“预览”按钮即可正常,暂时搁置,后续集成过程中处理其他问题很可能就顺手解决了。
长文本TextArea的行数设置
对于“备注”这样的字段,控件类型仍为input,属性需要设置为textarea,对于行数量rows,ElementPlus中该属性类型为number,按照该模式设置发现在fcd组件中无效。
property = maker.create('input', item.code, item.name).props({ type: 'textarea', rows: 4 })
尝试将其按字符串处理时,则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)
}
以上方式测试有效。
如何绑定事件
在附件上传场景下,需要组合使用附件管理和附件上传组件,如下图所示:
当附件上传组件完成某个文件上传后,需要调用附件管理组件的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>
常规模式效果如下:
作为fc的自定义组件时,解析的效果则有问题。
变更下样式,去除外围的form-item标签
<template><AttachmentManager ref="attachmentManager" :entity-id="entityId" /><AttachmentUploader:entity-type="entityType":entity-id="entityId":module-code="moduleCode"@file-complete="fileComplete"/>
</template>
调整后效果如下:
测试功能正常,该方式还有一个优点在于,如果某个功能需要多个上传附件区域,则可以通过扩展一个附件类型,就能实现,而不会A区域附件上传成功如何去刷新A区域的附件列表的问题。
开源平台资料
平台名称:一二三开发平台
简介: 企业级通用开发平台
设计资料:csdn专栏
开源地址:Gitee
开源协议:MIT
欢迎收藏、点赞、评论,你的支持是我前行的动力。