自定义表单-操作说明

功能列表

点击自定义表单信息,打开自定义表单管理页面。
image.png

拖拽表单设计

左侧为拖拽表单的基本控件,右侧为拖拽表单的属性,中间位置为拖拽表单预览部分。
image.png

基础表单组件

输入框、文本框、数字输入框、下拉选择器、多选框、单选框、日期选择框、时间选择框、评分、滑动输入条、树形选择器、级联选择器、选择输入列、富文本、开关。

辅助组件

按钮、警告提示、文字、HTML

子表表单组件(重要)

动态表格
每次拖拽一个动态表格,相当于创建一个子表,一个动态表格内的字段为子表字段。

布局组件(辅助元素)

分割线、卡片布局、标签页布局、栅格布局、表格布局。

拖拽表单属性(重点属性介绍)

表单属性设置

表单名称:创建数据表的备注说明。
数据表名称:创建数据表的表名称。
image.png

基础控件属性

标签:数据字段的备注名称。
数据字段:数据表字段名称。
image.png

组件动态数据

下拉框、多选框、单选框组件可以配置动态数据,动态数据须指定字典的父级编码:
字典下拉项会自动展示父级节点的下级内容。
image.png
image.png

自定义表单-技术要点说明

前端代码重点解析

image.png
下面只介绍拖拽表单的整合及预览的代码,首页代码为通用代码,不做展示。

拖拽表单整合代码如下

  1. <template>
  2. <div class="design_div">
  3. <k-form-design
  4. showToolbarsText
  5. toolbarsTop
  6. title="青锋表单设计器"
  7. :showHead="false"
  8. :hideModel="false"
  9. @save="handleSave"
  10. @close="handleClose"
  11. ref="qfRef"
  12. />
  13. </div>
  14. </template>
  15. <script>
  16. import { saveOrUpdate } from "@/api/customize/vform";
  17. export default {
  18. // 声明当前子组件接收父组件传递的属性
  19. props: {
  20. record: {
  21. type: Object,
  22. default: null,
  23. },
  24. },
  25. data() {
  26. return {
  27. jsonData: {},
  28. };
  29. },
  30. mounted() {
  31. if (this.record.id != "" && this.record.id != undefined) {
  32. this.jsonData = JSON.parse(this.record.table_content);
  33. this.importData();
  34. }
  35. },
  36. methods: {
  37. handleSave(values) {
  38. //alert("触发保存方法");
  39. //console.log(values);
  40. //this.$refs.qfRef.handleReset();
  41. let data = this.$refs.qfRef.getValue();
  42. data.table_content = values;
  43. console.log(this.record);
  44. let myRecord = "";
  45. if (this.record != "") {
  46. myRecord = JSON.stringify(this.record);
  47. }
  48. saveOrUpdate({
  49. record: myRecord,
  50. table_content: values,
  51. config: JSON.stringify(data.config),
  52. list: JSON.stringify(data.list),
  53. }).then((response) => {
  54. if (response.data.success) {
  55. this.$success({
  56. title: "提示:",
  57. content: "表单创建成功。",
  58. });
  59. this.$emit("finishResponse", response);
  60. } else {
  61. this.$message.error(response.data.msg);
  62. }
  63. });
  64. },
  65. handleClose(values) {},
  66. importData() {
  67. this.$refs.qfRef.handleSetData(this.jsonData);
  68. },
  69. },
  70. };
  71. </script>
  72. <style>
  73. .design_div {
  74. margin-top: -20px;
  75. }
  76. .operating-area {
  77. background-color: #f6f6f6;
  78. }
  79. .k-form-design {
  80. margin-top: -20px;
  81. }
  82. </style>

