# 项目管理 - 订单分配阶段 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
可选信息 {{ isOptionalFormExpanded ? '▼' : '▶' }}
@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 **维护人**:产品团队