# 审批流程时间线增强设计方案

> 创建日期：2026-01-06  
> 设计原则：遵循业内规范（钉钉/飞书），适配 FFWorkspace 设计系统

---

## 📐 设计目标

### 核心需求
1. ✅ 显示完整的审批流程（包括未执行节点）
2. ✅ 清晰区分节点状态（已执行 vs 未执行）
3. ✅ 提供悬浮提示，说明节点状态
4. ✅ 保持与现有设计系统的一致性

### 用户体验目标
- 用户能一眼看到完整的审批流程设计
- 拒绝/撤回后，清楚知道哪些节点未执行
- 视觉层次清晰，主次分明

---

## 🎨 视觉设计方案

### 1. 颜色系统

#### 1.1 当前状态颜色（保持不变）

```typescript
// 已执行状态（饱和度高，对比强）
const EXECUTED_STATES = {
  completed: {
    bg: '#00b96b',        // 绿色 - 已通过
    icon: '✓',
    text: '#ffffff',
    ring: 'ring-[#00b96b]/20',
  },
  rejected: {
    bg: '#f5222d',        // 红色 - 已拒绝
    icon: '✕',
    text: '#ffffff',
    ring: 'ring-[#f5222d]/20',
  },
  active: {
    bg: '#1890ff',        // 蓝色 - 进行中
    icon: '⏰',
    text: '#ffffff',
    ring: 'ring-[#1890ff]/30',
    animation: 'animate-pulse',
  },
  returned: {
    bg: '#ff7d00',        // 橙色 - 已退回
    icon: '↩',
    text: '#ffffff',
    ring: 'ring-[#ff7d00]/20',
  },
};
```

#### 1.2 未执行状态颜色（新增）

```typescript
// 未执行状态（饱和度低，视觉弱化）
const PENDING_STATES = {
  pending: {
    bg: '#d9d9d9',        // 中灰 - 待执行
    icon: '○',
    text: '#8c8c8c',
    ring: 'ring-gray-200',
    opacity: 0.6,
  },
  skipped: {
    bg: '#f5f5f5',        // 浅灰 - 已跳过（流程终止）
    icon: '−',
    text: '#bfbfbf',
    ring: 'ring-gray-100',
    opacity: 0.4,
  },
};
```

**设计理念**：
- ✅ 使用灰色系表示未执行，符合用户认知
- ✅ 降低饱和度和透明度，让已执行节点更突出
- ✅ 与现有颜色系统协调，不引入新的主色调

---

### 2. 连接线样式

#### 2.1 实线连接器（已执行路径）

```css
.connector-solid {
  width: 2px;
  height: 48px; /* 或更长，根据 compact 模式 */
  background: linear-gradient(
    to bottom,
    #e5e7eb 0%,
    #d1d5db 50%,
    #e5e7eb 100%
  );
  position: relative;
  margin: 8px auto;
}

/* 渐变效果，让连接更自然 */
.connector-solid::before,
.connector-solid::after {
  content: '';
  position: absolute;
  left: 0;
  width: 2px;
  height: 4px;
  background: #f3f4f6;
}

.connector-solid::before {
  top: -4px;
}

.connector-solid::after {
  bottom: -4px;
}
```

#### 2.2 虚线连接器（未执行路径）⭐ 新增

```css
.connector-dashed {
  width: 2px;
  height: 48px;
  background-image: linear-gradient(
    to bottom,
    #d9d9d9 0%,
    #d9d9d9 50%,
    transparent 50%,
    transparent 100%
  );
  background-size: 2px 8px;
  background-repeat: repeat-y;
  opacity: 0.4;
  position: relative;
  margin: 8px auto;
}

/* 虚线淡入淡出效果 */
.connector-dashed::before,
.connector-dashed::after {
  content: '';
  position: absolute;
  left: 0;
  width: 2px;
  height: 8px;
  background: linear-gradient(
    to bottom,
    transparent,
    #d9d9d9
  );
  opacity: 0.3;
}

.connector-dashed::before {
  top: -8px;
  transform: scaleY(-1);
}

.connector-dashed::after {
  bottom: -8px;
}
```

**设计理念**：
- ✅ 虚线表示"计划中但未执行"，符合用户认知
- ✅ 低透明度，不干扰视觉焦点
- ✅ 渐变效果，让过渡更自然

---

### 3. 节点样式设计

#### 3.1 已执行节点（现有样式优化）

