自定义一个玩出花来的查询构建器React Query Builder
首先我们来看看官方给出demo:一键直达
通过自定义Antd的Cascader组件,最后的效果如下:
一、还原demo
首先,我们需要把整个demo的架子还原,然后再此基础上才好做自定义拓展。
1)引入所需库
import QueryBuilder from 'react-querybuilder';
import {
AntDActionElement,
AntDDragHandle,
AntDNotToggle,
AntDValueEditor,
AntDValueSelector,
} from '@react-querybuilder/antd';
import './index.css';
2)自定义控件对象:controlElements
const controlElements = {
addGroupAction: AntDActionElement,
addRuleAction: AntDActionElement,
cloneGroupAction: AntDActionElement,
cloneRuleAction: AntDActionElement,
combinatorSelector: AntDValueSelector,
fieldSelector: AntDValueSelector,
notToggle: AntDNotToggle,
operatorSelector: AntDValueSelector,
removeGroupAction: AntDActionElement,
removeRuleAction: AntDActionElement,
valueEditor: AntDValueEditor,
dragHandle: AntDDragHandle,
};
3)定义运算符数组:operators
const operators = [
{ name: '=', label: '=' },
{ name: '!=', label: '!=' },
{ name: '<', label: '<' },
{ name: '>', label: '>' },
{ name: '<=', label: '<=' },
{ name: '>=', label: '>=' },
{ name: 'contains', label: 'contains' },
{ name: 'beginsWith', label: 'begins with' },
{ name: 'endsWith', label: 'ends with' },
{ name: 'doesNotContain', label: 'does not contain' },
{ name: 'doesNotBeginWith', label: 'does not begin with' },
{ name: 'doesNotEndWith', label: 'does not end with' },
{ name: 'null', label: 'is null' },
{ name: 'notNull', label: 'is not null' },
{ name: 'in', label: 'in' },
{ name: 'notIn', label: 'not in' },
{ name: 'between', label: 'between' },
{ name: 'notBetween', label: 'not between' },
];
中文版的可以这么写:
const operators = [
{ label: '等于', name: '=' },
{ label: '不等于', name: '!=' },
{ label: '小于', name: '<' },
{ label: '大于', name: '>' },
{ label: '小于等于', name: '<=' },
{ label: '大于等于', name: '>=' },
{ label: '包含', name: 'contains' },
{ label: '不包含', name: 'does not contain' },
{ label: '为空', name: 'is null' },
{ label: '不为空', name: 'is not null' },
{ label: '在范围中', name: 'between' },
{ label: '不在范围中', name: 'not between' },
];
4)定义用于 RuleGroups 的组合子数组:combinators
const combinators = [
{ name: 'and', label: '且' },
{ name: 'or', label: '或' },
];
5)定义可翻译的文本: translations
{
fields: {
title: "Fields",
},
operators: {
title: "Operators",
},
value: {
title: "Value",
},
removeRule: {
label: "x",
title: "Remove rule",
},
removeGroup: {
label: "x",
title: "Remove group",
},
addRule: {
label: "+Rule",
title: "Add rule",
},
addGroup: {
label: "+Group",
title: "Add group",
},
combinators: {
title: "Combinators",
},
notToggle: {
label: "Not",
title: "Invert this group",
},
cloneRule: {
label: '⧉',
title: 'Clone rule'
},
cloneRuleGroup: {
label: '⧉',
title: 'Clone group'
},
dragHandle: {
label: '⁞⁞',
title: 'Drag handle'
}
}
6)定义特定的 CSS 类分配给组件呈现的各种控件:controlClassnames
const controlClassnames = {
removeGroup: 'bg_grey',
removeRule: 'bg_grey',
combinators: 'bg_grey',
};
对应的css样式:
.bg_grey {
background-color: #ff7e4e;
border-color: #ff7e4e;
}
.bg_grey:hover {
background-color: #ff9b75;
border-color: #ff9b75;
}
7)最后
export default (props: any) => {
const { query, setQuery, variableFields, disabled } = props;
return (
<QueryBuilder
disabled={disabled}
query={query}
fields={variableFields}
operators={operators}
combinators={combinators}
translations={translations}
controlElements={controlElements}
controlClassnames={controlClassnames}
onQueryChange={(q) => {
setQuery(q)
}}
/>
);
};
到这里,我们就配置好了基础版的demo。
二、把fields的select下拉改造成Cascader级联选择
1)关闭fields的默认来源
修改controlElements配置:
// fieldSelector: AntDValueSelector,
修改QueryBuilder配置:
// fields={variableFields}
2)引入自定义组件CustomValueEditor
修改controlElements配置:
fieldSelector: CustomValueEditor,
修改QueryBuilder配置:不需要额外定义fields
controlElements={controlElements}
3)自定义组件CustomValueEditor
import React, { useEffect, useState } from 'react';
import { ValueEditor } from 'react-querybuilder';
import { Cascader, message } from 'antd';
import client from '../../../utils/request';
const CustomValueEditor: React.FC = (props: any) => {
const [options, setOptions] = useState([]);
const [cascaderDisabled, setCascaderDisabled] = useState(Boolean);
const list = [] as any;
const DFS = (op: any, field: any) => {
for (let i = 0; i < op.length; i++) {
const localName = op[i].name;
const localChildren = op[i].children;
list.push(localName);
if (localName === field) {
return true;
}
if (localChildren) {
const res = DFS(localChildren, field);
if (res) {
return true;
} else {
list.pop(localName);
}
}
}
};
function displayRender(label: any) {
return label[label.length - 1];
}
useEffect(() => {
(async (params = {}) => {
let data = await client<any>(`接口请求地址`, {
params,
});
setOptions(data.data);
})();
}, []);
if (props.value != '~' && options.length>0) {
DFS(options, props.value);
}
if (props && options.length>0) {
return (
<div>
<Cascader
expandTrigger="hover"
options={options}
displayRender={displayRender}
onChange={(value: any) => {
const data = value[value.length - 1]
props.handleOnChange(data);
}}
defaultValue={list}
value={list}
/>
</div>
);
}
DFS(options, props.value);
return <ValueEditor {...props} />;
};
export default CustomValueEditor;
options的JSON格式如下:
{
value: 'zhejiang',
label: 'Zhejiang',
children: [
{
value: 'hangzhou',
label: 'Hangzhou',
children: [
{
value: 'xihu',
label: 'West Lake',
},
],
},
],
},
{
value: 'jiangsu',
label: 'Jiangsu',
children: [
{
value: 'nanjing',
label: 'Nanjing',
children: [
{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
},
],
},
],
},
⚠️props.handleOnChange()
会将数据更新给QueryBuilder
onChange={(value: any) => {
const data = value[value.length - 1]
props.handleOnChange(data);
}}
到这里整个改造完成啦!
转载自:https://juejin.cn/post/7058525058687303710