拖拽表单代码预览

  1. <template>
  2. <div>
  3. <k-form-build ref="qfRef" :value="jsonData" />
  4. <a-modal v-model="visible" title="表单数据" on-ok="handleOk">
  5. <template slot="footer">
  6. <a-button
  7. key="submit"
  8. type="primary"
  9. :loading="loading"
  10. @click="handleOk"
  11. >
  12. 关闭
  13. </a-button>
  14. </template>
  15. <pre>{{data}}</pre>
  16. </a-modal>
  17. </div>
  18. </template>
  19. <script>
  20. export default {
  21. // 声明当前子组件接收父组件传递的属性
  22. props: {
  23. record: {
  24. type: Object,
  25. default: null,
  26. },
  27. },
  28. data() {
  29. return {
  30. jsonData: {},
  31. visible: false,
  32. data:{}
  33. };
  34. },
  35. mounted() {
  36. this.jsonData = JSON.parse(this.record.table_content);
  37. },
  38. methods: {
  39. onOk() {
  40. // 使用getData函数获取数据
  41. this.$refs.qfRef
  42. .getData()
  43. .then((values) => {
  44. this.visible = true;
  45. this.data = values;
  46. console.log("验证通过", values);
  47. })
  48. .catch(() => {
  49. console.log("验证未通过,获取失败");
  50. });
  51. },
  52. onCancel() {
  53. return new Promise((resolve) => {
  54. resolve(true);
  55. });
  56. },
  57. handleOk() {
  58. this.visible = false;
  59. },
  60. },
  61. };
  62. </script>

后端代码重点解析

image.png

后端代码的核心功能在于设计表单的数据存储和更新,下面重点讲解拖拽代码的保存和更新方法。

拖拽表单保存更新方法

1、根据record判断是否为空,为空则添加,不为空则更新
image.png
后端代码如下:

  1. @PostMapping
  2. @PreAuthorize("hasAnyAuthority('vform:add')")
  3. public void save(@Valid @RequestBody PageData pd,HttpServletResponse response) throws Exception {
  4. Json json = new Json();
  5. try {
  6. System.out.println(pd.get("record"));
  7. //根据record判断是否为空,为空则添加,不为空则更新
  8. if(Verify.verifyIsNotNull(pd.get("record"))){
  9. //查询数据表是否已经存在,如果存在则提示
  10. Map record = (Map) JSON.parse(pd.get("record").toString());
  11. Map maps = (Map) JSON.parse(pd.get("config").toString());
  12. if(record.get("table_name").equals(maps.get("table_name"))){
  13. this.vformService.updateVform(pd);
  14. json.setSuccess(true);
  15. json.setMsg("数据更新成功");
  16. }else{
  17. QueryWrapper queryWrapper = new QueryWrapper();
  18. queryWrapper.eq("table_name",maps.get("table_name"));
  19. int num = vformService.list(queryWrapper).size();
  20. if(num==0){
  21. pd.put("table_schema",table_schema);
  22. pd.put("table_name",maps.get("table_name"));
  23. int n = vformService.findTableList(pd).size();
  24. if(n==0){
  25. //添加-创建信息
  26. this.vformService.updateVform(pd);
  27. json.setSuccess(true);
  28. json.setMsg("数据更新成功");
  29. }else{
  30. json.setSuccess(false);
  31. json.setMsg("数据表已创建,不可重复创建。");
  32. }
  33. }else{
  34. json.setSuccess(false);
  35. json.setMsg("数据表信息已存在,不可重复创建。");
  36. }
  37. }
  38. }else{
  39. //判断数据表是否存在,存在则提示
  40. Map maps = (Map) JSON.parse(pd.get("config").toString());
  41. QueryWrapper queryWrapper = new QueryWrapper();
  42. queryWrapper.eq("table_name",maps.get("table_name"));
  43. int num = vformService.list(queryWrapper).size();
  44. if(num==0){
  45. pd.put("table_schema",table_schema);
  46. pd.put("table_name",maps.get("table_name"));
  47. int n = vformService.findTableList(pd).size();
  48. if(n==0){
  49. //添加-创建信息
  50. this.vformService.saveVform(pd);
  51. json.setSuccess(true);
  52. json.setMsg("新增信息成功");
  53. }else{
  54. json.setSuccess(false);
  55. json.setMsg("数据表已创建,不可重复创建。");
  56. }
  57. }else{
  58. json.setSuccess(false);
  59. json.setMsg("数据表信息已存在,不可重复创建。");
  60. }
  61. }
  62. } catch (Exception e) {
  63. e.printStackTrace();
  64. String message = "新增信息失败";
  65. json.setSuccess(false);
  66. json.setMsg(message);
  67. throw new MyException(message);
  68. }
  69. this.writeJson(response,json);
  70. }

