应用开发平台集成表单设计器系列之5——运行模式下的表单集成验证

背景

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

实体视图功能扩展

实体属性扩展

扩展实体视图的属性,新增三个属性,如下图所示:
image.png
enableAdvanceConfig:启用高级配置,标识位,用于判断是基于标准布局(自上而下单列显示所有属性),还是基于表单设计器实现的高级布局。增加该属性的主要目的是便于控制模式,可以直接根据该属性判断是否启用了高级配置,而不是通过advanceConfigRule是否为空来判断。
advanceConfigRule:高级配置规则,用于存放fcd组件生成的规则。
advanceConfigOption:高级配置选项,用于存放fcd组件生成的表单选项。

页面调整

相应调整列表查询页面,在查询结果增加“启用高级配置”显示。
image.png
相应调整新增、修改、查看页面,增加“启用高级配置”属性。
image.png

功能调整

增加了一个服务方法,更新高级配置,接收前端传入的高级配置规则和选项,持久化到库表。

public void updateAdvanceConfig(String id, String advanceConfigRule, String advanceConfigOption) {
    EntityView entity=query(id);
    entity.setAdvanceConfigRule(advanceConfigRule);
    entity.setAdvanceConfigOption(advanceConfigOption);
    modify(entity);
}

控制器层新增方法,接收前端调用,调用后端服务。

    /**
     * 更新高级配置
     */
    @PutMapping("/updateAdvanceConfig")
    @SystemLog(value = "实体视图-高级配置")
    @PreAuthorize("hasPermission(null,'entityconfig:entityView:advanceConfig')")
    public ResponseEntity<Result> updateAdvanceConfig(@RequestBody EntityViewVO vo) {
        EntityView entity=convert2Entity(vo);
        entityViewService.updateAdvanceConfig(entity.getId(),entity.getAdvanceConfigRule(),entity.getAdvanceConfigOption());
        EntityViewVO newVO = convert2VO(entity);
        return ResultUtil.success(newVO);
    }

前端API定义

// 实体视图
export const entityView = Object.assign({}, COMMON_METHOD, {
  serveUrl: '/' + moduleName + '/' + 'entityView' + '/',
  // 更新高级配置
  updateAdvanceConfig(params) {
    return request.put({ url: this.serveUrl + 'updateAdvanceConfig', data: params })
  }
})

前端使用,在高级配置页面,扩展fcd的自定义按钮区,增加“保存”按钮,点击时调用api。

 <fc-designer v-model="value" ref="designer">
      <template #handle>
        <el-button type="primary" @click="save">保存</el-button>
        <el-button type="primary" @click="preview">预览</el-button>
      </template>
</fc-designer>


save() {
    const param = {
      id: this.$route.query.id,
      advanceConfigRule: JSON.stringify(this.$refs.designer.getRule()),
      advanceConfigOption: JSON.stringify(this.$refs.designer.getOption())
    }
    this.$api.entityconfig.entityView.updateAdvanceConfig(param)
}

高级配置模型的运行

通过表单设计器fcd组件,生成高级配置的规则和选项后,写入到库表中,这是配置阶段的工作。
然后平台进行代码生成时,根据启用高级配置的标识位,来决定采用哪种模式类生成前端的功能页面。
在高级配置模式下,需要读取配置阶段的规则和选项,来初始化fc组件。

以修改视图为例,UI部分如下:

<template>
  <Dialog title="修改" v-model="visible" width="500px">
    <form-create :rule="rule" v-model:api="fApi" :option="options"  />
    <template #footer>
      <el-button type="primary" @click="save" v-permission="pageCode + 'modify'">保存</el-button>
      <el-button @click="close">关闭</el-button>
    </template>
  </Dialog>
</template>

使用fc组件,替代了原来的el-form表单以及el-form-item表单元素。
集成过程中遇到了以下几个问题,下面列出来,并说明对应的处理方案。

如何去除提交按钮?

