From 94e38e453a54f0447fa75ab0f76f176a47c6f67c Mon Sep 17 00:00:00 2001 From: katherinehhh Date: Sat, 25 Feb 2023 00:40:51 +0800 Subject: [PATCH] feat: form/button linkage rules (#1456) * feat(plugin-workflow): add prompt node * feat(plugin-workflow): useValueGetter for all instructions and triggers * feat(plugin-workflow): add workflow block initializer * refactor(plugin-workflow): change prompt node type to manual * feat(plugin-workflow): add ModeConfig component for mode * feat(plugin-workflow): add todo drawer * feat(plugin-workflow): add block value provider * feat(plugin-workflow): improve todo block and drawer * fix(plugin-workflow): fix instruction name in test cases * fix(plugin-workflow): fix test cases * refactor(plugin-workflow): change param type of collection field initializer * feat(plugin-workflow): add filter types for getters * fix(plugin-workflow): fix assignees variable * fix(plugin-workflow): filter todo by exist workflow * fix(plugin-workflow): fix duplicated save action in manual config * fix(plugin-workflow): fix transaction * feat(plugin-workflow): destroy workflow will be cascaded * fix(plugin-workflow): fix merge * fix(plugin-workflow): fix locale * fix(plugin-workflow): allow open ui view when executed * fix(plugin-workflow): change todo table filter * feat(plugin-workflow): use formula for calculation * fix(plugin-workflow): fix variable template regexp * fix(plugin-workflow): fix sub-options logic with types * refactor(plugin-workflow): drop useless component * fix(plugin-workflow): fix manual node action button * feat(plugin-workflow): add new variable input component * refactor(plugin-workflow): change all variable to new component * fix(plugin-workflow): fix type * fix(plugin-workflow): fix functions init * fix(plugin-workflow): change jsonb to json for stable order * fix(plugin-workflow): fix duplicated field name when initialize * fix(plugin-workflow): fix manual result in manual block * test(plugin-workflow): log field initializer props * fix(plugin-workflow): fix nullable arguments * test(plugin-workflow): test initializer fields schema * fix: observer * fix(plugin-workflow): adjust hints * fix(plugin-workflow): fix locale and cursor in variable input * refactor(plugin-workflow): change status keys * fix(plugin-workflow): fix parallel instruction * feat: linkage rules * refactor: linkage rules * refactor: linkage rules update * refactor: linkage rules button * refactor(linakge rules): suport value action * refactor(linakge rules): suport value action * fix(workflow test): workflow test * refactor(linakge rules): action linkage only for detail form * refactor(linakge rules): logic calculation * refactor(linakge rules): logic calculation * refactor(linakge rules): logic calculation * refactor(linakge rules): logic calculation * refactor(linakge rules): linkageAction * refactor(linakge rules): linkageAction * refactor(linakge rules): field value linkageAction * refactor(linakge rules): local * refactor(linakge rules): local * fix(linakge rules): value property * fix(linakge rules): value property * fix(linakge rules): value property * fix(linakge rules): conditionAnalyse * fix(linakge rules): conditionAnalyse * fix(linakge rules): conditionAnalyse * fix(linakge rules): conditionAnalyse form fields * fix(linakge rules): conditionAnalyse form fields * fix(linakge rules): conditionAnalyse operators * fix(linakge rules): form button linkage * fix(linakge rules): form button linkage * fix(linakge rules): select component suport pattern * fix(linakge rules): component suport field patter for disabled * fix(linakge rules): component suport field patter for disabled * fix(linakge rules): component suport field patter for disabled * fix(linakge rules): component suport field patter for disabled * fix(action): action desginer suport linkage * refactor: formula calculation assignment * refactor: formula calculation assignment * style: unified component disable style * feat: richText suport disabled * feat: condiction suport varible * feat: condiction suport varible * refactor: variableInput * refactor: condiction scope * refactor: multi level linkage * fix: empry value linkage * refactor: components using core schema-component * fix: fix template reference linkage * refactor: evalute foumala * fix: mutual linkage fix * refactor: variables suport obo&oho * style: style improve * style: style improve * style: style improve * feat: variables suport m2o * refactor: code improve * refactor: code improve * refactor: code improve * refactor: code improve * refactor: code improve * fix: actions path * refactor: code improve * refactor: code improve * refactor: code improve * refactor: code improve * refactor: code improve * refactor: code improve * refactor: code improve --------- Co-authored-by: mytharcher Co-authored-by: Junyi Co-authored-by: chenos --- packages/core/client/src/locale/en_US.ts | 15 + packages/core/client/src/locale/ja_JP.ts | 17 +- packages/core/client/src/locale/zh_CN.ts | 16 + .../antd/action/Action.Designer.tsx | 11 +- .../schema-component/antd/action/Action.tsx | 16 +- .../src/schema-component/antd/action/utils.ts | 42 +- .../antd/checkbox/Checkbox.tsx | 14 +- .../schema-component/antd/date-picker/util.ts | 2 +- .../antd/filter/FilterItem.tsx | 3 + .../antd/form-v2/Form.Designer.tsx | 1 + .../schema-component/antd/form-v2/Form.tsx | 67 ++- .../schema-component/antd/form-v2/utils.tsx | 109 ++++ .../antd/input-number/InputNumber.tsx | 7 +- .../src/schema-component/antd/input/Input.tsx | 5 +- .../antd/password/Password.tsx | 2 +- .../schema-component/antd/percent/Percent.tsx | 2 +- .../src/schema-component/antd/radio/Radio.tsx | 18 +- .../antd/record-picker/RecordPicker.tsx | 3 +- .../antd/rich-text/RichText.tsx | 11 +- .../antd/time-picker/TimePicker.tsx | 2 +- .../schema-component/antd/upload/Upload.tsx | 317 ++++++------ .../schema-component/antd/variable/Input.tsx | 207 ++++---- .../antd/variable/TextArea.tsx | 74 +-- .../schema-component/common/utils/logic.js | 487 ++++++++++++++++++ .../schema-component/common/utils/uitls.tsx | 60 +++ .../LinkageRules/DynamicComponent.tsx | 107 ++++ .../LinkageRules/FilterDynamicComponent.tsx | 13 + .../LinkageRules/LinkageRuleAction.tsx | 130 +++++ .../LinkageRules/LinkageRuleActionGroup.tsx | 52 ++ .../LinkageRules/ValueDynamicComponent.tsx | 50 ++ .../LinkageRules/Variables.tsx | 105 ++++ .../LinkageRules/action-hooks.ts | 61 +++ .../schema-settings/LinkageRules/context.ts | 15 + .../schema-settings/LinkageRules/index.tsx | 117 +++++ .../src/schema-settings/LinkageRules/type.ts | 13 + .../schema-settings/LinkageRules/useValues.ts | 59 +++ .../src/schema-settings/SchemaSettings.tsx | 74 ++- .../src/schema-templates/BlockTemplate.tsx | 5 +- .../src/client/formula/Result.tsx | 2 +- .../workflow/src/server/__tests__/index.ts | 12 +- 40 files changed, 1968 insertions(+), 355 deletions(-) create mode 100644 packages/core/client/src/schema-component/antd/form-v2/utils.tsx create mode 100644 packages/core/client/src/schema-component/common/utils/logic.js create mode 100644 packages/core/client/src/schema-component/common/utils/uitls.tsx create mode 100644 packages/core/client/src/schema-settings/LinkageRules/DynamicComponent.tsx create mode 100644 packages/core/client/src/schema-settings/LinkageRules/FilterDynamicComponent.tsx create mode 100644 packages/core/client/src/schema-settings/LinkageRules/LinkageRuleAction.tsx create mode 100644 packages/core/client/src/schema-settings/LinkageRules/LinkageRuleActionGroup.tsx create mode 100644 packages/core/client/src/schema-settings/LinkageRules/ValueDynamicComponent.tsx create mode 100644 packages/core/client/src/schema-settings/LinkageRules/Variables.tsx create mode 100644 packages/core/client/src/schema-settings/LinkageRules/action-hooks.ts create mode 100644 packages/core/client/src/schema-settings/LinkageRules/context.ts create mode 100644 packages/core/client/src/schema-settings/LinkageRules/index.tsx create mode 100644 packages/core/client/src/schema-settings/LinkageRules/type.ts create mode 100644 packages/core/client/src/schema-settings/LinkageRules/useValues.ts diff --git a/packages/core/client/src/locale/en_US.ts b/packages/core/client/src/locale/en_US.ts index 4b53ebab6..b097165a4 100644 --- a/packages/core/client/src/locale/en_US.ts +++ b/packages/core/client/src/locale/en_US.ts @@ -41,6 +41,21 @@ export default { "Collection category":"Collection category", "Sort":"Sort", "Categories":"Categories", + "Visible":"Visible", + "Read only":"Read only", + "Easy reading":"Easy reading", + "Hidden":"Hidden", + "Hidden(reserved value)":"Hidden(reserved value)", + "Not required":"Not required", + "Value":"Value", + "Disabled":"Disabled", + "Enabled":"Enabled", + "Linkage rule":"Linkage rule", + "Linkage rules":"Linkage rules", + "Condition":"Condition", + "Properties":"Properties", + "Add linkage rule":"Add linkage rule", + "Add property":"Add property", "Category name":"Category name", "Roles & Permissions": "Roles & Permissions", "Edit profile": "Edit profile", diff --git a/packages/core/client/src/locale/ja_JP.ts b/packages/core/client/src/locale/ja_JP.ts index 0256c325d..0ee613a1b 100644 --- a/packages/core/client/src/locale/ja_JP.ts +++ b/packages/core/client/src/locale/ja_JP.ts @@ -42,6 +42,21 @@ export default { "Category name":"分類名", "Delete category":"分類の削除", "Collection category":"Collection category", + "Visible":"表示", + "Read only":"読み取り専用(編集不可)", + "Easy reading":"読み取り専用(読取りモード)", + "Hidden":"非表示", + "Hidden(reserved value)":"非表示(値の保持)", + "Not required":"必須ではありません", + "Value":"フィールド値", + "Disabled":"無効化", + "Enabled":"有効化", + "Linkage rule":"連動規則", + "Linkage rules":"連動規則", + "Condition":"条件#ジョウケン#", + "Properties":"属性#ゾクセイ#", + "Add linkage rule":"連動規則の追加", + "Add property":"属性の追加", "Roles & Permissions": "役割と権限", "Edit profile": "プロフィール", "Change password": "パスワード変更", @@ -377,7 +392,7 @@ export default { "Role UID": "役割ID", "Precision": "精度", "Formula mode": "数式モード", - "Expression": "表示", + "Expression": "表达式", "Input +, -, *, /, ( ) to calculate, input @ to open field variables.": "+、-、*、/、( ) で算術演算、@でフィールド変数を開くことができます。", "Formula error.": "式の検証エラーです。", "Accept": "ファイル形式", diff --git a/packages/core/client/src/locale/zh_CN.ts b/packages/core/client/src/locale/zh_CN.ts index 1723168ed..048b56a36 100644 --- a/packages/core/client/src/locale/zh_CN.ts +++ b/packages/core/client/src/locale/zh_CN.ts @@ -39,6 +39,22 @@ export default { "Delete category":"删除分类", "Edit category":"编辑分类", "Collection category":"数据表类别", + "Visible":"显示", + "Read only":"只读(禁止编辑)", + "Easy reading":"只读(阅读模式)", + "Hidden":"隐藏", + "Hidden(reserved value)":"隐藏(保留值)", + "Not required":"非必填", + "Value":"字段值", + "Disabled":"禁用", + "Enabled":"启用", + "Linkage rule":"联动规则", + "Linkage rules":"联动规则", + "Condition":"条件", + "Properties":"属性", + "Add linkage rule":"添加联动规则", + "Add property":"添加属性", + "Expression":"表达式", "Sort":"排序", "Categories":"数据表类别", "Category name":"分类名称", diff --git a/packages/core/client/src/schema-component/antd/action/Action.Designer.tsx b/packages/core/client/src/schema-component/antd/action/Action.Designer.tsx index 732063837..478728c4f 100644 --- a/packages/core/client/src/schema-component/antd/action/Action.Designer.tsx +++ b/packages/core/client/src/schema-component/antd/action/Action.Designer.tsx @@ -3,8 +3,12 @@ import { isValid, uid } from '@formily/shared'; import { Menu, Select } from 'antd'; import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useActionContext, useCompile, useDesignable } from '../..'; +import { useDesignable } from '../..'; import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings'; +import { useCollection } from '../../../collection-manager'; +import { useRecord } from '../../../record-provider'; +import { useFormBlockContext } from '../../../block-provider/FormBlockProvider'; + import { requestSettingsSchema } from './utils'; const MenuGroup = (props) => { @@ -36,14 +40,14 @@ export const ActionDesigner = (props) => { const { modalTip, ...restProps } = props; const field = useField(); const fieldSchema = useFieldSchema(); + const { name } = useCollection(); const { dn } = useDesignable(); const { t } = useTranslation(); - const compile = useCompile(); const isPopupAction = ['create', 'update', 'view', 'customize:popup'].includes(fieldSchema['x-action'] || ''); const isUpdateModePopupAction = ['customize:bulkUpdate', 'customize:bulkEdit'].includes(fieldSchema['x-action']); - const context = useActionContext(); const [initialSchema, setInitialSchema] = useState(); const actionType = fieldSchema['x-action'] ?? ''; + const isLinkageAction = Object.keys(useFormBlockContext()).length > 0 && Object.keys(useRecord()).length > 0; useEffect(() => { const schemaUid = uid(); @@ -132,6 +136,7 @@ export const ActionDesigner = (props) => { } }} /> + {isLinkageAction && } {isPopupAction && ( { const fieldSchema = useFieldSchema(); const compile = useCompile(); const form = useForm(); + const values = useRecord(); const designerProps = fieldSchema['x-designer-props']; const openMode = fieldSchema?.['x-component-props']?.['openMode']; const disabled = form.disabled || field.disabled; const openSize = fieldSchema?.['x-component-props']?.['openSize']; + const linkageRules = fieldSchema?.['x-linkage-rules'] || []; + const { designable } = useDesignable(); + useEffect(() => { + linkageRules.map((v) => { + return v.actions?.map((h) => { + linkageAction(h.operator, field, v.condition, values, designable); + }); + }); + }, [linkageRules]); const renderButton = () => ( } disabled={disabled} + style={{ border: field?.data?.hidden && '1px dashed #ede9e9' }} onClick={(e: React.MouseEvent) => { if (!disabled) { e.preventDefault(); diff --git a/packages/core/client/src/schema-component/antd/action/utils.ts b/packages/core/client/src/schema-component/antd/action/utils.ts index 7d5407de3..20cbad989 100644 --- a/packages/core/client/src/schema-component/antd/action/utils.ts +++ b/packages/core/client/src/schema-component/antd/action/utils.ts @@ -1,5 +1,7 @@ import type { ISchema } from '@formily/react'; - +import { last } from 'lodash'; +import { conditionAnalyse } from '../../common/utils/uitls'; +import { ActionType } from '../../../schema-settings/LinkageRules/type'; const validateJSON = { validator: `{{(value, rule)=> { if (!value) { @@ -66,3 +68,41 @@ export const requestSettingsSchema: ISchema = { }, }, }; + +export const linkageAction = (operator, field, condition, values, designable) => { + const displayResult = [field.display]; + const disableResult = [field.disabled]; + switch (operator) { + case ActionType.Visible: + if (conditionAnalyse(condition, values)) { + displayResult.push(operator); + } + field.display = last(displayResult); + break; + case ActionType.Hidden: + if (conditionAnalyse(condition, values)) { + if (!designable) { + displayResult.push(operator); + } else { + field.data = field.data || {}; + field.data.hidden = true; + } + } + field.display = last(displayResult); + break; + case ActionType.Disabled: + if (conditionAnalyse(condition, values)) { + disableResult.push(true); + } + field.disabled = last(disableResult); + break; + case ActionType.Active: + if (conditionAnalyse(condition, values)) { + disableResult.push(false); + } + field.disabled = last(disableResult); + break; + default: + return null; + } +}; diff --git a/packages/core/client/src/schema-component/antd/checkbox/Checkbox.tsx b/packages/core/client/src/schema-component/antd/checkbox/Checkbox.tsx index fe7f1305f..c9cd46c9b 100644 --- a/packages/core/client/src/schema-component/antd/checkbox/Checkbox.tsx +++ b/packages/core/client/src/schema-component/antd/checkbox/Checkbox.tsx @@ -23,12 +23,6 @@ export const Checkbox: ComposedCheckbox = connect( value: 'checked', onInput: 'onChange', }, - (props, field) => { - // console.log({ props, field }); - return { - ...props, - }; - }, ), mapReadPretty((props) => { if (!isValid(props.value)) { @@ -45,9 +39,11 @@ Checkbox.__ANT_CHECKBOX = true; Checkbox.Group = connect( AntdCheckbox.Group, - mapProps({ - dataSource: 'options', - }), + mapProps( + { + dataSource: 'options', + }, + ), mapReadPretty((props) => { if (!isValid(props.value)) { return null; diff --git a/packages/core/client/src/schema-component/antd/date-picker/util.ts b/packages/core/client/src/schema-component/antd/date-picker/util.ts index 442d56501..76d5650c3 100644 --- a/packages/core/client/src/schema-component/antd/date-picker/util.ts +++ b/packages/core/client/src/schema-component/antd/date-picker/util.ts @@ -47,7 +47,7 @@ export const moment2str = (value?: moment.Moment | moment.Moment[], options: Mom }; export const mapDateFormat = function () { - return (props: any) => { + return (props: any, field) => { const format = getDefaultFormat(props) as any; const onChange = props.onChange; return { diff --git a/packages/core/client/src/schema-component/antd/filter/FilterItem.tsx b/packages/core/client/src/schema-component/antd/filter/FilterItem.tsx index ef0b6975e..0af7c87d3 100644 --- a/packages/core/client/src/schema-component/antd/filter/FilterItem.tsx +++ b/packages/core/client/src/schema-component/antd/filter/FilterItem.tsx @@ -35,6 +35,9 @@ export const FilterItem = observer((props: any) => { placeholder={t('Select Field')} /> { + setOperator(value); + }} + placeholder={t('action')} + /> + {[ActionType.Value].includes(operator) && ( + + )} + {!props.disabled && ( + + remove()} style={{ color: '#bfbfbf' }} /> + + )} + + + ); +}); + +export const FormButtonLinkageRuleAction = observer((props: any) => { + const { value, options } = props; + const { t } = useTranslation(); + const compile = useCompile(); + const [editFalg, setEditFlag] = useState(false); + const remove = useContext(RemoveActionContext); + const { schema, operator, setOperator, setValue } = useValues(options); + const operators = [ + { label: t('Visible'), value: ActionType.Visible, schema: {} }, + { label: t('Hidden'), value: ActionType.Hidden, schema: {} }, + { label: t('Disabled'), value: ActionType.Disabled, schema: {} }, + { label: t('Enabled'), value: ActionType.Active, schema: {} }, + ]; + return ( +
+ + setMode(value)}> + + + +
+ {mode === 'constant' ? ( + React.createElement(DynamicComponent, { + value: fieldValue?.value || fieldValue, + schema, + onChange(value) { + setValue({ + mode, + value, + }); + }, + }) + ) : ( + { + const result = value.replaceAll(`${collectionName}.`, '').replaceAll('$system.', '').trim(); + setValue({ + mode, + value, + result, + }); + }} + scope={scope} + style={{ minWidth: 460, marginRight: 15 }} + /> + )} +
+ + ); +}; diff --git a/packages/core/client/src/schema-settings/LinkageRules/Variables.tsx b/packages/core/client/src/schema-settings/LinkageRules/Variables.tsx new file mode 100644 index 000000000..0febb0e5b --- /dev/null +++ b/packages/core/client/src/schema-settings/LinkageRules/Variables.tsx @@ -0,0 +1,105 @@ +import { useCompile } from '../../schema-component'; +import { useCollectionManager, useCollection } from '../../collection-manager'; + +const supportsType = [ + 'checkbox', + 'number', + 'percent', + 'integer', + 'input', + 'textarea', + 'email', + 'phone', + 'datetime', + 'createdAt', + 'updatedAt', + 'radioGroup', + 'checkboxGroup', + 'select', + 'multipleSelect', + 'oho', + 'obo', + 'm2o' +]; +const VariableTypes = (currentCollection) => { + const { getCollectionFields, getInterface, getCollection } = useCollectionManager(); + const collection = getCollection(currentCollection); + const fields = getCollectionFields(currentCollection); + return [ + { + title: collection.title, + value: currentCollection, + options() { + const field2option = (field, depth) => { + if (!field.interface || !supportsType.includes(field.interface)) { + return; + } + const fieldInterface = getInterface(field.interface); + if (!fieldInterface.filterable) { + return; + } + const { nested, children } = fieldInterface.filterable; + const option = { + key: field.name, + label: field?.title || field.uiSchema?.title, + schema: field?.uiSchema, + value: field.name, + }; + if (field.target && depth > 2) { + return; + } + if (depth > 2) { + return option; + } + if (children?.length) { + option['children'] = children; + } + if (nested) { + const targetFields = getCollectionFields(field.target); + const options = getOptions(targetFields, depth + 1).filter(Boolean); + option['children'] = option['children'] || []; + option['children'].push(...options); + } + return option; + }; + const getOptions = (fields, depth) => { + const options = []; + fields.forEach((field) => { + const option = field2option(field, depth); + if (option) { + options.push(option); + } + }); + return options; + }; + return getOptions(fields, 1); + }, + }, + { + title: `{{t("System variables")}}`, + value: '$system', + options: [ + { + key: 'now', + value: 'now', + label: `{{t("Current time")}}`, + }, + ], + }, + ]; +}; + +export function useVariableOptions(collectionName) { + const compile = useCompile(); + const options = VariableTypes(collectionName).map((item: any) => { + const options = typeof item.options === 'function' ? item.options() : item.options; + return { + label: compile(item.title), + value: item.value, + key: item.value, + children: compile(options), + disabled: options && !options.length, + }; + }); + return options; +} diff --git a/packages/core/client/src/schema-settings/LinkageRules/action-hooks.ts b/packages/core/client/src/schema-settings/LinkageRules/action-hooks.ts new file mode 100644 index 000000000..4193348b5 --- /dev/null +++ b/packages/core/client/src/schema-settings/LinkageRules/action-hooks.ts @@ -0,0 +1,61 @@ +import { useTranslation } from 'react-i18next'; +import { useCollectionManager } from '../../collection-manager'; +import { ActionType } from './type'; + +export const useLinkageCollectionFieldOptions = (collectionName: string) => { + const { getCollectionFields, getInterface } = useCollectionManager(); + const fields = getCollectionFields(collectionName).filter((v) => { + return !['id', 'createdAt', 'createdBy', 'updatedAt', 'updatedBy'].includes(v.name); + }); + const { t } = useTranslation(); + const operators = [ + { label: t('Visible'), value: ActionType.Visible, selected: true, schema: {} }, + { label: t('Editable'), value: ActionType.Editable, selected: false, schema: {} }, + { label: t('Disabled'), value: ActionType.ReadOnly, selected: false, schema: {} }, + { label: t('Easy reading'), value: ActionType.ReadPretty, selected: false, schema: {} }, + { label: t('Hidden'), value: ActionType.None, selected: false, schema: {} }, + { label: t('Hidden(reserved value)'), value: ActionType.Hidden, selected: false, schema: {} }, + { label: t('Required'), value: ActionType.Required, selected: false, schema: {} }, + { label: t('Not required'), value: ActionType.InRequired, selected: false, schema: {} }, + { label: t('Value'), value: ActionType.Value, selected: false, schema: {} }, + ]; + const field2option = (field, depth) => { + const fieldInterface = getInterface(field.interface); + if (!fieldInterface) { + return; + } + const { nested, children } = fieldInterface?.filterable || {}; + const option = { + name: field.name, + title: field?.uiSchema?.title || field.name, + schema: field?.uiSchema, + interface: field.interface, + target: field.target, + operators: + operators?.filter?.((operator) => { + if (nested || children || ['formula', 'richText', 'sequence'].includes(fieldInterface.name)) { + return operator?.value !== ActionType.Value; + } + return true; + }) || [], + }; + if (field.target && depth > 2) { + return; + } + if (depth > 2) { + return option; + } + return option; + }; + const getOptions = (fields, depth) => { + const options = []; + fields.forEach((field) => { + const option = field2option(field, depth); + if (option) { + options.push(option); + } + }); + return options; + }; + return getOptions(fields, 1); +}; diff --git a/packages/core/client/src/schema-settings/LinkageRules/context.ts b/packages/core/client/src/schema-settings/LinkageRules/context.ts new file mode 100644 index 000000000..77349c388 --- /dev/null +++ b/packages/core/client/src/schema-settings/LinkageRules/context.ts @@ -0,0 +1,15 @@ +import { ObjectField } from '@formily/core'; +import { Schema } from '@formily/react'; +import { createContext } from 'react'; + +export interface FilterContextProps { + field?: ObjectField; + fieldSchema?: Schema; + dynamicComponent?: any; + options?: any[]; + disabled?: boolean +} + +export const RemoveActionContext = createContext(null); +export const FilterContext = createContext(null); +export const FilterLogicContext = createContext(null); \ No newline at end of file diff --git a/packages/core/client/src/schema-settings/LinkageRules/index.tsx b/packages/core/client/src/schema-settings/LinkageRules/index.tsx new file mode 100644 index 000000000..9cec347c3 --- /dev/null +++ b/packages/core/client/src/schema-settings/LinkageRules/index.tsx @@ -0,0 +1,117 @@ +import { css } from '@emotion/css'; +import { observer, useFieldSchema } from '@formily/react'; +import React from 'react'; +import { SchemaComponent } from '../../schema-component'; +import { FilterContext } from '../../schema-component/antd/filter/context'; +import { FilterDynamicComponent } from './FilterDynamicComponent'; +import { LinkageRuleActionGroup } from './LinkageRuleActionGroup'; + +export const FormLinkageRules = observer((props: any) => { + const fieldSchema = useFieldSchema(); + const { useProps, dynamicComponent } = props; + const { options, defaultValues, collectionName } = useProps(); + return ( + + FilterDynamicComponent({ ...props, collectionName }), + }, + }, + actions: { + 'x-component': 'h4', + 'x-content': '{{ t("Properties") }}', + }, + action: { + type: 'void', + 'x-component': LinkageRuleActionGroup, + 'x-component-props': { + ...props, + }, + }, + }, + }, + remove: { + type: 'void', + 'x-component': 'ArrayCollapse.Remove', + }, + moveUp: { + type: 'void', + 'x-component': 'ArrayCollapse.MoveUp', + }, + moveDown: { + type: 'void', + 'x-component': 'ArrayCollapse.MoveDown', + }, + }, + }, + properties: { + add: { + type: 'void', + title: '{{ t("Add linkage rule") }}', + 'x-component': 'ArrayCollapse.Addition', + 'x-reactions': { + dependencies: ['rules'], + fulfill: { + state: { + // disabled: '{{$deps[0].length >= 3}}', + }, + }, + }, + }, + }, + }, + }, + }} + /> + + ); +}); diff --git a/packages/core/client/src/schema-settings/LinkageRules/type.ts b/packages/core/client/src/schema-settings/LinkageRules/type.ts new file mode 100644 index 000000000..ab35cd5ad --- /dev/null +++ b/packages/core/client/src/schema-settings/LinkageRules/type.ts @@ -0,0 +1,13 @@ +export enum ActionType { + Visible = 'visible', + Editable = 'editable', + ReadOnly = 'disabled', + ReadPretty = 'readPretty', + None = 'none', + Hidden = 'hidden', + Required = 'required', + InRequired ='notRequired', + Disabled ='disabled', + Value = 'value', + Active = 'enabled', +} diff --git a/packages/core/client/src/schema-settings/LinkageRules/useValues.ts b/packages/core/client/src/schema-settings/LinkageRules/useValues.ts new file mode 100644 index 000000000..66ce3880a --- /dev/null +++ b/packages/core/client/src/schema-settings/LinkageRules/useValues.ts @@ -0,0 +1,59 @@ +import { useField } from '@formily/react'; +import { useContext, useEffect } from 'react'; +import { FilterLogicContext } from './context'; + +const findOption = (dataIndex = [], options) => { + let items = options; + let option; + dataIndex?.forEach?.((name, index) => { + const item = items.find((v) => v.name === name); + if (item) { + option = item; + } + items = item?.children || []; + }); + return option; +}; + +export const useValues = (options) => { + const field = useField(); + const logic = useContext(FilterLogicContext); + + const value2data = () => { + field.data = { ...field.initialValue }; + const dataIndex = field.initialValue?.targetFields; + const option = (dataIndex && findOption(dataIndex, options)) || {}; + const operators = option?.operators || []; + field.data.operators = operators; + field.data.schema = option?.schema; + }; + useEffect(value2data, [logic]); + return { + fields: options, + ...field.data, + setDataIndex(dataIndex) { + const option = findOption(dataIndex, options); + field.data = field.data || {}; + const operators = option?.operators; + field.data.operators = operators?.filter((v) => { + // if (dataIndex.length > 1) { + // return v.value !== 'value'; + // } + return true; + }); + field.data.schema = option?.schema; + field.value = field.value || []; + field.data.operator = undefined; + field.data.value = undefined; + field.value = { ...field.value, targetFields: dataIndex, operator: undefined, value: undefined }; + }, + setOperator(operatorValue) { + field.data.operator = operatorValue; + field.value = { ...field.value, operator: operatorValue }; + }, + setValue(value) { + field.data.value = value; + field.value = { ...field.value, value }; + }, + }; +}; diff --git a/packages/core/client/src/schema-settings/SchemaSettings.tsx b/packages/core/client/src/schema-settings/SchemaSettings.tsx index ea620f699..271e3ca90 100644 --- a/packages/core/client/src/schema-settings/SchemaSettings.tsx +++ b/packages/core/client/src/schema-settings/SchemaSettings.tsx @@ -1,7 +1,8 @@ import { css } from '@emotion/css'; -import { FormDialog, FormItem, FormLayout, Input } from '@formily/antd'; +import { FormDialog, FormItem, FormLayout, Input, ArrayCollapse } from '@formily/antd'; import { createForm, Field, GeneralField } from '@formily/core'; import { ISchema, Schema, SchemaOptionsContext, useField, useFieldSchema, useForm } from '@formily/react'; +import _ from 'lodash'; import { uid } from '@formily/shared'; import { Alert, @@ -14,7 +15,7 @@ import { Modal, Select, Space, - Switch + Switch, } from 'antd'; import classNames from 'classnames'; import { cloneDeep } from 'lodash'; @@ -35,10 +36,14 @@ import { useCollection, useCollectionManager, useCompile, - useDesignable + useDesignable, + useCollectionFilterOptions, } from '..'; import { useSchemaTemplateManager } from '../schema-templates'; import { useBlockTemplateContext } from '../schema-templates/BlockTemplate'; +import { FormLinkageRules } from './LinkageRules'; +import { useLinkageCollectionFieldOptions } from './LinkageRules/action-hooks'; + interface SchemaSettingsProps { title?: any; dn?: Designable; @@ -223,7 +228,7 @@ const findGridSchema = (fieldSchema) => { return fieldSchema.reduceProperties((buf, s) => { if (s['x-component'] === 'FormV2') { const f = s.reduceProperties((buf, s) => { - if (s['x-component'] === 'Grid') { + if (s['x-component'] === 'Grid' || s['x-component'] === 'BlockTemplate') { return s; } return buf; @@ -629,6 +634,7 @@ SchemaSettings.ModalItem = (props) => { onSubmit, asyncGetInitialValues, initialValues, + width, ...others } = props; const options = useContext(SchemaOptionsContext); @@ -641,7 +647,7 @@ SchemaSettings.ModalItem = (props) => { {...others} onClick={async () => { const values = asyncGetInitialValues ? await asyncGetInitialValues() : initialValues; - FormDialog(schema.title || title, () => { + FormDialog({ title: schema.title || title, width }, () => { return ( @@ -706,3 +712,61 @@ SchemaSettings.BlockTitleItem = () => { /> ); }; + +SchemaSettings.LinkageRules = (props) => { + const { collectionName } = props; + const fieldSchema = useFieldSchema(); + const { dn } = useDesignable(); + const { t } = useTranslation(); + const { getTemplateById } = useSchemaTemplateManager(); + const type = fieldSchema['x-component'] === 'Action' ? 'button' : 'field'; + const gridSchema = findGridSchema(fieldSchema) || fieldSchema; + return ( + { + const options = useCollectionFilterOptions(collectionName); + return { + options, + defaultValues: gridSchema?.['x-linkage-rules'] || fieldSchema?.['x-linkage-rules'], + type, + linkageOptions: useLinkageCollectionFieldOptions(collectionName), + collectionName, + }; + }, + }, + }, + }, + } as ISchema + } + onSubmit={(v) => { + const rules = []; + for (const rule of v.fieldReaction.rules) { + rules.push(_.pickBy(rule, _.identity)); + } + const templateId = gridSchema['x-component'] === 'BlockTemplate' && gridSchema['x-component-props'].templateId; + const uid = (templateId && getTemplateById(templateId).uid) || gridSchema['x-uid']; + const schema = { + ['x-uid']: uid, + }; + + gridSchema['x-linkage-rules'] = rules; + schema['x-linkage-rules'] = rules; + dn.emit('patch', { + schema, + }); + dn.refresh(); + }} + /> + ); +}; diff --git a/packages/core/client/src/schema-templates/BlockTemplate.tsx b/packages/core/client/src/schema-templates/BlockTemplate.tsx index 1c1cb19e4..22ec6bffc 100644 --- a/packages/core/client/src/schema-templates/BlockTemplate.tsx +++ b/packages/core/client/src/schema-templates/BlockTemplate.tsx @@ -18,9 +18,12 @@ export const BlockTemplate = observer((props: any) => { const { dn } = useDesignable(); const { t } = useTranslation(); const template = useMemo(() => getTemplateById(templateId), [templateId]); + const onSuccess = (data) => { + fieldSchema['x-linkage-rules'] = data?.data?.['x-linkage-rules'] || []; + }; return template ? ( - + ) : null; }); diff --git a/packages/plugins/formula-field/src/client/formula/Result.tsx b/packages/plugins/formula-field/src/client/formula/Result.tsx index 9ba5a84da..177129418 100644 --- a/packages/plugins/formula-field/src/client/formula/Result.tsx +++ b/packages/plugins/formula-field/src/client/formula/Result.tsx @@ -24,7 +24,7 @@ const ReadPretty = (props) => { const Input = (props) => { const { options } = props; - const { dataType, expression, engine = 'math.js' } = options; + const { dataType, expression, engine = 'math.js' } = options||{}; const { evaluate } = (evaluators as Registry).get(engine); const form = useForm(); const val = () => { diff --git a/packages/plugins/workflow/src/server/__tests__/index.ts b/packages/plugins/workflow/src/server/__tests__/index.ts index e30168c45..6d03cfd1e 100644 --- a/packages/plugins/workflow/src/server/__tests__/index.ts +++ b/packages/plugins/workflow/src/server/__tests__/index.ts @@ -7,7 +7,7 @@ import Plugin from '..'; import { JOB_STATUS } from '../constants'; export function sleep(ms: number) { - return new Promise((resolve) => { + return new Promise(resolve => { setTimeout(resolve, ms); }); } @@ -26,21 +26,21 @@ export async function getApp({ manual, ...options }: MockAppOptions = {}): Promi run(node, { result }, processor) { return { status: JOB_STATUS.RESOLVED, - result, + result }; - }, + } }, error: { run(node, input, processor) { throw new Error('definite error'); - }, + } }, 'prompt->error': { run(node, input, processor) { return { - status: JOB_STATUS.PENDING, + status: JOB_STATUS.PENDING }; }, resume(node, input, processor) { @@ -72,7 +72,7 @@ export async function getApp({ manual, ...options }: MockAppOptions = {}): Promi await app.load(); await app.db.import({ - directory: path.resolve(__dirname, './collections'), + directory: path.resolve(__dirname, './collections') }); try {