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

企业微信截图_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.mfbz.cn/a/476999.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Django实战一】创建新项目

一、新建Project django-admin startproject 项目名称二、创建应用 1、创建应用 python manage.py startapp 应用名称应用创建后&#xff0c;项目的根目录下会生成对应应用名称的文件夹 2、注册应用 新创建的应用需要在settings.py中的INSTALLED_APPS中注册该应用 INSTALL…

Prompt-RAG:在特定领域中应用的革新性无需向量嵌入的RAG技术

论文地址&#xff1a;https://arxiv.org/ftp/arxiv/papers/2401/2401.11246.pdf 原文地址&#xff1a;https://cobusgreyling.medium.com/prompt-rag-98288fb38190 2024 年 3 月 21 日 虽然 Prompt-RAG 确实有其局限性&#xff0c;但在特定情况下它可以有效地替代传统向量嵌入 …

KW音乐搜索参数

声明&#xff1a; 本文章中所有内容仅供学习交流&#xff0c;抓包内容、敏感网址、数据接口均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff0c;若有侵权&#xff0c;请联系我立即删除&#xff01; 逆向目标: …

基于SpringBoot+Layui的社区物业管理系统

项目介绍 社区物业管理系统是基于java程序开发,本系统分为业主和管理员两个角色 业主可以登陆系统,查看车位费用信息,查看物业费用信息,在线投诉,查看投诉,在线报修; 管理员可以车位收费信息,物业收费信息,投诉信息,楼宇信息,房屋信息,业主信息,车位信息,抄表信…

ArkTS编写的HarmonyOS原生聊天UI框架

简介 ChatUI&#xff0c;是一个ArkTS编写的HarmonyOS原生聊天UI框架&#xff0c;提供了开箱即用的聊天对话组件。 下载安装 ohpm install changwei/chatuiOpenHarmony ohpm 环境配置等更多内容&#xff0c;请参考如何安装 OpenHarmony ohpm 包 接口和属性列表 接口列表 接…

Git、Github、Gitee、GitLab学习,团队协助/版本控制

Git 是一个免费的、开源的分布式版本控制系统&#xff0c;可以快速高效地处理从小型到大型的各种 项目。B站尚硅谷Git学习笔记 一、Git的常用命令 1.git工作机制 工作区和暂存区的文件都可删除&#xff0c;但是提交到本地库则不可删除&#xff0c;有历史记录 2.历史版本 2.1查…

如何打破SAST代码审计工具的局限性?

关键词&#xff1a;白盒测试&#xff1b;代码分析工具&#xff1b;代码扫描工具&#xff1b;静态代码检测工具&#xff1b; 在代码的世界里&#xff0c;安全问题如同潜伏的暗礁&#xff0c;随时可能让航行中的软件项目触礁沉没。SAST代码审计工具如同雷达一样&#xff0c;以其独…

Doris记录

Doris是一个开源的分布式分析型数据库&#xff0c;最初由阿里巴巴开发并开源&#xff0c;目前隶属于Apache基金会。 Doris基于大规模并行处理&#xff08;MPP&#xff09;架构&#xff0c;提供高性能和实时的数据分析能力。它以极速易用的特点被广泛使用&#xff0c;能够应对高…

探索 PostgreSQL 的外部数据包装器和统计函数

PostgreSQL 因其稳定性和可扩展性而广受青睐&#xff0c;为开发人员和数据管理员提供了许多有用的函数。在这些函数中&#xff0c;file_fdw_handler、file_fdw_validator、pg_stat_statements、pg_stat_statements_info 以及 pg_stat_statements_reset 是其中的重要函数&#x…

鸿蒙Harmony应用开发—ArkTS-全局UI方法(时间滑动选择器弹窗)

以24小时的时间区间创建时间滑动选择器&#xff0c;展示在弹窗上。 说明&#xff1a; 该组件从API Version 8开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 本模块功能依赖UI的执行上下文&#xff0c;不可在UI上下文不明确的地方使用&…

java设计模式--模板方法

