# 项目阶段切换验证问题分析
## 问题描述
**用户反馈**:订单分配阶段还没有填写必填信息和分配组员,点击顶部导航栏就可以直接进入其他阶段。
## 问题根源
### 1️⃣ switchStage 方法取消了权限验证
**文件**:`project-detail.component.ts` 第504-535行
```typescript
/**
* 切换阶段(点击顶部导航栏,无权限限制)
* ❌ 允许自由访问所有阶段,无论状态如何
*/
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行
```html
(click)="switchStage(stage.id)">
```
**问题**:所有阶段都标记为 `clickable=true`,没有根据阶段状态禁用未完成的阶段。
---
## 正确的阶段切换逻辑
### ✅ 应该的验证流程
```
用户点击导航栏阶段
↓
检查目标阶段状态
↓
【情况1】目标阶段状态 = 'pending'(未开始)
→ ❌ 禁止跳转
→ 提示:"请先完成当前阶段"
↓
【情况2】目标阶段状态 = 'active'(当前阶段)
→ ✅ 允许跳转(刷新当前页面)
↓
【情况3】目标阶段状态 = 'completed'(已完成)
→ ✅ 允许跳转(可以回顾已完成的阶段)
```
### ✅ 各阶段的完成条件
#### 订单分配阶段(order)→ 确认需求(requirements)
**必填项验证**:(`stage-order.component.ts` 第1192-1223行)
```typescript
// 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行)
```typescript
// 检查是否已分配设计师
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`
```typescript
/**
* 切换阶段(添加验证逻辑)
*/
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`
```html
[class.disabled]="getStageStatus(stage.id) === 'pending'"
(click)="getStageStatus(stage.id) !== 'pending' && switchStage(stage.id)">
```
**对应CSS**:(在 `project-detail.component.scss` 中)
```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行
```typescript
/**
* 切换阶段(添加权限验证)
* 只允许访问当前阶段或已完成的阶段
*/
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行
```html
@for (stage of stages; track stage.id) {
}
```
### 3️⃣ 添加禁用样式
**文件**:`project-detail.component.scss`
```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()` 的返回值进行权限判断
- **友好提示**:告知用户需要完成哪些必填项
- **视觉反馈**:禁用状态的阶段显示为灰色且不可点击
- **保持灵活性**:允许回顾已完成的阶段