前言
在很多有工作流设置的地方、比如需要在不同的流程中,需要实现流程表单的自定义。在以前的一些业务中,我们几乎都需要用户来固化表单。这样的实现方式,非常不友好,扩展性也不强。通常在上线后会需要进行调整。同时,在调整时,一般需要用户先明确流程,然后再反馈给开发人员,因为当时没有在线的流程表单构造器,流程的表单调整还需要开发人员来配合。这样一来,系统的开发步骤就比较长,一个流程要想走下来,花费的时间代价就非常大。因此,在这样的需求背景下,有的技术团队开始研究动态表单,将用户的创造和动手能力直接引入进来。用户不仅能自定义流程引擎,同时还能基于流程引擎来定义挂载在流程引擎上的表单。
到了这一步,应该说是非常友好的,用户可以深度的参与相关的设计,如果想调整流程。只需要下架旧的流程,然后设计新的流程,同时把相应的表单也设计好后一同发布。这样系统就会启动新的流程,表单也会自动更新。曾几何时,这种技术还是少数部分用户玩的,而今再看,就像“旧时王谢堂前燕飞入寻常百姓家”,已经没有了什么神秘的面纱,向大众展示它背后的一面。
虽然在流程中使用动态表单,有很好的扩展性和可用性。但是,同时也带来了一些额外的技术实现复杂度,就是在生成动态表单的时候,表单通常比较复杂,通常我们需要进行很多表单元素的编辑,还要定义表单的值,各种各样的表单元素类型。如果我想使用表格来导入这些数据,应该怎么来进行对应。导出的时候,怎么精准的写出数据。这对我们在数据库中设计相应的表以及针对动态表单的数据进行表单元素级的精准匹配有了一定的技术要求。
本文重点不是在于讲解如何设计动态表单,而是重点讲解,如何在将设计好的动态表单信息进行提取,比如进行模板数据导入的时候,可以根据不同的表单类型,比如根据单行文字框的名字来动态设置值,也可以在导数数据时,知道将数据库的性别一列保存的1和2翻译成男和女这两种属性。这都需要我们精准的提取表单中的不同的信息,能精准提取表单的文本、类型、默认值域还有其他的表单元素的设置。通过本文,您可以了解如何正确的操作动态表单信息,同时了解如何从表单中查找表单元素。
一、动态表单技术
为了让大家了解一些动态表单可能会包含哪些技术,我还是决定对动态表单技术进行简单的讲解,更深入的就不再进行赘述。目的是让大家对动态表单有个基本的认识。
1、包含的主要信息
众所周知,在web界面的设计和实现中,表单其实就一个form界面,我们在这个web界面中可以定义不同的表单元素,比如单行文本框,多行文本域、单选按钮、多选按钮、下拉框,而在现代的界面中,对元素的类型做了更进一步的细分,比如时间又可以分为时间选择器、日期选择器,其它的常见还有打分控件、计数器、颜色选择器、开关、滑块等等。在下图中列出来了常见的一些表单要素。
上面这个就是一个非常典型的表单设计器,它按照功能区域可以分为表单元素类型、表单设计渔区、属性设计渔区三个部分。从结构来说分为左、中、右三种类型。最左边的部分是表单中包含的元素类型,这个在上面的内容中有所涉及。下面有布局字段,布局的话就是用来控制页面的元素如何布置,比如一行是摆放三个单行文本域还是摆2个,这些都是通过布局元素来设置的。 中间就是主题的设计界面。点击左边的元素,然后拖到中间的设计器中即可。
2、元素属性设置
将元素和布局都设置好之后,一个设计良好的表单,还需要对表单的属性信息进行定义,比如表单的名字、它的默认值是什么,如果是下拉框,下拉框的值域又是什么?默认的下拉选项是哪个。表单的元素是否必填,是有其它的数据格式校验类型等等。这些属性信息都在最右边的属性编辑器中进行定义和设置。在中间的要素设计器中点击对应的表单元素,可以打开它对应的属性设置信息。如下图所示:
通过上面的表单设计界面,我们就可以实现表单的灵活设置。
3、表单内容
相信大家对于生成的表单内容是什么样的,一定很有兴趣吧。下面我们来看一下经过上面的动态表单设计之后,生成的表单内容是什么样的?具体的格式是什么?首先来点击预览,看一下表单设计器生成的页面效果。
点击“生成json”按钮可以将动态表单转成json,这样我们就可以把表单存储到数据库中,实现动态的管理和配置。来看下json的表单生成结果。
内容比较多,我们将表单内容复制到文本编辑器中,然后将json进行格式的美化后来看实际的效果。
"list": [ { "type": "grid", "icon": "icon-grid-", "columns": [ { "span": 12, "list": [ { "type": "input", "icon": "icon-input", "options": { "width": "100%", "defaultvalue": "", "required": false, "datatype": "string", "pattern": "", "placeholder": "请输入姓名", "disabled": false, "maxlength": -1, "showwordlimit": false, "remotefunc": "func_1721826724000_49979" }, "name": "姓名", "key": "1721826724000_49979", "model": "input_1721826724000_49979", "rules": [ { "type": "string", "message": "姓名格式不正确" } ] } ] }
以上就是我们对上面的动态表单的相关知识的介绍。介绍上面的内容,主要是为了让大家对数据的格式和样例有一个基本的掌握。
二、表单数据存储和查询
在掌握了动态表单的设计之后,为了实现可以灵活的管理和检索,我们还需要将表单的数据进行存储,在需要调用的时候还需要进行高效的查询。本节就简单的来讲一下如何进行动态表单数据的存储和查询。数据库采用的是mysql数据库,版本是5.7,采用的数据类型是json。
1、数据存储
要想把上述的表单数据存储起来,我们可以有两种选择,第一种是将表单的内容直接存成字符串,配合后台的开发语言来实现表单的存储。第二种就是利用数据库的特性,直接将数据以json的形式存储。第一种方案,数据库简单,但是应用程序复杂。而第二种方案刚好相反。依然使用了数据库,而且用到了5.7这样的版本,在mysql当中,是支持我们直接操作和管理json类型的数据的。因此这里我们采用第二种方案,直接设计json字段。将动态表单的内容存储到json字段中。设计的表结构如下:
-- ---------------------------- -- table structure for ems_equipment_classification_attr -- ---------------------------- drop table if exists `ems_equipment_classification_attr`; create table `ems_equipment_classification_attr` ( `equip_class_id` varchar(10) character set utf8mb4 collate utf8mb4_general_ci not null default '' comment '设备分类id', `attr_config` json not null comment '属性信息,以json的形式存储', `create_uid` varchar(20) character set utf8mb4 collate utf8mb4_general_ci not null default '' comment '创建人', `create_time` int(11) not null default 0 comment '创建时间', `modify_uid` varchar(20) character set utf8mb4 collate utf8mb4_general_ci not null default '' comment '更新人', `modify_time` int(11) not null default 0 comment '更新时间', `version` int(4) not null default 0 comment '版本号', `inherit_flag` int(4) not null default 1 comment '属性继承标记,1表示继承上级属性,0表示独立属性', index `idx_ems_equipment_class_attr_class_id`(`equip_class_id`) using btree ) engine = innodb character set = utf8mb4 collate = utf8mb4_general_ci comment = '设备分类属性,以json的形式进行存储,支持各层级单独定义,下级分类自动继承上级分类属性' row_format = dynamic;
在上述表中我们有一个attr_config 字段,这个字段设计成json类型的,主要用来保存动态表单的值。然后我们使用以下语句来新增一条记录:
-- ---------------------------- -- records of ems_equipment_classification_attr -- ---------------------------- insert into `ems_equipment_classification_attr` values ('3', '{\"formconfig\": {\"size\": \"\", \"csscode\": \"\", \"refname\": \"vform\", \"functions\": \"\", \"modelname\": \"formdata\", \"rulesname\": \"rules\", \"labelalign\": \"label-left-align\", \"labelwidth\": 80, \"layouttype\": \"pc\", \"customclass\": \"\", \"jsonversion\": 3, \"labelposition\": \"left\", \"onformcreated\": \"\", \"onformmounted\": \"\", \"onformvalidate\": \"\", \"onformdatachange\": \"\"}, \"widgetlist\": [{\"id\": \"grid93809\", \"key\": 43585, \"cols\": [{\"id\": \"grid-col-52064\", \"icon\": \"grid-col\", \"type\": \"grid-col\", \"options\": {\"md\": 12, \"sm\": 12, \"xs\": 12, \"name\": \"gridcol52064\", \"pull\": 0, \"push\": 0, \"span\": 12, \"hidden\": false, \"offset\": 0, \"responsive\": false, \"customclass\": []}, \"category\": \"container\", \"internal\": true, \"widgetlist\": [{\"id\": \"input36308\", \"key\": 90048, \"icon\": \"text-field\", \"type\": \"input\", \"options\": {\"name\": \"input36308\", \"size\": \"\", \"type\": \"text\", \"label\": \"直径(dn)\", \"hidden\": false, \"onblur\": \"\", \"onfocus\": \"\", \"oninput\": \"\", \"disabled\": false, \"onchange\": \"\", \"readonly\": false, \"required\": true, \"clearable\": true, \"maxlength\": null, \"minlength\": null, \"oncreated\": \"\", \"onmounted\": \"\", \"buttonicon\": \"custom-search\", \"labelalign\": \"\", \"labelwidth\": null, \"onvalidate\": \"\", \"prefixicon\": \"\", \"suffixicon\": \"\", \"validation\": \"\", \"columnwidth\": \"200px\", \"customclass\": [], \"labelhidden\": false, \"placeholder\": \"\", \"appendbutton\": false, \"defaultvalue\": \"\", \"labeltooltip\": null, \"requiredhint\": \"品牌不能为空\", \"showpassword\": false, \"showwordlimit\": false, \"labeliconclass\": null, \"validationhint\": \"\", \"labeliconposition\": \"rear\", \"onappendbuttonclick\": \"\", \"appendbuttondisabled\": false}, \"formitemflag\": true}]}, {\"id\": \"grid-col-95927\", \"icon\": \"grid-col\", \"type\": \"grid-col\", \"options\": {\"md\": 12, \"sm\": 12, \"xs\": 12, \"name\": \"gridcol95927\", \"pull\": 0, \"push\": 0, \"span\": 12, \"hidden\": false, \"offset\": 0, \"responsive\": false, \"customclass\": []}, \"category\": \"container\", \"internal\": true, \"widgetlist\": [{\"id\": \"select17890\", \"key\": 27039, \"icon\": \"select-field\", \"type\": \"select\", \"options\": {\"name\": \"select17890\", \"size\": \"\", \"label\": \"制动方式\", \"hidden\": false, \"onblur\": \"\", \"remote\": false, \"onfocus\": \"\", \"disabled\": false, \"multiple\": false, \"onchange\": \"\", \"required\": true, \"clearable\": true, \"oncreated\": \"\", \"onmounted\": \"\", \"filterable\": false, \"labelalign\": \"\", \"labelwidth\": null, \"onvalidate\": \"\", \"validation\": \"\", \"allowcreate\": false, \"columnwidth\": \"200px\", \"customclass\": [], \"labelhidden\": false, \"optionitems\": [{\"label\": \"手动\", \"value\": 1}, {\"label\": \"电动\", \"value\": 2}, {\"label\": \"气动\", \"value\": \"3\"}, {\"label\": \"液压控制\", \"value\": 4}, {\"label\": \"其他\", \"value\": 5}], \"placeholder\": \"\", \"defaultvalue\": \"\", \"labeltooltip\": null, \"requiredhint\": \"制动方式不能空\", \"multiplelimit\": 0, \"onremotequery\": \"\", \"labeliconclass\": null, \"validationhint\": \"\", \"automaticdropdown\": false, \"labeliconposition\": \"rear\"}, \"formitemflag\": true}]}], \"icon\": \"grid\", \"type\": \"grid\", \"options\": {\"name\": \"grid93809\", \"gutter\": 12, \"hidden\": false, \"colheight\": null, \"customclass\": []}, \"category\": \"container\"}, {\"id\": \"grid40469\", \"key\": 43585, \"cols\": [{\"id\": \"grid-col-77638\", \"icon\": \"grid-col\", \"type\": \"grid-col\", \"options\": {\"md\": 12, \"sm\": 12, \"xs\": 12, \"name\": \"gridcol77638\", \"pull\": 0, \"push\": 0, \"span\": 12, \"hidden\": false, \"offset\": 0, \"responsive\": false, \"customclass\": []}, \"category\": \"container\", \"internal\": true, \"widgetlist\": [{\"id\": \"input30722\", \"key\": 90048, \"icon\": \"text-field\", \"type\": \"input\", \"options\": {\"name\": \"input30722\", \"size\": \"\", \"type\": \"text\", \"label\": \"压力(mp)\", \"hidden\": false, \"onblur\": \"\", \"onfocus\": \"\", \"oninput\": \"\", \"disabled\": false, \"onchange\": \"\", \"readonly\": false, \"required\": true, \"clearable\": true, \"maxlength\": null, \"minlength\": null, \"oncreated\": \"\", \"onmounted\": \"\", \"buttonicon\": \"custom-search\", \"labelalign\": \"\", \"labelwidth\": null, \"onvalidate\": \"\", \"prefixicon\": \"\", \"suffixicon\": \"\", \"validation\": \"\", \"columnwidth\": \"200px\", \"customclass\": [], \"labelhidden\": false, \"placeholder\": \"\", \"appendbutton\": false, \"defaultvalue\": \"\", \"labeltooltip\": null, \"requiredhint\": \"压力不能为空\", \"showpassword\": false, \"showwordlimit\": false, \"labeliconclass\": null, \"validationhint\": \"\", \"labeliconposition\": \"rear\", \"onappendbuttonclick\": \"\", \"appendbuttondisabled\": false}, \"formitemflag\": true}]}, {\"id\": \"grid-col-71433\", \"icon\": \"grid-col\", \"type\": \"grid-col\", \"options\": {\"md\": 12, \"sm\": 12, \"xs\": 12, \"name\": \"gridcol71433\", \"pull\": 0, \"push\": 0, \"span\": 12, \"hidden\": false, \"offset\": 0, \"responsive\": false, \"customclass\": []}, \"category\": \"container\", \"internal\": true, \"widgetlist\": [{\"id\": \"select11081\", \"key\": 31288, \"icon\": \"select-field\", \"type\": \"select\", \"options\": {\"name\": \"select11081\", \"size\": \"\", \"label\": \"材质\", \"hidden\": false, \"onblur\": \"\", \"remote\": false, \"onfocus\": \"\", \"disabled\": false, \"multiple\": false, \"onchange\": \"\", \"required\": true, \"clearable\": true, \"oncreated\": \"\", \"onmounted\": \"\", \"filterable\": true, \"labelalign\": \"\", \"labelwidth\": null, \"onvalidate\": \"\", \"validation\": \"\", \"allowcreate\": false, \"columnwidth\": \"200px\", \"customclass\": [], \"labelhidden\": false, \"optionitems\": [{\"label\": \"铸铁\", \"value\": 1}, {\"label\": \"不锈钢\", \"value\": 2}, {\"label\": \"碳钢\", \"value\": 3}, {\"label\": \"铜\", \"value\": 4}, {\"label\": \"其他\", \"value\": 5}], \"placeholder\": \"\", \"defaultvalue\": \"\", \"labeltooltip\": null, \"requiredhint\": \"材质不能为空\", \"multiplelimit\": 0, \"onremotequery\": \"\", \"labeliconclass\": null, \"validationhint\": \"\", \"automaticdropdown\": false, \"labeliconposition\": \"rear\"}, \"formitemflag\": true}]}], \"icon\": \"grid\", \"type\": \"grid\", \"options\": {\"name\": \"grid40469\", \"gutter\": 12, \"hidden\": false, \"colheight\": null, \"customclass\": []}, \"category\": \"container\"}, {\"id\": \"grid4823\", \"key\": 62462, \"cols\": [{\"id\": \"grid-col-24973\", \"icon\": \"grid-col\", \"type\": \"grid-col\", \"options\": {\"md\": 12, \"sm\": 12, \"xs\": 12, \"name\": \"gridcol24973\", \"pull\": 0, \"push\": 0, \"span\": 12, \"hidden\": false, \"offset\": 0, \"responsive\": false, \"customclass\": []}, \"category\": \"container\", \"internal\": true, \"widgetlist\": [{\"id\": \"input21916\", \"key\": 28602, \"icon\": \"text-field\", \"type\": \"input\", \"options\": {\"name\": \"input21916\", \"size\": \"\", \"type\": \"text\", \"label\": \"其他属性\", \"hidden\": false, \"onblur\": \"\", \"onfocus\": \"\", \"oninput\": \"\", \"disabled\": false, \"onchange\": \"\", \"readonly\": false, \"required\": false, \"clearable\": true, \"maxlength\": null, \"minlength\": null, \"oncreated\": \"\", \"onmounted\": \"\", \"buttonicon\": \"custom-search\", \"labelalign\": \"\", \"labelwidth\": null, \"onvalidate\": \"\", \"prefixicon\": \"\", \"suffixicon\": \"\", \"validation\": \"\", \"columnwidth\": \"400px\", \"customclass\": [], \"labelhidden\": false, \"placeholder\": \"\", \"appendbutton\": false, \"defaultvalue\": \"\", \"labeltooltip\": null, \"requiredhint\": \"\", \"showpassword\": false, \"showwordlimit\": false, \"labeliconclass\": null, \"validationhint\": \"\", \"labeliconposition\": \"rear\", \"onappendbuttonclick\": \"\", \"appendbuttondisabled\": false}, \"formitemflag\": true}]}], \"icon\": \"grid\", \"type\": \"grid\", \"options\": {\"name\": \"grid4823\", \"gutter\": 12, \"hidden\": false, \"colheight\": null, \"customclass\": []}, \"category\": \"container\"}]}', '', 1709188741, 'g6', 1711434246, 1021100012, 1);
2、数据的查询
在使用上面的sql脚本将数据插入到数据库之后呢,我们怎么把页面的表单给精准的查询出来呢?实现我们最初的需求呢。这里就需要介绍一下在mysql中的json数据的查询问题。在mysql5.7中关于json的方法参见其官网的定义:
name | description | introduced | deprecated |
---|---|---|---|
-> | return value from json column after evaluating path; equivalent to json_extract(). | ||
->> | return value from json column after evaluating path and unquoting the result; equivalent to json_unquote(json_extract()). | 5.7.13 | |
json_append() | append data to json document | yes | |
json_array() | create json array | ||
json_array_append() | append data to json document | ||
json_array_insert() | insert into json array | ||
json_contains() | whether json document contains specific object at path | ||
json_contains_path() | whether json document contains any data at path | ||
json_depth() | maximum depth of json document | ||
json_extract() | return data from json document | ||
json_insert() | insert data into json document | ||
json_keys() | array of keys from json document | ||
json_length() | number of elements in json document | ||
json_merge() | merge json documents, preserving duplicate keys. deprecated synonym for json_merge_preserve() | 5.7.22 | |
json_merge_patch() | merge json documents, replacing values of duplicate keys | 5.7.22 | |
json_merge_preserve() | merge json documents, preserving duplicate keys | 5.7.22 | |
json_object() | create json object | ||
json_pretty() | print a json document in human-readable format | 5.7.22 | |
json_quote() | quote json document | ||
json_remove() | remove data from json document | ||
json_replace() | replace values in json document | ||
json_search() | path to value within json document | ||
json_set() | insert data into json document | ||
json_storage_size() | space used for storage of binary representation of a json document | 5.7.22 | |
json_type() | type of json value | ||
json_unquote() | unquote json value | ||
json_valid() | whether json value is valid |
3、在5.7版本中进行json检索
首先第一步查询一下数据版本,在客户端软件中执行以下sql:
-- 查看mysql 的服务器版本 select version(); 5.7.14-log
第二步、查询当前待提取的表单的选项的最大长度
-- 这里只能在mysql中使用一种折中的实现方案 -- 查出数组最大长度 select max(json_length(json_extract(attr_config, concat('$**.options')))) from ems_equipment_classification_attr where equip_class_id = '3'; 13
这里要查询json_length的原因是因为,在当前我这个版本的mysql中,如果要实现行列转护转换,只能使用一种折中的方式。通过上述的步骤求出待提取的目标的长度,然后动态拼接参数,实现数据转成多行。这里直接给出最终拼接成的sql,在得到这个sql之前,我也是花了很长的时间进行实验才找到这个方法,感谢前人提供的思路。
select tb.element -> '$[0].key' keystr,tb.element ->> '$[0].type' type, tb.element ->> '$[0].icon' icon, tb.element ->> '$[0].options.name' namestr, tb.element ->> '$[0].options.label' label, tb.element ->> '$[0].options.optionitems' optionitems, tb.element from ( select json_extract(ta.json_val, concat('$[', idx ,']')) as element from ( select json_type( attr_config -> '$**.widgetlist' ) type, attr_config -> '$**.widgetlist' json_val,t.* from ems_equipment_classification_attr t where json_type( attr_config -> '$**.label' ) is not null and equip_class_id = '3' ) ta -- inline table of sequential values to index into json array inner join ( select 0 as idx union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 -- 一直 union 到数组最大长度 - 1 ) as indexes -- 排除元素数量比最大值要小导致的空项 where json_extract(ta.json_val, concat('$[', idx ,']')) is not null order by idx ) tb where json_type(tb.element ->'$**.label') is not null;
请注意这里的1到8就是需要动态生成,结合mybatis框架,可以支持按传入的参数进行生成。
查询出来的结果如下所示:
43585 grid grid grid93809
90048 input text-field input36308 直径(dn)
27039 select select-field select17890 制动方式 [{"label": "手动", "value": 1}, {"label": "电动", "value": 2}, {"label": "气动", "value": "3"}, {"label": "液压控制", "value": 4}, {"label": "其他", "value": 5}]
90048 input text-field input30722 压力(mp)
31288 select select-field select11081 材质 [{"label": "铸铁", "value": 1}, {"label": "不锈钢", "value": 2}, {"label": "碳钢", "value": 3}, {"label": "铜", "value": 4}, {"label": "其他", "value": 5}]
28602 input text-field input21916 其他属性
到此,在mysql5.7下的查询完全实现。剩下的参数匹配等,我们在系统重直接对应节即可。
4、8.0后的优化查询
在mysql8.0之前,mysql没有支持json_table的用法,所以我们只用采用上面的这种处理办法。如果您的项目环境用的是8.0的,那么可以直接使用json_table的方法直接生成,更加简单。各位如果有8.0的环境,可以按照以下sql进行实验。
select * from ems_equipment_classification_attr t cross join json_table( t.attr_config -> '$**.widgetlist', '$[*]' columns( value varchar(255) path '$[0].options.value', label varchar(255) path '$[0].options.label', name varchar(255) path '$[0].options.name', type varchar(255) path '$[0].type', icon varchar(255) path '$[0].icon', requiredhint varchar(255) path '$[0].options.requiredhint', optionitems json path '$[0].options.optionitems' ) ) as jt where t.equip_class_id = '3';
正常执行的话可以得到以下的结果:
43585 grid grid grid93809
90048 input text-field input36308 直径(dn)
27039 select select-field select17890 制动方式 [{"label": "手动", "value": 1}, {"label": "电动", "value": 2}, {"label": "气动", "value": "3"}, {"label": "液压控制", "value": 4}, {"label": "其他", "value": 5}]
90048 input text-field input30722 压力(mp)
31288 select select-field select11081 材质 [{"label": "铸铁", "value": 1}, {"label": "不锈钢", "value": 2}, {"label": "碳钢", "value": 3}, {"label": "铜", "value": 4}, {"label": "其他", "value": 5}]
28602 input text-field input21916 其他属性
基本上跟5.7的执行结果差不太多。到此就完成了在mysql中两个版本的json数据的精准查询的支持。希望对大家有所帮助。
三、总结
以上就是本文的主要内容,本文重点讲解如何在将设计好的动态表单信息进行提取,比如进行模板数据导入的时候,可以根据不同的表单类型,比如根据单行文字框的名字来动态设置值,也可以在导数数据时,知道将数据库的性别一列保存的1和2翻译成男和女这两种属性。这都需要我们精准的提取表单中的不同的信息,能精准提取表单的文本、类型、默认值域还有其他的表单元素的设置。通过本文,您可以了解如何正确的操作动态表单信息,同时了解如何从表单中查找表单元素。行文仓促,难免有不足之处,欢迎各位专家朋友批评指正,不甚感激。
参考资料:
3、流程设计器演示地址。
到此这篇关于mysql中实现动态表单中json元素精准匹配的方法示例的文章就介绍到这了,更多相关mysql json元素精准匹配内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论