开源表单设计器颗粒度级别控制表单的显示条件原理分析

企业微信截图_9a2f2c80-0069-48f4-8654-e1557f724fd5.png

表单渲染中, 有些表单的显示有不同条件, 比如需要上一个表单的开关打开,或者文本内容为 xxxx, 或者需要大于或等于或小于指定值, 或者需要选中某个选项, 或者需满足以上多个条件或在满足多个条件中的一个, 有 n 种场景选择, 这样就需要条件显示配置功能, 来满足多样化需求

预览

gaoji.gif

架构实现

条件显示,其实就是该表单和其他表单的逻辑关系,我们可以合理的利用或(||)且(&&)逻辑运算符来实现

(A && B) || (A && (B || C || D && (F && (G || H && (L && O && P)))) && E) && D

实现出能匹配出以上等不同复杂类型表达式的组件, 差不多条件显示功能难点没有了

思考

观察以上表达式, 有以下几个特点

  • 只有且或两个逻辑表达式
  • 关系层级嵌套多, 可能会嵌套 n 层, 层数不确定

综合以上两个原因, 可以利用两个按钮来表示并和或, 然后使用 vue 的递归组件来实现关系的层级嵌套, 这样就能在不同层级下的组件样式保持一致

实现

递归组件

定义: 组件是可以在它们自己的模板中调用自身的。不过它们只能通过 name 选项来做这件事:

如:

{"name": "stack-overflow", // 组件名称"template": "<div><stack-overflow></stack-overflow></div>" // 模板内部调用自身
}

步骤原理

我们首先来实现下如下的表达式:

如:

A || (B && C)

上文说过, 只有两个逻辑运算符, 且、或, 观察 A || (B && C), 除了逻辑运算符外, 还有条件 A、B、C, 这个条件具有多样性, 且可配

我们可以定义一个下拉框(不使用按钮,不然后面循环嵌套按钮太多,样式太丑), 包含并组、或组、条件, 可以理解并组和或组就是一个数组, 数组内有不同的条件, 并组就是每个条件必须满足,或组就是不同条件满足其中一个即可

<el-select v-model="result.type" placeholder="请选择" @change="onChange"><el-option v-for="item in groupSelect" :key="item.value" :label="item.label" :value="item.value"> </el-option>
</el-select>
export default {data() {return {groupSelect: [{value: "andgroup",label: "+并组",},{value: "orgroup",label: "+或组",},{value: "data",label: "条件",},],};},
};

样式如下

企业微信截图_45f47d83-1940-4ade-8607-abdd5686dbc1.png

一个下拉框, 一个增加条件按钮和一个删除按钮

上文也说过下拉框的作用, 增加条件按钮其实和下拉框的条件按钮功能一致的, 不过为了方便新增条件做的快捷按钮, 删除按钮指的是删除当前条件或子条件

观察如 A || (B && C)表达式, 满足 A 或者满足 B 且满足 C, 整体是一个或组, 相信大家通过我对或和且命名为或组和且组就能理解到, 或组和且组都是一个数组.或组数组内部有两条数据, 一个是条件 A, 一个是并组, 并组数组内部有两条数据, 条件 B 和条件 C

综合以上分析, 递归组件的使用场景就是在并组和或组内部配置子条件, 子条件包括条件、或组、并组, 然后子条件内部的并组和或组又有子条件, 只要有子条件就可能会有或组和并组, 不断递归下去,直到最底层的是条件, 不能再有子条件为止

所以可以设计成如下数据格式:

{"showRule": {"type": "orgroup","result": [{"type": "data","data": "A"},{"type": "andgroup","result": [{"type": "data","data": "B"},{"type": "data","data": "C"}]}]}
}

以上数据转换为表达式就是:

('A' or ('B' and 'C')
)

来分析下该数据格式, 其中 type 字段, 值有 orgroup、andgroup、data, 代表或组、并组、条件, 或组和并组是一个数组 result, 数组内部可以是或组、并组、条件, 如果 type 为 data, 则没有 result, 为 data 对象,即条件