fcd组件默认显示内置的提交按钮,与平台自身的保存按钮重复,如下图所示:
image.png
这个简单,调整fcd组件选项,设置提交按钮不可见即可。
image.png

如何初始化表单数据?

对于修改视图,通过数据的标识调用后端服务获取到实体数据后,赋值到表单。
标准模式下,使用entityData来做这件事情。

//获取数据
this.api.get(id).then((res) => {
        this.entityData = res.data
})  

//数据绑定到表单
<el-form
      ref="form"
      :model="entityData"
      :rules="rules"
      label-width="120px"
      label-position="right"
      style="width: 90%; margin: 0 auto"
    >
      <!--表单区域 -->
      <el-form-item label="组织机构" prop="organization">
        <OrganizationReference
          v-model="entityData.organization"
          :organization-param="organizationParam"
        />
      </el-form-item>
      <el-form-item label="名称" prop="name">
        <el-input v-model="entityData.name" />
      </el-form-item>
      <el-form-item label="编码" prop="code">
        <el-input v-model="entityData.code" />
      </el-form-item>
      <el-form-item label="类型" prop="type">
        <dictionary-select v-model="entityData.type" code="OrganizationType" />
      </el-form-item>
      <el-form-item label="状态" prop="status">
        <dictionary-select v-model="entityData.status" code="Status" />
      </el-form-item>
      <el-form-item label="排序" prop="orderNo">
        <el-input v-model="entityData.orderNo" />
      </el-form-item>
      <el-form-item label="备注" prop="remark">
        <el-input v-model="entityData.remark" />
      </el-form-item>
    </el-form>

使用fc组件的情况下,找了一圈api,没找到合适的赋值方法,然后在属性绑定里找到了解决方法,即使用v-model来绑定。

 <form-create :rule="rule" v-model:api="fApi" :option="options" v-model="entityData" />

按照上述方式,修改视图的初始化可以正常处理。

如何验证数据?

标准模式下使用el-form的验证机制,如下:

this.$refs.form.validate((valid) => {
        if (valid) {         
              this.saveData()
        }          
})

使用高级配置的情况下,需要遵循fc组件的验证方式,官方示例如下:

this.fApi.validate((valid, fail) => {
         if (valid) {         
              this.saveData()
        	}  
})

经测试存在问题,当表单验证失败后,回调参数valid中放的是验证失败的提示信息数组
image.png
按照js的判定规则, if (valid)会返回true,从而继续往下走,数据发给了后端服务,需做以下调整:

this.fApi.validate((valid, fail) => {
         if (valid===true) {         
              this.saveData()
        	}  
})

如何提交表单?

标准模式下,使用entityData即可,如下处理:

// 保存
saveData() {
  this.api
    .modify(this.entityData)
    .then((res) => {
      this.entityData = res.data         
      this.$emit('refresh', this.entityData)
      this.close()
    })
    .finally(() => {
      this.loading = false
    })
}

高级配置模式下,entityData与fc组件的v-model属性进行了绑定,出现了问题。
在初始化环节,调用后端服务,通过实体标识获取数据后赋值给entityData,fc组件绑定entityData的时候,内部会解析,只保留表单规则rule中定义的属性,其他属性会抛弃掉,如实体标识,这样就会造成保存异常。
image.png
基于上述fc组件的表现,另行增加一个属性formValue用于fc组件的属性绑定,然后在初始化时,使用entityData赋值,保存前,合并entityData与formValue的值,提交到后端。

<form-create :rule="rule" v-model:api="fApi" :option="options" v-model="formValue" />

// 初始化
init(id) {
  this.api.get(id).then((res) => {
    this.entityData = res.data
    this.formValue = res.data
    this.visible = true
  })
}


// 保存
saveData() {
  const data = Object.assign(this.entityData, this.formValue)
  this.api
    .modify(data)
    .then((res) => {
      this.entityData = res.data
      this.formValue = res.data         
      this.$emit('refresh', this.entityData)
      this.close()
    })
    .finally(() => {
      this.loading = false
    })
} 

