如何实现条件组合组件
我们是
袋鼠云数栈 UED 团队
,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。
本文作者:
霜序
本文首发于:
https://juejin.cn/post/7299384698882539574
在大数据业务中,时常会出现且或关系逻辑的拼接,有需要做成可视化配置,如下图
目前该组件已经开源到了我们组件库
dt-react-component
,
详细查看
前期分析
需要确定好数据结构
因为是嵌套结构,可以通过 ➕➖ 来增加层级或者数据,因此采用树形结构来存储数据。
export interface IFilterValue<T> {
key: string;
level?: number; // 当前节点的层级,用于判断一些按钮的展示
type?: number; // 当前节点的条件关系,1 | 2
rowValues?: T; // Form 节点的相关的信息(子节点无条件节点时才有)
children?: IFilterValue<T>[]; // 子节点的信息(子节点存在条件节点时才有)
}
上述的图片的数据为:
{
"key": "qTipLrlUt",
"level": 1,
"children": [
{
"key": "B6Jrbqcfof",
"type": 2,
"level": 2,
"children": [
{
"rowValues": {
"condition": 1,
"rowPermission": ""
},
"key": "deg8x8UgZ",
"level": 2
},
{
"key": "_sczw_1h8H",
"type": 1,
"level": 3,
"children": [
{
"key": "Z5UkUPJoA",
"rowValues": {
"condition": 1,
"rowPermission": ""
},
"level": 3
},
{
"key": "MbpJILqHGx",
"rowValues": {
"condition": 1,
"rowPermission": ""
},
"level": 3
}
]
}
]
},
{
"rowValues": {
"condition": 1,
"rowPermission": ""
},
"key": "qx6bG0o5H",
"level": 1
}
],
"type": 1
}
明确每个操作按钮的实现
明确组件的封装
- 组件只希望实现条件节点/线条/操作按钮的展示,因此后面的组件需要作为参数 component 传入
- 组件对层级有一个控制,支持 maxLevel 来控制
- 每一次新增数据的时候,默认值需要传入
initValues - 支持两种模式 编辑状态 和 查看状态
- 支持受控和非受控两种模式
组件封装
FilterRules
提供给用户使用的组件,实现数据的增删改查操作。可以采用受控和非受控两种模式。
它接受的参数如下:
interface IProps<T> {
value?: IFilterValue<T>;
disabled?: boolean;
maxLevel?: number;
initValues: T;
notEmpty?: { data: boolean; message?: string };
component: (props: IComponentProps<T>) => React.ReactNode;
onChange?: (value: IFilterValue<T> | undefined) => void;
}
export const FilterRules = <T>(props: IProps<T>) => {
const {
component,
maxLevel = 5,
disabled = false,
notEmpty = { data: true, message: '必须有一条数据' },
value,
initValues,
onChange,
} = props;
// 查找当前操作的节点
const finRelationNode = (
parentData: IFilterValue<T>,
targetKey: string,
needCurrent?: boolean,
): IFilterValue<T> | null | undefined => {};
const handleAddCondition = (keyObj: { key: string; isOut?: boolean }) => {};
// 增加新的数据,判断是在当前节点下新增或者新生成一个条件节点
const addCondition = (
treeNode: any,
keyObj: { key: string; isOut?: boolean },
initRowValue: T,
) => {};
const handleDeleteCondition = (key: string) => {};
// 删除节点,删除当前节点下的一条数据或者是删除一个条件节点
const deleteCondition = (parentData: IFilterValue<T>, key: string) => {};
// 删除一个条件节点时,更新当前数据的层级
const updateLevel = (node: IFilterValue<T>) => {};
// 更改条件节点的条件
const handleChangeCondition = (
key: string,
type: ROW_PERMISSION_RELATION,
) => {};
// 改变节点的的数据
const handleChangeRowValues = (key: string, values: T) => {};
return (
<RulesController<T>
maxLevel={maxLevel}
disabled={disabled}
value={value}
component={component}
onAddCondition={handleAddCondition}
onDeleteCondition={handleDeleteCondition}
onChangeCondition={handleChangeCondition}
onChangeRowValues={handleChangeRowValues}
/>
);
};
编辑情况
非受控组件使用
<Form form={form}>
<Form.Item name={'condition'}>
<FilterRules<IRowValue>
component={(props) => (
<RowColumnConfig columns={record?.columns ?? []} {...props} />
)}
maxLevel={MAX_LEVEL}
initValues={INIT_ROW_VALUES}
/>
</Form.Item>
</Form>;
// RowColumnConfig 实现,name 可能是 children[0].formValues
<Form.Item
name={['condition', ...name, 'column']}
rules={[{ message: '请选择字段', required: true }]}
initialValue={column}
>
<Select placeholder="请选择字段">
{columns.map((item) => (
<Option key={item} value={item}>
{item}
</Option>
))}
</Select>
</Form.Item>;
// 最后通过 form.validateFields() 拿到的和上述的数据结构一致
受控组件使用
const [ruleData, setRuleData] = useState({
key: shortid(),
level: 0,
rowValues: {
column: first.column,
condition: first.condition,
rowPermission: first?.value,
},
});
<FilterRules<IRowValue>
value={ruleData}
component={(props) => (
<RowColumnConfig columns={record?.columns ?? []} {...props} />
)}
maxLevel={MAX_LEVEL}
initValues={INIT_ROW_VALUES}
onChange={setRuleData}
/>;
// 通过 ruleData 就能够拿到最后的结果
查看使用
<FilterRules
component={(props) => <RowColumnConfig columns={[]} {...props} />}
disabled
value={value}
/>
编辑查看使用(后续新增)
上图为最后实现的效果,适用于部分数据禁用且可以编辑其他数据。常见业务情景:上一次保存的数据不可修改,但需要在当前基础上继续新增数据。
在这种使用模式下,FilterRules 组件上的 props 依旧为 false,通过设置 value 中每一个节点的 disabled 属性来实现上述功能
// 修改 IFilterValue 的类型
//