STAGE_SWITCH_VALIDATION_ISSUE.md 10 KB

项目阶段切换验证问题分析

问题描述

用户反馈:订单分配阶段还没有填写必填信息和分配组员,点击顶部导航栏就可以直接进入其他阶段。

问题根源

1️⃣ switchStage 方法取消了权限验证

文件project-detail.component.ts 第504-535行

/**
 * 切换阶段(点击顶部导航栏,无权限限制)
 * ❌ 允许自由访问所有阶段,无论状态如何
 */
switchStage(stageId: string) {
  console.log('🔄 用户点击切换阶段:', stageId);
  
  // ❌ 取消权限限制,允许访问所有阶段
  console.log(`✅ 允许访问阶段: ${stageId} (状态: ${status})`);
  
  // ❌ 直接更新状态,不验证
  this.currentStage = stageId;
  
  // ❌ 直接导航,不检查前置条件
  this.router.navigate([stageId], { relativeTo: this.route });
}

问题

  • ❌ 没有检查当前阶段是否已完成必填项
  • ❌ 没有检查是否已分配设计师
  • ❌ 没有检查审批状态
  • ❌ 直接允许跳转到任意阶段

2️⃣ 导航栏设置为可点击

文件project-detail.component.html 第10-11行

<div
  class="stage-item"
  [class.clickable]="true"  <!-- ❌ 所有阶段都可点击 -->
  (click)="switchStage(stage.id)">

问题:所有阶段都标记为 clickable=true,没有根据阶段状态禁用未完成的阶段。


正确的阶段切换逻辑

✅ 应该的验证流程

用户点击导航栏阶段
    ↓
检查目标阶段状态
    ↓
【情况1】目标阶段状态 = 'pending'(未开始)
    → ❌ 禁止跳转
    → 提示:"请先完成当前阶段"
    ↓
【情况2】目标阶段状态 = 'active'(当前阶段)
    → ✅ 允许跳转(刷新当前页面)
    ↓
【情况3】目标阶段状态 = 'completed'(已完成)
    → ✅ 允许跳转(可以回顾已完成的阶段)

✅ 各阶段的完成条件

订单分配阶段(order)→ 确认需求(requirements)

必填项验证:(stage-order.component.ts 第1192-1223行)

// 1. 项目名称
if (!this.projectInfo.title.trim()) {
  alert('请填写项目名称');
  return;
}

// 2. 项目类型
if (!this.projectInfo.projectType) {
  alert('请选择项目类型');
  return;
}

// 3. 小图日期
if (!this.projectInfo.demoday) {
  alert('请选择小图日期');
  return;
}

// 4. 报价明细
if (this.quotation.total === 0) {
  alert('请配置报价明细');
  return;
}

设计师分配验证:(第1228-1237行)

// 检查是否已分配设计师
const query = new Parse.Query('ProjectTeam');
query.equalTo('project', this.project.toPointer());
query.notEqualTo('isDeleted', true);
const assignedTeams = await query.find();

const hasAssignedDesigners = assignedTeams.length > 0;

完成条件

  • ✅ 已填写必填信息 + 已分配设计师 → 自动通过,进入"确认需求"
  • ⚠️ 已填写必填信息 + 未分配设计师 → 提交组长审批,等待批准

确认需求阶段(requirements)→ 交付执行(delivery)

