diff --git a/packages/core/client/src/block-provider/hooks/index.ts b/packages/core/client/src/block-provider/hooks/index.ts index 2083d99fd..bd8ecd7bf 100644 --- a/packages/core/client/src/block-provider/hooks/index.ts +++ b/packages/core/client/src/block-provider/hooks/index.ts @@ -1,9 +1,11 @@ import { useField, useFieldSchema, useForm } from '@formily/react'; -import { Modal } from 'antd'; +import { message, Modal } from 'antd'; import { useTranslation } from 'react-i18next'; import { useHistory } from 'react-router-dom'; import { useCollection } from '../../collection-manager'; +import { useRecord } from '../../record-provider'; import { useActionContext, useCompile } from '../../schema-component'; +import { useCurrentUserContext } from '../../user'; import { useBlockRequestContext, useFilterByTk } from '../BlockProvider'; import { useDetailsBlockContext } from '../DetailsBlockProvider'; import { TableFieldResource } from '../TableFieldProvider'; @@ -65,8 +67,8 @@ export const useCreateActionProps = () => { return { async onClick() { const fieldNames = fields.map((field) => field.name); - const skipValidator = actionSchema?.['x-action-settings']?.skipValidator; - const overwriteValues = actionSchema?.['x-action-settings']?.overwriteValues; + const { assignedValues, onSuccess, overwriteValues, skipValidator } = actionSchema?.['x-action-settings'] ?? {}; + if (!skipValidator) { await form.submit(); } @@ -107,28 +109,79 @@ export const useCreateActionProps = () => { values: { ...values, ...overwriteValues, + ...assignedValues, }, }); actionField.data.loading = false; __parent?.service?.refresh?.(); setVisible?.(false); - const onSuccess = actionSchema?.['x-action-settings']?.onSuccess; if (!onSuccess?.successMessage) { return; } - Modal.success({ - title: compile(onSuccess?.successMessage), - onOk: async () => { - await form.reset(); - if (onSuccess?.redirecting && onSuccess?.redirectTo) { - if (isURL(onSuccess.redirectTo)) { - window.location.href = onSuccess.redirectTo; - } else { - history.push(onSuccess.redirectTo); + if (onSuccess?.manualClose) { + Modal.success({ + title: compile(onSuccess?.successMessage), + onOk: async () => { + await form.reset(); + if (onSuccess?.redirecting && onSuccess?.redirectTo) { + if (isURL(onSuccess.redirectTo)) { + window.location.href = onSuccess.redirectTo; + } else { + history.push(onSuccess.redirectTo); + } } - } - }, + }, + }); + } else { + message.success(compile(onSuccess?.successMessage)); + } + }, + }; +}; + +export const useCustomizeUpdateActionProps = () => { + const { resource, __parent, service } = useBlockRequestContext(); + const filterByTk = useFilterByTk(); + const actionSchema = useFieldSchema(); + const currentRecord = useRecord(); + const ctx = useCurrentUserContext(); + const history = useHistory(); + const compile = useCompile(); + const form = useForm(); + + return { + async onClick() { + const { assignedValues, onSuccess, skipValidator } = actionSchema?.['x-action-settings'] ?? {}; + if (skipValidator === false) { + await form.submit(); + } + await resource.update({ + filterByTk, + values: { ...assignedValues }, }); + service?.refresh?.(); + if (!(resource instanceof TableFieldResource)) { + __parent?.service?.refresh?.(); + } + if (!onSuccess?.successMessage) { + return; + } + if (onSuccess?.manualClose) { + Modal.success({ + title: compile(onSuccess?.successMessage), + onOk: async () => { + if (onSuccess?.redirecting && onSuccess?.redirectTo) { + if (isURL(onSuccess.redirectTo)) { + window.location.href = onSuccess.redirectTo; + } else { + history.push(onSuccess.redirectTo); + } + } + }, + }); + } else { + message.success(compile(onSuccess?.successMessage)); + } }, }; }; @@ -145,8 +198,8 @@ export const useUpdateActionProps = () => { const actionField = useField(); return { async onClick() { - const skipValidator = actionSchema?.['x-action-settings']?.skipValidator; - const overwriteValues = actionSchema?.['x-action-settings']?.overwriteValues; + const { assignedValues, onSuccess, overwriteValues, skipValidator } = actionSchema?.['x-action-settings'] ?? {}; + if (!skipValidator) { await form.submit(); } @@ -196,6 +249,7 @@ export const useUpdateActionProps = () => { values: { ...values, ...overwriteValues, + ...assignedValues, }, }); actionField.data.loading = false; @@ -204,8 +258,10 @@ export const useUpdateActionProps = () => { __parent?.__parent?.service?.refresh?.(); } setVisible?.(false); - const onSuccess = actionSchema?.['x-action-settings']?.onSuccess; - if (onSuccess?.successMessage) { + if (!onSuccess?.successMessage) { + return; + } + if (onSuccess?.manualClose) { Modal.success({ title: compile(onSuccess?.successMessage), onOk: async () => { @@ -219,6 +275,8 @@ export const useUpdateActionProps = () => { } }, }); + } else { + message.success(compile(onSuccess?.successMessage)); } }, }; diff --git a/packages/core/client/src/collection-manager/CollectionField.tsx b/packages/core/client/src/collection-manager/CollectionField.tsx index ca1365033..42695cadc 100644 --- a/packages/core/client/src/collection-manager/CollectionField.tsx +++ b/packages/core/client/src/collection-manager/CollectionField.tsx @@ -9,6 +9,7 @@ import { useCollectionField } from './hooks'; // TODO: 初步适配 const InternalField: React.FC = (props) => { const field = useField(); + const fieldSchema = useFieldSchema(); const { name, interface: interfaceType, uiSchema } = useCollectionField(); const component = useComponent(uiSchema?.['x-component']); diff --git a/packages/core/client/src/locale/en_US.ts b/packages/core/client/src/locale/en_US.ts index bd7123038..97c5cd45a 100644 --- a/packages/core/client/src/locale/en_US.ts +++ b/packages/core/client/src/locale/en_US.ts @@ -288,4 +288,15 @@ export default { 'Unsaved changes': 'Unsaved changes', 'Are you sure you don\'t want to save?': 'Are you sure you don\'t want to save?', + + 'Popup': 'Popup', + 'Trigger workflow': 'Trigger workflow', + 'Request API': 'Request API', + 'Assign field values': 'Assign field values', + 'Popup close method': 'Popup close method', + 'Automatic close': 'Automatic close', + 'Manually close': 'Manually close', + 'After successful update': 'After successful update', + 'Save record': 'Save record', + 'After successful save': 'After successful save', } diff --git a/packages/core/client/src/locale/zh_CN.ts b/packages/core/client/src/locale/zh_CN.ts index eb0eeefc5..6a55f8938 100644 --- a/packages/core/client/src/locale/zh_CN.ts +++ b/packages/core/client/src/locale/zh_CN.ts @@ -365,7 +365,7 @@ export default { 'Tab name': '标签名称', 'Current record blocks': '当前数据区块', - 'Pop-up message': '弹窗提示消息', + 'Popup message': '弹窗提示消息', 'Delete role': '删除角色', @@ -487,4 +487,22 @@ export default { 'Unsaved changes': '未保存修改', 'Are you sure you don\'t want to save?': '你确定不保存修改吗?', 'Dragging': '拖拽中', + + 'Popup': '打开弹窗', + 'Trigger workflow': '触发工作流', + 'Request API': '请求API', + 'Assign field values': '字段赋值', + 'Constant value': '静态值', + 'Dynamic value': '动态值', + 'Current user': '当前用户', + 'Current record': '当前记录', + 'Popup close method': '弹窗关闭方式', + 'Automatic close': '自动关闭', + 'Manually close': '手动关闭', + 'After successful update': '更新成功后', + 'Save record': '保存数据', + 'Updated successfully': '更新成功', + 'After successful save': '保存成功后', + 'After clicking the custom button, the following field values will be assigned according to the following form.': '点击当前自定义按钮时,以下字段值将按照以下表单赋值。', + 'After clicking the custom button, The following fields of the current record will be saved according to the following form.': '点击当前自定义按钮时,当前数据以下字段将按照以下表单保存。', } 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 17fb099c9..f244e7b14 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 @@ -1,121 +1,154 @@ import { ISchema, useField, useFieldSchema } from '@formily/react'; -import React from 'react'; +import { isValid } from '@formily/shared'; +import { uid } from '@nocobase/utils'; +import { Menu } from 'antd'; +import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useDesignable } from '../..'; +import { useActionContext, useCompile, useDesignable } from '../..'; import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings'; +const MenuGroup = (props) => { + const fieldSchema = useFieldSchema(); + const actionType = fieldSchema['x-action'] || ''; + const { t } = useTranslation(); + const actionTitles = { + 'customize:popup': t('Popup'), + 'customize:update': t('Update record'), + 'customize:save': t('Save record'), + }; + if (!['customize:popup', 'customize:update', 'customize:save'].includes(actionType)) { + return <>{props.children}; + } + return ${actionTitles[actionType]}`}>{props.children}; +}; + export const ActionDesigner = (props) => { + const { modalTip, ...restProps } = props; const field = useField(); const fieldSchema = useFieldSchema(); const { dn } = useDesignable(); const { t } = useTranslation(); - const isPopupAction = ['create', 'update', 'view'].includes(fieldSchema['x-action'] || ''); + const compile = useCompile(); + const isPopupAction = ['create', 'update', 'view', 'customize:popup'].includes(fieldSchema['x-action'] || ''); + const context = useActionContext(); + const [initialSchema, setInitialSchema] = useState(); + const actionType = fieldSchema['x-action'] || ''; + + useEffect(() => { + const schemaUid = uid(); + const schema: ISchema = { + type: 'void', + 'x-uid': schemaUid, + 'x-component': 'Grid', + 'x-initializer': 'CustomFormItemInitializers', + }; + setInitialSchema(schema); + }, [field.address]); + + const tips = { + 'customize:update': t( + 'After clicking the custom button, The following fields of the current record will be saved according to the following form.', + ), + 'customize:save': t( + 'After clicking the custom button, The following fields of the current record will be saved according to the following form.', + ), + }; return ( - - { - if (title) { - fieldSchema.title = title; - field.title = title; - field.componentProps.icon = icon; - fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {}; - fieldSchema['x-component-props'].icon = icon; - dn.emit('patch', { - schema: { - ['x-uid']: fieldSchema['x-uid'], - title, - 'x-component-props': { - ...fieldSchema['x-component-props'], - }, - }, - }); - dn.refresh(); - } - }} - /> - {isPopupAction && ( - { - field.componentProps.openMode = value; - fieldSchema['x-component-props']['openMode'] = value; - dn.emit('patch', { - schema: { - 'x-uid': fieldSchema['x-uid'], - 'x-component-props': fieldSchema['x-component-props'], - }, - }); - dn.refresh(); - }} - /> - )} - {fieldSchema?.['x-action-settings'] && ( - { - fieldSchema['x-action-settings'].skipValidator = value; - dn.emit('patch', { - schema: { - ['x-uid']: fieldSchema['x-uid'], - 'x-action-settings': { - ...fieldSchema['x-action-settings'], - }, - }, - }); - }} - /> - )} - {fieldSchema?.['x-action-settings'] && ( + + { - try { - const values = JSON.parse(overwriteValues); - fieldSchema['x-action-settings'].overwriteValues = values; + onSubmit={({ title, icon }) => { + if (title) { + fieldSchema.title = title; + field.title = title; + field.componentProps.icon = icon; + fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {}; + fieldSchema['x-component-props'].icon = icon; + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + title, + 'x-component-props': { + ...fieldSchema['x-component-props'], + }, + }, + }); + dn.refresh(); + } + }} + /> + {isPopupAction && ( + { + field.componentProps.openMode = value; + fieldSchema['x-component-props']['openMode'] = value; + dn.emit('patch', { + schema: { + 'x-uid': fieldSchema['x-uid'], + 'x-component-props': fieldSchema['x-component-props'], + }, + }); + dn.refresh(); + }} + /> + )} + + {isValid(fieldSchema?.['x-action-settings']?.assignedValues) && ( + { + fieldSchema['x-action-settings']['assignedValues'] = assignedValues; + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-action-settings': fieldSchema['x-action-settings'], + }, + }); + dn.refresh(); + }} + /> + )} + {isValid(fieldSchema?.['x-action-settings']?.skipValidator) && ( + { + fieldSchema['x-action-settings'].skipValidator = value; dn.emit('patch', { schema: { ['x-uid']: fieldSchema['x-uid'], @@ -124,76 +157,123 @@ export const ActionDesigner = (props) => { }, }, }); - dn.refresh(); - } catch (e) {} - }} - /> - )} - {fieldSchema?.['x-action-settings'] && ( - + )} + {isValid(fieldSchema?.['x-action-settings']?.overwriteValues) && ( + { + try { + const values = JSON.parse(overwriteValues); + fieldSchema['x-action-settings'].overwriteValues = values; + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-action-settings': { + ...fieldSchema['x-action-settings'], + }, + }, + }); + dn.refresh(); + } catch (e) {} + }} + /> + )} + {isValid(fieldSchema?.['x-action-settings']?.['onSuccess']) && ( + { + fieldSchema['x-action-settings']['onSuccess'] = onSuccess; + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-action-settings': fieldSchema['x-action-settings'], }, - }, - } as ISchema - } - onSubmit={(onSuccess) => { - fieldSchema['x-action-settings']['onSuccess'] = onSuccess; - dn.emit('patch', { - schema: { - ['x-uid']: fieldSchema['x-uid'], - 'x-action-settings': fieldSchema['x-action-settings'], - }, - }); + }); + }} + /> + )} + + { + return s['x-component'] === 'Space' || s['x-component'].endsWith('ActionBar'); + }} + confirm={{ + title: t('Delete action'), }} /> - )} - - { - return s['x-component'] === 'Space' || s['x-component'].endsWith('ActionBar'); - }} - confirm={{ - title: t('Delete action') - }} - /> + ); }; diff --git a/packages/core/client/src/schema-component/antd/action/hooks.ts b/packages/core/client/src/schema-component/antd/action/hooks.ts index 0df389ca7..45d3220a3 100644 --- a/packages/core/client/src/schema-component/antd/action/hooks.ts +++ b/packages/core/client/src/schema-component/antd/action/hooks.ts @@ -25,11 +25,11 @@ export const useActionContext = () => { content: t("Are you sure you don't want to save?"), async onOk() { ctx.setFormValueChanged(false); - ctx.setVisible(false); + ctx.setVisible?.(false); }, }); } else { - ctx.setVisible(false); + ctx?.setVisible?.(false); } } else { ctx?.setVisible?.(visible); diff --git a/packages/core/client/src/schema-component/antd/markdown/Markdown.Void.tsx b/packages/core/client/src/schema-component/antd/markdown/Markdown.Void.tsx index 0b6ff9d5b..7d9997670 100644 --- a/packages/core/client/src/schema-component/antd/markdown/Markdown.Void.tsx +++ b/packages/core/client/src/schema-component/antd/markdown/Markdown.Void.tsx @@ -46,7 +46,7 @@ export const MarkdownVoid: any = observer((props: any) => { const schema = useFieldSchema(); const { dn } = useDesignable(); const { onSave, onCancel } = props; - return field.editable ? ( + return field?.editable ? ( const { initializers, components, children } = props; return ( - {children} + + {children} + ); }; diff --git a/packages/core/client/src/schema-initializer/buttons/CustomFormItemInitializers.tsx b/packages/core/client/src/schema-initializer/buttons/CustomFormItemInitializers.tsx new file mode 100644 index 000000000..81cf42777 --- /dev/null +++ b/packages/core/client/src/schema-initializer/buttons/CustomFormItemInitializers.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { SchemaInitializer } from '../SchemaInitializer'; +import { gridRowColWrap, useCustomFormItemInitializerFields } from '../utils'; + +// 表单里配置字段 +export const CustomFormItemInitializers = (props: any) => { + const { t } = useTranslation(); + const { insertPosition, component } = props; + return ( + + ); +}; diff --git a/packages/core/client/src/schema-initializer/buttons/FormActionInitializers.tsx b/packages/core/client/src/schema-initializer/buttons/FormActionInitializers.tsx index e646f4513..f10df8bc1 100644 --- a/packages/core/client/src/schema-initializer/buttons/FormActionInitializers.tsx +++ b/packages/core/client/src/schema-initializer/buttons/FormActionInitializers.tsx @@ -1,4 +1,3 @@ - // 表单的操作配置 export const FormActionInitializers = { title: '{{t("Configure actions")}}', @@ -13,12 +12,7 @@ export const FormActionInitializers = { title: '{{t("Submit")}}', component: 'CreateSubmitActionInitializer', schema: { - 'x-action-settings': { - initialValues: {}, - onSuccess: { - successMessage: '{{t("Submitted successfully")}}', - }, - }, + 'x-action-settings': {}, }, }, ], @@ -32,16 +26,74 @@ export const FormActionInitializers = { children: [ { type: 'item', - title: '{{t("Save action")}}', + title: '{{t("Popup")}}', component: 'CustomizeActionInitializer', schema: { - title: '{{ t("Save") }}', + type: 'void', + title: '{{ t("Popup") }}', + 'x-action': 'customize:popup', + 'x-designer': 'Action.Designer', + 'x-component': 'Action', + 'x-component-props': { + openMode: 'drawer', + }, + properties: { + drawer: { + type: 'void', + title: '{{ t("Popup") }}', + 'x-component': 'Action.Container', + 'x-component-props': { + className: 'nb-action-popup', + }, + properties: { + tabs: { + type: 'void', + 'x-component': 'Tabs', + 'x-component-props': {}, + 'x-initializer': 'TabPaneInitializers', + properties: { + tab1: { + type: 'void', + title: '{{t("Details")}}', + 'x-component': 'Tabs.TabPane', + 'x-designer': 'Tabs.Designer', + 'x-component-props': {}, + properties: { + grid: { + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'RecordBlockInitializers', + properties: {}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + type: 'item', + title: '{{t("Save record")}}', + component: 'CustomizeActionInitializer', + schema: { + title: '{{ t("Save record") }}', + 'x-action': 'customize:save', 'x-component': 'Action', 'x-designer': 'Action.Designer', + 'x-designer-props': { + modalTip: + '{{ t("When the button is clicked, the following fields will be assigned and saved together with the fields in the form. If there are overlapping fields, the value here will overwrite the value in the form.") }}', + }, 'x-action-settings': { - initialValues: {}, + assignedValues: {}, + skipValidator: false, onSuccess: { - successMessage: '{{t("Submitted successfully")}}', + manualClose: true, + redirecting: false, + successMessage: '{{t("Saved successfully")}}', }, }, 'x-component-props': { @@ -67,9 +119,7 @@ export const CreateFormActionInitializers = { title: '{{t("Submit")}}', component: 'CreateSubmitActionInitializer', schema: { - 'x-action-settings': { - overwriteValues: {}, - }, + 'x-action-settings': {}, }, }, ], @@ -83,14 +133,75 @@ export const CreateFormActionInitializers = { children: [ { type: 'item', - title: '{{t("Save action")}}', + title: '{{t("Popup")}}', component: 'CustomizeActionInitializer', schema: { - title: '{{ t("Save") }}', + type: 'void', + title: '{{ t("Popup") }}', + 'x-action': 'customize:popup', + 'x-designer': 'Action.Designer', + 'x-component': 'Action', + 'x-component-props': { + openMode: 'drawer', + }, + properties: { + drawer: { + type: 'void', + title: '{{ t("Popup") }}', + 'x-component': 'Action.Container', + 'x-component-props': { + className: 'nb-action-popup', + }, + properties: { + tabs: { + type: 'void', + 'x-component': 'Tabs', + 'x-component-props': {}, + 'x-initializer': 'TabPaneInitializers', + properties: { + tab1: { + type: 'void', + title: '{{t("Details")}}', + 'x-component': 'Tabs.TabPane', + 'x-designer': 'Tabs.Designer', + 'x-component-props': {}, + properties: { + grid: { + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'RecordBlockInitializers', + properties: {}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + type: 'item', + title: '{{t("Save record")}}', + component: 'CustomizeActionInitializer', + schema: { + title: '{{ t("Save record") }}', + 'x-action': 'customize:save', 'x-component': 'Action', 'x-designer': 'Action.Designer', + 'x-designer-props': { + modalTip: + '{{ t("When the button is clicked, the following fields will be assigned and saved together with the fields in the form. If there are overlapping fields, the value here will overwrite the value in the form.") }}', + }, 'x-action-settings': { - overwriteValues: {}, + assignedValues: {}, + skipValidator: false, + onSuccess: { + manualClose: true, + redirecting: false, + successMessage: '{{t("Saved successfully")}}', + }, }, 'x-component-props': { useProps: '{{ useCreateActionProps }}', @@ -115,9 +226,7 @@ export const UpdateFormActionInitializers = { title: '{{t("Submit")}}', component: 'UpdateSubmitActionInitializer', schema: { - 'x-action-settings': { - overwriteValues: {}, - }, + 'x-action-settings': {}, }, }, ], @@ -131,14 +240,75 @@ export const UpdateFormActionInitializers = { children: [ { type: 'item', - title: '{{t("Save action")}}', + title: '{{t("Popup")}}', + component: 'CustomizeActionInitializer', + schema: { + type: 'void', + title: '{{ t("Popup") }}', + 'x-action': 'customize:popup', + 'x-designer': 'Action.Designer', + 'x-component': 'Action', + 'x-component-props': { + openMode: 'drawer', + }, + properties: { + drawer: { + type: 'void', + title: '{{ t("Popup") }}', + 'x-component': 'Action.Container', + 'x-component-props': { + className: 'nb-action-popup', + }, + properties: { + tabs: { + type: 'void', + 'x-component': 'Tabs', + 'x-component-props': {}, + 'x-initializer': 'TabPaneInitializers', + properties: { + tab1: { + type: 'void', + title: '{{t("Details")}}', + 'x-component': 'Tabs.TabPane', + 'x-designer': 'Tabs.Designer', + 'x-component-props': {}, + properties: { + grid: { + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'RecordBlockInitializers', + properties: {}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + type: 'item', + title: '{{t("Save record")}}', component: 'CustomizeActionInitializer', schema: { title: '{{ t("Save") }}', 'x-component': 'Action', + 'x-action': 'customize:save', 'x-designer': 'Action.Designer', + 'x-designer-props': { + modalTip: + '{{ t("When the button is clicked, the following fields will be assigned and saved together with the fields in the form. If there are overlapping fields, the value here will overwrite the value in the form.") }}', + }, 'x-action-settings': { - overwriteValues: {}, + assignedValues: {}, + skipValidator: false, + onSuccess: { + manualClose: true, + redirecting: false, + successMessage: '{{t("Saved successfully")}}', + }, }, 'x-component-props': { useProps: '{{ useUpdateActionProps }}', diff --git a/packages/core/client/src/schema-initializer/buttons/ReadPrettyFormActionInitializers.tsx b/packages/core/client/src/schema-initializer/buttons/ReadPrettyFormActionInitializers.tsx index f894cc19d..be4e0c194 100644 --- a/packages/core/client/src/schema-initializer/buttons/ReadPrettyFormActionInitializers.tsx +++ b/packages/core/client/src/schema-initializer/buttons/ReadPrettyFormActionInitializers.tsx @@ -33,5 +33,87 @@ export const ReadPrettyFormActionInitializers = { }, ], }, + + { + type: 'divider', + }, + { + type: 'subMenu', + title: '{{t("Customize")}}', + children: [ + { + type: 'item', + title: '{{t("Popup")}}', + component: 'CustomizeActionInitializer', + schema: { + type: 'void', + title: '{{ t("Popup") }}', + 'x-action': 'customize:popup', + 'x-designer': 'Action.Designer', + 'x-component': 'Action', + 'x-component-props': { + openMode: 'drawer', + }, + properties: { + drawer: { + type: 'void', + title: '{{ t("Popup") }}', + 'x-component': 'Action.Container', + 'x-component-props': { + className: 'nb-action-popup', + }, + properties: { + tabs: { + type: 'void', + 'x-component': 'Tabs', + 'x-component-props': {}, + 'x-initializer': 'TabPaneInitializers', + properties: { + tab1: { + type: 'void', + title: '{{t("Details")}}', + 'x-component': 'Tabs.TabPane', + 'x-designer': 'Tabs.Designer', + 'x-component-props': {}, + properties: { + grid: { + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'RecordBlockInitializers', + properties: {}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + type: 'item', + title: '{{t("Update record")}}', + component: 'CustomizeActionInitializer', + schema: { + title: '{{ t("Update record") }}', + 'x-component': 'Action', + 'x-designer': 'Action.Designer', + 'x-action': 'customize:update', + 'x-action-settings': { + assignedValues: {}, + onSuccess: { + manualClose: true, + redirecting: false, + successMessage: '{{t("Updated successfully")}}', + }, + }, + 'x-component-props': { + useProps: '{{ useCustomizeUpdateActionProps }}', + }, + }, + }, + ], + }, ], }; diff --git a/packages/core/client/src/schema-initializer/buttons/TableActionColumnInitializers.tsx b/packages/core/client/src/schema-initializer/buttons/TableActionColumnInitializers.tsx index 90d8d88c8..51fc7f9f7 100644 --- a/packages/core/client/src/schema-initializer/buttons/TableActionColumnInitializers.tsx +++ b/packages/core/client/src/schema-initializer/buttons/TableActionColumnInitializers.tsx @@ -70,6 +70,87 @@ export const TableActionColumnInitializers = (props: any) => { }, ], }, + { + type: 'divider', + }, + { + type: 'subMenu', + title: '{{t("Customize")}}', + children: [ + { + type: 'item', + title: '{{t("Popup")}}', + component: 'CustomizeActionInitializer', + schema: { + type: 'void', + title: '{{ t("Popup") }}', + 'x-action': 'customize:popup', + 'x-designer': 'Action.Designer', + 'x-component': 'Action.Link', + 'x-component-props': { + openMode: 'drawer', + }, + properties: { + drawer: { + type: 'void', + title: '{{ t("Popup") }}', + 'x-component': 'Action.Container', + 'x-component-props': { + className: 'nb-action-popup', + }, + properties: { + tabs: { + type: 'void', + 'x-component': 'Tabs', + 'x-component-props': {}, + 'x-initializer': 'TabPaneInitializers', + properties: { + tab1: { + type: 'void', + title: '{{t("Details")}}', + 'x-component': 'Tabs.TabPane', + 'x-designer': 'Tabs.Designer', + 'x-component-props': {}, + properties: { + grid: { + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'RecordBlockInitializers', + properties: {}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + type: 'item', + title: '{{t("Update record")}}', + component: 'CustomizeActionInitializer', + schema: { + title: '{{ t("Update record") }}', + 'x-component': 'Action.Link', + 'x-action': 'customize:update', + 'x-designer': 'Action.Designer', + 'x-action-settings': { + assignedValues: {}, + onSuccess: { + manualClose: true, + redirecting: false, + successMessage: '{{t("Updated successfully")}}', + }, + }, + 'x-component-props': { + useProps: '{{ useCustomizeUpdateActionProps }}', + }, + }, + }, + ], + }, ]} component={} /> diff --git a/packages/core/client/src/schema-initializer/buttons/index.ts b/packages/core/client/src/schema-initializer/buttons/index.ts index 16415165a..1dd66cda4 100644 --- a/packages/core/client/src/schema-initializer/buttons/index.ts +++ b/packages/core/client/src/schema-initializer/buttons/index.ts @@ -1,6 +1,7 @@ export * from './BlockInitializers'; export * from './CalendarActionInitializers'; export * from './CreateFormBlockInitializers'; +export * from './CustomFormItemInitializers'; export * from './DetailsActionInitializers'; export * from './FormActionInitializers'; export * from './FormItemInitializers'; @@ -15,4 +16,3 @@ export * from './TableActionInitializers'; export * from './TableColumnInitializers'; export * from './TableSelectorInitializers'; export * from './TabPaneInitializers'; - diff --git a/packages/core/client/src/schema-initializer/components/assigned-field/AssignedField.tsx b/packages/core/client/src/schema-initializer/components/assigned-field/AssignedField.tsx new file mode 100644 index 000000000..5f5601a04 --- /dev/null +++ b/packages/core/client/src/schema-initializer/components/assigned-field/AssignedField.tsx @@ -0,0 +1,70 @@ +import { Field } from '@formily/core'; +import { useField, useFieldSchema } from '@formily/react'; +import React, { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { CollectionField, useCollection } from '../../../collection-manager'; +import { useCompile, useFilterOptions } from '../../../schema-component'; + +export const AssignedField = (props: any) => { + const { t } = useTranslation(); + const compile = useCompile(); + const field = useField(); + const fieldSchema = useFieldSchema(); + const [type, setType] = useState('constantValue'); + const [value, setValue] = useState(field?.value?.value ?? ''); + const [options, setOptions] = useState([]); + const { getField } = useCollection(); + const collectionField = getField(fieldSchema.name); + const { uiSchema } = collectionField; + const currentUser = useFilterOptions('users'); + const currentRecord = useFilterOptions(collectionField.collectionName); + + useEffect(() => { + const opt = [ + { + name: 'currentUser', + title: t('Current user'), + children: [...currentUser], + }, + { + name: 'currentRecord', + title: t('Current record'), + children: [...currentRecord], + }, + ]; + setOptions(compile(opt)); + }, []); + + const valueChangeHandler = (val) => { + setValue(val); + }; + + return ; + + // return ( + // + // + + // {type === 'constantValue' ? ( + // + // ) : ( + // + // )} + // + // ); +}; diff --git a/packages/core/client/src/schema-initializer/components/assigned-field/index.ts b/packages/core/client/src/schema-initializer/components/assigned-field/index.ts new file mode 100644 index 000000000..ffbb3e51d --- /dev/null +++ b/packages/core/client/src/schema-initializer/components/assigned-field/index.ts @@ -0,0 +1 @@ +export * from './AssignedField'; diff --git a/packages/core/client/src/schema-initializer/components/index.ts b/packages/core/client/src/schema-initializer/components/index.ts new file mode 100644 index 000000000..f7d836316 --- /dev/null +++ b/packages/core/client/src/schema-initializer/components/index.ts @@ -0,0 +1 @@ +export * from './assigned-field'; diff --git a/packages/core/client/src/schema-initializer/items/index.tsx b/packages/core/client/src/schema-initializer/items/index.tsx index 442c69bed..7995db6ca 100644 --- a/packages/core/client/src/schema-initializer/items/index.tsx +++ b/packages/core/client/src/schema-initializer/items/index.tsx @@ -20,7 +20,7 @@ import { createTableSelectorSchema, useCollectionDataSourceItems, useCurrentSchema, - useRecordCollectionDataSourceItems + useRecordCollectionDataSourceItems, } from '../utils'; // Block diff --git a/packages/core/client/src/schema-initializer/utils.ts b/packages/core/client/src/schema-initializer/utils.ts index 65760838b..e039f468b 100644 --- a/packages/core/client/src/schema-initializer/utils.ts +++ b/packages/core/client/src/schema-initializer/utils.ts @@ -39,6 +39,19 @@ export const removeGridFormItem = (schema, cb) => { }); }; +export const useRemoveGridFormItem = () => { + const form = useForm(); + return (schema, cb) => { + cb(schema, { + removeParentsIfNoChildren: true, + breakRemoveOn: { + 'x-component': 'Grid', + }, + }); + delete form.values?.[schema.name]; + }; +}; + export const findTableColumn = (schema: Schema, key: string, action: string, deepth: number = 0) => { return schema.reduceProperties((buf, s) => { if (s[key] === action) { @@ -92,6 +105,7 @@ export const useFormItemInitializerFields = (options?: any) => { const { getInterface } = useCollectionManager(); const form = useForm(); const { readPretty = form.readPretty, block = 'Form' } = options || {}; + return fields ?.filter((field) => field?.interface) ?.map((field) => { @@ -119,6 +133,40 @@ export const useFormItemInitializerFields = (options?: any) => { }); }; +export const useCustomFormItemInitializerFields = (options?: any) => { + const { name, fields } = useCollection(); + const { getInterface } = useCollectionManager(); + const form = useForm(); + const { readPretty = form.readPretty, block = 'Form' } = options || {}; + const remove = useRemoveGridFormItem(); + return fields + ?.filter((field) => { + return field?.interface && !field?.uiSchema?.['x-read-pretty']; + }) + ?.map((field) => { + const interfaceConfig = getInterface(field.interface); + const schema = { + type: 'string', + name: field.name, + title: field?.uiSchema?.title || field.name, + 'x-designer': 'FormItem.Designer', + 'x-component': 'AssignedField', + 'x-decorator': 'FormItem', + 'x-collection-field': `${name}.${field.name}`, + }; + return { + type: 'item', + title: field?.uiSchema?.title || field.name, + component: 'CollectionFieldInitializer', + remove: remove, + schemaInitialize: (s) => { + interfaceConfig?.schemaInitialize?.(s, { field, block, readPretty }); + }, + schema, + } as SchemaInitializerItemOptions; + }); +}; + const findSchema = (schema: Schema, key: string, action: string) => { return schema.reduceProperties((buf, s) => { if (s[key] === action) { diff --git a/packages/core/client/src/schema-settings/SchemaSettings.tsx b/packages/core/client/src/schema-settings/SchemaSettings.tsx index a1461db0a..9170f3d26 100644 --- a/packages/core/client/src/schema-settings/SchemaSettings.tsx +++ b/packages/core/client/src/schema-settings/SchemaSettings.tsx @@ -1,25 +1,28 @@ import { css } from '@emotion/css'; import { FormDialog, FormItem, FormLayout, Input } from '@formily/antd'; -import { GeneralField } from '@formily/core'; -import { ISchema, Schema, SchemaOptionsContext } from '@formily/react'; +import { createForm, GeneralField } from '@formily/core'; +import { ISchema, Schema, SchemaOptionsContext, useFieldSchema } from '@formily/react'; import { uid } from '@formily/shared'; -import { Dropdown, Menu, MenuItemProps, Modal, Select, Switch } from 'antd'; +import { Alert, Button, Dropdown, Menu, MenuItemProps, Modal, Select, Space, Switch } from 'antd'; import classNames from 'classnames'; -import React, { createContext, useContext, useState } from 'react'; +import { cloneDeep } from 'lodash'; +import React, { createContext, useContext, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { ActionContext, createDesignable, Designable, + FormProvider, + RemoteSchemaComponent, SchemaComponent, SchemaComponentOptions, useActionContext, useAPIClient, - useCollection + useCollection, + useCompile } from '..'; import { useSchemaTemplateManager } from '../schema-templates'; import { useBlockTemplateContext } from '../schema-templates/BlockTemplate'; - interface SchemaSettingsProps { title?: any; dn?: Designable; @@ -462,6 +465,83 @@ SchemaSettings.PopupItem = (props) => { ); }; +SchemaSettings.ActionModalItem = React.memo((props: any) => { + const { title, onSubmit, initialValues, initialSchema, modalTip, ...others } = props; + const [visible, setVisible] = useState(false); + const [schemaUid, setSchemaUid] = useState(props.uid); + const { t } = useTranslation(); + const fieldSchema = useFieldSchema(); + const ctx = useContext(SchemaSettingsContext); + const { dn } = useSchemaSettings(); + const compile = useCompile(); + const api = useAPIClient(); + const form = useMemo( + () => + createForm({ + initialValues: cloneDeep(initialValues), + }), + [], + ); + + const cancelHandler = () => { + setVisible(false); + }; + const submitHandler = () => { + onSubmit?.(cloneDeep(form.values)); + setVisible(false); + }; + + const openAssignedFieldValueHandler = async () => { + if (!schemaUid && initialSchema['x-uid']) { + fieldSchema['x-action-settings'].schemaUid = initialSchema['x-uid']; + dn.emit('patch', { schema: fieldSchema }); + await api.resource('uiSchemas').insert({ values: initialSchema }); + setSchemaUid(initialSchema['x-uid']); + } + + ctx.setVisible(false); + setVisible(true); + }; + + return ( +
{ + e.preventDefault(); + e.stopPropagation(); + }} + > + + {props.children || props.title} + + + + + + + } + > + + + {modalTip && } + {modalTip &&
} + {visible && } +
+
+
+
+ ); +}); + SchemaSettings.ModalItem = (props) => { const { hidden, title, components, scope, effects, schema, onSubmit, initialValues, ...others } = props; const options = useContext(SchemaOptionsContext); diff --git a/packages/core/client/src/settings-form/SettingsForm.tsx b/packages/core/client/src/settings-form/SettingsForm.tsx index 65bc3bf4c..5d46a7f73 100644 --- a/packages/core/client/src/settings-form/SettingsForm.tsx +++ b/packages/core/client/src/settings-form/SettingsForm.tsx @@ -202,7 +202,7 @@ SettingsForm.Drawer = () => { style={{ width: 200 }} onClick={async () => { setDropdownVisible(false); - const values = await FormDrawer('Pop-up form', () => { + const values = await FormDrawer('Popup form', () => { return (