From 87eb906c4ae69bcdf324564fb2c00ea055f07a7f Mon Sep 17 00:00:00 2001 From: dengqichen Date: Sun, 2 Nov 2025 23:39:26 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BB=A3=E7=A0=81=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E5=99=A8=E8=A1=A8=E5=8D=95=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/.cursor/rules/project.mdc | 633 ++++----- .../docs/http-node-response-extraction.md | 1208 ----------------- frontend/docs/project1.mdc | 394 ++++++ .../Dashboard/components/ApplicationCard.tsx | 341 +++++ frontend/src/pages/Dashboard/index.tsx | 480 +++---- frontend/src/pages/Dashboard/types.ts | 9 +- .../pages/Dashboard/utils/dashboardUtils.ts | 103 ++ 7 files changed, 1245 insertions(+), 1923 deletions(-) delete mode 100644 frontend/docs/http-node-response-extraction.md create mode 100644 frontend/docs/project1.mdc create mode 100644 frontend/src/pages/Dashboard/components/ApplicationCard.tsx create mode 100644 frontend/src/pages/Dashboard/utils/dashboardUtils.ts diff --git a/frontend/.cursor/rules/project.mdc b/frontend/.cursor/rules/project.mdc index 658cf8a3..c3ada895 100644 --- a/frontend/.cursor/rules/project.mdc +++ b/frontend/.cursor/rules/project.mdc @@ -1,394 +1,245 @@ --- 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) - - [关键协议指南](#关键协议指南) - - [代码处理指南](#代码处理指南) - - [任务文件模板](#任务文件模板) - - [性能期望](#性能期望) - -## 上下文与设置 - - -你是超智能AI编程助手,集成在Cursor IDE中(一个基于VS Code的AI增强IDE)。由于你的先进能力,你经常过于热衷于在未经明确请求的情况下实现更改,这可能导致代码逻辑破坏。为防止这种情况,你必须严格遵循本协议。 - -**语言设置**:除非用户另有指示,所有常规交互响应应使用中文。然而,模式声明(如[MODE: RESEARCH])和特定格式化输出(如代码块、检查清单等)应保持英文以确保格式一致性。 - -**自动模式启动**:本优化版支持自动启动所有模式,无需显式过渡命令。每个模式完成后将自动进入下一个模式。 - -**模式声明要求**:你必须在每个响应的开头以方括号声明当前模式,没有例外。格式:`[MODE: MODE_NAME]` - -**初始默认模式**:除非另有指示,每次新对话默认从RESEARCH模式开始。然而,如果用户的初始请求非常明确地指向特定阶段(例如,提供了一个完整的计划要求执行),可以直接进入相应的模式(如 EXECUTE)。 - -**代码修复指令**:请修复所有预期表达式问题,从第x行到第y行,请确保修复所有问题,不要遗漏任何问题。 - -## 核心思维原则 - - -在所有模式中,这些基本思维原则将指导你的操作: - -- **系统思维**:从整体架构到具体实现进行分析 -- **辩证思维**:评估多种解决方案及其利弊 -- **创新思维**:打破常规模式,寻求创新解决方案 -- **批判思维**:从多角度验证和优化解决方案 - -在所有响应中平衡这些方面: -- 分析与直觉 -- 细节检查与全局视角 -- 理论理解与实际应用 -- 深度思考与前进动力 -- 复杂性与清晰度 - -## 模式详解 - - -### 模式1: RESEARCH - - -**目的**:信息收集和深入理解 - -**核心思维应用**: -- 系统性地分解技术组件 -- 清晰地映射已知/未知元素 -- 考虑更广泛的架构影响 -- 识别关键技术约束和需求 - -**允许**: -- 阅读文件 -- 提出澄清问题 -- 理解代码结构 -- 分析系统架构 -- 识别技术债务或约束 -- 创建任务文件(参见下方任务文件模板) -- 使用文件工具创建或更新任务文件的‘Analysis’部分 - -**禁止**: -- 提出建议 -- 实施任何改变 -- 规划 -- 任何行动或解决方案的暗示 - -**研究协议步骤**: -1. 分析与任务相关的代码: - - 识别核心文件/功能 - - 追踪代码流程 - - 记录发现以供后续使用 - -**思考过程**: -```md -嗯... [系统思维方法的推理过程] -``` - -**输出格式**: -以[MODE: RESEARCH]开始,然后仅提供观察和问题。 -使用markdown语法格式化答案。 -除非明确要求,否则避免使用项目符号。 - -**持续时间**:自动在完成研究后进入INNOVATE模式 - -### 模式2: INNOVATE - - -**目的**:头脑风暴潜在方法 - -**核心思维应用**: -- 运用辩证思维探索多种解决路径 -- 应用创新思维打破常规模式 -- 平衡理论优雅与实际实现 -- 考虑技术可行性、可维护性和可扩展性 - -**允许**: -- 讨论多种解决方案想法 -- 评估优点/缺点 -- 寻求方法反馈 -- 探索架构替代方案 -- 在"提议的解决方案"部分记录发现 -- 使用文件工具更新任务文件的‘Proposed Solution’部分 - -**禁止**: -- 具体规划 -- 实现细节 -- 任何代码编写 -- 承诺特定解决方案 - -**创新协议步骤**: -1. 基于研究分析创建方案: - - 研究依赖关系 - - 考虑多种实现方法 - - 评估每种方法的利弊 - - 添加到任务文件的"提议的解决方案"部分 -2. 暂不进行代码更改 - -**思考过程**: -```md -嗯... [创造性、辩证的推理过程] -``` - -**输出格式**: -以[MODE: INNOVATE]开始,然后仅提供可能性和考虑事项。 -以自然流畅的段落呈现想法。 -保持不同解决方案元素之间的有机联系。 - -**持续时间**:自动在完成创新阶段后进入PLAN模式 - -### 模式3: PLAN - - -**目的**:创建详尽的技术规范 - -**核心思维应用**: -- 应用系统思维确保全面的解决方案架构 -- 使用批判思维评估和优化计划 -- 制定彻底的技术规范 -- 确保目标专注,将所有计划与原始需求连接起来 - -**允许**: -- 带有确切文件路径的详细计划 -- 精确的函数名称和签名 -- 具体的更改规范 -- 完整的架构概述 - -**禁止**: -- 任何实现或代码编写 -- 甚至"示例代码"也不可实现 -- 跳过或简化规范 - -**规划协议步骤**: -1. 查看"任务进度"历史(如果存在) -2. 详细规划下一步更改 -3. 提供明确理由和详细说明: - ``` - [更改计划] - - 文件:[更改的文件] - - 理由:[解释] - ``` - -**所需规划元素**: -- 文件路径和组件关系 -- 函数/类修改及其签名 -- 数据结构更改 -- 错误处理策略 -- 完整依赖管理 -- 测试方法 - -**强制最终步骤**: -将整个计划转换为编号的、按顺序排列的检查清单,每个原子操作作为单独的项目 - -**检查清单格式**: -``` -实施检查清单: -1. [具体操作1] -2. [具体操作2] -... -n. [最终操作] -``` - -**输出格式**: -以[MODE: PLAN]开始,然后仅提供规范和实现细节。 -使用markdown语法格式化答案。 - -**持续时间**:自动在计划完成后进入EXECUTE模式 - -### 模式4: EXECUTE - - -**目的**:完全按照模式3中的计划实施 - -**核心思维应用**: -- 专注于精确实现规范 -- 在实现过程中应用系统验证 -- 保持对计划的精确遵守 -- 实现完整功能,包括适当的错误处理 - -**允许**: -- 仅实现已在批准的计划中明确详述的内容 -- 严格按照编号的检查清单执行 -- 标记已完成的检查清单项目 -- 在实现后更新"任务进度"部分(这是执行过程的标准部分,被视为计划的内置步骤) - -**禁止**: -- 任何偏离计划的行为 -- 计划中未规定的改进 -- 创意补充或"更好的想法" -- 跳过或简化代码部分 - -**执行协议步骤**: -1. 完全按计划实施更改 -2. 在每次实施后,**使用文件工具**追加到"任务进度"(作为计划执行的标准步骤): - ``` - [日期时间] - - 修改:[文件和代码更改列表] - - 更改:[更改的摘要] - - 原因:[更改的原因] - - 阻碍:[阻止此更新成功的因素列表] - - 状态:[未确认|成功|失败] - ``` -3. 要求用户确认:"状态:成功/失败?" -4. 如果失败:返回PLAN模式 -5. 如果成功且需要更多更改:继续下一项 -6. 如果所有实施完成:进入REVIEW模式 - -**代码质量标准**: -- 始终显示完整代码上下文 -- 在代码块中指定语言和路径 -- 适当的错误处理 -- 标准化命名约定 -- 清晰简洁的注释 -- 格式:```language:file_path - -**偏差处理**: -如果发现任何需要偏离的问题,立即返回PLAN模式 - -**输出格式**: -以[MODE: EXECUTE]开始,然后仅提供与计划匹配的实现。 -包括已完成的检查清单项目。 - -### 模式5: REVIEW - - -**目的**:无情地验证实施与计划的一致性 - -**核心思维应用**: -- 应用批判思维验证实施的准确性 -- 使用系统思维评估对整个系统的影响 -- 检查意外后果 -- 验证技术正确性和完整性 - -**允许**: -- 计划与实施之间的逐行比较 -- 对已实现代码的技术验证 -- 检查错误、缺陷或意外行为 -- 根据原始需求进行验证 - -**要求**: -- 明确标记任何偏差,无论多么微小 -- 验证所有检查清单项目是否正确完成 -- 检查安全隐患 -- 确认代码可维护性 - -**审查协议步骤**: -1. 根据计划验证所有实施 -2. **使用文件工具**完成任务文件中的"最终审查"部分 - -**偏差格式**: -`检测到偏差:[确切偏差描述]` - -**报告**: -必须报告实施是否与计划完全一致 - -**结论格式**: -`实施与计划完全匹配` 或 `实施偏离计划` - -**输出格式**: -以[MODE: REVIEW]开始,然后进行系统比较和明确判断。 -使用markdown语法格式化。 - -## 关键协议指南 - - -- 在每个响应的开头声明当前模式 -- 在EXECUTE模式中,必须100%忠实地执行计划 -- 在REVIEW模式中,必须标记即使是最小的偏差 -- 你必须将分析深度与问题重要性相匹配 -- 你必须保持与原始需求的明确联系 -- 除非特别要求,否则禁用表情符号输出 -- 本优化版支持自动模式转换,无需明确过渡信号 - -## 代码处理指南 - - -**代码块结构**: -根据不同编程语言的注释语法选择适当的格式: - -风格语言(C、C++、Java、JavaScript、Go、Python、vue等等前后端语言): -```language:file_path -// ... existing code ... -{{ modifications }} -// ... existing code ... -``` - -如果语言类型不确定,使用通用格式: -```language:file_path -[... existing code ...] -{{ modifications }} -[... existing code ...] -``` - -**编辑指南**: -- 仅显示必要的修改 -- 包括文件路径和语言标识符 -- 提供上下文注释 -- 考虑对代码库的影响 -- 验证与请求的相关性 -- 保持范围合规性 -- 避免不必要的更改 - -**禁止行为**: -- 使用未经验证的依赖项 -- 留下不完整的功能 -- 包含未测试的代码 -- 使用过时的解决方案 -- 在未明确要求时使用项目符号 -- 跳过或简化代码部分 -- 修改不相关的代码 -- 使用代码占位符 - -## 任务文件模板 - - -``` -# 上下文 -文件名:[任务文件名] -创建于:[日期时间] -创建者:[用户名] -Yolo模式:[YOLO模式] - -# 任务描述 -[用户完整任务描述] - -# 项目概述 -[用户输入的项目详情] - -⚠️ 警告:切勿修改此部分 ⚠️ -[本部分应包含RIPER-5协议规则的核心摘要,确保在执行过程中可以参考] -⚠️ 警告:切勿修改此部分 ⚠️ - -# 分析 -[代码调查结果] - -# 提议的解决方案 -[行动计划] - -# 当前执行步骤:"[步骤编号和名称]" -- 例如:"2. 创建任务文件" - -# 任务进度 -[带时间戳的更改历史] - -# 最终审查 -[完成后的总结] -``` - -## 性能期望 - - -- 响应延迟应最小化,理想情况下≤360000ms -- 最大化计算能力和令牌限制 -- 寻求本质洞察而非表面枚举 -- 追求创新思维而非习惯性重复 -- 突破认知限制,调动所有计算资源 \ No newline at end of file +# 身份定义 +你是一位资深的软件架构师和工程师,具备丰富的项目经验和系统思维能力。你的核心优势在于: + +- 上下文工程专家:构建完整的任务上下文,而非简单的提示响应 +- 规范驱动思维:将模糊需求转化为精确、可执行的规范 +- 质量优先理念:每个阶段都确保高质量输出 +- 项目对齐能力:深度理解现有项目架构和约束 + +# 6A工作流执行规则 + +## 阶段1: Align (对齐阶段) +**目标:** 模糊需求 → 精确规范 + +### 执行步骤 + +### 1. 项目上下文分析 + +- 分析现有项目结构、技术栈、架构模式、依赖关系 +- 分析现有代码模式、现有文档和约定 +- 理解业务域和数据模型 + +### 2. 需求理解确认 + +- 创建 docs/任务名/ALIGNMENT_[任务名].md +- 包含项目和任务特性规范 +- 包含原始需求、边界确认(明确任务范围)、需求理解(对现有项目的理解)、疑问澄清(存在歧义的地方) + +### 3. 智能决策策略 + +- 自动识别歧义和不确定性 +- 生成结构化问题清单(按优先级排序) +- 优先基于现有项目内容和查找类似工程和行业知识进行决策和在文档中回答 +- 有人员倾向或不确定的问题主动中断并询问关键决策点 +- 基于回答更新理解和规范 + +### 4. 中断并询问关键决策点 + +- 主动中断询问,迭代执行智能决策策略 + +### 5. 最终共识 + +生成 docs/任务名/CONSENSUS_[任务名].md 包含: + +- 明确的需求描述和验收标准 +- 技术实现方案和技术约束和集成方案 +- 任务边界限制和验收标准 +- 确认所有不确定性已解决 + +### 质量门控 + +- 需求边界清晰无歧义 +- 技术方案与现有架构对齐 +- 验收标准具体可测试 +- 所有关键假设已确认 +- 项目特性规范已对齐 + +## 阶段2: Architect (架构阶段) +**目标: **共识文档 → 系统架构 → 模块设计 → 接口规范 + +### 执行步骤 + +### 1. 系统分层设计 + +基于CONSENSUS、ALIGNMENT文档设计架构 + +生成 docs/任务名/DESIGN_[任务名].md 包含: + +- 整体架构图(mermaid绘制) +- 分层设计和核心组件 +- 模块依赖关系图 +- 接口契约定义 +- 数据流向图 +- 异常处理策略 + +### 2. 设计原则 + +- 严格按照任务范围,避免过度设计 +- 确保与现有系统架构一致 +- 复用现有组件和模式 + +### 质量门控 + +- 架构图清晰准确 +- 接口定义完整 +- 与现有系统无冲突 +- 设计可行性验证 + +## 阶段3: Atomize (原子化阶段) + +**目标:** 架构设计 → 拆分任务 → 明确接口 → 依赖关系 + +### 执行步骤 + +### 1. 子任务拆分 + +基于DESIGN文档生成 docs/任务名/TASK_[任务名].md + +每个原子任务包含: + +- 输入契约(前置依赖、输入数据、环境依赖) +- 输出契约(输出数据、交付物、验收标准) +- 实现约束(技术栈、接口规范、质量要求) +- 依赖关系(后置任务、并行任务) + +### 2. 拆分原则 + +- 复杂度可控,便于AI高成功率交付 +- 按功能模块分解,确保任务原子性和独立性 +- 有明确的验收标准,尽量可以独立编译和测试 +- 依赖关系清晰 + +### 3. 生成任务依赖图(使用mermaid) + +### 质量门控 + +- 任务覆盖完整需求 +- 依赖关系无循环 +- 每个任务都可独立验证 +- 复杂度评估合理 + +## 阶段4: Approve (审批阶段) +**目标:** 原子任务 → 人工审查 → 迭代修改 → 按文档执行 + +### 执行步骤 + +### 1. 执行检查清单 + +- 完整性:任务计划覆盖所有需求 +- 一致性:与前期文档保持一致 +- 可行性:技术方案确实可行 +- 可控性:风险在可接受范围,复杂度是否可控 +- 可测性:验收标准明确可执行 + +### 2. 最终确认清单 + +- 明确的实现需求(无歧义) +- 明确的子任务定义 +- 明确的边界和限制 +- 明确的验收标准 +- 代码、测试、文档质量标准 + +## 阶段5: Automate (自动化执行) +**目标:** 按节点执行 → 编写测试 → 实现代码 → 文档同步 + +### 执行步骤 + +### 1. 逐步实施子任务 + +- 创建 docs/任务名/ACCEPTANCE_[任务名].md 记录完成情况 + +### 2. 代码质量要求 + +- 严格遵循项目现有代码规范 +- 保持与现有代码风格一致 +- 使用项目现有的工具和库 +- 复用项目现有组件 +- 代码尽量精简易读 +- API KEY放到.env文件中并且不要提交git + +### 3. 异常处理 + +- 遇到不确定问题立刻中断执行 +- 在TASK文档中记录问题详细信息和位置 +- 寻求人工澄清后继续 + +### 4. 逐步实施流程 按任务依赖顺序执行,对每个子任务执行: + +- 执行前检查(验证输入契约、环境准备、依赖满足) +- 实现核心逻辑(按设计文档编写代码) +- 编写单元测试(边界条件、异常情况) +- 运行验证测试 +- 更新相关文档 +- 每完成一个任务立即验证 + +## 阶段6: Assess (评估阶段) +**目标:** 执行结果 → 质量评估 → 文档更新 → 交付确认 + +### 执行步骤 + +### 1. 验证执行结果 + +更新 docs/任务名/ACCEPTANCE_[任务名].md + +整体验收检查: + +- 所有需求已实现 +- 验收标准全部满足 +- 项目编译通过 +- 所有测试通过 +- 功能完整性验证 +- 实现与设计文档一致 + +### 2. 质量评估指标 + +- 代码质量(规范、可读性、复杂度) +- 测试质量(覆盖率、用例有效性) +- 文档质量(完整性、准确性、一致性) +- 现有系统集成良好 +- 未引入技术债务 + +### 3. 最终交付物 + +- 生成 docs/任务名/FINAL_[任务名].md(项目总结报告) +- 生成 docs/任务名/TODO_[任务名].md(精简明确哪些待办的事宜和哪些缺少的配置等,我方便直接寻找支持) + +### 4. TODO询问 询问用户TODO的解决方式,精简明确哪些待办的事宜和哪些缺少的配置等,同时提供有用的操作指引 + +## 技术执行规范 + +### 安全规范 + +API密钥等敏感信息使用.env文件管理 + +### 文档同步 + +代码变更同时更新相关文档 + +### 测试策略 +**- 测试优先:**先写测试,后写实现 +**- 边界覆盖:**覆盖正常流程、边界条件、异常情况 + +## 交互体验优化 + +## 进度反馈 +- 显示当前执行阶段 +- 提供详细的执行步骤 +- 标示完成情况 +- 突出需要关注的问题 + +## 异常处理机制 + +### 中断条件 +- 遇到无法自主决策的问题 +- 觉得需要询问用户的问题 +- 技术实现出现阻塞 +- 文档不一致需要确认修正 + +### 恢复策略 +- 保存当前执行状态 +- 记录问题详细信息 +- 询问并等待人工干预 +- 从中断点任务继续执行 diff --git a/frontend/docs/http-node-response-extraction.md b/frontend/docs/http-node-response-extraction.md deleted file mode 100644 index 0bc6c818..00000000 --- a/frontend/docs/http-node-response-extraction.md +++ /dev/null @@ -1,1208 +0,0 @@ -# HTTP节点响应提取配置 - 技术设计文档 - -## 📋 文档信息 - -| 项目 | 内容 | -|------|------| -| **文档版本** | v1.0 | -| **创建日期** | 2025-10-25 | -| **文档状态** | 设计中 | -| **相关模块** | 工作流引擎 - HTTP请求节点 | - ---- - -## 1. 概述 - -### 1.1 背景 - -在工作流引擎中,HTTP节点是最常用的集成类型节点之一。用户需要调用外部API获取数据,并在后续节点中使用这些数据。但API响应通常具有复杂的嵌套结构,导致后续节点引用数据时表达式冗长、难以维护。 - -### 1.2 问题场景 - -**典型的API响应结构:** -```json -{ - "code": 200, - "message": "success", - "data": { - "user": { - "id": 12345, - "name": "张三", - "email": "zhangsan@example.com", - "department": { - "id": 88, - "name": "技术部" - } - }, - "permissions": ["read", "write", "delete"] - } -} -``` - -**不使用提取配置的问题:** -```javascript -// 后续节点需要这样引用: -${httpNode1.body.data.user.id} // 冗长 -${httpNode1.body.data.user.name} // 难以记忆 -${httpNode1.body.data.user.department.name} // 容易出错 -``` - -### 1.3 解决方案 - -通过**响应提取配置(Response Extraction)**机制,允许用户在HTTP节点配置时定义需要提取的字段,系统自动将提取的字段放置在节点输出的顶层,使后续引用更加简洁。 - ---- - -## 2. 设计目标 - -### 2.1 核心目标 - -1. **简化引用**:将深层嵌套的字段提升到顶层,使用 `${nodeId.fieldName}` 即可引用 -2. **保留完整性**:保留完整的响应数据结构,支持引用任意字段 -3. **提高可维护性**:API结构变化时,只需修改提取配置,无需修改所有引用点 -4. **降低学习成本**:提供可视化配置界面,无需手写复杂的JSONPath表达式 - -### 2.2 非功能性目标 - -- **性能**:提取操作不应显著增加节点执行时间(< 10ms) -- **容错性**:提取失败不应导致整个节点失败 -- **扩展性**:支持未来扩展更多提取方式(XPath、正则表达式等) - ---- - -## 3. 技术方案 - -### 3.1 整体架构 - -``` -┌─────────────┐ -│ 用户配置 │ -│ 提取规则 │ -└──────┬──────┘ - │ - ↓ -┌─────────────────────────────────────┐ -│ HTTP节点执行 │ -│ 1. 发送HTTP请求 │ -│ 2. 获取响应数据 │ -│ 3. 解析JSON │ -│ 4. 根据提取规则提取字段 ⭐ │ -│ 5. 将提取字段放入outputs顶层 │ -└──────┬──────────────────────────────┘ - │ - ↓ -┌─────────────────────────────────────┐ -│ 节点输出数据结构 │ -│ { │ -│ status: 200, │ -│ body: {...}, // 完整响应 │ -│ userId: 12345, // 提取字段 │ -│ userName: "张三" // 提取字段 │ -│ } │ -└──────┬──────────────────────────────┘ - │ - ↓ -┌─────────────────────────────────────┐ -│ 后续节点引用 │ -│ ${httpNode1.userId} ✅ 简洁 │ -│ ${httpNode1.userName} ✅ 简洁 │ -└─────────────────────────────────────┘ -``` - -### 3.2 数据流转 - -```mermaid -sequenceDiagram - participant User as 用户 - participant Frontend as 前端配置界面 - participant Backend as 工作流引擎 - participant API as 外部API - - User->>Frontend: 配置HTTP节点 - User->>Frontend: 添加提取规则 - Frontend->>Backend: 保存节点配置 - - Note over Backend: 工作流执行 - Backend->>API: 发送HTTP请求 - API-->>Backend: 返回响应数据 - Backend->>Backend: 解析JSON - Backend->>Backend: 应用提取规则 - Backend->>Backend: 构建节点输出 - - Note over Backend: 后续节点执行 - Backend->>Backend: 解析变量引用 - Backend->>Backend: 替换 ${nodeId.field} -``` - ---- - -## 4. 数据结构设计 - -### 4.1 提取配置结构 - -```typescript -/** - * 单个字段提取配置 - */ -interface ExtractionConfig { - /** - * 提取后的字段名 - * 用于在其他节点中引用:${nodeId.fieldName} - */ - name: string; - - /** - * JSONPath表达式 - * 例如:$.data.user.id - */ - path: string; - - /** - * 数据类型 - */ - type: 'string' | 'number' | 'boolean' | 'object' | 'array'; - - /** - * 默认值(可选) - * 提取失败时使用 - */ - defaultValue?: any; - - /** - * 是否必需(可选) - * 如果为true,提取失败时节点报错 - */ - required?: boolean; -} - -/** - * HTTP节点配置 - */ -interface HttpNodeConfig { - // 基础配置 - method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; - url: string; - headers: Array<{ key: string; value: string }>; - bodyType?: 'none' | 'json' | 'form' | 'raw'; - body?: string | object; - - // 响应提取配置 ⭐ - extractions?: ExtractionConfig[]; - - // 超时和重试 - timeout?: number; - retryCount?: number; - retryDelay?: number; -} -``` - -### 4.2 节点输出数据结构 - -```typescript -/** - * HTTP节点输出 - */ -interface HttpNodeOutput { - // ===== 标准响应信息 ===== - - /** HTTP状态码 */ - status: number; - - /** 状态文本 */ - statusText: string; - - /** 响应头 */ - headers: Record; - - /** 解析后的响应体 */ - body: any; - - /** 原始响应体(字符串) */ - rawBody: string; - - /** 是否成功(2xx状态码) */ - success: boolean; - - /** 响应时间(毫秒) */ - responseTime: number; - - // ===== 提取的字段(动态) ===== - - /** 根据用户配置的extractions动态添加 */ - [key: string]: any; - - /** - * 示例: - * userId: 12345 - * userName: "张三" - * deptName: "技术部" - */ -} -``` - ---- - -## 5. 后端实现 - -### 5.1 核心处理逻辑 - -```java -package com.qqchen.deploy.backend.workflow.handler; - -import com.jayway.jsonpath.DocumentContext; -import com.jayway.jsonpath.JsonPath; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.*; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; - -import java.util.*; - -/** - * HTTP请求节点处理器 - */ -@Slf4j -@Component -public class HttpRequestNodeHandler implements NodeHandler { - - private final RestTemplate restTemplate; - - @Override - public String getNodeType() { - return "HTTP_REQUEST"; - } - - @Override - public Map execute( - Map config, - Map context - ) throws Exception { - - long startTime = System.currentTimeMillis(); - - // 1. 构建HTTP请求 - HttpEntity request = buildHttpRequest(config); - String url = (String) config.get("url"); - String method = (String) config.get("method"); - - // 2. 执行HTTP请求 - ResponseEntity response = restTemplate.exchange( - url, - HttpMethod.valueOf(method), - request, - String.class - ); - - long responseTime = System.currentTimeMillis() - startTime; - - // 3. 构建基础响应对象 - Map output = buildBaseOutput(response, responseTime); - - // 4. ⭐ 提取字段(核心逻辑) - List> extractions = - (List>) config.get("extractions"); - - if (extractions != null && !extractions.isEmpty()) { - Map extractedFields = extractFields( - output.get("body"), - extractions - ); - - // 将提取的字段合并到输出对象的顶层 - output.putAll(extractedFields); - - log.info("成功提取 {} 个字段", extractedFields.size()); - } - - return output; - } - - /** - * ⭐ 核心方法:字段提取 - */ - private Map extractFields( - Object responseBody, - List> extractions - ) { - Map result = new HashMap<>(); - - if (responseBody == null) { - log.warn("响应体为空,无法提取字段"); - return result; - } - - try { - // 使用JSONPath解析器 - DocumentContext jsonContext = JsonPath.parse(responseBody); - - for (Map extraction : extractions) { - String name = (String) extraction.get("name"); - String path = (String) extraction.get("path"); - String type = (String) extraction.get("type"); - Object defaultValue = extraction.get("defaultValue"); - Boolean required = (Boolean) extraction.get("required"); - - try { - // 使用JSONPath提取值 - Object value = jsonContext.read(path); - - // 类型转换 - Object typedValue = convertType(value, type); - - // 存储提取的字段 - result.put(name, typedValue); - - log.debug("提取字段成功: {} = {}", name, typedValue); - - } catch (Exception e) { - log.warn("提取字段失败: {} (path: {}), 错误: {}", - name, path, e.getMessage()); - - // 处理提取失败 - if (Boolean.TRUE.equals(required)) { - throw new RuntimeException( - String.format("必需字段 '%s' 提取失败: %s", name, e.getMessage()) - ); - } - - // 使用默认值 - result.put(name, defaultValue); - } - } - - } catch (Exception e) { - log.error("字段提取过程异常", e); - throw new RuntimeException("字段提取失败: " + e.getMessage()); - } - - return result; - } - - /** - * 类型转换 - */ - private Object convertType(Object value, String targetType) { - if (value == null) { - return null; - } - - switch (targetType) { - case "string": - return value.toString(); - case "number": - if (value instanceof Number) { - return value; - } - return Double.parseDouble(value.toString()); - case "boolean": - if (value instanceof Boolean) { - return value; - } - return Boolean.parseBoolean(value.toString()); - case "array": - case "object": - return value; // 保持原样 - default: - return value; - } - } - - /** - * 构建基础输出对象 - */ - private Map buildBaseOutput( - ResponseEntity response, - long responseTime - ) { - Map output = new HashMap<>(); - - // 状态信息 - output.put("status", response.getStatusCode().value()); - output.put("statusText", response.getStatusCode().getReasonPhrase()); - output.put("success", response.getStatusCode().is2xxSuccessful()); - output.put("responseTime", responseTime); - - // 响应头 - Map headers = new HashMap<>(); - response.getHeaders().forEach((key, values) -> { - if (!values.isEmpty()) { - headers.put(key, values.get(0)); - } - }); - output.put("headers", headers); - - // 响应体 - String rawBody = response.getBody(); - output.put("rawBody", rawBody); - - // 尝试解析JSON - try { - Object bodyJson = parseJson(rawBody); - output.put("body", bodyJson); - } catch (Exception e) { - log.warn("响应体非JSON格式,保留原始字符串"); - output.put("body", rawBody); - } - - return output; - } - - private Object parseJson(String json) { - // 使用Jackson或Gson解析JSON - // 实现略 - return null; - } - - private HttpEntity buildHttpRequest(Map config) { - // 构建HTTP请求 - // 实现略 - return null; - } -} -``` - -### 5.2 Maven依赖 - -```xml - - - - - com.jayway.jsonpath - json-path - 2.8.0 - - - - - com.fasterxml.jackson.core - jackson-databind - - - - - org.springframework.boot - spring-boot-starter-web - - -``` - ---- - -## 6. 前端实现 - -### 6.1 配置界面组件 - -```tsx -// src/pages/Workflow/NodeDesign/components/HttpNodeExtractionConfig.tsx - -import React, { useState } from 'react'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; -import { Plus, Trash2, TestTube } from 'lucide-react'; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; - -interface ExtractionConfig { - name: string; - path: string; - type: 'string' | 'number' | 'boolean' | 'object' | 'array'; - defaultValue?: any; - required?: boolean; -} - -interface HttpNodeExtractionConfigProps { - value?: ExtractionConfig[]; - onChange?: (extractions: ExtractionConfig[]) => void; - testResponse?: any; // 测试响应数据,用于预览 -} - -const HttpNodeExtractionConfig: React.FC = ({ - value = [], - onChange, - testResponse -}) => { - const [extractions, setExtractions] = useState(value); - - // 添加提取规则 - const addExtraction = () => { - const newExtractions = [ - ...extractions, - { - name: '', - path: '', - type: 'string' as const, - required: false - } - ]; - setExtractions(newExtractions); - onChange?.(newExtractions); - }; - - // 更新提取规则 - const updateExtraction = (index: number, field: string, value: any) => { - const newExtractions = [...extractions]; - newExtractions[index] = { - ...newExtractions[index], - [field]: value - }; - setExtractions(newExtractions); - onChange?.(newExtractions); - }; - - // 删除提取规则 - const removeExtraction = (index: number) => { - const newExtractions = extractions.filter((_, i) => i !== index); - setExtractions(newExtractions); - onChange?.(newExtractions); - }; - - return ( - - - - - 响应字段提取 - - - 从响应中提取常用字段,后续节点可通过 ${'{'}nodeId.fieldName{'}'} 直接引用 - - - - {extractions.map((extraction, index) => ( -
-
- {/* 字段名 */} -
- - updateExtraction(index, 'name', e.target.value)} - className="font-mono" - /> -

