# 项目管理 - 订单分配阶段 PRD
## 1. 功能概述
### 1.1 阶段定位
订单分配阶段是项目管理流程的第一个环节,主要负责将客户咨询转化为正式项目订单,并完成设计师团队的初步分配。该阶段是连接客服端和设计师端的关键桥梁。
### 1.2 核心目标
- 完成客户信息的结构化录入和同步
- 确定项目报价和付款条件
- 匹配并分配合适的设计师资源
- 建立项目基础档案,为后续环节提供数据支撑
### 1.3 涉及角色
- **客服人员**:负责创建订单、录入客户信息、初步需求沟通
- **设计师**:接收订单分配、查看项目基础信息
- **组长**:查看团队订单分配情况、协调设计师资源
## 2. 核心功能模块
### 2.1 客户信息管理
#### 2.1.1 信息展示卡片
**位置**:订单分配阶段左侧面板
**展示内容**:
```typescript
interface CustomerInfoDisplay {
// 基础信息
name: string; // 客户姓名
phone: string; // 联系电话
wechat?: string; // 微信号
customerType: string; // 客户类型:新客户/老客户/VIP客户
source: string; // 来源:小程序/官网咨询/推荐介绍
remark?: string; // 备注信息
// 状态信息
syncStatus: 'syncing' | 'synced' | 'error'; // 同步状态
lastSyncTime?: Date; // 最后同步时间
// 需求标签
demandType?: string; // 需求类型:价格敏感/质量敏感/综合要求
followUpStatus?: string; // 跟进状态:待报价/待确认需求/已失联
preferenceTags?: string[]; // 偏好标签数组
}
```
**数据来源**:
1. **客服端同步**:通过路由查询参数 `syncData` 传递客户信息
```typescript
// 客服端跳转示例
router.navigate(['/designer/project-detail', projectId], {
queryParams: {
syncData: JSON.stringify({
customerInfo: {...},
requirementInfo: {...},
preferenceTags: [...]
})
}
});
```
2. **实时同步机制**:
- 每30秒自动同步一次客户信息
- 显示同步状态指示器(同步中/已同步/同步失败)
- 支持手动触发同步
**交互特性**:
- 卡片可展开/收起(`isCustomerInfoExpanded`)
- 展开时显示完整客户信息和标签
- 收起时仅显示客户姓名和联系方式
- 同步状态实时更新,显示"刚刚/X分钟前/X小时前"
#### 2.1.2 客户搜索功能
**适用场景**:手动创建订单时快速选择已有客户
**搜索逻辑**:
```typescript
searchCustomer(): void {
// 至少输入2个字符才触发搜索
if (this.customerSearchKeyword.trim().length >= 2) {
// 模糊搜索客户姓名、手机号、微信号
this.customerSearchResults = this.customerService.search({
keyword: this.customerSearchKeyword,
fields: ['name', 'phone', 'wechat']
});
}
}
```
**搜索结果展示**:
- 下拉列表形式
- 每项显示:客户姓名、电话(脱敏)、客户类型、来源
- 点击选择后自动填充表单
### 2.2 核心需求表单
#### 2.2.1 必填项配置
**表单定义**:
```typescript
orderCreationForm = this.fb.group({
orderAmount: ['', [Validators.required, Validators.min(0)]],
smallImageDeliveryTime: ['', Validators.required],
decorationType: ['', Validators.required],
requirementReason: ['', Validators.required],
isMultiDesigner: [false]
});
```
**字段详解**:
| 字段名 | 类型 | 验证规则 | 说明 | UI组件 |
|-------|------|---------|------|--------|
| `orderAmount` | number | required, min(0) | 订单金额,单位:元 | 数字输入框,支持千分位格式化 |
| `smallImageDeliveryTime` | Date | required | 小图交付时间 | 日期选择器,限制最早日期为今天 |
| `decorationType` | string | required | 装修类型:全包/半包/清包/软装 | 下拉选择框 |
| `requirementReason` | string | required | 需求原因:新房装修/旧房改造/局部翻新 | 单选框组 |
| `isMultiDesigner` | boolean | - | 是否需要多设计师协作 | 复选框 |
**表单验证提示**:
- 实时验证:失焦时触发
- 错误提示:红色边框 + 底部错误文字
- 提交验证:点击"分配订单"按钮时调用 `markAllAsTouched()` 显示所有错误
#### 2.2.2 可选信息表单
**折叠面板设计**:
```html
@if (isOptionalFormExpanded) {
}
```
**可选字段**:
```typescript
optionalForm = this.fb.group({
largeImageDeliveryTime: [''], // 大图交付时间
spaceRequirements: [''], // 空间需求描述
designAngles: [''], // 设计角度要求
specialAreaHandling: [''], // 特殊区域处理说明
materialRequirements: [''], // 材质要求
lightingRequirements: [''] // 光照需求
});
```
### 2.3 报价明细组件
#### 2.3.1 组件集成
**组件标签**:
```html
```
**数据结构**:
```typescript
interface QuotationData {
items: Array<{
id: string;
room: string; // 空间名称:客餐厅/主卧/次卧/厨房/卫生间
amount: number; // 金额
description?: string; // 描述
}>;
totalAmount: number; // 总金额
materialCost: number; // 材料费
laborCost: number; // 人工费
designFee: number; // 设计费
managementFee: number; // 管理费
}
```
#### 2.3.2 组件功能
1. **报价项管理**:
- 添加报价项:按空间/按项目添加
- 删除报价项:确认后删除
- 编辑金额:实时更新总金额
2. **AI辅助生成**(可选功能):
```typescript
generateQuotationDetails(): void {
// 基于项目信息自动生成报价明细
const rooms = ['客餐厅', '主卧', '次卧', '厨房', '卫生间'];
this.quotationDetails = rooms.map((room, index) => ({
id: `quote_${index + 1}`,
room: room,
amount: Math.floor(Math.random() * 1000) + 300,
description: `${room}装修设计费用`
}));
this.orderAmount = this.quotationDetails.reduce((total, item) => total + item.amount, 0);
}
```
3. **金额自动汇总**:
- 实时计算总金额
- 费用分类占比显示
- 支持导出报价单(PDF/Excel)
### 2.4 设计师指派组件
#### 2.4.1 组件集成
**组件标签**:
```html
```
**数据结构**:
```typescript
interface DesignerAssignmentData {
selectedDesigners: Designer[];
teamId: string;
teamName: string;
leaderId: string;
assignmentDate: Date;
expectedStartDate: Date;
}
interface Designer {
id: string;
name: string;
avatar: string;
teamId: string;
teamName: string;
isTeamLeader: boolean;
status: 'idle' | 'busy' | 'unavailable';
currentProjects: number;
skillMatch: number; // 技能匹配度 0-100
recentOrders: number; // 近期订单数
idleDays: number; // 闲置天数
workload: number; // 工作负荷 0-100
reviewDates: string[]; // 对图评审日期列表
}
```
#### 2.4.2 设计师选择逻辑
**智能推荐算法**:
```typescript
calculateDesignerScore(designer: Designer): number {
let score = 0;
// 1. 技能匹配度(权重40%)
score += designer.skillMatch * 0.4;
// 2. 工作负荷(权重30%,负荷越低分数越高)
score += (100 - designer.workload) * 0.3;
// 3. 闲置时间(权重20%,闲置越久分数越高)
score += Math.min(designer.idleDays * 2, 100) * 0.2;
// 4. 近期接单数(权重10%,接单越少分数越高)
score += Math.max(0, 100 - designer.recentOrders * 10) * 0.1;
return score;
}
```
**设计师列表展示**:
- 卡片网格布局,每行3-4个设计师卡片
- 卡片信息:头像、姓名、团队、状态标签、技能匹配度进度条
- 状态颜色:
- `idle` 空闲 - 绿色
- `busy` 繁忙 - 橙色
- `unavailable` 不可用 - 灰色
- 点击卡片可查看设计师详细日历
#### 2.4.3 设计师日历弹窗
**触发条件**:点击设计师卡片时
**弹窗内容**:
```html
```
**日历功能**:
- 月视图展示设计师日程
- 标记对图评审日期(红点)
- 显示已分配项目时间段
- 计算下一个可用日期
- 支持按日期筛选空闲设计师
### 2.5 下单时间自动生成
**实现逻辑**:
```typescript
ngOnInit(): void {
// 自动生成下单时间
this.orderTime = new Date().toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
}
```
**显示格式**:`2025-10-16 14:30:25`
**用途**:
- 记录订单创建的准确时间
- 作为项目开始时间的参考
- 用于计算项目周期和延期预警
## 3. 数据流转
### 3.1 客服端同步流程
```mermaid
sequenceDiagram
participant CS as 客服端
participant Route as 路由
participant PD as 项目详情页
participant Form as 订单表单
CS->>Route: navigate with syncData
Route->>PD: queryParams.syncData
PD->>PD: parseJSON(syncData)
PD->>Form: patchValue(customerInfo)
PD->>PD: syncRequirementKeyInfo(requirementInfo)
PD->>PD: 更新 projectData
PD->>PD: 触发 cdr.detectChanges()
PD-->>CS: 同步完成
```
**关键代码实现**:
```typescript
// project-detail.ts lines 741-816
this.route.queryParamMap.subscribe({
next: (qp) => {
const syncDataParam = qp.get('syncData');
if (syncDataParam) {
try {
const syncData = JSON.parse(syncDataParam);
// 设置同步状态
this.isSyncingCustomerInfo = true;
// 存储订单分配数据用于显示
this.orderCreationData = syncData;
// 同步客户信息到表单
if (syncData.customerInfo) {
this.customerForm.patchValue({
name: syncData.customerInfo.name || '',
phone: syncData.customerInfo.phone || '',
wechat: syncData.customerInfo.wechat || '',
customerType: syncData.customerInfo.customerType || '新客户',
source: syncData.customerInfo.source || '小程序',
remark: syncData.customerInfo.remark || ''
});
}
// 同步需求信息
if (syncData.requirementInfo) {
this.syncRequirementKeyInfo(syncData.requirementInfo);
}
// 同步偏好标签
if (syncData.preferenceTags) {
this.project.customerTags = syncData.preferenceTags;
}
// 模拟同步完成
setTimeout(() => {
this.isSyncingCustomerInfo = false;
this.lastSyncTime = new Date();
this.cdr.detectChanges();
}, 1500);
} catch (error) {
console.error('解析同步数据失败:', error);
this.isSyncingCustomerInfo = false;
}
}
}
});
```
### 3.2 订单创建流程
```mermaid
flowchart TD
A[客服/设计师填写表单] --> B{表单验证}
B -->|验证失败| C[显示错误提示]
C --> A
B -->|验证成功| D[调用 createOrder]
D --> E[整合表单数据]
E --> F[整合报价数据]
F --> G[整合设计师分配数据]
G --> H[调用 ProjectService.createProject]
H --> I{API响应}
I -->|成功| J[显示成功提示]
J --> K[推进到需求沟通阶段]
K --> L[展开需求沟通面板]
L --> M[滚动到需求沟通区域]
I -->|失败| N[显示错误信息]
```
**关键方法实现**:
```typescript
// project-detail.ts lines 4783-4808
createOrder(): void {
if (!this.canCreateOrder()) {
// 标记所有字段为已触摸,以显示验证错误
this.orderCreationForm.markAllAsTouched();
return;
}
const orderData = {
...this.orderCreationForm.value,
...this.optionalForm.value,
customerInfo: this.orderCreationData?.customerInfo,
quotationData: this.quotationData,
designerAssignment: this.designerAssignmentData
};
console.log('分配订单:', orderData);
// 调用 ProjectService 创建项目
this.projectService.createProject(orderData).subscribe({
next: (result) => {
if (result.success) {
alert('订单分配成功!');
// 订单分配成功后自动切换到下一环节
this.advanceToNextStage('订单分配');
}
},
error: (error) => {
console.error('订单分配失败:', error);
alert('订单分配失败,请重试');
}
});
}
```
### 3.3 阶段推进机制
**推进触发条件**:
- 订单分配完成(必填项填写 + 报价确认 + 设计师分配)
- 点击"分配订单"按钮并验证通过
- 或者通过 `onProjectCreated` 事件自动推进
**推进逻辑**:
```typescript
// project-detail.ts lines 1391-1423
advanceToNextStage(afterStage: ProjectStage): void {
const idx = this.stageOrder.indexOf(afterStage);
if (idx >= 0 && idx < this.stageOrder.length - 1) {
const next = this.stageOrder[idx + 1];
// 更新项目阶段
this.updateProjectStage(next);
// 更新展开状态,折叠当前、展开下一阶段
this.expandedStages[afterStage] = false;
this.expandedStages[next] = true;
// 更新板块展开状态
const nextSection = this.getSectionKeyForStage(next);
this.expandedSection = nextSection;
// 触发变更检测以更新导航栏颜色
this.cdr.detectChanges();
}
}
// project-detail.ts lines 3038-3069
onProjectCreated(projectData: any): void {
console.log('项目创建完成:', projectData);
this.projectData = projectData;
// 团队分配已在子组件中完成并触发该事件:推进到需求沟通阶段
this.updateProjectStage('需求沟通');
// 更新项目对象的当前阶段,确保四大板块状态正确显示
if (this.project) {
this.project.currentStage = '需求沟通';
}
// 展开需求沟通阶段,收起订单分配阶段
this.expandedStages['需求沟通'] = true;
this.expandedStages['订单分配'] = false;
// 自动展开确认需求板块
this.expandedSection = 'requirements';
// 强制触发变更检测,确保UI更新
this.cdr.detectChanges();
// 延迟滚动到需求沟通阶段,确保DOM更新完成
setTimeout(() => {
this.scrollToStage('需求沟通');
this.cdr.detectChanges();
}, 100);
}
```
## 4. 权限控制
### 4.1 角色权限矩阵
| 操作 | 客服 | 设计师 | 组长 | 技术 |
|-----|------|--------|------|------|
| 查看订单分配阶段 | ✅ | ✅ | ✅ | ✅ |
| 创建订单 | ✅ | ❌ | ✅ | ❌ |
| 编辑客户信息 | ✅ | ❌ | ✅ | ❌ |
| 填写报价明细 | ✅ | ❌ | ✅ | ❌ |
| 选择设计师 | ✅ | ❌ | ✅ | ❌ |
| 接收订单通知 | ❌ | ✅ | ✅ | ❌ |
| 查看设计师日历 | ✅ | ✅ | ✅ | ❌ |
| 推进到下一阶段 | ✅ | ❌ | ✅ | ❌ |
### 4.2 权限检查方法
```typescript
// project-detail.ts lines 890-936
private detectRoleContextFromUrl(): 'customer-service' | 'designer' | 'team-leader' | 'technical' {
const url = this.router.url || '';
// 首先检查查询参数中的role
const queryParams = this.route.snapshot.queryParamMap;
const roleParam = queryParams.get('roleName');
if (roleParam === 'customer-service') {
return 'customer-service';
}
if (roleParam === 'technical') {
return 'technical';
}
// 如果没有role查询参数,则根据URL路径判断
if (url.includes('/customer-service/')) return 'customer-service';
if (url.includes('/team-leader/')) return 'team-leader';
if (url.includes('/technical/')) return 'technical';
return 'designer';
}
isDesignerView(): boolean { return this.roleContext === 'designer'; }
isTeamLeaderView(): boolean { return this.roleContext === 'team-leader'; }
isCustomerServiceView(): boolean { return this.roleContext === 'customer-service'; }
isTechnicalView(): boolean { return this.roleContext === 'technical'; }
isReadOnly(): boolean { return this.isCustomerServiceView(); }
canEditSection(sectionKey: SectionKey): boolean {
if (this.isCustomerServiceView()) {
return sectionKey === 'order' || sectionKey === 'requirements' || sectionKey === 'aftercare';
}
return true; // 设计师和组长可以编辑所有板块
}
canEditStage(stage: ProjectStage): boolean {
if (this.isCustomerServiceView()) {
const editableStages: ProjectStage[] = [
'订单分配', '需求沟通', '方案确认', // 订单分配和确认需求板块
'尾款结算', '客户评价', '投诉处理' // 售后板块
];
return editableStages.includes(stage);
}
return true; // 设计师和组长可以编辑所有阶段
}
```
### 4.3 UI权限控制
**模板权限指令**:
```html
@if (isReadOnly()) {
当前为只读模式,您可以查看项目信息但无法编辑
}
@if (canEditSection('order')) {
}
```
## 5. 关键交互设计
### 5.1 表单验证交互
**实时验证**:
```typescript
// 失焦时触发验证
// 错误信息显示
@if (orderCreationForm.get('orderAmount')?.invalid &&
orderCreationForm.get('orderAmount')?.touched) {
@if (orderCreationForm.get('orderAmount')?.errors?.['required']) {
订单金额不能为空
}
@if (orderCreationForm.get('orderAmount')?.errors?.['min']) {
订单金额不能小于0
}
}
```
**提交验证**:
```typescript
createOrder(): void {
if (!this.canCreateOrder()) {
// 标记所有字段为已触摸,显示所有验证错误
this.orderCreationForm.markAllAsTouched();
// 滚动到第一个错误字段
const firstError = document.querySelector('.error-message');
if (firstError) {
firstError.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
return;
}
// ... 继续提交逻辑
}
```
### 5.2 按钮状态控制
**分配订单按钮状态**:
```typescript
canCreateOrder(): boolean {
// 检查必填表单是否有效
const formValid = this.orderCreationForm?.valid;
// 检查是否已选择设计师
const designerAssigned = this.designerAssignmentData?.selectedDesigners?.length > 0;
// 检查是否已填写报价明细
const quotationFilled = this.quotationData?.items?.length > 0 && this.quotationData?.totalAmount > 0;
return formValid && designerAssigned && quotationFilled;
}
```
**按钮样式**:
```html
```
### 5.3 阶段推进动画
**滚动动画**:
```typescript
// project-detail.ts lines 2253-2260
scrollToStage(stage: ProjectStage): void {
const anchor = this.stageToAnchor(stage);
const el = document.getElementById(anchor);
if (el) {
el.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}
```
**展开动画**:
```scss
.stage-content {
transition: max-height 0.3s ease-in-out, opacity 0.2s ease-in-out;
&.collapsed {
max-height: 0;
opacity: 0;
overflow: hidden;
}
&.expanded {
max-height: 5000px;
opacity: 1;
}
}
```
## 6. 组件依赖关系
### 6.1 父子组件通信
```mermaid
graph TD
A[ProjectDetail 父组件] --> B[QuotationDetailsComponent]
A --> C[DesignerAssignmentComponent]
A --> D[DesignerCalendarComponent]
B -->|dataChange| A
C -->|assignmentChange| A
C -->|designerClick| A
D -->|designerSelected| A
D -->|assignmentRequested| A
```
### 6.2 Input/Output 接口
**QuotationDetailsComponent**:
```typescript
@Component({
selector: 'app-quotation-details',
...
})
export class QuotationDetailsComponent {
@Input() initialData?: QuotationData;
@Input() readonly: boolean = false;
@Output() dataChange = new EventEmitter();
// 当报价数据变化时触发
onDataChange(): void {
this.dataChange.emit(this.quotationData);
}
}
```
**DesignerAssignmentComponent**:
```typescript
@Component({
selector: 'app-designer-assignment',
...
})
export class DesignerAssignmentComponent {
@Input() projectData?: any;
@Input() readonly: boolean = false;
@Output() assignmentChange = new EventEmitter();
@Output() designerClick = new EventEmitter();
// 当设计师分配变化时触发
onAssignmentChange(): void {
this.assignmentChange.emit(this.assignmentData);
}
// 当点击设计师卡片时触发
onDesignerCardClick(designer: Designer): void {
this.designerClick.emit(designer);
}
}
```
**父组件处理**:
```typescript
// project-detail.ts lines 2843-2873
onQuotationDataChange(data: QuotationData): void {
this.quotationData = { ...data };
this.orderAmount = data.totalAmount || 0;
}
onDesignerAssignmentChange(data: DesignerAssignmentData): void {
this.designerAssignmentData = { ...data };
}
onDesignerClick(designer: AssignmentDesigner): void {
// 映射为日历组件需要的数据格式
const mapped = this.mapAssignmentDesignerToCalendar(designer);
this.calendarDesigners = [mapped];
this.calendarGroups = [{
id: designer.teamId,
name: designer.teamName,
leaderId: designer.id,
memberIds: [designer.id]
}];
this.selectedCalendarDate = new Date();
this.showDesignerCalendar = true;
}
```
## 7. API集成
### 7.1 项目创建接口
**接口地址**:`POST /api/projects`
**请求参数**:
```typescript
interface CreateProjectRequest {
// 客户信息
customerId: string;
customerName: string;
customerPhone: string;
customerWechat?: string;
customerType: string;
customerSource: string;
customerRemark?: string;
// 订单信息
orderAmount: number;
smallImageDeliveryTime: Date;
largeImageDeliveryTime?: Date;
decorationType: string;
requirementReason: string;
isMultiDesigner: boolean;
// 空间需求(可选)
spaceRequirements?: string;
designAngles?: string;
specialAreaHandling?: string;
materialRequirements?: string;
lightingRequirements?: string;
// 报价明细
quotation: {
items: Array<{
room: string;
amount: number;
description?: string;
}>;
totalAmount: number;
materialCost: number;
laborCost: number;
designFee: number;
managementFee: number;
};
// 设计师分配
assignment: {
designerIds: string[];
teamId: string;
leaderId: string;
assignmentDate: Date;
expectedStartDate: Date;
};
// 偏好标签
preferenceTags?: string[];
}
```
**响应数据**:
```typescript
interface CreateProjectResponse {
success: boolean;
message: string;
projectId: string;
project: {
id: string;
name: string;
currentStage: ProjectStage;
createdAt: Date;
assignedDesigners: string[];
};
}
```
### 7.2 设计师列表接口
**接口地址**:`GET /api/designers/available`
**查询参数**:
```typescript
interface DesignerQueryParams {
projectType?: string; // 项目类型,用于技能匹配
requiredSkills?: string[]; // 必需技能
startDate?: Date; // 项目预计开始日期
teamId?: string; // 指定团队ID
sortBy?: 'skillMatch' | 'workload' | 'idleDays'; // 排序方式
}
```
**响应数据**:
```typescript
interface DesignerListResponse {
success: boolean;
designers: Designer[];
recommendedDesignerId?: string; // AI推荐的最佳设计师
}
```
## 8. 异常处理
### 8.1 表单验证失败
```typescript
createOrder(): void {
if (!this.canCreateOrder()) {
// 1. 标记所有字段为已触摸
this.orderCreationForm.markAllAsTouched();
// 2. 收集所有错误信息
const errors: string[] = [];
Object.keys(this.orderCreationForm.controls).forEach(key => {
const control = this.orderCreationForm.get(key);
if (control?.invalid) {
errors.push(this.getFieldLabel(key));
}
});
// 3. 显示错误提示
alert(`请完善以下必填项:\n${errors.join('\n')}`);
// 4. 滚动到第一个错误字段
const firstError = document.querySelector('.error-message');
firstError?.scrollIntoView({ behavior: 'smooth', block: 'center' });
return;
}
// ... 继续提交
}
```
### 8.2 API调用失败
```typescript
this.projectService.createProject(orderData).subscribe({
next: (result) => {
if (result.success) {
alert('订单分配成功!');
this.advanceToNextStage('订单分配');
} else {
// 服务端返回失败
alert(`订单分配失败:${result.message || '未知错误'}`);
}
},
error: (error) => {
// 网络错误或服务端异常
console.error('订单分配失败:', error);
let errorMessage = '订单分配失败,请重试';
if (error.status === 400) {
errorMessage = '请求参数有误,请检查表单填写';
} else if (error.status === 401) {
errorMessage = '未登录或登录已过期,请重新登录';
} else if (error.status === 403) {
errorMessage = '没有权限执行此操作';
} else if (error.status === 500) {
errorMessage = '服务器错误,请稍后重试';
}
alert(errorMessage);
}
});
```
### 8.3 设计师资源不足
```typescript
onDesignerAssignmentChange(data: DesignerAssignmentData): void {
if (!data.selectedDesigners || data.selectedDesigners.length === 0) {
// 没有选择设计师
this.showWarning('请至少选择一位设计师');
return;
}
// 检查设计师是否可用
const unavailableDesigners = data.selectedDesigners.filter(d => d.status === 'unavailable');
if (unavailableDesigners.length > 0) {
const names = unavailableDesigners.map(d => d.name).join('、');
this.showWarning(`以下设计师当前不可用:${names}\n请重新选择`);
return;
}
// 检查工作负荷
const overloadedDesigners = data.selectedDesigners.filter(d => d.workload > 90);
if (overloadedDesigners.length > 0) {
const names = overloadedDesigners.map(d => d.name).join('、');
const confirmed = confirm(`以下设计师工作负荷较高(>90%):${names}\n确定要继续分配吗?`);
if (!confirmed) {
return;
}
}
this.designerAssignmentData = { ...data };
}
```
### 8.4 数据同步失败
```typescript
// 客服端同步数据解析失败
this.route.queryParamMap.subscribe({
next: (qp) => {
const syncDataParam = qp.get('syncData');
if (syncDataParam) {
try {
const syncData = JSON.parse(syncDataParam);
// ... 同步逻辑
} catch (error) {
console.error('解析同步数据失败:', error);
this.isSyncingCustomerInfo = false;
// 显示错误提示
alert('客户信息同步失败,请手动填写表单');
// 回退到手动模式
this.orderCreationMethod = 'manual';
}
}
}
});
```
## 9. 性能优化
### 9.1 变更检测优化
```typescript
// 使用 OnPush 策略的子组件
@Component({
selector: 'app-quotation-details',
changeDetection: ChangeDetectionStrategy.OnPush,
...
})
// 父组件手动触发变更检测
onQuotationDataChange(data: QuotationData): void {
this.quotationData = { ...data }; // 不可变更新
this.orderAmount = data.totalAmount || 0;
this.cdr.markForCheck(); // 标记需要检查
}
```
### 9.2 懒加载子组件
```typescript
// 只有展开订单分配阶段时才加载子组件
@if (expandedSection === 'order' || getSectionStatus('order') === 'active') {
}
```
### 9.3 防抖处理
```typescript
// 客户搜索防抖
private searchDebounce = new Subject();
ngOnInit(): void {
this.searchDebounce
.pipe(debounceTime(300), distinctUntilChanged())
.subscribe(keyword => {
this.performSearch(keyword);
});
}
onSearchInputChange(keyword: string): void {
this.searchDebounce.next(keyword);
}
```
## 10. 测试用例
### 10.1 单元测试
**表单验证测试**:
```typescript
describe('OrderCreationForm Validation', () => {
it('should require orderAmount', () => {
const control = component.orderCreationForm.get('orderAmount');
control?.setValue('');
expect(control?.valid).toBeFalsy();
expect(control?.errors?.['required']).toBeTruthy();
});
it('should reject negative orderAmount', () => {
const control = component.orderCreationForm.get('orderAmount');
control?.setValue(-100);
expect(control?.valid).toBeFalsy();
expect(control?.errors?.['min']).toBeTruthy();
});
it('should accept valid orderAmount', () => {
const control = component.orderCreationForm.get('orderAmount');
control?.setValue(5000);
expect(control?.valid).toBeTruthy();
});
});
```
**权限控制测试**:
```typescript
describe('Permission Control', () => {
it('should allow customer-service to edit order section', () => {
component.roleContext = 'customer-service';
expect(component.canEditSection('order')).toBeTruthy();
});
it('should not allow designer to edit order section', () => {
component.roleContext = 'designer';
expect(component.canEditSection('order')).toBeFalsy();
});
it('should allow team-leader to edit all sections', () => {
component.roleContext = 'team-leader';
expect(component.canEditSection('order')).toBeTruthy();
expect(component.canEditSection('requirements')).toBeTruthy();
expect(component.canEditSection('delivery')).toBeTruthy();
expect(component.canEditSection('aftercare')).toBeTruthy();
});
});
```
### 10.2 集成测试
**订单创建流程测试**:
```typescript
describe('Order Creation Flow', () => {
it('should complete full order creation flow', async () => {
// 1. 填写客户信息
component.customerForm.patchValue({
name: '测试客户',
phone: '13800138000',
customerType: '新客户'
});
// 2. 填写必填项
component.orderCreationForm.patchValue({
orderAmount: 10000,
smallImageDeliveryTime: new Date(),
decorationType: '全包',
requirementReason: '新房装修'
});
// 3. 添加报价明细
component.quotationData = {
items: [{ id: '1', room: '客厅', amount: 5000 }],
totalAmount: 10000,
materialCost: 6000,
laborCost: 3000,
designFee: 1000,
managementFee: 0
};
// 4. 分配设计师
component.designerAssignmentData = {
selectedDesigners: [mockDesigner],
teamId: 'team1',
teamName: '设计一组',
leaderId: 'designer1',
assignmentDate: new Date(),
expectedStartDate: new Date()
};
// 5. 验证可以提交
expect(component.canCreateOrder()).toBeTruthy();
// 6. 提交订单
spyOn(component.projectService, 'createProject').and.returnValue(
of({ success: true, projectId: 'proj-001' })
);
component.createOrder();
expect(component.projectService.createProject).toHaveBeenCalled();
});
});
```
### 10.3 E2E测试
**完整用户流程测试**:
```typescript
describe('Order Assignment E2E', () => {
it('should complete order assignment as customer-service', async () => {
// 1. 客服登录
await page.goto('/customer-service/login');
await page.fill('#username', 'cs_user');
await page.fill('#password', 'password');
await page.click('button[type="submit"]');
// 2. 创建咨询订单
await page.goto('/customer-service/consultation-order/create');
await page.fill('#customerName', '张三');
await page.fill('#customerPhone', '13800138000');
await page.click('button[text="创建订单"]');
// 3. 跳转到项目详情页
await page.waitForURL(/\/designer\/project-detail/);
// 4. 填写订单分配表单
await page.fill('#orderAmount', '10000');
await page.fill('#smallImageDeliveryTime', '2025-11-01');
await page.selectOption('#decorationType', '全包');
await page.check('#requirementReason[value="新房装修"]');
// 5. 添加报价明细
await page.click('button[text="添加报价项"]');
await page.fill('.quotation-item:last-child #room', '客厅');
await page.fill('.quotation-item:last-child #amount', '5000');
// 6. 选择设计师
await page.click('.designer-card:first-child');
// 7. 提交订单
await page.click('button[text="分配订单"]');
// 8. 验证成功跳转到需求沟通阶段
await expect(page.locator('.stage-nav-item.active')).toContainText('确认需求');
});
});
```
## 11. 附录
### 11.1 字段标签映射
```typescript
private fieldLabelMap: Record = {
'orderAmount': '订单金额',
'smallImageDeliveryTime': '小图交付时间',
'decorationType': '装修类型',
'requirementReason': '需求原因',
'isMultiDesigner': '多设计师协作',
'largeImageDeliveryTime': '大图交付时间',
'spaceRequirements': '空间需求',
'designAngles': '设计角度',
'specialAreaHandling': '特殊区域处理',
'materialRequirements': '材质要求',
'lightingRequirements': '光照需求'
};
private getFieldLabel(fieldName: string): string {
return this.fieldLabelMap[fieldName] || fieldName;
}
```
### 11.2 阶段锚点映射
```typescript
// project-detail.ts lines 2237-2251
stageToAnchor(stage: ProjectStage): string {
const map: Record = {
'订单分配': 'order',
'需求沟通': 'requirements-talk',
'方案确认': 'proposal-confirm',
'建模': 'modeling',
'软装': 'softdecor',
'渲染': 'render',
'后期': 'postprocess',
'尾款结算': 'settlement',
'客户评价': 'customer-review',
'投诉处理': 'complaint'
};
return `stage-${map[stage] || 'unknown'}`;
}
```
### 11.3 设计师状态颜色映射
```scss
.designer-card {
&.status-idle {
border-left: 4px solid #10b981; // 绿色 - 空闲
}
&.status-busy {
border-left: 4px solid #f59e0b; // 橙色 - 繁忙
}
&.status-unavailable {
border-left: 4px solid #9ca3af; // 灰色 - 不可用
opacity: 0.6;
cursor: not-allowed;
}
}
```
---
**文档版本**:v1.0.0
**创建日期**:2025-10-16
**最后更新**:2025-10-16
**维护人**:产品团队