```tsx
// 已通过
<div className="relative flex flex-col items-center">
  {/* 节点图标 */}
  <div className="w-8 h-8 rounded-full bg-[#00b96b] shadow-sm ring-4 ring-[#00b96b]/10 flex items-center justify-center">
    <Check className="w-4 h-4 text-white stroke-[2.5]" />
  </div>
  
  {/* 连接线 */}
  <div className="connector-solid" />
</div>

// 已拒绝
<div className="relative flex flex-col items-center">
  {/* 节点图标 */}
  <div className="w-8 h-8 rounded-full bg-[#f5222d] shadow-sm ring-4 ring-[#f5222d]/10 flex items-center justify-center">
    <X className="w-4 h-4 text-white stroke-[2.5]" />
  </div>
  
  {/* 连接线 */}
  <div className="connector-solid" />
</div>
```

#### 3.2 未执行节点（新增）⭐

```tsx
// 待执行（流程进行中）
<div className="relative flex flex-col items-center group">
  {/* 节点图标 */}
  <div className="w-8 h-8 rounded-full bg-[#d9d9d9] shadow-sm ring-4 ring-gray-200/50 flex items-center justify-center transition-all group-hover:ring-gray-300/80">
    <div className="w-2 h-2 rounded-full bg-[#8c8c8c]" />
  </div>
  
  {/* 悬浮提示（见下文） */}
  <div className="tooltip">待执行</div>
  
  {/* 虚线连接 */}
  <div className="connector-dashed" />
</div>

// 已跳过（流程终止后的节点）
<div className="relative flex flex-col items-center group">
  {/* 节点图标 */}
  <div className="w-8 h-8 rounded-full bg-[#f5f5f5] shadow-none ring-4 ring-gray-100/30 flex items-center justify-center opacity-40 transition-all group-hover:opacity-60">
    <Minus className="w-3 h-3 text-[#bfbfbf]" />
  </div>
  
  {/* 悬浮提示 */}
  <div className="tooltip">流程已终止，此节点未执行</div>
  
  {/* 虚线连接 */}
  <div className="connector-dashed" />
</div>
```

**设计亮点**：
- ✅ 使用小圆点表示"待执行"，简洁
- ✅ 使用横线表示"已跳过"，符合"划掉"的语义
- ✅ group-hover 交互，鼠标悬浮时提供视觉反馈
- ✅ 透明度变化，清晰区分优先级

---

### 4. 悬浮提示设计

#### 4.1 基础样式

```tsx
<div className="absolute left-full ml-4 px-3 py-1.5 bg-gray-900 text-white text-xs rounded-md shadow-lg whitespace-nowrap opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-50 pointer-events-none">
  {/* 箭头 */}
  <div className="absolute right-full top-1/2 -translate-y-1/2 border-4 border-transparent border-r-gray-900" />
  
  {/* 提示文本 */}
  <span>流程已终止，此节点未执行</span>
</div>
```

#### 4.2 不同状态的提示内容

```typescript
const TOOLTIP_TEXTS = {
  // 未执行状态
  pending: {
    zh: '待执行',
    en: 'Pending',
  },
  skipped: {
    zh: '流程已终止，此节点未执行',
    en: 'Process terminated, node not executed',
  },
  
  // 已执行状态（可选，提供更多信息）
  completed: {
    zh: (operator: string, time: string) => `${operator} 于 ${time} 通过`,
    en: (operator: string, time: string) => `Approved by ${operator} at ${time}`,
  },
  rejected: {
    zh: (operator: string, time: string, reason: string) => 
      `${operator} 于 ${time} 拒绝\n原因：${reason}`,
    en: (operator: string, time: string, reason: string) => 
      `Rejected by ${operator} at ${time}\nReason: ${reason}`,
  },
};
```

**设计理念**：
- ✅ 黑色背景（#1f2937）高对比，易阅读
- ✅ 小箭头指向节点，明确关联
- ✅ 短暂延迟（200ms），避免误触
- ✅ 支持多行文本（拒绝原因）

---

### 5. 完整流程示例

#### 5.1 审批通过场景

```
┌──────────────┐
│ 发起人 ✅    │ 张员工
│ 2026/1/5     │
└──────┬───────┘
       │ (实线)
┌──────────────┐
│ 审批人 ✅    │ 王经理 "同意"
│ 2026/1/5     │
└──────┬───────┘
       │ (实线)
┌──────────────┐
│ 审批人 ✅    │ 赵总监 "同意"
│ 2026/1/5     │
└──────┬───────┘
       │ (实线)
┌──────────────┐
│ 结束 ✅      │
└──────────────┘
```