那我们根据以上数据接口来实现递归组件

子组件 ConditionGroup

template

<Transition><div v-show="result.type && result.type !== 'data'"><div v-for="(item, index) in result.result" :key="index"><ConditionGroup :result="item" @update="handleUpdateForce" @delete="handleDelete" :index="index" :fieldList="fieldList" /></div><div v-if="result.result && result.result.length >= 2"></div></div>
</Transition>
<div v-show="result.type && result.type == 'data'"><ConditionTanc ref="ConditionTanc" :data="result.data" @end="handleUpdateForce" :fieldList="fieldList"></ConditionTanc>
</div>

script

export default {name: "ConditionGroup",props: {result: {type: Object,default() {return {};},},fieldList: {type: Array,default() {return [];},},},
};

父组件 ConditionModule

template

<ConditionGroup :result="result" @update="handleUpdateForce" />

script

export default {data() {return {result: {},};},
};

通过不断的向子组件或递归组件传递 result, 利用 vue 的双向数据绑定, 来实现配置

其中需要注意一点

因为是数据是通过配置的形式, 不知道是并组、或组还是条件, 所以在最顶层的时候是只透传了空对象, 我们需要在增加并组、或组还是条件的时候判断类型然后生成对应字段

如:

if (!this.result.type) return;
if (!this.result.result && this.result.type != "data") {this.result.result = [];
}
if (this.result.type == "data" && !this.result.data) {this.result.data = {};
}
this.result.control = true;switch (this.result.type) {case "orgroup":this.result.result.push({ typ: "orgroup", result: [] });break;e;case "andgroup":this.result.result.push({ type: "andgroup", result: [] });break;case "data":this.result.result.push({ type: "data", data: {} });break;
}

这样就能生成 n 层嵌套的数据

条件生成

这个条件是什么, 是需要其他表单达到什么条件, 比如大于等于某个值, 选择某个选项等

所以要有三个条件

  • 获取其他表单的数据
  • 判断是等于不等于、包含不包含逻辑运算
  • 表单的值是固定的还是用户输入的, 比如下拉选择框是固定的几个选项, 不可能让用户随意配置, 必须在固定几个选项的基础上进行选择

获取数据

我相信大家应该也看到上面代码的 fieldList 字段, 该字段就是全局表单列表, 该数据列表我是通过 reative 进行数据的状态管理, 配置的数据就能在全局(所有组件)共享(访问), 这样就能获取其他表单配置

const allFormList = formStore?.get("allFormList");
const fieldResult = [];
// 获取显示条件所有字段列表(详情请看源代码)
toRaw(allFormList)?.forEach((item) => {window.VueContext.$Flex.getFormDataList(item, fieldResult, this.data.fieldName);
});
this.leftField = fieldResult;

动态生成逻辑运算符

是确定表单显示条件值是否满足, 如 a 是否包含字符串 c, a.includes(‘c’), b 是否不等于 2, b != 2

export default {data() {return {logicList: [{ value: "=", label: "等于" },{ value: "!=", label: "不等于" },],};},methods: {getLogic() {const item = this.fieldList.find((item) => {if (this.table && this.table.length > 0) {if (item.value == this.table[0].field) {return item;}}});if (item && item.options && item.multiple) {return [{ value: "in", label: "包含" },{ value: "not in", label: "不包含" },];}if (item && item.switch) {return [{value: "=",label: "等于",},];}return this.logicList;},},
};

值类型

默认情况下是常量, 然后用户输入指定值, 如果是选择框,下拉框等, 就是选项,获取该表单配置固定选项值,作为最后的条件值, 如果是 switch, 则是布尔, 值只能为 true 或 false

{data(){return {typeList: [{rule: [],value: "常量",label: "常量",},],}},computed: {newtypeList() {const item = this.fieldList.find((item) => {if (this.table && this.table.length > 0) {if (item.value == this.table[0].field) {return item;}}});if (item && item.options) {return [{rule: [],value: "选项",label: "选项",},];}if (item && item.switch) {return [{value: "布尔",label: "布尔",},];}return this.typeList;},}
}