完成条件:(stage-requirements.component.ts

  • ✅ 所有空间都已确认需求
  • ✅ 已保存需求数据

交付执行阶段(delivery)→ 售后归档(aftercare)

完成条件:(stage-delivery.component.ts

  • ✅ 所有交付阶段(建模、软装、渲染、后期)都已审批通过

修复方案

方案1:添加阶段切换验证

修改文件project-detail.component.ts

/**
 * 切换阶段(添加验证逻辑)
 */
switchStage(stageId: string) {
  console.log('🔄 用户点击切换阶段:', stageId);
  
  const status = this.getStageStatus(stageId);
  
  // ✅ 验证1:只允许访问当前阶段或已完成的阶段
  if (status === 'pending') {
    console.warn('❌ 无法访问未开始的阶段:', stageId);
    window?.fmode?.alert('请先完成当前阶段,再进入下一阶段');
    return;
  }
  
  // ✅ 验证2:检查当前阶段是否已完成必填项(可选,根据需求)
  // const canLeaveCurrentStage = await this.checkCurrentStageCompletion();
  // if (!canLeaveCurrentStage) {
  //   window?.fmode?.alert('当前阶段还有未完成的必填项');
  //   return;
  // }
  
  // ✅ 允许访问当前阶段或已完成的阶段
  console.log(`✅ 允许访问阶段: ${stageId} (状态: ${status})`);
  
  this.currentStage = stageId;
  
  this.router.navigate([stageId], { relativeTo: this.route })
    .then(success => {
      if (success) {
        console.log('✅ 导航成功:', stageId);
      } else {
        console.warn('⚠️ 导航失败:', stageId);
      }
    })
    .catch(err => {
      console.error('❌ 导航出错:', err);
    });
}

方案2:禁用未开始的阶段点击

修改文件project-detail.component.html

<div
  class="stage-item"
  [class.completed]="getStageStatus(stage.id) === 'completed'"
  [class.active]="getStageStatus(stage.id) === 'active'"
  [class.pending]="getStageStatus(stage.id) === 'pending'"
  [class.clickable]="getStageStatus(stage.id) !== 'pending'"  <!-- ✅ 只有非pending的阶段可点击 -->
  [class.disabled]="getStageStatus(stage.id) === 'pending'"   <!-- ✅ pending阶段显示为禁用 -->
  (click)="getStageStatus(stage.id) !== 'pending' && switchStage(stage.id)">  <!-- ✅ 添加条件判断 -->

对应CSS:(在 project-detail.component.scss 中)

.stage-item {
  &.disabled {
    cursor: not-allowed;
    opacity: 0.5;
    pointer-events: none;  // 禁用点击事件
  }
  
  &.clickable:not(.disabled) {
    cursor: pointer;
    
    &:hover {
      transform: translateY(-2px);
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
    }
  }
}

完整修复代码

1️⃣ 修改 TypeScript 文件

文件project-detail.component.ts 第504-535行

/**
 * 切换阶段(添加权限验证)
 * 只允许访问当前阶段或已完成的阶段
 */
switchStage(stageId: string) {
  console.log('🔄 用户点击切换阶段:', stageId, {
    currentRoute: this.router.url,
    currentStage: this.currentStage,
    workflowStage: this.project?.get('currentStage')
  });
  
  // 获取点击阶段的状态
  const status = this.getStageStatus(stageId);
  
  // ✅ 关键验证:只允许访问当前阶段或已完成的阶段
  if (status === 'pending') {
    console.warn(`❌ 阶段 "${stageId}" 尚未开始,无法访问`);
    
    // 获取阶段的友好名称
    const stageName = this.stages.find(s => s.id === stageId)?.name || stageId;
    const currentStageName = this.stages.find(s => s.id === this.currentStage)?.name || this.currentStage;
    
    window?.fmode?.alert(
      `无法进入"${stageName}"阶段\n\n` +
      `请先完成"${currentStageName}"阶段的必填项:\n` +
      `1. 填写项目基本信息\n` +
      `2. 配置报价明细\n` +
      `3. 分配设计师(或提交组长审批)`
    );
    return;
  }
  
  // ✅ 允许访问当前阶段或已完成的阶段
  console.log(`✅ 允许访问阶段: ${stageId} (状态: ${status})`);
  
  // 更新本地显示状态
  this.currentStage = stageId;
  
  // 导航到指定阶段
  this.router.navigate([stageId], { relativeTo: this.route })
    .then(success => {
      if (success) {
        console.log('✅ 导航成功:', stageId);
      } else {
        console.warn('⚠️ 导航失败:', stageId);
      }
    })
    .catch(err => {
      console.error('❌ 导航出错:', err);
    });
}

2️⃣ 修改 HTML 模板

文件project-detail.component.html 第4-11行

@for (stage of stages; track stage.id) {
  <div
    class="stage-item"
    [class.completed]="getStageStatus(stage.id) === 'completed'"
    [class.active]="getStageStatus(stage.id) === 'active'"
    [class.pending]="getStageStatus(stage.id) === 'pending'"
    [class.clickable]="getStageStatus(stage.id) !== 'pending'"
    [class.disabled]="getStageStatus(stage.id) === 'pending'"
    (click)="getStageStatus(stage.id) !== 'pending' && switchStage(stage.id)">
    <!-- 阶段内容 -->
  </div>
  <!-- ... -->
}

3️⃣ 添加禁用样式

文件project-detail.component.scss

.stage-item {
  // ... 原有样式 ...
  
  // ✅ 新增:禁用状态
  &.disabled {
    cursor: not-allowed !important;
    opacity: 0.5;
    pointer-events: none;
    
    .stage-circle {
      background: #e0e0e0;
      border-color: #e0e0e0;
    }
    
    .stage-label {
      color: #999;
    }
  }
  
  // ✅ 修改:只有非禁用的可点击项才有hover效果
  &.clickable:not(.disabled) {
    cursor: pointer;
    
    &:hover {
      transform: translateY(-2px);
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
    }
  }
}

验证步骤

测试1:订单分配阶段未完成

  1. 创建新项目,进入订单分配阶段
  2. 不填写任何信息
  3. 点击顶部导航栏的"确认需求"
  4. 预期结果:❌ 弹出提示"请先完成当前阶段"

测试2:订单分配阶段已完成

  1. 填写项目名称、类型、日期
  2. 配置报价明细
  3. 分配设计师
  4. 点击"确认订单"按钮
  5. 项目自动进入"确认需求"阶段
  6. 预期结果:✅ 顶部导航栏"订单分配"显示为绿色(已完成),"确认需求"显示为红色(当前)

测试3:回顾已完成的阶段

  1. 在"确认需求"阶段
  2. 点击顶部导航栏的"订单分配"(已完成)
  3. 预期结果:✅ 可以访问,查看已填写的信息

测试4:尝试跳过阶段

  1. 在"确认需求"阶段
  2. 点击顶部导航栏的"交付执行"(未开始)
  3. 预期结果:❌ 弹出提示"请先完成当前阶段"

总结

❌ 当前问题

  1. switchStage 方法完全取消了权限验证
  2. 所有阶段都可以随意点击
  3. 用户可以绕过必填项验证直接进入其他阶段

✅ 修复后效果

  1. 只能访问当前阶段或已完成的阶段
  2. 未开始的阶段显示为禁用状态
  3. 点击未开始的阶段会弹出友好提示
  4. 必须通过提交/审批流程才能推进到下一阶段

📝 关键改进

  • 严格验证:根据 getStageStatus() 的返回值进行权限判断
  • 友好提示:告知用户需要完成哪些必填项
  • 视觉反馈:禁用状态的阶段显示为灰色且不可点击
  • 保持灵活性:允许回顾已完成的阶段