高级配置模式的代码生成

高级配置模式需要做出调整的是前端,并且限于前端的新增、修改和查看视图。
以修改视图为例,说明相关调整,新增与查看类似。

代码模板

拷贝实体配置模块modify.vue.ftl,命名为modifyForAdvanceConfig.vue.ftl,修改为使用fc组件实现的高级配置模式。

<template>
    <Dialog title="修改" v-model="visible" width="500px">
        <form-create :rule="rule" v-model:api="fApi" :option="options" v-model="formValue" />
        <template #footer>
            <el-button type="primary" @click="save" v-permission="pageCode + 'modify'">保存</el-button>
            <el-button @click="close">关闭</el-button>
        </template>
    </Dialog>
</template>

<script>
import {modifyMixin} from '@/mixin/modifyForAdvanceConfigMixin.js'
<#list modifyViewPropertyList as item>
<#if item.dataType=="ENTITY">
import ${item.entityCode}Reference from '@/modules/${item.moduleCode}/view/${item.entityCode?uncap_first}/${mainReferenceViewMap[item.entityCode]}.vue'
</#if>
</#list>
const MODULE_CODE = '${package.ModuleName}'
const ENTITY_TYPE = '${entity?uncap_first}'
export default {
    name: ENTITY_TYPE + '-modify',
    mixins: [modifyMixin],
    components:{
    <#list modifyViewPropertyList as item>
    <#if item.dataType=="ENTITY">
        ${item.entityCode}Reference,
    </#if>
    </#list>
    },
    data() {
        return {
            entityType: ENTITY_TYPE,
            moduleCode: MODULE_CODE,
            // eslint-disable-next-line no-eval
            api: eval('this.$api.' + MODULE_CODE + '.' + ENTITY_TYPE),
            pageCode: MODULE_CODE + ':' + ENTITY_TYPE + ':',
            entityData: {},
            <#list modifyViewPropertyList as item>
            <#if item.dataType=="ENTITY">
            // ${item.name}组件参数,用于传递数据
            ${item.entityCode?uncap_first}Param: {},
            </#if>
            </#list>
            rules: {
                //前端验证规则
            <#list entityModelPropertyList as item>
            <#if item.nullFlag=="NO">
                ${item.code}: [{ required: true, message: '【${item.name}】不能为空', trigger: 'blur' }]<#if item_has_next>, </#if>
            </#if>
            </#list>
            },
            //fc组件
            options:${modifyEntityView.advanceConfigOption},
            rule:${modifyEntityView.advanceConfigRule}

        }
    },
    methods: {
        <#if modifyEntityView.beforeInit?? && modifyEntityView.beforeInit!="">
        beforeInit(param){
            ${modifyEntityView.beforeInit}
        },
        </#if>
        <#if modifyEntityView.afterInit?? && modifyEntityView.afterInit!="">
        afterInit(param){
            ${modifyEntityView.afterInit}
        },
        </#if>
        <#if modifyEntityView.validateData?? && modifyEntityView.validateData!="">
        validateData(){
            ${modifyEntityView.validateData}
        },
        </#if>
        <#if modifyEntityView.beforeSave?? && modifyEntityView.beforeSave!="">
        beforeSave(){
            ${modifyEntityView.beforeSave}
        },
        </#if>
        <#if modifyEntityView.afterSave?? && modifyEntityView.afterSave!="">
        afterSave(){
            ${modifyEntityView.afterSave}
        },
        </#if>
    }
}
</script>

<style></style>

代码生成

