自定义表单-操作说明
功能列表
拖拽表单设计
左侧为拖拽表单的基本控件,右侧为拖拽表单的属性,中间位置为拖拽表单预览部分。
基础表单组件
输入框、文本框、数字输入框、下拉选择器、多选框、单选框、日期选择框、时间选择框、评分、滑动输入条、树形选择器、级联选择器、选择输入列、富文本、开关。
辅助组件
按钮、警告提示、文字、HTML
子表表单组件(重要)
动态表格
每次拖拽一个动态表格,相当于创建一个子表,一个动态表格内的字段为子表字段。
布局组件(辅助元素)
分割线、卡片布局、标签页布局、栅格布局、表格布局。
拖拽表单属性(重点属性介绍)
表单属性设置
表单名称:创建数据表的备注说明。
数据表名称:创建数据表的表名称。
基础控件属性
标签:数据字段的备注名称。
数据字段:数据表字段名称。
组件动态数据
下拉框、多选框、单选框组件可以配置动态数据,动态数据须指定字典的父级编码:
字典下拉项会自动展示父级节点的下级内容。

自定义表单-技术要点说明
前端代码重点解析

下面只介绍拖拽表单的整合及预览的代码,首页代码为通用代码,不做展示。
拖拽表单整合代码如下
<template><div class="design_div"><k-form-designshowToolbarsTexttoolbarsToptitle="青锋表单设计器":showHead="false":hideModel="false"@save="handleSave"@close="handleClose"ref="qfRef"/></div></template><script>import { saveOrUpdate } from "@/api/customize/vform";export default {// 声明当前子组件接收父组件传递的属性props: {record: {type: Object,default: null,},},data() {return {jsonData: {},};},mounted() {if (this.record.id != "" && this.record.id != undefined) {this.jsonData = JSON.parse(this.record.table_content);this.importData();}},methods: {handleSave(values) {//alert("触发保存方法");//console.log(values);//this.$refs.qfRef.handleReset();let data = this.$refs.qfRef.getValue();data.table_content = values;console.log(this.record);let myRecord = "";if (this.record != "") {myRecord = JSON.stringify(this.record);}saveOrUpdate({record: myRecord,table_content: values,config: JSON.stringify(data.config),list: JSON.stringify(data.list),}).then((response) => {if (response.data.success) {this.$success({title: "提示:",content: "表单创建成功。",});this.$emit("finishResponse", response);} else {this.$message.error(response.data.msg);}});},handleClose(values) {},importData() {this.$refs.qfRef.handleSetData(this.jsonData);},},};</script><style>.design_div {margin-top: -20px;}.operating-area {background-color: #f6f6f6;}.k-form-design {margin-top: -20px;}</style>
拖拽表单代码预览
<template><div><k-form-build ref="qfRef" :value="jsonData" /><a-modal v-model="visible" title="表单数据" on-ok="handleOk"><template slot="footer"><a-buttonkey="submit"type="primary":loading="loading"@click="handleOk">关闭</a-button></template><pre>{{data}}</pre></a-modal></div></template><script>export default {// 声明当前子组件接收父组件传递的属性props: {record: {type: Object,default: null,},},data() {return {jsonData: {},visible: false,data:{}};},mounted() {this.jsonData = JSON.parse(this.record.table_content);},methods: {onOk() {// 使用getData函数获取数据this.$refs.qfRef.getData().then((values) => {this.visible = true;this.data = values;console.log("验证通过", values);}).catch(() => {console.log("验证未通过,获取失败");});},onCancel() {return new Promise((resolve) => {resolve(true);});},handleOk() {this.visible = false;},},};</script>
后端代码重点解析

后端代码的核心功能在于设计表单的数据存储和更新,下面重点讲解拖拽代码的保存和更新方法。
拖拽表单保存更新方法
1、根据record判断是否为空,为空则添加,不为空则更新
后端代码如下:
@PostMapping@PreAuthorize("hasAnyAuthority('vform:add')")public void save(@Valid @RequestBody PageData pd,HttpServletResponse response) throws Exception {Json json = new Json();try {System.out.println(pd.get("record"));//根据record判断是否为空,为空则添加,不为空则更新if(Verify.verifyIsNotNull(pd.get("record"))){//查询数据表是否已经存在,如果存在则提示Map record = (Map) JSON.parse(pd.get("record").toString());Map maps = (Map) JSON.parse(pd.get("config").toString());if(record.get("table_name").equals(maps.get("table_name"))){this.vformService.updateVform(pd);json.setSuccess(true);json.setMsg("数据更新成功");}else{QueryWrapper queryWrapper = new QueryWrapper();queryWrapper.eq("table_name",maps.get("table_name"));int num = vformService.list(queryWrapper).size();if(num==0){pd.put("table_schema",table_schema);pd.put("table_name",maps.get("table_name"));int n = vformService.findTableList(pd).size();if(n==0){//添加-创建信息this.vformService.updateVform(pd);json.setSuccess(true);json.setMsg("数据更新成功");}else{json.setSuccess(false);json.setMsg("数据表已创建,不可重复创建。");}}else{json.setSuccess(false);json.setMsg("数据表信息已存在,不可重复创建。");}}}else{//判断数据表是否存在,存在则提示Map maps = (Map) JSON.parse(pd.get("config").toString());QueryWrapper queryWrapper = new QueryWrapper();queryWrapper.eq("table_name",maps.get("table_name"));int num = vformService.list(queryWrapper).size();if(num==0){pd.put("table_schema",table_schema);pd.put("table_name",maps.get("table_name"));int n = vformService.findTableList(pd).size();if(n==0){//添加-创建信息this.vformService.saveVform(pd);json.setSuccess(true);json.setMsg("新增信息成功");}else{json.setSuccess(false);json.setMsg("数据表已创建,不可重复创建。");}}else{json.setSuccess(false);json.setMsg("数据表信息已存在,不可重复创建。");}}} catch (Exception e) {e.printStackTrace();String message = "新增信息失败";json.setSuccess(false);json.setMsg(message);throw new MyException(message);}this.writeJson(response,json);}
重点在于saveVform和updateVform两个方法。
saveVform方法解析/updateVform同理
@Transactionalpublic void saveVform(PageData pd) throws Exception{//更新操作:如果表名称发生改变,则修改表名称;如果字段发生变更,则变更数据表结构。Map maps = (Map) JSON.parse(pd.get("config").toString());Vform vform = new Vform();String id = GuidUtil.getUuid();vform.setId(id);String time = DateTimeUtil.getDateTimeStr();vform.setCreate_time(time);String type = "0";//处理数据权限String authParams = SecurityContextHolder.getContext().getAuthentication().getName();vform.setCreate_user(authParams.split(":")[1]);vform.setCreate_organize(authParams.split(":")[2]);vform.setType(type);vform.setTable_name(maps.get("table_name").toString());vform.setTable_comment(maps.get("table_comment").toString());vform.setTable_content(pd.get("table_content").toString());this.save(vform);//数据表字段List<Vfield> vfields = new ArrayList<Vfield>();List<PageData> list = JSON.parseArray(pd.get("list").toString(),PageData.class);//获取需要创建的主表字段vfields = createField(id,list,vfields,"add",new ArrayList<Vform>());vfieldService.saveBatch(vfields);//创建数据表pd.put("table_name",maps.get("table_name").toString());pd.put("table_comment",maps.get("table_comment").toString());this.baseMapper.createTable(pd);this.baseMapper.updateTableComment(pd);//创建数据字段for (Vfield vfield:vfields) {pd.put("field_name",vfield.getField_name());if(vfield.getField_name().contains("textarea")){pd.put("field_type","varchar(500)");}else if(vfield.getField_name().contains("editor")||vfield.getField_name().contains("uploadFile")||vfield.getField_name().contains("uploadImg")){pd.put("field_type","text");}else{if(Verify.verifyIsNotNull(vfield.getMaxLength())){if(Integer.parseInt(vfield.getMaxLength())<=120){pd.put("field_type","varchar(120)");}else{pd.put("field_type","varchar("+vfield.getMaxLength()+")");}}else{pd.put("field_type","varchar(120)");}}pd.put("field_null","null");pd.put("field_comment",vfield.getField_comment());System.out.println(pd.toString());this.vfieldService.addField(pd);}}
1、保存主表数据
2、解析子表组队并保存
子表已list json格式传递,解析成为list对象在进行存储。
3、创建基础数据并修改备注
此处并未创建完整的表,只创建基础数据表,基础数据表的字段内容有:
初始字段: id、type、status(0,1)、order_by、create_time、create_user、create_organize、update_user、update_time
4、执行添加表字段

5、动态数据表单-子表的创建

public List<Vfield> createField(String table_id,List<PageData> list,List<Vfield> vfields,String type,List<Vform> vforms){String time = DateTimeUtil.getDateTimeStr();//当前登录用户String authParams = SecurityContextHolder.getContext().getAuthentication().getName();for (PageData p:list) {if(noField.contains(p.get("type").toString())){continue;}if(bjField.contains(p.get("type").toString())){vfields = createLayoutField(table_id,vfields,p,type,vforms);}else{Vfield vfield = new Vfield();vfield.setId(GuidUtil.getUuid());vfield.setTable_id(table_id);vfield.setType("0");vfield.setField_name(p.get("model").toString());vfield.setField_comment(p.get("label").toString());vfield.setField_key(p.get("key").toString());vfield.setField_type(p.get("type").toString());vfield.setCreate_time(time);vfield.setCreate_user(authParams.split(":")[1]);vfield.setCreate_organize(authParams.split(":")[2]);Map pp = (Map) JSON.parse(p.get("options").toString());vfield.setMaxLength(pp.get("maxLength")+"");//处理包含动态数据源的选项标签if(dynamicField.contains(p.get("type").toString())){vfield.setDynamic(pp.get("dynamic").toString());vfield.setDynamicKey(pp.get("dynamicKey").toString());vfield.setOptions(pp.get("options").toString());}vfields.add(vfield);if(p.get("type").equals("batch")){if(type.equals("save")){createLinkTable(table_id,p);}else if(type.equals("update")){updateLinkTable(table_id,vforms,p);}}}}return vfields;}
下面详细分析createField
1、字段类型为:button、alert、text、html、divider说明是辅助字段,忽略。
2、字段类型为:card、tabs、grid、table 说明是布局组件,需要继续遍历布局组件,找到里面真正的表单组件。下面会单独介绍。
判断是否为动态数据表,如果为动态数据表则处理关联字段。
6、布局组件的递归遍历
递归遍历-查询布局组件下的实际form组件。
下面分别对卡片布局、标签页布局、栅格布局、表格布局做了递归操作。
大家可能会注意到,在递归操作过程中又调用了【createField】细心的朋友会发现,明明刚才在createField中调用的createLayoutField,的确如此,这就是递归的妙处。
//递归遍历-创建字段 tabs、grid、tablepublic List<Vfield> createLayoutField(String table_id,List<Vfield> vfields,PageData fieldPd,String type,List<Vform> vforms){if(fieldPd.get("type").equals("card")){List<PageData> list = JSON.parseArray(fieldPd.get("list").toString(),PageData.class);vfields = createField(table_id,list,vfields,type,vforms);}else if(fieldPd.get("type").equals("tabs")||fieldPd.get("type").equals("grid")){List<PageData> columns = JSON.parseArray(fieldPd.get("columns").toString(),PageData.class);for(PageData cp:columns){List<PageData> list = JSON.parseArray(cp.get("list").toString(),PageData.class);vfields = createField(table_id,list,vfields,type,vforms);}}else if(fieldPd.get("type").equals("table")){List<PageData> trs = JSON.parseArray(fieldPd.get("trs").toString(),PageData.class);for (PageData trPd:trs){List<PageData> tds = JSON.parseArray(trPd.get("tds").toString(),PageData.class);for (PageData tdPd:tds) {List<PageData> list = JSON.parseArray(tdPd.get("list").toString(),PageData.class);vfields = createField(table_id,list,vfields,type,vforms);}}}return vfields;}
7、动态数据表-关联字段递归
通过分析,拆解创建关联表信息,大致分为一下内容。
//处理关联表数据public void createLinkTable(String main_id,PageData pd){Vform vform = new Vform();String id = GuidUtil.getUuid();vform.setId(id);String time = DateTimeUtil.getDateTimeStr();vform.setCreate_time(time);String type = "1";vform.setMain_id(main_id);//处理数据权限String authParams = SecurityContextHolder.getContext().getAuthentication().getName();vform.setCreate_user(authParams.split(":")[1]);vform.setCreate_organize(authParams.split(":")[2]);vform.setType(type);vform.setTable_name(pd.get("model").toString());vform.setTable_comment(pd.get("label").toString());vform.setTable_content(pd.get("key").toString());this.save(vform);//数据表字段List<Vfield> vfields = new ArrayList<Vfield>();List<PageData> list = JSON.parseArray(pd.get("list").toString(),PageData.class);for (PageData p:list) {if(noField.contains(p.get("type").toString())){continue;}Vfield vfield = new Vfield();vfield.setId(GuidUtil.getUuid());vfield.setTable_id(id);vfield.setType(type);vfield.setField_name(p.get("model").toString());vfield.setField_comment(p.get("label").toString());vfield.setField_key(p.get("key").toString());vfield.setField_type(p.get("type").toString());vfield.setCreate_time(time);vfield.setCreate_user(authParams.split(":")[1]);vfield.setCreate_organize(authParams.split(":")[2]);Map pp = (Map) JSON.parse(p.get("options").toString());vfield.setMaxLength(pp.get("maxLength")+"");//处理包含动态数据源的选项标签if(dynamicField.contains(p.get("type").toString())){vfield.setDynamic(pp.get("dynamic").toString());vfield.setDynamicKey(pp.get("dynamicKey").toString());vfield.setOptions(pp.get("options").toString());}vfields.add(vfield);}vfieldService.saveBatch(vfields);//创建数据表pd.put("table_name",pd.get("model").toString());pd.put("table_comment",pd.get("label").toString());this.baseMapper.dropTable(pd);this.baseMapper.createTable(pd);this.baseMapper.updateTableComment(pd);//创建数据字段for (Vfield vfield:vfields) {pd.put("field_name",vfield.getField_name());if(vfield.getField_name().contains("textarea")){pd.put("field_type","varchar(500)");}else if(vfield.getField_name().contains("editor")){pd.put("field_type","text");}else{if(Verify.verifyIsNotNull(vfield.getMaxLength())){if(Integer.parseInt(vfield.getMaxLength())<=120){pd.put("field_type","varchar(120)");}else{pd.put("field_type","varchar("+vfield.getMaxLength()+")");}}else{pd.put("field_type","varchar(120)");}}pd.put("field_null","null");pd.put("field_comment",vfield.getField_comment());System.out.println(pd.toString());this.vfieldService.addField(pd);}}
说明
本节内容,从理解上有点负责,稍微有点难度,对于新手来说有点困难,需要根据文档的指导,一步步的查找实现方式,本节也是拖拽表单的核心,这一块了解了,拖拽表单和数据表的创建模式也就明白了。