#### 5.2 审批拒绝场景（方案A 推荐）⭐

```
┌──────────────┐
│ 发起人 ✅    │ 张员工
│ 2026/1/5     │
└──────┬───────┘
       │ (实线)
┌──────────────┐
│ 审批人 ✅    │ 王经理 "同意"
│ 2026/1/5     │
└──────┬───────┘
       │ (实线)
┌──────────────┐
│ 审批人 ❌    │ 赵总监 "不符合要求"
│ 2026/1/5     │
└──────┬───────┘
       ┊ (虚线，透明度 40%)
┌──────────────┐
│ 审批人 ⚪    │ 李总 (灰色，透明度 40%)
│              │ [Tooltip: 流程已终止，此节点未执行]
└──────┬───────┘
       ┊ (虚线)
┌──────────────┐
│ 结束 ⚪      │ (灰色，透明度 40%)
└──────────────┘
```

**视觉层次**：
1. **最突出**：拒绝节点（红色，饱和度高）
2. **次要**：已执行节点（绿色/蓝色，饱和度中）
3. **弱化**：未执行节点（灰色，透明度低）

---

## 🖼️ 设计稿（Tailwind CSS）

### 完整组件代码示例

```tsx
// 时间线节点组件
interface TimelineNodeProps {
  node: ProcessNode;
  status: NodeStatusMapping;
  isLast: boolean;
  instanceStatus: InstanceStatus;
}

function TimelineNode({ node, status, isLast, instanceStatus }: TimelineNodeProps) {
  const { t, locale } = useTranslation();
  
  // 判断节点是否为未执行状态
  const isSkipped = status.status === 'skipped';
  const isPending = status.status === 'pending';
  const isUnexecuted = isSkipped || isPending;
  
  return (
    <div className="flex gap-4">
      {/* 左侧时间线 */}
      <div className="flex flex-col items-center relative group">
        {/* 节点图标 */}
        <TimelineIcon status={status.status} type={node.type} />
        
        {/* 悬浮提示 */}
        {isUnexecuted && (
          <div className="absolute left-full ml-4 px-3 py-1.5 bg-gray-900 text-white text-xs rounded-md shadow-lg whitespace-nowrap opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-50">
            <div className="absolute right-full top-1/2 -translate-y-1/2 border-4 border-transparent border-r-gray-900" />
            <span>
              {isSkipped 
                ? t.approvals.timeline.nodeSkipped 
                : t.approvals.timeline.nodePending}
            </span>
          </div>
        )}
        
        {/* 连接线 */}
        {!isLast && (
          <div className={`
            w-0.5 mt-2
            ${isUnexecuted ? 'h-12 connector-dashed' : 'h-12 connector-solid'}
          `} />
        )}
      </div>
      
      {/* 右侧内容 */}
      <div className={`flex-1 pb-6 ${isUnexecuted ? 'opacity-40' : 'opacity-100'}`}>
        {/* 节点名称 */}
        <div className="flex items-center gap-2">
          <h4 className="text-sm font-medium text-gray-900">
            {node.name}
          </h4>
          {/* 状态标签 */}
          <StatusBadge status={status.status} />
        </div>
        
        {/* 审批人信息（仅已执行节点） */}
        {!isUnexecuted && status.operator && (
          <p className="text-xs text-gray-500 mt-1">
            {status.operator}
          </p>
        )}
        
        {/* 评论（仅已执行节点） */}
        {!isUnexecuted && status.comment && (
          <div className="mt-2 p-2 bg-gray-50 rounded text-sm text-gray-600 border-l-2 border-gray-300">
            {status.comment}
          </div>
        )}
      </div>
    </div>
  );
}

// 时间线图标组件
function TimelineIcon({ status, type }: { status: NodeDisplayStatus; type: ProcessNodeType }) {
  const baseClasses = 'w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0 transition-all';
  
  const statusStyles: Record<NodeDisplayStatus, string> = {
    completed: `${baseClasses} bg-[#00b96b] shadow-sm ring-4 ring-[#00b96b]/10`,
    rejected: `${baseClasses} bg-[#f5222d] shadow-sm ring-4 ring-[#f5222d]/10`,
    active: `${baseClasses} bg-[#1890ff] shadow-sm ring-4 ring-[#1890ff]/20 animate-pulse`,
    returned: `${baseClasses} bg-[#ff7d00] shadow-sm ring-4 ring-[#ff7d00]/10`,
    pending: `${baseClasses} bg-[#d9d9d9] shadow-sm ring-4 ring-gray-200/50 group-hover:ring-gray-300/80`,
    skipped: `${baseClasses} bg-[#f5f5f5] shadow-none ring-4 ring-gray-100/30 opacity-40 group-hover:opacity-60`,
    cancelled: `${baseClasses} bg-gray-300 shadow-sm ring-4 ring-gray-200/30`,
    failed: `${baseClasses} bg-[#f5222d] shadow-sm ring-4 ring-[#f5222d]/10`,
  };
  
  const statusIcons: Record<NodeDisplayStatus, React.ReactNode> = {
    completed: <Check className="w-4 h-4 text-white stroke-[2.5]" />,
    rejected: <X className="w-4 h-4 text-white stroke-[2.5]" />,
    active: <Clock className="w-4 h-4 text-white stroke-[2.5]" />,
    returned: <CornerUpLeft className="w-4 h-4 text-white stroke-[2.5]" />,
    pending: <div className="w-2 h-2 rounded-full bg-[#8c8c8c]" />,
    skipped: <Minus className="w-3 h-3 text-[#bfbfbf]" />,
    cancelled: <Ban className="w-4 h-4 text-white stroke-[2]" />,
    failed: <AlertCircle className="w-4 h-4 text-white stroke-[2]" />,
  };
  
  return (
    <div className={statusStyles[status]}>
      {statusIcons[status]}
    </div>
  );
}