这样就能保证数据的准确性和安全性, 规范规则, 否则会出现要求 switch(开关)的值为’abc’, 要求下拉框(固定值 a,b,c)的值等于 7, 会出现各种牛头不对马嘴的情况

具体的选项值的获取等请移步到源码中

表达式

数据生成后, 如果嵌套递归很多, 我相信大家很难准确的知道其中的逻辑关系, 想在某个地方添加一个或组, 可能都很难找对, 所以为了方便查看其中的逻辑关系, 程序实时生成表达式

企业微信截图_368f1c82-361c-4aba-a8e5-b15ca9c079df.png

function filterCondition(result) {if (result.type == "andgroup" || result.type == "orgroup") {const b = result.result.map((item) => filterCondition(item)).join(result.type == "andgroup" ? '<span class="and">and</span>' : '<span class="or">or</span>');const a = `<span class='kh ${result.type}'>(${b})</span>`;return a;} else if (result.type == "data") {return `<span class='data kh'>${getDataConditionRelate(result)}</span>`;} else {return "";}
}
function getDataConditionRelate(data) {if (Object.keys(data.data).length == 0) return "空";const fieldData = data.data;const field = fieldData.field;let value;if (fieldData.type == "选项") {const rightField = fieldData.value;value = `${JSON.stringify(rightField)}`;} else if (fieldData.type == "常量" || fieldData.type == "布尔") {value = fieldData.value;}return `${field} ${fieldData.logic} ${value}`;
}

显示条件

通过以上方式我们能生成每个表单的显示条件配置数据, 那如何来实际控制表单的显示隐藏?

首先我们需要监听表单值的改变, 这样才能实时来实现显示与隐藏

定义了一个 hooks

import { watch, getCurrentInstance, ComponentInternalInstance } from "vue";function useWatch(props: any) {const vm = getCurrentInstance() as ComponentInternalInstance;// 预览模式下才有效if (!props.data.fieldName && !props.item.controlItems) {watch(() => props.data[props.item.data.fieldName],(val, oldVal) => {if (props.item.data.action && props.item.data.action.onChange) {window.VApp.$Flex.funcExec(props.item.data.action.onChange, vm.proxy, [val, oldVal, props.data]);}vm.emit("change");},{deep: true,});}
}export { useWatch };

然后在每个表单组件中这样引用, 就不需要多余的逻辑来实现数据的改变

import { useWatch } from "../../utils/customHooks";
export default defineComponent{setup(props){useWatch(props);}
}

所以组件改变的时候会触发 change 方法, 我们就可以写组件的显示隐藏逻辑

下面是核心代码, 详细代码请看源码

function conditionChange(data: any) {if (data.type == "andgroup") {const result = data.result.map((item: any) => {const r = conditionChange(item);return r;}).find((item: boolean) => {return item == false;});return result === undefined ? true : result;} else if (data.type == "orgroup") {const result = data.result.map((item: any) => {const r = conditionChange(item);return r;}).find((item: boolean) => {return item == true;});return result === undefined ? false : result;} else if (data.type == "data") {const result = data.data;const formResults: any = props.formResult;const value = formResults[result.field];let isShow = false;switch (result.logic) {case "=":isShow = value == result.value;break;case "!=":isShow = value != result.value;break;case "in":if (Array.isArray(value)) {value.find((item) => {if (result.value.include(item)) {isShow = result.value.includes(item);return item;}});} else {isShow = result.value.includes(value);}break;case "not in":if (Array.isArray(value)) {value.find((item) => {if (!result.value.include(item)) {isShow = !result.value.includes(item);return item;}});} else {isShow = !result.value.includes(value);}break;}return isShow;}
}

总结

通过以上剖丝薄茧, 我相信大家对动态表单显示与隐藏的判断应该了如指掌了, 如果要阅读源码, 请移步

github 地址

预览

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/760168.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【干货】Java开发者快速上手.NET指南

