diff --git a/PROJECT_EVALUATION.md b/PROJECT_EVALUATION.md index ff8220e..8b92cac 100644 --- a/PROJECT_EVALUATION.md +++ b/PROJECT_EVALUATION.md @@ -129,7 +129,7 @@ | ~~`api.ts` 1324 行/352 方法~~ → 已拆分为 12 个域文件 (api/kubernetes.ts, deploy.ts, monitor.ts 等) | ✅ 已解决 | 前端 | ~~P0~~ done | | 前端 **1026 处** `any` 类型 | 类型安全缺失,重构风险高 | 前端全局 | ~~P0~~ **done** (any 1076→40, unused 25→6, console 93→1, tsc --noEmit=0) | | ~~K8s 双路由体系(集群域+Kuboard 域)~~ → code 路由统一为主,ID 路由标记 deprecated 向后兼容 | ✅ 已解决 | 后端 | ~~P1~~ done | -| 25 个 ≥500 行巨型组件 | 拆分困难 | 前端 | P1 → **Q6+Q7+Q8+Q9+Q10+Q11+Q12+Q13+Q14 done: 最大组件 ResourceVisualEditor 882L→282L (-68%) + AgentCLI 814L→521L (-36%) + SystemSettingsTab 754L→173L (-77%) + NamespaceResourcePage 719L→300L (-58%) + ApprovalWorkflowPage 705L→167L (-76%) + PipelineRunDetail 678L→273L (-60%) + CloudServerImport 699L→404L (-42%,any[]→具体接口) + UnifiedQueryPanel 682L→529L (-22%) + ServerManager 652L→442L (-32%),累计提取 39 个独立组件文件** | +| 25 个 ≥500 行巨型组件 | 拆分困难 | 前端 | P1 → **Q6-Q15 done: 最大组件 ResourceVisualEditor 882L→282L (-68%) + AgentCLI 814L→521L (-36%) + SystemSettingsTab 754L→173L (-77%) + NamespaceResourcePage 719L→300L (-58%) + ApprovalWorkflowPage 705L→167L (-76%) + PipelineRunDetail 678L→273L (-60%) + CloudServerImport 699L→404L (-42%,any[]→具体接口) + UnifiedQueryPanel 682L→529L (-22%) + ServerManager 652L→442L (-32%) + PipelineEditor 676L→406L (-40%),累计提取 45 个独立组件文件** | ### 3.2 🟡 中优先级 diff --git a/ROADMAP.md b/ROADMAP.md index 71074a1..07127c7 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -486,6 +486,7 @@ services/api.ts (1324L) | Q12 | 前端巨型组件拆分(CloudServerImport 699L→404L,提取 StepIndicator + 4 个向导步骤组件 + types,any[]→具体接口) | P0 | **done** | | Q13 | 前端巨型组件拆分(UnifiedQueryPanel 682L→529L,提取 constants + entry-mappers + 5 个 UI 子组件) | P0 | **done** | | Q14 | 前端巨型组件拆分(ServerManager 652L→442L,提取 ServerTableSection + server-export-utils) | P0 | **done** | +| Q15 | 前端巨型组件拆分(PipelineEditor 676L→406L,提取 types + TaskCard + TaskEditorPanel + PipelineLogDialog + RunsHistoryTab + PipelineMembersTab) | P0 | **done** | --- @@ -920,3 +921,40 @@ pending → in_progress → done / blocked - [x] Docker 部署 healthy,前端页面正常加载(HTTP 200) - [x] 服务器管理页功能不变(列表/搜索/筛选/分页/选择/批量删除/CSV 导出/创建编辑/终端/RDP/文件浏览全部正常) +--- + +## Q15: 前端巨型组件拆分 — PipelineEditor [done] + +**问题**: `PipelineEditor.tsx`(676 行)是 Q14 拆分后项目最大的核心业务组件之一(排除 UI 库 sidebar/data-table),包含流水线编辑器页面的全部逻辑:类型定义 + 常量 + 工具函数(Task/Stage/LogEntry 接口 + taskIcons 映射 + uid/fmtDuration)、任务卡片组件、任务编辑面板、日志弹窗、以及主组件(状态管理 + 数据加载 + 事件处理 + 5 个 Tab 的 JSX:流程配置/触发器/变量/发布记录/成员管理)。单文件过大导致维护困难、代码导航低效。 + +**已完成 (2026-07-08)**: + +将类型/常量/工具、任务卡片、任务编辑面板、日志弹窗、发布记录 Tab、成员管理 Tab 提取为 `pipeline-editor/` 子目录下的独立文件: + +| 新文件 | 行数 | 职责 | +|--------|------|------| +| `types.ts` | 66 | Task/Stage/LogEntry 接口 + stageTypeLabelKeys/taskIcons 常量 + uid/fmtDuration 工具函数 | +| `TaskCard.tsx` | 58 | 任务卡片展示组件(选中态 + 图标 + 名称/类型 + 悬浮删除按钮) | +| `TaskEditorPanel.tsx` | 91 | 任务编辑面板(名称/类型/图标选择 + 脚本类型切换 + 脚本内容编辑) | +| `PipelineLogDialog.tsx` | 71 | 流水线运行日志弹窗(运行元信息 + 彩色日志渲染 + 空状态) | +| `RunsHistoryTab.tsx` | 177 | 发布记录 Tab(运行历史表格 + 状态徽章 + 触发方式 + 操作按钮) | +| `PipelineMembersTab.tsx` | 172 | 成员管理 Tab(添加成员表单 + 直接用户列表 + 组授权列表) | + +主文件 `PipelineEditor.tsx` 从 **676 行降至 406 行**(-40%),仅保留:20+ 个状态声明、数据加载(pipeline/members/runs/environments/buildNodes)、事件处理(task/stage CRUD + save/trigger/stop + member add/remove)、触发器 Tab + 变量 Tab JSX(与主组件状态紧耦合)、Flow Tab 编排(stages 列表 + TaskCard + TaskEditor 集成)、以及 5 个子组件的编排。 + +**提取原则**: types 为纯数据/类型(零 React 依赖,仅 lucide 图标类型引用);TaskCard 为受控展示组件(接收 task + selected + 回调);TaskEditorPanel 为受控编辑组件(接收 task + onUpdate 回调);PipelineLogDialog 为纯展示弹窗(接收 run + logs);RunsHistoryTab 为受控列表组件(接收 runs + onViewLog/onStop 回调);PipelineMembersTab 为受控管理组件(接收 members + 6 个状态/回调 props)。零逻辑变更。 + +**验收标准**: +- [x] PipelineEditor.tsx 从 676 行降至 **406 行**(-40%) +- [x] 6 个提取的文件全部 < 180 行(最大 RunsHistoryTab 177 行) +- [x] `tsc -b` 零错误 +- [x] `npm run build` 零错误(✓ built in 16.35s) +- [x] ESLint errors 保持 **0**(清理 1 个 unused import: Loader2) +- [x] ESLint warnings 保持 **64**(无新增) +- [x] 全局 ESLint any 保持 **0** +- [x] `go build` 零错误 +- [x] 17/17 集成回归测试通过(零回归) +- [x] Docker 部署 healthy,前端页面正常加载(HTTP 200) +- [x] 流水线编辑器页功能不变(流程配置/触发器/变量/发布记录/成员管理 5 个 Tab 全部正常) + + diff --git a/frontend/src/components/deploy/PipelineEditor.tsx b/frontend/src/components/deploy/PipelineEditor.tsx index 03f4b64..81a8514 100644 --- a/frontend/src/components/deploy/PipelineEditor.tsx +++ b/frontend/src/components/deploy/PipelineEditor.tsx @@ -2,7 +2,6 @@ import { useState, useEffect, useCallback } from 'react'; import { useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { - Plus, Trash2, ChevronUp, @@ -10,26 +9,12 @@ import { ChevronRight, Save, Play, - Loader2, GitBranch, - Wrench, - Server, - FlaskConical, - Zap, - Terminal, - FileCode, - Container, Clock, Webhook, Hand, - Check, - X, - StopCircle, - ScrollText, - Users, - UserPlus, Shield, - FileText, + Server, } from 'lucide-react'; import { Card, CardContent } from '@/components/ui/card'; @@ -38,10 +23,8 @@ import { Input } from '@/components/ui/input'; import { Badge } from '@/components/ui/badge'; import { Tabs, TabsContent } from '@/components/ui/tabs'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; -import { Textarea } from '@/components/ui/textarea'; -import { Switch } from '@/components/ui/switch'; import { Label } from '@/components/ui/label'; -import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { Switch } from '@/components/ui/switch'; import { toast } from 'sonner'; import { pipelineApi, deployApi, buildNodeApi, type BuildNode } from '@/api'; import { useConfirm } from '@/components/ui/confirm-dialog'; @@ -49,124 +32,13 @@ import { PageShell } from '@/components/layout/PageShell'; import { PageEmpty } from '@/components/layout/PageEmpty'; import type { JsonData } from '@/types/common'; -// ---- types ---- -interface Task { - id: string; name: string; type: string; icon: string; - config: Record; script: string; script_type: string; -} -interface Stage { - id: string; name: string; type: string; tasks: Task[]; -} -interface LogEntry { - time: string; level: string; message: string; stage: string; -} - -const stageTypeLabelKeys: Record = { - source: 'deploy.stage.checkoutDesc', - build: 'deploy.stage.build', - deploy: 'deploy.stage.deploy', - test: 'deploy.stage.test', - custom: 'deploy.stage.custom', -}; -const taskIcons: JsonData = { GitBranch, Wrench, Server, FlaskConical, Zap, Terminal, FileCode, Container }; - -function uid() { return `tsk-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`; } -function fmtDuration(s: number | undefined): string { - if (!s) return '-'; - if (s < 60) return `${s}s`; - const m = Math.floor(s / 60); const sec = s % 60; - return sec > 0 ? `${m}分${sec}秒` : `${m}分`; -} - -// ---- Task Card ---- -function TaskCard({ task, selected, onSelect, onDelete }: { task: Task; selected: boolean; onSelect: () => void; onDelete: () => void }) { - const Icon = taskIcons[task.icon] || Wrench; - return ( -
-
- -
-
-
{task.name}
-
{task.type}
-
- -
- ); -} - -// ---- Task Editor Panel ---- -function TaskEditor({ task, onUpdate }: { task: Task | null; onUpdate: (t: Task) => void; onClose: () => void }) { - const { t } = useTranslation(); - if (!task) return
{t('deploy.pipeline.selectTaskHint')}
; - return ( -
-
onUpdate({ ...task, name: e.target.value })} className="h-8 text-sm" />
-
-
- -
-
- -
-
-
-
{['shell', 'python'].map(s => ( onUpdate({ ...task, script_type: s })}>{s}))}
-
-
-