// CSS 样式
const styles = `
  .connector-solid {
    background: linear-gradient(
      to bottom,
      #e5e7eb 0%,
      #d1d5db 50%,
      #e5e7eb 100%
    );
  }
  
  .connector-dashed {
    background-image: linear-gradient(
      to bottom,
      #d9d9d9 0%,
      #d9d9d9 50%,
      transparent 50%,
      transparent 100%
    );
    background-size: 2px 8px;
    background-repeat: repeat-y;
    opacity: 0.4;
  }
`;
```

---

## 📱 响应式设计

### 桌面端（>=1024px）
- 时间线图标：8x8 (32px)
- 连接线高度：48px
- 悬浮提示：向右展开

### 移动端 (<768px)
- 时间线图标：6x6 (24px)
- 连接线高度：32px
- 悬浮提示：向下展开（避免超出屏幕）

---

## 🌐 国际化文本

需要添加的翻译键：

```typescript
// zh.ts
approvals: {
  timeline: {
    nodePending: '待执行',
    nodeSkipped: '流程已终止，此节点未执行',
    processTerminated: '流程已终止',
    statusBadges: {
      pending: '待执行',
      skipped: '未执行',
      // ... 其他状态
    },
  },
}

// en.ts
approvals: {
  timeline: {
    nodePending: 'Pending',
    nodeSkipped: 'Process terminated, node not executed',
    processTerminated: 'Process terminated',
    statusBadges: {
      pending: 'Pending',
      skipped: 'Skipped',
      // ... other statuses
    },
  },
}
```

---

## ✅ 实施检查清单

### 阶段1：设计确认
- [ ] 颜色方案确认
- [ ] 虚线样式确认
- [ ] 悬浮提示样式确认
- [ ] 响应式布局确认

### 阶段2：技术实施
- [ ] 更新类型定义（增加 pending/skipped 状态）
- [ ] 修改数据获取逻辑（获取完整流程定义）
- [ ] 更新状态映射工具
- [ ] 实现虚线连接器样式
- [ ] 实现未执行节点样式
- [ ] 实现悬浮提示
- [ ] 添加国际化文本
- [ ] 更新 LarkProcessPreview 组件

### 阶段3：测试验证
- [ ] 审批通过场景
- [ ] 审批拒绝场景（单级）
- [ ] 审批拒绝场景（多级）
- [ ] 撤回场景
- [ ] 条件分支场景
- [ ] 并行分支场景
- [ ] 移动端适配

### 阶段4：文档完善
- [ ] 更新组件文档
- [ ] 更新架构文档
- [ ] 创建视觉规范文档

---

## 🎯 预期效果

### 改进前 ❌
- 用户看不到完整流程
- 拒绝后不知道后续有哪些环节
- 无法预判审批时间

### 改进后 ✅
- 完整流程一目了然
- 清楚知道哪些环节已执行/未执行
- 视觉层次清晰，主次分明
- 符合业内规范，用户体验好

---

**附录：参考资料**

- 钉钉审批流程展示规范
- 飞书审批流程展示规范
- Material Design - Stepper Component
- Ant Design - Steps Component

