feat: 重构项目页面架构并实现流水线步骤拖拽排序功能

主要更新:
- 重构项目页面结构:将原有项目页面拆分为 list 和 detail 两个子模块
- 新增项目详情页面,支持多标签页展示(流水线、部署记录)
- 实现流水线管理功能:支持新建、编辑、复制、删除、启用/禁用
- 实现流水线步骤管理:支持添加、编辑、删除、启用/禁用步骤
- 新增流水线步骤拖拽排序功能:集成 @dnd-kit 实现拖拽重排
- 优化左右两栏布局:左侧流水线列表,右侧步骤详情
- 新增部署记录展示功能:左右两栏布局,支持选中切换
- 提取可复用组件:DeployRecordItem、PipelineStepItem
- 添加表单验证和用户交互反馈
- 更新路由配置支持项目详情页面

技术改进:
- 安装 @dnd-kit 相关依赖实现拖拽功能
- 优化 TypeScript 类型定义
- 改进组件化设计,提高代码复用性
- 增强用户体验和交互反馈
This commit is contained in:
2025-09-07 22:35:33 +08:00
parent f0e1a649ee
commit ef4fce6d42
14 changed files with 1272 additions and 122 deletions

View File

@@ -0,0 +1,108 @@
import { Typography, Tag, Switch, Button } from '@arco-design/web-react';
import { IconDragArrow, IconEdit, IconDelete } from '@arco-design/web-react/icon';
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
// 流水线步骤类型定义
interface PipelineStep {
id: string;
name: string;
script: string;
enabled: boolean;
}
interface PipelineStepItemProps {
step: PipelineStep;
index: number;
pipelineId: string;
onToggle: (pipelineId: string, stepId: string, enabled: boolean) => void;
onEdit: (pipelineId: string, step: PipelineStep) => void;
onDelete: (pipelineId: string, stepId: string) => void;
}
function PipelineStepItem({
step,
index,
pipelineId,
onToggle,
onEdit,
onDelete,
}: PipelineStepItemProps) {
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
isDragging,
} = useSortable({ id: step.id });
const style = {
transform: CSS.Transform.toString(transform),
transition,
opacity: isDragging ? 0.5 : 1,
};
return (
<div
ref={setNodeRef}
style={style}
className={`bg-gray-50 rounded-lg p-4 ${isDragging ? 'shadow-lg z-10' : ''}`}
>
<div className="flex items-start gap-4">
<div className="flex items-center gap-2">
<div
{...attributes}
{...listeners}
className="cursor-grab active:cursor-grabbing"
>
<IconDragArrow className="text-gray-400" />
</div>
<div className="w-8 h-8 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center text-sm font-medium">
{index + 1}
</div>
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-3 mb-2">
<Typography.Title heading={6} className="!m-0">
{step.name}
</Typography.Title>
<Switch
size="small"
checked={step.enabled}
onChange={(enabled) => onToggle(pipelineId, step.id, enabled)}
/>
{!step.enabled && (
<Tag color="gray" size="small">
</Tag>
)}
</div>
<div className="bg-gray-900 text-green-400 p-3 rounded font-mono text-sm">
<pre className="whitespace-pre-wrap break-words">{step.script}</pre>
</div>
</div>
<div className="flex items-center gap-2">
<Button
type="text"
size="small"
icon={<IconEdit />}
className="text-gray-400 hover:text-blue-500 hover:bg-blue-50 rounded-md p-1 transition-all duration-200"
onClick={() => onEdit(pipelineId, step)}
/>
<Button
type="text"
size="small"
icon={<IconDelete />}
className="text-gray-400 hover:text-red-500 hover:bg-red-50 rounded-md p-1 transition-all duration-200"
onClick={() => onDelete(pipelineId, step.id)}
/>
</div>
</div>
</div>
);
}
export default PipelineStepItem;