diff --git a/bricks/next-builder/src/interface.ts b/bricks/next-builder/src/interface.ts index 8c9d84bf7..39bbd6288 100644 --- a/bricks/next-builder/src/interface.ts +++ b/bricks/next-builder/src/interface.ts @@ -18,6 +18,8 @@ export interface TypeFieldItem { model?: "multiple" | "tags"; placeholder?: string; regex?: string; + groupId?: string; + groupLabel?: string; } interface WorkflowDataChildrenOption { @@ -98,3 +100,9 @@ export interface ComparatorOption { id: string; name: string; } + +export interface TypeFieldGroup { + groupId: string; + groupLabel: string; + children?: Partial[]; +} diff --git a/bricks/next-builder/src/shared/components/workflow-condition-field/WorkflowConditionField.module.css b/bricks/next-builder/src/shared/components/workflow-condition-field/WorkflowConditionField.module.css index d037b9a64..a0ddbacbe 100644 --- a/bricks/next-builder/src/shared/components/workflow-condition-field/WorkflowConditionField.module.css +++ b/bricks/next-builder/src/shared/components/workflow-condition-field/WorkflowConditionField.module.css @@ -1,7 +1,7 @@ .dropdownMenu { padding: 10px; max-width: 250px; - max-height: 500px; + max-height: 400px; overflow-y: auto; li { overflow: hidden; diff --git a/bricks/next-builder/src/shared/components/workflow-condition-field/WorkflowConditionField.spec.tsx b/bricks/next-builder/src/shared/components/workflow-condition-field/WorkflowConditionField.spec.tsx index d9182adfd..cffb5413d 100644 --- a/bricks/next-builder/src/shared/components/workflow-condition-field/WorkflowConditionField.spec.tsx +++ b/bricks/next-builder/src/shared/components/workflow-condition-field/WorkflowConditionField.spec.tsx @@ -234,7 +234,6 @@ describe("WorkflowConditionField", () => { fireEvent.click(screen.getByText("next-builder:DYNAMIC_VALUE")); fireEvent.click(screen.getByText("next-builder:FIXED_VALUE")); - screen.debug(); expect(mockChange.mock.calls[0][0]).toEqual({ comparator: "$eq", fieldKey: "name", @@ -285,4 +284,51 @@ describe("FieldDropdownButton", () => { expect(mockClick.mock.calls[0][0]).toEqual("name"); }); + + it("should work with group", () => { + const options = [ + { + id: "name", + name: "名称", + groupLabel: "开始", + groupId: "start", + }, + { + id: "age", + name: "年龄", + groupLabel: "开始", + groupId: "start", + }, + { + id: "id", + name: "id", + groupLabel: "基本", + groupId: "basic", + }, + { + id: "data", + name: "数据", + groupLabel: "基本", + groupId: "basic", + }, + ]; + + const mockClick = jest.fn(); + + render( + + 条件 + + ); + + fireEvent.click(screen.getByText("条件")); + + fireEvent.change(screen.getByRole("textbox"), { + target: { value: "name" }, + }); + + screen.debug(); + + expect(screen.getByText("开始")).toBeInTheDocument(); + }); }); diff --git a/bricks/next-builder/src/shared/components/workflow-condition-field/WorkflowConditionField.tsx b/bricks/next-builder/src/shared/components/workflow-condition-field/WorkflowConditionField.tsx index 07e34f28b..acda2e151 100644 --- a/bricks/next-builder/src/shared/components/workflow-condition-field/WorkflowConditionField.tsx +++ b/bricks/next-builder/src/shared/components/workflow-condition-field/WorkflowConditionField.tsx @@ -1,7 +1,7 @@ import React, { useMemo, useState, useCallback, useEffect } from "react"; import { useTranslation } from "react-i18next"; import { NS_NEXT_BUILDER, K } from "../../../i18n/constants"; -import { Select, Menu, Input, Dropdown, Button } from "antd"; +import { Menu, Input, Dropdown, Button } from "antd"; import { DownOutlined, DeleteOutlined } from "@ant-design/icons"; import { WorkflowDataField } from "../workflow-data-field/WorkflowDataField"; import { ValueTypeField } from "../value-type-field/ValueTypeField"; @@ -12,7 +12,9 @@ import { ComparatorOption, WorkflowDataItem, WorkFLowValueType, + TypeFieldGroup, } from "../../../interface"; +import { processFieldGroup, filterFieldOptions } from "./processor"; import styles from "./WorkflowConditionField.module.css"; interface WorkflowConditionFieldProps { @@ -109,7 +111,9 @@ export function WorkflowConditionField( )} - {field.name} + + {field.groupLabel ? `${field.groupLabel}.${field.name}` : field.name} + handleComparatorChange(id)} @@ -156,8 +160,9 @@ export function WorkflowConditionField( ); } +type FieldDropdownOption = Partial; interface FieldDropdownButtonProps { - options: Partial[]; + options: FieldDropdownOption[]; onClick?: (id: string) => void; children?: React.ReactNode; hiddenSearch?: boolean; @@ -176,17 +181,8 @@ export function FieldDropdownButton( (e: React.ChangeEvent): void => { const v = e.target.value; setQ(v); - if (!v) { - setFilterOptions(options); - } else { - const filter = options.filter( - (item) => - item.name?.toLowerCase().includes(v.toLowerCase()) || - item.id?.toLowerCase().includes(v.toLowerCase()) - ); - - setFilterOptions(filter); - } + const filter = filterFieldOptions(options, v); + setFilterOptions(filter); }, [options] ); @@ -206,6 +202,17 @@ export function FieldDropdownButton( } }, [visible, options]); + const getMenuItem = useCallback( + (item: FieldDropdownOption) => { + return ( + handleItemClick(item.id)}> + {item.name} + + ); + }, + [handleItemClick] + ); + const menu = useMemo( () => ( @@ -216,14 +223,20 @@ export function FieldDropdownButton( onChange={handleChange} placeholder={t(K.SEARCH_FIELD)} /> - {filterOptions?.map((item) => ( - handleItemClick(item.id)}> - {item.name} - - ))} + {processFieldGroup(filterOptions)?.map((item, index) => + item.groupId ? ( + + {(item as TypeFieldGroup).children?.map((child) => + getMenuItem(child) + )} + + ) : ( + getMenuItem(item) + ) + )} ), - [filterOptions, handleChange, handleItemClick, q, hiddenSearch, t] + [filterOptions, getMenuItem, handleChange, hiddenSearch, q, t] ); return ( diff --git a/bricks/next-builder/src/shared/components/workflow-condition-field/processor.spec.ts b/bricks/next-builder/src/shared/components/workflow-condition-field/processor.spec.ts new file mode 100644 index 000000000..012d1a248 --- /dev/null +++ b/bricks/next-builder/src/shared/components/workflow-condition-field/processor.spec.ts @@ -0,0 +1,339 @@ +import { processFieldGroup, filterFieldOptions } from "./processor"; + +describe("processor", () => { + it.each([ + [ + [ + { + name: "名称", + id: "name", + type: "string", + originType: "str", + required: true, + }, + { + name: "年龄", + id: "age", + type: "number", + originType: "int", + required: true, + }, + { + name: "日期", + id: "date", + type: "date", + originType: "date", + required: false, + timeFormat: "YYYY/MM/DD", + }, + { + name: "时间", + id: "datetime", + type: "datetime", + originType: "datetime", + required: false, + }, + ], + [ + { + id: "name", + name: "名称", + originType: "str", + required: true, + type: "string", + }, + { + id: "age", + name: "年龄", + originType: "int", + required: true, + type: "number", + }, + { + id: "date", + name: "日期", + originType: "date", + required: false, + timeFormat: "YYYY/MM/DD", + type: "date", + }, + { + id: "datetime", + name: "时间", + originType: "datetime", + required: false, + type: "datetime", + }, + ], + ], + [ + [ + { + name: "名称", + id: "name", + type: "string", + originType: "str", + required: true, + groupId: "start", + groupLabel: "开始", + }, + { + name: "年龄", + id: "age", + type: "number", + originType: "int", + required: true, + groupId: "start", + groupLabel: "开始", + }, + { + name: "日期", + id: "date", + type: "date", + originType: "date", + required: false, + timeFormat: "YYYY/MM/DD", + groupId: "step1", + groupLabel: "步骤1", + }, + { + name: "时间", + id: "datetime", + type: "datetime", + originType: "datetime", + required: false, + groupId: "step1", + groupLabel: "步骤1", + }, + ], + [ + { + children: [ + { + groupId: "start", + groupLabel: "开始", + id: "name", + name: "名称", + originType: "str", + required: true, + type: "string", + }, + { + groupId: "start", + groupLabel: "开始", + id: "age", + name: "年龄", + originType: "int", + required: true, + type: "number", + }, + ], + groupId: "start", + groupLabel: "开始", + }, + { + children: [ + { + groupId: "step1", + groupLabel: "步骤1", + id: "date", + name: "日期", + originType: "date", + required: false, + timeFormat: "YYYY/MM/DD", + type: "date", + }, + { + groupId: "step1", + groupLabel: "步骤1", + id: "datetime", + name: "时间", + originType: "datetime", + required: false, + type: "datetime", + }, + ], + groupId: "step1", + groupLabel: "步骤1", + }, + ], + ], + ])("%# should work", (fieldList, result) => { + expect(processFieldGroup(fieldList)).toEqual(result); + }); + + it.each([ + [ + [ + { + name: "名称", + id: "name", + type: "string", + originType: "str", + required: true, + }, + { + name: "年龄", + id: "age", + type: "number", + originType: "int", + required: true, + }, + { + name: "日期", + id: "date", + type: "date", + originType: "date", + required: false, + timeFormat: "YYYY/MM/DD", + }, + { + name: "时间", + id: "datetime", + type: "datetime", + originType: "datetime", + required: false, + }, + ], + undefined, + [ + { + name: "名称", + id: "name", + type: "string", + originType: "str", + required: true, + }, + { + name: "年龄", + id: "age", + type: "number", + originType: "int", + required: true, + }, + { + name: "日期", + id: "date", + type: "date", + originType: "date", + required: false, + timeFormat: "YYYY/MM/DD", + }, + { + name: "时间", + id: "datetime", + type: "datetime", + originType: "datetime", + required: false, + }, + ], + ], + [ + [ + { + name: "名称", + id: "name", + type: "string", + originType: "str", + required: true, + }, + { + name: "年龄", + id: "age", + type: "number", + originType: "int", + required: true, + }, + { + name: "日期", + id: "date", + type: "date", + originType: "date", + required: false, + timeFormat: "YYYY/MM/DD", + }, + { + name: "时间", + id: "datetime", + type: "datetime", + originType: "datetime", + required: false, + }, + ], + "name", + [ + { + id: "name", + name: "名称", + originType: "str", + required: true, + type: "string", + }, + ], + ], + [ + [ + { + name: "名称", + id: "name", + type: "string", + originType: "str", + required: true, + groupId: "start", + groupLabel: "开始", + }, + { + name: "年龄", + id: "age", + type: "number", + originType: "int", + required: true, + groupId: "start", + groupLabel: "开始", + }, + { + name: "日期", + id: "date", + type: "date", + originType: "date", + required: false, + timeFormat: "YYYY/MM/DD", + groupId: "step1", + groupLabel: "步骤1", + }, + { + name: "时间", + id: "datetime", + type: "datetime", + originType: "datetime", + required: false, + groupId: "step1", + groupLabel: "步骤1", + }, + ], + "开始", + [ + { + groupId: "start", + groupLabel: "开始", + id: "name", + name: "名称", + originType: "str", + required: true, + type: "string", + }, + { + groupId: "start", + groupLabel: "开始", + id: "age", + name: "年龄", + originType: "int", + required: true, + type: "number", + }, + ], + ], + ])("%# should work", (fieldList, q, result) => { + expect(filterFieldOptions(fieldList, q)).toEqual(result); + }); +}); diff --git a/bricks/next-builder/src/shared/components/workflow-condition-field/processor.ts b/bricks/next-builder/src/shared/components/workflow-condition-field/processor.ts new file mode 100644 index 000000000..bcb2bf770 --- /dev/null +++ b/bricks/next-builder/src/shared/components/workflow-condition-field/processor.ts @@ -0,0 +1,45 @@ +import { TypeFieldItem, TypeFieldGroup } from "../../../interface"; + +type FieldOptionItem = Partial; +type ProcessFieldGroupItem = FieldOptionItem | TypeFieldGroup; + +export function processFieldGroup( + fieldList: FieldOptionItem[] +): ProcessFieldGroupItem[] { + const options = [] as ProcessFieldGroupItem[]; + + fieldList?.forEach((field) => { + if (field.groupId) { + const find = options.find((item) => item.groupId === field.groupId); + + if (!find) { + options.push({ + groupLabel: field.groupLabel, + groupId: field.groupId, + children: [field], + }); + } else { + (find as TypeFieldGroup).children.push(field); + } + } else { + options.push(field); + } + }); + + return options; +} + +export function filterFieldOptions( + fieldList: FieldOptionItem[], + q: string +): FieldOptionItem[] { + if (!q) return fieldList; + + return fieldList?.filter( + (item) => + item.groupId?.toLowerCase().includes(q.toLowerCase()) || + item.groupLabel?.toLowerCase().includes(q.toLowerCase()) || + item.name?.toLowerCase().includes(q.toLowerCase()) || + item.id?.toLowerCase().includes(q.toLowerCase()) + ); +}