简介
条件编辑器可以让用户以可视化的方式组织条件以及条件组,每个条件或条件组的运算结果是一个布尔类型的数据(是/否),用于配置规则。
先看一下条件编辑器的界面布局:
可以看出条件编辑器由条件和条件组组成,条件和条件组都可以嵌套,条件组可以包含多个条件或条件组,条件组的运算结果是所有条件或条件组的运算结果的逻辑运算结果。
类型定义
按数据格式可以将条件分成三类:
- 条件组(有子条件,and/or)
- 无比较值的条件(拥有了比较符即可确定条件的值,isNull/isNotNull)
- 普通条件(包含被比较对象、比较符和比较值三个部分)
具体 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>
}
界面实现思路
界面可以根据是否是条件组来进行分类,只有条件组才可以包含子条件,所以可以将单个条件封装为一个组件。
- 单个条件:根据选中的变量类型不同,过滤出符合该类型的比较符,然后根据比较符的类型,决定值的输入方式,例如:选中的类型是一个字符串,可以用于字符串的比较符包括:isNull, isNotNull, eq, neq, like, notLike,当选择了比较符后再判断其应该输入什么样类型的值,若选择 isNull/isNotNull 则无需渲染出值的输入框,若选择 eq/neq/like/notLike 则需要渲染一个文本输入框即可。
- 条件组:条件组包括逻辑符(and/or)和该组的子条件,所以需要渲染一个逻辑符的选择框和一个子条件的列表,子条件的列表可以通过递归的方式来逐条渲染。
边界情况:
- 未配置任何条件时:渲染一个添加条件的按钮即可,点击该按钮直接添加一个包含一个条件的条件组
- 条件组的子条件为空时:默认条件组的条件若为空则直接将给条件组移除(因此无需渲染删除条件组的按钮,但是在添加条件组时需要默认向条件组中插入一个条件)
计算条件
获得了条件的描述信息后,就需要在适当的时机来计算条件的值,例如将该条件用于数据库查询,那么就需要将条件转换成 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
}
总结
条件编辑器是一个可视化工具,允许用户通过图形界面组织和配置复杂的条件逻辑。它能够构建由条件和条件组组成的嵌套结构,最终输出布尔值结果,适用于规则配置等场景。
最后推荐一下低代码平台我的应用,可以直接去下载后私有化部署且完全免费。开源不易,望多多支持,也可通过平台提出宝贵意见,感谢!