重点在于saveVform和updateVform两个方法。
image.png

saveVform方法解析/updateVform同理

  1. @Transactional
  2. public void saveVform(PageData pd) throws Exception{
  3. //更新操作:如果表名称发生改变,则修改表名称;如果字段发生变更,则变更数据表结构。
  4. Map maps = (Map) JSON.parse(pd.get("config").toString());
  5. Vform vform = new Vform();
  6. String id = GuidUtil.getUuid();
  7. vform.setId(id);
  8. String time = DateTimeUtil.getDateTimeStr();
  9. vform.setCreate_time(time);
  10. String type = "0";
  11. //处理数据权限
  12. String authParams = SecurityContextHolder.getContext().getAuthentication().getName();
  13. vform.setCreate_user(authParams.split(":")[1]);
  14. vform.setCreate_organize(authParams.split(":")[2]);
  15. vform.setType(type);
  16. vform.setTable_name(maps.get("table_name").toString());
  17. vform.setTable_comment(maps.get("table_comment").toString());
  18. vform.setTable_content(pd.get("table_content").toString());
  19. this.save(vform);
  20. //数据表字段
  21. List<Vfield> vfields = new ArrayList<Vfield>();
  22. List<PageData> list = JSON.parseArray(pd.get("list").toString(),PageData.class);
  23. //获取需要创建的主表字段
  24. vfields = createField(id,list,vfields,"add",new ArrayList<Vform>());
  25. vfieldService.saveBatch(vfields);
  26. //创建数据表
  27. pd.put("table_name",maps.get("table_name").toString());
  28. pd.put("table_comment",maps.get("table_comment").toString());
  29. this.baseMapper.createTable(pd);
  30. this.baseMapper.updateTableComment(pd);
  31. //创建数据字段
  32. for (Vfield vfield:vfields) {
  33. pd.put("field_name",vfield.getField_name());
  34. if(vfield.getField_name().contains("textarea")){
  35. pd.put("field_type","varchar(500)");
  36. }else if(vfield.getField_name().contains("editor")||vfield.getField_name().contains("uploadFile")||vfield.getField_name().contains("uploadImg")){
  37. pd.put("field_type","text");
  38. }else{
  39. if(Verify.verifyIsNotNull(vfield.getMaxLength())){
  40. if(Integer.parseInt(vfield.getMaxLength())<=120){
  41. pd.put("field_type","varchar(120)");
  42. }else{
  43. pd.put("field_type","varchar("+vfield.getMaxLength()+")");
  44. }
  45. }else{
  46. pd.put("field_type","varchar(120)");
  47. }
  48. }
  49. pd.put("field_null","null");
  50. pd.put("field_comment",vfield.getField_comment());
  51. System.out.println(pd.toString());
  52. this.vfieldService.addField(pd);
  53. }
  54. }

1、保存主表数据

image.png

2、解析子表组队并保存

子表已list json格式传递,解析成为list对象在进行存储。
image.png

3、创建基础数据并修改备注

此处并未创建完整的表,只创建基础数据表,基础数据表的字段内容有:
初始字段: id、type、status(0,1)、order_by、create_time、create_user、create_organize、update_user、update_time
image.png

4、执行添加表字段

image.png

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