在开始模板方法的学习之前&#xff0c;先看下面一段话&#xff1a; 模板&#xff0c;是指作图或设计方案的固定格式。模板是将一个事物的结构规律予以固定化、标准化的成果&#xff0c;它体现的是结构形式的标准化。 ----百度百科 通俗来说&#xff0c;模板其实就是把一个事物的…

前端案例:产品模块

文章目录 产品模块效果结构布局分析父级盒子布局图片和段落评价和详情 产品模块效果 结构布局分析 1、大的父级盒子包含全部的内容 2、内容装入 图片&#xff08;img标签&#xff09;&#xff1b;分别三个子盒子装入两段评价以及商品信息。 父级盒子布局 div {width: 300px…

ChatGPT高效完成简历制作[中篇4]-有爱AI实战教程(十一)

演示站点&#xff1a; https://ai.uaai.cn 对话模块 官方论坛&#xff1a; www.jingyuai.com 京娱AI 一、导读&#xff1a; 在使用 ChatGPT 时&#xff0c;当你给的指令越精确&#xff0c;它的回答会越到位&#xff0c;举例来说&#xff0c;假如你要请它帮忙写文案&#xff0c…

6.2 ServiceNow 自动化测试框架 (ATF) 简介

6.2 自动化测试框架 ATF 简介 目录一、自动化测试框架 (ATF) 简介1. Automated Test Framework&#xff08;ATF&#xff09;2. 使用自动化测试框架 (ATF)的好处&#xff1a; 二、 ATF的测试类型1. 功能业务逻辑测试2. 回归测试3. 浏览器兼容性测试4. 服务器端 Jasmine测试 三、…

IBM SPSS Statistics for Mac v27.0.1中文激活版

IBM SPSS Statistics for Mac是一款功能强大的统计分析软件&#xff0c;专为Mac用户设计&#xff0c;用于数据分析和决策支持。该软件拥有直观易用的界面和丰富多样的统计工具&#xff0c;使得用户可以轻松进行数据处理、分析和解释。 软件下载&#xff1a;IBM SPSS Statistics…

JQuery EasyUI DataGrid行添加水印

代码 css: .water-mark::after {content: 有异议;position: absolute;left: 460px;top: 40px;color: rgb(255 0 0);transform: rotate(-25deg);pointer-events: none;z-index: 10;} js: $(#dgData).datagrid({loadMsg: 数据加载中&#xff0c;请稍后……,// fitColumns: true,…

使用工具类简单快速导出复杂的Excel,以及下载Excel 模板

Gitee 地址如下&#xff1a; https://gitee.com/xia-lijun/export-Excel.githttps://gitee.com/xia-lijun/export-Excel.git 一&#xff1a;首先引入pom.xml 依赖 <dependency><groupId>cn.afterturn</groupId><artifactId>easypoi-web</artifact…

Linux:Prometheus+Grafana+睿象云告警平台(3)

在上一章我进行了Prometheus和Grafana的基础搭建以及部署 Linux&#xff1a;Prometheus的源码包安装及操作&#xff08;2&#xff09;-CSDN博客https://blog.csdn.net/w14768855/article/details/136855988?spm1001.2014.3001.5501 1.注册 在监控中必不可少的就是监控告警&am…

基于深度学习的心律异常分类系统设计——算法设计

基于深度学习的心律异常分类系统——算法设计 第一章 研究背景算法流程本文研究内容 第二章 心电信号分类理论基础心电信号产生机理MIT-BIH 心律失常数据库 第三章 心电信号预处理心电信号噪声来源与特点基线漂移工频干扰肌电干扰 心电信号读取与加噪基于小波阈值去噪技术的应用…

Linux下Docker部署中间件(Mysql、Redis、Nginx等)

我的自备文件 文件传输 内网下直接上传很慢 使用scp命令将另一台服务器上的文件传输过来&#xff1b;在已有文件的服务器往没有文件的服务器传输 scp -r 传输的文件夹/文件 root要传输的地址:放置的地址 scp -r tools root172.xx.x.xxx:/data/ 安装二进制文件、脚本及各中间件…
最新文章