diff --git a/README.md b/README.md index b06b4727..7eeba77b 100644 --- a/README.md +++ b/README.md @@ -291,7 +291,7 @@ pnpm run dev:app-mobile ### 八层架构 1. **流程层** (Workflow Layer) - 流程定义层 -2. **节点层** (Node Layer) - 节点层(12种节点类型) +2. **节点层** (Node Layer) - 节点层(15种节点类型) 3. **动作层** (Action Layer) - 动作层(8种动作类型) 4. **记录层** (Record Layer) - 记录层 5. **会话层** (Session Layer) - 会话层 diff --git a/designs/view-plugin/DESIGN.md b/designs/view-plugin/DESIGN.md new file mode 100644 index 00000000..28c3ea48 --- /dev/null +++ b/designs/view-plugin/DESIGN.md @@ -0,0 +1,281 @@ +# 界面拓展机制 + +支持流程组件对各类业务层面内容的界面拓展机制。 + +## 实现方式 + +``` +import {ViewBindPlugin} from "@flow-engine/flow-core"; + +// 界面视图 +const MyView:React.FC = (props)=>{ + return ( + <> + ) +} + +// 注册,关键信息为key 和 界面ComponentType,传递的属性根据不同的界面对应查看 +ViewBindPlugin.getInstance().register('MyView',MyView) +``` + +## 拓展界面 + +### 流程审核 + +* 表单渲染 +``` +export interface FormViewProps { + /** 表单操控对象 */ + form: FormInstance; + /** 表单数据更新事件 */ + onValuesChange?: (values: any) => void; + /** 表单元数据对象 */ + meta: FlowForm; + /** 是否预览模式 */ + review:boolean; +} + +``` +表单选择的key对应流程节点设置的view名称,流程引擎对default进行了模型的渲染支持。 + +* 流程操作 加签 + +``` +export const VIEW_KEY = 'AddAuditViewPlugin'; + +export interface AddAuditViewPlugin { + /** 返回用户 */ + onChange?: (value: string|string[]) => void; + /** 当前用户 */ + value?: string|string[]; +} +``` +* 流程操作 委派 +``` +export const VIEW_KEY = 'DelegateViewPlugin'; + +export interface DelegateViewPlugin { + /** 返回用户 */ + onChange?: (value: string|string[]) => void; + /** 当前用户 */ + value?: string|string[]; +} +``` + +* 流程操作 退回流程 +``` +export const VIEW_KEY = 'ReturnViewPlugin'; + +export interface ReturnViewPlugin { + /** 返回用户 */ + onChange?: (value: string|string[]) => void; + /** 当前用户 */ + value?: string|string[]; +} +``` + +* 流程操作 提交时的获取签名界面 +``` +import {FlowOperator} from "@flow-engine/flow-types"; + +export const VIEW_KEY = 'SignKeyViewPlugin'; + +export interface SignKeyViewPlugin { + /** 当前用户 */ + current: FlowOperator; + /** 返回签名 */ + onChange?: (value: string) => void; + /** 当前签名 */ + value?: string; +} +``` + +* 流程操作 转办操作 +``` +export const VIEW_KEY = 'TransferViewPlugin'; + +export interface TransferViewPlugin { + /** 返回用户 */ + onChange?: (value: string|string[]) => void; + /** 当前用户 */ + value?: string|string[]; +} +``` + +### 流程设计-节点配置 + +* 流程条件控制界面 +``` +import {GroovyVariableMapping, ScriptType} from "@/components/script/typings"; + +export const VIEW_KEY = 'ConditionViewPlugin'; + +export interface ConditionViewPlugin { + /** 脚本类型 */ + type: ScriptType; + /** 当前脚本 */ + script: string; + /** 变量映射列表 */ + variables: GroovyVariableMapping[]; + /** 确认回调 */ + onChange: (script: string) => void; +} +``` + +* 异常处理逻辑界面 +``` +import {GroovyVariableMapping, ScriptType} from "@/components/script/typings"; + +export const VIEW_KEY = 'ErrorTriggerViewPlugin'; + +export interface ErrorTriggerViewPlugin { + /** 脚本类型 */ + type: ScriptType; + /** 当前脚本 */ + script: string; + /** 变量映射列表 */ + variables: GroovyVariableMapping[]; + /** 确认回调 */ + onChange: (script: string) => void; +} +``` + +* 自定义标题界面 +``` +import {GroovyVariableMapping, ScriptType} from "@/components/script/typings"; + +export const VIEW_KEY = 'NodeTitleViewPlugin'; + +export interface NodeTitleViewPlugin { + /** 脚本类型 */ + type: ScriptType; + /** 当前脚本 */ + script: string; + /** 变量映射列表 */ + variables: GroovyVariableMapping[]; + /** 确认回调 */ + onChange: (script: string) => void; +} +``` + +* 设置发起人范围界面 + +``` +export const VIEW_KEY = 'OperatorCreateViewPlugin'; + +export interface OperatorCreateViewPlugin { + /** 当前脚本 */ + script: string; + /** 确认回调 */ + onChange: (script: string) => void; +} +``` + +* 节点人员选择界面 + +``` +export const VIEW_KEY = 'OperatorLoadViewPlugin'; + +export interface OperatorLoadViewPlugin { + /** 当前脚本 */ + script: string; + /** 确认回调 */ + onChange: (script: string) => void; +} +``` +* 路由配置界面 + +``` +import {GroovyVariableMapping, ScriptType} from "@/components/script/typings"; + +export const VIEW_KEY = 'RouterViewPlugin'; + +export interface RouterViewPlugin { + /** 脚本类型 */ + type: ScriptType; + /** 当前脚本 */ + script: string; + /** 变量映射列表 */ + variables: GroovyVariableMapping[]; + /** 确认回调 */ + onChange: (script: string) => void; +} +``` +* 子流程配置界面 +``` +import {GroovyVariableMapping, ScriptType} from "@/components/script/typings"; + +export const VIEW_KEY = 'SubProcessViewPlugin'; + +export interface SubProcessViewPlugin { + /** 脚本类型 */ + type: ScriptType; + /** 当前脚本 */ + script: string; + /** 变量映射列表 */ + variables: GroovyVariableMapping[]; + /** 确认回调 */ + onChange: (script: string) => void; +} +``` +* 触发流程界面 +``` +import {GroovyVariableMapping, ScriptType} from "@/components/script/typings"; + +export const VIEW_KEY = 'TriggerViewPlugin'; + +export interface TriggerViewPlugin { + /** 脚本类型 */ + type: ScriptType; + /** 当前脚本 */ + script: string; + /** 变量映射列表 */ + variables: GroovyVariableMapping[]; + /** 确认回调 */ + onChange: (script: string) => void; +} +``` + +### 流程设计-动作配置 + +* 自定义按钮触发脚本界面 +``` +import {ActionSelectOption} from "@/components/script/typings"; + +export const VIEW_KEY = 'ConditionCustomViewPlugin'; + +export interface ConditionCustomViewPlugin { + // 当前的脚本 + value?: string; + // 脚本更改回掉 + onChange?: (value: string) => void; + // 可选择的动作范围 + options:ActionSelectOption[]; +} +``` + +* 拒绝动作界面 +``` +export const VIEW_KEY = 'ConditionRejectViewPlugin'; + +export interface ConditionRejectViewPlugin { + // 当前节点id + nodeId:string; + // 当前的脚本 + value?: string; + // 脚本更改回掉 + onChange?: (value: string) => void; +} +``` + +* 委派/转办/加签/界面 与(节点人员选择界面一致) +``` +export const VIEW_KEY = 'OperatorLoadViewPlugin'; + +export interface OperatorLoadViewPlugin { + /** 当前脚本 */ + script: string; + /** 确认回调 */ + onChange: (script: string) => void; +} +``` \ No newline at end of file diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/action/actions/CustomAction.java b/flow-engine-framework/src/main/java/com/codingapi/flow/action/actions/CustomAction.java index c54d737e..33738fad 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/action/actions/CustomAction.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/action/actions/CustomAction.java @@ -36,7 +36,7 @@ public CustomAction() { this.enable = true; this.type = ActionType.CUSTOM.name(); this.display = new ActionDisplay(this.title); - this.script = CustomScript.defaultCustomScript(); + this.script = CustomScript.defaultScript(); } @Override diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/action/actions/RejectAction.java b/flow-engine-framework/src/main/java/com/codingapi/flow/action/actions/RejectAction.java index 39d9dc6f..9441b241 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/action/actions/RejectAction.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/action/actions/RejectAction.java @@ -35,7 +35,7 @@ public RejectAction() { this.enable = true; this.type = ActionType.REJECT.name(); this.display = new ActionDisplay(this.title); - this.script = RejectActionScript.startScript(); + this.script = RejectActionScript.defaultScript(); } @Override diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/BaseFlowNode.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/BaseFlowNode.java index 7837ae96..02808a7a 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/node/BaseFlowNode.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/node/BaseFlowNode.java @@ -147,6 +147,7 @@ public Map toMap() { map.put("id", id); map.put("name", name); map.put("type", getType()); + map.put("display",this instanceof IDisplayNode); map.put("order", String.valueOf(order)); if (this.blocks != null && !this.blocks.isEmpty()) { map.put("blocks", blocks.stream().map(IFlowNode::toMap).toList()); diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/script/ScriptDefaultConstants.java b/flow-engine-framework/src/main/java/com/codingapi/flow/script/ScriptDefaultConstants.java index c5dcdd88..ec0714f7 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/script/ScriptDefaultConstants.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/script/ScriptDefaultConstants.java @@ -5,6 +5,28 @@ */ public class ScriptDefaultConstants { + /** + * 默认自定义动作脚本 + */ + public static final String SCRIPT_DEFAULT_ACTION_CUSTOM = """ + // @SCRIPT_TITLE 默认条件 触发通过 + def run(request){ + return 'PASS'; + } + """; + + + /** + * 默认拒绝动作脚本 + */ + public static final String SCRIPT_DEFAULT_ACTION_REJECT = """ + // @SCRIPT_TITLE 返回开始节点 + // @SCRIPT_META {"type":"START"} + def run(request){ + return request.getStartNode().getId(); + } + """; + /** * 默认条件脚本 */ diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/script/action/CustomScript.java b/flow-engine-framework/src/main/java/com/codingapi/flow/script/action/CustomScript.java index 6fb06e5d..21c3a1cd 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/script/action/CustomScript.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/script/action/CustomScript.java @@ -1,5 +1,6 @@ package com.codingapi.flow.script.action; +import com.codingapi.flow.script.ScriptDefaultConstants; import com.codingapi.flow.script.runtime.ScriptRuntimeContext; import com.codingapi.flow.session.FlowSession; import lombok.AllArgsConstructor; @@ -11,8 +12,6 @@ @AllArgsConstructor public class CustomScript { - public static final String SCRIPT_DEFAULT = "def run(session){return 'PASS'}"; - @Getter private final String script; @@ -26,8 +25,8 @@ public String execute(FlowSession session) { /** * 默认节点脚本 */ - public static CustomScript defaultCustomScript() { - return new CustomScript(SCRIPT_DEFAULT); + public static CustomScript defaultScript() { + return new CustomScript(ScriptDefaultConstants.SCRIPT_DEFAULT_ACTION_CUSTOM); } } diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/script/action/RejectActionScript.java b/flow-engine-framework/src/main/java/com/codingapi/flow/script/action/RejectActionScript.java index 599f0429..dc2d4421 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/script/action/RejectActionScript.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/script/action/RejectActionScript.java @@ -1,5 +1,6 @@ package com.codingapi.flow.script.action; +import com.codingapi.flow.script.ScriptDefaultConstants; import com.codingapi.flow.script.runtime.ScriptRuntimeContext; import com.codingapi.flow.session.FlowSession; import lombok.AllArgsConstructor; @@ -12,28 +13,22 @@ @AllArgsConstructor public class RejectActionScript { - public static final String SCRIPT_START = "def run(session){return new com.codingapi.flow.script.action.RejectActionScript.RejectResult(session.getStartNode().getId())}"; - public static final String SCRIPT_TERMINATE = "def run(session){return new com.codingapi.flow.script.action.RejectActionScript.RejectResult(\"TERMINATE\")}"; + public static final String TYPE_TERMINATE = "TERMINATE"; + @Getter private final String script; public RejectResult execute(FlowSession session) { - return ScriptRuntimeContext.getInstance().run(script, RejectResult.class, session); + String result = ScriptRuntimeContext.getInstance().run(script, String.class, session); + return new RejectResult(result); } /** * 退回至发起节点 */ - public static RejectActionScript startScript() { - return new RejectActionScript(SCRIPT_START); - } - - /** - * 终止流程 - */ - public static RejectActionScript terminateScript() { - return new RejectActionScript(SCRIPT_TERMINATE); + public static RejectActionScript defaultScript() { + return new RejectActionScript(ScriptDefaultConstants.SCRIPT_DEFAULT_ACTION_REJECT); } @@ -58,7 +53,7 @@ public boolean isTerminate() { } public RejectResult(String result) { - if (result.equals("TERMINATE")) { + if (result.equals(TYPE_TERMINATE)) { this.type = RejectType.TERMINATE; } else { this.type = RejectType.RETURN_NODE; diff --git a/frontend/packages/flow-core/README.md b/frontend/packages/flow-core/README.md index 6e456844..d9e70d30 100644 --- a/frontend/packages/flow-core/README.md +++ b/frontend/packages/flow-core/README.md @@ -1,19 +1,20 @@ # @flow-engine/flow-core -Flow Engine 前端核心库,提供与后端 API 交互的基础功能。 +Flow Engine 前端核心框架库,提供 HTTP 客户端、Hooks、Presenter 等基础能力(不包含 UI 组件)。 ## 简介 -`flow-core` 是 Flow Engine 的核心前端库,包含: +`flow-core` 是 Flow Engine 的核心框架库,提供与 UI 无关的基础能力: -- HTTP 客户端封装 (基于 axios) -- API 服务接口定义 -- 通用类型和工具函数 +- HTTP 客户端封装(基于 axios) +- React Hooks 工具 +- Presenter 模式实现 +- 通用工具函数 +- 全局状态管理 -### 核心依赖 +### 依赖关系 -- `axios` - HTTP 客户端 -- `react` + `react-dom` - React 框架 +- **依赖**: 无 ## Setup @@ -39,25 +40,92 @@ pnpm run dev ## 核心功能 -### API 服务 +### HTTP 客户端 -提供与 Flow Engine 后端交互的 API 接口: +基于 axios 封装的 HTTP 客户端,提供: -- 流程定义 API -- 流程实例 API -- 待办/已办 API -- 流程操作 API +- 请求拦截器/响应拦截器 +- 统一的错误处理 +- 类型安全的请求/响应 +- 请求取消支持 -### HTTP 客户端 +```typescript +import { httpClient } from '@flow-engine/flow-core'; -基于 axios 封装的 HTTP 客户端,支持: +// GET 请求 +const workflow = await httpClient.get('/api/workflows/1'); -- 请求拦截 -- 响应拦截 -- 错误处理 -- 类型安全的请求/响应 +// POST 请求 +const result = await httpClient.post('/api/workflows', workflowData); +``` + +### React Hooks + +提供常用的业务 Hooks: + +- `useWorkflow` - 工作流相关操作 +- `useFlowRecord` - 流程记录相关操作 +- `useApproval` - 审批相关操作 +- 自定义 Hooks 工具 + +### Presenter 模式 + +实现业务逻辑与 UI 分离的 Presenter 模式: + +```typescript +import { Presenter } from '@flow-engine/flow-core'; + +class WorkflowPresenter extends Presenter { + async loadWorkflow(id: string): Promise { + // 加载工作流 + } + + async saveWorkflow(workflow: Workflow): Promise { + // 保存工作流 + } +} +``` + +### 工具函数 + +提供通用工具函数: + +- 日期格式化 +- 字符串处理 +- 对象深拷贝 +- 类型判断 + +## 模块结构 + +``` +flow-core/ +├── src/ +│ ├── http/ # HTTP 客户端 +│ ├── hooks/ # React Hooks +│ ├── presenter/ # Presenter 基类 +│ ├── utils/ # 工具函数 +│ └── types/ # 基础类型定义 +└── README.md +``` + +## 使用示例 + +```typescript +import { httpClient, useWorkflow, WorkflowPresenter } from '@flow-engine/flow-core'; + +// 使用 HTTP 客户端 +const workflows = await httpClient.get('/api/workflows'); + +// 使用 Hooks +const { data, loading, error } = useWorkflow('wf-001'); + +// 使用 Presenter +const presenter = new WorkflowPresenter(); +await presenter.loadWorkflow('wf-001'); +``` ## Learn more - [Rslib documentation](https://lib.rsbuild.io/) - Rslib 特性和 API - [Flow Engine Docs](https://github.com/codingapi/flow-engine) - 完整文档 +- [CLAUDE.md](../../CLAUDE.md) - 开发指南 diff --git a/frontend/packages/flow-pc/flow-pc-approval/README.md b/frontend/packages/flow-pc/flow-pc-approval/README.md index 84d85101..f3f11922 100644 --- a/frontend/packages/flow-pc/flow-pc-approval/README.md +++ b/frontend/packages/flow-pc/flow-pc-approval/README.md @@ -1,36 +1,261 @@ -# Rsbuild project +# @flow-engine/flow-pc-approval + +Flow Engine PC 端审批组件库,提供待办/已办/审批处理等功能。 + +## 简介 + +`flow-pc-approval` 是 Flow Engine PC 端的审批页面组件库,提供: + +- 待办列表(我的待办) +- 已办列表(我的已办) +- 我发起的(我的申请) +- 审批处理(审批操作) +- 流程详情查看 +- 审批记录展示 + +### 依赖关系 + +- **依赖**: `@flow-engine/flow-pc-design`, `@flow-engine/flow-pc-ui` ## Setup -Install the dependencies: +安装依赖: ```bash pnpm install ``` -## Get started +## 开发 -Start the dev server, and the app will be available at [http://localhost:3000](http://localhost:3000). +构建组件库: ```bash -pnpm run dev +pnpm run build ``` -Build the app for production: +监听模式构建: ```bash -pnpm run build +pnpm run dev ``` -Preview the production build locally: +## 核心功能 -```bash -pnpm run preview +### 待办列表 + +我的待办任务列表: + +- 任务筛选(按状态、类型、时间等) +- 任务搜索 +- 批量操作 +- 分页加载 + +```typescript +import { TodoList } from '@flow-engine/flow-pc-approval'; + + ``` -## Learn more +### 已办列表 + +我的已办任务列表: + +- 历史记录查询 +- 筛选和搜索 +- 查看审批详情 + +```typescript +import { DoneList } from '@flow-engine/flow-pc-approval'; + + +``` + +### 我发起的 + +我发起的流程列表: + +- 流程状态查看 +- 流程跟踪 +- 撤回/取消操作 + +```typescript +import { MyRequestsList } from '@flow-engine/flow-pc-approval'; + + +``` + +### 审批处理 + +审批操作组件: + +- 表单展示(只读/编辑) +- 审批意见填写 +- 审批操作(通过/拒绝/保存/转交/退回/委托) +- 附件上传 + +```typescript +import { ApprovalForm } from '@flow-engine/flow-pc-approval'; + + +``` -To learn more about Rsbuild, check out the following resources: +### 流程详情 + +流程详情查看: + +- 流程基本信息 +- 节点状态展示 +- 审批记录时间轴 +- 表单数据展示 + +```typescript +import { FlowDetail } from '@flow-engine/flow-pc-approval'; + + +``` + +### 审批记录 + +审批记录展示: + +- 时间轴展示 +- 审批人信息 +- 审批意见 +- 审批时间 +- 操作记录 + +```typescript +import { ApprovalTimeline } from '@flow-engine/flow-pc-approval'; + + +``` + +## 模块结构 + +``` +flow-pc-approval/ +├── src/ +│ ├── todo/ # 待办列表 +│ ├── done/ # 已办列表 +│ ├── my-requests/ # 我发起的 +│ ├── approval-form/ # 审批表单 +│ ├── flow-detail/ # 流程详情 +│ ├── timeline/ # 审批记录时间轴 +│ ├── components/ # 公共组件 +│ └── index.ts # 统一导出 +└── README.md +``` + +## 使用示例 + +### 待办页面 + +```typescript +import { TodoList, ApprovalModal } from '@flow-engine/flow-pc-approval'; + +const TodoPage = () => { + const [selectedTask, setSelectedTask] = useState(null); + const [showApprovalModal, setShowApprovalModal] = useState(false); + + const handleTaskClick = async (task) => { + setSelectedTask(task); + setShowApprovalModal(true); + }; + + return ( + <> + + {showApprovalModal && ( + setShowApprovalModal(false)} + onApproved={handleRefresh} + /> + )} + + ); +}; +``` + +### 审批操作 + +```typescript +import { ApprovalForm } from '@flow-engine/flow-pc-approval'; + +const MyApprovalForm = () => { + const handleSubmit = async (action, opinion) => { + await approvalApi.submit({ + taskId, + action, + opinion, + formData + }); + }; + + return ( + handleSubmit('PASS', opinion)} + onReject={(opinion) => handleSubmit('REJECT', opinion)} + onSave={(data) => handleSubmit('SAVE', data)} + onTransfer={(userId) => handleSubmit('TRANSFER', { userId })} + onReturn={(nodeId) => handleSubmit('RETURN', { nodeId })} + onDelegate={(userId) => handleSubmit('DELEGATE', { userId })} + /> + ); +}; +``` + +## 审批动作 + +支持以下审批动作: + +- `PASS` - 通过 +- `REJECT` - 拒绝 +- `SAVE` - 保存 +- `ADD_AUDIT` - 加签 +- `DELEGATE` - 委托 +- `RETURN` - 退回 +- `TRANSFER` - 转交 +- `CUSTOM` - 自定义动作 + +## Learn more -- [Rsbuild documentation](https://rsbuild.rs) - explore Rsbuild features and APIs. -- [Rsbuild GitHub repository](https://github.com/web-infra-dev/rsbuild) - your feedback and contributions are welcome! +- [Rslib documentation](https://lib.rsbuild.io/) - Rslib 特性和 API +- [Flow Engine Docs](https://github.com/codingapi/flow-engine) - 完整文档 +- [CLAUDE.md](../../../CLAUDE.md) - 开发指南 diff --git a/frontend/packages/flow-pc/flow-pc-approval/src/components/flow-approval/components/action/custom.tsx b/frontend/packages/flow-pc/flow-pc-approval/src/components/flow-approval/components/action/custom.tsx index af397948..79defc8c 100644 --- a/frontend/packages/flow-pc/flow-pc-approval/src/components/flow-approval/components/action/custom.tsx +++ b/frontend/packages/flow-pc/flow-pc-approval/src/components/flow-approval/components/action/custom.tsx @@ -5,6 +5,7 @@ import {useApprovalContext} from "@/components/flow-approval/hooks/use-approval- import {GroovyScriptConvertorUtil} from "@flow-engine/flow-core"; import {ActionFactory} from "@/components/flow-approval/components/action/factory"; import {ActionButton} from "@/components/flow-approval/components/action-button"; +import {ActionType} from "@flow-engine/flow-types"; /** * 自定义 @@ -24,7 +25,7 @@ export const CustomAction: React.FC = (props) => { const ActionView = ActionFactory.getInstance().render({ ...props.action, - type: triggerType, + type: triggerType as ActionType, }); if (ActionView) { diff --git a/frontend/packages/flow-pc/flow-pc-design/README.md b/frontend/packages/flow-pc/flow-pc-design/README.md index e159353f..618ec17c 100644 --- a/frontend/packages/flow-pc/flow-pc-design/README.md +++ b/frontend/packages/flow-pc/flow-pc-design/README.md @@ -1,21 +1,21 @@ -# @flow-engine/flow-design +# @flow-engine/flow-pc-design -Flow Engine 流程设计器组件库,基于 @flowgram.ai fixed-layout-editor 构建。 +Flow Engine PC 端流程设计器组件库,提供节点配置、属性面板、脚本配置等功能。 ## 简介 -`flow-design` 是 Flow Engine 的核心前端组件库,提供可视化的流程设计能力。 +`flow-pc-design` 是 Flow Engine PC 端的流程设计器组件库,提供: -### 核心依赖 +- 流程设计器(可视化流程设计) +- 节点配置面板(15 种节点类型配置) +- 属性面板(节点属性编辑) +- 脚本配置器(Groovy 脚本编辑) +- 变量选择器 +- 策略配置组件 -- `@flowgram.ai/fixed-layout-editor` - 固定布局编辑器核心 -- `@flowgram.ai/fixed-semi-materials` - Semi Design 组件物料 -- `@flowgram.ai/form-materials` - 表单组件物料 -- `@flowgram.ai/panel-manager-plugin` - 面板管理插件 -- `@flowgram.ai/minimap-plugin` - 小地图插件 -- `@flowgram.ai/export-plugin` - 导出插件 -- `antd` - Ant Design 组件库 -- `@reduxjs/toolkit` + `react-redux` - 状态管理 +### 依赖关系 + +- **依赖**: `@flow-engine/flow-core`, `@flow-engine/flow-types`, `@flow-engine/flow-pc-ui` ## Setup @@ -39,76 +39,176 @@ pnpm run build pnpm run dev ``` -运行测试: +## 核心功能 -```bash -pnpm run test -``` +### 流程设计器 -## 核心功能 +可视化流程设计器,支持: + +- 节点拖拽添加 +- 节点连线 +- 节点配置 +- 流程预览 +- 流程校验 +- 流程导入/导出 -### 流程设计面板 +```typescript +import { WorkflowDesigner } from '@flow-engine/flow-pc-design'; + + +``` -`pages/design-panel/` 目录包含流程设计的核心组件: +### 节点配置 -- `types.ts` - TypeScript 类型定义 - - `Workflow` - 工作流定义 - - `FlowNode` - 节点定义 - - `FlowForm` - 表单定义 - - `FlowNode.blocks?: FlowNode[]` - 子节点(层次化结构) +支持 15 种节点类型的配置: -### 节点类型 +**基础节点 (9 种)**: +- `StartNode` - 开始节点 +- `EndNode` - 结束节点 +- `ApprovalNode` - 审批节点 +- `HandleNode` - 办理节点 +- `NotifyNode` - 通知节点 +- `RouterNode` - 路由节点 +- `SubProcessNode` - 子流程节点 +- `DelayNode` - 延迟节点 +- `TriggerNode` - 触发节点 -支持 15 种节点类型: +**块节点 (3 种)**: +- `ConditionNode` - 条件节点 +- `ParallelNode` - 并行节点 +- `InclusiveNode` - 包容节点 -**基础节点 (9种)**: StartNode, EndNode, ApprovalNode, HandleNode, NotifyNode, RouterNode, SubProcessNode, DelayNode, TriggerNode +**分支节点 (3 种)**: +- `ConditionBranchNode` - 条件分支 +- `ParallelBranchNode` - 并行分支 +- `InclusiveBranchNode` - 包容分支 -**块节点 (3种)**: ConditionNode, ParallelNode, InclusiveNode(包含子节点 blocks) +### 属性面板 -**分支节点 (3种)**: ConditionBranchNode, ParallelBranchNode, InclusiveBranchNode +节点属性编辑: -### 数据结构 +- 基本信息配置 +- 审批人配置 +- 表单权限配置 +- 通知配置 +- 超时策略配置 -#### 层次化节点结构 (Blocks) +### 脚本配置器 -使用 `blocks` 属性实现节点间的层次关系: +Groovy 脚本编辑支持: + +- 语法高亮 +- 代码补全 +- 语法检查 +- 变量提示 +- TypeScript/Groovy 转换 ```typescript -interface FlowNode { - id: string; - name: string; - type: NodeType; - blocks?: FlowNode[]; // 子节点列表 - // ... 其他属性 -} +import { ScriptEditor } from '@flow-engine/flow-pc-design'; + + ``` -#### 节点配置 +### 变量选择器 + +流程变量选择: + +- 表单字段变量 +- 流程实例变量 +- 系统内置变量 +- 自定义变量 ```typescript -interface FlowNode { - strategies?: NodeStrategy[]; // 节点策略 - actions?: FlowAction[]; // 节点动作 - // ... 其他属性 -} +import { VariablePicker } from '@flow-engine/flow-pc-design'; + + +``` + +### 策略配置 + +节点策略配置: + +- 多人审批策略(会签/或签) +- 超时策略 +- 通知策略 +- 权限策略 + +## 模块结构 + +``` +flow-pc-design/ +├── src/ +│ ├── designer/ # 流程设计器 +│ ├── node-config/ # 节点配置组件 +│ ├── property-panel/ # 属性面板 +│ ├── script/ # 脚本编辑器 +│ ├── variable/ # 变量选择器 +│ ├── strategy/ # 策略配置 +│ ├── components/ # 公共组件 +│ └── index.ts # 统一导出 +└── README.md ``` -## 开发指南 +## 使用示例 -### 添加新节点 +### 流程设计器 -1. 在 `types.ts` 中定义节点类型 -2. 创建对应的节点配置组件 -3. 注册到设计面板 +```typescript +import { WorkflowDesigner } from '@flow-engine/flow-pc-design'; +import type { Workflow } from '@flow-engine/flow-types'; + +const MyWorkflowDesigner = () => { + const [workflow, setWorkflow] = useState({ ... }); + + return ( + + ); +}; +``` -### 添加新策略 +### 脚本编辑器 -1. 扩展 `NodeStrategy` 类型 -2. 创建策略配置 UI 组件 -3. 集成到节点配置面板 +```typescript +import { ScriptEditor } from '@flow-engine/flow-pc-design'; +import type { ScriptType } from '@flow-engine/flow-pc-design'; + +const MyScriptEditor = () => { + const [script, setScript] = useState(''); + + return ( + { + setScript(prev => prev + variable); + }} + /> + ); +}; +``` ## Learn more - [Rslib documentation](https://lib.rsbuild.io/) - Rslib 特性和 API - [Flow Engine Docs](https://github.com/codingapi/flow-engine) - 完整文档 -- [CLAUDE.md](../../CLAUDE.md) - 开发指南 +- [CLAUDE.md](../../../CLAUDE.md) - 开发指南 diff --git a/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/components/branch-adder/index.tsx b/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/components/branch-adder/index.tsx index b0038edd..f3422f17 100644 --- a/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/components/branch-adder/index.tsx +++ b/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/components/branch-adder/index.tsx @@ -6,7 +6,7 @@ import {Button} from "antd"; import {nodeFormPanelFactory} from "@/components/design-editor/components/sidebar"; import {usePanelManager} from "@flowgram.ai/panel-manager-plugin"; import {useDesignContext} from "@/components/design-panel/hooks/use-design-context"; -import {NodeType} from "@/components/design-editor/typings/node-type"; +import {NodeType} from "@flow-engine/flow-types"; interface BranchAdderPropsType { activated?: boolean; diff --git a/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/components/node-icon/index.tsx b/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/components/node-icon/index.tsx index c0c4c23d..ef653fc1 100644 --- a/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/components/node-icon/index.tsx +++ b/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/components/node-icon/index.tsx @@ -1,9 +1,20 @@ import React from "react"; -import {NodeType} from "@/components/design-editor/typings/node-type"; -import {Button, Flex, Space,Input,theme} from "antd"; -import {ApiOutlined, AuditOutlined, BellOutlined, BranchesOutlined, ClockCircleOutlined, CloseOutlined, EditOutlined, +import {NodeType} from "@flow-engine/flow-types"; +import {theme} from "antd"; +import { + ApiOutlined, + AuditOutlined, + BellOutlined, + BranchesOutlined, + ClockCircleOutlined, + EditOutlined, MergeOutlined, - NodeExpandOutlined, PoweroffOutlined, PullRequestOutlined, ShareAltOutlined, UserOutlined} from "@ant-design/icons"; + NodeExpandOutlined, + PoweroffOutlined, + PullRequestOutlined, + ShareAltOutlined, + UserOutlined +} from "@ant-design/icons"; interface NodeIconProps { diff --git a/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/components/node-list/index.tsx b/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/components/node-list/index.tsx index 24f6ef86..bb530d00 100644 --- a/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/components/node-list/index.tsx +++ b/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/components/node-list/index.tsx @@ -4,7 +4,7 @@ import {FlowNodeRegistry} from "@/components/design-editor/typings"; import styled from 'styled-components'; import {FlowNodeRegistries} from "@/components/design-editor/nodes"; import {NodeIcon} from "@/components/design-editor/components/node-icon"; -import {NodeType} from "@/components/design-editor/typings/node-type"; +import {NodeType} from "@flow-engine/flow-types"; import {useDesignContext} from "@/components/design-panel/hooks/use-design-context"; const NodesWrap = styled.div` diff --git a/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/action/table.tsx b/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/action/index.tsx similarity index 64% rename from frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/action/table.tsx rename to frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/action/index.tsx index 7fced750..52e73ea5 100644 --- a/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/action/table.tsx +++ b/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/action/index.tsx @@ -1,11 +1,11 @@ import React from "react"; -import {Button, Form, Space, Switch,Popconfirm} from "antd"; +import {Button, Form, Popconfirm, Space, Switch} from "antd"; import {Table} from "@flow-engine/flow-pc-ui"; -import {ActionManager} from "@/components/design-editor/node-components/action/index"; import {useNodeRenderContext} from "@/components/design-editor/hooks/use-node-render-context"; import {PlusOutlined} from "@ant-design/icons"; -import {ActionModal} from "@/components/design-editor/node-components/action/modal"; -import {GroovyScriptConvertorUtil} from "@flow-engine/flow-core"; +import {actionOptions} from "@flow-engine/flow-types"; +import {ActionConfigModal} from "@/components/script/modal/action-config-modal"; +import {FlowActionListPresenter} from "./presenter"; interface ActionTableProps { value: any[]; @@ -15,11 +15,11 @@ interface ActionTableProps { export const ActionTable: React.FC = (props) => { const {node} = useNodeRenderContext(); const actions = node.getNodeRegistry()?.meta.actions || []; - const actionManager = new ActionManager(props.value, props.onChange); - const datasource = actionManager.getDatasource(actions); + const presenter = new FlowActionListPresenter(props.value, props.onChange); + const datasource = presenter.getDatasource(actions); const [visible, setVisible] = React.useState(false); - const [customVisible, setCustomVisible] = React.useState(false); const [form] = Form.useForm(); + const columns = React.useCallback(() => { return [ { @@ -33,6 +33,15 @@ export const ActionTable: React.FC = (props) => { dataIndex: 'title', key: 'title', }, + { + title: '类型', + dataIndex: 'type', + key: 'type', + render:(value:string) => { + const type = actionOptions.find(item=>item.value === value); + return type?.label + } + }, { title: '启用', dataIndex: 'enable', @@ -43,7 +52,7 @@ export const ActionTable: React.FC = (props) => { size="small" value={record.enable} onChange={(value) => { - actionManager.enable(record.id, value); + presenter.enable(record.id, value); }} /> ) @@ -59,21 +68,8 @@ export const ActionTable: React.FC = (props) => { { - const custom = record.type==='CUSTOM'; - let trigger = {}; - if(custom){ - const meta = GroovyScriptConvertorUtil.getScriptMeta(record.script); - trigger = JSON.parse(meta); - } - const data = { - ...record, - ...record.display, - ...trigger, - title: record.title, - id: record.id, - } - form.setFieldsValue(data); - setCustomVisible(custom); + form.resetFields(); + form.setFieldsValue(record); setVisible(true); }} > @@ -83,7 +79,7 @@ export const ActionTable: React.FC = (props) => { { - actionManager.delete(record.id); + presenter.delete(record.id); }} > @@ -107,10 +103,9 @@ export const ActionTable: React.FC = (props) => { icon={} onClick={() => { form.resetFields(); - setCustomVisible(true); setVisible(true); }} - >添加按钮 + >自定义按钮 ]} columns={columns()} dataSource={datasource} @@ -120,16 +115,16 @@ export const ActionTable: React.FC = (props) => { pagination={false} /> - { setVisible(false); }} onFinish={(values) => { - actionManager.update(values); + presenter.update(values); }} /> diff --git a/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/action/manager.ts b/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/action/manager.ts new file mode 100644 index 00000000..7cbed8aa --- /dev/null +++ b/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/action/manager.ts @@ -0,0 +1,23 @@ +import {actionOptions, FlowAction} from "@flow-engine/flow-types"; + +export class FlowActionManager { + private readonly data: FlowAction[]; + + public constructor(data: FlowAction[]) { + this.data = data; + } + + public getCurrentNodeActionOptions() { + const actions = this.data.filter(item => item.type !== "CUSTOM"); + const options: {label: string; value: string}[] = []; + for (const action of actions) { + const type = action.type; + const option = actionOptions.find(item => item.value === type); + if (option) { + options.push(option); + } + } + return options; + } + +} \ No newline at end of file diff --git a/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/action/modal.tsx b/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/action/modal.tsx deleted file mode 100644 index 1e0485d1..00000000 --- a/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/action/modal.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import {Col, Form, FormInstance, Input, Modal, Row} from "antd"; -import React from "react"; -import {CustomScriptView} from "./script"; -import {ActionStyle} from "./style"; -import {ActionIcon} from "@/components/design-editor/node-components/action/icon"; - -interface ActionModalProps { - open: boolean; - onCancel: () => void; - form: FormInstance; - onFinish: (values: any) => void; - custom: boolean; - options: any[]; -} - - -export const ActionModal: React.FC = (props) => { - const custom = props.custom; - - return ( - { - props.form.submit(); - }} - > -
{ - props.onFinish(values); - props.onCancel(); - }} - > - - - - - - - - - - - - - - - - - - - {custom && ( - - )} - - -
-
- ) -} \ No newline at end of file diff --git a/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/action/index.ts b/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/action/presenter.ts similarity index 63% rename from frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/action/index.ts rename to frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/action/presenter.ts index fec7d339..d057d2ad 100644 --- a/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/action/index.ts +++ b/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/action/presenter.ts @@ -1,27 +1,21 @@ -import {actionOptions, ActionType} from "@/components/design-editor/typings/node-type"; +import {actionOptions, ActionType, FlowAction} from "@flow-engine/flow-types"; import {nanoid} from "nanoid"; import {IdUtils} from "@/utils"; +import {FlowActionManager} from "./manager"; -export class ActionManager { - private readonly data: any[]; - private readonly onChange: (data: any[]) => void; +export class FlowActionListPresenter { + private readonly data: FlowAction[]; + private readonly onChange: (data: FlowAction[]) => void; + private readonly manager:FlowActionManager; - public constructor(data: any[], onChange: (data: any[]) => void) { + public constructor(data: FlowAction[], onChange: (data: FlowAction[]) => void) { this.onChange = onChange; this.data = data; + this.manager = new FlowActionManager(data); } - public getActionOptions() { - const actions = this.data.filter(item => item.type !== "CUSTOM"); - const options:any[] = []; - for (const action of actions) { - const type = action.type; - const option = actionOptions.find(item => item.value === type); - if (option) { - options.push(option); - } - } - return options; + public getFlowActionManager(){ + return this.manager; } public enable(id: any, value: boolean) { @@ -38,7 +32,7 @@ export class ActionManager { } - public delete(id:string){ + public delete(id: string) { const data = this.data.filter(item => item.id !== id); this.onChange(data); } @@ -46,28 +40,29 @@ export class ActionManager { public update(action: any) { const actionId = action.id; - if(actionId) { + if (actionId) { const data = this.data.map(item => { if (item.id === actionId) { return { ...item, - title: action.title, + ...action, display: { - ...action + ...item.display, + ...action.display, } } } return item; }); this.onChange(data); - }else { + } else { const custom = { ...action, - type:'CUSTOM', - enable:true, - id:IdUtils.generateId(), + type: 'CUSTOM', + enable: true, + id: IdUtils.generateId(), } - this.onChange([...this.data,custom]); + this.onChange([...this.data, custom]); } } @@ -90,7 +85,7 @@ export class ActionManager { enable: true, title: title, type: type, - } + } as FlowAction; }); this.onChange(list); return list; diff --git a/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/header/index.tsx b/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/header/index.tsx index d078031f..0048b932 100644 --- a/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/header/index.tsx +++ b/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/header/index.tsx @@ -6,7 +6,7 @@ import {usePanelManager} from "@flowgram.ai/panel-manager-plugin"; import {Field, FieldRenderProps} from "@flowgram.ai/fixed-layout-editor"; import {CloseOutlined, EditOutlined} from "@ant-design/icons"; import {NodeIcon} from "@/components/design-editor/components/node-icon"; -import {NodeType} from "@/components/design-editor/typings/node-type"; +import {NodeType} from "@flow-engine/flow-types"; import {FlowNodeRegistry} from "@/components/design-editor/typings"; import {useNodeRenderContext} from "@/components/design-editor/hooks/use-node-render-context"; diff --git a/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/taps/action.tsx b/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/taps/action.tsx index c1100c58..50a41e10 100644 --- a/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/taps/action.tsx +++ b/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/taps/action.tsx @@ -1,6 +1,6 @@ import React from "react"; import {Field, FieldRenderProps} from "@flowgram.ai/fixed-layout-editor"; -import {ActionTable} from "@/components/design-editor/node-components/action/table"; +import {ActionTable} from "@/components/design-editor/node-components/action"; export const TabAction: React.FC = () => { diff --git a/frontend/packages/flow-pc/flow-pc-design/src/components/design-panel/manager/node.ts b/frontend/packages/flow-pc/flow-pc-design/src/components/design-panel/manager/node.ts index 31fb4138..b8ed79bc 100644 --- a/frontend/packages/flow-pc/flow-pc-design/src/components/design-panel/manager/node.ts +++ b/frontend/packages/flow-pc/flow-pc-design/src/components/design-panel/manager/node.ts @@ -25,6 +25,7 @@ export class NodeConvertorManager { actions: node.actions, script: node.script, view: node.view, + display: node.display, ...this.toStrategyRender(node), }, blocks: blocks.map(item => this.toItemRender(item)) @@ -50,6 +51,7 @@ export class NodeConvertorManager { actions: data?.actions || [], strategies: this.toStrategyData(data), script: data?.script, + display: data?.display, blocks: blocks.map(item => { return this.toDataItem(item) }), @@ -98,11 +100,11 @@ interface MappingData { } -export class NodeManger{ +export class NodeManger { + // 设计时最新的数据 private readonly nodes: FlowNode[]; private readonly nodeList: FlowNode[]; - constructor(nodes: FlowNode[]) { this.nodes = nodes; this.nodeList = []; @@ -119,12 +121,43 @@ export class NodeManger{ } } + /** + * 获取可以退回的节点 + * @param nodeId + */ + public getBackNodes(nodeId: string) { + const list: FlowNode[] = []; + for (const node of this.nodeList) { + if (node.id === nodeId) { + break; + } + if (node.display) { + list.push(node); + } + } + return list; + } + + + public getSize() { + return this.nodes.length; + } + + public getNodeByType(type:string){ + for (const node of this.nodeList) { + if (node.type === type) { + return node; + } + } + return null; + } + /** * 获取节点 * @param id 节点id */ - public getNode(id:string){ + public getNode(id: string) { for (const node of this.nodeList) { if (node.id === id) { return node; @@ -231,15 +264,15 @@ export class NodeRouterManager { public mapping(data: MappingData) { const nodeId = data.node; let matchNode = null; - for(const node of this.nodeList) { - if(matchNode) { - break; + for (const node of this.nodeList) { + if (matchNode) { + break; } - if(node.type === nodeId){ + if (node.type === nodeId) { matchNode = node; } } - if(matchNode){ + if (matchNode) { return { ...data, node: matchNode.id diff --git a/frontend/packages/flow-pc/flow-pc-design/src/components/design-panel/presenters/index.ts b/frontend/packages/flow-pc/flow-pc-design/src/components/design-panel/presenters/index.ts index 792a3d3e..90f01fa8 100644 --- a/frontend/packages/flow-pc/flow-pc-design/src/components/design-panel/presenters/index.ts +++ b/frontend/packages/flow-pc/flow-pc-design/src/components/design-panel/presenters/index.ts @@ -142,6 +142,10 @@ export class Presenter { console.log('save latest:', apiData); } + public getNodeManager(){ + return new NodeManger(this.state.workflow.nodes || []); + } + public async createNode(form: string, type: string) { const flowNode = await this.api.createNode(type); const nodeManager = new NodeConvertorManager(); diff --git a/frontend/packages/flow-pc/flow-pc-design/src/components/design-panel/types.ts b/frontend/packages/flow-pc/flow-pc-design/src/components/design-panel/types.ts index d6470f4b..9497f09f 100644 --- a/frontend/packages/flow-pc/flow-pc-design/src/components/design-panel/types.ts +++ b/frontend/packages/flow-pc/flow-pc-design/src/components/design-panel/types.ts @@ -1,6 +1,5 @@ // Tab布局类型 -import {NodeType} from "@/components/design-editor/typings/node-type"; -import {FlowForm} from "@flow-engine/flow-types"; +import {FlowForm,NodeType,FlowAction} from "@flow-engine/flow-types"; export type TabPanelType = 'base' | 'form' | 'flow' | 'setting'; @@ -8,60 +7,57 @@ export type TabPanelType = 'base' | 'form' | 'flow' | 'setting'; export const LayoutHeaderHeight = 50; - export interface DesignPanelProps { // 流程编码 id?:string + // 是否开启 open: boolean; + // 关闭 onClose?: () => void; } // 流程配置 export interface Workflow { + // 设计id id: string; + // 流程名称 title: string; + // 流程编码 code: string; + // 流程表单 form: FlowForm; // 流程创建人脚本 operatorCreateScript:string; + // 流程策略 strategies?:any[]; + // 流程节点 nodes?:FlowNode[]; - edges?:FlowEdge[]; -} - -// 节点关系 -export interface FlowEdge { - from:string; - to:string; } -// 动作展示 -export interface ActionDisplay{ - title:string; - style:any; - icon:string; -} - -// 节点动作 -export interface FlowAction{ - id:string; - type:string; - title:string; - display:ActionDisplay; -} // 流程节点 export interface FlowNode{ + // 节点id id:string; + // 节点名称 name:string; + // 节点类型 type:NodeType; + // 节点优先级 order:number; + // 节点动作 actions:FlowAction[]; + // 节点策略 strategies:any[]; + // 节点条件块 blocks?:FlowNode[]; + // 节点表达式 script?:string; + // 节点视图 view?:string; + // 流程展示节点 + display?:boolean; } // 全局状态 diff --git a/frontend/packages/flow-pc/flow-pc-design/src/components/script/components/action/components/add-audit.tsx b/frontend/packages/flow-pc/flow-pc-design/src/components/script/components/action/components/add-audit.tsx new file mode 100644 index 00000000..e546abda --- /dev/null +++ b/frontend/packages/flow-pc/flow-pc-design/src/components/script/components/action/components/add-audit.tsx @@ -0,0 +1,43 @@ +import React from "react"; +import {ActionFormProps} from "@/components/script/typings"; +import {Col, Form, Row} from "antd"; +import {OperatorLoadPluginView} from "@/components/script/plugins/view/operator-load-view"; + + +interface AddAuditInputProps{ + value?:string; + onChange?:(value:string) => void; +} + +const AddAuditInput:React.FC = (props)=>{ + + const script = props.value || ''; + + const handleChange = (value:string)=>{ + props.onChange && props.onChange(value); + } + + return ( + + ) +} + +export const AddAuditActionForm:React.FC = (props)=>{ + + return ( + + + + + + + + ) +} \ No newline at end of file diff --git a/frontend/packages/flow-pc/flow-pc-design/src/components/script/components/action/components/custom.tsx b/frontend/packages/flow-pc/flow-pc-design/src/components/script/components/action/components/custom.tsx new file mode 100644 index 00000000..4a923357 --- /dev/null +++ b/frontend/packages/flow-pc/flow-pc-design/src/components/script/components/action/components/custom.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import {ActionFormProps} from "@/components/script/typings"; +import {Col, Form, Row} from "antd"; +import {ConditionCustomView} from "@/components/script/plugins/view/action-custom-view"; + +export const CustomActionForm: React.FC = (props) => { + + const actionOptionTypes = props.manager.getCurrentNodeActionOptions(); + + return ( + + + + + + + + ) +} \ No newline at end of file diff --git a/frontend/packages/flow-pc/flow-pc-design/src/components/script/components/action/components/delegate.tsx b/frontend/packages/flow-pc/flow-pc-design/src/components/script/components/action/components/delegate.tsx new file mode 100644 index 00000000..48718278 --- /dev/null +++ b/frontend/packages/flow-pc/flow-pc-design/src/components/script/components/action/components/delegate.tsx @@ -0,0 +1,44 @@ +import React from "react"; +import {ActionFormProps} from "@/components/script/typings"; +import {OperatorLoadPluginView} from "@/components/script/plugins/view/operator-load-view"; +import {Col, Form, Row } from "antd"; + + + +interface DelegateInputProps{ + value?:string; + onChange?:(value:string) => void; +} + +const DelegateInput:React.FC = (props)=>{ + + const script = props.value || ''; + + const handleChange = (value:string)=>{ + props.onChange && props.onChange(value); + } + + return ( + + ) +} + +export const DelegateActionForm:React.FC = (props)=>{ + + return ( + + + + + + + + ) +} \ No newline at end of file diff --git a/frontend/packages/flow-pc/flow-pc-design/src/components/script/components/action/components/factory.tsx b/frontend/packages/flow-pc/flow-pc-design/src/components/script/components/action/components/factory.tsx new file mode 100644 index 00000000..712978a0 --- /dev/null +++ b/frontend/packages/flow-pc/flow-pc-design/src/components/script/components/action/components/factory.tsx @@ -0,0 +1,38 @@ +import React from "react"; +import {ActionFormProps} from "@/components/script/typings"; +import {ActionType} from "@flow-engine/flow-types"; +import {CustomActionForm} from "@/components/script/components/action/components/custom"; +import {AddAuditActionForm} from "@/components/script/components/action/components/add-audit"; +import {DelegateActionForm} from "@/components/script/components/action/components/delegate"; +import {RejectActionForm} from "@/components/script/components/action/components/reject"; +import {TransferActionForm} from "@/components/script/components/action/components/transfer"; + +export class ActionFactory{ + + private readonly actions:Map>; + + private constructor() { + this.actions = new Map(); + this.initialize(); + } + + private initialize():void{ + this.actions.set('ADD_AUDIT',AddAuditActionForm); + this.actions.set('CUSTOM',CustomActionForm); + this.actions.set('DELEGATE',DelegateActionForm); + this.actions.set('REJECT',RejectActionForm); + this.actions.set('TRANSFER',TransferActionForm); + + } + + private static readonly instance = new ActionFactory(); + + public static getInstance(){ + return this.instance; + } + + public getActionForm(type:ActionType){ + return this.actions.get(type); + } + +} \ No newline at end of file diff --git a/frontend/packages/flow-pc/flow-pc-design/src/components/script/components/action/components/reject.tsx b/frontend/packages/flow-pc/flow-pc-design/src/components/script/components/action/components/reject.tsx new file mode 100644 index 00000000..29c1938a --- /dev/null +++ b/frontend/packages/flow-pc/flow-pc-design/src/components/script/components/action/components/reject.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import {ActionFormProps} from "@/components/script/typings"; +import {Col, Form, Row} from "antd"; +import {ConditionRejectView} from "@/components/script/plugins/view/action-reject-view"; + + +export const RejectActionForm:React.FC = (props)=>{ + + + return ( + + + + + + + + ) +} \ No newline at end of file diff --git a/frontend/packages/flow-pc/flow-pc-design/src/components/script/components/action/components/transfer.tsx b/frontend/packages/flow-pc/flow-pc-design/src/components/script/components/action/components/transfer.tsx new file mode 100644 index 00000000..6b23f842 --- /dev/null +++ b/frontend/packages/flow-pc/flow-pc-design/src/components/script/components/action/components/transfer.tsx @@ -0,0 +1,45 @@ +import React from "react"; +import {ActionFormProps} from "@/components/script/typings"; +import {OperatorLoadPluginView} from "@/components/script/plugins/view/operator-load-view"; +import {Col, Form, Row } from "antd"; + + + +interface TransferInputProps{ + value?:string; + onChange?:(value:string) => void; +} + +const TransferInput:React.FC = (props)=>{ + + const script = props.value || ''; + + const handleChange = (value:string)=>{ + props.onChange && props.onChange(value); + } + + return ( + + ) +} + + +export const TransferActionForm:React.FC = (props)=>{ + + return ( + + + + + + + + ) +} \ No newline at end of file diff --git a/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/action/icon.tsx b/frontend/packages/flow-pc/flow-pc-design/src/components/script/components/action/icon.tsx similarity index 100% rename from frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/action/icon.tsx rename to frontend/packages/flow-pc/flow-pc-design/src/components/script/components/action/icon.tsx diff --git a/frontend/packages/flow-pc/flow-pc-design/src/components/script/components/action/index.tsx b/frontend/packages/flow-pc/flow-pc-design/src/components/script/components/action/index.tsx new file mode 100644 index 00000000..7c8e8fab --- /dev/null +++ b/frontend/packages/flow-pc/flow-pc-design/src/components/script/components/action/index.tsx @@ -0,0 +1,68 @@ +import React from "react"; +import {ActionFormProps} from "@/components/script/typings"; +import { Form,Input,Row,Col } from "antd"; +import {ActionIcon} from "./icon"; +import {ActionStyle} from "./style"; +import {ActionFactory} from "@/components/script/components/action/components/factory"; + + +export const ActionForm:React.FC = (props)=>{ + + const type = props.form.getFieldValue("type"); + + const FormAction = ActionFactory.getInstance().getActionForm(type); + + return ( +
{ + props.onFinish(values); + }} + > + + + + + + + + + + + + + + + + + + + + {FormAction && ( + + )} + + ) +} \ No newline at end of file diff --git a/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/action/script.tsx b/frontend/packages/flow-pc/flow-pc-design/src/components/script/components/action/script.tsx similarity index 100% rename from frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/action/script.tsx rename to frontend/packages/flow-pc/flow-pc-design/src/components/script/components/action/script.tsx diff --git a/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/action/style.tsx b/frontend/packages/flow-pc/flow-pc-design/src/components/script/components/action/style.tsx similarity index 100% rename from frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/action/style.tsx rename to frontend/packages/flow-pc/flow-pc-design/src/components/script/components/action/style.tsx diff --git a/frontend/packages/flow-pc/flow-pc-design/src/components/script/components/action/type.ts b/frontend/packages/flow-pc/flow-pc-design/src/components/script/components/action/type.ts new file mode 100644 index 00000000..e69de29b diff --git a/frontend/packages/flow-pc/flow-pc-design/src/components/script/modal/action-config-modal.tsx b/frontend/packages/flow-pc/flow-pc-design/src/components/script/modal/action-config-modal.tsx new file mode 100644 index 00000000..47275a69 --- /dev/null +++ b/frontend/packages/flow-pc/flow-pc-design/src/components/script/modal/action-config-modal.tsx @@ -0,0 +1,30 @@ +import React from "react"; +import {ActionModalProps} from "@/components/script/typings"; +import {Modal} from "antd"; +import {ActionForm} from "@/components/script/components/action"; + + +export const ActionConfigModal: React.FC = (props) => { + return ( + { + props.form.submit(); + }} + > + { + props.onFinish(values); + props.onCancel(); + }} + /> + + ) +} \ No newline at end of file diff --git a/frontend/packages/flow-pc/flow-pc-design/src/components/script/plugins/action-custom-view-type.ts b/frontend/packages/flow-pc/flow-pc-design/src/components/script/plugins/action-custom-view-type.ts new file mode 100644 index 00000000..747235bb --- /dev/null +++ b/frontend/packages/flow-pc/flow-pc-design/src/components/script/plugins/action-custom-view-type.ts @@ -0,0 +1,12 @@ +import {ActionSelectOption} from "@/components/script/typings"; + +export const VIEW_KEY = 'ConditionCustomViewPlugin'; + +export interface ConditionCustomViewPlugin { + // 当前的脚本 + value?: string; + // 脚本更改回掉 + onChange?: (value: string) => void; + // 可选择的动作范围 + options:ActionSelectOption[]; +} \ No newline at end of file diff --git a/frontend/packages/flow-pc/flow-pc-design/src/components/script/plugins/action-reject-view-type.ts b/frontend/packages/flow-pc/flow-pc-design/src/components/script/plugins/action-reject-view-type.ts new file mode 100644 index 00000000..6aae183f --- /dev/null +++ b/frontend/packages/flow-pc/flow-pc-design/src/components/script/plugins/action-reject-view-type.ts @@ -0,0 +1,10 @@ +export const VIEW_KEY = 'ConditionRejectViewPlugin'; + +export interface ConditionRejectViewPlugin { + // 当前节点id + nodeId:string; + // 当前的脚本 + value?: string; + // 脚本更改回掉 + onChange?: (value: string) => void; +} \ No newline at end of file diff --git a/frontend/packages/flow-pc/flow-pc-design/src/components/script/plugins/view/action-custom-view.tsx b/frontend/packages/flow-pc/flow-pc-design/src/components/script/plugins/view/action-custom-view.tsx new file mode 100644 index 00000000..c5447d8f --- /dev/null +++ b/frontend/packages/flow-pc/flow-pc-design/src/components/script/plugins/view/action-custom-view.tsx @@ -0,0 +1,80 @@ +import React from "react"; +import {ActionCustomScriptUtils} from "@/components/script/services/action-custom"; +import {GroovyCodeEditor} from "@/components/groovy-code"; +import {Select} from "antd"; +import {ConditionCustomViewPlugin, VIEW_KEY} from "@/components/script/plugins/action-custom-view-type"; +import {ViewBindPlugin} from "@flow-engine/flow-core"; + + +export const ConditionCustomView: React.FC = (props) => { + + const ConditionCustomViewComponent = ViewBindPlugin.getInstance().get(VIEW_KEY); + + if (ConditionCustomViewComponent) { + return ( + + ); + } + + const trigger = React.useMemo(() => { + if (props.value) { + return ActionCustomScriptUtils.getTrigger(props.value); + } + return undefined; + }, [props.value]); + + const handleChangeNodeType = (value: string) => { + const script = props.value; + if (script) { + const groovy = ActionCustomScriptUtils.update(value, script); + props.onChange?.(groovy); + } + } + + return ( +
+
+ 触发动作: + { + handleChange(option); + }} + placeholder={"请选择拒绝退回到的节点"} + /> + + ) +} \ No newline at end of file diff --git a/frontend/packages/flow-pc/flow-pc-design/src/components/script/plugins/view/error-trigger-view.tsx b/frontend/packages/flow-pc/flow-pc-design/src/components/script/plugins/view/error-trigger-view.tsx index 46a0848e..3f49673d 100644 --- a/frontend/packages/flow-pc/flow-pc-design/src/components/script/plugins/view/error-trigger-view.tsx +++ b/frontend/packages/flow-pc/flow-pc-design/src/components/script/plugins/view/error-trigger-view.tsx @@ -7,7 +7,7 @@ import {ViewBindPlugin} from "@flow-engine/flow-core"; import {SCRIPT_DEFAULT_ERROR_TRIGGER} from "@/components/script/default-script"; import {useNodeRouterManager} from "@/components/design-panel/hooks/use-node-router-manager"; import {useNodeRenderContext} from "@/components/design-editor/hooks/use-node-render-context"; -import {ErrorTriggerScriptUtils} from "@/components/script/services/error-trigger"; +import {ErrorTriggerScriptUtils} from "@/components/script/services/node-error-trigger"; import {useScriptMetaData} from "@/components/script/hooks/use-script-meta-data"; /** diff --git a/frontend/packages/flow-pc/flow-pc-design/src/components/script/plugins/view/operator-load-view.tsx b/frontend/packages/flow-pc/flow-pc-design/src/components/script/plugins/view/operator-load-view.tsx index fee53125..d03c6c90 100644 --- a/frontend/packages/flow-pc/flow-pc-design/src/components/script/plugins/view/operator-load-view.tsx +++ b/frontend/packages/flow-pc/flow-pc-design/src/components/script/plugins/view/operator-load-view.tsx @@ -23,11 +23,12 @@ export const OperatorLoadPluginView: React.FC = (props) initialValues={{ ...data }} + layout="vertical" > + +// 使用对话框组件 +Modal.confirm({ + title: '确认提交', + content: '确定要提交该流程吗?', + onOk: handleSubmit +}); +``` + +## 主题定制 + +支持通过 CSS 变量进行主题定制: + +```css +:root { + --primary-color: #1890ff; + --success-color: #52c41a; + --warning-color: #faad14; + --error-color: #f5222d; +} +``` + +## Learn more -- [Rsbuild documentation](https://rsbuild.rs) - explore Rsbuild features and APIs. -- [Rsbuild GitHub repository](https://github.com/web-infra-dev/rsbuild) - your feedback and contributions are welcome! +- [Rslib documentation](https://lib.rsbuild.io/) - Rslib 特性和 API +- [Flow Engine Docs](https://github.com/codingapi/flow-engine) - 完整文档 +- [CLAUDE.md](../../../CLAUDE.md) - 开发指南 diff --git a/frontend/packages/flow-types/README.md b/frontend/packages/flow-types/README.md index 4385d9cd..5766247e 100644 --- a/frontend/packages/flow-types/README.md +++ b/frontend/packages/flow-types/README.md @@ -1,14 +1,14 @@ # @flow-engine/flow-types -Flow Engine 前端类型定义库,提供整个项目的 TypeScript 类型系统。 +Flow Engine 前端 TypeScript 类型定义库,提供流程实例、表单、审批等业务类型定义。 ## 简介 -`flow-types` 是 Flow Engine 的类型定义库,为整个前端项目提供统一的类型系统。 +`flow-types` 是 Flow Engine 的类型定义库,为整个前端项目提供统一的业务类型系统。 -### 核心依赖 +### 依赖关系 -- `@flow-engine/flow-core` - 核心库类型引用 +- **依赖**: `@flow-engine/flow-core` ## Setup @@ -43,19 +43,19 @@ pnpm run dev - `FlowNode` - 节点基础类型 - `NodeType` - 节点类型枚举 -- 节点具体类型: `StartNode`, `EndNode`, `ApprovalNode`, `HandleNode`, 等 +- 节点具体类型:`StartNode`, `EndNode`, `ApprovalNode`, `HandleNode`, `NotifyNode`, `RouterNode`, `SubProcessNode`, `DelayNode`, `TriggerNode`, `ConditionNode`, `ParallelNode`, `InclusiveNode` 等 ### 策略类型 - `NodeStrategy` - 节点策略基础类型 - `WorkflowStrategy` - 工作流策略类型 -- 策略具体类型: `MultiOperatorAuditStrategy`, `TimeoutStrategy`, 等 +- 策略具体类型:`MultiOperatorAuditStrategy`, `TimeoutStrategy` 等 ### 动作类型 - `FlowAction` - 动作基础类型 - `ActionType` - 动作类型枚举 -- 动作具体类型: `PassAction`, `RejectAction`, `SaveAction`, 等 +- 动作具体类型:`PassAction`, `RejectAction`, `SaveAction`, `AddAuditAction`, `DelegateAction`, `ReturnAction`, `TransferAction`, `CustomAction` ### 表单类型 @@ -69,6 +69,11 @@ pnpm run dev - `FlowState` - 流程状态枚举 - `RecordState` - 记录状态枚举 +### 审批类型 + +- `ApprovalTask` - 审批任务 +- `ApprovalResult` - 审批结果 + ## 类型系统特点 ### 层次化节点结构 @@ -80,7 +85,7 @@ interface FlowNode { id: string; name: string; type: NodeType; - blocks?: FlowNode[]; // 子节点列表(块节点) + blocks?: FlowNode[]; // 子节点列表(块节点包含子节点) strategies?: NodeStrategy[]; actions?: FlowAction[]; } @@ -88,9 +93,35 @@ interface FlowNode { ### 节点分类 -- **基础节点** (9种): START, END, APPROVAL, HANDLE, NOTIFY, ROUTER, SUB_PROCESS, DELAY, TRIGGER -- **块节点** (3种): CONDITION, PARALLEL, INCLUSIVE(包含子节点) -- **分支节点** (3种): CONDITION_BRANCH, PARALLEL_BRANCH, INCLUSIVE_BRANCH +- **基础节点** (9 种): START, END, APPROVAL, HANDLE, NOTIFY, ROUTER, SUB_PROCESS, DELAY, TRIGGER +- **块节点** (3 种): CONDITION, PARALLEL, INCLUSIVE(包含子节点) +- **分支节点** (3 种): CONDITION_BRANCH, PARALLEL_BRANCH, INCLUSIVE_BRANCH + +## 使用示例 + +```typescript +import type { Workflow, FlowNode, NodeType } from '@flow-engine/flow-types'; + +// 定义一个工作流 +const workflow: Workflow = { + id: 'wf-001', + name: '请假审批流程', + nodes: [ + { + id: 'start-1', + name: '开始', + type: 'START' as NodeType + }, + { + id: 'approval-1', + name: '经理审批', + type: 'APPROVAL' as NodeType, + strategies: [...], + actions: [...] + } + ] +}; +``` ## Learn more diff --git a/frontend/packages/flow-types/src/types/flow-approval.ts b/frontend/packages/flow-types/src/types/flow-approval.ts index eed2384e..b51f6514 100644 --- a/frontend/packages/flow-types/src/types/flow-approval.ts +++ b/frontend/packages/flow-types/src/types/flow-approval.ts @@ -1,3 +1,5 @@ +import {ActionType} from "@/types/flow-design"; + /** * 数据类型 */ @@ -97,7 +99,7 @@ export interface FlowAction { // 按钮名称 title: string; // 动作类型 - type: string; + type: ActionType; // 展示样式 display: FlowActionDisplay; // 是否启用 diff --git a/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/typings/node-type.ts b/frontend/packages/flow-types/src/types/flow-design.ts similarity index 98% rename from frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/typings/node-type.ts rename to frontend/packages/flow-types/src/types/flow-design.ts index 157bb0dc..c5ebb15f 100644 --- a/frontend/packages/flow-pc/flow-pc-design/src/components/design-editor/typings/node-type.ts +++ b/frontend/packages/flow-types/src/types/flow-design.ts @@ -2,7 +2,7 @@ * 节点类型 */ export type NodeType = - // 审批 +// 审批 "APPROVAL" | // 分支控制 "CONDITION" | @@ -37,7 +37,7 @@ export type NodeType = * 操作类型 */ export type ActionType = - // 保存 +// 保存 'SAVE'| // 通过,流程继续往下流转 'PASS'| diff --git a/frontend/packages/flow-types/src/types/index.ts b/frontend/packages/flow-types/src/types/index.ts index 022f1d55..7ab035bd 100644 --- a/frontend/packages/flow-types/src/types/index.ts +++ b/frontend/packages/flow-types/src/types/index.ts @@ -1,3 +1,4 @@ +export * from './flow-design'; export * from './form-view'; export * from './form-instance'; export * from './flow-approval';