资讯动态

政务低代码平台实战④:可视化表单设计器——拖拽、合并拆分、代码生成

发布时间:2026/8/11 20:19:31 来源:尧图企业网站定制
政务低代码平台实战④可视化表单设计器——拖拽、合并拆分、代码生成文章目录政务低代码平台实战④可视化表单设计器——拖拽、合并拆分、代码生成为什么不能做固定行列设计器整体结构表格的创建和自由布局创建表格合并单元格 mc()拆分单元格 cf()控件拖入表格属性编辑面板代码自动生成生成的函数列表保存方法的自动生成控件拖拽单元格间移动设计器的两种模式决策原则非科班野生程序员深耕政务信息化20年。政务审批表单有多复杂请假申请要并排三列报销审批要有合并单元格的大标题栏采购审批要嵌套子表——固定行列的低代码方案根本搞不定。这篇拆解基于Dojo的可视化表单设计器怎么实现自由布局。最后感谢豆包、智谱、OpenCode决策是我做的代码是我搓的文字是他们总结的。为什么不能做固定行列市面上很多低代码平台的表单设计器是这样的配置几个字段选几列布局自动排列。政务审批表单不行。一张采购审批表可能是这样的┌─────────┬─────────┬─────────┬─────────┐ │ 申请人 │ 申请部门│ 申请日期│ │ ├─────────┼─────────┼─────────┴─────────┤ │采购事项 │ 合并4列 │ ├─────────┼─────────┬─────────┬─────────┤ │物品名称 │ 数量 │ 单价 │ 金额 │ │物品名称 │ 数量 │ 单价 │ 金额 │ ├─────────┴─────────┴─────────┴─────────┤ │ 审批意见合并4列文本域 │ └───────────────────────────────────────┘有合并、有嵌套、有不同宽度——这就是为什么必须做可合并拆分的表格布局。设计器整体结构formEdit.jsp是三栏布局┌──────────┬──────────────────────┬──────────┐ │控件与容器│ 工作区域 │属性/事件 │ │ │ │ │ │可创建控件│ 编辑 | 源码 | 脚本 │属性面板 │ │ (树形) │ │事件面板 │ │ │ │ │ │ 容器结构 │ [合并][拆分][取消] │ │ │ (树形) │ [插行][追行][删行] │ │ │ │ [载入][保存JSP][保存]│ │ └──────────┴──────────────────────┴──────────┘左栏控件树双击创建 容器结构树显示当前组件层级中栏工作区拖拽设计 源码预览 脚本编辑右栏属性面板中文名/英文名/类型/校验/默认值 事件面板底部合并/拆分/插入行/追加行/删除行/载入/保存JSP/保存scriptUI目录下的10个JS文件各司其职文件职责const.js控件注册表定义可创建的控件列表values.js全局状态组件树、SQL绑定、事件绑定optable.js表格操作创建/合并/拆分/行操作opgrid.jsGrid操作创建数据窗口coms.js控件创建TextBox/DateTextBox/ComboBox/…optab.jsTab页签操作optitle.js标题容器操作uiglobal.js属性编辑 代码生成treecommon.js树数据源管理表格的创建和自由布局创建表格双击控件树的表格弹出配置对话框tableTitle.jsp输入行列数、宽度、绑定的SQL语句。确定后createTable()在工作区生成一个 HTML tablefunctioncreateTable(value,pid){varcolsvalue.cols;varrowsvalue.rows;vartableNodedojo.create(table,{width:width1,id:tid1});// 生成colgroup等宽列varcolgroupdojo.create(colgroup);varwparseInt(100/cols)%;for(vary0;ycols;y){dojo.place(dojo.create(col,{width:w}),colgroup);}// 生成rows x cols的单元格for(varx0;xrows;x){vartrNodetableNode.insertRow();for(vary0;ycols;y){vartdNodetrNode.insertCell();tdNode.setAttribute(class,td1);// 绑定事件双击选中、拖拽、鼠标悬停dojo.query(#tdNode.id).connect(ondblclick,tddbclick);dojo.query(#tdNode.id).connect(onmousedown,tdmousedown);dojo.query(#tdNode.id).connect(onmouseup,tdmouseup);}}// 表格放在Form里方便数据绑定vartfcreateform(fid1,width1,rows,cols,fbinding);dojo.place(tableNode,tf.containerNode,last);}表格外面自动套一个browise.form.Form用于数据绑定。合并单元格mc()双击选中多个单元格同一行或同一列点合并functionmc(){vartabObjdocument.getElementById(tableid);varmaxcolgetmax(tableNodes,col);varmincolgetmin(tableNodes,col);varmaxrowgetmax(tableNodes,row);varminrowgetmin(tableNodes,row);// 校验只能合并同行或同列if(maxcol-mincol0maxrow-minrow0){MessageBox.alert({message:无法合并!只能选择同行或者同列});return;}if(maxcol-mincol0){// 横向合并计算总colSpan删除多余tdfor(varimaxcol;imincol;i--){colspanstabObj.rows[minrow].cells[i].colSpan;if(i!mincol)tabObj.rows[minrow].deleteCell(i);}tabObj.rows[minrow].cells[mincol].colSpancolspans;}else{// 纵向合并计算总rowSpan删除多余tdfor(variminrow;imaxrow;){rowspanstabObj.rows[i].cells[mincol].rowSpan;if(i!minrow)tabObj.rows[i].deleteCell(mincol);itabObj.rows[i].cells[mincol].rowSpan;}tabObj.rows[minrow].cells[mincol].rowSpanrowspans;}}拆分单元格cf()选中一个已合并的单元格点拆分还原为1x1并补上新的tdfunctioncf(){varrowcountcurr.rowSpan;varcolcountcurr.colSpan;// 在对应位置插入新的tdfor(vari0;irowcount;i){for(varj0;jcolcount;j){if(j0i0)continue;// 原单元格保留vartdNodetabObj.rows[icurrrowindex].insertCell(currcellindexj);tdNode.setAttribute(class,td1);// 绑定事件...}}curr.colSpan1;curr.rowSpan1;}合并拆分的难点是位置计算——前面有跨行单元格时拆分要先检查位置占用否则会错位。cf()里有一段遍历前面所有行的代码做这个检查。控件拖入表格双击左侧控件树创建控件控件出现在当前选中的表格单元格里functionsetconts(txt,com,div,event){if(!curr){MessageBox.alert({message:请选择单元格!});returnfalse;}// 创建一个包裹divdivdojo.create(div,{width:100%,id:d_random()});coms[div.id]com;// 注册到全局组件树divs[div.id]div;if(event)aevent[com.id]event;com.startup();dojo.place(div,curr,last);// div放进单元格com.placeAt(div,last);// 控件放进divdojo.connect(div,onclick,click);// 点击显示属性curr.setAttribute(align,left);// 放控件后左对齐currnull;clear1();}支持的控件类型coms.js函数控件说明createtextTextBox普通文本createtdDateTextBox日期选择createcbComboBox下拉框绑二级代码createtreecomComboBoxTree下拉树createcbtnButton按钮createrdRadioButtonGroup单选按钮组createcbgCheckBoxGroup复选框组createtaTextarea文本域createcrtRichTextEditor2富文本编辑器createTextIconTextBoxWithIcon图标文本前置流程选择创建控件后自动在前面一格填上中文名标签functionsave(id,ots,json){if(currcurr.cellIndex1ots.cls!browise.form.Button){if(!curr.parentNode.cells[curr.cellIndex-1].innerText){curr.parentNode.cells[curr.cellIndex-1].innerTextots.dis:;}}ajsons[id]json;// 保存组件配置afothers[id]ots;// 保存其他参数}属性编辑面板点击控件右侧属性面板显示所有可编辑属性getPara()函数属性说明控件类型中文名显示的标签文本所有英文名数据绑定字段名binding.name非Button二级代码ComboBox的数据源store名ComboBox/RadioButtonGroup/CheckBoxGroup数据类型eae009编码非Button校验正则表达式非Button是否必录required属性非Button是否禁用disabled属性非Button默认值日期/用户/部门/电话等预设非Button含时间DateTextBox是否显示时分秒DateTextBox字体大小td的fontSize所有对齐方式textAlign非Button点应用按钮apply()函数把修改写回控件实例。代码自动生成这是设计器最省时的功能。点生成默认脚本按钮impdefault1()自动根据当前设计的组件生成完整的JS代码生成的函数列表函数功能什么时候有seach_xxxGrid的查询方法Grid绑定了SQL时commansave统一保存方法Form绑定了SQL时del_xxxGrid的删除方法Grid绑定了SQL时xz_xxxGrid的新增方法弹窗Grid配了新增界面时xg_xxxGrid的修改方法弹窗Grid配了修改界面时xxx_dblickGrid的双击事件弹窗Grid配了双击事件时open_xxx前置流程选择弹窗TextBoxWithIcon配了前置流程时保存方法的自动生成以create_save()为例遍历所有Form和Grid自动生成保存JSfunctioncreate_save(nodes){myscriptsfunction commansave(){\r\n;myscripts\tvar dc new browise.ds.DataCenter();\r\n;for(vari0;inodes.length;i){if(asql[nodes[i].id]asql[nodes[i].id].seach){// Form类型先校验if(nodes[i].declaredClassbrowise.form.Form){myscripts\tvar myformbrowise.byId(\nodes[i].id\);\r\n;myscripts\tvar isValidmyform.validate();\r\n;myscripts\tif(!isValid){ alert(...); return; }\r\n;}// 取DataStore设置up/ins/del参数myscripts\tvar dsibrowise.byId(\nodes[i].id\).getBinding().getDataStore();\r\n;myscripts\tdsi.setParameter(\up\,\sqlId\);\r\n;myscripts\tdsi.setParameter(\ins\,\sqlId\);\r\n;myscripts\tdsi.setParameter(\del\,\sqlId\);\r\n;myscripts\tdc.addDataStore(dsi);\r\n;}}myscripts\tvar URLwebRootDir/BusinessAction?BusinessCommonSQLActionCommonSaveByseach;\r\n;myscripts\tbrowise.Action.requestData(data, dc, true);\r\n;myscripts}\r\n;}生成的代码直接利用了CommonSQLcontrol.CommonSaveByseach用SELECT语句的元数据做增删改——不需要单独配INSERT/UPDATE/DELETE。控件拖拽单元格间移动支持单元格之间拖拽控件// mousedown记录拖拽源functiontdmousedown(evt){tdojbthis;this.style.cursormove;}// mouseup放到目标单元格functiontdmouseup(evt){if(tdojbnull||tdojbthis)return;if(this.children[0]){// 目标有内容确认覆盖if(!window.confirm(目标单元格里存在内容!确认覆盖?))return;}if(tdojb.children[0]){dojo.place(tdojb.children[0],this,last);// 移控件}else{this.innerTexttdojb.innerText;// 移文本tdojb.innerText;}tdojbnull;}也支持从控件树拖拽到单元格——mousedown记录树节点mouseup时在目标单元格创建控件。设计器的两种模式UI/formEdit.jsp和UI2/formEdit.jsp有两个版本UI版本UI2版本myedittype“1”“2”oneSqlfalsetrue用途列表表单查询页纯表单编辑页额外功能—选择SQL语句、预览按钮代码生成查询Grid增删改保存仅保存UI模式用于设计上Grid下条件的查询页面UI2模式用于设计表单页面工作流绑定的那种。决策原则用业务人员能理解的方式描述表单。政务审批表单的本质是一张表格业务人员习惯了Excel式的布局。给他们一个可以合并拆分的表格编辑器比教他们配置JSON schema容易得多。控件拖进去、属性填上、代码自动生成一个表单从设计到可用不超过半小时。你项目里的表单设计器是怎么做的是固定布局还是自由布局欢迎评论区聊聊。系列导航总纲[政务低代码平台实战——从元数据引擎到可视化设计器的五个关键决策]上一篇[政务低代码平台实战③CommonSQL——一个接口扛起所有CRUD]下一篇[政务低代码平台实战⑤双输出模式——存DB与导出JSP的完整链路]作者许彰午| 非科班野生程序员深耕政务信息化20年标签#Java #低代码 #表单设计器 #拖拽 #合并拆分 #Dojo #政务信息化

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价