前言 前几天有小伙伴在技术群里发了一个微软官方出的&#xff1a;适用于Java开发人员的.NET快速入门免费电子书&#xff0c;今天大姚来分享一下Java开发者想要快速上手.NET有哪些教程和优质资料。 微软适用于Java开发人员的.NET快速入门指南 下载阅读地址&#xff1a;适用于 …

(基础)AJAX概念和axios使用、URL、请求方法和数据提交、HTTP协议、接口、form-serialize插件

AJAX概念和axios使用 AJAX概念 AJAX就是使用XMLHttpRequest对象与服务器通信&#xff0c;它可以使用JSON、XML、HTML和text文本等格式发送和接收数据&#xff0c;AJAX最吸引人的就是它的异步特性&#xff0c;也就是说它可以在不重新刷新页面的情况下与服务器通信&#xff0c;…

windows10装windows11的CMD(Terminal)终端

文章目录 一 前言二 安装Terminal三 启动设置成默认启动cmd 一 前言 我装了WSL的LINUX系统&#xff0c;在 windows10 系统下面不方便启动linux&#xff0c;但是Windows11 在cmd 里面就可以打开&#xff0c; 所以找了这个方法 二 安装Terminal 先在windows10微软的商城(Microso…

>>Vue3+pinia+echarts等实现疫情可视化大图

一.>>前言 1.这个项目是在小满实战篇可视化&#xff08;第九章-饼图&#xff09;_哔哩哔哩_bilibili 这一系列课程为基础来做的&#xff0c;真的很感谢小满老师&#xff0c;讲的内容干货满满&#xff0c;暂时解决了手上没有项目的难题。大家可以去观摩一下他的优质课程。…

什么是膨胀卷积/空洞卷积

什么是膨胀卷积/空洞卷积 膨胀卷积&#xff08;Dilated Convolution/Atrous Convolution&#xff09;&#xff08;下面都使用膨胀卷积 这个名词&#xff09; 先来一张图&#xff0c;让大家对于膨胀卷积有个直观的理解&#xff0c;上图左边就是普通卷积&#xff0c;右边是膨胀卷…

外包干了14天,技术退步明显。。。

先说一下自己的情况&#xff0c;本科生&#xff0c;2019年我通过校招踏入了成都一家软件公司&#xff0c;开始了我的职业生涯。那时的我&#xff0c;满怀热血和憧憬&#xff0c;期待着在这个行业中闯出一片天地。然而&#xff0c;随着时间的推移&#xff0c;我发现自己逐渐陷入…

京东按图搜索京东商品(拍立淘) API 返回值说明

京东按图搜索商品&#xff08;拍立淘&#xff09;的API返回值包含了关于通过图片搜索到的京东商品的相关信息。由于我无法提供最新的京东API返回值的确切结构&#xff0c;以下是基于常见API设计原则的一个大致的返回值示例和说明&#xff1a; 调用链接获取详情 item_search_i…

MC10T1S-10BASE-T1S车载以太网转换器

10BASE-T1S车载以太网转换器 为10BASE-T1S车载以太网转换器&#xff0c;支持Multidrop bus line和Point-to-Point。采用的DB9接口类型&#xff0c;支持PLCA。10BASE-T1S是IEEE 802.3cg标准制定的IEEE汽车以太网的最新标准之一&#xff0c;采用UTP这样的一对无屏蔽的双芯电缆进行…

matlab simulink 电力系统同步发电机励磁系统的建模与仿真

1、内容简介 略 77-可以交流、咨询、答疑 电力系统同步发电机励磁系统的建模与仿真 建立MATLAB的同步发电机励磁调节系统仿真模型&#xff0c;最后建立了以PID和PSS为励磁控制方式的同步发电机励磁调节系统数学模型&#xff0c;在Simulink环境下进行了仿真&#xff0c;收到…

C++除了Qt还有什么GUI库?

C除了Qt还有什么GUI库&#xff1f; 先&#xff0c;不要折腾&#xff0c;不要想着用 C 来做 App 类的 GUI 开发。 所以你问用 c gui 库&#xff0c;本来确实有很多&#xff0c;但是经过几十年的沉淀&#xff0c;最后只留下一个 qt quick 和其他特殊需求的库&#xff08;包括 qt…