image.png

  1. public List<Vfield> createField(String table_id,List<PageData> list,List<Vfield> vfields,String type,List<Vform> vforms){
  2. String time = DateTimeUtil.getDateTimeStr();
  3. //当前登录用户
  4. String authParams = SecurityContextHolder.getContext().getAuthentication().getName();
  5. for (PageData p:list) {
  6. if(noField.contains(p.get("type").toString())){
  7. continue;
  8. }
  9. if(bjField.contains(p.get("type").toString())){
  10. vfields = createLayoutField(table_id,vfields,p,type,vforms);
  11. }else{
  12. Vfield vfield = new Vfield();
  13. vfield.setId(GuidUtil.getUuid());
  14. vfield.setTable_id(table_id);
  15. vfield.setType("0");
  16. vfield.setField_name(p.get("model").toString());
  17. vfield.setField_comment(p.get("label").toString());
  18. vfield.setField_key(p.get("key").toString());
  19. vfield.setField_type(p.get("type").toString());
  20. vfield.setCreate_time(time);
  21. vfield.setCreate_user(authParams.split(":")[1]);
  22. vfield.setCreate_organize(authParams.split(":")[2]);
  23. Map pp = (Map) JSON.parse(p.get("options").toString());
  24. vfield.setMaxLength(pp.get("maxLength")+"");
  25. //处理包含动态数据源的选项标签
  26. if(dynamicField.contains(p.get("type").toString())){
  27. vfield.setDynamic(pp.get("dynamic").toString());
  28. vfield.setDynamicKey(pp.get("dynamicKey").toString());
  29. vfield.setOptions(pp.get("options").toString());
  30. }
  31. vfields.add(vfield);
  32. if(p.get("type").equals("batch")){
  33. if(type.equals("save")){
  34. createLinkTable(table_id,p);
  35. }else if(type.equals("update")){
  36. updateLinkTable(table_id,vforms,p);
  37. }
  38. }
  39. }
  40. }
  41. return vfields;
  42. }

下面详细分析createField
1、字段类型为:button、alert、text、html、divider说明是辅助字段,忽略。
2、字段类型为:card、tabs、grid、table 说明是布局组件,需要继续遍历布局组件,找到里面真正的表单组件。下面会单独介绍。
image.png
判断是否为动态数据表,如果为动态数据表则处理关联字段。
image.png

6、布局组件的递归遍历

递归遍历-查询布局组件下的实际form组件。
image.png
下面分别对卡片布局、标签页布局、栅格布局、表格布局做了递归操作。
大家可能会注意到,在递归操作过程中又调用了【createField】细心的朋友会发现,明明刚才在createField中调用的createLayoutField,的确如此,这就是递归的妙处。

  1. //递归遍历-创建字段 tabs、grid、table
  2. public List<Vfield> createLayoutField(String table_id,List<Vfield> vfields,PageData fieldPd,String type,List<Vform> vforms){
  3. if(fieldPd.get("type").equals("card")){
  4. List<PageData> list = JSON.parseArray(fieldPd.get("list").toString(),PageData.class);
  5. vfields = createField(table_id,list,vfields,type,vforms);
  6. }else if(fieldPd.get("type").equals("tabs")||fieldPd.get("type").equals("grid")){
  7. List<PageData> columns = JSON.parseArray(fieldPd.get("columns").toString(),PageData.class);
  8. for(PageData cp:columns){
  9. List<PageData> list = JSON.parseArray(cp.get("list").toString(),PageData.class);
  10. vfields = createField(table_id,list,vfields,type,vforms);
  11. }
  12. }else if(fieldPd.get("type").equals("table")){
  13. List<PageData> trs = JSON.parseArray(fieldPd.get("trs").toString(),PageData.class);
  14. for (PageData trPd:trs){
  15. List<PageData> tds = JSON.parseArray(trPd.get("tds").toString(),PageData.class);
  16. for (PageData tdPd:tds) {
  17. List<PageData> list = JSON.parseArray(tdPd.get("list").toString(),PageData.class);
  18. vfields = createField(table_id,list,vfields,type,vforms);
  19. }
  20. }
  21. }
  22. return vfields;
  23. }

