条件编辑器

363 阅读4分钟

简介

条件编辑器可以让用户以可视化的方式组织条件以及条件组,每个条件或条件组的运算结果是一个布尔类型的数据(是/否),用于配置规则。

先看一下条件编辑器的界面布局:

条件编辑器界面.jpeg

可以看出条件编辑器由条件和条件组组成,条件和条件组都可以嵌套,条件组可以包含多个条件或条件组,条件组的运算结果是所有条件或条件组的运算结果的逻辑运算结果。

类型定义

按数据格式可以将条件分成三类:

  1. 条件组(有子条件,and/or)
  2. 无比较值的条件(拥有了比较符即可确定条件的值,isNull/isNotNull)
  3. 普通条件(包含被比较对象、比较符和比较值三个部分)

具体 ts 类型定义如下:

export namespace VariableCondition {
  export type SimpleValue = string | number | boolean

  export interface BaseCondition<T extends string> {
    id: string
    keyPath: string[]
    op: T
  }

  export interface HasValueCondition<T extends string, V> extends BaseCondition<T> {
    value: V
  }

  export interface AndCondition {
    id: string
    op: 'and'
    children: Desc[]
  }
  export interface OrCondition {
    id: string
    op: 'or'
    children: Desc[]
  }

  export interface NullCondition extends BaseCondition<'isNull'> {}
  export interface NotNullCondition extends BaseCondition<'isNotNull'> {}

  export type EqCondition = HasValueCondition<'eq', SimpleValue>
  export type NeqCondition = HasValueCondition<'neq', SimpleValue>
  export type GtCondition = HasValueCondition<'gt', number>
  export type LtCondition = HasValueCondition<'lt', number>
  export type GteCondition = HasValueCondition<'gte', number>
  export type LteCondition = HasValueCondition<'lte', number>

  export type LikeCondition = HasValueCondition<'like', string>
  export type NotLikeCondition = HasValueCondition<'notLike', string>

  export type IncludesCondition = HasValueCondition<'includes', SimpleValue>
  export type NotIncludesCondition = HasValueCondition<'notIncludes', SimpleValue>
  export type InCondition = HasValueCondition<'in', SimpleValue[]>
  export type NotInCondition = HasValueCondition<'notIn', SimpleValue[]>
  export type HasAnyOfCondition = HasValueCondition<'hasAnyOf', SimpleValue[]>
  export type NotAnyOfCondition = HasValueCondition<'notAnyOf', SimpleValue[]>

  export type Desc =
    | AndCondition
    | OrCondition
    | NullCondition
    | NotNullCondition
    | EqCondition
    | NeqCondition
    | GtCondition
    | LtCondition
    | GteCondition
    | LteCondition
    | LikeCondition
    | NotLikeCondition
    | IncludesCondition
    | NotIncludesCondition
    | InCondition
    | NotInCondition
    | HasAnyOfCondition
    | NotAnyOfCondition

  export type HasChildren = AndCondition | OrCondition

  export type Normal = Exclude<Desc, HasChildren>
}

界面实现思路

界面可以根据是否是条件组来进行分类,只有条件组才可以包含子条件,所以可以将单个条件封装为一个组件。

  1. 单个条件:根据选中的变量类型不同,过滤出符合该类型的比较符,然后根据比较符的类型,决定值的输入方式,例如:选中的类型是一个字符串,可以用于字符串的比较符包括:isNull, isNotNull, eq, neq, like, notLike,当选择了比较符后再判断其应该输入什么样类型的值,若选择 isNull/isNotNull 则无需渲染出值的输入框,若选择 eq/neq/like/notLike 则需要渲染一个文本输入框即可。
  2. 条件组:条件组包括逻辑符(and/or)和该组的子条件,所以需要渲染一个逻辑符的选择框和一个子条件的列表,子条件的列表可以通过递归的方式来逐条渲染。

边界情况:

  1. 未配置任何条件时:渲染一个添加条件的按钮即可,点击该按钮直接添加一个包含一个条件的条件组
  2. 条件组的子条件为空时:默认条件组的条件若为空则直接将给条件组移除(因此无需渲染删除条件组的按钮,但是在添加条件组时需要默认向条件组中插入一个条件)

计算条件

获得了条件的描述信息后,就需要在适当的时机来计算条件的值,例如将该条件用于数据库查询,那么就需要将条件转换成 SQL 语言,若仅需要在前端判断条件是否满足,则可以直接计算条件的值。

以下提供一个简单的方式,通过判断比较符的类型进行实际的计算,这种方式不方便扩展,可以通过策略模式来实现这部分逻辑。

interface GetValueByPath {
  (keyPath: string[]): Promise<any>
}

async function computedCondition(condition: VariableCondition.Desc, getValueByPath: GetValueByPath): Promise<boolean> {
  if (!condition) return true

  if (condition.op === 'and') {
    for (const subCondition of condition.children) {
      const subConditionIsPass = await computedCondition(subCondition, getValueByPath)

      if (!subConditionIsPass) return false
    }

    return true
  }

  if (condition.op === 'or') {
    for (const subCondition of condition.children) {
      const subConditionIsPass = await computedCondition(subCondition, getValueByPath)

      if (subConditionIsPass) return true
    }

    return false
  }

  const leftValue = await getValueByPath(condition.keyPath)
  if (condition.op === 'isNull') {
    return isEmpty(leftValue)
  }

  if (condition.op === 'isNotNull') {
    return !isEmpty(leftValue)
  }

  const rightValue = condition.value

  if (condition.op === 'eq') {
    return leftValue === rightValue
  }

  if (condition.op === 'neq') {
    return leftValue !== rightValue
  }

  if (condition.op === 'gt') {
    return leftValue > rightValue
  }

  if (condition.op === 'gte') {
    return leftValue >= rightValue
  }

  if (condition.op === 'lt') {
    return leftValue < rightValue
  }

  if (condition.op === 'lte') {
    return leftValue <= rightValue
  }

  if (condition.op === 'like') {
    return leftValue?.includes(rightValue)
  }

  if (condition.op === 'notLike') {
    return !leftValue?.includes(rightValue)
  }

  if (condition.op === 'includes') {
    return leftValue?.includes(rightValue)
  }

  if (condition.op === 'notIncludes') {
    return !leftValue?.includes(rightValue)
  }

  if (condition.op === 'in') {
    return rightValue?.includes(leftValue)
  }

  if (condition.op === 'notIn') {
    return !rightValue?.includes(leftValue)
  }

  if (condition.op === 'hasAnyOf') {
    return rightValue?.some((item) => leftValue?.includes(item))
  }

  if (condition.op === 'notAnyOf') {
    return !rightValue?.some((item) => leftValue?.includes(item))
  }

  return true
}

function isEmpty(v: any) {
  if (v === undefined || v === null || v === '') return true

  if (v instanceof Array) {
    return v.length === 0
  }

  if (isObject(v)) {
    return Object.keys(v).length === 0
  }

  if (typeof v === 'number' && isNaN(v)) {
    return true
  }

  return false
}

总结

条件编辑器是一个可视化工具,允许用户通过图形界面组织和配置复杂的条件逻辑。它能够构建由条件和条件组组成的嵌套结构,最终输出布尔值结果,适用于规则配置等场景。

最后推荐一下低代码平台我的应用,可以直接去下载后私有化部署且完全免费。开源不易,望多多支持,也可通过平台提出宝贵意见,感谢!