【Flutter 面试题】Flutter如何进行本地存储和缓存数据?

【Flutter 面试题】Flutter如何进行本地存储和缓存数据&#xff1f; 文章目录 写在前面口述回答补充说明实际案例完整代码示例运行结果详细说明 写在前面 &#x1f64b; 关于我 &#xff0c;小雨青年 &#x1f449; CSDN博客专家&#xff0c;GitChat专栏作者&#xff0c;阿里云…

【火猫TV】LPL春季赛前瞻:Tabe迎战LNG OMG关键一战!

北京时间3月20日&#xff0c;LPL春季赛今天继续进行&#xff0c;今天将会迎来春季赛常规赛第八周第三个比赛日&#xff0c;今天的两场比赛是LNG战队对阵AL战队以及OMG战队对阵BLG战队&#xff0c;今天的两场比赛对于LNG、AL以及OMG战队都是比较重要的&#xff0c;目前三支战队都…

第十二届蓝桥杯省赛CC++ 研究生组-货物摆放

还是整数分解问题,注意n本身也是约数 #include <iostream> int main(){printf("2430");return 0; }#include <iostream> #include<cmath> #include<algorithm> using namespace std; typedef long long ll; const ll n 2021041820210418LL…

QGIS开发笔记(一):QGIS介绍、软件下载和加载shp地图数据Demo

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/136888334 红胖子网络科技博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬…

实现安卓连接阿里云物联网平台(2)

完整工程链接 链接&#xff1a;https://pan.baidu.com/s/1ykcJHPBSKBXVMaMWKoVRvA?pwd8888 提取码&#xff1a;8888 &#xff08;1&#xff09;创建一个新工程 &#xff08;2&#xff09;添加mqtt包的依赖 implementation org.eclipse.paho:org.eclipse.paho.client.mqttv…

MINT: Detecting Fraudulent Behaviors from Time-series Relational Data论文阅读笔记

2. 问题定义 时间序列关系数据&#xff08;Time Series Relation Data&#xff09; 这个数据是存放在关系型数据库中&#xff0c;每一条记录都是泰永时间搓的行为。 更具体地&#xff0c;每条记录表示为 x ( v , t , x 1 , x 2 , … , x m − 2 ) x (v,t,x_1,x_2,\dots,x…

【Unity】UI九宫格

什么是九宫格&#xff1f; 顾名思义&#xff0c;九宫格就是指UI切成9个格子&#xff0c;9个格子可以任意拉伸。 1、3、7、9不拉伸。 2、8水平拉伸。 4、6垂直拉伸。 5既可以水平也可以垂直拉伸。 怎么切九宫格&#xff1f; 选中图片&#xff0c;改成Sprite模式&#xff0c;点…

[flask] flask的基本介绍、flask快速搭建项目并运行

笔记 Flask Flask 本身相当于一个内核&#xff0c;其他几乎所有的功能都要用到扩展&#xff08;邮件扩展Flask-Mail&#xff0c;用户认证Flask-Login&#xff0c;数据库Flask-SQLAlchemy&#xff09;&#xff0c;都需要用第三方的扩展来实现。比如可以用 Flask 扩展加入ORM、…

婴儿奶瓶哪个品牌最好?五大热门品牌深度测评推荐

最近很多新手爸妈都在后台私信咨询如何选购婴儿奶瓶&#xff0c;以及要怎么选才能够避雷避坑。为了解答大家的问题&#xff0c;我特地为大家对现在的各种主流款奶瓶进行全面测评。 对于婴儿奶瓶&#xff0c;可能有些新手爸妈觉得随便买买就好&#xff0c;但实际上挑选婴儿奶瓶…

day12-SpringBootWeb 登录认证

一、登录功能 Slf4j RestController public class LoginController {Autowiredprivate EmpService empService;PostMapping("/login")public Result login(RequestBody Emp emp){log.info("员工登录: {}", emp);Emp e empService.login(emp);//登录失败, …