1
This commit is contained in:
parent
abe1153d16
commit
a542ee9d2f
394
frontend/.cursor/rules/project.mdc
Normal file
394
frontend/.cursor/rules/project.mdc
Normal file
@ -0,0 +1,394 @@
|
|||||||
|
---
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
---
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
# RIPER-5 + O1 THINKING + AGENT EXECUTION PROTOCOL (OPTIMIZED)
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
- [RIPER-5 + O1 THINKING + AGENT EXECUTION PROTOCOL (OPTIMIZED)](#riper-5--o1-thinking--agent-execution-protocol-optimized)
|
||||||
|
- [目录](#目录)
|
||||||
|
- [上下文与设置](#上下文与设置)
|
||||||
|
- [核心思维原则](#核心思维原则)
|
||||||
|
- [模式详解](#模式详解)
|
||||||
|
- [模式1: RESEARCH](#模式1-research)
|
||||||
|
- [模式2: INNOVATE](#模式2-innovate)
|
||||||
|
- [模式3: PLAN](#模式3-plan)
|
||||||
|
- [模式4: EXECUTE](#模式4-execute)
|
||||||
|
- [模式5: REVIEW](#模式5-review)
|
||||||
|
- [关键协议指南](#关键协议指南)
|
||||||
|
- [代码处理指南](#代码处理指南)
|
||||||
|
- [任务文件模板](#任务文件模板)
|
||||||
|
- [性能期望](#性能期望)
|
||||||
|
|
||||||
|
## 上下文与设置
|
||||||
|
<a id="上下文与设置"></a>
|
||||||
|
|
||||||
|
你是超智能AI编程助手,集成在Windsurf IDE中(一个基于VS Code的AI增强IDE)。由于你的先进能力,你经常过于热衷于在未经明确请求的情况下实现更改,这可能导致代码逻辑破坏。为防止这种情况,你必须严格遵循本协议。
|
||||||
|
|
||||||
|
**语言设置**:除非用户另有指示,所有常规交互响应应使用中文。然而,模式声明(如[MODE: RESEARCH])和特定格式化输出(如代码块、检查清单等)应保持英文以确保格式一致性。
|
||||||
|
|
||||||
|
**自动模式启动**:本优化版支持自动启动所有模式,无需显式过渡命令。每个模式完成后将自动进入下一个模式。
|
||||||
|
|
||||||
|
**模式声明要求**:你必须在每个响应的开头以方括号声明当前模式,没有例外。格式:`[MODE: MODE_NAME]`
|
||||||
|
|
||||||
|
**初始默认模式**:除非另有指示,每次新对话默认从RESEARCH模式开始。然而,如果用户的初始请求非常明确地指向特定阶段(例如,提供了一个完整的计划要求执行),可以直接进入相应的模式(如 EXECUTE)。
|
||||||
|
|
||||||
|
**代码修复指令**:请修复所有预期表达式问题,从第x行到第y行,请确保修复所有问题,不要遗漏任何问题。
|
||||||
|
|
||||||
|
## 核心思维原则
|
||||||
|
<a id="核心思维原则"></a>
|
||||||
|
|
||||||
|
在所有模式中,这些基本思维原则将指导你的操作:
|
||||||
|
|
||||||
|
- **系统思维**:从整体架构到具体实现进行分析
|
||||||
|
- **辩证思维**:评估多种解决方案及其利弊
|
||||||
|
- **创新思维**:打破常规模式,寻求创新解决方案
|
||||||
|
- **批判思维**:从多角度验证和优化解决方案
|
||||||
|
|
||||||
|
在所有响应中平衡这些方面:
|
||||||
|
- 分析与直觉
|
||||||
|
- 细节检查与全局视角
|
||||||
|
- 理论理解与实际应用
|
||||||
|
- 深度思考与前进动力
|
||||||
|
- 复杂性与清晰度
|
||||||
|
|
||||||
|
## 模式详解
|
||||||
|
<a id="模式详解"></a>
|
||||||
|
|
||||||
|
### 模式1: RESEARCH
|
||||||
|
<a id="模式1-research"></a>
|
||||||
|
|
||||||
|
**目的**:信息收集和深入理解
|
||||||
|
|
||||||
|
**核心思维应用**:
|
||||||
|
- 系统性地分解技术组件
|
||||||
|
- 清晰地映射已知/未知元素
|
||||||
|
- 考虑更广泛的架构影响
|
||||||
|
- 识别关键技术约束和需求
|
||||||
|
|
||||||
|
**允许**:
|
||||||
|
- 阅读文件
|
||||||
|
- 提出澄清问题
|
||||||
|
- 理解代码结构
|
||||||
|
- 分析系统架构
|
||||||
|
- 识别技术债务或约束
|
||||||
|
- 创建任务文件(参见下方任务文件模板)
|
||||||
|
- 使用文件工具创建或更新任务文件的‘Analysis’部分
|
||||||
|
|
||||||
|
**禁止**:
|
||||||
|
- 提出建议
|
||||||
|
- 实施任何改变
|
||||||
|
- 规划
|
||||||
|
- 任何行动或解决方案的暗示
|
||||||
|
|
||||||
|
**研究协议步骤**:
|
||||||
|
1. 分析与任务相关的代码:
|
||||||
|
- 识别核心文件/功能
|
||||||
|
- 追踪代码流程
|
||||||
|
- 记录发现以供后续使用
|
||||||
|
|
||||||
|
**思考过程**:
|
||||||
|
```md
|
||||||
|
嗯... [系统思维方法的推理过程]
|
||||||
|
```
|
||||||
|
|
||||||
|
**输出格式**:
|
||||||
|
以[MODE: RESEARCH]开始,然后仅提供观察和问题。
|
||||||
|
使用markdown语法格式化答案。
|
||||||
|
除非明确要求,否则避免使用项目符号。
|
||||||
|
|
||||||
|
**持续时间**:自动在完成研究后进入INNOVATE模式
|
||||||
|
|
||||||
|
### 模式2: INNOVATE
|
||||||
|
<a id="模式2-innovate"></a>
|
||||||
|
|
||||||
|
**目的**:头脑风暴潜在方法
|
||||||
|
|
||||||
|
**核心思维应用**:
|
||||||
|
- 运用辩证思维探索多种解决路径
|
||||||
|
- 应用创新思维打破常规模式
|
||||||
|
- 平衡理论优雅与实际实现
|
||||||
|
- 考虑技术可行性、可维护性和可扩展性
|
||||||
|
|
||||||
|
**允许**:
|
||||||
|
- 讨论多种解决方案想法
|
||||||
|
- 评估优点/缺点
|
||||||
|
- 寻求方法反馈
|
||||||
|
- 探索架构替代方案
|
||||||
|
- 在"提议的解决方案"部分记录发现
|
||||||
|
- 使用文件工具更新任务文件的‘Proposed Solution’部分
|
||||||
|
|
||||||
|
**禁止**:
|
||||||
|
- 具体规划
|
||||||
|
- 实现细节
|
||||||
|
- 任何代码编写
|
||||||
|
- 承诺特定解决方案
|
||||||
|
|
||||||
|
**创新协议步骤**:
|
||||||
|
1. 基于研究分析创建方案:
|
||||||
|
- 研究依赖关系
|
||||||
|
- 考虑多种实现方法
|
||||||
|
- 评估每种方法的利弊
|
||||||
|
- 添加到任务文件的"提议的解决方案"部分
|
||||||
|
2. 暂不进行代码更改
|
||||||
|
|
||||||
|
**思考过程**:
|
||||||
|
```md
|
||||||
|
嗯... [创造性、辩证的推理过程]
|
||||||
|
```
|
||||||
|
|
||||||
|
**输出格式**:
|
||||||
|
以[MODE: INNOVATE]开始,然后仅提供可能性和考虑事项。
|
||||||
|
以自然流畅的段落呈现想法。
|
||||||
|
保持不同解决方案元素之间的有机联系。
|
||||||
|
|
||||||
|
**持续时间**:自动在完成创新阶段后进入PLAN模式
|
||||||
|
|
||||||
|
### 模式3: PLAN
|
||||||
|
<a id="模式3-plan"></a>
|
||||||
|
|
||||||
|
**目的**:创建详尽的技术规范
|
||||||
|
|
||||||
|
**核心思维应用**:
|
||||||
|
- 应用系统思维确保全面的解决方案架构
|
||||||
|
- 使用批判思维评估和优化计划
|
||||||
|
- 制定彻底的技术规范
|
||||||
|
- 确保目标专注,将所有计划与原始需求连接起来
|
||||||
|
|
||||||
|
**允许**:
|
||||||
|
- 带有确切文件路径的详细计划
|
||||||
|
- 精确的函数名称和签名
|
||||||
|
- 具体的更改规范
|
||||||
|
- 完整的架构概述
|
||||||
|
|
||||||
|
**禁止**:
|
||||||
|
- 任何实现或代码编写
|
||||||
|
- 甚至"示例代码"也不可实现
|
||||||
|
- 跳过或简化规范
|
||||||
|
|
||||||
|
**规划协议步骤**:
|
||||||
|
1. 查看"任务进度"历史(如果存在)
|
||||||
|
2. 详细规划下一步更改
|
||||||
|
3. 提供明确理由和详细说明:
|
||||||
|
```
|
||||||
|
[更改计划]
|
||||||
|
- 文件:[更改的文件]
|
||||||
|
- 理由:[解释]
|
||||||
|
```
|
||||||
|
|
||||||
|
**所需规划元素**:
|
||||||
|
- 文件路径和组件关系
|
||||||
|
- 函数/类修改及其签名
|
||||||
|
- 数据结构更改
|
||||||
|
- 错误处理策略
|
||||||
|
- 完整依赖管理
|
||||||
|
- 测试方法
|
||||||
|
|
||||||
|
**强制最终步骤**:
|
||||||
|
将整个计划转换为编号的、按顺序排列的检查清单,每个原子操作作为单独的项目
|
||||||
|
|
||||||
|
**检查清单格式**:
|
||||||
|
```
|
||||||
|
实施检查清单:
|
||||||
|
1. [具体操作1]
|
||||||
|
2. [具体操作2]
|
||||||
|
...
|
||||||
|
n. [最终操作]
|
||||||
|
```
|
||||||
|
|
||||||
|
**输出格式**:
|
||||||
|
以[MODE: PLAN]开始,然后仅提供规范和实现细节。
|
||||||
|
使用markdown语法格式化答案。
|
||||||
|
|
||||||
|
**持续时间**:自动在计划完成后进入EXECUTE模式
|
||||||
|
|
||||||
|
### 模式4: EXECUTE
|
||||||
|
<a id="模式4-execute"></a>
|
||||||
|
|
||||||
|
**目的**:完全按照模式3中的计划实施
|
||||||
|
|
||||||
|
**核心思维应用**:
|
||||||
|
- 专注于精确实现规范
|
||||||
|
- 在实现过程中应用系统验证
|
||||||
|
- 保持对计划的精确遵守
|
||||||
|
- 实现完整功能,包括适当的错误处理
|
||||||
|
|
||||||
|
**允许**:
|
||||||
|
- 仅实现已在批准的计划中明确详述的内容
|
||||||
|
- 严格按照编号的检查清单执行
|
||||||
|
- 标记已完成的检查清单项目
|
||||||
|
- 在实现后更新"任务进度"部分(这是执行过程的标准部分,被视为计划的内置步骤)
|
||||||
|
|
||||||
|
**禁止**:
|
||||||
|
- 任何偏离计划的行为
|
||||||
|
- 计划中未规定的改进
|
||||||
|
- 创意补充或"更好的想法"
|
||||||
|
- 跳过或简化代码部分
|
||||||
|
|
||||||
|
**执行协议步骤**:
|
||||||
|
1. 完全按计划实施更改
|
||||||
|
2. 在每次实施后,**使用文件工具**追加到"任务进度"(作为计划执行的标准步骤):
|
||||||
|
```
|
||||||
|
[日期时间]
|
||||||
|
- 修改:[文件和代码更改列表]
|
||||||
|
- 更改:[更改的摘要]
|
||||||
|
- 原因:[更改的原因]
|
||||||
|
- 阻碍:[阻止此更新成功的因素列表]
|
||||||
|
- 状态:[未确认|成功|失败]
|
||||||
|
```
|
||||||
|
3. 要求用户确认:"状态:成功/失败?"
|
||||||
|
4. 如果失败:返回PLAN模式
|
||||||
|
5. 如果成功且需要更多更改:继续下一项
|
||||||
|
6. 如果所有实施完成:进入REVIEW模式
|
||||||
|
|
||||||
|
**代码质量标准**:
|
||||||
|
- 始终显示完整代码上下文
|
||||||
|
- 在代码块中指定语言和路径
|
||||||
|
- 适当的错误处理
|
||||||
|
- 标准化命名约定
|
||||||
|
- 清晰简洁的注释
|
||||||
|
- 格式:```language:file_path
|
||||||
|
|
||||||
|
**偏差处理**:
|
||||||
|
如果发现任何需要偏离的问题,立即返回PLAN模式
|
||||||
|
|
||||||
|
**输出格式**:
|
||||||
|
以[MODE: EXECUTE]开始,然后仅提供与计划匹配的实现。
|
||||||
|
包括已完成的检查清单项目。
|
||||||
|
|
||||||
|
### 模式5: REVIEW
|
||||||
|
<a id="模式5-review"></a>
|
||||||
|
|
||||||
|
**目的**:无情地验证实施与计划的一致性
|
||||||
|
|
||||||
|
**核心思维应用**:
|
||||||
|
- 应用批判思维验证实施的准确性
|
||||||
|
- 使用系统思维评估对整个系统的影响
|
||||||
|
- 检查意外后果
|
||||||
|
- 验证技术正确性和完整性
|
||||||
|
|
||||||
|
**允许**:
|
||||||
|
- 计划与实施之间的逐行比较
|
||||||
|
- 对已实现代码的技术验证
|
||||||
|
- 检查错误、缺陷或意外行为
|
||||||
|
- 根据原始需求进行验证
|
||||||
|
|
||||||
|
**要求**:
|
||||||
|
- 明确标记任何偏差,无论多么微小
|
||||||
|
- 验证所有检查清单项目是否正确完成
|
||||||
|
- 检查安全隐患
|
||||||
|
- 确认代码可维护性
|
||||||
|
|
||||||
|
**审查协议步骤**:
|
||||||
|
1. 根据计划验证所有实施
|
||||||
|
2. **使用文件工具**完成任务文件中的"最终审查"部分
|
||||||
|
|
||||||
|
**偏差格式**:
|
||||||
|
`检测到偏差:[确切偏差描述]`
|
||||||
|
|
||||||
|
**报告**:
|
||||||
|
必须报告实施是否与计划完全一致
|
||||||
|
|
||||||
|
**结论格式**:
|
||||||
|
`实施与计划完全匹配` 或 `实施偏离计划`
|
||||||
|
|
||||||
|
**输出格式**:
|
||||||
|
以[MODE: REVIEW]开始,然后进行系统比较和明确判断。
|
||||||
|
使用markdown语法格式化。
|
||||||
|
|
||||||
|
## 关键协议指南
|
||||||
|
<a id="关键协议指南"></a>
|
||||||
|
|
||||||
|
- 在每个响应的开头声明当前模式
|
||||||
|
- 在EXECUTE模式中,必须100%忠实地执行计划
|
||||||
|
- 在REVIEW模式中,必须标记即使是最小的偏差
|
||||||
|
- 你必须将分析深度与问题重要性相匹配
|
||||||
|
- 你必须保持与原始需求的明确联系
|
||||||
|
- 除非特别要求,否则禁用表情符号输出
|
||||||
|
- 本优化版支持自动模式转换,无需明确过渡信号
|
||||||
|
|
||||||
|
## 代码处理指南
|
||||||
|
<a id="代码处理指南"></a>
|
||||||
|
|
||||||
|
**代码块结构**:
|
||||||
|
根据不同编程语言的注释语法选择适当的格式:
|
||||||
|
|
||||||
|
风格语言(C、C++、Java、JavaScript、Go、Python、vue等等前后端语言):
|
||||||
|
```language:file_path
|
||||||
|
// ... existing code ...
|
||||||
|
{{ modifications }}
|
||||||
|
// ... existing code ...
|
||||||
|
```
|
||||||
|
|
||||||
|
如果语言类型不确定,使用通用格式:
|
||||||
|
```language:file_path
|
||||||
|
[... existing code ...]
|
||||||
|
{{ modifications }}
|
||||||
|
[... existing code ...]
|
||||||
|
```
|
||||||
|
|
||||||
|
**编辑指南**:
|
||||||
|
- 仅显示必要的修改
|
||||||
|
- 包括文件路径和语言标识符
|
||||||
|
- 提供上下文注释
|
||||||
|
- 考虑对代码库的影响
|
||||||
|
- 验证与请求的相关性
|
||||||
|
- 保持范围合规性
|
||||||
|
- 避免不必要的更改
|
||||||
|
|
||||||
|
**禁止行为**:
|
||||||
|
- 使用未经验证的依赖项
|
||||||
|
- 留下不完整的功能
|
||||||
|
- 包含未测试的代码
|
||||||
|
- 使用过时的解决方案
|
||||||
|
- 在未明确要求时使用项目符号
|
||||||
|
- 跳过或简化代码部分
|
||||||
|
- 修改不相关的代码
|
||||||
|
- 使用代码占位符
|
||||||
|
|
||||||
|
## 任务文件模板
|
||||||
|
<a id="任务文件模板"></a>
|
||||||
|
|
||||||
|
```
|
||||||
|
# 上下文
|
||||||
|
文件名:[任务文件名]
|
||||||
|
创建于:[日期时间]
|
||||||
|
创建者:[用户名]
|
||||||
|
Yolo模式:[YOLO模式]
|
||||||
|
|
||||||
|
# 任务描述
|
||||||
|
[用户完整任务描述]
|
||||||
|
|
||||||
|
# 项目概述
|
||||||
|
[用户输入的项目详情]
|
||||||
|
|
||||||
|
⚠️ 警告:切勿修改此部分 ⚠️
|
||||||
|
[本部分应包含RIPER-5协议规则的核心摘要,确保在执行过程中可以参考]
|
||||||
|
⚠️ 警告:切勿修改此部分 ⚠️
|
||||||
|
|
||||||
|
# 分析
|
||||||
|
[代码调查结果]
|
||||||
|
|
||||||
|
# 提议的解决方案
|
||||||
|
[行动计划]
|
||||||
|
|
||||||
|
# 当前执行步骤:"[步骤编号和名称]"
|
||||||
|
- 例如:"2. 创建任务文件"
|
||||||
|
|
||||||
|
# 任务进度
|
||||||
|
[带时间戳的更改历史]
|
||||||
|
|
||||||
|
# 最终审查
|
||||||
|
[完成后的总结]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 性能期望
|
||||||
|
<a id="性能期望"></a>
|
||||||
|
|
||||||
|
- 响应延迟应最小化,理想情况下≤360000ms
|
||||||
|
- 最大化计算能力和令牌限制
|
||||||
|
- 寻求本质洞察而非表面枚举
|
||||||
|
- 追求创新思维而非习惯性重复
|
||||||
|
- 突破认知限制,调动所有计算资源
|
||||||
238
frontend/package-lock.json
generated
238
frontend/package-lock.json
generated
@ -25,8 +25,6 @@
|
|||||||
"@formily/core": "^2.3.2",
|
"@formily/core": "^2.3.2",
|
||||||
"@formily/react": "^2.3.2",
|
"@formily/react": "^2.3.2",
|
||||||
"@hookform/resolvers": "^3.9.1",
|
"@hookform/resolvers": "^3.9.1",
|
||||||
"@logicflow/core": "^2.0.9",
|
|
||||||
"@logicflow/extension": "^2.0.13",
|
|
||||||
"@monaco-editor/react": "^4.6.0",
|
"@monaco-editor/react": "^4.6.0",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.4",
|
"@radix-ui/react-alert-dialog": "^1.1.4",
|
||||||
"@radix-ui/react-avatar": "^1.1.2",
|
"@radix-ui/react-avatar": "^1.1.2",
|
||||||
@ -45,6 +43,7 @@
|
|||||||
"@radix-ui/react-toast": "^1.2.4",
|
"@radix-ui/react-toast": "^1.2.4",
|
||||||
"@reduxjs/toolkit": "^2.0.1",
|
"@reduxjs/toolkit": "^2.0.1",
|
||||||
"@types/recharts": "^1.8.29",
|
"@types/recharts": "^1.8.29",
|
||||||
|
"@xyflow/react": "^12.8.6",
|
||||||
"ajv": "^8.17.1",
|
"ajv": "^8.17.1",
|
||||||
"ajv-formats": "^3.0.1",
|
"ajv-formats": "^3.0.1",
|
||||||
"antd": "^5.23.1",
|
"antd": "^5.23.1",
|
||||||
@ -515,11 +514,6 @@
|
|||||||
"@antv/event-emitter": "^0.1.3"
|
"@antv/event-emitter": "^0.1.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@antv/hierarchy": {
|
|
||||||
"version": "0.6.14",
|
|
||||||
"resolved": "https://registry.npmmirror.com/@antv/hierarchy/-/hierarchy-0.6.14.tgz",
|
|
||||||
"integrity": "sha512-V3uknf7bhynOqQDw2sg+9r9DwZ9pc6k/EcqyTFdfXB1+ydr7urisP0MipIuimucvQKN+Qkd+d6w601r1UIroqQ=="
|
|
||||||
},
|
|
||||||
"node_modules/@antv/layout": {
|
"node_modules/@antv/layout": {
|
||||||
"version": "1.2.14-beta.8",
|
"version": "1.2.14-beta.8",
|
||||||
"resolved": "https://registry.npmmirror.com/@antv/layout/-/layout-1.2.14-beta.8.tgz",
|
"resolved": "https://registry.npmmirror.com/@antv/layout/-/layout-1.2.14-beta.8.tgz",
|
||||||
@ -2069,40 +2063,6 @@
|
|||||||
"integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==",
|
"integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==",
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/@logicflow/core": {
|
|
||||||
"version": "2.0.9",
|
|
||||||
"resolved": "https://registry.npmmirror.com/@logicflow/core/-/core-2.0.9.tgz",
|
|
||||||
"integrity": "sha512-HRc3W+XbcXbq9E3wElFNkaxNBja7Ga+FK6LYbsoOGWDWzZ7gSBCaYTpKPUlQsfUK0EvztqzCuer2DRJQQ77Ylg==",
|
|
||||||
"dependencies": {
|
|
||||||
"classnames": "^2.3.2",
|
|
||||||
"lodash-es": "^4.17.21",
|
|
||||||
"mobx": "^5.15.7",
|
|
||||||
"mobx-preact": "^3.0.0",
|
|
||||||
"mobx-utils": "^5.6.1",
|
|
||||||
"mousetrap": "^1.6.5",
|
|
||||||
"preact": "^10.17.1",
|
|
||||||
"uuid": "^9.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@logicflow/extension": {
|
|
||||||
"version": "2.0.13",
|
|
||||||
"resolved": "https://registry.npmmirror.com/@logicflow/extension/-/extension-2.0.13.tgz",
|
|
||||||
"integrity": "sha512-1csZP2RYyGItvOMxVSpzrP7MguPrMRQYV+PUbm+8jZLrFbt3LcOQCNdHsEFU0cfuaiBF6Nx4qBdBeo0kog0eCw==",
|
|
||||||
"dependencies": {
|
|
||||||
"@antv/hierarchy": "^0.6.11",
|
|
||||||
"@logicflow/core": "2.0.9",
|
|
||||||
"classnames": "^2.3.2",
|
|
||||||
"lodash-es": "^4.17.21",
|
|
||||||
"medium-editor": "^5.23.3",
|
|
||||||
"mobx": "^5.15.7",
|
|
||||||
"preact": "^10.17.1",
|
|
||||||
"rangy": "^1.3.1",
|
|
||||||
"vanilla-picker": "^2.12.3"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@logicflow/core": "2.0.9"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@monaco-editor/loader": {
|
"node_modules/@monaco-editor/loader": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmmirror.com/@monaco-editor/loader/-/loader-1.4.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@monaco-editor/loader/-/loader-1.4.0.tgz",
|
||||||
@ -3541,11 +3501,6 @@
|
|||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@sphinxxxx/color-conversion": {
|
|
||||||
"version": "2.2.2",
|
|
||||||
"resolved": "https://registry.npmmirror.com/@sphinxxxx/color-conversion/-/color-conversion-2.2.2.tgz",
|
|
||||||
"integrity": "sha512-XExJS3cLqgrmNBIP3bBw6+1oQ1ksGjFh0+oClDKFYpCCqx/hlqwWO5KO/S63fzUo67SxI9dMrF0y5T/Ey7h8Zw=="
|
|
||||||
},
|
|
||||||
"node_modules/@types/babel__core": {
|
"node_modules/@types/babel__core": {
|
||||||
"version": "7.20.5",
|
"version": "7.20.5",
|
||||||
"resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz",
|
"resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||||
@ -3597,6 +3552,14 @@
|
|||||||
"resolved": "https://registry.npmmirror.com/@types/d3-color/-/d3-color-3.1.3.tgz",
|
"resolved": "https://registry.npmmirror.com/@types/d3-color/-/d3-color-3.1.3.tgz",
|
||||||
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="
|
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/d3-drag": {
|
||||||
|
"version": "3.0.7",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@types/d3-drag/-/d3-drag-3.0.7.tgz",
|
||||||
|
"integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-selection": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/d3-ease": {
|
"node_modules/@types/d3-ease": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmmirror.com/@types/d3-ease/-/d3-ease-3.0.2.tgz",
|
"resolved": "https://registry.npmmirror.com/@types/d3-ease/-/d3-ease-3.0.2.tgz",
|
||||||
@ -3623,6 +3586,11 @@
|
|||||||
"@types/d3-time": "*"
|
"@types/d3-time": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/d3-selection": {
|
||||||
|
"version": "3.0.11",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@types/d3-selection/-/d3-selection-3.0.11.tgz",
|
||||||
|
"integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w=="
|
||||||
|
},
|
||||||
"node_modules/@types/d3-shape": {
|
"node_modules/@types/d3-shape": {
|
||||||
"version": "1.3.12",
|
"version": "1.3.12",
|
||||||
"resolved": "https://registry.npmmirror.com/@types/d3-shape/-/d3-shape-1.3.12.tgz",
|
"resolved": "https://registry.npmmirror.com/@types/d3-shape/-/d3-shape-1.3.12.tgz",
|
||||||
@ -3641,6 +3609,23 @@
|
|||||||
"resolved": "https://registry.npmmirror.com/@types/d3-timer/-/d3-timer-3.0.2.tgz",
|
"resolved": "https://registry.npmmirror.com/@types/d3-timer/-/d3-timer-3.0.2.tgz",
|
||||||
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="
|
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/d3-transition": {
|
||||||
|
"version": "3.0.9",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@types/d3-transition/-/d3-transition-3.0.9.tgz",
|
||||||
|
"integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-selection": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-zoom": {
|
||||||
|
"version": "3.0.8",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
|
||||||
|
"integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-interpolate": "*",
|
||||||
|
"@types/d3-selection": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/dagre": {
|
"node_modules/@types/dagre": {
|
||||||
"version": "0.7.52",
|
"version": "0.7.52",
|
||||||
"resolved": "https://registry.npmmirror.com/@types/dagre/-/dagre-0.7.52.tgz",
|
"resolved": "https://registry.npmmirror.com/@types/dagre/-/dagre-0.7.52.tgz",
|
||||||
@ -4142,6 +4127,36 @@
|
|||||||
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
|
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@xyflow/react": {
|
||||||
|
"version": "12.8.6",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@xyflow/react/-/react-12.8.6.tgz",
|
||||||
|
"integrity": "sha512-SksAm2m4ySupjChphMmzvm55djtgMDPr+eovPDdTnyGvShf73cvydfoBfWDFllooIQ4IaiUL5yfxHRwU0c37EA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@xyflow/system": "0.0.70",
|
||||||
|
"classcat": "^5.0.3",
|
||||||
|
"zustand": "^4.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=17",
|
||||||
|
"react-dom": ">=17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@xyflow/system": {
|
||||||
|
"version": "0.0.70",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@xyflow/system/-/system-0.0.70.tgz",
|
||||||
|
"integrity": "sha512-PpC//u9zxdjj0tfTSmZrg3+sRbTz6kop/Amky44U2Dl51sxzDTIUfXMwETOYpmr2dqICWXBIJwXL2a9QWtX2XA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-drag": "^3.0.7",
|
||||||
|
"@types/d3-interpolate": "^3.0.4",
|
||||||
|
"@types/d3-selection": "^3.0.10",
|
||||||
|
"@types/d3-transition": "^3.0.8",
|
||||||
|
"@types/d3-zoom": "^3.0.8",
|
||||||
|
"d3-drag": "^3.0.0",
|
||||||
|
"d3-interpolate": "^3.0.1",
|
||||||
|
"d3-selection": "^3.0.0",
|
||||||
|
"d3-zoom": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/acorn": {
|
"node_modules/acorn": {
|
||||||
"version": "8.14.0",
|
"version": "8.14.0",
|
||||||
"resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.14.0.tgz",
|
"resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.14.0.tgz",
|
||||||
@ -4662,6 +4677,11 @@
|
|||||||
"url": "https://polar.sh/cva"
|
"url": "https://polar.sh/cva"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/classcat": {
|
||||||
|
"version": "5.0.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/classcat/-/classcat-5.0.5.tgz",
|
||||||
|
"integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w=="
|
||||||
|
},
|
||||||
"node_modules/classnames": {
|
"node_modules/classnames": {
|
||||||
"version": "2.5.1",
|
"version": "2.5.1",
|
||||||
"resolved": "https://registry.npmmirror.com/classnames/-/classnames-2.5.1.tgz",
|
"resolved": "https://registry.npmmirror.com/classnames/-/classnames-2.5.1.tgz",
|
||||||
@ -4922,6 +4942,18 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/d3-drag": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/d3-drag/-/d3-drag-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-dispatch": "1 - 3",
|
||||||
|
"d3-selection": "3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/d3-ease": {
|
"node_modules/d3-ease": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmmirror.com/d3-ease/-/d3-ease-3.0.1.tgz",
|
"resolved": "https://registry.npmmirror.com/d3-ease/-/d3-ease-3.0.1.tgz",
|
||||||
@ -5013,6 +5045,14 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/d3-selection": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/d3-selection/-/d3-selection-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/d3-shape": {
|
"node_modules/d3-shape": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmmirror.com/d3-shape/-/d3-shape-3.2.0.tgz",
|
"resolved": "https://registry.npmmirror.com/d3-shape/-/d3-shape-3.2.0.tgz",
|
||||||
@ -5054,6 +5094,39 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/d3-transition": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/d3-transition/-/d3-transition-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-color": "1 - 3",
|
||||||
|
"d3-dispatch": "1 - 3",
|
||||||
|
"d3-ease": "1 - 3",
|
||||||
|
"d3-interpolate": "1 - 3",
|
||||||
|
"d3-timer": "1 - 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"d3-selection": "2 - 3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-zoom": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/d3-zoom/-/d3-zoom-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-dispatch": "1 - 3",
|
||||||
|
"d3-drag": "2 - 3",
|
||||||
|
"d3-interpolate": "1 - 3",
|
||||||
|
"d3-selection": "2 - 3",
|
||||||
|
"d3-transition": "2 - 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dagre": {
|
"node_modules/dagre": {
|
||||||
"version": "0.8.5",
|
"version": "0.8.5",
|
||||||
"resolved": "https://registry.npmmirror.com/dagre/-/dagre-0.8.5.tgz",
|
"resolved": "https://registry.npmmirror.com/dagre/-/dagre-0.8.5.tgz",
|
||||||
@ -6021,11 +6094,6 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/hoist-non-react-statics": {
|
|
||||||
"version": "2.5.5",
|
|
||||||
"resolved": "https://registry.npmmirror.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
|
|
||||||
"integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw=="
|
|
||||||
},
|
|
||||||
"node_modules/iconv-lite": {
|
"node_modules/iconv-lite": {
|
||||||
"version": "0.6.3",
|
"version": "0.6.3",
|
||||||
"resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
"resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||||
@ -6551,11 +6619,6 @@
|
|||||||
"semver": "bin/semver"
|
"semver": "bin/semver"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/medium-editor": {
|
|
||||||
"version": "5.23.3",
|
|
||||||
"resolved": "https://registry.npmmirror.com/medium-editor/-/medium-editor-5.23.3.tgz",
|
|
||||||
"integrity": "sha512-he9/TdjX8f8MGdXGfCs8AllrYnqXJJvjNkDKmPg3aPW/uoIrlRqtkFthrwvmd+u4QyzEiadhCCM0EwTiRdUCJw=="
|
|
||||||
},
|
|
||||||
"node_modules/memoize-one": {
|
"node_modules/memoize-one": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz",
|
||||||
@ -6682,35 +6745,6 @@
|
|||||||
"ml-array-rescale": "^1.3.7"
|
"ml-array-rescale": "^1.3.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mobx": {
|
|
||||||
"version": "5.15.7",
|
|
||||||
"resolved": "https://registry.npmmirror.com/mobx/-/mobx-5.15.7.tgz",
|
|
||||||
"integrity": "sha512-wyM3FghTkhmC+hQjyPGGFdpehrcX1KOXsDuERhfK2YbJemkUhEB+6wzEN639T21onxlfYBmriA1PFnvxTUhcKw==",
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/mobx"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/mobx-preact": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmmirror.com/mobx-preact/-/mobx-preact-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-ijan/cBs3WmRye87E5+3JmoFBB00KDAwNA3pm7bMwYLPHBAXlN86aC3gdrXw8aKzM5RI8V3a993PphzPv6P4FA==",
|
|
||||||
"dependencies": {
|
|
||||||
"hoist-non-react-statics": "^2.3.1"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"mobx": "5.x",
|
|
||||||
"preact": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/mobx-utils": {
|
|
||||||
"version": "5.6.2",
|
|
||||||
"resolved": "https://registry.npmmirror.com/mobx-utils/-/mobx-utils-5.6.2.tgz",
|
|
||||||
"integrity": "sha512-a/WlXyGkp6F12b01sTarENpxbmlRgPHFyR1Xv2bsSjQBm5dcOtd16ONb40/vOqck8L99NHpI+C9MXQ+SZ8f+yw==",
|
|
||||||
"peerDependencies": {
|
|
||||||
"mobx": "^4.13.1 || ^5.13.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/monaco-editor": {
|
"node_modules/monaco-editor": {
|
||||||
"version": "0.52.2",
|
"version": "0.52.2",
|
||||||
"resolved": "https://registry.npmmirror.com/monaco-editor/-/monaco-editor-0.52.2.tgz",
|
"resolved": "https://registry.npmmirror.com/monaco-editor/-/monaco-editor-0.52.2.tgz",
|
||||||
@ -7244,15 +7278,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/preact": {
|
|
||||||
"version": "10.25.1",
|
|
||||||
"resolved": "https://registry.npmmirror.com/preact/-/preact-10.25.1.tgz",
|
|
||||||
"integrity": "sha512-frxeZV2vhQSohQwJ7FvlqC40ze89+8friponWUFeVEkaCfhC6Eu4V0iND5C9CXz8JLndV07QRDeXzH1+Anz5Og==",
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/preact"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/prelude-ls": {
|
"node_modules/prelude-ls": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
"resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||||
@ -7334,11 +7359,6 @@
|
|||||||
"safe-buffer": "^5.1.0"
|
"safe-buffer": "^5.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rangy": {
|
|
||||||
"version": "1.3.2",
|
|
||||||
"resolved": "https://registry.npmmirror.com/rangy/-/rangy-1.3.2.tgz",
|
|
||||||
"integrity": "sha512-fS1C4MOyk8T+ZJZdLcgrukPWxkyDXa+Hd2Kj+Zg4wIK71yrWgmjzHubzPMY1G+WD9EgGxMp3fIL0zQ1ickmSWA=="
|
|
||||||
},
|
|
||||||
"node_modules/rc-align": {
|
"node_modules/rc-align": {
|
||||||
"version": "2.4.5",
|
"version": "2.4.5",
|
||||||
"resolved": "https://registry.npmmirror.com/rc-align/-/rc-align-2.4.5.tgz",
|
"resolved": "https://registry.npmmirror.com/rc-align/-/rc-align-2.4.5.tgz",
|
||||||
@ -9310,26 +9330,6 @@
|
|||||||
"node": ">= 4"
|
"node": ">= 4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/uuid": {
|
|
||||||
"version": "9.0.1",
|
|
||||||
"resolved": "https://registry.npmmirror.com/uuid/-/uuid-9.0.1.tgz",
|
|
||||||
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
|
|
||||||
"funding": [
|
|
||||||
"https://github.com/sponsors/broofa",
|
|
||||||
"https://github.com/sponsors/ctavan"
|
|
||||||
],
|
|
||||||
"bin": {
|
|
||||||
"uuid": "dist/bin/uuid"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/vanilla-picker": {
|
|
||||||
"version": "2.12.3",
|
|
||||||
"resolved": "https://registry.npmmirror.com/vanilla-picker/-/vanilla-picker-2.12.3.tgz",
|
|
||||||
"integrity": "sha512-qVkT1E7yMbUsB2mmJNFmaXMWE2hF8ffqzMMwe9zdAikd8u2VfnsVY2HQcOUi2F38bgbxzlJBEdS1UUhOXdF9GQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"@sphinxxxx/color-conversion": "^2.2.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/victory-vendor": {
|
"node_modules/victory-vendor": {
|
||||||
"version": "36.9.2",
|
"version": "36.9.2",
|
||||||
"resolved": "https://registry.npmmirror.com/victory-vendor/-/victory-vendor-36.9.2.tgz",
|
"resolved": "https://registry.npmmirror.com/victory-vendor/-/victory-vendor-36.9.2.tgz",
|
||||||
|
|||||||
@ -45,6 +45,7 @@
|
|||||||
"@radix-ui/react-toast": "^1.2.4",
|
"@radix-ui/react-toast": "^1.2.4",
|
||||||
"@reduxjs/toolkit": "^2.0.1",
|
"@reduxjs/toolkit": "^2.0.1",
|
||||||
"@types/recharts": "^1.8.29",
|
"@types/recharts": "^1.8.29",
|
||||||
|
"@xyflow/react": "^12.8.6",
|
||||||
"ajv": "^8.17.1",
|
"ajv": "^8.17.1",
|
||||||
"ajv-formats": "^3.0.1",
|
"ajv-formats": "^3.0.1",
|
||||||
"antd": "^5.23.1",
|
"antd": "^5.23.1",
|
||||||
|
|||||||
164
frontend/src/pages/Workflow2/Definition/components/EditModal.tsx
Normal file
164
frontend/src/pages/Workflow2/Definition/components/EditModal.tsx
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { Modal, Form, Input, Select, message } from 'antd';
|
||||||
|
import * as service from '../service';
|
||||||
|
import type { WorkflowDefinition, WorkflowCategory } from '../types';
|
||||||
|
|
||||||
|
interface EditModalProps {
|
||||||
|
visible: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSuccess: () => void;
|
||||||
|
record?: WorkflowDefinition;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EditModal: React.FC<EditModalProps> = ({
|
||||||
|
visible,
|
||||||
|
onClose,
|
||||||
|
onSuccess,
|
||||||
|
record
|
||||||
|
}) => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [loading, setLoading] = React.useState(false);
|
||||||
|
const [categories, setCategories] = React.useState<WorkflowCategory[]>([]);
|
||||||
|
|
||||||
|
// 加载工作流分类
|
||||||
|
useEffect(() => {
|
||||||
|
const loadCategories = async () => {
|
||||||
|
try {
|
||||||
|
const data = await service.getWorkflowCategories();
|
||||||
|
setCategories(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载工作流分类失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (visible) {
|
||||||
|
loadCategories();
|
||||||
|
}
|
||||||
|
}, [visible]);
|
||||||
|
|
||||||
|
// 设置表单初始值
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible && record) {
|
||||||
|
form.setFieldsValue({
|
||||||
|
name: record.name,
|
||||||
|
key: record.key,
|
||||||
|
description: record.description,
|
||||||
|
category: record.category,
|
||||||
|
triggers: record.triggers || []
|
||||||
|
});
|
||||||
|
} else if (visible) {
|
||||||
|
form.resetFields();
|
||||||
|
}
|
||||||
|
}, [visible, record, form]);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
const values = await form.validateFields();
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
if (record) {
|
||||||
|
await service.updateDefinition(record.id, values);
|
||||||
|
message.success('更新成功');
|
||||||
|
} else {
|
||||||
|
await service.createDefinition({
|
||||||
|
...values,
|
||||||
|
status: 'DRAFT',
|
||||||
|
graph: { nodes: [], edges: [] },
|
||||||
|
formConfig: { formItems: [] }
|
||||||
|
});
|
||||||
|
message.success('创建成功');
|
||||||
|
}
|
||||||
|
|
||||||
|
onSuccess();
|
||||||
|
onClose();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存失败:', error);
|
||||||
|
if (error instanceof Error) {
|
||||||
|
message.error(error.message);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={record ? '编辑工作流' : '新建工作流'}
|
||||||
|
open={visible}
|
||||||
|
onCancel={onClose}
|
||||||
|
onOk={handleSubmit}
|
||||||
|
confirmLoading={loading}
|
||||||
|
width={600}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
layout="vertical"
|
||||||
|
initialValues={{
|
||||||
|
triggers: []
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
label="流程名称"
|
||||||
|
name="name"
|
||||||
|
rules={[{ required: true, message: '请输入流程名称' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="请输入流程名称" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label="流程标识"
|
||||||
|
name="key"
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: '请输入流程标识' },
|
||||||
|
{ pattern: /^[a-zA-Z][a-zA-Z0-9_]*$/, message: '流程标识只能包含字母、数字和下划线,且以字母开头' }
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input placeholder="请输入流程标识" disabled={!!record} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label="流程分类"
|
||||||
|
name="category"
|
||||||
|
rules={[{ required: true, message: '请选择流程分类' }]}
|
||||||
|
>
|
||||||
|
<Select placeholder="请选择流程分类">
|
||||||
|
{categories.map(category => (
|
||||||
|
<Select.Option key={category.code} value={category.code}>
|
||||||
|
{category.label}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label="触发方式"
|
||||||
|
name="triggers"
|
||||||
|
rules={[{ required: true, message: '请选择至少一种触发方式' }]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
mode="multiple"
|
||||||
|
placeholder="请选择触发方式"
|
||||||
|
options={categories.find(c => c.code === form.getFieldValue('category'))?.supportedTriggers?.map(trigger => ({
|
||||||
|
label: trigger.label,
|
||||||
|
value: trigger.code
|
||||||
|
})) || []}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label="描述"
|
||||||
|
name="description"
|
||||||
|
>
|
||||||
|
<Input.TextArea
|
||||||
|
placeholder="请输入流程描述"
|
||||||
|
rows={3}
|
||||||
|
maxLength={500}
|
||||||
|
showCount
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditModal;
|
||||||
247
frontend/src/pages/Workflow2/Definition/index.tsx
Normal file
247
frontend/src/pages/Workflow2/Definition/index.tsx
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Table, Card, Button, Space, Tag, message, Modal } from 'antd';
|
||||||
|
import { PlusOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import * as service from './service';
|
||||||
|
import type { WorkflowDefinition, WorkflowDefinitionQuery, WorkflowCategory } from './types';
|
||||||
|
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
|
||||||
|
import EditModal from './components/EditModal';
|
||||||
|
|
||||||
|
const { confirm } = Modal;
|
||||||
|
|
||||||
|
const WorkflowDefinitionList: React.FC = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [pageData, setPageData] = useState<{
|
||||||
|
content: WorkflowDefinition[];
|
||||||
|
totalElements: number;
|
||||||
|
size: number;
|
||||||
|
number: number;
|
||||||
|
} | null>(null);
|
||||||
|
const [modalVisible, setModalVisible] = useState(false);
|
||||||
|
const [currentRecord, setCurrentRecord] = useState<WorkflowDefinition>();
|
||||||
|
const [categories, setCategories] = useState<WorkflowCategory[]>([]);
|
||||||
|
const [query, setQuery] = useState<WorkflowDefinitionQuery>({
|
||||||
|
pageNum: DEFAULT_CURRENT - 1,
|
||||||
|
pageSize: DEFAULT_PAGE_SIZE
|
||||||
|
});
|
||||||
|
|
||||||
|
const loadData = async (params: WorkflowDefinitionQuery) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const data = await service.getDefinitions(params);
|
||||||
|
setPageData(data);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
message.error(error.message);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadCategories = async () => {
|
||||||
|
try {
|
||||||
|
const data = await service.getWorkflowCategories();
|
||||||
|
setCategories(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载工作流分类失败:', error);
|
||||||
|
message.error('加载工作流分类失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadData(query);
|
||||||
|
loadCategories();
|
||||||
|
}, [query]);
|
||||||
|
|
||||||
|
const handleCreateFlow = () => {
|
||||||
|
setCurrentRecord(undefined);
|
||||||
|
setModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditFlow = (record: WorkflowDefinition) => {
|
||||||
|
setCurrentRecord(record);
|
||||||
|
setModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDesignFlow = (record: WorkflowDefinition) => {
|
||||||
|
// 新的React Flow设计器路径
|
||||||
|
navigate(`/workflow2/design/${record.id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleModalClose = () => {
|
||||||
|
setModalVisible(false);
|
||||||
|
setCurrentRecord(undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeploy = async (id: number) => {
|
||||||
|
confirm({
|
||||||
|
title: '确认发布',
|
||||||
|
icon: <ExclamationCircleOutlined />,
|
||||||
|
content: '确定要发布该流程定义吗?发布后将不能修改。',
|
||||||
|
onOk: async () => {
|
||||||
|
try {
|
||||||
|
await service.publishDefinition(id);
|
||||||
|
message.success('发布成功');
|
||||||
|
loadData(query);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
message.error(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async (id: number) => {
|
||||||
|
confirm({
|
||||||
|
title: '确认删除',
|
||||||
|
icon: <ExclamationCircleOutlined />,
|
||||||
|
content: '确定要删除该流程定义吗?删除后不可恢复。',
|
||||||
|
onOk: async () => {
|
||||||
|
try {
|
||||||
|
await service.deleteDefinition(id);
|
||||||
|
message.success('删除成功');
|
||||||
|
loadData(query);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
message.error(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleStartFlow = async (record: WorkflowDefinition) => {
|
||||||
|
try {
|
||||||
|
await service.startWorkflowInstance(record.key, record.category);
|
||||||
|
message.success('流程启动成功');
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
message.error(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '流程名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '流程标识',
|
||||||
|
dataIndex: 'key',
|
||||||
|
key: 'key',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '流程分类',
|
||||||
|
dataIndex: 'category',
|
||||||
|
key: 'category',
|
||||||
|
render: (category: string) => {
|
||||||
|
const categoryInfo = categories.find(c => c.code === category);
|
||||||
|
return categoryInfo?.label || category;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '触发方式',
|
||||||
|
dataIndex: 'triggers',
|
||||||
|
key: 'triggers',
|
||||||
|
render: (triggers: string[], record: WorkflowDefinition) => {
|
||||||
|
const categoryInfo = categories.find(c => c.code === record.category);
|
||||||
|
return (triggers || [])?.map(triggerCode => {
|
||||||
|
const triggerInfo = categoryInfo?.supportedTriggers?.find(t => t.code === triggerCode);
|
||||||
|
return (
|
||||||
|
<Tag key={triggerCode}>
|
||||||
|
{triggerInfo?.label || triggerCode}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '版本',
|
||||||
|
dataIndex: 'flowVersion',
|
||||||
|
key: 'flowVersion',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
key: 'status',
|
||||||
|
render: (status: string) => (
|
||||||
|
<Tag color={status === 'DRAFT' ? 'orange' : 'green'}>
|
||||||
|
{status === 'DRAFT' ? '草稿' : '已发布'}
|
||||||
|
</Tag>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '描述',
|
||||||
|
dataIndex: 'description',
|
||||||
|
key: 'description',
|
||||||
|
ellipsis: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
fixed: 'right' as const,
|
||||||
|
width: 220,
|
||||||
|
render: (_: any, record: WorkflowDefinition) => (
|
||||||
|
<Space size="middle">
|
||||||
|
{record.status === 'DRAFT' && (
|
||||||
|
<>
|
||||||
|
<a onClick={() => handleEditFlow(record)}>编辑</a>
|
||||||
|
<a onClick={() => handleDesignFlow(record)}>设计(React Flow)</a>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{record.status === 'DRAFT' && (
|
||||||
|
<a onClick={() => handleDeploy(record.id)}>发布</a>
|
||||||
|
)}
|
||||||
|
<a onClick={() => handleDelete(record.id)}>删除</a>
|
||||||
|
{record.status !== 'DRAFT' && (
|
||||||
|
<a onClick={() => handleStartFlow(record)}>启动</a>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
title={
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
|
<span>工作流定义管理 (React Flow版本)</span>
|
||||||
|
<Button type="primary" icon={<PlusOutlined />} onClick={handleCreateFlow}>
|
||||||
|
新建流程
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
dataSource={pageData?.content}
|
||||||
|
loading={loading}
|
||||||
|
rowKey="id"
|
||||||
|
scroll={{ x: 1400 }}
|
||||||
|
pagination={{
|
||||||
|
current: (query.pageNum || 0) + 1,
|
||||||
|
pageSize: query.pageSize,
|
||||||
|
total: pageData?.totalElements || 0,
|
||||||
|
onChange: (page, pageSize) => setQuery({
|
||||||
|
...query,
|
||||||
|
pageNum: page - 1,
|
||||||
|
pageSize
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<EditModal
|
||||||
|
visible={modalVisible}
|
||||||
|
onClose={handleModalClose}
|
||||||
|
onSuccess={() => loadData(query)}
|
||||||
|
record={currentRecord}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WorkflowDefinitionList;
|
||||||
71
frontend/src/pages/Workflow2/Definition/service.ts
Normal file
71
frontend/src/pages/Workflow2/Definition/service.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import request from '@/utils/request';
|
||||||
|
import type { WorkflowDefinition, WorkflowDefinitionQuery, WorkflowCategory } from './types';
|
||||||
|
import type { Page } from '@/types/base';
|
||||||
|
|
||||||
|
// API 基础路径
|
||||||
|
const DEFINITION_URL = '/api/v1/workflow-definitions';
|
||||||
|
const CATEGORY_URL = '/api/v1/workflow-categories';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取工作流定义列表
|
||||||
|
*/
|
||||||
|
export const getDefinitions = (params?: WorkflowDefinitionQuery) =>
|
||||||
|
request.get<Page<WorkflowDefinition>>(`${DEFINITION_URL}/page`, { params });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取工作流定义详情
|
||||||
|
*/
|
||||||
|
export const getDefinitionDetail = (id: number) =>
|
||||||
|
request.get<WorkflowDefinition>(`${DEFINITION_URL}/${id}`);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建工作流定义
|
||||||
|
*/
|
||||||
|
export const createDefinition = (data: Partial<WorkflowDefinition>) =>
|
||||||
|
request.post<WorkflowDefinition>(DEFINITION_URL, data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新工作流定义
|
||||||
|
*/
|
||||||
|
export const updateDefinition = (id: number, data: Partial<WorkflowDefinition>) =>
|
||||||
|
request.put<WorkflowDefinition>(`${DEFINITION_URL}/${id}`, data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除工作流定义
|
||||||
|
*/
|
||||||
|
export const deleteDefinition = (id: number) =>
|
||||||
|
request.delete(`${DEFINITION_URL}/${id}`);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发布工作流定义
|
||||||
|
*/
|
||||||
|
export const publishDefinition = (id: number) =>
|
||||||
|
request.post<void>(`${DEFINITION_URL}/${id}/publish`);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动工作流实例
|
||||||
|
*/
|
||||||
|
export const startWorkflowInstance = (key: string, category: string, variables?: Record<string, any>) =>
|
||||||
|
request.post<void>(`/api/v1/workflow-instances/start`, {
|
||||||
|
definitionKey: key,
|
||||||
|
category,
|
||||||
|
variables
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取工作流分类
|
||||||
|
*/
|
||||||
|
export const getWorkflowCategories = () =>
|
||||||
|
request.get<WorkflowCategory[]>(CATEGORY_URL);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存工作流图形数据
|
||||||
|
*/
|
||||||
|
export const saveWorkflowGraph = (id: number, graphData: any) =>
|
||||||
|
request.put<void>(`${DEFINITION_URL}/${id}/graph`, graphData);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取工作流图形数据
|
||||||
|
*/
|
||||||
|
export const getWorkflowGraph = (id: number) =>
|
||||||
|
request.get<any>(`${DEFINITION_URL}/${id}/graph`);
|
||||||
74
frontend/src/pages/Workflow2/Definition/types.ts
Normal file
74
frontend/src/pages/Workflow2/Definition/types.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { BaseResponse, BaseQuery } from '@/types/base';
|
||||||
|
|
||||||
|
// 复用现有的工作流定义类型,保持API兼容性
|
||||||
|
export interface WorkflowDefinition extends BaseResponse {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
key: string;
|
||||||
|
description?: string;
|
||||||
|
flowVersion?: number;
|
||||||
|
status?: string;
|
||||||
|
category: string;
|
||||||
|
triggers: string[];
|
||||||
|
graph: {
|
||||||
|
nodes: WorkflowDefinitionNode[];
|
||||||
|
edges: any[];
|
||||||
|
};
|
||||||
|
formConfig: {
|
||||||
|
formItems: any[];
|
||||||
|
};
|
||||||
|
formVariablesSchema?: {
|
||||||
|
type: string;
|
||||||
|
required?: string[];
|
||||||
|
properties: {
|
||||||
|
[key: string]: {
|
||||||
|
type: string;
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
dataSource?: {
|
||||||
|
url: string;
|
||||||
|
type: string;
|
||||||
|
params?: Record<string, any>;
|
||||||
|
dependsOn?: string[];
|
||||||
|
labelField?: string;
|
||||||
|
valueField?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
bpmnXml?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorkflowDefinitionNode {
|
||||||
|
id: number;
|
||||||
|
nodeCode: string;
|
||||||
|
nodeType: string;
|
||||||
|
nodeName: string;
|
||||||
|
uiVariables: JSON;
|
||||||
|
panelVariables: JSON;
|
||||||
|
localVariables: JSON;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorkflowDefinitionQuery extends BaseQuery {
|
||||||
|
name?: string;
|
||||||
|
key?: string;
|
||||||
|
status?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作流触发器类型
|
||||||
|
*/
|
||||||
|
export interface WorkflowTrigger {
|
||||||
|
code: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作流分类
|
||||||
|
*/
|
||||||
|
export interface WorkflowCategory {
|
||||||
|
code: string;
|
||||||
|
label: string;
|
||||||
|
description: string;
|
||||||
|
supportedTriggers: WorkflowTrigger[];
|
||||||
|
}
|
||||||
@ -0,0 +1,72 @@
|
|||||||
|
import React, { memo } from 'react';
|
||||||
|
import { Handle, Position, NodeProps } from '@xyflow/react';
|
||||||
|
import type { FlowNodeData } from '../../types';
|
||||||
|
|
||||||
|
const EndEventNode: React.FC<NodeProps> = ({ data, selected }) => {
|
||||||
|
const nodeData = data as FlowNodeData;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: '8px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
border: `3px solid ${selected ? '#3b82f6' : '#ef4444'}`,
|
||||||
|
background: '#fef2f2',
|
||||||
|
minWidth: '60px',
|
||||||
|
minHeight: '60px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
position: 'relative',
|
||||||
|
boxShadow: selected ? '0 4px 8px rgba(59, 130, 246, 0.3)' : '0 2px 4px rgba(0,0,0,0.1)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'all 0.2s ease-in-out',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* 图标 */}
|
||||||
|
<div style={{
|
||||||
|
fontSize: '18px',
|
||||||
|
color: '#ef4444',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
fontWeight: 'bold'
|
||||||
|
}}>
|
||||||
|
⏹️
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 输入连接点 */}
|
||||||
|
<Handle
|
||||||
|
type="target"
|
||||||
|
position={Position.Left}
|
||||||
|
style={{
|
||||||
|
background: '#ef4444',
|
||||||
|
border: '2px solid white',
|
||||||
|
width: '10px',
|
||||||
|
height: '10px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 节点标签 */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: '-25px',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translateX(-50%)',
|
||||||
|
fontSize: '12px',
|
||||||
|
color: '#374151',
|
||||||
|
fontWeight: '500',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
background: 'white',
|
||||||
|
padding: '2px 6px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
boxShadow: '0 1px 2px rgba(0,0,0,0.1)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{nodeData.label || '结束'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(EndEventNode);
|
||||||
@ -0,0 +1,102 @@
|
|||||||
|
import React, { memo } from 'react';
|
||||||
|
import { Handle, Position, NodeProps } from '@xyflow/react';
|
||||||
|
import type { FlowNodeData } from '../../types';
|
||||||
|
|
||||||
|
const ServiceTaskNode: React.FC<NodeProps> = ({ data, selected }) => {
|
||||||
|
const nodeData = data as FlowNodeData;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: '12px 16px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: `2px solid ${selected ? '#3b82f6' : '#f59e0b'}`,
|
||||||
|
background: '#fffbeb',
|
||||||
|
minWidth: '120px',
|
||||||
|
minHeight: '60px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
position: 'relative',
|
||||||
|
boxShadow: selected ? '0 4px 8px rgba(59, 130, 246, 0.3)' : '0 2px 4px rgba(0,0,0,0.1)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'all 0.2s ease-in-out',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* 输入连接点 */}
|
||||||
|
<Handle
|
||||||
|
type="target"
|
||||||
|
position={Position.Left}
|
||||||
|
style={{
|
||||||
|
background: '#f59e0b',
|
||||||
|
border: '2px solid white',
|
||||||
|
width: '10px',
|
||||||
|
height: '10px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 节点内容 */}
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '4px'
|
||||||
|
}}>
|
||||||
|
{/* 图标 */}
|
||||||
|
<div style={{
|
||||||
|
fontSize: '16px',
|
||||||
|
color: '#f59e0b',
|
||||||
|
}}>
|
||||||
|
⚙️
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 标签 */}
|
||||||
|
<div style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
color: '#374151',
|
||||||
|
fontWeight: '500',
|
||||||
|
textAlign: 'center',
|
||||||
|
lineHeight: '1.2'
|
||||||
|
}}>
|
||||||
|
{nodeData.label || '服务任务'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 输出连接点 */}
|
||||||
|
<Handle
|
||||||
|
type="source"
|
||||||
|
position={Position.Right}
|
||||||
|
style={{
|
||||||
|
background: '#f59e0b',
|
||||||
|
border: '2px solid white',
|
||||||
|
width: '10px',
|
||||||
|
height: '10px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 配置指示器 */}
|
||||||
|
{nodeData.configs && Object.keys(nodeData.configs).length > 0 && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '-6px',
|
||||||
|
right: '-6px',
|
||||||
|
width: '12px',
|
||||||
|
height: '12px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: '#10b981',
|
||||||
|
border: '2px solid white',
|
||||||
|
fontSize: '8px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
color: 'white'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
✓
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(ServiceTaskNode);
|
||||||
@ -0,0 +1,71 @@
|
|||||||
|
import React, { memo } from 'react';
|
||||||
|
import { Handle, Position, NodeProps } from '@xyflow/react';
|
||||||
|
import type { FlowNodeData } from '../../types';
|
||||||
|
|
||||||
|
const StartEventNode: React.FC<NodeProps> = ({ data, selected }) => {
|
||||||
|
const nodeData = data as FlowNodeData;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: '8px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
border: `2px solid ${selected ? '#3b82f6' : '#10b981'}`,
|
||||||
|
background: '#ecfdf5',
|
||||||
|
minWidth: '60px',
|
||||||
|
minHeight: '60px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
position: 'relative',
|
||||||
|
boxShadow: selected ? '0 4px 8px rgba(59, 130, 246, 0.3)' : '0 2px 4px rgba(0,0,0,0.1)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'all 0.2s ease-in-out',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* 图标 */}
|
||||||
|
<div style={{
|
||||||
|
fontSize: '20px',
|
||||||
|
color: '#10b981',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
|
}}>
|
||||||
|
▶️
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 输出连接点 */}
|
||||||
|
<Handle
|
||||||
|
type="source"
|
||||||
|
position={Position.Right}
|
||||||
|
style={{
|
||||||
|
background: '#10b981',
|
||||||
|
border: '2px solid white',
|
||||||
|
width: '10px',
|
||||||
|
height: '10px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 节点标签 */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: '-25px',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translateX(-50%)',
|
||||||
|
fontSize: '12px',
|
||||||
|
color: '#374151',
|
||||||
|
fontWeight: '500',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
background: 'white',
|
||||||
|
padding: '2px 6px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
boxShadow: '0 1px 2px rgba(0,0,0,0.1)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{nodeData.label || '开始'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(StartEventNode);
|
||||||
@ -0,0 +1,102 @@
|
|||||||
|
import React, { memo } from 'react';
|
||||||
|
import { Handle, Position, NodeProps } from '@xyflow/react';
|
||||||
|
import type { FlowNodeData } from '../../types';
|
||||||
|
|
||||||
|
const UserTaskNode: React.FC<NodeProps> = ({ data, selected }) => {
|
||||||
|
const nodeData = data as FlowNodeData;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: '12px 16px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: `2px solid ${selected ? '#3b82f6' : '#6366f1'}`,
|
||||||
|
background: '#f8faff',
|
||||||
|
minWidth: '120px',
|
||||||
|
minHeight: '60px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
position: 'relative',
|
||||||
|
boxShadow: selected ? '0 4px 8px rgba(59, 130, 246, 0.3)' : '0 2px 4px rgba(0,0,0,0.1)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'all 0.2s ease-in-out',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* 输入连接点 */}
|
||||||
|
<Handle
|
||||||
|
type="target"
|
||||||
|
position={Position.Left}
|
||||||
|
style={{
|
||||||
|
background: '#6366f1',
|
||||||
|
border: '2px solid white',
|
||||||
|
width: '10px',
|
||||||
|
height: '10px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 节点内容 */}
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '4px'
|
||||||
|
}}>
|
||||||
|
{/* 图标 */}
|
||||||
|
<div style={{
|
||||||
|
fontSize: '16px',
|
||||||
|
color: '#6366f1',
|
||||||
|
}}>
|
||||||
|
👤
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 标签 */}
|
||||||
|
<div style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
color: '#374151',
|
||||||
|
fontWeight: '500',
|
||||||
|
textAlign: 'center',
|
||||||
|
lineHeight: '1.2'
|
||||||
|
}}>
|
||||||
|
{nodeData.label || '用户任务'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 输出连接点 */}
|
||||||
|
<Handle
|
||||||
|
type="source"
|
||||||
|
position={Position.Right}
|
||||||
|
style={{
|
||||||
|
background: '#6366f1',
|
||||||
|
border: '2px solid white',
|
||||||
|
width: '10px',
|
||||||
|
height: '10px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 配置指示器 */}
|
||||||
|
{nodeData.configs && Object.keys(nodeData.configs).length > 0 && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '-6px',
|
||||||
|
right: '-6px',
|
||||||
|
width: '12px',
|
||||||
|
height: '12px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: '#10b981',
|
||||||
|
border: '2px solid white',
|
||||||
|
fontSize: '8px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
color: 'white'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
✓
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(UserTaskNode);
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
// 统一导出所有自定义节点组件
|
||||||
|
export { default as StartEventNode } from './StartEventNode';
|
||||||
|
export { default as EndEventNode } from './EndEventNode';
|
||||||
|
export { default as UserTaskNode } from './UserTaskNode';
|
||||||
|
export { default as ServiceTaskNode } from './ServiceTaskNode';
|
||||||
212
frontend/src/pages/Workflow2/Design/components/FlowCanvas.tsx
Normal file
212
frontend/src/pages/Workflow2/Design/components/FlowCanvas.tsx
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import {
|
||||||
|
ReactFlow,
|
||||||
|
Background,
|
||||||
|
Controls,
|
||||||
|
MiniMap,
|
||||||
|
useNodesState,
|
||||||
|
useEdgesState,
|
||||||
|
addEdge,
|
||||||
|
Connection,
|
||||||
|
Edge,
|
||||||
|
BackgroundVariant,
|
||||||
|
Panel
|
||||||
|
} from '@xyflow/react';
|
||||||
|
import '@xyflow/react/dist/style.css';
|
||||||
|
|
||||||
|
import type { FlowNode, FlowEdge } from '../types';
|
||||||
|
import {
|
||||||
|
StartEventNode,
|
||||||
|
EndEventNode,
|
||||||
|
UserTaskNode,
|
||||||
|
ServiceTaskNode
|
||||||
|
} from './CustomNodes';
|
||||||
|
|
||||||
|
// 注册自定义节点类型
|
||||||
|
const nodeTypes = {
|
||||||
|
START_EVENT: StartEventNode,
|
||||||
|
END_EVENT: EndEventNode,
|
||||||
|
USER_TASK: UserTaskNode,
|
||||||
|
SERVICE_TASK: ServiceTaskNode,
|
||||||
|
};
|
||||||
|
|
||||||
|
interface FlowCanvasProps {
|
||||||
|
initialNodes?: FlowNode[];
|
||||||
|
initialEdges?: FlowEdge[];
|
||||||
|
onNodesChange?: (nodes: FlowNode[]) => void;
|
||||||
|
onEdgesChange?: (edges: FlowEdge[]) => void;
|
||||||
|
onNodeClick?: (event: React.MouseEvent, node: any) => void;
|
||||||
|
onEdgeClick?: (event: React.MouseEvent, edge: any) => void;
|
||||||
|
onDrop?: (event: React.DragEvent) => void;
|
||||||
|
onDragOver?: (event: React.DragEvent) => void;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FlowCanvas: React.FC<FlowCanvasProps> = ({
|
||||||
|
initialNodes = [],
|
||||||
|
initialEdges = [],
|
||||||
|
onNodesChange,
|
||||||
|
onEdgesChange,
|
||||||
|
onNodeClick,
|
||||||
|
onEdgeClick,
|
||||||
|
onDrop,
|
||||||
|
onDragOver,
|
||||||
|
className = ''
|
||||||
|
}) => {
|
||||||
|
const [nodes, , onNodesStateChange] = useNodesState(initialNodes);
|
||||||
|
const [edges, setEdges, onEdgesStateChange] = useEdgesState(initialEdges);
|
||||||
|
|
||||||
|
// 处理连接
|
||||||
|
const onConnect = useCallback(
|
||||||
|
(params: Connection | Edge) => {
|
||||||
|
const newEdge = {
|
||||||
|
...params,
|
||||||
|
type: 'default',
|
||||||
|
animated: true,
|
||||||
|
data: {
|
||||||
|
label: '连接',
|
||||||
|
condition: {
|
||||||
|
type: 'DEFAULT' as const,
|
||||||
|
priority: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
setEdges((eds) => addEdge(newEdge, eds));
|
||||||
|
},
|
||||||
|
[setEdges]
|
||||||
|
);
|
||||||
|
|
||||||
|
// 节点变化处理
|
||||||
|
const handleNodesChange = useCallback((changes: any) => {
|
||||||
|
onNodesStateChange(changes);
|
||||||
|
if (onNodesChange) {
|
||||||
|
// 延迟获取最新状态 - 在实际项目中可以使用useEffect监听nodes变化
|
||||||
|
setTimeout(() => {
|
||||||
|
onNodesChange(nodes as FlowNode[]);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
}, [onNodesStateChange, onNodesChange, nodes]);
|
||||||
|
|
||||||
|
// 边变化处理
|
||||||
|
const handleEdgesChange = useCallback((changes: any) => {
|
||||||
|
onEdgesStateChange(changes);
|
||||||
|
if (onEdgesChange) {
|
||||||
|
// 延迟获取最新状态 - 在实际项目中可以使用useEffect监听edges变化
|
||||||
|
setTimeout(() => {
|
||||||
|
onEdgesChange(edges as FlowEdge[]);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
}, [onEdgesStateChange, onEdgesChange, edges]);
|
||||||
|
|
||||||
|
// 连接验证
|
||||||
|
const isValidConnection = useCallback((connection: Connection | Edge) => {
|
||||||
|
// 防止自连接
|
||||||
|
if (connection.source === connection.target) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否已存在连接
|
||||||
|
const isDuplicate = edges.some(
|
||||||
|
(edge) =>
|
||||||
|
edge.source === connection.source &&
|
||||||
|
edge.target === connection.target
|
||||||
|
);
|
||||||
|
|
||||||
|
return !isDuplicate;
|
||||||
|
}, [edges]);
|
||||||
|
|
||||||
|
// 处理拖拽放置
|
||||||
|
const handleDrop = useCallback((event: React.DragEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (onDrop) {
|
||||||
|
onDrop(event);
|
||||||
|
}
|
||||||
|
}, [onDrop]);
|
||||||
|
|
||||||
|
// 处理拖拽悬停
|
||||||
|
const handleDragOver = useCallback((event: React.DragEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.dataTransfer.dropEffect = 'move';
|
||||||
|
|
||||||
|
if (onDragOver) {
|
||||||
|
onDragOver(event);
|
||||||
|
}
|
||||||
|
}, [onDragOver]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`flow-canvas ${className}`} style={{ width: '100%', height: '100%', position: 'relative', overflow: 'hidden' }}>
|
||||||
|
<ReactFlow
|
||||||
|
nodes={nodes}
|
||||||
|
edges={edges}
|
||||||
|
onNodesChange={handleNodesChange}
|
||||||
|
onEdgesChange={handleEdgesChange}
|
||||||
|
onConnect={onConnect}
|
||||||
|
onNodeClick={onNodeClick}
|
||||||
|
onEdgeClick={onEdgeClick}
|
||||||
|
onDrop={handleDrop}
|
||||||
|
onDragOver={handleDragOver}
|
||||||
|
nodeTypes={nodeTypes}
|
||||||
|
isValidConnection={isValidConnection}
|
||||||
|
fitView
|
||||||
|
fitViewOptions={{
|
||||||
|
padding: 0.1,
|
||||||
|
includeHiddenNodes: false,
|
||||||
|
minZoom: 0.5,
|
||||||
|
maxZoom: 1.5,
|
||||||
|
duration: 800,
|
||||||
|
}}
|
||||||
|
minZoom={0.1}
|
||||||
|
maxZoom={4}
|
||||||
|
deleteKeyCode={['Backspace', 'Delete']}
|
||||||
|
multiSelectionKeyCode={['Meta', 'Ctrl']}
|
||||||
|
panOnScroll
|
||||||
|
selectionOnDrag
|
||||||
|
panOnDrag={[1, 2]}
|
||||||
|
selectNodesOnDrag={false}
|
||||||
|
style={{ width: '100%', height: '100%' }}
|
||||||
|
>
|
||||||
|
<Background
|
||||||
|
variant={BackgroundVariant.Dots}
|
||||||
|
gap={20}
|
||||||
|
size={1}
|
||||||
|
color="#e5e7eb"
|
||||||
|
/>
|
||||||
|
<Controls
|
||||||
|
position="bottom-right"
|
||||||
|
showZoom
|
||||||
|
showFitView
|
||||||
|
showInteractive
|
||||||
|
/>
|
||||||
|
<MiniMap
|
||||||
|
position="bottom-left"
|
||||||
|
nodeColor="#3b82f6"
|
||||||
|
maskColor="rgba(0, 0, 0, 0.2)"
|
||||||
|
style={{
|
||||||
|
height: 120,
|
||||||
|
width: 200,
|
||||||
|
backgroundColor: '#f9fafb',
|
||||||
|
border: '1px solid #e5e7eb',
|
||||||
|
borderRadius: '8px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 状态面板 */}
|
||||||
|
<Panel position="top-left">
|
||||||
|
<div style={{
|
||||||
|
background: 'white',
|
||||||
|
padding: '8px 12px',
|
||||||
|
borderRadius: '6px',
|
||||||
|
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
|
||||||
|
fontSize: '12px',
|
||||||
|
color: '#6b7280'
|
||||||
|
}}>
|
||||||
|
节点: {nodes.length}, 连接: {edges.length}
|
||||||
|
</div>
|
||||||
|
</Panel>
|
||||||
|
</ReactFlow>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FlowCanvas;
|
||||||
248
frontend/src/pages/Workflow2/Design/components/NodePanel.tsx
Normal file
248
frontend/src/pages/Workflow2/Design/components/NodePanel.tsx
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Card, Divider } from 'antd';
|
||||||
|
import { NodeType, NodeCategory, type NodeDefinitionData } from '../types';
|
||||||
|
|
||||||
|
// 节点定义数据
|
||||||
|
const nodeDefinitions: NodeDefinitionData[] = [
|
||||||
|
{
|
||||||
|
nodeCode: 'start_event',
|
||||||
|
nodeName: '开始事件',
|
||||||
|
nodeType: NodeType.START_EVENT,
|
||||||
|
category: NodeCategory.EVENT,
|
||||||
|
description: '工作流开始节点',
|
||||||
|
icon: '▶️',
|
||||||
|
color: '#10b981'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nodeCode: 'end_event',
|
||||||
|
nodeName: '结束事件',
|
||||||
|
nodeType: NodeType.END_EVENT,
|
||||||
|
category: NodeCategory.EVENT,
|
||||||
|
description: '工作流结束节点',
|
||||||
|
icon: '⏹️',
|
||||||
|
color: '#ef4444'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nodeCode: 'user_task',
|
||||||
|
nodeName: '用户任务',
|
||||||
|
nodeType: NodeType.USER_TASK,
|
||||||
|
category: NodeCategory.TASK,
|
||||||
|
description: '需要用户手动处理的任务',
|
||||||
|
icon: '👤',
|
||||||
|
color: '#6366f1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nodeCode: 'service_task',
|
||||||
|
nodeName: '服务任务',
|
||||||
|
nodeType: NodeType.SERVICE_TASK,
|
||||||
|
category: NodeCategory.TASK,
|
||||||
|
description: '自动执行的服务任务',
|
||||||
|
icon: '⚙️',
|
||||||
|
color: '#f59e0b'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nodeCode: 'script_task',
|
||||||
|
nodeName: '脚本任务',
|
||||||
|
nodeType: NodeType.SCRIPT_TASK,
|
||||||
|
category: NodeCategory.TASK,
|
||||||
|
description: '执行脚本代码的任务',
|
||||||
|
icon: '📜',
|
||||||
|
color: '#8b5cf6'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nodeCode: 'deploy_node',
|
||||||
|
nodeName: '部署节点',
|
||||||
|
nodeType: NodeType.DEPLOY_NODE,
|
||||||
|
category: NodeCategory.TASK,
|
||||||
|
description: '应用部署任务',
|
||||||
|
icon: '🚀',
|
||||||
|
color: '#06b6d4'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nodeCode: 'jenkins_build',
|
||||||
|
nodeName: 'Jenkins构建',
|
||||||
|
nodeType: NodeType.JENKINS_BUILD,
|
||||||
|
category: NodeCategory.TASK,
|
||||||
|
description: 'Jenkins构建任务',
|
||||||
|
icon: '🔨',
|
||||||
|
color: '#dc2626'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nodeCode: 'gateway_node',
|
||||||
|
nodeName: '网关节点',
|
||||||
|
nodeType: NodeType.GATEWAY_NODE,
|
||||||
|
category: NodeCategory.GATEWAY,
|
||||||
|
description: '条件分支网关',
|
||||||
|
icon: '💎',
|
||||||
|
color: '#7c3aed'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
interface NodePanelProps {
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NodePanel: React.FC<NodePanelProps> = ({ className = '' }) => {
|
||||||
|
// 按分类分组节点
|
||||||
|
const nodesByCategory = nodeDefinitions.reduce((acc, node) => {
|
||||||
|
if (!acc[node.category]) {
|
||||||
|
acc[node.category] = [];
|
||||||
|
}
|
||||||
|
acc[node.category].push(node);
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<NodeCategory, NodeDefinitionData[]>);
|
||||||
|
|
||||||
|
// 拖拽开始处理
|
||||||
|
const handleDragStart = (event: React.DragEvent, nodeDefinition: NodeDefinitionData) => {
|
||||||
|
event.dataTransfer.setData('application/reactflow', JSON.stringify({
|
||||||
|
nodeType: nodeDefinition.nodeType,
|
||||||
|
nodeDefinition
|
||||||
|
}));
|
||||||
|
event.dataTransfer.effectAllowed = 'move';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 渲染节点项
|
||||||
|
const renderNodeItem = (nodeDefinition: NodeDefinitionData) => (
|
||||||
|
<div
|
||||||
|
key={nodeDefinition.nodeCode}
|
||||||
|
draggable
|
||||||
|
onDragStart={(e) => handleDragStart(e, nodeDefinition)}
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '8px',
|
||||||
|
padding: '8px 12px',
|
||||||
|
borderRadius: '6px',
|
||||||
|
border: '1px solid #e5e7eb',
|
||||||
|
background: 'white',
|
||||||
|
cursor: 'grab',
|
||||||
|
transition: 'all 0.2s ease-in-out',
|
||||||
|
marginBottom: '6px'
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.background = '#f9fafb';
|
||||||
|
e.currentTarget.style.borderColor = nodeDefinition.color;
|
||||||
|
e.currentTarget.style.transform = 'translateX(2px)';
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.background = 'white';
|
||||||
|
e.currentTarget.style.borderColor = '#e5e7eb';
|
||||||
|
e.currentTarget.style.transform = 'translateX(0)';
|
||||||
|
}}
|
||||||
|
onDragStart={(e) => {
|
||||||
|
e.currentTarget.style.cursor = 'grabbing';
|
||||||
|
handleDragStart(e, nodeDefinition);
|
||||||
|
}}
|
||||||
|
onDragEnd={(e) => {
|
||||||
|
e.currentTarget.style.cursor = 'grab';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{
|
||||||
|
fontSize: '16px',
|
||||||
|
width: '20px',
|
||||||
|
textAlign: 'center'
|
||||||
|
}}>
|
||||||
|
{nodeDefinition.icon}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div style={{
|
||||||
|
fontSize: '13px',
|
||||||
|
fontWeight: '500',
|
||||||
|
color: '#374151',
|
||||||
|
lineHeight: '1.2'
|
||||||
|
}}>
|
||||||
|
{nodeDefinition.nodeName}
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
fontSize: '11px',
|
||||||
|
color: '#6b7280',
|
||||||
|
lineHeight: '1.2',
|
||||||
|
marginTop: '2px'
|
||||||
|
}}>
|
||||||
|
{nodeDefinition.description}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// 分类标题映射
|
||||||
|
const categoryTitles = {
|
||||||
|
[NodeCategory.EVENT]: '🎯 事件节点',
|
||||||
|
[NodeCategory.TASK]: '📋 任务节点',
|
||||||
|
[NodeCategory.GATEWAY]: '🔀 网关节点',
|
||||||
|
[NodeCategory.CONTAINER]: '📦 容器节点'
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`node-panel ${className}`} style={{
|
||||||
|
width: '260px',
|
||||||
|
height: '100%',
|
||||||
|
background: '#f8fafc',
|
||||||
|
borderRight: '1px solid #e5e7eb',
|
||||||
|
overflow: 'hidden',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column'
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
padding: '16px',
|
||||||
|
background: 'white',
|
||||||
|
borderBottom: '1px solid #e5e7eb'
|
||||||
|
}}>
|
||||||
|
<h3 style={{
|
||||||
|
margin: 0,
|
||||||
|
fontSize: '14px',
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#374151'
|
||||||
|
}}>
|
||||||
|
节点面板
|
||||||
|
</h3>
|
||||||
|
<p style={{
|
||||||
|
margin: '4px 0 0 0',
|
||||||
|
fontSize: '12px',
|
||||||
|
color: '#6b7280'
|
||||||
|
}}>
|
||||||
|
拖拽节点到画布创建工作流
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{
|
||||||
|
flex: '1',
|
||||||
|
overflow: 'auto',
|
||||||
|
padding: '12px'
|
||||||
|
}}>
|
||||||
|
{Object.entries(nodesByCategory).map(([category, nodes]) => (
|
||||||
|
<div key={category} style={{ marginBottom: '16px' }}>
|
||||||
|
<div style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#4b5563',
|
||||||
|
marginBottom: '8px',
|
||||||
|
padding: '4px 0',
|
||||||
|
borderBottom: '1px solid #e5e7eb'
|
||||||
|
}}>
|
||||||
|
{categoryTitles[category as NodeCategory]}
|
||||||
|
</div>
|
||||||
|
{nodes.map(renderNodeItem)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 使用提示 */}
|
||||||
|
<div style={{
|
||||||
|
padding: '12px',
|
||||||
|
background: '#f1f5f9',
|
||||||
|
borderTop: '1px solid #e5e7eb',
|
||||||
|
fontSize: '11px',
|
||||||
|
color: '#64748b',
|
||||||
|
lineHeight: '1.4'
|
||||||
|
}}>
|
||||||
|
💡 提示:
|
||||||
|
<br />• 拖拽节点到画布创建
|
||||||
|
<br />• 双击节点进行配置
|
||||||
|
<br />• 连接节点创建流程
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NodePanel;
|
||||||
@ -0,0 +1,234 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Button, Space, Divider, Tooltip } from 'antd';
|
||||||
|
import {
|
||||||
|
SaveOutlined,
|
||||||
|
UndoOutlined,
|
||||||
|
RedoOutlined,
|
||||||
|
CopyOutlined,
|
||||||
|
ScissorOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
ZoomInOutlined,
|
||||||
|
ZoomOutOutlined,
|
||||||
|
ExpandOutlined,
|
||||||
|
ArrowLeftOutlined,
|
||||||
|
SelectOutlined
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
|
||||||
|
interface WorkflowToolbarProps {
|
||||||
|
title?: string;
|
||||||
|
onSave?: () => void;
|
||||||
|
onUndo?: () => void;
|
||||||
|
onRedo?: () => void;
|
||||||
|
onCopy?: () => void;
|
||||||
|
onCut?: () => void;
|
||||||
|
onPaste?: () => void;
|
||||||
|
onDelete?: () => void;
|
||||||
|
onZoomIn?: () => void;
|
||||||
|
onZoomOut?: () => void;
|
||||||
|
onFitView?: () => void;
|
||||||
|
onSelectAll?: () => void;
|
||||||
|
onBack?: () => void;
|
||||||
|
canUndo?: boolean;
|
||||||
|
canRedo?: boolean;
|
||||||
|
zoom?: number;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const WorkflowToolbar: React.FC<WorkflowToolbarProps> = ({
|
||||||
|
title = '工作流设计器',
|
||||||
|
onSave,
|
||||||
|
onUndo,
|
||||||
|
onRedo,
|
||||||
|
onCopy,
|
||||||
|
onCut,
|
||||||
|
onPaste,
|
||||||
|
onDelete,
|
||||||
|
onZoomIn,
|
||||||
|
onZoomOut,
|
||||||
|
onFitView,
|
||||||
|
onSelectAll,
|
||||||
|
onBack,
|
||||||
|
canUndo = false,
|
||||||
|
canRedo = false,
|
||||||
|
zoom = 1,
|
||||||
|
className = ''
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`workflow-toolbar ${className}`}
|
||||||
|
style={{
|
||||||
|
height: '56px',
|
||||||
|
background: 'white',
|
||||||
|
borderBottom: '1px solid #e5e7eb',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
padding: '0 16px',
|
||||||
|
boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* 左侧:标题和返回按钮 */}
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
||||||
|
<Tooltip title="返回列表">
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<ArrowLeftOutlined />}
|
||||||
|
onClick={onBack}
|
||||||
|
style={{ color: '#6b7280' }}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Divider type="vertical" />
|
||||||
|
<div>
|
||||||
|
<h2 style={{
|
||||||
|
margin: 0,
|
||||||
|
fontSize: '16px',
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#374151'
|
||||||
|
}}>
|
||||||
|
{title}
|
||||||
|
</h2>
|
||||||
|
<div style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
color: '#6b7280',
|
||||||
|
marginTop: '2px'
|
||||||
|
}}>
|
||||||
|
基于 React Flow 的工作流设计器
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 中间:主要操作按钮 */}
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<Space size={4}>
|
||||||
|
{/* 保存 */}
|
||||||
|
<Tooltip title="保存工作流">
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<SaveOutlined />}
|
||||||
|
onClick={onSave}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Divider type="vertical" />
|
||||||
|
|
||||||
|
{/* 撤销/重做 */}
|
||||||
|
<Tooltip title="撤销">
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<UndoOutlined />}
|
||||||
|
onClick={onUndo}
|
||||||
|
disabled={!canUndo}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="重做">
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<RedoOutlined />}
|
||||||
|
onClick={onRedo}
|
||||||
|
disabled={!canRedo}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Divider type="vertical" />
|
||||||
|
|
||||||
|
{/* 编辑操作 */}
|
||||||
|
<Tooltip title="复制">
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<CopyOutlined />}
|
||||||
|
onClick={onCopy}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="剪切">
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<ScissorOutlined />}
|
||||||
|
onClick={onCut}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="删除选中">
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
onClick={onDelete}
|
||||||
|
size="small"
|
||||||
|
style={{ color: '#ef4444' }}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="全选">
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<SelectOutlined />}
|
||||||
|
onClick={onSelectAll}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Divider type="vertical" />
|
||||||
|
|
||||||
|
{/* 视图操作 */}
|
||||||
|
<Tooltip title="放大">
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<ZoomInOutlined />}
|
||||||
|
onClick={onZoomIn}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="缩小">
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<ZoomOutOutlined />}
|
||||||
|
onClick={onZoomOut}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="适应视图">
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<ExpandOutlined />}
|
||||||
|
onClick={onFitView}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 右侧:状态信息 */}
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '12px',
|
||||||
|
fontSize: '12px',
|
||||||
|
color: '#6b7280'
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
background: '#f3f4f6',
|
||||||
|
padding: '4px 8px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
fontFamily: 'monospace'
|
||||||
|
}}>
|
||||||
|
缩放: {Math.round(zoom * 100)}%
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
background: '#ecfdf5',
|
||||||
|
color: '#065f46',
|
||||||
|
padding: '4px 8px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
fontWeight: '500'
|
||||||
|
}}>
|
||||||
|
React Flow
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WorkflowToolbar;
|
||||||
267
frontend/src/pages/Workflow2/Design/index.less
Normal file
267
frontend/src/pages/Workflow2/Design/index.less
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
// 工作流设计器 - React Flow版本
|
||||||
|
// 变量定义
|
||||||
|
@header-height: 64px;
|
||||||
|
@toolbar-height: 56px;
|
||||||
|
@sidebar-width: 260px;
|
||||||
|
@container-padding: 24px;
|
||||||
|
|
||||||
|
// 主容器 - 覆盖父容器样式
|
||||||
|
.workflow-design-container {
|
||||||
|
// 使用负边距抵消父容器的padding
|
||||||
|
margin: -@container-padding;
|
||||||
|
// 精确计算高度:视口高度 - header高度
|
||||||
|
height: calc(100vh - @header-height);
|
||||||
|
// 补偿左右padding
|
||||||
|
width: calc(100% + @container-padding * 2);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #ffffff;
|
||||||
|
position: relative;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
|
||||||
|
// React Flow 样式覆盖
|
||||||
|
.react-flow {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
|
||||||
|
&__renderer {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自定义控制按钮样式
|
||||||
|
&__controls {
|
||||||
|
button {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f9fafb;
|
||||||
|
border-color: #3b82f6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 小地图样式
|
||||||
|
&__minimap {
|
||||||
|
background: #f9fafb;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 内容区域
|
||||||
|
.workflow-content-area {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
// 节点面板区域
|
||||||
|
.node-panel {
|
||||||
|
width: @sidebar-width;
|
||||||
|
background: #f8fafc;
|
||||||
|
border-right: 1px solid #e5e7eb;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-shadow: 2px 0 4px rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
// 面板头部
|
||||||
|
.panel-header {
|
||||||
|
padding: 16px;
|
||||||
|
background: #ffffff;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 4px 0 0 0;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 节点分组
|
||||||
|
.node-group {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
.group-title {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #4b5563;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
padding: 4px 0;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 节点项
|
||||||
|
.node-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
background: #ffffff;
|
||||||
|
cursor: grab;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f9fafb;
|
||||||
|
border-color: var(--node-color, #3b82f6);
|
||||||
|
transform: translateX(2px);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
width: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-info {
|
||||||
|
.node-name {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #374151;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-desc {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #6b7280;
|
||||||
|
line-height: 1.2;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用提示
|
||||||
|
.panel-tips {
|
||||||
|
padding: 12px;
|
||||||
|
background: #f1f5f9;
|
||||||
|
border-top: 1px solid #e5e7eb;
|
||||||
|
font-size: 11px;
|
||||||
|
color: #64748b;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 画布区域
|
||||||
|
.workflow-canvas-area {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #f8fafc;
|
||||||
|
|
||||||
|
// 画布状态面板
|
||||||
|
.canvas-status {
|
||||||
|
position: absolute;
|
||||||
|
top: 12px;
|
||||||
|
left: 12px;
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
font-size: 12px;
|
||||||
|
color: #6b7280;
|
||||||
|
z-index: 10;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应式适配
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.workflow-content-area .node-panel {
|
||||||
|
width: 220px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.workflow-content-area {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.node-panel {
|
||||||
|
width: 100%;
|
||||||
|
height: 200px;
|
||||||
|
border-right: none;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 工具栏特殊样式
|
||||||
|
.workflow-toolbar {
|
||||||
|
height: @toolbar-height;
|
||||||
|
background: #ffffff;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 16px;
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.toolbar-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-title {
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #6b7280;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-status {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #6b7280;
|
||||||
|
|
||||||
|
.status-item {
|
||||||
|
background: #f3f4f6;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: 'Monaco', 'Menlo', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow-badge {
|
||||||
|
background: #ecfdf5;
|
||||||
|
color: #065f46;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
293
frontend/src/pages/Workflow2/Design/index.tsx
Normal file
293
frontend/src/pages/Workflow2/Design/index.tsx
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
import React, { useState, useCallback, useRef, useEffect } from 'react';
|
||||||
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
|
import { message } from 'antd';
|
||||||
|
import { ReactFlowProvider, useReactFlow } from '@xyflow/react';
|
||||||
|
|
||||||
|
import WorkflowToolbar from './components/WorkflowToolbar';
|
||||||
|
import NodePanel from './components/NodePanel';
|
||||||
|
import FlowCanvas from './components/FlowCanvas';
|
||||||
|
import type { FlowNode, FlowEdge, DragNodeData } from './types';
|
||||||
|
import { NodeType } from './types';
|
||||||
|
|
||||||
|
// 样式
|
||||||
|
import '@xyflow/react/dist/style.css';
|
||||||
|
import './index.less';
|
||||||
|
|
||||||
|
const WorkflowDesignInner: React.FC = () => {
|
||||||
|
const { id } = useParams<{ id: string }>();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const {
|
||||||
|
getNodes,
|
||||||
|
setNodes,
|
||||||
|
getEdges,
|
||||||
|
setEdges,
|
||||||
|
screenToFlowPosition,
|
||||||
|
fitView,
|
||||||
|
zoomIn,
|
||||||
|
zoomOut,
|
||||||
|
getZoom
|
||||||
|
} = useReactFlow();
|
||||||
|
|
||||||
|
const [workflowTitle, setWorkflowTitle] = useState('新建工作流');
|
||||||
|
const reactFlowWrapper = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
// 自动适应视图
|
||||||
|
useEffect(() => {
|
||||||
|
// 延迟执行fitView以确保节点已渲染
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
fitView({
|
||||||
|
padding: 0.1,
|
||||||
|
duration: 800,
|
||||||
|
minZoom: 0.5,
|
||||||
|
maxZoom: 1.5
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [fitView]);
|
||||||
|
|
||||||
|
// 初始化示例节点 - 优化位置和布局
|
||||||
|
const initialNodes: FlowNode[] = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
type: 'START_EVENT',
|
||||||
|
position: { x: 250, y: 50 },
|
||||||
|
data: {
|
||||||
|
label: '开始',
|
||||||
|
nodeType: NodeType.START_EVENT,
|
||||||
|
category: 'EVENT' as any,
|
||||||
|
icon: '▶️',
|
||||||
|
color: '#10b981'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
type: 'USER_TASK',
|
||||||
|
position: { x: 200, y: 150 },
|
||||||
|
data: {
|
||||||
|
label: '用户审批',
|
||||||
|
nodeType: NodeType.USER_TASK,
|
||||||
|
category: 'TASK' as any,
|
||||||
|
icon: '👤',
|
||||||
|
color: '#6366f1'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
type: 'END_EVENT',
|
||||||
|
position: { x: 250, y: 250 },
|
||||||
|
data: {
|
||||||
|
label: '结束',
|
||||||
|
nodeType: NodeType.END_EVENT,
|
||||||
|
category: 'EVENT' as any,
|
||||||
|
icon: '⏹️',
|
||||||
|
color: '#ef4444'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const initialEdges: FlowEdge[] = [
|
||||||
|
{
|
||||||
|
id: 'e1-2',
|
||||||
|
source: '1',
|
||||||
|
target: '2',
|
||||||
|
type: 'default',
|
||||||
|
animated: true,
|
||||||
|
data: {
|
||||||
|
label: '提交',
|
||||||
|
condition: {
|
||||||
|
type: 'DEFAULT',
|
||||||
|
priority: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'e2-3',
|
||||||
|
source: '2',
|
||||||
|
target: '3',
|
||||||
|
type: 'default',
|
||||||
|
animated: true,
|
||||||
|
data: {
|
||||||
|
label: '通过',
|
||||||
|
condition: {
|
||||||
|
type: 'DEFAULT',
|
||||||
|
priority: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// 工具栏事件处理
|
||||||
|
const handleSave = useCallback(() => {
|
||||||
|
const nodes = getNodes();
|
||||||
|
const edges = getEdges();
|
||||||
|
console.log('保存工作流:', { nodes, edges });
|
||||||
|
message.success('工作流保存成功!');
|
||||||
|
}, [getNodes, getEdges]);
|
||||||
|
|
||||||
|
const handleBack = useCallback(() => {
|
||||||
|
navigate('/workflow2/definition');
|
||||||
|
}, [navigate]);
|
||||||
|
|
||||||
|
const handleUndo = useCallback(() => {
|
||||||
|
console.log('撤销操作');
|
||||||
|
message.info('撤销功能开发中...');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleRedo = useCallback(() => {
|
||||||
|
console.log('重做操作');
|
||||||
|
message.info('重做功能开发中...');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleCopy = useCallback(() => {
|
||||||
|
const selectedNodes = getNodes().filter(node => node.selected);
|
||||||
|
if (selectedNodes.length > 0) {
|
||||||
|
console.log('复制节点:', selectedNodes);
|
||||||
|
message.success(`已复制 ${selectedNodes.length} 个节点`);
|
||||||
|
} else {
|
||||||
|
message.warning('请先选择要复制的节点');
|
||||||
|
}
|
||||||
|
}, [getNodes]);
|
||||||
|
|
||||||
|
const handleDelete = useCallback(() => {
|
||||||
|
const selectedNodes = getNodes().filter(node => node.selected);
|
||||||
|
const selectedEdges = getEdges().filter(edge => edge.selected);
|
||||||
|
|
||||||
|
if (selectedNodes.length > 0 || selectedEdges.length > 0) {
|
||||||
|
setNodes(nodes => nodes.filter(node => !node.selected));
|
||||||
|
setEdges(edges => edges.filter(edge => !edge.selected));
|
||||||
|
message.success(`已删除 ${selectedNodes.length} 个节点和 ${selectedEdges.length} 条连接`);
|
||||||
|
} else {
|
||||||
|
message.warning('请先选择要删除的元素');
|
||||||
|
}
|
||||||
|
}, [getNodes, getEdges, setNodes, setEdges]);
|
||||||
|
|
||||||
|
const handleSelectAll = useCallback(() => {
|
||||||
|
setNodes(nodes => nodes.map(node => ({ ...node, selected: true })));
|
||||||
|
setEdges(edges => edges.map(edge => ({ ...edge, selected: true })));
|
||||||
|
message.info('已全选所有元素');
|
||||||
|
}, [setNodes, setEdges]);
|
||||||
|
|
||||||
|
const handleFitView = useCallback(() => {
|
||||||
|
fitView({ padding: 0.2, duration: 800 });
|
||||||
|
}, [fitView]);
|
||||||
|
|
||||||
|
const handleZoomIn = useCallback(() => {
|
||||||
|
zoomIn({ duration: 300 });
|
||||||
|
}, [zoomIn]);
|
||||||
|
|
||||||
|
const handleZoomOut = useCallback(() => {
|
||||||
|
zoomOut({ duration: 300 });
|
||||||
|
}, [zoomOut]);
|
||||||
|
|
||||||
|
// 处理节点拖拽放置
|
||||||
|
const handleDrop = useCallback((event: React.DragEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const reactFlowBounds = reactFlowWrapper.current?.getBoundingClientRect();
|
||||||
|
if (!reactFlowBounds) return;
|
||||||
|
|
||||||
|
const dragData = event.dataTransfer.getData('application/reactflow');
|
||||||
|
if (!dragData) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { nodeType, nodeDefinition }: DragNodeData = JSON.parse(dragData);
|
||||||
|
|
||||||
|
const position = screenToFlowPosition({
|
||||||
|
x: event.clientX - reactFlowBounds.left,
|
||||||
|
y: event.clientY - reactFlowBounds.top,
|
||||||
|
});
|
||||||
|
|
||||||
|
const newNode: FlowNode = {
|
||||||
|
id: `${nodeType}-${Date.now()}`,
|
||||||
|
type: nodeType,
|
||||||
|
position,
|
||||||
|
data: {
|
||||||
|
label: nodeDefinition.nodeName,
|
||||||
|
nodeType,
|
||||||
|
category: nodeDefinition.category,
|
||||||
|
icon: nodeDefinition.icon,
|
||||||
|
color: nodeDefinition.color,
|
||||||
|
nodeDefinition
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setNodes(nodes => [...nodes, newNode]);
|
||||||
|
message.success(`已添加 ${nodeDefinition.nodeName} 节点`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('解析拖拽数据失败:', error);
|
||||||
|
message.error('添加节点失败');
|
||||||
|
}
|
||||||
|
}, [screenToFlowPosition, setNodes]);
|
||||||
|
|
||||||
|
// 处理节点点击
|
||||||
|
const handleNodeClick = useCallback((event: React.MouseEvent, node: FlowNode) => {
|
||||||
|
console.log('节点点击:', node);
|
||||||
|
message.info(`点击了节点: ${node.data.label}`);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 处理边点击
|
||||||
|
const handleEdgeClick = useCallback((event: React.MouseEvent, edge: FlowEdge) => {
|
||||||
|
console.log('边点击:', edge);
|
||||||
|
message.info(`点击了连接: ${edge.data?.label || '连接'}`);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="workflow-design-container"
|
||||||
|
style={{
|
||||||
|
// 确保覆盖父容器的overflow设置
|
||||||
|
overflow: 'hidden'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* 工具栏 */}
|
||||||
|
<WorkflowToolbar
|
||||||
|
title={workflowTitle}
|
||||||
|
onSave={handleSave}
|
||||||
|
onBack={handleBack}
|
||||||
|
onUndo={handleUndo}
|
||||||
|
onRedo={handleRedo}
|
||||||
|
onCopy={handleCopy}
|
||||||
|
onDelete={handleDelete}
|
||||||
|
onSelectAll={handleSelectAll}
|
||||||
|
onZoomIn={handleZoomIn}
|
||||||
|
onZoomOut={handleZoomOut}
|
||||||
|
onFitView={handleFitView}
|
||||||
|
canUndo={false}
|
||||||
|
canRedo={false}
|
||||||
|
zoom={getZoom()}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 主要内容区域 */}
|
||||||
|
<div className="workflow-content-area">
|
||||||
|
{/* 节点面板 */}
|
||||||
|
<NodePanel />
|
||||||
|
|
||||||
|
{/* 画布区域 */}
|
||||||
|
<div
|
||||||
|
ref={reactFlowWrapper}
|
||||||
|
className="workflow-canvas-area"
|
||||||
|
>
|
||||||
|
<FlowCanvas
|
||||||
|
initialNodes={initialNodes}
|
||||||
|
initialEdges={initialEdges}
|
||||||
|
onNodeClick={handleNodeClick}
|
||||||
|
onEdgeClick={handleEdgeClick}
|
||||||
|
onDrop={handleDrop}
|
||||||
|
className="workflow-canvas"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const WorkflowDesign: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<ReactFlowProvider>
|
||||||
|
<WorkflowDesignInner />
|
||||||
|
</ReactFlowProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WorkflowDesign;
|
||||||
117
frontend/src/pages/Workflow2/Design/types.ts
Normal file
117
frontend/src/pages/Workflow2/Design/types.ts
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import type { Node, Edge } from '@xyflow/react';
|
||||||
|
|
||||||
|
// 节点类型枚举 - 与原有系统保持一致
|
||||||
|
export enum NodeType {
|
||||||
|
START_EVENT = 'START_EVENT',
|
||||||
|
END_EVENT = 'END_EVENT',
|
||||||
|
USER_TASK = 'USER_TASK',
|
||||||
|
SERVICE_TASK = 'SERVICE_TASK',
|
||||||
|
SCRIPT_TASK = 'SCRIPT_TASK',
|
||||||
|
DEPLOY_NODE = 'DEPLOY_NODE',
|
||||||
|
JENKINS_BUILD = 'JENKINS_BUILD',
|
||||||
|
GATEWAY_NODE = 'GATEWAY_NODE',
|
||||||
|
SUB_PROCESS = 'SUB_PROCESS',
|
||||||
|
CALL_ACTIVITY = 'CALL_ACTIVITY'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 节点分类
|
||||||
|
export enum NodeCategory {
|
||||||
|
EVENT = 'EVENT',
|
||||||
|
TASK = 'TASK',
|
||||||
|
GATEWAY = 'GATEWAY',
|
||||||
|
CONTAINER = 'CONTAINER'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 节点定义数据结构
|
||||||
|
export interface NodeDefinitionData {
|
||||||
|
nodeCode: string;
|
||||||
|
nodeName: string;
|
||||||
|
nodeType: NodeType;
|
||||||
|
category: NodeCategory;
|
||||||
|
description?: string;
|
||||||
|
icon: string;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// React Flow 节点数据 - 添加索引签名以满足React Flow的类型约束
|
||||||
|
export interface FlowNodeData extends Record<string, unknown> {
|
||||||
|
label: string;
|
||||||
|
nodeType: NodeType;
|
||||||
|
category: NodeCategory;
|
||||||
|
icon: string;
|
||||||
|
color: string;
|
||||||
|
// 节点配置数据
|
||||||
|
configs?: Record<string, any>;
|
||||||
|
inputMapping?: Record<string, any>;
|
||||||
|
outputMapping?: Record<string, any>;
|
||||||
|
// 原始节点定义
|
||||||
|
nodeDefinition?: NodeDefinitionData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// React Flow 边数据 - 添加索引签名以满足React Flow的类型约束
|
||||||
|
export interface FlowEdgeData extends Record<string, unknown> {
|
||||||
|
label?: string;
|
||||||
|
condition?: {
|
||||||
|
type: 'EXPRESSION' | 'SCRIPT' | 'DEFAULT';
|
||||||
|
expression?: string;
|
||||||
|
script?: string;
|
||||||
|
priority: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 扩展的React Flow节点类型
|
||||||
|
export type FlowNode = Node<FlowNodeData>;
|
||||||
|
|
||||||
|
// 扩展的React Flow边类型
|
||||||
|
export type FlowEdge = Edge<FlowEdgeData>;
|
||||||
|
|
||||||
|
// 工作流画布数据
|
||||||
|
export interface WorkflowFlowData {
|
||||||
|
nodes: FlowNode[];
|
||||||
|
edges: FlowEdge[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 节点配置模态框属性
|
||||||
|
export interface NodeConfigModalProps {
|
||||||
|
visible: boolean;
|
||||||
|
node: FlowNode | null;
|
||||||
|
onCancel: () => void;
|
||||||
|
onOk: (node: FlowNode, updatedData: Partial<FlowNodeData>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 边配置模态框属性
|
||||||
|
export interface EdgeConfigModalProps {
|
||||||
|
visible: boolean;
|
||||||
|
edge: FlowEdge | null;
|
||||||
|
onCancel: () => void;
|
||||||
|
onOk: (edge: FlowEdge, updatedData: Partial<FlowEdgeData>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 工具栏操作类型
|
||||||
|
export interface ToolbarActions {
|
||||||
|
onSave: () => void;
|
||||||
|
onUndo: () => void;
|
||||||
|
onRedo: () => void;
|
||||||
|
onZoomIn: () => void;
|
||||||
|
onZoomOut: () => void;
|
||||||
|
onFitView: () => void;
|
||||||
|
onCopy: () => void;
|
||||||
|
onPaste: () => void;
|
||||||
|
onDelete: () => void;
|
||||||
|
onBack: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 画布状态
|
||||||
|
export interface CanvasState {
|
||||||
|
zoom: number;
|
||||||
|
canUndo: boolean;
|
||||||
|
canRedo: boolean;
|
||||||
|
selectedNodes: FlowNode[];
|
||||||
|
selectedEdges: FlowEdge[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 节点拖拽数据
|
||||||
|
export interface DragNodeData {
|
||||||
|
nodeType: NodeType;
|
||||||
|
nodeDefinition: NodeDefinitionData;
|
||||||
|
}
|
||||||
24
frontend/src/pages/Workflow2/Instance/index.tsx
Normal file
24
frontend/src/pages/Workflow2/Instance/index.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Card } from 'antd';
|
||||||
|
|
||||||
|
const WorkflowInstance: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<Card title="工作流实例管理 (React Flow版本)">
|
||||||
|
<div style={{
|
||||||
|
textAlign: 'center',
|
||||||
|
color: '#6b7280',
|
||||||
|
padding: '40px',
|
||||||
|
fontSize: '16px'
|
||||||
|
}}>
|
||||||
|
🚧 工作流实例管理功能开发中...
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<span style={{ fontSize: '14px' }}>
|
||||||
|
此模块将复用现有的实例管理API,提供React Flow版本的实例查看功能
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WorkflowInstance;
|
||||||
24
frontend/src/pages/Workflow2/Monitor/index.tsx
Normal file
24
frontend/src/pages/Workflow2/Monitor/index.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Card } from 'antd';
|
||||||
|
|
||||||
|
const WorkflowMonitor: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<Card title="工作流监控 (React Flow版本)">
|
||||||
|
<div style={{
|
||||||
|
textAlign: 'center',
|
||||||
|
color: '#6b7280',
|
||||||
|
padding: '40px',
|
||||||
|
fontSize: '16px'
|
||||||
|
}}>
|
||||||
|
📊 工作流监控功能开发中...
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<span style={{ fontSize: '14px' }}>
|
||||||
|
此模块将提供实时的工作流执行监控和统计分析功能
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WorkflowMonitor;
|
||||||
@ -45,6 +45,10 @@ const JenkinsManagerList = lazy(() => import('../pages/Deploy/JenkinsManager/Lis
|
|||||||
const GitManagerList = lazy(() => import('../pages/Deploy/GitManager/List'));
|
const GitManagerList = lazy(() => import('../pages/Deploy/GitManager/List'));
|
||||||
const External = lazy(() => import('../pages/Deploy/External'));
|
const External = lazy(() => import('../pages/Deploy/External'));
|
||||||
|
|
||||||
|
// Workflow2 React Flow 版本
|
||||||
|
const Workflow2DefinitionList = lazy(() => import('../pages/Workflow2/Definition'));
|
||||||
|
const Workflow2Design = lazy(() => import('../pages/Workflow2/Design'));
|
||||||
|
|
||||||
// 创建路由
|
// 创建路由
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
@ -224,6 +228,27 @@ const router = createBrowserRouter([
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'workflow2',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'definition',
|
||||||
|
element: (
|
||||||
|
<Suspense fallback={<LoadingComponent/>}>
|
||||||
|
<Workflow2DefinitionList/>
|
||||||
|
</Suspense>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'design/:id',
|
||||||
|
element: (
|
||||||
|
<Suspense fallback={<LoadingComponent/>}>
|
||||||
|
<Workflow2Design/>
|
||||||
|
</Suspense>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '*',
|
path: '*',
|
||||||
element: <Navigate to="/dashboard"/>
|
element: <Navigate to="/dashboard"/>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user