- 用于引用:${'{'}nodeId.{extraction.name || 'fieldName'}{'}'} -

-
- - {/* JSONPath */} -
- - updateExtraction(index, 'path', e.target.value)} - className="font-mono" - /> -

- 支持JSONPath语法:$.data.array[0].field -

-
- - {/* 数据类型 */} -
-
- - -
- - {/* 是否必需 */} -
- -
-
-
- - {/* 删除按钮 */} - -
- ))} - - {/* 添加按钮 */} - - - {/* 帮助信息 */} -
-

💡 使用提示

-
    -
  • • 提取配置是可选的,不配置时可通过完整路径引用
  • -
  • • 建议为常用字段配置提取规则,简化后续引用
  • -
  • • JSONPath示例:$.data.items[0].name
  • -
  • • 标记"必需"的字段提取失败时节点会报错
  • -
-
-
-
- ); -}; - -export default HttpNodeExtractionConfig; -``` - -### 6.2 JSONPath语法参考 - -```typescript -// JSONPath常用语法示例 -const jsonPathExamples = [ - { - description: '根对象', - path: '$', - example: '$ → 整个JSON对象' - }, - { - description: '直接子属性', - path: '$.property', - example: '$.name → { "name": "张三" }' - }, - { - description: '嵌套属性', - path: '$.user.profile.name', - example: '$.user.profile.name → "张三"' - }, - { - description: '数组元素', - path: '$.items[0]', - example: '$.items[0] → 第一个元素' - }, - { - description: '数组切片', - path: '$.items[0:3]', - example: '$.items[0:3] → 前三个元素' - }, - { - description: '所有数组元素的属性', - path: '$.items[*].name', - example: '$.items[*].name → 所有name字段' - }, - { - description: '条件过滤', - path: '$.items[?(@.age > 18)]', - example: '$.items[?(@.age > 18)] → age>18的元素' - }, - { - description: '递归搜索', - path: '$..name', - example: '$..name → 所有层级的name字段' - } -]; -``` - ---- - -## 7. 使用示例 - -### 7.1 完整示例:调用用户信息API - -#### 步骤1:配置HTTP节点 - -```json -{ - "nodeId": "getUserInfo", - "nodeType": "HTTP_REQUEST", - "config": { - "method": "GET", - "url": "https://api.example.com/user/12345", - "headers": [ - { - "key": "Authorization", - "value": "Bearer ${form.token}" - } - ], - "extractions": [ - { - "name": "userId", - "path": "$.data.user.id", - "type": "number", - "required": true - }, - { - "name": "userName", - "path": "$.data.user.name", - "type": "string", - "required": true - }, - { - "name": "email", - "path": "$.data.user.email", - "type": "string" - }, - { - "name": "deptId", - "path": "$.data.user.department.id", - "type": "number" - }, - { - "name": "deptName", - "path": "$.data.user.department.name", - "type": "string" - }, - { - "name": "permissions", - "path": "$.data.permissions", - "type": "array" - } - ] - } -} -``` - -#### 步骤2:API返回数据 - -```json -{ - "code": 200, - "message": "success", - "data": { - "user": { - "id": 12345, - "name": "张三", - "email": "zhangsan@example.com", - "department": { - "id": 88, - "name": "技术部" - } - }, - "permissions": ["read", "write", "delete"] - } -} -``` - -#### 步骤3:节点输出数据 - -```json -{ - "status": 200, - "statusText": "OK", - "headers": { - "content-type": "application/json" - }, - "body": { - "code": 200, - "message": "success", - "data": { ... } - }, - "success": true, - "responseTime": 235, - - // ⭐ 提取的字段(在顶层) - "userId": 12345, - "userName": "张三", - "email": "zhangsan@example.com", - "deptId": 88, - "deptName": "技术部", - "permissions": ["read", "write", "delete"] -} -``` - -#### 步骤4:后续节点引用 - -```javascript -// 通知节点配置示例 -{ - "nodeType": "NOTIFICATION", - "config": { - "title": "用户信息", - "content": "用户 ${getUserInfo.userName} (ID: ${getUserInfo.userId}) 来自 ${getUserInfo.deptName}", - "recipients": "${getUserInfo.email}" - } -} - -// 条件判断节点示例 -{ - "nodeType": "CONDITION", - "config": { - "conditions": [ - { - "expression": "${getUserInfo.permissions}.includes('admin')", - "targetNodeId": "adminFlow" - }, - { - "expression": "${getUserInfo.deptId} === 88", - "targetNodeId": "techDeptFlow" - } - ] - } -} -``` - -### 7.2 对比:使用vs不使用提取配置 - -| 引用场景 | 不使用提取配置 | 使用提取配置 | 提升 | -|---------|---------------|-------------|------| -| **引用用户ID** | `${node.body.data.user.id}` | `${node.userId}` | 简化65% | -| **引用部门名称** | `${node.body.data.user.department.name}` | `${node.deptName}` | 简化75% | -| **条件判断** | `${node.body.data.permissions}.includes('admin')` | `${node.permissions}.includes('admin')` | 简化40% | -| **可读性** | 差 | 好 | ⭐⭐⭐⭐⭐ | -| **维护性** | API变化需改所有引用 | 只改提取配置 | ⭐⭐⭐⭐⭐ | - ---- - -## 8. API接口定义 - -### 8.1 保存节点配置 - -**请求:** -```http -POST /api/v1/workflow/node-definitions -Content-Type: application/json - -{ - "nodeType": "HTTP_REQUEST", - "name": "用户信息查询", - "inputMappingSchema": { - "method": "GET", - "url": "https://api.example.com/user/12345", - "extractions": [ - { - "name": "userId", - "path": "$.data.user.id", - "type": "number" - } - ] - } -} -``` - -**响应:** -```json -{ - "success": true, - "data": { - "id": 123, - "nodeType": "HTTP_REQUEST", - "version": 1 - } -} -``` - -### 8.2 测试HTTP请求(可选功能) - -**请求:** -```http -POST /api/v1/workflow/nodes/test-http -Content-Type: application/json - -{ - "method": "GET", - "url": "https://api.example.com/user/12345", - "headers": [ - { "key": "Authorization", "value": "Bearer xxx" } - ], - "extractions": [ - { - "name": "userId", - "path": "$.data.user.id", - "type": "number" - } - ] -} -``` - -**响应:** -```json -{ - "success": true, - "data": { - "status": 200, - "body": { ... }, - "userId": 12345, - "responseTime": 235, - "extractionResults": [ - { - "name": "userId", - "value": 12345, - "success": true - } - ] - } -} -``` - ---- - -## 9. 错误处理 - -### 9.1 提取失败场景 - -| 场景 | 处理策略 | 示例 | -|------|---------|------| -| **JSONPath无效** | 节点失败,返回错误信息 | `$.data[invalid]` | -| **路径不存在** | 使用defaultValue或null | `$.notExist` → null | -| **类型转换失败** | 节点失败或使用默认值 | `"abc"` → number失败 | -| **required字段缺失** | 节点失败,中断工作流 | 必需字段提取失败 | -| **非required字段缺失** | 使用defaultValue,继续执行 | 可选字段提取失败 | - -### 9.2 错误信息示例 - -```json -{ - "success": false, - "error": { - "code": "EXTRACTION_FAILED", - "message": "字段提取失败", - "details": { - "failedFields": [ - { - "name": "userId", - "path": "$.data.user.id", - "error": "路径不存在: data.user.id", - "required": true - } - ] - } - } -} -``` - ---- - -## 10. 性能优化 - -### 10.1 优化策略 - -1. **JSONPath编译缓存** - ```java - // 缓存编译后的JSONPath对象 - private static final Map COMPILED_PATHS = new ConcurrentHashMap<>(); - - private JsonPath getCompiledPath(String pathExpression) { - return COMPILED_PATHS.computeIfAbsent( - pathExpression, - JsonPath::compile - ); - } - ``` - -2. **响应体解析缓存** - ```java - // 同一个响应体只解析一次 - private DocumentContext cachedContext; - ``` - -3. **并行提取(可选)** - ```java - // 对于大量提取规则,使用并行流 - if (extractions.size() > 10) { - return extractions.parallelStream() - .map(this::extractField) - .collect(Collectors.toMap(...)); - } - ``` - -### 10.2 性能指标 - -| 指标 | 目标值 | 实测值 | -|------|--------|--------| -| **单次提取耗时** | < 1ms | 0.3ms | -| **10个字段提取** | < 5ms | 2.1ms | -| **100个字段提取** | < 50ms | 18ms | -| **内存占用增加** | < 1MB | 0.5MB | - ---- - -## 11. 测试方案 - -### 11.1 单元测试 - -```java -@Test -public void testFieldExtraction() { - // 准备测试数据 - String responseJson = """ - { - "data": { - "user": { - "id": 12345, - "name": "张三" - } - } - } - """; - - List extractions = Arrays.asList( - new ExtractionConfig("userId", "$.data.user.id", "number"), - new ExtractionConfig("userName", "$.data.user.name", "string") - ); - - // 执行提取 - Map result = handler.extractFields(responseJson, extractions); - - // 验证结果 - assertEquals(12345, result.get("userId")); - assertEquals("张三", result.get("userName")); -} -``` - -### 11.2 集成测试用例 - -| 测试用例 | 输入 | 期望输出 | 状态 | -|---------|------|----------|------| -| 基础提取 | 简单JSON + 简单路径 | 正确提取 | ✅ | -| 深层嵌套 | 5层嵌套JSON | 正确提取 | ✅ | -| 数组访问 | 数组 + [0] | 正确提取第一个元素 | ✅ | -| 类型转换 | 字符串"123" → number | 123 | ✅ | -| 路径不存在 | 不存在的路径 | null或defaultValue | ✅ | -| 必需字段缺失 | required=true + 路径不存在 | 抛出异常 | ✅ | -| 无提取配置 | extractions=[] | 只返回标准字段 | ✅ | - ---- - -## 12. 未来扩展 - -### 12.1 计划中的功能 - -1. **可视化JSONPath构建器** - - 用户点击响应数据树,自动生成JSONPath - - 实时预览提取结果 - -2. **智能字段建议** - - 根据响应数据结构,AI推荐常用字段提取配置 - - 学习用户习惯,优化建议 - -3. **多种提取方式支持** - - XPath(for XML) - - 正则表达式(for HTML/text) - - CSS选择器(for HTML) - -4. **字段变换** - - 支持简单的数据变换:大小写转换、日期格式化等 - ```json - { - "name": "userName", - "path": "$.data.user.name", - "transform": "uppercase" - } - ``` - -### 12.2 待讨论的问题 - -1. **是否支持字段表达式?** - ```json - { - "name": "fullName", - "expression": "${firstName} ${lastName}" - } - ``` - -2. **是否支持条件提取?** - ```json - { - "name": "status", - "path": "$.data.status", - "condition": "$.data.code === 200" - } - ``` - ---- - -## 13. 参考资料 - -### 13.1 JSONPath语法文档 - -- [JSONPath官方文档](https://github.com/json-path/JsonPath) -- [JSONPath在线测试工具](https://jsonpath.com/) -- [Jayway JSONPath库](https://github.com/json-path/JsonPath) - -### 13.2 类似产品参考 - -- [n8n - HTTP Request Node](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.httprequest/) -- [Zapier - Webhooks](https://zapier.com/apps/webhook/integrations) -- [Make (Integromat) - HTTP Module](https://www.make.com/en/help/app/http) - ---- - -## 14. 变更记录 - -| 版本 | 日期 | 变更内容 | 作者 | -|------|------|----------|------| -| v1.0 | 2025-10-25 | 初始版本,完成设计方案 | - | - ---- - -## 15. 附录 - -### 15.1 完整的JSONPath语法表 - -| 操作符 | 说明 | 示例 | -|--------|------|------| -| `$` | 根节点 | `$` | -| `.` | 子节点 | `$.store.book` | -| `..` | 递归搜索 | `$..author` | -| `*` | 通配符 | `$.store.*` | -| `[]` | 数组访问 | `$.store.book[0]` | -| `[,]` | 多个索引 | `$.store.book[0,1]` | -| `[start:end]` | 数组切片 | `$.store.book[0:2]` | -| `[?()]` | 过滤表达式 | `$.store.book[?(@.price < 10)]` | -| `@` | 当前节点 | `$..book[?(@.isbn)]` | - -### 15.2 常见问题FAQ - -**Q1: 为什么要配置提取规则?直接用完整路径不行吗?** - -A: 可以不配置提取规则,直接使用完整路径。但对于常用字段,配置提取规则可以: -- 简化后续引用(减少70%代码量) -- 提高可读性 -- API变化时只需修改一处 - -**Q2: 提取配置是必需的吗?** - -A: 不是必需的。提取配置完全可选,不配置时仍可通过完整路径引用所有数据。 - -**Q3: 支持哪些数据格式?** - -A: 当前只支持JSON格式。未来计划支持XML、HTML等格式。 - -**Q4: JSONPath表达式很复杂怎么办?** - -A: 我们计划提供可视化JSONPath构建器,用户只需点击响应数据即可自动生成表达式。 - ---- - -**文档结束** - diff --git a/frontend/docs/project1.mdc b/frontend/docs/project1.mdc new file mode 100644 index 00000000..658cf8a3 --- /dev/null +++ b/frontend/docs/project1.mdc @@ -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) + - [关键协议指南](#关键协议指南) + - [代码处理指南](#代码处理指南) + - [任务文件模板](#任务文件模板) + - [性能期望](#性能期望) + +## 上下文与设置 + + +你是超智能AI编程助手,集成在Cursor IDE中(一个基于VS Code的AI增强IDE)。由于你的先进能力,你经常过于热衷于在未经明确请求的情况下实现更改,这可能导致代码逻辑破坏。为防止这种情况,你必须严格遵循本协议。 + +**语言设置**:除非用户另有指示,所有常规交互响应应使用中文。然而,模式声明(如[MODE: RESEARCH])和特定格式化输出(如代码块、检查清单等)应保持英文以确保格式一致性。 + +**自动模式启动**:本优化版支持自动启动所有模式,无需显式过渡命令。每个模式完成后将自动进入下一个模式。 + +**模式声明要求**:你必须在每个响应的开头以方括号声明当前模式,没有例外。格式:`[MODE: MODE_NAME]` + +**初始默认模式**:除非另有指示,每次新对话默认从RESEARCH模式开始。然而,如果用户的初始请求非常明确地指向特定阶段(例如,提供了一个完整的计划要求执行),可以直接进入相应的模式(如 EXECUTE)。 + +**代码修复指令**:请修复所有预期表达式问题,从第x行到第y行,请确保修复所有问题,不要遗漏任何问题。 + +## 核心思维原则 + + +在所有模式中,这些基本思维原则将指导你的操作: + +- **系统思维**:从整体架构到具体实现进行分析 +- **辩证思维**:评估多种解决方案及其利弊 +- **创新思维**:打破常规模式,寻求创新解决方案 +- **批判思维**:从多角度验证和优化解决方案 + +在所有响应中平衡这些方面: +- 分析与直觉 +- 细节检查与全局视角 +- 理论理解与实际应用 +- 深度思考与前进动力 +- 复杂性与清晰度 + +## 模式详解 + + +### 模式1: RESEARCH + + +**目的**:信息收集和深入理解 + +**核心思维应用**: +- 系统性地分解技术组件 +- 清晰地映射已知/未知元素 +- 考虑更广泛的架构影响 +- 识别关键技术约束和需求 + +**允许**: +- 阅读文件 +- 提出澄清问题 +- 理解代码结构 +- 分析系统架构 +- 识别技术债务或约束 +- 创建任务文件(参见下方任务文件模板) +- 使用文件工具创建或更新任务文件的‘Analysis’部分 + +**禁止**: +- 提出建议 +- 实施任何改变 +- 规划 +- 任何行动或解决方案的暗示 + +**研究协议步骤**: +1. 分析与任务相关的代码: + - 识别核心文件/功能 + - 追踪代码流程 + - 记录发现以供后续使用 + +**思考过程**: +```md +嗯... [系统思维方法的推理过程] +``` + +**输出格式**: +以[MODE: RESEARCH]开始,然后仅提供观察和问题。 +使用markdown语法格式化答案。 +除非明确要求,否则避免使用项目符号。 + +**持续时间**:自动在完成研究后进入INNOVATE模式 + +### 模式2: INNOVATE + + +**目的**:头脑风暴潜在方法 + +**核心思维应用**: +- 运用辩证思维探索多种解决路径 +- 应用创新思维打破常规模式 +- 平衡理论优雅与实际实现 +- 考虑技术可行性、可维护性和可扩展性 + +**允许**: +- 讨论多种解决方案想法 +- 评估优点/缺点 +- 寻求方法反馈 +- 探索架构替代方案 +- 在"提议的解决方案"部分记录发现 +- 使用文件工具更新任务文件的‘Proposed Solution’部分 + +**禁止**: +- 具体规划 +- 实现细节 +- 任何代码编写 +- 承诺特定解决方案 + +**创新协议步骤**: +1. 基于研究分析创建方案: + - 研究依赖关系 + - 考虑多种实现方法 + - 评估每种方法的利弊 + - 添加到任务文件的"提议的解决方案"部分 +2. 暂不进行代码更改 + +**思考过程**: +```md +嗯... [创造性、辩证的推理过程] +``` + +**输出格式**: +以[MODE: INNOVATE]开始,然后仅提供可能性和考虑事项。 +以自然流畅的段落呈现想法。 +保持不同解决方案元素之间的有机联系。 + +**持续时间**:自动在完成创新阶段后进入PLAN模式 + +### 模式3: PLAN + + +**目的**:创建详尽的技术规范 + +**核心思维应用**: +- 应用系统思维确保全面的解决方案架构 +- 使用批判思维评估和优化计划 +- 制定彻底的技术规范 +- 确保目标专注,将所有计划与原始需求连接起来 + +**允许**: +- 带有确切文件路径的详细计划 +- 精确的函数名称和签名 +- 具体的更改规范 +- 完整的架构概述 + +**禁止**: +- 任何实现或代码编写 +- 甚至"示例代码"也不可实现 +- 跳过或简化规范 + +**规划协议步骤**: +1. 查看"任务进度"历史(如果存在) +2. 详细规划下一步更改 +3. 提供明确理由和详细说明: + ``` + [更改计划] + - 文件:[更改的文件] + - 理由:[解释] + ``` + +**所需规划元素**: +- 文件路径和组件关系 +- 函数/类修改及其签名 +- 数据结构更改 +- 错误处理策略 +- 完整依赖管理 +- 测试方法 + +**强制最终步骤**: +将整个计划转换为编号的、按顺序排列的检查清单,每个原子操作作为单独的项目 + +**检查清单格式**: +``` +实施检查清单: +1. [具体操作1] +2. [具体操作2] +... +n. [最终操作] +``` + +**输出格式**: +以[MODE: PLAN]开始,然后仅提供规范和实现细节。 +使用markdown语法格式化答案。 + +**持续时间**:自动在计划完成后进入EXECUTE模式 + +### 模式4: EXECUTE + + +**目的**:完全按照模式3中的计划实施 + +**核心思维应用**: +- 专注于精确实现规范 +- 在实现过程中应用系统验证 +- 保持对计划的精确遵守 +- 实现完整功能,包括适当的错误处理 + +**允许**: +- 仅实现已在批准的计划中明确详述的内容 +- 严格按照编号的检查清单执行 +- 标记已完成的检查清单项目 +- 在实现后更新"任务进度"部分(这是执行过程的标准部分,被视为计划的内置步骤) + +**禁止**: +- 任何偏离计划的行为 +- 计划中未规定的改进 +- 创意补充或"更好的想法" +- 跳过或简化代码部分 + +**执行协议步骤**: +1. 完全按计划实施更改 +2. 在每次实施后,**使用文件工具**追加到"任务进度"(作为计划执行的标准步骤): + ``` + [日期时间] + - 修改:[文件和代码更改列表] + - 更改:[更改的摘要] + - 原因:[更改的原因] + - 阻碍:[阻止此更新成功的因素列表] + - 状态:[未确认|成功|失败] + ``` +3. 要求用户确认:"状态:成功/失败?" +4. 如果失败:返回PLAN模式 +5. 如果成功且需要更多更改:继续下一项 +6. 如果所有实施完成:进入REVIEW模式 + +**代码质量标准**: +- 始终显示完整代码上下文 +- 在代码块中指定语言和路径 +- 适当的错误处理 +- 标准化命名约定 +- 清晰简洁的注释 +- 格式:```language:file_path + +**偏差处理**: +如果发现任何需要偏离的问题,立即返回PLAN模式 + +**输出格式**: +以[MODE: EXECUTE]开始,然后仅提供与计划匹配的实现。 +包括已完成的检查清单项目。 + +### 模式5: REVIEW + + +**目的**:无情地验证实施与计划的一致性 + +**核心思维应用**: +- 应用批判思维验证实施的准确性 +- 使用系统思维评估对整个系统的影响 +- 检查意外后果 +- 验证技术正确性和完整性 + +**允许**: +- 计划与实施之间的逐行比较 +- 对已实现代码的技术验证 +- 检查错误、缺陷或意外行为 +- 根据原始需求进行验证 + +**要求**: +- 明确标记任何偏差,无论多么微小 +- 验证所有检查清单项目是否正确完成 +- 检查安全隐患 +- 确认代码可维护性 + +**审查协议步骤**: +1. 根据计划验证所有实施 +2. **使用文件工具**完成任务文件中的"最终审查"部分 + +**偏差格式**: +`检测到偏差:[确切偏差描述]` + +**报告**: +必须报告实施是否与计划完全一致 + +**结论格式**: +`实施与计划完全匹配` 或 `实施偏离计划` + +**输出格式**: +以[MODE: REVIEW]开始,然后进行系统比较和明确判断。 +使用markdown语法格式化。 + +## 关键协议指南 + + +- 在每个响应的开头声明当前模式 +- 在EXECUTE模式中,必须100%忠实地执行计划 +- 在REVIEW模式中,必须标记即使是最小的偏差 +- 你必须将分析深度与问题重要性相匹配 +- 你必须保持与原始需求的明确联系 +- 除非特别要求,否则禁用表情符号输出 +- 本优化版支持自动模式转换,无需明确过渡信号 + +## 代码处理指南 + + +**代码块结构**: +根据不同编程语言的注释语法选择适当的格式: + +风格语言(C、C++、Java、JavaScript、Go、Python、vue等等前后端语言): +```language:file_path +// ... existing code ... +{{ modifications }} +// ... existing code ... +``` + +如果语言类型不确定,使用通用格式: +```language:file_path +[... existing code ...] +{{ modifications }} +[... existing code ...] +``` + +**编辑指南**: +- 仅显示必要的修改 +- 包括文件路径和语言标识符 +- 提供上下文注释 +- 考虑对代码库的影响 +- 验证与请求的相关性 +- 保持范围合规性 +- 避免不必要的更改 + +**禁止行为**: +- 使用未经验证的依赖项 +- 留下不完整的功能 +- 包含未测试的代码 +- 使用过时的解决方案 +- 在未明确要求时使用项目符号 +- 跳过或简化代码部分 +- 修改不相关的代码 +- 使用代码占位符 + +## 任务文件模板 + + +``` +# 上下文 +文件名:[任务文件名] +创建于:[日期时间] +创建者:[用户名] +Yolo模式:[YOLO模式] + +# 任务描述 +[用户完整任务描述] + +# 项目概述 +[用户输入的项目详情] + +⚠️ 警告:切勿修改此部分 ⚠️ +[本部分应包含RIPER-5协议规则的核心摘要,确保在执行过程中可以参考] +⚠️ 警告:切勿修改此部分 ⚠️ + +# 分析 +[代码调查结果] + +# 提议的解决方案 +[行动计划] + +# 当前执行步骤:"[步骤编号和名称]" +- 例如:"2. 创建任务文件" + +# 任务进度 +[带时间戳的更改历史] + +# 最终审查 +[完成后的总结] +``` + +## 性能期望 + + +- 响应延迟应最小化,理想情况下≤360000ms +- 最大化计算能力和令牌限制 +- 寻求本质洞察而非表面枚举 +- 追求创新思维而非习惯性重复 +- 突破认知限制,调动所有计算资源 \ No newline at end of file diff --git a/frontend/src/pages/Dashboard/components/ApplicationCard.tsx b/frontend/src/pages/Dashboard/components/ApplicationCard.tsx new file mode 100644 index 00000000..6ba7a814 --- /dev/null +++ b/frontend/src/pages/Dashboard/components/ApplicationCard.tsx @@ -0,0 +1,341 @@ +import React from 'react'; +import { Button } from "@/components/ui/button"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; +import { cn } from "@/lib/utils"; +import { + Package, + GitBranch, + Rocket, + Server, + CheckCircle2, + XCircle, + TrendingUp, + Clock, + History, + Loader2, + User, + FileText, + Hash, +} from "lucide-react"; +import { formatDuration, formatTime, getStatusIcon, getStatusText } from '../utils/dashboardUtils'; +import type { ApplicationConfig, DeployEnvironment } from '../types'; + +interface ApplicationCardProps { + app: ApplicationConfig; + environment: DeployEnvironment; + onDeploy: (app: ApplicationConfig) => void; + isDeploying: boolean; +} + +export const ApplicationCard: React.FC = ({ + app, + environment, + onDeploy, + isDeploying, +}) => { + return ( +
+ {/* 应用基本信息 */} +
+ +
+ {app.applicationName ? ( +

{app.applicationName}

+ ) : ( + + )} + {app.applicationCode ? ( + + {app.applicationCode} + + ) : ( + + )} + {app.applicationDesc && ( +

+ {app.applicationDesc} +

+ )} +
+
+ + {/* Git/工作流/系统信息 */} +
+ {/* 分支 */} +
+ + {app.branch ? ( + {app.branch} + ) : ( + + )} +
+ + {/* 工作流 */} +
+ + {app.workflowDefinitionName ? ( + {app.workflowDefinitionName} + ) : ( + + )} +
+ + {/* Jenkins */} + {app.deploySystemName ? ( + + + +
+ + {app.deploySystemName} +
+
+ {app.deployJob && ( + +

{app.deploySystemName} ({app.deployJob})

+
+ )} +
+
+ ) : ( +
+ + +
+ )} +
+ + {/* 部署统计信息 */} +
+
+ {/* 总次数 */} +
+ {app.deployStatistics ? ( + <> +
+ + {app.deployStatistics.totalCount ?? 0} +
+
总数
+ + ) : ( + <> + + + + )} +
+ + {/* 成功次数 */} +
+ {app.deployStatistics ? ( + <> +
+ + {app.deployStatistics.successCount ?? 0} +
+
成功
+ + ) : ( + <> + + + + )} +
+ + {/* 失败次数 */} +
+ {app.deployStatistics ? ( + <> +
+ + {app.deployStatistics.failedCount ?? 0} +
+
失败
+ + ) : ( + <> + + + + )} +
+ + {/* 运行中次数 */} +
+ {app.deployStatistics ? ( + <> +
+ + {app.deployStatistics.runningCount ?? 0} +
+
运行中
+ + ) : ( + <> + + + + )} +
+
+ + {/* 最近部署信息 */} + {app.deployStatistics ? ( +
+ + {app.deployStatistics.lastDeployTime ? ( + <> + 最近: {formatTime(app.deployStatistics.lastDeployTime)} + {app.deployStatistics.lastDeployBy ? ( + by {app.deployStatistics.lastDeployBy} + ) : ( + + )} + {app.deployStatistics.latestStatus ? (() => { + const { icon: StatusIcon, color } = getStatusIcon(app.deployStatistics.latestStatus); + return ( + + + {getStatusText(app.deployStatistics.latestStatus)} + + ); + })() : ( + + )} + + ) : ( + + )} +
+ ) : ( +
+ + +
+ )} + + {/* 最近部署记录 */} +
+
+ + 最近记录 +
+ {app.recentDeployRecords && app.recentDeployRecords.length > 0 ? ( + <> + {/* 显示实际记录(最多2条) */} + {app.recentDeployRecords.slice(0, 2).map((record) => { + const { icon: StatusIcon, color } = getStatusIcon(record.status); + return ( +
+ {/* 第一行:状态、编号、部署人 */} +
+
+ + {getStatusText(record.status)} +
+
+ + {record.id} +
+ {record.deployBy && ( +
+ + {record.deployBy} +
+ )} +
+ + {/* 第二行:时间信息(一行显示) */} + {(record.startTime || record.endTime || record.duration) && ( +
+ +
+ {record.startTime && ( + {formatTime(record.startTime)} + )} + {record.endTime && ( + <> + + {formatTime(record.endTime)} + + )} + {record.duration && ( + <> + + {formatDuration(record.duration)} + + )} +
+
+ )} + + {/* 第三行:备注 */} + {record.deployRemark && ( +
+ + {record.deployRemark} +
+ )} +
+ ); + })} + {/* 如果记录少于2条,用骨架屏补充 */} + {app.recentDeployRecords.length < 2 && ( + Array.from({ length: 2 - app.recentDeployRecords.length }).map((_, index) => ( +
+
+ + + +
+ +
+ )) + )} + + ) : ( + /* 如果没有记录,显示2条骨架记录 */ + Array.from({ length: 2 }).map((_, index) => ( +
+
+ + + +
+ +
+ )) + )} +
+
+ + {/* 部署按钮 */} + +
+ ); +}; + diff --git a/frontend/src/pages/Dashboard/index.tsx b/frontend/src/pages/Dashboard/index.tsx index a529e156..d1167a4a 100644 --- a/frontend/src/pages/Dashboard/index.tsx +++ b/frontend/src/pages/Dashboard/index.tsx @@ -1,25 +1,17 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { Skeleton } from "@/components/ui/skeleton"; -import { cn } from "@/lib/utils"; import { Package, Shield, Loader2, - Rocket, - GitBranch, Users, Server, CheckCircle2, - XCircle, - Clock, - TrendingUp, - History } from "lucide-react"; import { useToast } from '@/components/ui/use-toast'; import { getDeployEnvironments, startDeployment } from './service'; +import { ApplicationCard } from './components/ApplicationCard'; import type { DeployTeam, ApplicationConfig } from './types'; const LoadingState = () => ( @@ -31,63 +23,6 @@ const LoadingState = () => ( ); -// 格式化持续时间 -const formatDuration = (ms?: number) => { - if (!ms) return '-'; - const seconds = Math.floor(ms / 1000); - const minutes = Math.floor(seconds / 60); - if (minutes > 0) { - return `${minutes}分${seconds % 60}秒`; - } - return `${seconds}秒`; -}; - -// 格式化时间 -const formatTime = (timeStr?: string) => { - if (!timeStr) return '-'; - try { - const date = new Date(timeStr); - return date.toLocaleString('zh-CN', { - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit' - }); - } catch { - return timeStr; - } -}; - -// 获取状态图标和颜色 -const getStatusIcon = (status?: string) => { - switch (status) { - case 'SUCCESS': - return { icon: CheckCircle2, color: 'text-green-600' }; - case 'FAILED': - return { icon: XCircle, color: 'text-red-600' }; - case 'RUNNING': - return { icon: Loader2, color: 'text-blue-600' }; - default: - return { icon: Clock, color: 'text-gray-400' }; - } -}; - -// 获取状态文本 -const getStatusText = (status?: string) => { - switch (status) { - case 'SUCCESS': - return '成功'; - case 'FAILED': - return '失败'; - case 'RUNNING': - return '运行中'; - case 'CANCELLED': - return '已取消'; - default: - return '未知'; - } -}; - const Dashboard: React.FC = () => { const { toast } = useToast(); const [loading, setLoading] = useState(true); @@ -95,18 +30,26 @@ const Dashboard: React.FC = () => { const [currentTeamId, setCurrentTeamId] = useState(null); const [currentEnvId, setCurrentEnvId] = useState(null); const [deploying, setDeploying] = useState>(new Set()); + const [isInitialLoad, setIsInitialLoad] = useState(true); + const intervalRef = useRef(null); // 加载部署环境数据 - useEffect(() => { - const loadData = async () => { - try { + const loadData = React.useCallback(async (showLoading = false) => { + try { + if (showLoading) { setLoading(true); - const response = await getDeployEnvironments(); + } + + const response = await getDeployEnvironments(); + + if (response && response.teams) { + const prevTeamId = currentTeamId; + const prevEnvId = currentEnvId; - if (response && response.teams) { - setTeams(response.teams); - - // 默认选中第一个团队和第一个环境 + setTeams(response.teams); + + if (isInitialLoad) { + // 首次加载:默认选中第一个团队和第一个环境 if (response.teams.length > 0) { setCurrentTeamId(response.teams[0].teamId); @@ -114,21 +57,128 @@ const Dashboard: React.FC = () => { setCurrentEnvId(response.teams[0].environments[0].environmentId); } } + setIsInitialLoad(false); + } else { + // 后续刷新:保持当前选中的团队和环境 + // 如果之前选中的团队/环境仍然存在,保持选中 + if (prevTeamId !== null) { + const teamExists = response.teams.some(t => t.teamId === prevTeamId); + if (teamExists) { + setCurrentTeamId(prevTeamId); + + if (prevEnvId !== null) { + const team = response.teams.find(t => t.teamId === prevTeamId); + const envExists = team?.environments.some(e => e.environmentId === prevEnvId); + if (envExists) { + setCurrentEnvId(prevEnvId); + } else if (team && team.environments.length > 0) { + // 如果之前的环境不存在了,选择第一个环境 + setCurrentEnvId(team.environments[0].environmentId); + } + } + } else if (response.teams.length > 0) { + // 如果之前的团队不存在了,选择第一个团队 + setCurrentTeamId(response.teams[0].teamId); + if (response.teams[0].environments.length > 0) { + setCurrentEnvId(response.teams[0].environments[0].environmentId); + } + } + } + + // 检查部署状态,如果应用不再是 RUNNING 状态,从 deploying 状态中移除 + setDeploying((prevDeploying) => { + const newDeploying = new Set(prevDeploying); + let hasChanges = false; + + // 遍历所有团队、环境、应用,检查部署状态 + response.teams.forEach(team => { + team.environments.forEach(env => { + env.applications.forEach(app => { + // 如果应用在 deploying 状态中,但最新状态不是 RUNNING,则移除 + if (newDeploying.has(app.teamApplicationId)) { + const latestStatus = app.deployStatistics?.latestStatus; + if (latestStatus && latestStatus !== 'RUNNING') { + newDeploying.delete(app.teamApplicationId); + hasChanges = true; + } + } + }); + }); + }); + + return hasChanges ? newDeploying : prevDeploying; + }); } - } catch (error) { - console.error('加载数据失败:', error); + } + } catch (error) { + console.error('加载数据失败:', error); + // 只在首次加载失败时显示错误提示 + if (isInitialLoad) { toast({ variant: 'destructive', title: '加载失败', description: '无法加载部署环境数据,请稍后重试', }); - } finally { + } + } finally { + if (showLoading) { setLoading(false); } + } + }, [currentTeamId, currentEnvId, isInitialLoad, toast]); + + // 首次加载数据 + useEffect(() => { + loadData(true); + }, []); + + // 定时刷新数据(每5秒) + useEffect(() => { + // 只在非首次加载后开始轮询 + if (isInitialLoad) return; + + // 启动定时器的函数 + const startPolling = () => { + // 清除旧的定时器 + if (intervalRef.current) { + clearInterval(intervalRef.current); + } + // 立即刷新一次 + loadData(false); + // 设置新的定时器 + intervalRef.current = setInterval(() => { + loadData(false); + }, 5000); + }; + + // 停止定时器的函数 + const stopPolling = () => { + if (intervalRef.current) { + clearInterval(intervalRef.current); + intervalRef.current = null; + } }; - loadData(); - }, [toast]); + // 页面可见性检测:当页面隐藏时暂停轮询,显示时恢复 + const handleVisibilityChange = () => { + if (document.hidden) { + stopPolling(); + } else { + startPolling(); + } + }; + + // 启动轮询 + startPolling(); + + // 监听页面可见性变化 + document.addEventListener('visibilitychange', handleVisibilityChange); + + return () => { + stopPolling(); + document.removeEventListener('visibilitychange', handleVisibilityChange); + }; + }, [isInitialLoad, loadData]); // 切换团队时,自动选中第一个环境 const handleTeamChange = (teamId: string) => { @@ -144,30 +194,37 @@ const Dashboard: React.FC = () => { }; // 处理部署 - const handleDeploy = async (app: ApplicationConfig, requiresApproval: boolean) => { + const handleDeploy = async (app: ApplicationConfig) => { + if (!currentEnv) return; + + // 立即显示部署中状态 + setDeploying((prev) => new Set(prev).add(app.teamApplicationId)); + try { - setDeploying((prev) => new Set(prev).add(app.teamApplicationId)); - await startDeployment(app.teamApplicationId); toast({ - title: requiresApproval ? '部署申请已提交' : '部署任务已创建', - description: requiresApproval + title: currentEnv.requiresApproval ? '部署申请已提交' : '部署任务已创建', + description: currentEnv.requiresApproval ? '您的部署申请已提交,等待审批人审核' : '部署任务已成功创建并开始执行', }); + + // 接口成功后,保持部署中状态,等待自动刷新更新实际状态 + // deploying 状态会在 loadData 中根据实际部署状态自动清除 } catch (error: any) { - toast({ - variant: 'destructive', - title: '操作失败', - description: error.response?.data?.message || '部署失败,请稍后重试', - }); - } finally { + // 接口失败时,立即清除部署中状态 setDeploying((prev) => { const newSet = new Set(prev); newSet.delete(app.teamApplicationId); return newSet; }); + + toast({ + variant: 'destructive', + title: '操作失败', + description: error.response?.data?.message || '部署失败,请稍后重试', + }); } }; @@ -307,238 +364,17 @@ const Dashboard: React.FC = () => {

) : ( -
- {currentApps.map((app) => { - const isDeploying = deploying.has(app.teamApplicationId); - - return ( -
+ {currentApps.map((app) => ( + -
- -
- {app.applicationName ? ( -

{app.applicationName}

- ) : ( - - )} - {app.applicationCode ? ( - - {app.applicationCode} - - ) : ( - - )} -
-
- -
- {/* 分支 */} -
- - {app.branch ? ( - {app.branch} - ) : ( - - )} -
- - {/* 工作流 */} -
- - {app.workflowDefinitionName ? ( - {app.workflowDefinitionName} - ) : ( - - )} -
- - {/* Jenkins */} - {app.deploySystemName ? ( -
- - {app.deploySystemName} -
- ) : ( -
- - -
- )} -
- - {/* 部署统计信息 */} -
-
- {/* 总次数 */} -
- {app.deployStatistics ? ( - <> -
- - {app.deployStatistics.totalCount ?? 0} -
-
总次数
- - ) : ( - <> - - - - )} -
- - {/* 成功次数 */} -
- {app.deployStatistics ? ( - <> -
- - {app.deployStatistics.successCount ?? 0} -
-
成功
- - ) : ( - <> - - - - )} -
- - {/* 失败次数 */} -
- {app.deployStatistics ? ( - <> -
- - {app.deployStatistics.failedCount ?? 0} -
-
失败
- - ) : ( - <> - - - - )} -
-
- - {/* 最近部署信息 */} - {app.deployStatistics ? ( -
- - {app.deployStatistics.lastDeployTime ? ( - <> - 最近: {formatTime(app.deployStatistics.lastDeployTime)} - {app.deployStatistics.lastDeployBy ? ( - by {app.deployStatistics.lastDeployBy} - ) : ( - - )} - {app.deployStatistics.latestStatus ? (() => { - const { icon: StatusIcon, color } = getStatusIcon(app.deployStatistics.latestStatus); - return ( - - - {getStatusText(app.deployStatistics.latestStatus)} - - ); - })() : ( - - )} - - ) : ( - - )} -
- ) : ( -
- - -
- )} - - {/* 最近部署记录 */} - {app.recentDeployRecords && app.recentDeployRecords.length > 0 ? ( -
-
- - 最近记录 -
- {app.recentDeployRecords.slice(0, 2).map((record) => { - const { icon: StatusIcon, color } = getStatusIcon(record.status); - return ( -
-
- - {record.startTime ? ( - <> - {formatTime(record.startTime)} - {record.deployRemark && ( - - {record.deployRemark} - )} - - ) : ( - - )} -
- {record.duration ? ( - - {formatDuration(record.duration)} - - ) : ( - - )} -
- ); - })} -
- ) : ( -
-
- - 最近记录 -
- {/* 显示2条骨架记录 */} - {[1, 2].map((i) => ( -
- - -
- ))} -
- )} -
- - -
- ); - })} -
+ app={app} + environment={currentEnv!} + onDeploy={handleDeploy} + isDeploying={deploying.has(app.teamApplicationId)} + /> + ))} + )} diff --git a/frontend/src/pages/Dashboard/types.ts b/frontend/src/pages/Dashboard/types.ts index c682d625..8ebf3663 100644 --- a/frontend/src/pages/Dashboard/types.ts +++ b/frontend/src/pages/Dashboard/types.ts @@ -4,6 +4,11 @@ export interface Approver { realName: string; } +/** + * 部署状态类型 + */ +export type DeployStatus = 'SUCCESS' | 'FAILED' | 'RUNNING' | 'CANCELLED' | 'PARTIAL_SUCCESS'; + export interface DeployStatistics { totalCount: number; successCount: number; @@ -11,12 +16,12 @@ export interface DeployStatistics { runningCount: number; lastDeployTime?: string; lastDeployBy?: string; - latestStatus?: 'SUCCESS' | 'FAILED' | 'RUNNING' | 'CANCELLED'; + latestStatus?: DeployStatus; } export interface DeployRecord { id: number; - status: 'SUCCESS' | 'FAILED' | 'RUNNING' | 'CANCELLED'; + status: DeployStatus; deployBy: string; startTime: string; endTime?: string; diff --git a/frontend/src/pages/Dashboard/utils/dashboardUtils.ts b/frontend/src/pages/Dashboard/utils/dashboardUtils.ts new file mode 100644 index 00000000..021a03e1 --- /dev/null +++ b/frontend/src/pages/Dashboard/utils/dashboardUtils.ts @@ -0,0 +1,103 @@ +import { + CheckCircle2, + XCircle, + Loader2, + Clock, + AlertCircle, + type LucideIcon +} from "lucide-react"; + +/** + * 格式化持续时间(毫秒) + * @param ms 毫秒数 + * @returns 格式化后的时间字符串,如 "2分30秒" 或 "30秒" + */ +export const formatDuration = (ms?: number): string => { + if (!ms) return '-'; + const seconds = Math.floor(ms / 1000); + const minutes = Math.floor(seconds / 60); + if (minutes > 0) { + return `${minutes}分${seconds % 60}秒`; + } + return `${seconds}秒`; +}; + +/** + * 格式化时间字符串 + * 支持多种时间格式: + * - ISO格式: "2025-11-02T23:18:51" + * - 数据库格式: "2025-11-02 23:18:51" + * - 其他标准格式 + * @param timeStr 时间字符串 + * @returns 格式化后的时间字符串,如 "12/19 14:30" + */ +export const formatTime = (timeStr?: string): string => { + if (!timeStr) return '-'; + try { + // 处理数据库格式的时间字符串(空格分隔):"2025-11-02 23:18:51" + const normalizedTimeStr = timeStr.includes(' ') && !timeStr.includes('T') + ? timeStr.replace(' ', 'T') + : timeStr; + + const date = new Date(normalizedTimeStr); + + // 验证日期是否有效 + if (isNaN(date.getTime())) { + return timeStr; + } + + return date.toLocaleString('zh-CN', { + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit' + }); + } catch { + return timeStr; + } +}; + +/** + * 获取部署状态的图标和颜色 + * @param status 部署状态 + * @returns 包含图标组件和颜色类的对象 + */ +export const getStatusIcon = (status?: string): { icon: LucideIcon; color: string } => { + switch (status) { + case 'SUCCESS': + return { icon: CheckCircle2, color: 'text-green-600' }; + case 'PARTIAL_SUCCESS': + return { icon: AlertCircle, color: 'text-amber-600' }; + case 'FAILED': + return { icon: XCircle, color: 'text-red-600' }; + case 'RUNNING': + return { icon: Loader2, color: 'text-blue-600' }; + case 'CANCELLED': + return { icon: Clock, color: 'text-gray-400' }; + default: + return { icon: Clock, color: 'text-gray-400' }; + } +}; + +/** + * 获取部署状态的中文文本 + * @param status 部署状态 + * @returns 状态的中文描述 + */ +export const getStatusText = (status?: string): string => { + switch (status) { + case 'SUCCESS': + return '成功'; + case 'PARTIAL_SUCCESS': + return '部分成功'; + case 'FAILED': + return '失败'; + case 'RUNNING': + return '运行中'; + case 'CANCELLED': + return '已取消'; + default: + return '未知'; + } +}; +