Commit 816e81936e75b8fef6b092d41d1ee7c34c135b17

Authored by qiang.tian
1 parent dc1b1717

refactor: 优化 flow-node-selector

... ... @@ -44,6 +44,7 @@
44 44 },
45 45 "dependencies": {
46 46 "@ant-design/icons": "^5.2.5",
  47 + "@qx/flow": "^1.0.0-alpha.23",
47 48 "@qx/ui": "0.0.3-beta.1",
48 49 "@qx/utils": "0.0.58",
49 50 "ahooks": "^3.7.5",
... ...
... ... @@ -5,13 +5,13 @@ nav:
5 5 order: 1
6 6 group:
7 7 path: /common
8   - title: 基础条件配置
  8 + title: 条件配置
9 9 order: 0
10 10 ---
11 11
12   -## QxBaseCondition 条件配置
  12 +## QxBaseCondition 基础条件配置
13 13
14   -### 条件配置
  14 +### 基础条件配置
15 15
16 16 ```tsx
17 17 import { QxBaseCondition } from '@qx/common';
... ...
... ... @@ -2,17 +2,11 @@
2 2
3 3 .qx-condition {
4 4 width: 100%;
5   -
6   - &-item {
  5 +
  6 + &-header {
7 7 display: flex;
8 8 align-items: center;
9   -
10   -
11   - &__idx {
12   - color: @N9;
13   - margin-right: 4px;
14   - width: 24px;
15   - display: inline-block;
16   - }
  9 + justify-content: space-between;
  10 + margin-bottom: 16px;
17 11 }
18 12 }
... ...
1   -import React, { useEffect } from 'react';
  1 +import { Button } from 'antd';
  2 +import React, { useEffect, useRef, useState } from 'react';
2 3 import {
3 4 QxBaseCondition,
4 5 QxBaseConditionValueType,
5 6 } from '../qx-base-condition';
  7 +import { QxBaseIcon } from '../qx-base-icon';
6 8 import { QxConditionSql, SqlType } from '../qx-condition-sql';
7   -import { INode } from '../qx-flow-node-selector';
  9 +import QxFieldPopover from '../qx-field/src/popover';
  10 +import FieldsCheckboxGroup from '../qx-field/src/popover/checkbox';
  11 +import { INode, QxFlowNodeFieldSelector } from '../qx-flow-node-selector';
  12 +import { request } from '../utils';
  13 +
  14 +import './index.less';
  15 +
  16 +const excludeCodes = ['id'];
  17 +
  18 +export function getFieldsByFunId(funId: string, params?: { useKey: boolean }) {
  19 + return request.get(`/qx-apaas-lowcode/app/form/${funId}/field`, { params });
  20 +}
8 21
9 22 export const QxCondition: React.FC<QxConditionProps> = ({
10 23 value,
... ... @@ -14,14 +27,115 @@ export const QxCondition: React.FC<QxConditionProps> = ({
14 27 showAssignment = true,
15 28 onChange,
16 29 node,
17   - nodes
  30 + nodes,
  31 + header,
  32 + headerLeft,
  33 + headerRight,
  34 + formId,
  35 + multiple,
  36 + showHeader = false
18 37 }) => {
  38 + const [fields, setFields] = useState([]);
  39 +
  40 + const fieldSelectorRef = useRef();
  41 +
19 42 const handleChange = (opt: Partial<QxConditionValueType>) => {
20 43 onChange?.(Object.assign({}, value, opt, { enabled }));
21 44 };
22 45
  46 + const handleGetFieldsList = async (formId: string) => {
  47 + try {
  48 + const data = await getFieldsByFunId(formId);
  49 + setFields(data.filter((i) => !excludeCodes.includes(i.code)));
  50 + } catch (error) {
  51 + setFields([]);
  52 + }
  53 + };
  54 +
  55 + useEffect(() => {
  56 + if (formId) handleGetFieldsList(formId);
  57 + }, [formId]);
  58 +
  59 + const PopoverComponent = multiple ? FieldsCheckboxGroup : QxFieldPopover;
  60 +
  61 + const handleAddField = (val: any, opt: any) => {
  62 + handleChange({
  63 + operators: [
  64 + ...(value?.operators || []),
  65 + {
  66 + field: opt,
  67 + code: opt.code,
  68 + name: opt.name,
  69 + type: opt.extract?.fieldType,
  70 + opt: 'IS',
  71 + mappingValues: [],
  72 + },
  73 + ],
  74 + });
  75 + // setFieldValue(
  76 + // 'condition',
  77 + // Object.assign({}, condition, {
  78 + // operators: [
  79 + // ...(condition.operators || []),
  80 + // { field: opt, code: val },
  81 + // ],
  82 + // }),
  83 + // );
  84 + };
  85 +
  86 + // const fieldOpt = useMemo(() => {
  87 + // return fieldSelectorRef.current?.fieldMap || {};
  88 + // }, []);
  89 +
  90 + const RenderHeader = header ? (
  91 + header
  92 + ) : (
  93 + <div className="qx-condition-header">
  94 + {!headerLeft ? (
  95 + <div className="qx-condition-header__title">条件配置</div>
  96 + ) : (
  97 + headerLeft
  98 + )}
  99 + {!headerRight ? (
  100 + <div className="qx-condition-header__right">
  101 + {node ? (
  102 + <QxFlowNodeFieldSelector
  103 + ref={fieldSelectorRef}
  104 + node={node}
  105 + nodes={nodes!}
  106 + mode="variable"
  107 + onChange={handleAddField}
  108 + >
  109 + <Button size="small" type="link">
  110 + <QxBaseIcon style={{ fontSize: 16 }} type="qx-icon-plus" />
  111 + 添加字段
  112 + </Button>
  113 + </QxFlowNodeFieldSelector>
  114 + ) : (
  115 + <PopoverComponent
  116 + ref={fieldSelectorRef}
  117 + data={fields}
  118 + onSelect={handleAddField}
  119 + onChange={handleAddField}
  120 + >
  121 + <Button size="small" type="link">
  122 + <QxBaseIcon style={{ fontSize: 16 }} type="qx-icon-plus" />
  123 + 添加字段
  124 + </Button>
  125 + </PopoverComponent>
  126 + )}
  127 + </div>
  128 + ) : (
  129 + headerRight
  130 + )}
  131 + </div>
  132 + );
  133 +
  134 + console.log('operators', fieldSelectorRef);
  135 +
23 136 return (
24   - <div className={`qx-condition ${className}`} style={style}>
  137 + <div className={`qx-condition ${className && className}`} style={style}>
  138 + {showHeader && RenderHeader}
25 139 <QxBaseCondition
26 140 mode="condition"
27 141 nodes={nodes}
... ... @@ -69,7 +183,13 @@ export interface QxConditionProps {
69 183 onChange?: (value: QxConditionValueType) => void;
70 184 className?: string;
71 185 style?: React.CSSProperties;
72   - node?: INode
73   - nodes?: INode[]
74   - showAssignment?: boolean
  186 + node?: INode;
  187 + nodes?: INode[];
  188 + showAssignment?: boolean;
  189 + header?: React.ReactNode;
  190 + headerRight?: React.ReactNode;
  191 + headerLeft?: React.ReactNode;
  192 + multiple?: boolean;
  193 + formId?: string;
  194 + showHeader?: boolean;
75 195 }
... ...
  1 +---
  2 +nav:
  3 + path: /component
  4 + title: 组件
  5 + order: 1
  6 +group:
  7 + path: /flow
  8 + title: 流程
  9 + order: 0
  10 +---
  11 +
  12 +## QxFlowNodeFieldSelector 流程结果集选择器
  13 +
  14 +### 普通
  15 +
  16 +```tsx
  17 +import { QxFlowNodeFieldSelector } from '@qx/common';
  18 +
  19 +const node = {
  20 + id: 'dfc29d5b64fa42489a65b3cfaeb999da',
  21 + type: 'default_DF_CONDITION',
  22 + name: '条件',
  23 + iconColor: '#F77234',
  24 + data: {},
  25 + children: [],
  26 + previousId: 'b0a2c7925a7b45e884fe8f82a0f39e9b',
  27 +};
  28 +
  29 +const nodes = [
  30 + {
  31 + id: '4c4fc5213db149808c57d093b15e6295',
  32 + name: '开始',
  33 + type: 'default_DF_START',
  34 + data: {
  35 + nodeVersion: '3.0.0',
  36 + data: {
  37 + enablePropagation: false,
  38 + propagation: 'REQUIRED',
  39 + isolation: 'REPEATABLE_READ',
  40 + },
  41 + result: [
  42 + {
  43 + id: '9911c21704ba4d8da8651185f441f9d3',
  44 + code: '5sfuiz',
  45 + type: 'OBJECT',
  46 + title: '5sfuiz',
  47 + qxProps: {},
  48 + pid: '',
  49 + description: '',
  50 + valueMapping: { mappingValues: [] },
  51 + valuesObj: [],
  52 + child: [
  53 + {
  54 + id: '022c007b4c304c58850ed592ef5c1774',
  55 + type: 'STRING',
  56 + pid: '9911c21704ba4d8da8651185f441f9d3',
  57 + code: 'gcc8qd',
  58 + title: 'gcc8qd',
  59 + },
  60 + ],
  61 + },
  62 + {
  63 + id: '6289083b52474567aba6d3f5fe9eda90',
  64 + code: '3coizb',
  65 + type: 'FORM',
  66 + title: '3coizb',
  67 + qxProps: {
  68 + appId: 'HQIXKC0dxbuYENalZzP',
  69 + formId: 'VX1TdanWSgYrKYn3vT8',
  70 + isTree: false,
  71 + },
  72 + pid: '',
  73 + },
  74 + ],
  75 + },
  76 + children: [],
  77 + },
  78 + {
  79 + id: 'b0a2c7925a7b45e884fe8f82a0f39e9b',
  80 + name: '分支',
  81 + type: 'default_DF_BRANCH',
  82 + previousId: '4c4fc5213db149808c57d093b15e6295',
  83 + children: [
  84 + {
  85 + id: 'dfc29d5b64fa42489a65b3cfaeb999da',
  86 + type: 'default_DF_CONDITION',
  87 + previousId: 'b0a2c7925a7b45e884fe8f82a0f39e9b',
  88 + name: '条件',
  89 + data: {},
  90 + children: [],
  91 + },
  92 + {
  93 + id: '003ca991f70548a3ba8c4f9b8d0daad2',
  94 + name: '条件',
  95 + type: 'default_DF_CONDITION',
  96 + previousId: 'b0a2c7925a7b45e884fe8f82a0f39e9b',
  97 + children: [],
  98 + },
  99 + ],
  100 + },
  101 + {
  102 + id: '576f817ce67846318d1132f231128f05',
  103 + name: '结束',
  104 + previousId: 'b0a2c7925a7b45e884fe8f82a0f39e9b',
  105 + type: 'default_DF_END',
  106 + data: { nodeVersion: '3.0.0' },
  107 + children: [],
  108 + },
  109 +];
  110 +
  111 +export default () => {
  112 + return <QxFlowNodeFieldSelector node={node} nodes={nodes} />;
  113 +};
  114 +```
  115 +
  116 +<API id="NodeFieldSelectProps"></API>
... ...
... ... @@ -7,8 +7,6 @@ import { FieldBaseType } from '../qx-base-condition';
7 7 import { QxBaseIcon } from '../qx-base-icon';
8 8 import { request } from '../utils';
9 9 import './index.less';
10   -// import type { FiledType } from '@/interface';
11   -// import type { INode } from '@qx/flow';
12 10
13 11 const getAppsFields = (params: string[], appId = 'default') => {
14 12 return request.post(`/qx-apaas-lowcode/app/${appId}/fields`, {
... ... @@ -217,13 +215,16 @@ export const useNodeFieldDisplay = ({
217 215 /**
218 216 * 查询所有 formId 的字段,并给 result 添加 child
219 217 */
220   - const handleGetAppsFields = async () => {
  218 + const handleFormTypeAddChild = async () => {
221 219 const forms = findResultByFormId(sourceParentNodes);
  220 + if (!forms.length) return sourceParentNodes;
  221 +
222 222 const ids = forms.map((item) => item.qxProps?.formId);
223 223 if (Array.isArray(ids) && ids.length && subset) {
224 224 try {
225 225 const data = await getAppsFields(ids as any[]);
226 226 Object.keys(data).forEach((id) => {
  227 + // 此处是引用类型,会间接修改 sourceParentNodes
227 228 const form = forms.find((item) => item.qxProps?.formId === id);
228 229 if (!form) return;
229 230
... ... @@ -272,12 +273,12 @@ export const useNodeFieldDisplay = ({
272 273 name: fields.title,
273 274 code:
274 275 parent && ['ORG', 'FORM', 'USER'].includes(parent.type)
275   - ? `${parent.code}.${fields.code}`
276   - : `${nodeId}|${fields.code}`,
  276 + ? `${parent.code}.${fields.id}`
  277 + : `${nodeId}|${fields.id}`,
277 278 extract: {
278 279 ...(fields.extract || {}),
279 280 fieldType: fields.type,
280   - fieldKey: fields.code,
  281 + fieldKey: fields.id,
281 282 },
282 283 });
283 284 fields.child = correctionNodeField(
... ... @@ -290,43 +291,31 @@ export const useNodeFieldDisplay = ({
290 291 return fields;
291 292 };
292 293
  294 + /**
  295 + * 获取可被选择的节点和结果
  296 + */
293 297 const getOptionalNodes = async () => {
294   - const targetParentNodes = await handleGetAppsFields();
  298 + const targetParentNodes = await handleFormTypeAddChild(); // 给 form 类型的字段添加 child
295 299
296 300 for (let i = 0; i < targetParentNodes.length; i++) {
  301 + // TODO: 统一节点和 result 格式
297 302 correctionNodeField(
298 303 targetParentNodes[i].data?.result || [],
299 304 targetParentNodes[i].id,
300 305 );
301 306 }
302 307
303   - if (!limitTypes || !limitTypes.length) {
304   - setOptionalNodes(targetParentNodes);
305   - renderInputDisplay(targetParentNodes);
306   - return;
307   - }
308   -
309 308 /**
310 309 * 根据 limitType 获取可选择的字段
311 310 */
312 311 function getEffectiveResult(result: FiledType[]) {
313 312 const newResult = [];
  313 +
314 314 for (let i = 0; i < result.length; i++) {
315 315 const resultItem = result[i] || {};
316   - // correctionNodeField(resultItem, nodeId, parent);
317 316
318   - if (resultItem.child) {
319   - resultItem.child = getEffectiveResult(resultItem.child);
320   - if (
321   - (Array.isArray(resultItem.child) && resultItem.child.length) ||
322   - limitTypes?.includes(
323   - FieldBaseType[resultItem.type as keyof typeof FieldBaseType] ||
324   - resultItem.type,
325   - )
326   - ) {
327   - newResult.push(resultItem);
328   - }
329   - } else if (
  317 + if (
  318 + // 先将表单字段类型或流程参数类型转换为基础类型再做过滤
330 319 limitTypes?.includes(
331 320 FieldBaseType[resultItem.type as keyof typeof FieldBaseType] ||
332 321 resultItem.type,
... ... @@ -334,6 +323,10 @@ export const useNodeFieldDisplay = ({
334 323 ) {
335 324 newResult.push(resultItem);
336 325 }
  326 +
  327 + if (Array.isArray(resultItem.child) && resultItem.child.length) {
  328 + resultItem.child = getEffectiveResult(resultItem.child);
  329 + }
337 330 }
338 331
339 332 return newResult;
... ... @@ -351,9 +344,15 @@ export const useNodeFieldDisplay = ({
351 344 return nodes;
352 345 }
353 346
354   - const newNodes = getEffectiveNodes(targetParentNodes);
355   - setOptionalNodes([...newNodes]);
356   - renderInputDisplay([...newNodes]);
  347 + let newNodes = targetParentNodes;
  348 +
  349 + // 有类型限制根据 limitType 筛选出可选的节点和 result
  350 + if (limitTypes && Array.isArray(limitTypes) && limitTypes.length) {
  351 + newNodes = getEffectiveNodes(targetParentNodes);
  352 + }
  353 +
  354 + setOptionalNodes(newNodes);
  355 + renderInputDisplay(newNodes);
357 356 };
358 357
359 358 const getResultFieldMaps = (optionalNodes: INode[]) => {
... ... @@ -470,13 +469,18 @@ const SelectItem = (props: any) => {
470 469 );
471 470 };
472 471
473   -export const QxFlowNodeFieldSelector: React.FC<NodeFieldSelectProps> = (
474   - props,
475   -) => {
  472 +export const QxFlowNodeFieldSelector = React.forwardRef<
  473 + any,
  474 + NodeFieldSelectProps
  475 +>((props, ref) => {
476 476 const [visible, setVisible] = useState(false);
477 477
478   - const { optionalNodes, renderInputDisplay, inputDisplay } =
479   - useNodeFieldDisplay(props);
  478 + const {
  479 + optionalNodes,
  480 + renderInputDisplay,
  481 + inputDisplay,
  482 + } = useNodeFieldDisplay(props);
  483 + // console.log('111111', props, optionalNodes);
480 484
481 485 const getOptions = () => {
482 486 return optionalNodes.map((node) => ({
... ... @@ -528,8 +532,23 @@ export const QxFlowNodeFieldSelector: React.FC<NodeFieldSelectProps> = (
528 532 }
529 533 };
530 534
  535 + useEffect(() => {
  536 + const closeDropdown = () => {
  537 + setVisible(false);
  538 + };
  539 + document.body.addEventListener('click', closeDropdown);
  540 + return () => {
  541 + document.body.removeEventListener('click', closeDropdown);
  542 + };
  543 + }, []);
  544 +
531 545 return (
532   - <div className={cls('qx-node-select')}>
  546 + <div
  547 + className={cls('qx-node-select')}
  548 + onClick={(e) => {
  549 + e.stopPropagation();
  550 + }}
  551 + >
533 552 <Dropdown
534 553 destroyPopupOnHide
535 554 trigger={['click']}
... ... @@ -556,7 +575,7 @@ export const QxFlowNodeFieldSelector: React.FC<NodeFieldSelectProps> = (
556 575 </Dropdown>
557 576 </div>
558 577 );
559   -};
  578 +});
560 579
561 580 export interface NodeFieldSelectProps {
562 581 node: INode;
... ...