调整修改视图的生成逻辑,实际调整点非常少,依据实体视图的新加属性“启用高级配置”判断,调用不同的代码模板。

    private void generateModifyView(EntityView entityView, Map<String, Object> customKeyValue, InjectionConfig.Builder builder) {
        // 视图对象放入自定义键值map
        customKeyValue.put("modifyEntityView", entityView);
        //模板路径
        String templatePath="";
        if(entityView.getEnableAdvanceConfig().equals(YesOrNoEnum.NO.name())){
            //标准配置模式
            templatePath="/templates/modify.vue.ftl";
        }else{
            //高级配置模式
            templatePath="/templates/modifyForAdvanceConfig.vue.ftl";
        }
        // 获取视图属性配置
        List<ViewProperty> viewPropertyList = viewPropertyService.listByView(entityView.getId());
        customKeyValue.put("modifyViewPropertyList", viewPropertyList);
        // 自定义编辑视图模板
        CustomFile templateFile = new CustomFile.Builder()
                .fileName("modify.vue")
                .templatePath(templatePath)
                .enableFileOverride()
                .packageName(viewFolderName)
                .build();
        builder.customFile(templateFile);

    }

前端混入

拷贝modifyMixin.js,重命名为modifyForAdvanceConfigMixin.js,内容调整为使用fc组件的高级配置模式,如下:

/**
 * 高级配置修改页面混入
 */

import { Dialog } from '@/components/abc/Dialog'
import DictionaryRadioGroup from '@/components/abc/DictionarySelect/DictionaryRadioGroup.vue'
import DictionarySelect from '@/components/abc/DictionarySelect/DictionarySelect.vue'
import DataDictionarySelect from '@/modules/system/view/dictionaryType/treeReferenceUseCode.vue'
import IconPicker from '@/components/abc/IconPicker/index.vue'
import { Editor } from '@/components/abc/Editor'
import CronExpression from '@/components/abc/CronExpression/index.vue'
import OrganizationSingleSelect from '@/modules/system/view/organization/treeReference.vue'
import OrganizationMultipleSelect from '@/modules/system/view/organization/treeMultipleSelect.vue'
import UserSingleSelect from '@/modules/system/view/user/treeListReference.vue'
import AttachmentManager from '@/modules/support/view/attachment/attachmentManager.vue'
import AttachmentUploader from '@/modules/support/view/attachment/attachmentUploader.vue'
import AttachmentViewer from '@/modules/support/view/attachment/attachmentViewer.vue'
export const modifyMixin = {
  components: {
    Dialog,
    DictionaryRadioGroup,
    DictionarySelect,
    DataDictionarySelect,
    Editor,
    CronExpression,
    OrganizationSingleSelect,
    OrganizationMultipleSelect,
    UserSingleSelect,
    IconPicker,
    AttachmentManager,
    AttachmentUploader,
    AttachmentViewer
  },
  data() {
    return {
      // 可见性
      visible: false,
      // 加载中
      loading: false,
      fApi: {},
      formValue: {}
    }
  },
  methods: {
    // 初始化
    init(id) {
      this.$formCreate.component('DictionarySelect', DictionarySelect)

      if (this.beforeInit) {
        this.beforeInit()
      }

      this.api.get(id).then((res) => {
        this.entityData = res.data
        this.formValue = res.data
        if (this.afterInit) {
          this.afterInit()
        }
        this.visible = true
      })
    },
    // 保存
    save() {
      if (this.beforeSave) {
        this.beforeSave()
      }
      this.fApi.validate((valid, fail) => {
        if (valid) {
          if (this.validateData) {
            // 数据验证通过后才执行保存操作
            if (this.validateData()) {
              this.saveData()
            }
          } else {
            // 无需数据验证,直接执行
            this.saveData()
          }
        }
      })
    },
    // 关闭
    close() {
      this.visible = false
    },
    // 保存
    saveData() {
      const data = Object.assign(this.entityData, this.formValue)
      this.api
        .modify(this.data)
        .then((res) => {
          this.entityData = res.data
          this.formValue = res.data
          if (this.afterSave) {
            this.afterSave()
          }
          this.$emit('refresh', this.entityData)
          this.close()
        })
        .finally(() => {
          this.loading = false
        })
    },
    // 附件上传完成,刷新管理组件
    fileComplete() {
      this.$refs.attachmentManager.list()
    }
  }
}

开源平台资料

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

  • 22
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学海无涯,行者无疆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值