7、动态数据表-关联字段递归

通过分析,拆解创建关联表信息,大致分为一下内容。
image.png

  1. //处理关联表数据
  2. public void createLinkTable(String main_id,PageData pd){
  3. Vform vform = new Vform();
  4. String id = GuidUtil.getUuid();
  5. vform.setId(id);
  6. String time = DateTimeUtil.getDateTimeStr();
  7. vform.setCreate_time(time);
  8. String type = "1";
  9. vform.setMain_id(main_id);
  10. //处理数据权限
  11. String authParams = SecurityContextHolder.getContext().getAuthentication().getName();
  12. vform.setCreate_user(authParams.split(":")[1]);
  13. vform.setCreate_organize(authParams.split(":")[2]);
  14. vform.setType(type);
  15. vform.setTable_name(pd.get("model").toString());
  16. vform.setTable_comment(pd.get("label").toString());
  17. vform.setTable_content(pd.get("key").toString());
  18. this.save(vform);
  19. //数据表字段
  20. List<Vfield> vfields = new ArrayList<Vfield>();
  21. List<PageData> list = JSON.parseArray(pd.get("list").toString(),PageData.class);
  22. for (PageData p:list) {
  23. if(noField.contains(p.get("type").toString())){
  24. continue;
  25. }
  26. Vfield vfield = new Vfield();
  27. vfield.setId(GuidUtil.getUuid());
  28. vfield.setTable_id(id);
  29. vfield.setType(type);
  30. vfield.setField_name(p.get("model").toString());
  31. vfield.setField_comment(p.get("label").toString());
  32. vfield.setField_key(p.get("key").toString());
  33. vfield.setField_type(p.get("type").toString());
  34. vfield.setCreate_time(time);
  35. vfield.setCreate_user(authParams.split(":")[1]);
  36. vfield.setCreate_organize(authParams.split(":")[2]);
  37. Map pp = (Map) JSON.parse(p.get("options").toString());
  38. vfield.setMaxLength(pp.get("maxLength")+"");
  39. //处理包含动态数据源的选项标签
  40. if(dynamicField.contains(p.get("type").toString())){
  41. vfield.setDynamic(pp.get("dynamic").toString());
  42. vfield.setDynamicKey(pp.get("dynamicKey").toString());
  43. vfield.setOptions(pp.get("options").toString());
  44. }
  45. vfields.add(vfield);
  46. }
  47. vfieldService.saveBatch(vfields);
  48. //创建数据表
  49. pd.put("table_name",pd.get("model").toString());
  50. pd.put("table_comment",pd.get("label").toString());
  51. this.baseMapper.dropTable(pd);
  52. this.baseMapper.createTable(pd);
  53. this.baseMapper.updateTableComment(pd);
  54. //创建数据字段
  55. for (Vfield vfield:vfields) {
  56. pd.put("field_name",vfield.getField_name());
  57. if(vfield.getField_name().contains("textarea")){
  58. pd.put("field_type","varchar(500)");
  59. }else if(vfield.getField_name().contains("editor")){
  60. pd.put("field_type","text");
  61. }else{
  62. if(Verify.verifyIsNotNull(vfield.getMaxLength())){
  63. if(Integer.parseInt(vfield.getMaxLength())<=120){
  64. pd.put("field_type","varchar(120)");
  65. }else{
  66. pd.put("field_type","varchar("+vfield.getMaxLength()+")");
  67. }
  68. }else{
  69. pd.put("field_type","varchar(120)");
  70. }
  71. }
  72. pd.put("field_null","null");
  73. pd.put("field_comment",vfield.getField_comment());
  74. System.out.println(pd.toString());
  75. this.vfieldService.addField(pd);
  76. }
  77. }

说明

本节内容,从理解上有点负责,稍微有点难度,对于新手来说有点困难,需要根据文档的指导,一步步的查找实现方式,本节也是拖拽表单的核心,这一块了解了,拖拽表单和数据表的创建模式也就明白了。