From a298f4ddf069b471f74c53f4b136af4c20bc1072 Mon Sep 17 00:00:00 2001 From: "bai.zixv" Date: Tue, 28 May 2024 09:52:08 +0800 Subject: [PATCH] feat/workflow-notice (#1037) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ![image](/attachments/9c701f22-f743-40a4-895c-d40fb170a3a3) ![image](/attachments/04e3e2c6-0545-4230-b7e5-fd0d00add727) 1. 可能有一些遗留问题, 比如工作流状态同步显示的问题, 以及后续需要配置. 2. 目前没有时间仔细核对, 需要先做其他事情 Reviewed-on: https://git.daoyoucloud.com/daoyoucloud/tachybase/pulls/1037 Co-authored-by: bai.zixv Co-committed-by: bai.zixv --- .../trigger/Approval.trigger.tsx | 5 - .../approval-block/ApprovalBlock.provider.tsx | 3 +- .../plugin-workflow-notice/.npmignore | 2 + .../plugin-workflow-notice/README.md | 1 + .../plugin-workflow-notice/client.d.ts | 2 + .../plugin-workflow-notice/client.js | 1 + .../plugin-workflow-notice/package.json | 34 +++ .../plugin-workflow-notice/server.d.ts | 2 + .../plugin-workflow-notice/server.js | 1 + .../src/client/common/flowNodes.collection.ts | 42 +++ .../src/client/common/notice.collection.ts | 144 +++++++++ .../src/client/common/workflow.collection.ts | 39 +++ .../configuration/Notice.instruction.ts | 95 ++++++ .../config-items/AddNotifiedPerson.view.tsx | 58 ++++ .../config-items/ConfigButtonNotice.view.tsx | 26 ++ .../SelectNotifiedPerson.view.tsx | 62 ++++ .../src/client/configuration/kit.ts | 24 ++ .../show-interface/NoticeDetail.block.tsx | 45 +++ .../NoticeDetail.initializer.tsx | 78 +++++ .../show-interface/NoticeDetail.schema.tsx | 86 ++++++ .../NoticeDetailContainer.schema.tsx | 43 +++ .../configuration/show-interface/style.ts | 9 + .../src/client/configuration/style.ts | 26 ++ .../src/client/index.tsx | 13 + .../src/client/locale.ts | 21 ++ .../client/usage/NoticeBlock.initializer.tsx | 66 +++++ .../src/client/usage/NoticeBlock.provider.tsx | 45 +++ .../src/client/usage/NoticeCenter.schema.tsx | 199 +++++++++++++ .../src/client/usage/column/column.action.tsx | 42 +++ .../src/client/usage/column/column.node.tsx | 17 ++ .../src/client/usage/column/column.status.tsx | 49 ++++ .../src/client/usage/column/column.user.tsx | 16 + .../client/usage/column/column.workflow.tsx | 28 ++ .../src/client/usage/kit.ts | 25 ++ .../show-detail/NoticeDetail.provider.tsx | 78 +++++ .../usage/show-detail/NoticeDetail.schema.tsx | 77 +++++ .../contexts/MyComponent.context.tsx | 13 + .../contexts/WorkflowNotice.context.tsx | 10 + .../show-detail/hooks/usePropsNoticeDetail.ts | 15 + .../src/common/constants.ts | 2 + .../plugin-workflow-notice/src/index.ts | 2 + .../src/locale/en-US.json | 17 ++ .../src/locale/zh-CN.json | 17 ++ .../src/server/NoticeInstruction.ts | 157 ++++++++++ .../src/server/actions.ts | 107 +++++++ .../src/server/collections/workflowNotice.ts | 67 +++++ .../src/server/constants.ts | 16 + .../src/server/index.ts | 1 + .../src/server/plugin.ts | 49 ++++ .../src/server/tools.ts | 138 +++++++++ pnpm-lock.yaml | 274 +++++++++++++----- 51 files changed, 2308 insertions(+), 81 deletions(-) create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/.npmignore create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/README.md create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/client.d.ts create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/client.js create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/package.json create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/server.d.ts create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/server.js create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/client/common/flowNodes.collection.ts create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/client/common/notice.collection.ts create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/client/common/workflow.collection.ts create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/Notice.instruction.ts create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/config-items/AddNotifiedPerson.view.tsx create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/config-items/ConfigButtonNotice.view.tsx create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/config-items/SelectNotifiedPerson.view.tsx create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/kit.ts create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/show-interface/NoticeDetail.block.tsx create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/show-interface/NoticeDetail.initializer.tsx create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/show-interface/NoticeDetail.schema.tsx create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/show-interface/NoticeDetailContainer.schema.tsx create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/show-interface/style.ts create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/style.ts create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/client/index.tsx create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/client/locale.ts create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/NoticeBlock.initializer.tsx create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/NoticeBlock.provider.tsx create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/NoticeCenter.schema.tsx create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/column/column.action.tsx create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/column/column.node.tsx create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/column/column.status.tsx create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/column/column.user.tsx create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/column/column.workflow.tsx create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/kit.ts create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/show-detail/NoticeDetail.provider.tsx create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/show-detail/NoticeDetail.schema.tsx create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/show-detail/contexts/MyComponent.context.tsx create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/show-detail/contexts/WorkflowNotice.context.tsx create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/show-detail/hooks/usePropsNoticeDetail.ts create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/common/constants.ts create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/index.ts create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/locale/en-US.json create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/locale/zh-CN.json create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/server/NoticeInstruction.ts create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/server/actions.ts create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/server/collections/workflowNotice.ts create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/server/constants.ts create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/server/index.ts create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/server/plugin.ts create mode 100644 packages/plugins/@tachybase/plugin-workflow-notice/src/server/tools.ts diff --git a/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/Approval.trigger.tsx b/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/Approval.trigger.tsx index 967174181..aa25021a6 100644 --- a/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/Approval.trigger.tsx +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/Approval.trigger.tsx @@ -172,11 +172,6 @@ export class ApprovalTrigger extends Trigger { compile, getCollectionFields, }); - console.log( - '%c Line:176 🍷 collectionFieldOptions', - 'font-size:18px;color:#33a5ff;background:#3f7cff', - collectionFieldOptions, - ); return collectionFieldOptions; } diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/ApprovalBlock.provider.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/ApprovalBlock.provider.tsx index fa74b5718..3504618da 100644 --- a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/ApprovalBlock.provider.tsx +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/ApprovalBlock.provider.tsx @@ -1,5 +1,6 @@ -import { ExtendCollectionsProvider, TableBlockProvider, useRecord } from '@tachybase/client'; import React from 'react'; +import { ExtendCollectionsProvider, TableBlockProvider, useRecord } from '@tachybase/client'; + import { CollectionApprovalTodos } from '../../common/Cn.ApprovalTodos'; import { CollectionApprovals } from '../approval-common/Approvals.collection'; import { CollectionFlowNodes } from '../approval-common/FlowNodes.collection'; diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/.npmignore b/packages/plugins/@tachybase/plugin-workflow-notice/.npmignore new file mode 100644 index 000000000..65f5e8779 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/.npmignore @@ -0,0 +1,2 @@ +/node_modules +/src diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/README.md b/packages/plugins/@tachybase/plugin-workflow-notice/README.md new file mode 100644 index 000000000..f7ffdf277 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/README.md @@ -0,0 +1 @@ +# @tachybase/plugin-workflow-notice diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/client.d.ts b/packages/plugins/@tachybase/plugin-workflow-notice/client.d.ts new file mode 100644 index 000000000..6c459cbac --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/client.d.ts @@ -0,0 +1,2 @@ +export * from './dist/client'; +export { default } from './dist/client'; diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/client.js b/packages/plugins/@tachybase/plugin-workflow-notice/client.js new file mode 100644 index 000000000..b6e3be70e --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/client.js @@ -0,0 +1 @@ +module.exports = require('./dist/client/index.js'); diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/package.json b/packages/plugins/@tachybase/plugin-workflow-notice/package.json new file mode 100644 index 000000000..e19c99454 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/package.json @@ -0,0 +1,34 @@ +{ + "name": "@tachybase/plugin-workflow-notice", + "displayName": "Workflow: Notice", + "version": "0.21.32", + "description": "A powerful BPM tool that provides foundational support for business automation, with the capability to extend unlimited triggers and nodes.", + "keywords": [ + "Workflow", + "Notice" + ], + "main": "dist/server/index.js", + "devDependencies": { + "@ant-design/icons": "5.x", + "@tachybase/actions": "workspace:*", + "@tachybase/components": "workspace:*", + "@tachybase/data-source-manager": "workspace:*", + "@tachybase/database": "workspace:*", + "@tachybase/plugin-ui-schema-storage": "workspace:*", + "@tachybase/schema": "workspace:*", + "@tachybase/utils": "workspace:*", + "antd": "5.16.1", + "lodash": "^4.17.21", + "react": "18.x", + "react-i18next": "^11.15.1", + "sequelize": "^6.26.0" + }, + "peerDependencies": { + "@tachybase/client": "workspace:*", + "@tachybase/plugin-workflow": "workspace:*", + "@tachybase/server": "workspace:*", + "@tachybase/test": "workspace:*" + }, + "description.zh-CN": "工作流节点, 通知。", + "displayName.zh-CN": "工作流: 通知" +} diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/server.d.ts b/packages/plugins/@tachybase/plugin-workflow-notice/server.d.ts new file mode 100644 index 000000000..c41081ddc --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/server.d.ts @@ -0,0 +1,2 @@ +export * from './dist/server'; +export { default } from './dist/server'; diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/server.js b/packages/plugins/@tachybase/plugin-workflow-notice/server.js new file mode 100644 index 000000000..972842039 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/server.js @@ -0,0 +1 @@ +module.exports = require('./dist/server/index.js'); diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/client/common/flowNodes.collection.ts b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/common/flowNodes.collection.ts new file mode 100644 index 000000000..6f397decb --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/common/flowNodes.collection.ts @@ -0,0 +1,42 @@ +import { tval } from '../locale'; + +export const CollectionFlowNodes = { + title: tval('Node'), + name: 'flow_nodes', + fields: [ + { + type: 'bigInt', + name: 'id', + interface: 'm2o', + uiSchema: { + type: 'number', + title: 'ID', + 'x-component': 'RemoteSelect', + 'x-component-props': { + fieldNames: { + label: 'title', + value: 'id', + }, + service: { + resource: 'flow_nodes', + params: { + filter: { + type: 'manual', + }, + }, + }, + }, + }, + }, + { + type: 'string', + name: 'title', + interface: 'input', + uiSchema: { + type: 'string', + title: '{{t("Title")}}', + 'x-component': 'Input', + }, + }, + ], +}; diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/client/common/notice.collection.ts b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/common/notice.collection.ts new file mode 100644 index 000000000..bed7bbbd2 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/common/notice.collection.ts @@ -0,0 +1,144 @@ +import { COLLECTION_NOTICE_NAME } from '../../common/constants'; +import { NAMESPACE, tval } from '../locale'; +import { approvalStatusOptions } from '../usage/column/column.status'; + +export const CollectionWorkflowNotice = { + title: tval('Notice Center'), + name: COLLECTION_NOTICE_NAME, + fields: [ + { + type: 'bigInt', + name: 'id', + interface: 'number', + uiSchema: { + type: 'number', + title: 'ID', + 'x-component': 'InputNumber', + }, + }, + { + type: 'belongsTo', + name: 'user', + target: 'users', + foreignKey: 'userId', + interface: 'm2o', + uiSchema: { + type: 'number', + title: tval('The Notified Person'), + 'x-component': 'RemoteSelect', + 'x-component-props': { + fieldNames: { + label: 'nickname', + value: 'id', + }, + service: { resource: 'users' }, + }, + }, + }, + { + type: 'belongsTo', + name: 'node', + target: 'flow_nodes', + foreignKey: 'nodeId', + interface: 'm2o', + isAssociation: true, + uiSchema: { + type: 'number', + title: `{{t("Task node", { ns: "${NAMESPACE}" })}}`, + 'x-component': 'RemoteSelect', + 'x-component-props': { + fieldNames: { + label: 'title', + value: 'id', + }, + service: { + resource: 'flow_nodes', + }, + }, + }, + }, + { + type: 'belongsTo', + name: 'workflow', + target: 'workflows', + foreignKey: 'workflowId', + interface: 'm2o', + uiSchema: { + type: 'number', + title: '{{t("Workflow", { ns: "workflow" })}}', + 'x-component': 'RemoteSelect', + 'x-component-props': { + fieldNames: { + label: 'title', + value: 'id', + }, + service: { resource: 'workflows' }, + }, + }, + }, + { + type: 'integer', + name: 'status', + interface: 'select', + uiSchema: { + type: 'number', + title: '{{t("Status", { ns: "workflow" })}}', + 'x-component': 'Select', + enum: approvalStatusOptions, + }, + }, + { + type: 'text', + name: 'comment', + interface: 'markdown', + uiSchema: { + type: 'string', + 'x-component': 'Markdown', + title: `{{t("Comment", { ns: "${NAMESPACE}" })}}`, + }, + }, + { + name: 'createdAt', + type: 'date', + interface: 'createdAt', + uiSchema: { + type: 'datetime', + title: '{{t("Created at")}}', + 'x-component': 'DatePicker', + 'x-component-props': { + showTime: true, + }, + }, + }, + { + name: 'updatedAt', + type: 'date', + interface: 'updatedAt', + uiSchema: { + type: 'datetime', + title: '{{t("Updated at")}}', + 'x-component': 'DatePicker', + 'x-component-props': { + showTime: true, + }, + }, + }, + { + type: 'string', + name: 'summary', + interface: 'input', + uiSchema: { + type: 'string', + title: tval('Summary'), + 'x-component': 'ApprovalsSummary', + 'x-component-props': { + style: { + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + }, + }, + }, + }, + ], +}; diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/client/common/workflow.collection.ts b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/common/workflow.collection.ts new file mode 100644 index 000000000..c21f50dc1 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/common/workflow.collection.ts @@ -0,0 +1,39 @@ +import { tval } from '../locale'; + +export const CollectionWorkflows = { + title: tval('Workflow'), + name: 'workflows', + fields: [ + { + type: 'string', + name: 'title', + interface: 'input', + uiSchema: { + title: '{{t("Name")}}', + type: 'string', + 'x-component': 'Input', + required: true, + }, + }, + { + type: 'boolean', + name: 'enabled', + interface: 'select', + uiSchema: { + type: 'boolean', + title: '{{t("Status", { ns: "workflow" })}}', + 'x-component': 'Select', + enum: [ + { + label: '{{t("On", { ns: "workflow" })}}', + value: true, + }, + { + label: '{{t("Off", { ns: "workflow" })}}', + value: false, + }, + ], + }, + }, + ], +}; diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/Notice.instruction.ts b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/Notice.instruction.ts new file mode 100644 index 000000000..217d2f23e --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/Notice.instruction.ts @@ -0,0 +1,95 @@ +import { ArrayItems } from '@tachybase/components'; +import { Instruction } from '@tachybase/plugin-workflow/client'; + +import { NOTICE_INSTRUCTION_NAMESPACE } from '../../common/constants'; +import { tval } from '../locale'; +import { AdditionNotifiedPerson } from './config-items/AddNotifiedPerson.view'; +import { ConfigButtonNotice } from './config-items/ConfigButtonNotice.view'; +import { SelectNotifiedPerson } from './config-items/SelectNotifiedPerson.view'; +import { NoticeDetailContainer } from './show-interface/NoticeDetailContainer.schema'; +import { configSytles } from './style'; + +// group 的取值, 经查询猜测,可能为 +// 'control' 'collection' 'extended' 'manual' 'advanced' +// TODO: 需要整理梳理 workflow, 将这个类型转移到 workflow 中去 +type GroupType = 'control' | 'collection' | 'extended' | 'manual' | 'advanced'; + +export class NoticeInstruction extends Instruction { + title = tval('Notice'); + type = NOTICE_INSTRUCTION_NAMESPACE; + group: GroupType = 'extended'; + description = tval( + 'In the workflow, notification messages can be viewed by the notified person in the notification center.', + ); + components = { + ArrayItems: ArrayItems, + SelectNotifiedPerson: SelectNotifiedPerson, + AdditionNotifiedPerson: AdditionNotifiedPerson, + ConfigButtonNotice: ConfigButtonNotice, + ShowNoticeDetail: NoticeDetailContainer, + }; + fieldset = { + notifiedPerson: { + type: 'array', + title: tval('The Notified Person'), + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'ArrayItems', + 'x-component-props': { + className: configSytles.ArrayItemsStyle, + }, + + items: { + type: 'void', + 'x-component': 'Space', + 'x-component-props': { + className: configSytles.SpaceStyle, + }, + properties: { + sort: { + type: 'void', + 'x-decorator': 'FormItem', + 'x-component': 'ArrayItems.SortHandle', + }, + input: { + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'SelectNotifiedPerson', + }, + remove: { + type: 'void', + 'x-decorator': 'FormItem', + 'x-component': 'ArrayItems.Remove', + }, + }, + }, + properties: { + add: { + type: 'void', + title: tval('Add Notified Person'), + // HACK: 这里原本的框架逻辑, 解析竟然和 Addition 这个单词绑定, 奇怪 + // 类似的还有, Index, Remove, MoveUp, MoveDown, Copy + // isOperationComponent() 针对这类操作组件, 做了特殊逻辑 + 'x-component': 'AdditionNotifiedPerson', + }, + }, + }, + showNoticeDetail: { + type: 'void', + title: tval('Show Notice Detail'), + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'ConfigButtonNotice', + properties: { + showNoticeDetail: { + type: 'void', + 'x-component': 'ShowNoticeDetail', + }, + }, + }, + }; + + isAvailable(params) { + return true; + } +} diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/config-items/AddNotifiedPerson.view.tsx b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/config-items/AddNotifiedPerson.view.tsx new file mode 100644 index 000000000..5b4b28064 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/config-items/AddNotifiedPerson.view.tsx @@ -0,0 +1,58 @@ +// import { PlusOutlined } from '@ant-design/icons'; +import React, { useCallback, useState } from 'react'; +import { ArrayItems } from '@tachybase/components'; +import { useWorkflowExecuted } from '@tachybase/plugin-workflow/client'; + +import { Button, Popover, Space } from 'antd'; + +import { useTranslation } from '../../locale'; + +export const AdditionNotifiedPerson = () => { + const isWorkflowExecuted = useWorkflowExecuted(); + const array = ArrayItems.useArray() || { field: [] }; + const { t } = useTranslation(); + const [isOpen, setIsOpen] = useState(false); + + const onSlecet = useCallback(() => { + array.field.push(''); + setIsOpen(false); + }, [array.field]); + + const onQuery = useCallback(() => { + array.field.push({ filter: {} }); + setIsOpen(false); + }, [array.field]); + + return ( + } + > + + + ); +}; + +// Child Component +const PopoverContent = ({ onSlecet, onQuery }) => { + const { t } = useTranslation(); + return ( + + + + + ); +}; diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/config-items/ConfigButtonNotice.view.tsx b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/config-items/ConfigButtonNotice.view.tsx new file mode 100644 index 000000000..a86cf07ad --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/config-items/ConfigButtonNotice.view.tsx @@ -0,0 +1,26 @@ +import React, { useCallback, useState } from 'react'; +import { ActionContextProvider } from '@tachybase/client'; + +import { Button } from 'antd'; + +import { useTranslation } from '../../locale'; + +export const ConfigButtonNotice = (props) => { + const { t } = useTranslation(); + const [visible, setVisible] = useState(false); + + const handleButtonClick = useCallback(() => { + setVisible(true); + }, [setVisible]); + + return ( + <> + + + {props.children} + + + ); +}; diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/config-items/SelectNotifiedPerson.view.tsx b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/config-items/SelectNotifiedPerson.view.tsx new file mode 100644 index 000000000..a211e53fb --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/config-items/SelectNotifiedPerson.view.tsx @@ -0,0 +1,62 @@ +import { RemoteSelect, SchemaComponent, Variable, useCollectionFilterOptions, useToken } from '@tachybase/client'; +import { FilterDynamicComponent, useWorkflowVariableOptions } from '@tachybase/plugin-workflow/client'; +import { useField } from '@tachybase/schema'; +import React from 'react'; + +// TODO: 可以将这个选择组件封装为一个通用组件, 放在 workflow 下提供使用 +export const SelectNotifiedPerson = (props) => { + if (props.value && typeof props.value === 'object') { + return ; + } else { + return ; + } +}; + +const SelectNotifiedPersonNormal = ({ value, onChange }) => { + const scope = useWorkflowVariableOptions({ types: [isUserKeyField] }); + return ( + + + + ); +}; + +const SelectNotifiedPersonCustom = () => { + const field = useField(); + const currentFormFields = useCollectionFilterOptions('users'); + const { token } = useToken(); + return ( +
+ +
+ ); +}; + +function isUserKeyField(field) { + if (field.isForeignKey) { + return field.target === 'users'; + } else { + return field.collectionName === 'users' && field.name === 'id'; + } +} diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/kit.ts b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/kit.ts new file mode 100644 index 000000000..957bb852d --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/kit.ts @@ -0,0 +1,24 @@ +import { Plugin } from '@tachybase/client'; +import PluginWorkflow from '@tachybase/plugin-workflow/client'; +import { NoticeInstruction } from './Notice.instruction'; +import { NOTICE_INSTRUCTION_NAMESPACE } from '../../common/constants'; +import { SCNoticeDetail } from './show-interface/NoticeDetail.schema'; +import { ArrayItems } from '@tachybase/components'; + +// NOTE: 试着用一种新的约束规则去声明文件属于哪种功能. 比如 kit.ts 导出的是单个 Plugin 下的功能集成 +// 注册一个节点 +export class KitConfiguration extends Plugin { + async afterAdd() { + await this.app.pm.add(SCNoticeDetail); + } + + async load() { + const pluginWorkflow = this.app.pm.get(PluginWorkflow); + pluginWorkflow.registerInstruction(NOTICE_INSTRUCTION_NAMESPACE, NoticeInstruction); + this.app.addComponents({ + ArrayItems, + }); + // this.app.schemaInitializerManager.add(ApproverActionConfigInitializer); + // this.app.schemaInitializerManager.add(ApproverAddBlockInitializer); + } +} diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/show-interface/NoticeDetail.block.tsx b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/show-interface/NoticeDetail.block.tsx new file mode 100644 index 000000000..9f9578106 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/show-interface/NoticeDetail.block.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { + createReadPrettyFormBlockSchema, + parseCollectionName, + SchemaInitializerItem, + useRecordCollectionDataSourceItems, + useSchemaInitializer, + useSchemaInitializerItem, + useSchemaTemplateManager, +} from '@tachybase/client'; +import { useFlowContext } from '@tachybase/plugin-workflow/client'; + +import _ from 'lodash'; + +export const NoticeDetailAddBlock = () => { + const { workflow } = useFlowContext(); + const [dataSourceName, collection] = parseCollectionName(workflow.config?.collection); + const itemConfig = useSchemaInitializerItem(); + const { insert } = useSchemaInitializer(); + const { getTemplateSchemaByMode } = useSchemaTemplateManager(); + const items = useRecordCollectionDataSourceItems('FormItem'); + const handleClick = async ({ item }) => { + const template = item.template ? await getTemplateSchemaByMode(item) : null; + const schema = createReadPrettyFormBlockSchema({ + actionInitializers: null, + resource: collection, + collection, + dataSource: dataSourceName, + template, + settings: 'blockSettings:details', + }); + + const shcemaWithPatch = {}; + + // REVIEW: 删除这个属性的必要性, 需要再思考下 + _.unset(schema, ['x-acl-action', 'x-acl-action-props']); + + const [childSchemaKey] = Object.keys(schema.properties); + + _.set(schema.properties[childSchemaKey], 'x-component-props.useProps', 'usePropsNoticeDetail'); + + insert(schema); + }; + return ; +}; diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/show-interface/NoticeDetail.initializer.tsx b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/show-interface/NoticeDetail.initializer.tsx new file mode 100644 index 000000000..972c9ca92 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/show-interface/NoticeDetail.initializer.tsx @@ -0,0 +1,78 @@ +import { SchemaInitializer, gridRowColWrap, usePlugin } from '@tachybase/client'; +import PluginWorkflow, { + useAvailableUpstreams, + useFlowContext, + useNodeContext, + useTrigger, +} from '@tachybase/plugin-workflow/client'; +import { tval } from '../../locale'; +import { NoticeDetailAddBlock } from './NoticeDetail.block'; +// import { ApproverAddBlockComponent } from './VC.ApproverAddBlock'; +// import { ApproverAddBlockKit } from './VC.ApproverAddBlockKit'; + +export const NoticeDetailInitializer = new SchemaInitializer({ + name: 'NoticeDetailInitializer', + wrap: gridRowColWrap, + title: "{{t('Add block')}}", + items: [ + { + name: 'notice', + type: 'itemGroup', + title: tval('Notice blocks'), + children: [ + { + name: 'detail', + type: 'item', + title: '{{t("Details")}}', + Component: NoticeDetailAddBlock, + }, + ], + }, + { + type: 'itemGroup', + name: 'dataBlocks', + title: '{{t("Data blocks")}}', + checkChildrenLength: true, + useChildren() { + const workflowPlugin = usePlugin(PluginWorkflow); + const { workflow } = useFlowContext(); + const trigger = useTrigger(); + const currentNodeContext = useNodeContext(); + const nodes = useAvailableUpstreams(currentNodeContext); + const triggerInitializers = [trigger.useInitializers?.call(trigger, workflow.config)].filter(Boolean); + const nodeBlockInitializers = nodes + .map((node) => { + const instruction = workflowPlugin.instructions.get(node.type); + return instruction?.useInitializers?.call(instruction, node); + }) + .filter(Boolean); + return [ + ...triggerInitializers, + ...(nodeBlockInitializers.length + ? [ + { + name: 'nodes', + type: 'subMenu', + title: '{{t("Node result", { ns: "workflow" })}}', + children: nodeBlockInitializers, + }, + ] + : []), + ].filter(Boolean); + }, + }, + { + name: 'others', + type: 'itemGroup', + title: '{{t("Other blocks")}}', + children: [ + { + name: 'markdown', + type: 'item', + title: '{{t("Markdown")}}', + component: 'MarkdownBlockInitializer', + }, + ], + }, + ], +}); diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/show-interface/NoticeDetail.schema.tsx b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/show-interface/NoticeDetail.schema.tsx new file mode 100644 index 000000000..d1aa38645 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/show-interface/NoticeDetail.schema.tsx @@ -0,0 +1,86 @@ +import React from 'react'; +import { Plugin, SchemaComponent, useAPIClient, useRequest } from '@tachybase/client'; +import { useForm } from '@tachybase/schema'; +import { uid } from '@tachybase/utils/client'; + +import { Spin } from 'antd'; + +import { NoticeDetailInitializer } from './NoticeDetail.initializer'; + +export class SCNoticeDetail extends Plugin { + async load() { + this.app.schemaInitializerManager.add(NoticeDetailInitializer); + } +} + +function createNewSchema(id) { + return { + type: 'void', + name: id, + 'x-uid': id, + 'x-component': 'Grid', + 'x-initializer': 'NoticeDetailInitializer', + }; +} + +export const NoticeDetail = (props) => { + const { schemaData, loading, handleChange } = useAction(props); + if (loading) { + return ; + } + return ( + { + // return { run() {} }; + // }, + // useApprovalFormBlockProps, + // useDetailsBlockProps: useFormBlockContext, + } + } + components={{}} + schema={schemaData as any} + onChange={handleChange} + /> + ); +}; + +function useAction(props) { + const { value: srcID, onChange } = props; + const apiClient = useAPIClient(); + const { values, setValuesIn } = useForm(); + const { data: schemaData, loading } = useRequest(async () => { + if (srcID) { + const remoteSchema = await fetchSchema(apiClient, srcID); + if (remoteSchema['x-uid'] === srcID) { + return remoteSchema; + } + } + const id = srcID ?? uid(); + const newSchema = createNewSchema(id); + await instertSchema(apiClient, newSchema); + onChange(id); + return newSchema; + }); + + const handleChange = (data) => {}; + + return { + schemaData, + loading, + handleChange, + }; +} + +async function fetchSchema(apiClient, srcID): Promise { + const { data: serviceData } = await apiClient.request({ url: `uiSchemas:getJsonSchema/${srcID}` }); + + const remoteSchema = serviceData.data ?? {}; + return remoteSchema; +} + +async function instertSchema(apiClient, schema): Promise { + await apiClient.resource('uiSchemas').insert({ values: schema }); +} diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/show-interface/NoticeDetailContainer.schema.tsx b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/show-interface/NoticeDetailContainer.schema.tsx new file mode 100644 index 000000000..3eda1d4de --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/show-interface/NoticeDetailContainer.schema.tsx @@ -0,0 +1,43 @@ +import { SchemaComponent, SchemaComponentContext } from '@tachybase/client'; +import { useFlowContext } from '@tachybase/plugin-workflow/client'; +import { uid } from '@tachybase/utils/client'; +import React, { useContext, useMemo, useState } from 'react'; +import { tval } from '../../locale'; +import { schemaStyle } from './style'; +import { NoticeDetail } from './NoticeDetail.schema'; + +export const NoticeDetailContainer = () => { + const context = useContext(SchemaComponentContext); + const [, setId] = useState(uid()); + const { workflow } = useFlowContext(); + + const contextValue = useMemo(() => { + return { + ...context, + refresh: () => setId(uid()), + designable: !workflow.executed, + }; + }, [context, workflow.executed]); + + return ( + + + + ); +}; + +const schema = { + name: 'drawer', + type: 'void', + title: tval('The interface of show notice detail'), + 'x-component': 'Action.Drawer', + 'x-component-props': { + className: schemaStyle.ActionStyle, + }, + properties: { + showNoticeDetail: { + type: 'string', + 'x-component': 'NoticeDetail', + }, + }, +}; diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/show-interface/style.ts b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/show-interface/style.ts new file mode 100644 index 000000000..b8a8de876 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/show-interface/style.ts @@ -0,0 +1,9 @@ +import { css } from '@tachybase/client'; + +export const schemaStyle = { + ActionStyle: css` + .ant-drawer-body { + background: var(--nb-box-bg); + } + `, +}; diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/style.ts b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/style.ts new file mode 100644 index 000000000..1e7c7c5c8 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/configuration/style.ts @@ -0,0 +1,26 @@ +import { createStyles, css } from '@tachybase/client'; + +export const useStyles = createStyles({}); + +export const configSytles = { + ArrayItemsStyle: css` + &[disabled] { + > .ant-formily-array-base-addition { + display: none; + } + > .ant-formily-array-items-item .ant-space-item:not(:nth-child(2)) { + display: none; + } + } + `, + + SpaceStyle: css` + width: 100%; + &.ant-space.ant-space-horizontal { + flex-wrap: nowrap; + } + > .ant-space-item:nth-child(2) { + flex-grow: 1; + } + `, +}; diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/client/index.tsx b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/index.tsx new file mode 100644 index 000000000..b80c8f87c --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/index.tsx @@ -0,0 +1,13 @@ +import { Plugin } from '@tachybase/client'; + +import { KitConfiguration } from './configuration/kit'; +import { KitUsage } from './usage/kit'; + +class PluginWorkflowNoticeClient extends Plugin { + async afterAdd() { + await this.app.pm.add(KitConfiguration); + await this.app.pm.add(KitUsage); + } +} + +export default PluginWorkflowNoticeClient; diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/client/locale.ts b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/locale.ts new file mode 100644 index 000000000..aab9a4a12 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/locale.ts @@ -0,0 +1,21 @@ +import { i18n, tval as nTval } from '@tachybase/client'; + +export const NAMESPACE = '@tachybase/plugin-workflow-notice'; + +export function usePluginTranslation(): any { + return useTranslation(); +} + +export function useTranslation() { + const t = (key: string, options = {}) => i18n.t(key, { ns: NAMESPACE, ...options }); + return { t }; +} + +export function lang(key: string, options = {}) { + return i18n.t(key, { + ...options, + ns: NAMESPACE, + }); +} + +export const tval = (key: string) => nTval(key, { ns: NAMESPACE }); diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/NoticeBlock.initializer.tsx b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/NoticeBlock.initializer.tsx new file mode 100644 index 000000000..5f9beebd9 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/NoticeBlock.initializer.tsx @@ -0,0 +1,66 @@ +import { Plugin, SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '@tachybase/client'; +import { uid } from '@tachybase/utils/client'; +import React, { useCallback } from 'react'; +import { COLLECTION_NOTICE_NAME } from '../../common/constants'; +import { NoticeBlockProvider } from './NoticeBlock.provider'; +import { NoticeCenter } from './NoticeCenter.schema'; + +export class SCApprovalBlock extends Plugin { + async load() { + this.app.addComponents({ + NoticeBlockProvider: NoticeBlockProvider, + NoticeCenter: NoticeCenter, + }); + } +} + +function createSchema() { + const id = uid(); + return { + type: 'void', + name: id, + 'x-uid': id, + 'x-decorator-props': { + collection: COLLECTION_NOTICE_NAME, + params: { + appends: [ + 'user.id', + 'user.nickname', + 'node.id', + 'node.title', + 'job.id', + 'job.status', + 'job.result', + 'workflow.id', + 'workflow.title', + 'workflow.enabled', + 'execution.id', + 'execution.status', + ], + }, + action: 'listCentralized', + }, + 'x-decorator': 'NoticeBlockProvider', + 'x-component': 'CardItem', + 'x-designer': 'TableBlockDesigner', + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:table', + properties: { + notice: { + type: 'void', + 'x-component': 'NoticeCenter', + }, + }, + }; +} + +export const NoticeBlockInitializer = () => { + const { insert } = useSchemaInitializer(); + const itemConfig = useSchemaInitializerItem(); + const handleClick = useCallback(() => { + const schema = createSchema(); + insert(schema); + }, [insert, createSchema]); + + return ; +}; diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/NoticeBlock.provider.tsx b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/NoticeBlock.provider.tsx new file mode 100644 index 000000000..8aa835086 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/NoticeBlock.provider.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { ExtendCollectionsProvider, TableBlockProvider, useRecord } from '@tachybase/client'; + +import { CollectionFlowNodes } from '../common/flowNodes.collection'; +import { CollectionWorkflowNotice } from '../common/notice.collection'; +import { CollectionWorkflows } from '../common/workflow.collection'; + +export const NoticeBlockProvider = ({ children, ...props }) => { + const { + collection, + params = { + filter: {}, + }, + action = 'list', + } = props; + + const record = useRecord(); + const config = { + collection, + resource: collection, + action, + params: { + pageSize: 20, + sort: ['-createdAt'], + ...params, + filter: record?.id + ? { + dataKey: `${record.id}`, + ...params.filter, + } + : { ...params.filter }, + }, + rowKey: 'id', + showIndex: true, + dragSort: false, + }; + + return ( + + + {children} + + + ); +}; diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/NoticeCenter.schema.tsx b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/NoticeCenter.schema.tsx new file mode 100644 index 000000000..51b794f1d --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/NoticeCenter.schema.tsx @@ -0,0 +1,199 @@ +import React from 'react'; +import { css, SchemaComponent } from '@tachybase/client'; + +import { NAMESPACE, tval } from '../locale'; +import { ColumnAction } from './column/column.action'; +import { ColumnNode } from './column/column.node'; +import { ColumnStatus } from './column/column.status'; +import { ColumnUser } from './column/column.user'; +import { ColumnWorkflow } from './column/column.workflow'; + +const schemaStyles = { + ActionbarStyle: css` + margin-bottom: 16px; + `, + TableV2Style: css` + .ant-table-cell { + text-align: center; + } + `, +}; + +export const NoticeCenter = () => ( + +); + +const schema = { + type: 'void', + name: 'notice-center', + properties: { + actions: { + type: 'void', + 'x-component': 'ActionBar', + 'x-component-props': { + className: schemaStyles.ActionbarStyle, + }, + properties: { + filter: { + type: 'void', + title: '{{ t("Filter") }}', + 'x-action': 'filter', + 'x-designer': 'Filter.Action.Designer', + 'x-component': 'Filter.Action', + 'x-use-component-props': 'useFilterActionProps', + 'x-component-props': { + icon: 'FilterOutlined', + }, + 'x-align': 'left', + }, + refresher: { + type: 'void', + title: '{{ t("Refresh") }}', + 'x-action': 'refresh', + 'x-component': 'Action', + 'x-use-component-props': 'useRefreshActionProps', + 'x-designer': 'Action.Designer', + 'x-component-props': { icon: 'ReloadOutlined' }, + 'x-align': 'right', + }, + }, + }, + table: { + type: 'array', + 'x-component': 'TableV2', + 'x-use-component-props': 'useTableBlockProps', + 'x-component-props': { + rowKey: 'id', + tableLayout: 'fixed', + className: css` + .ant-table-cell { + text-align: center; + } + `, + }, + properties: { + action: { + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-component': 'TableV2.Column', + 'x-component-props': { + width: 60, + }, + title: '{{t("Actions")}}', + properties: { + action: { + 'x-component': 'ColumnAction', + }, + }, + }, + id: { + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-component': 'TableV2.Column', + 'x-component-props': { + width: 80, + }, + title: tval('ID'), + properties: { + id: { + type: 'number', + 'x-component': 'Input', + 'x-read-pretty': true, + }, + }, + }, + workflow: { + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-component': 'TableV2.Column', + 'x-component-props': { + width: null, + }, + title: '{{t("Workflow", { ns: "workflow" })}}', + properties: { + workflow: { + 'x-component': 'ColumnWorkflow', + 'x-read-pretty': true, + }, + }, + }, + node: { + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-component': 'TableV2.Column', + 'x-component-props': { + width: null, + }, + title: `{{t("Task node", { ns: "${NAMESPACE}" })}}`, + properties: { + node: { + 'x-component': 'ColumnNode', + 'x-read-pretty': true, + }, + }, + }, + // status: { + // type: 'void', + // 'x-decorator': 'TableV2.Column.Decorator', + // 'x-component': 'TableV2.Column', + // 'x-component-props': { width: 100 }, + // title: '{{t("Status", { ns: "workflow" })}}', + // properties: { + // status: { + // 'x-component': 'ColumnStatus', + // 'x-read-pretty': true, + // }, + // }, + // }, + user: { + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-component': 'TableV2.Column', + 'x-component-props': { width: 140 }, + title: tval('The Notified Person'), + properties: { + user: { + 'x-component': 'ColumnUser', + 'x-read-pretty': true, + }, + }, + }, + createdAt: { + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-component': 'TableV2.Column', + 'x-component-props': { width: 160 }, + properties: { + createdAt: { + type: 'string', + 'x-component': 'CollectionField', + 'x-read-pretty': true, + }, + }, + }, + summary: { + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-component': 'TableV2.Column', + title: tval('Summary'), + properties: { + summary: { + type: 'string', + 'x-component': 'CollectionField', + 'x-read-pretty': true, + }, + }, + }, + }, + }, + }, +}; diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/column/column.action.tsx b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/column/column.action.tsx new file mode 100644 index 000000000..52f051d1b --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/column/column.action.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { SchemaComponent, useCollectionRecordData } from '@tachybase/client'; + +import { NoticeDetailContent } from '../show-detail/NoticeDetail.schema'; + +const createSchema = ({ record }) => { + const { id } = record; + return { + name: `notice-view-${id}`, + type: 'void', + 'x-component': 'Action.Link', + title: '{{t("View")}}', + properties: { + drawer: { + type: 'void', + 'x-component': 'Action.Drawer', + 'x-component-props': { + className: 'nb-action-popup', + }, + properties: { + content: { + type: 'void', + 'x-component': 'NoticeDetailContent', + }, + }, + }, + }, + }; +}; + +export const ColumnAction = () => { + const record = useCollectionRecordData(); + + return ( + + ); +}; diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/column/column.node.tsx b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/column/column.node.tsx new file mode 100644 index 000000000..7c8261c76 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/column/column.node.tsx @@ -0,0 +1,17 @@ +import { observer, useField, Field } from '@tachybase/schema'; + +export const ColumnNode = observer( + () => { + const field = useField(); + const fieldValue = field?.value; + + if (fieldValue?.title) { + return fieldValue.title; + } else if (fieldValue?.id) { + return `#${fieldValue.id}`; + } + + return ''; + }, + { displayName: 'ColumnNode' }, +); diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/column/column.status.tsx b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/column/column.status.tsx new file mode 100644 index 000000000..70b191a6f --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/column/column.status.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { useCompile, useRecord } from '@tachybase/client'; +import { EXECUTION_STATUS } from '@tachybase/plugin-workflow/client'; +import { Field, observer, useField } from '@tachybase/schema'; + +import { Tag } from 'antd'; +import _ from 'lodash'; + +import { NAMESPACE, useTranslation } from '../../locale'; + +export const ColumnStatus = observer( + () => { + const { value } = useField(); + const record = useRecord(); + return ; + }, + { displayName: 'ColumnStatus' }, +); + +export const ColumnStatusComponent = (props) => { + const { value, record } = props; + const omitProps = _.omit(props, ['value', 'record']); + const compile = useCompile(); + const { t } = useTranslation(); + const { option = approvalStatusConfigObj[value] } = omitProps; + const { workflow, execution, job } = record ?? {}; + + return (!workflow?.enabled || execution?.stauts || job?.status) && [].includes(value) ? ( + {t('Unprocessed')} + ) : ( + {compile(option?.label)} + ); +}; + +export const approvalStatusOptions = [ + { value: EXECUTION_STATUS.QUEUEING, label: `{{t("Assigned", { ns: "${NAMESPACE}" })}}`, color: 'blue' }, + { value: EXECUTION_STATUS.STARTED, label: `{{t("Pending", { ns: "${NAMESPACE}" })}}`, color: 'gold' }, + // { value: EXECUTION_STATUS.RETURNED, label: `{{t("Returned", { ns: "${NAMESPACE}" })}}`, color: 'purple' }, + { value: 2, label: `{{t("Approved", { ns: "${NAMESPACE}" })}}`, color: 'green' }, + { value: EXECUTION_STATUS.REJECTED, label: `{{t("Rejected", { ns: "${NAMESPACE}" })}}`, color: 'red' }, + // { value: EXECUTION_STATUS.WITHDRAWN, label: `{{t("Withdrawn", { ns: "${NAMESPACE}" })}}` }, +]; +const approvalStatusConfigObj = approvalStatusOptions.reduce( + (e, t) => + Object.assign(e, { + [t.value]: t, + }), + {}, +); diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/column/column.user.tsx b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/column/column.user.tsx new file mode 100644 index 000000000..72a990234 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/column/column.user.tsx @@ -0,0 +1,16 @@ +import { observer, useField, Field } from '@tachybase/schema'; + +export const ColumnUser = observer( + () => { + const field = useField(); + const fieldValue = field?.value; + + if (fieldValue?.nickname) { + return fieldValue.nickname; + } else if (fieldValue?.id !== undefined) { + return fieldValue.id; + } + return ''; + }, + { displayName: 'ColumnUser' }, +); diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/column/column.workflow.tsx b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/column/column.workflow.tsx new file mode 100644 index 000000000..da635af49 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/column/column.workflow.tsx @@ -0,0 +1,28 @@ +import { css } from '@tachybase/client'; +import { Field, observer, useField } from '@tachybase/schema'; +import React from 'react'; +import { useTranslation } from '../../locale'; + +export const ColumnWorkflow = observer( + () => { + const { t } = useTranslation(); + const { value } = useField(); + const title = value?.title || `#${value?.id}`; + + if (value?.enabled) { + return title; + } else { + return ( + + {title} + + ); + } + }, + { displayName: 'ColumnWorkflow' }, +); diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/kit.ts b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/kit.ts new file mode 100644 index 000000000..d4d6bedb1 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/kit.ts @@ -0,0 +1,25 @@ +import { Plugin } from '@tachybase/client'; +import { tval } from '../locale'; +import { NoticeBlockInitializer, SCApprovalBlock } from './NoticeBlock.initializer'; + +// NOTE: 试着用一种新的约束规则去声明文件属于哪种功能. 比如 kit.ts 导出的是单个 Plugin 下的功能集成 +export class KitUsage extends Plugin { + async afterAdd() { + await this.app.pm.add(SCApprovalBlock); + } + + async load() { + this.app.addComponents({ + NoticeBlockInitializer: NoticeBlockInitializer, + }); + + this.app.schemaInitializerManager.get('page:addBlock').add('otherBlocks.notice', { + key: 'noticeBlock', + name: 'noticeBlock', + type: 'item', + title: tval('Notice Center'), + Component: 'NoticeBlockInitializer', + icon: 'NotificationOutlined', + }); + } +} diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/show-detail/NoticeDetail.provider.tsx b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/show-detail/NoticeDetail.provider.tsx new file mode 100644 index 000000000..14d591bae --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/show-detail/NoticeDetail.provider.tsx @@ -0,0 +1,78 @@ +import React from 'react'; +import { + SchemaComponentContext, + useCollectionRecordData, + useRequest, + useSchemaComponentContext, +} from '@tachybase/client'; +import { ExecutionContextProvider } from '@tachybase/plugin-workflow/client'; + +import { Result, Spin } from 'antd'; + +import { COLLECTION_NOTICE_NAME } from '../../../common/constants'; +import { useTranslation } from '../../locale'; +import { ProviderContextMyComponent } from './contexts/MyComponent.context'; +import { ProviderContextWorkflowNotice } from './contexts/WorkflowNotice.context'; + +export const NoticeDetailProvider = ({ children, ...props }) => { + // THINK: 顺序问题, 工具优先, 数据之后, 外部优先, 本地滞后 + const schemaComponentContext = useSchemaComponentContext(); + const { t } = useTranslation(); + const { id } = useCollectionRecordData(); + // const { actionEnabled } = useContext(ContextWithActionEnabled); + const { loading, data } = useRequestData(id); + + if (loading) { + return ; + } + + if (!data?.data) { + return ; + } + + const itemsData = data.data; + const { node, workflow, execution } = itemsData; + const schemaId = node?.config.showNoticeDetail; + const { designable } = props; + + // THINK: Provider 的顺序, 数据放在外面, 配置放在内层 + return ( + + + + + {children} + + + + + ); +}; + +function useRequestData(id) { + const { loading, data }: any = useRequest( + { + resource: COLLECTION_NOTICE_NAME, + action: 'get', + params: { + filterByTk: id, + appends: ['node', 'job', 'workflow', 'workflow.nodes', 'execution', 'execution.jobs', 'user'], + except: ['workflow.config', 'workflow.options', 'nodes.config'], + sort: ['-createdAt'], + }, + }, + { + refreshDeps: [id], + }, + ); + + return { + loading, + data, + }; +} diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/show-detail/NoticeDetail.schema.tsx b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/show-detail/NoticeDetail.schema.tsx new file mode 100644 index 000000000..1d498eee3 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/show-detail/NoticeDetail.schema.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { + RemoteSchemaComponent, + SchemaComponent, + SchemaComponentProvider, + useFormBlockContext, +} from '@tachybase/client'; +import { DetailsBlockProvider } from '@tachybase/plugin-workflow/client'; + +import { tval } from '../../locale'; +import { useContextMyComponent } from './contexts/MyComponent.context'; +import { usePropsNoticeDetail } from './hooks/usePropsNoticeDetail'; +import { NoticeDetailProvider } from './NoticeDetail.provider'; + +// 审批-待办-查看: 内容 + +export const NoticeDetailContent = () => { + return ( + + + + ); +}; + +const NoticeDetail = (props) => { + const { id, schemaId } = useContextMyComponent(); + + return ( + + ); +}; diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/show-detail/contexts/MyComponent.context.tsx b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/show-detail/contexts/MyComponent.context.tsx new file mode 100644 index 000000000..59d06b0f8 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/show-detail/contexts/MyComponent.context.tsx @@ -0,0 +1,13 @@ +import React from 'react'; + +interface ContextMyComponentProps { + id?: string; + schemaId?: string; +} +export const ContextMyComponent = React.createContext({}); + +export const ProviderContextMyComponent = ContextMyComponent.Provider; + +export function useContextMyComponent() { + return React.useContext(ContextMyComponent); +} diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/show-detail/contexts/WorkflowNotice.context.tsx b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/show-detail/contexts/WorkflowNotice.context.tsx new file mode 100644 index 000000000..43a684bec --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/show-detail/contexts/WorkflowNotice.context.tsx @@ -0,0 +1,10 @@ +import React from 'react'; + +// NOTE: 传递的是内置表 workflowNotice 的数据 +export const ContextWorkflowNotice = React.createContext({}); + +export const ProviderContextWorkflowNotice = ContextWorkflowNotice.Provider; + +export function useContexWorkflowNotice() { + return React.useContext(ContextWorkflowNotice); +} diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/show-detail/hooks/usePropsNoticeDetail.ts b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/show-detail/hooks/usePropsNoticeDetail.ts new file mode 100644 index 000000000..601787cd4 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/client/usage/show-detail/hooks/usePropsNoticeDetail.ts @@ -0,0 +1,15 @@ +import { useEffect } from 'react'; +import { useFormBlockContext } from '@tachybase/client'; + +import { useContexWorkflowNotice } from '../contexts/WorkflowNotice.context'; + +export function usePropsNoticeDetail() { + const { snapshot } = useContexWorkflowNotice(); + const { form } = useFormBlockContext(); + + useEffect(() => { + form.setValues(snapshot); + }, [form, snapshot]); + + return { form }; +} diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/common/constants.ts b/packages/plugins/@tachybase/plugin-workflow-notice/src/common/constants.ts new file mode 100644 index 000000000..2f8b9d220 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/common/constants.ts @@ -0,0 +1,2 @@ +export const NOTICE_INSTRUCTION_NAMESPACE = 'notice'; +export const COLLECTION_NOTICE_NAME = 'workflowNotice'; diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/index.ts b/packages/plugins/@tachybase/plugin-workflow-notice/src/index.ts new file mode 100644 index 000000000..7e74612df --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/index.ts @@ -0,0 +1,2 @@ +export * from './server'; +export { default } from './server'; diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/locale/en-US.json b/packages/plugins/@tachybase/plugin-workflow-notice/src/locale/en-US.json new file mode 100644 index 000000000..c078c39a0 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/locale/en-US.json @@ -0,0 +1,17 @@ +{ + "Notice": "Notice", + "In the workflow, notification messages can be viewed by the notified person in the notification center.":"In the workflow, notification messages can be viewed by the notified person in the notification center.", + "The Notified Person": "The Notified Person", + "Add Notified Person": "Add Notified Person", + "Select Notified Person": "Select Notified Person", + "Query Notified Person": "Query Notified Person", + "Show Notice Detail": "Show Notice Detail", + "Go to configure": "Go to configure", + "The interface of show notice detail": "The interface of show notice detail", + "Notice blocks": "Notice blocks", + "Notice Center": "Notice Center", + "Summary": "Summary", + "Task node": "Task node", + "ID": "ID", + "Content Detail": "Content Detail" +} diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/locale/zh-CN.json b/packages/plugins/@tachybase/plugin-workflow-notice/src/locale/zh-CN.json new file mode 100644 index 000000000..e930e20d0 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/locale/zh-CN.json @@ -0,0 +1,17 @@ +{ + "Notice": "通知", + "In the workflow, notification messages can be viewed by the notified person in the notification center.":"在工作流程中通知消息, 被通知人可以在通知中心查看到消息.", + "The Notified Person": "被通知人", + "Add Notified Person": "添加被通知人", + "Select Notified Person": "选择被通知人", + "Query Notified Person": "查询被通知人", + "Show Notice Detail":"显示通知详情", + "Go to configure": "进行配置", + "The interface of show notice detail": "显示通知详情界面", + "Notice blocks": "通知区块", + "Notice Center": "工作流:通知中心", + "Summary": "摘要", + "Task node": "任务节点", + "ID": "编号", + "Content Detail": "内容详情" +} diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/server/NoticeInstruction.ts b/packages/plugins/@tachybase/plugin-workflow-notice/src/server/NoticeInstruction.ts new file mode 100644 index 000000000..f0c512bab --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/server/NoticeInstruction.ts @@ -0,0 +1,157 @@ +import { UiSchemaRepository } from '@tachybase/plugin-ui-schema-storage'; +import { Instruction, JOB_STATUS } from '@tachybase/plugin-workflow'; +import { uid } from '@tachybase/utils'; + +import { COLLECTION_NOTICE_NAME } from '../common/constants'; +import { NOTICE_ACTION_STATUS, NOTICE_STATUS } from './constants'; +import { getNegotiationMode, NoticeJobStatusMap, parsePerson } from './tools'; + +class NoticeInstruction extends Instruction { + async run(node, prevJob, processor) { + const job = await processor.saveJob({ + status: JOB_STATUS.RESOLVED, + nodeId: node.id, + nodeKey: node.key, + upstreamId: prevJob?.id ?? null, + }); + const notifiedPerson = await parsePerson(node, processor); + const { db } = processor.options.plugin; + // TODO-A: 改成取上一个节点的数据集, 或者当前执行流数据源的数据集, 目前是对审批事件做了特化处理; 需要理解下其他类型的触发事件, 怎么拿数据 + const ApprovalRepo = db.getRepository('approvals'); + const approval = await ApprovalRepo.findOne({ + filter: { + 'executions.id': processor.execution.id, + }, + fields: ['id', 'status', 'data', 'summary', 'collectionName'], + appends: ['approvalExecutions'], + except: ['data'], + }); + + const NoticeModel = db.getModel(COLLECTION_NOTICE_NAME); + + const notifiedPersonMapData = notifiedPerson.map((userId, index) => ({ + userId, + jobId: job.id, + nodeId: node.id, + executionId: job.executionId, + workflowId: node.workflowId, + index, + status: node.config.order && index ? NOTICE_ACTION_STATUS.ASSIGNED : NOTICE_ACTION_STATUS.APPROVED, + snapshot: approval.data, + summary: approval.summary, + collectionName: approval.collectionName, + dataKey: approval.dataKey, + })); + + await NoticeModel.bulkCreate(notifiedPersonMapData, { + transaction: processor.transaction, + }); + + return job; + } + // NOTE: 目前没有用处, 不过留着作为未来如果有动作的实现的参考 + async resume(node, job, processor) { + if (job.nodeId !== node.id) { + const nodeJob = processor.findBranchParentJob(job, node); + if (job.status === JOB_STATUS.RESOLVED) { + const jobNode = processor.nodesMap.get(job.nodeId); + const branchStart = processor.findBranchStartNode(jobNode); + if (branchStart.branchIndex === NOTICE_ACTION_STATUS.RETURNED) { + nodeJob.set('status', JOB_STATUS.RETRY_NEEDED); + } else if (branchStart.branchIndex === NOTICE_ACTION_STATUS.REJECTED && node.config.endOnReject) { + nodeJob.set('status', JOB_STATUS.REJECTED); + } + return nodeJob; + } + return processor.exit(job.status); + } + + const { branchMode, negotiation, order } = node.config; + + const notifiedPerson = await parsePerson(node, processor); + const NoticeRepo = this.workflow.app.db.getRepository(COLLECTION_NOTICE_NAME); + const records = await NoticeRepo.find({ + filter: { + jobId: job.id, + }, + except: ['snapshot'], + sort: ['index'], + transaction: processor.transaction, + }); + const distribution = records.reduce((prev, record) => { + const item = prev.find((item2) => item2.status === record.status); + if (item) { + item.count += 1; + } else { + prev.push({ + status: record.status, + count: 1, + }); + } + return prev; + }, []); + const processing = Boolean(distribution.find((item) => item.status !== NOTICE_ACTION_STATUS.PENDING)); + + const status = + job.status || + (getNegotiationMode(negotiation).getStatus(distribution, notifiedPerson, negotiation) ?? JOB_STATUS.PENDING); + + const result = NoticeJobStatusMap[status]; + + processor.logger.debug(`notice resume job and next status: ${status}`); + + job.set({ + status: status && status !== JOB_STATUS.CANCELED ? (branchMode ? JOB_STATUS.RESOLVED : status) : status, + result, + }); + + if ((status && status !== JOB_STATUS.CANCELED) || (negotiation && processing)) { + await job.latestUserJob.approval.update( + { + status: NOTICE_STATUS.PROCESSING, + }, + { transaction: processor.transaction }, + ); + } + const nextPerson = notifiedPerson[notifiedPerson.indexOf(job.latestUserJob.userId) + 1]; + + if (!status && negotiation && order && nextPerson) { + await NoticeRepo.update({ + values: { + status: NOTICE_ACTION_STATUS.PENDING, + }, + filter: { + jobId: job.id, + userId: nextPerson, + }, + transaction: processor.transaction, + }); + } + + if (branchMode) { + const branchNode = processor.nodes.find((item) => item.upstream === node && item.branchIndex === result); + if (branchNode) { + await processor.saveJob(job); + await processor.run(branchNode, job); + return null; + } + } + + return job; + } + async duplicateConfig(node, { transaction }) { + const uiSchemaRepo = this.workflow.app.db.getRepository('uiSchemas'); + if (!node.config.showNoticeDetail) { + return node.config; + } + const result = await uiSchemaRepo.duplicate(node.config.showNoticeDetail, { + transaction, + }); + return { + ...node.config, + showNoticeDetail: result?.['x-uid'] ?? uid(), + }; + } +} + +export default NoticeInstruction; diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/server/actions.ts b/packages/plugins/@tachybase/plugin-workflow-notice/src/server/actions.ts new file mode 100644 index 000000000..4222d894b --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/server/actions.ts @@ -0,0 +1,107 @@ +import actions, { utils } from '@tachybase/actions'; +import WorkflowPlugin from '@tachybase/plugin-workflow'; + +import { COLLECTION_NOTICE_NAME, NOTICE_INSTRUCTION_NAMESPACE } from '../common/constants'; +import { COLLECTION_WORKFLOWS_NAME } from './collections/workflowNotice'; +import { NOTICE_ACTION_STATUS } from './constants'; + +const workflows = { + async listWorkflowNoticeFlows(context, next) { + context.action.mergeParams({ + filter: { + type: NOTICE_INSTRUCTION_NAMESPACE, + enabled: true, + }, + }); + return actions.list(context, next); + }, +}; + +const workflowNotice = { + async listCentralized(context, next) { + const centralizedNoticeFlow = await context.db.getRepository(COLLECTION_WORKFLOWS_NAME).find({ + filter: { + 'config.centralized': true, + }, + fields: ['id'], + }); + context.action.mergeParams({ + filter: { + workflowId: centralizedNoticeFlow.map((item) => item.id), + }, + }); + return actions.list(context, next); + }, + async submit(context, next) { + const repository = utils.getRepositoryFromParams(context); + const { filterByTk, values } = context.action.params; + const { currentUser } = context.state; + if (!currentUser) { + return context.throw(401); + } + const workflowNotice = await repository.findOne({ + filterByTk, + filter: { + userId: currentUser == null ? void 0 : currentUser.id, + }, + appends: ['job', 'node', 'execution', 'workflow'], + context, + }); + + if (!workflowNotice) { + return context.throw(404); + } + + if ( + !workflowNotice.workflow.enabled || + workflowNotice.execution.status || + workflowNotice.job.status || + workflowNotice.status !== NOTICE_ACTION_STATUS.PENDING || + !(workflowNotice.node.config.actions ?? []).includes(values.status) + ) { + return context.throw(400); + } + + // TODO: 完善这里的取值逻辑 + await workflowNotice.update({ + status: values.status, + // comment: values.comment, + // snapshot: workflowNotice.approval.data, + // summary: workflowNotice.approval.summary, + // collectionName: workflowNotice.approval.collectionName, + }); + + context.body = workflowNotice.get(); + context.status = 202; + + await next(); + + workflowNotice.execution.workflow = workflowNotice.workflow; + workflowNotice.job.execution = workflowNotice.execution; + workflowNotice.job.latestUserJob = workflowNotice.get(); + const workflow = context.app.getPlugin(WorkflowPlugin); + const processor = workflow.createProcessor(workflowNotice.execution); + + processor.logger.info( + `notice node (${workflowNotice.nodeId}) action trigger execution (${workflowNotice.execution.id}) to resume`, + ); + workflow.resume(workflowNotice.job); + }, +}; + +export function init({ app }) { + app.actions({ + ...make(COLLECTION_WORKFLOWS_NAME, workflows), + ...make(COLLECTION_NOTICE_NAME, workflowNotice), + }); +} + +function make(name, mod) { + return Object.keys(mod).reduce( + (result, key) => ({ + ...result, + [`${name}:${key}`]: mod[key], + }), + {}, + ); +} diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/server/collections/workflowNotice.ts b/packages/plugins/@tachybase/plugin-workflow-notice/src/server/collections/workflowNotice.ts new file mode 100644 index 000000000..265f567b7 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/server/collections/workflowNotice.ts @@ -0,0 +1,67 @@ +import { defineCollection } from '@tachybase/database'; +import { COLLECTION_NOTICE_NAME } from '../../common/constants'; + +export const COLLECTION_WORKFLOWS_NAME = 'workflows'; + +const WorkflowNotice = defineCollection({ + namespace: 'workflow.notice', + dumpRules: 'required', + name: COLLECTION_NOTICE_NAME, + createdBy: true, + updatedBy: true, + fields: [ + { + type: 'string', + name: 'collectionName', + }, + { + type: 'string', + name: 'dataKey', + }, + { + type: 'string', + name: 'index', + }, + { + type: 'integer', + name: 'status', + }, + { + type: 'jsonb', + name: 'snapshot', + defaultValue: {}, + }, + { + type: 'jsonb', + name: 'summary', + defaultValue: {}, + }, + { + type: 'belongsTo', + name: 'user', + target: 'users', + }, + { + type: 'belongsTo', + name: 'workflow', + onDelete: 'CASCADE', + }, + + { + type: 'belongsTo', + name: 'execution', + }, + { + type: 'belongsTo', + name: 'node', + target: 'flow_nodes', + }, + { + type: 'belongsTo', + name: 'job', + target: 'jobs', + }, + ], +}); + +export default WorkflowNotice; diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/server/constants.ts b/packages/plugins/@tachybase/plugin-workflow-notice/src/server/constants.ts new file mode 100644 index 000000000..16caad660 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/server/constants.ts @@ -0,0 +1,16 @@ +export const NOTICE_STATUS = { + DRAFT: 0, + RETURNED: 1, + SUBMITTED: 2, + PROCESSING: 3, + APPROVED: 4, + REJECTED: -1, +}; +export const NOTICE_ACTION_STATUS = { + ASSIGNED: null, + PENDING: 0, + RETURNED: 1, + APPROVED: 2, + REJECTED: -1, + CANCELED: -2, +}; diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/server/index.ts b/packages/plugins/@tachybase/plugin-workflow-notice/src/server/index.ts new file mode 100644 index 000000000..b68aea57f --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/server/index.ts @@ -0,0 +1 @@ +export { default } from './plugin'; diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/server/plugin.ts b/packages/plugins/@tachybase/plugin-workflow-notice/src/server/plugin.ts new file mode 100644 index 000000000..7cdc214ec --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/server/plugin.ts @@ -0,0 +1,49 @@ +import WorkflowPlugin from '@tachybase/plugin-workflow'; +import { Plugin } from '@tachybase/server'; +import path from 'path'; +import { NOTICE_INSTRUCTION_NAMESPACE } from '../common/constants'; +import NoticeInstruction from './NoticeInstruction'; +import { init } from './actions'; +import { COLLECTION_WORKFLOWS_NAME } from './collections/workflowNotice'; +import { COLLECTION_NOTICE_NAME } from '../common/constants'; + +export class PluginWorkflowNoticeServer extends Plugin { + workflow; + async afterAdd() {} + + async beforeLoad() { + this.app.on('afterLoadPlugin', (plugin) => { + if (!(plugin instanceof WorkflowPlugin)) { + return; + } + this.workflow = plugin; + plugin.instructions.register(NOTICE_INSTRUCTION_NAMESPACE, new NoticeInstruction(plugin)); + }); + } + + async load() { + const { db } = this; + db.addMigrations({ + namespace: NOTICE_INSTRUCTION_NAMESPACE, + directory: path.resolve(__dirname, 'migrations'), + context: { + plugin: this, + }, + }); + await db.import({ + directory: path.resolve(__dirname, 'collections'), + }); + + init(this); + + this.app.acl.allow(COLLECTION_WORKFLOWS_NAME, ['listWorkflowNoticeFlows'], 'loggedIn'); + this.app.acl.allow(COLLECTION_NOTICE_NAME, ['get', 'list', 'listCentralized', 'submit'], 'loggedIn'); + } + + async install() {} + async afterEnable() {} + async afterDisable() {} + async remove() {} +} + +export default PluginWorkflowNoticeServer; diff --git a/packages/plugins/@tachybase/plugin-workflow-notice/src/server/tools.ts b/packages/plugins/@tachybase/plugin-workflow-notice/src/server/tools.ts new file mode 100644 index 000000000..34c7991fb --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow-notice/src/server/tools.ts @@ -0,0 +1,138 @@ +import { JOB_STATUS } from '@tachybase/plugin-workflow'; +import _ from 'lodash'; +import { NOTICE_ACTION_STATUS } from './constants'; + +interface ParamsType { + summaryConfig: Array; + data: object; +} + +export function getSummary(params: ParamsType): object { + const { summaryConfig = [], data } = params; + + const result = summaryConfig.reduce((summary, key) => { + const value = _.get(data, key); + return { + ...summary, + [key]: value, + }; + }, {}); + + return result; +} + +export async function parsePerson(node, processor) { + const configPerson = processor + .getParsedValue(node.config.notifiedPerson ?? [], node.id) + .flat() + .filter(Boolean); + + const notifiedPerson = new Set(); + const UserRepo = processor.options.plugin.app.db.getRepository('users'); + for (const item of configPerson) { + if (typeof item === 'object') { + const result = await UserRepo.find({ + ...item, + fields: ['id'], + transaction: processor.transaction, + }); + result.forEach((item2) => notifiedPerson.add(item2.id)); + } else { + notifiedPerson.add(item); + } + } + return [...notifiedPerson]; +} + +const NEGOTIATION_MODE = { + SINGLE: Symbol('single'), + ALL: Symbol('all'), + ANY: Symbol('any'), + PERCENTAGE: Symbol('percentage'), +}; +const JobStatusMap = { + [NOTICE_ACTION_STATUS.PENDING]: JOB_STATUS.PENDING, + [NOTICE_ACTION_STATUS.APPROVED]: JOB_STATUS.RESOLVED, + [NOTICE_ACTION_STATUS.REJECTED]: JOB_STATUS.REJECTED, + [NOTICE_ACTION_STATUS.RETURNED]: JOB_STATUS.RETRY_NEEDED, +}; + +const Modes = { + [NEGOTIATION_MODE.SINGLE]: { + getStatus(distribution, assignees, mode) { + const done = distribution.find((item) => item.status !== NOTICE_ACTION_STATUS.PENDING && item.count > 0); + return done ? JobStatusMap[done.status] : null; + }, + }, + [NEGOTIATION_MODE.ALL]: { + getStatus(distribution, assignees, mode) { + const rejected = distribution.find((item) => + [NOTICE_ACTION_STATUS.REJECTED, NOTICE_ACTION_STATUS.RETURNED].includes(item.status), + ); + if (rejected && rejected.count) { + return JobStatusMap[rejected.status]; + } + const approved = distribution.find((item) => item.status === NOTICE_ACTION_STATUS.APPROVED); + if (approved && approved.count === assignees.length) { + return JOB_STATUS.RESOLVED; + } + return null; + }, + }, + [NEGOTIATION_MODE.ANY]: { + getStatus(distribution, assignees, mode) { + const returned = distribution.find((item) => item.status === NOTICE_ACTION_STATUS.RETURNED); + if (returned && returned.count) { + return JOB_STATUS.RETRY_NEEDED; + } + const approved = distribution.find((item) => item.status === NOTICE_ACTION_STATUS.APPROVED); + if (approved && approved.count) { + return NOTICE_ACTION_STATUS.APPROVED; + } + const rejectedCount = distribution.reduce( + (count, item) => (item.status === NOTICE_ACTION_STATUS.REJECTED ? count + item.count : count), + 0, + ); + if (rejectedCount === assignees.length) { + return JOB_STATUS.REJECTED; + } + return null; + }, + }, + [NEGOTIATION_MODE.PERCENTAGE]: { + getStatus(distribution, assignees, mode) { + const returned = distribution.find((item) => item.status === NOTICE_ACTION_STATUS.RETURNED); + if (returned && returned.count) { + return JOB_STATUS.RETRY_NEEDED; + } + const approved = distribution.find((item) => item.status === NOTICE_ACTION_STATUS.APPROVED); + if (approved && approved.count / assignees.length > mode) { + return JOB_STATUS.RESOLVED; + } + const rejected = distribution.find((item) => item.status === NOTICE_ACTION_STATUS.REJECTED); + if (rejected && rejected.count / assignees.length >= 1 - mode) { + return JOB_STATUS.REJECTED; + } + return null; + }, + }, +}; + +export const NoticeJobStatusMap = { + [JOB_STATUS.PENDING]: NOTICE_ACTION_STATUS.PENDING, + [JOB_STATUS.RESOLVED]: NOTICE_ACTION_STATUS.APPROVED, + [JOB_STATUS.REJECTED]: NOTICE_ACTION_STATUS.REJECTED, + [JOB_STATUS.RETRY_NEEDED]: NOTICE_ACTION_STATUS.RETURNED, + [JOB_STATUS.CANCELED]: NOTICE_ACTION_STATUS.CANCELED, +}; + +export function getNegotiationMode(mode) { + switch (true) { + case mode === 1: + return Modes[NEGOTIATION_MODE.ALL]; + case 0 < mode && mode < 1: + return Modes[NEGOTIATION_MODE.PERCENTAGE]; + default: + return Modes[NEGOTIATION_MODE.SINGLE]; + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0fb9adb25..a39eecbed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3969,6 +3969,61 @@ importers: specifier: ^6.26.0 version: 6.35.2 + packages/plugins/@tachybase/plugin-workflow-notice: + dependencies: + '@tachybase/client': + specifier: workspace:* + version: link:../../../core/client + '@tachybase/plugin-workflow': + specifier: workspace:* + version: link:../plugin-workflow + '@tachybase/server': + specifier: workspace:* + version: link:../../../core/server + '@tachybase/test': + specifier: workspace:* + version: link:../../../core/test + devDependencies: + '@ant-design/icons': + specifier: 5.x + version: 5.3.7(react-dom@18.2.0)(react@18.2.0) + '@tachybase/actions': + specifier: workspace:* + version: link:../../../core/actions + '@tachybase/components': + specifier: workspace:* + version: link:../../../core/components + '@tachybase/data-source-manager': + specifier: workspace:* + version: link:../../../core/data-source-manager + '@tachybase/database': + specifier: workspace:* + version: link:../../../core/database + '@tachybase/plugin-ui-schema-storage': + specifier: workspace:* + version: link:../plugin-ui-schema-storage + '@tachybase/schema': + specifier: workspace:* + version: link:../../../core/schema + '@tachybase/utils': + specifier: workspace:* + version: link:../../../core/utils + antd: + specifier: 5.16.1 + version: 5.16.1(react-dom@18.2.0)(react@18.2.0) + lodash: + specifier: ^4.17.21 + version: 4.17.21 + react: + specifier: 18.x + version: 18.2.0 + react-i18next: + specifier: ^11.15.1 + version: 11.18.6(i18next@22.5.1)(react-dom@18.2.0)(react@18.2.0) + sequelize: + specifier: ^6.26.0 + version: 6.35.2 + packages/plugins/@tachybase/plugin-workflow-test: dependencies: '@tachybase/client': @@ -4919,6 +4974,21 @@ packages: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + /@ant-design/icons@5.3.7(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-bCPXTAg66f5bdccM4TT21SQBDO1Ek2gho9h3nO9DAKXJP4sq+5VBjrQMSxMVXSB3HyEz+cUbHQ5+6ogxCOpaew==} + engines: {node: '>=8'} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + dependencies: + '@ant-design/colors': 7.0.2 + '@ant-design/icons-svg': 4.4.2 + '@babel/runtime': 7.24.5 + classnames: 2.5.1 + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + /@ant-design/plots@2.1.14(lodash-es@4.17.21)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-ZFhvlNUsPcvD8d+nX4bXA9djbEUDVfKm8/xvFplffWT68rWM37d32qI7EghSvLUreszqh60DZAy80wCA08lgoQ==} peerDependencies: @@ -4972,7 +5042,7 @@ packages: '@babel/runtime': 7.24.5 '@ctrl/tinycolor': 3.6.1 antd: 5.16.1(react-dom@18.2.0)(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) swr: 2.2.4(react@18.2.0) @@ -4984,14 +5054,14 @@ packages: react: '>=17.0.0' react-dom: '>=17.0.0' dependencies: - '@ant-design/icons': 5.3.6(react-dom@18.2.0)(react@18.2.0) + '@ant-design/icons': 5.3.7(react-dom@18.2.0)(react@18.2.0) '@ant-design/pro-provider': 2.13.5(antd@5.16.1)(react-dom@18.2.0)(react@18.2.0) '@babel/runtime': 7.24.5 antd: 5.16.1(react-dom@18.2.0)(react@18.2.0) classnames: 2.5.1 dayjs: 1.11.10 lodash.merge: 4.6.2 - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) safe-stable-stringify: 2.4.3 @@ -10506,7 +10576,7 @@ packages: '@babel/runtime': 7.24.5 '@ctrl/tinycolor': 3.6.1 classnames: 2.5.1 - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -10517,7 +10587,7 @@ packages: react-dom: '>=16.9.0' dependencies: '@babel/runtime': 7.24.5 - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -10536,7 +10606,7 @@ packages: dependencies: '@babel/runtime': 7.24.5 classnames: 2.5.1 - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -10549,7 +10619,7 @@ packages: dependencies: '@babel/runtime': 7.24.5 classnames: 2.5.1 - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -10562,9 +10632,9 @@ packages: dependencies: '@babel/runtime': 7.24.5 '@rc-component/portal': 1.1.2(react-dom@18.2.0)(react@18.2.0) - '@rc-component/trigger': 2.1.0(react-dom@18.2.0)(react@18.2.0) + '@rc-component/trigger': 2.1.1(react-dom@18.2.0)(react@18.2.0) classnames: 2.5.1 - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -10578,9 +10648,9 @@ packages: '@babel/runtime': 7.24.5 '@rc-component/portal': 1.1.2(react-dom@18.2.0)(react@18.2.0) classnames: 2.5.1 - rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0) + rc-motion: 2.9.1(react-dom@18.2.0)(react@18.2.0) rc-resize-observer: 1.4.0(react-dom@18.2.0)(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: false @@ -10595,9 +10665,25 @@ packages: '@babel/runtime': 7.24.5 '@rc-component/portal': 1.1.2(react-dom@18.2.0)(react@18.2.0) classnames: 2.5.1 - rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0) + rc-motion: 2.9.1(react-dom@18.2.0)(react@18.2.0) rc-resize-observer: 1.4.0(react-dom@18.2.0)(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + + /@rc-component/trigger@2.1.1(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-UjHkedkgtEcgQu87w1VuWug1idoDJV7VUt0swxHXRcmei2uu1AuUzGBPEUlmOmXGJ+YtTgZfVLi7kuAUKoZTMA==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.24.5 + '@rc-component/portal': 1.1.2(react-dom@18.2.0)(react@18.2.0) + classnames: 2.5.1 + rc-motion: 2.9.1(react-dom@18.2.0)(react@18.2.0) + rc-resize-observer: 1.4.0(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -14276,7 +14362,7 @@ packages: dependencies: '@ant-design/colors': 7.0.2 '@ant-design/cssinjs': 1.20.0(react-dom@18.2.0)(react@18.2.0) - '@ant-design/icons': 5.3.6(react-dom@18.2.0)(react@18.2.0) + '@ant-design/icons': 5.3.7(react-dom@18.2.0)(react@18.2.0) '@ant-design/react-slick': 1.1.2(react@18.2.0) '@babel/runtime': 7.24.5 '@ctrl/tinycolor': 3.6.1 @@ -25654,7 +25740,7 @@ packages: '@babel/runtime': 7.24.5 classnames: 2.5.1 dom-align: 1.12.4 - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) resize-observer-polyfill: 1.5.1 @@ -25670,8 +25756,8 @@ packages: array-tree-filter: 2.1.0 classnames: 2.5.1 rc-select: 14.13.0(react-dom@18.2.0)(react@18.2.0) - rc-tree: 5.8.5(react-dom@18.2.0)(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-tree: 5.8.7(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -25683,7 +25769,7 @@ packages: dependencies: '@babel/runtime': 7.24.5 classnames: 2.5.1 - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -25695,8 +25781,8 @@ packages: dependencies: '@babel/runtime': 7.24.5 classnames: 2.5.1 - rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-motion: 2.9.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -25709,8 +25795,8 @@ packages: '@babel/runtime': 7.24.5 '@rc-component/portal': 1.1.2(react-dom@18.2.0)(react@18.2.0) classnames: 2.5.1 - rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-motion: 2.9.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -25723,8 +25809,8 @@ packages: '@babel/runtime': 7.24.5 '@rc-component/portal': 1.1.2(react-dom@18.2.0)(react@18.2.0) classnames: 2.5.1 - rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-motion: 2.9.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -25735,9 +25821,9 @@ packages: react-dom: '>=16.11.0' dependencies: '@babel/runtime': 7.24.5 - '@rc-component/trigger': 2.1.0(react-dom@18.2.0)(react@18.2.0) + '@rc-component/trigger': 2.1.1(react-dom@18.2.0)(react@18.2.0) classnames: 2.5.1 - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -25750,7 +25836,7 @@ packages: dependencies: '@babel/runtime': 7.24.5 async-validator: 4.2.5 - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -25763,7 +25849,7 @@ packages: dependencies: '@babel/runtime': 7.24.5 async-validator: 4.2.5 - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -25777,8 +25863,8 @@ packages: '@rc-component/portal': 1.1.2(react-dom@18.2.0)(react@18.2.0) classnames: 2.5.1 rc-dialog: 9.4.0(react-dom@18.2.0)(react@18.2.0) - rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-motion: 2.9.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -25792,7 +25878,7 @@ packages: '@rc-component/mini-decimal': 1.1.0 classnames: 2.5.1 rc-input: 1.4.5(react-dom@18.2.0)(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -25804,7 +25890,7 @@ packages: dependencies: '@babel/runtime': 7.24.5 classnames: 2.5.1 - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -25815,12 +25901,12 @@ packages: react-dom: '>=16.9.0' dependencies: '@babel/runtime': 7.24.5 - '@rc-component/trigger': 2.1.0(react-dom@18.2.0)(react@18.2.0) + '@rc-component/trigger': 2.1.1(react-dom@18.2.0)(react@18.2.0) classnames: 2.5.1 rc-input: 1.4.5(react-dom@18.2.0)(react@18.2.0) rc-menu: 9.13.0(react-dom@18.2.0)(react@18.2.0) rc-textarea: 1.6.3(react-dom@18.2.0)(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -25831,11 +25917,11 @@ packages: react-dom: '>=16.9.0' dependencies: '@babel/runtime': 7.24.5 - '@rc-component/trigger': 2.1.0(react-dom@18.2.0)(react@18.2.0) + '@rc-component/trigger': 2.1.1(react-dom@18.2.0)(react@18.2.0) classnames: 2.5.1 - rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0) + rc-motion: 2.9.1(react-dom@18.2.0)(react@18.2.0) rc-overflow: 1.3.2(react-dom@18.2.0)(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -25847,7 +25933,19 @@ packages: dependencies: '@babel/runtime': 7.24.5 classnames: 2.5.1 - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + + /rc-motion@2.9.1(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-QD4bUqByjVQs7PhUT1d4bNxvtTcK9ETwtg7psbDfo6TmYalH/1hhjj4r2hbhW7g5OOEqYHhfwfj4noIvuOVRtQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.24.5 + classnames: 2.5.1 + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -25860,8 +25958,8 @@ packages: dependencies: '@babel/runtime': 7.24.5 classnames: 2.5.1 - rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-motion: 2.9.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -25874,7 +25972,7 @@ packages: '@babel/runtime': 7.24.5 classnames: 2.5.1 rc-resize-observer: 1.4.0(react-dom@18.2.0)(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -25886,7 +25984,7 @@ packages: dependencies: '@babel/runtime': 7.24.5 classnames: 2.5.1 - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -25911,12 +26009,12 @@ packages: optional: true dependencies: '@babel/runtime': 7.24.5 - '@rc-component/trigger': 2.1.0(react-dom@18.2.0)(react@18.2.0) + '@rc-component/trigger': 2.1.1(react-dom@18.2.0)(react@18.2.0) classnames: 2.5.1 dayjs: 1.11.10 rc-overflow: 1.3.2(react-dom@18.2.0)(react@18.2.0) rc-resize-observer: 1.4.0(react-dom@18.2.0)(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -25928,7 +26026,7 @@ packages: dependencies: '@babel/runtime': 7.24.5 classnames: 2.5.1 - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -25941,7 +26039,7 @@ packages: dependencies: '@babel/runtime': 7.24.5 classnames: 2.5.1 - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -25953,7 +26051,7 @@ packages: dependencies: '@babel/runtime': 7.24.5 classnames: 2.5.1 - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) resize-observer-polyfill: 1.5.1 @@ -25966,8 +26064,8 @@ packages: dependencies: '@babel/runtime': 7.24.5 classnames: 2.5.1 - rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-motion: 2.9.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -25980,10 +26078,10 @@ packages: dependencies: '@babel/runtime': 7.24.5 classnames: 2.5.1 - rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0) + rc-motion: 2.9.1(react-dom@18.2.0)(react@18.2.0) rc-overflow: 1.3.2(react-dom@18.2.0)(react@18.2.0) rc-trigger: 5.3.4(react-dom@18.2.0)(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) rc-virtual-list: 3.11.4(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -26001,7 +26099,7 @@ packages: classnames: 2.5.1 rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0) rc-overflow: 1.3.2(react-dom@18.2.0)(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) rc-virtual-list: 3.11.3(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -26015,11 +26113,11 @@ packages: react-dom: '*' dependencies: '@babel/runtime': 7.24.5 - '@rc-component/trigger': 2.1.0(react-dom@18.2.0)(react@18.2.0) + '@rc-component/trigger': 2.1.1(react-dom@18.2.0)(react@18.2.0) classnames: 2.5.1 - rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0) + rc-motion: 2.9.1(react-dom@18.2.0)(react@18.2.0) rc-overflow: 1.3.2(react-dom@18.2.0)(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) rc-virtual-list: 3.11.4(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -26033,7 +26131,7 @@ packages: dependencies: '@babel/runtime': 7.24.5 classnames: 2.5.1 - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -26046,7 +26144,7 @@ packages: dependencies: '@babel/runtime': 7.24.5 classnames: 2.5.1 - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -26058,7 +26156,7 @@ packages: dependencies: '@babel/runtime': 7.24.5 classnames: 2.5.1 - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -26073,7 +26171,7 @@ packages: '@rc-component/context': 1.4.0(react-dom@18.2.0)(react@18.2.0) classnames: 2.5.1 rc-resize-observer: 1.4.0(react-dom@18.2.0)(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) rc-virtual-list: 3.11.4(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -26089,9 +26187,9 @@ packages: classnames: 2.5.1 rc-dropdown: 4.2.0(react-dom@18.2.0)(react@18.2.0) rc-menu: 9.13.0(react-dom@18.2.0)(react@18.2.0) - rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0) + rc-motion: 2.9.1(react-dom@18.2.0)(react@18.2.0) rc-resize-observer: 1.4.0(react-dom@18.2.0)(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -26105,7 +26203,7 @@ packages: classnames: 2.5.1 rc-input: 1.4.5(react-dom@18.2.0)(react@18.2.0) rc-resize-observer: 1.4.0(react-dom@18.2.0)(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -26116,7 +26214,7 @@ packages: react-dom: '>=16.9.0' dependencies: '@babel/runtime': 7.24.5 - '@rc-component/trigger': 2.1.0(react-dom@18.2.0)(react@18.2.0) + '@rc-component/trigger': 2.1.1(react-dom@18.2.0)(react@18.2.0) classnames: 2.5.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -26145,8 +26243,8 @@ packages: '@babel/runtime': 7.24.5 classnames: 2.5.1 rc-select: 14.13.0(react-dom@18.2.0)(react@18.2.0) - rc-tree: 5.8.5(react-dom@18.2.0)(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-tree: 5.8.7(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -26174,8 +26272,8 @@ packages: dependencies: '@babel/runtime': 7.24.5 classnames: 2.5.1 - rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-motion: 2.9.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) rc-virtual-list: 3.11.4(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -26191,7 +26289,7 @@ packages: '@babel/runtime': 7.24.5 classnames: 2.5.1 rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) rc-virtual-list: 3.11.3(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -26206,8 +26304,23 @@ packages: dependencies: '@babel/runtime': 7.24.5 classnames: 2.5.1 - rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-motion: 2.9.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) + rc-virtual-list: 3.11.4(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + + /rc-tree@5.8.7(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-cpsIQZ4nNYwpj6cqPRt52e/69URuNdgQF9wZ10InmEf8W3+i0A41OVmZWwHuX9gegQSqj+DPmaDkZFKQZ+ZV1w==} + engines: {node: '>=10.x'} + peerDependencies: + react: '*' + react-dom: '*' + dependencies: + '@babel/runtime': 7.24.5 + classnames: 2.5.1 + rc-motion: 2.9.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) rc-virtual-list: 3.11.4(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -26222,8 +26335,8 @@ packages: '@babel/runtime': 7.24.5 classnames: 2.5.1 rc-align: 4.0.15(react-dom@18.2.0)(react@18.2.0) - rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-motion: 2.9.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true @@ -26236,7 +26349,7 @@ packages: dependencies: '@babel/runtime': 7.24.5 classnames: 2.5.1 - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -26262,6 +26375,17 @@ packages: react-dom: 18.2.0(react@18.2.0) react-is: 18.3.1 + /rc-util@5.41.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-xtlCim9RpmVv0Ar2Nnc3WfJCxjQkTf3xHPWoFdjp1fSs2NirQwqiQrfqdU9HUe0kdfb168M/T8Dq0IaX50xeKg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + dependencies: + '@babel/runtime': 7.24.5 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-is: 18.3.1 + /rc-virtual-list@3.11.3(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-tu5UtrMk/AXonHwHxUogdXAWynaXsrx1i6dsgg+lOo/KJSF8oBAcprh1z5J3xgnPJD5hXxTL58F8s8onokdt0Q==} engines: {node: '>=8.x'} @@ -26272,7 +26396,7 @@ packages: '@babel/runtime': 7.24.5 classnames: 2.5.1 rc-resize-observer: 1.4.0(react-dom@18.2.0)(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: false @@ -26287,7 +26411,7 @@ packages: '@babel/runtime': 7.24.5 classnames: 2.5.1 rc-resize-observer: 1.4.0(react-dom@18.2.0)(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.41.0(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0)