import { Component, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ActivatedRoute, Router } from '@angular/router'; import { FormsModule, ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms'; import { ProjectService } from '../../../services/project.service'; import { PaymentVoucherRecognitionService } from '../../../services/payment-voucher-recognition.service'; import { ProjectReviewService, ReviewReportExportRequest, ReviewReportShareRequest } from '../../../services/project-review.service'; import { Project, RenderProgress, CustomerFeedback, DesignerChange, Settlement, ProjectStage, PanoramicSynthesis, ModelCheckItem } from '../../../models/project.model'; import { RequirementsConfirmCardComponent } from '../../../shared/components/requirements-confirm-card/requirements-confirm-card'; import { SettlementCardComponent } from '../../../shared/components/settlement-card/settlement-card'; import { CustomerReviewCardComponent, DetailedCustomerReview } from '../../../shared/components/customer-review-card/customer-review-card'; import { CustomerReviewFormComponent } from '../../../shared/components/customer-review-form/customer-review-form'; import { ComplaintCardComponent } from '../../../shared/components/complaint-card/complaint-card'; import { PanoramicSynthesisCardComponent } from '../../../shared/components/panoramic-synthesis-card/panoramic-synthesis-card'; import { QuotationDetailsComponent, QuotationData } from './components/quotation-details/quotation-details.component'; import { DesignerAssignmentComponent, DesignerAssignmentData, Designer as AssignmentDesigner } from './components/designer-assignment/designer-assignment.component'; // 引入客户服务模块的设计师日历组件 import { DesignerCalendarComponent, Designer as CalendarDesigner, ProjectGroup as CalendarProjectGroup } from '../../customer-service/consultation-order/components/designer-calendar/designer-calendar.component'; import { ColorAnalysisResult } from '../../../shared/services/color-analysis.service'; interface ExceptionHistory { id: string; type: 'failed' | 'stuck' | 'quality' | 'other'; description: string; submitTime: Date; status: '待处理' | '处理中' | '已解决'; response?: string; } interface ProjectMember { id: string; name: string; role: string; avatar: string; skillMatch: number; progress: number; contribution: number; } interface ProjectFile { id: string; name: string; type: string; size: string; date: string; url: string; } interface TimelineEvent { id: string; time: string; title: string; action: string; description: string; } // 新增:四大板块键类型(顶层声明,供组件与模板共同使用) type SectionKey = 'order' | 'requirements' | 'delivery' | 'aftercare'; // 素材解析后的方案数据结构 interface MaterialAnalysis { category: string; specifications: { type: string; grade: string; thickness?: string; finish?: string; durability: string; }; usage: { area: string; percentage: number; priority: 'primary' | 'secondary' | 'accent'; }; properties: { texture: string; color: string; maintenance: string; }; } interface DesignStyleAnalysis { primaryStyle: string; styleElements: { element: string; description: string; influence: number; // 影响程度 0-100 }[]; characteristics: { feature: string; value: string; importance: 'high' | 'medium' | 'low'; }[]; compatibility: { withMaterials: string[]; withColors: string[]; score: number; // 兼容性评分 0-100 }; } interface ColorSchemeAnalysis { palette: { color: string; hex: string; rgb: string; percentage: number; role: 'dominant' | 'secondary' | 'accent' | 'neutral'; }[]; harmony: { type: string; // 如:互补色、类似色、三角色等 temperature: 'warm' | 'cool' | 'neutral'; contrast: number; // 对比度 0-100 }; psychology: { mood: string; atmosphere: string; suitability: string[]; }; } interface SpaceAnalysis { dimensions: { length: number; width: number; height: number; area: number; volume: number; }; functionalZones: { zone: string; area: number; percentage: number; requirements: string[]; furniture: string[]; }[]; circulation: { mainPaths: string[]; pathWidth: number; efficiency: number; // 动线效率 0-100 }; lighting: { natural: { direction: string[]; intensity: string; duration: string; }; artificial: { zones: string[]; requirements: string[]; }; }; } // 新增:项目复盘数据结构 interface ProjectReview { id: string; projectId: string; generatedAt: Date; overallScore: number; // 项目总评分 0-100 sopAnalysis: { stageName: string; plannedDuration: number; // 计划天数 actualDuration: number; // 实际天数 score: number; // 阶段评分 0-100 executionStatus: 'excellent' | 'good' | 'average' | 'poor'; issues?: string[]; // 问题列表 }[]; keyHighlights: string[]; // 项目亮点 improvementSuggestions: string[]; // 改进建议 customerSatisfaction: { overallRating: number; // 1-5星 feedback?: string; // 客户反馈 responseTime: number; // 响应时间(小时) completionTime: number; // 完成时间(天) }; teamPerformance: { designerScore: number; communicationScore: number; timelinessScore: number; qualityScore: number; }; budgetAnalysis: { plannedBudget: number; actualBudget: number; variance: number; // 预算偏差百分比 costBreakdown: { category: string; planned: number; actual: number; }[]; }; lessonsLearned: string[]; // 经验教训 recommendations: string[]; // 后续项目建议 } interface ProposalAnalysis { id: string; name: string; version: string; createdAt: Date; status: 'analyzing' | 'completed' | 'approved' | 'rejected'; materials: MaterialAnalysis[]; designStyle: DesignStyleAnalysis; colorScheme: ColorSchemeAnalysis; spaceLayout: SpaceAnalysis; budget: { total: number; breakdown: { category: string; amount: number; percentage: number; }[]; }; timeline: { phase: string; duration: number; dependencies: string[]; }[]; feasibility: { technical: number; // 技术可行性 0-100 budget: number; // 预算可行性 0-100 timeline: number; // 时间可行性 0-100 overall: number; // 综合可行性 0-100 }; } // 交付执行板块数据结构 interface DeliverySpace { id: string; name: string; // 空间名称:卧室、餐厅、厨房等 isExpanded: boolean; // 是否展开 order: number; // 排序 } interface DeliveryProcess { id: string; name: string; // 流程名称:建模、软装、渲染、后期 type: 'modeling' | 'softDecor' | 'rendering' | 'postProcess'; spaces: DeliverySpace[]; // 该流程下的空间列表 isExpanded: boolean; // 是否展开 content: { [spaceId: string]: { // 每个空间的具体内容 images: Array<{ id: string; name: string; url: string; size?: string; reviewStatus?: 'pending' | 'approved' | 'rejected' }>; progress: number; // 进度百分比 status: 'pending' | 'in_progress' | 'completed' | 'approved'; notes: string; // 备注 lastUpdated: Date; }; }; } @Component({ selector: 'app-project-detail', standalone: true, imports: [CommonModule, FormsModule, ReactiveFormsModule, RequirementsConfirmCardComponent, SettlementCardComponent, CustomerReviewCardComponent, CustomerReviewFormComponent, ComplaintCardComponent, PanoramicSynthesisCardComponent, QuotationDetailsComponent, DesignerAssignmentComponent, DesignerCalendarComponent], templateUrl: './project-detail.html', styleUrls: ['./project-detail.scss', './debug-styles.scss', './horizontal-panel.scss'] }) export class ProjectDetail implements OnInit, OnDestroy { // 项目基本数据 projectId: string = ''; project: Project | undefined; renderProgress: RenderProgress | undefined; feedbacks: CustomerFeedback[] = []; detailedReviews: DetailedCustomerReview[] = []; designerChanges: DesignerChange[] = []; settlements: Settlement[] = []; requirementChecklist: string[] = []; reminderMessage: string = ''; isLoadingRenderProgress: boolean = false; errorLoadingRenderProgress: boolean = false; feedbackTimeoutCountdown: number = 0; private countdownInterval: any; projects: {id: string, name: string, status: string}[] = []; showDropdown: boolean = false; currentStage: string = ''; // 新增:尾款结算完成状态 isSettlementCompleted: boolean = false; isConfirmingSettlement: boolean = false; isSettlementInitiated: boolean = false; // 新增:自动化功能状态跟踪 miniprogramPaymentStatus: 'active' | 'inactive' = 'inactive'; voucherRecognitionCount: number = 0; notificationsSent: number = 0; isAutoSettling: boolean = false; isPaymentVerified: boolean = false; // 客户信息卡片展开/收起状态 isCustomerInfoExpanded: boolean = false; // 新增:订单分配表单相关 orderCreationForm!: FormGroup; optionalForm!: FormGroup; isOptionalFormExpanded: boolean = false; orderCreationData: any = null; // 新增:方案分析相关数据 proposalAnalysis: ProposalAnalysis | null = null; isAnalyzing: boolean = false; analysisProgress: number = 0; // 新增:右侧色彩分析结果展示 colorAnalysisResult: ColorAnalysisResult | null = null; dominantColorHex: string | null = null; // 新增:区分展示的参考图片与CAD文件(来源于需求确认子组件) referenceImages: any[] = []; cadFiles: any[] = []; // 新增:详细分析数据属性 enhancedColorAnalysis: any = null; formAnalysis: any = null; textureAnalysis: any = null; patternAnalysis: any = null; lightingAnalysis: any = null; materialAnalysisData: any[] = []; // 新增:9阶段顺序(串式流程)- 包含后期阶段 stageOrder: ProjectStage[] = ['订单分配', '需求沟通', '方案确认', '建模', '软装', '渲染', '后期', '尾款结算', '客户评价', '投诉处理']; // 新增:阶段展开状态(默认全部收起,当前阶段在数据加载后自动展开) expandedStages: Partial> = { '订单分配': false, '需求沟通': false, '方案确认': false, '建模': false, '软装': false, '渲染': false, '后期': false, '尾款结算': false, '客户评价': false, '投诉处理': false, }; // 新增:四大板块定义与展开状态 - 交付执行板块调整为三个阶段 sections: Array<{ key: SectionKey; label: string; stages: ProjectStage[] }> = [ { key: 'order', label: '订单分配', stages: ['订单分配'] }, { key: 'requirements', label: '确认需求', stages: ['需求沟通', '方案确认'] }, { key: 'delivery', label: '交付执行', stages: ['建模', '软装', '渲染', '后期'] }, { key: 'aftercare', label: '售后', stages: [] } ]; expandedSection: SectionKey | null = null; // 渲染异常反馈相关属性 exceptionType: 'failed' | 'stuck' | 'quality' | 'other' = 'failed'; exceptionDescription: string = ''; exceptionScreenshotUrl: string | null = null; exceptionHistories: ExceptionHistory[] = []; isSubmittingFeedback: boolean = false; selectedScreenshot: File | null = null; screenshotPreview: string | null = null; showExceptionForm: boolean = false; // 标签页相关 activeTab: 'progress' | 'members' | 'files' = 'progress'; tabs: Array<{ id: 'progress' | 'members' | 'files'; name: string }> = [ { id: 'progress', name: '项目进度' }, { id: 'members', name: '项目成员' }, { id: 'files', name: '项目文件' } ]; // 标准化阶段(视图层映射) standardPhases: Array<'待分配' | '需求方案' | '项目执行' | '收尾验收' | '归档'> = ['待分配', '需求方案', '项目执行', '收尾验收', '归档']; // 文件上传(通用) acceptedFileTypes: string = '.doc,.docx,.pdf,.jpg,.jpeg,.png,.zip,.rar,.max,.obj'; isUploadingFile: boolean = false; projectMembers: ProjectMember[] = []; // 项目文件数据 projectFiles: ProjectFile[] = []; // 团队协作时间轴 timelineEvents: TimelineEvent[] = []; // 团队分配弹窗相关 selectedDesigner: any = null; projectData: any = null; // ============ 阶段图片上传状态(新增) ============ allowedImageTypes: string = '.jpg,.jpeg,.png'; // 增加审核状态reviewStatus与是否已同步synced标记(仅由组长操作) whiteModelImages: Array<{ id: string; name: string; url: string; size?: string; reviewStatus?: 'pending' | 'approved' | 'rejected'; synced?: boolean }> = []; softDecorImages: Array<{ id: string; name: string; url: string; size?: string; reviewStatus?: 'pending' | 'approved' | 'rejected'; synced?: boolean }> = []; renderLargeImages: Array<{ id: string; name: string; url: string; size?: string; locked?: boolean; reviewStatus?: 'pending' | 'approved' | 'rejected'; synced?: boolean }> = []; postProcessImages: Array<{ id: string; name: string; url: string; size?: string; reviewStatus?: 'pending' | 'approved' | 'rejected'; synced?: boolean }> = []; showRenderUploadModal: boolean = false; pendingRenderLargeItems: Array<{ id: string; name: string; url: string; file: File }> = []; // 视图上下文:根据路由前缀识别角色视角(客服/设计师/组长) roleContext: 'customer-service' | 'designer' | 'team-leader' | 'technical' = 'designer'; // ============ 模型检查项数据 ============ modelCheckItems: ModelCheckItem[] = [ { id: 'check-1', name: '户型匹配度检查', isPassed: false, notes: '' }, { id: 'check-2', name: '尺寸精度验证', isPassed: false, notes: '' }, { id: 'check-3', name: '材质贴图检查', isPassed: false, notes: '' }, { id: 'check-4', name: '光影效果验证', isPassed: false, notes: '' }, { id: 'check-5', name: '细节完整性检查', isPassed: false, notes: '' } ]; // ============ 交付执行板块数据 ============ deliveryProcesses: DeliveryProcess[] = [ { id: 'modeling', name: '建模', type: 'modeling', isExpanded: true, // 默认展开第一个 spaces: [ { id: 'bedroom', name: '卧室', isExpanded: false, order: 1 }, { id: 'living', name: '客厅', isExpanded: false, order: 2 }, { id: 'kitchen', name: '厨房', isExpanded: false, order: 3 } ], content: { 'bedroom': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() }, 'living': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() }, 'kitchen': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() } } }, { id: 'softDecor', name: '软装', type: 'softDecor', isExpanded: false, spaces: [ { id: 'bedroom', name: '卧室', isExpanded: false, order: 1 }, { id: 'living', name: '客厅', isExpanded: false, order: 2 }, { id: 'kitchen', name: '厨房', isExpanded: false, order: 3 } ], content: { 'bedroom': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() }, 'living': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() }, 'kitchen': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() } } }, { id: 'rendering', name: '渲染', type: 'rendering', isExpanded: false, spaces: [ { id: 'bedroom', name: '卧室', isExpanded: false, order: 1 }, { id: 'living', name: '客厅', isExpanded: false, order: 2 }, { id: 'kitchen', name: '厨房', isExpanded: false, order: 3 } ], content: { 'bedroom': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() }, 'living': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() }, 'kitchen': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() } } }, { id: 'postProcess', name: '后期', type: 'postProcess', isExpanded: false, spaces: [ { id: 'bedroom', name: '卧室', isExpanded: false, order: 1 }, { id: 'living', name: '客厅', isExpanded: false, order: 2 }, { id: 'kitchen', name: '厨房', isExpanded: false, order: 3 } ], content: { 'bedroom': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() }, 'living': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() }, 'kitchen': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() } } } ]; // 新增空间输入框状态 newSpaceName: { [processId: string]: string } = {}; showAddSpaceInput: { [processId: string]: boolean } = {}; constructor( private route: ActivatedRoute, private projectService: ProjectService, private router: Router, private fb: FormBuilder, private cdr: ChangeDetectorRef, private paymentVoucherService: PaymentVoucherRecognitionService, private projectReviewService: ProjectReviewService ) {} // 切换标签页 switchTab(tabId: 'progress' | 'members' | 'files') { this.activeTab = tabId; } // 类型安全的标签页检查方法 isActiveTab(tabId: 'progress' | 'members' | 'files'): boolean { return this.activeTab === tabId; } // 根据事件类型获取作者名称 getEventAuthor(action: string): string { // 根据不同的action类型返回对应的作者名称 switch(action) { case '完成': case '更新': case '优化': return '李设计师'; case '收到': return '李客服'; case '提交': return '赵建模师'; default: return '王组长'; } } // 切换项目 switchProject(projectId: string): void { this.projectId = projectId; this.loadProjectData(); this.loadProjectMembers(); this.loadProjectFiles(); this.loadTimelineEvents(); // 更新URL但不触发组件重载 this.router.navigate([], { relativeTo: this.route, queryParamsHandling: 'merge', queryParams: { id: projectId } }); } // 检查是否处于订单分配阶段,用于红色高亮显示 isCurrentOrderCreation(): boolean { // 只有当订单分配阶段是当前活跃阶段时才标红显示 // 修复逻辑:完成前序环节后才标红当前阶段 const currentStage = this.project?.currentStage; if (!currentStage) return false; // 如果当前阶段就是订单分配阶段,说明需要标红显示 if (currentStage === '订单分配') return true; // 如果当前阶段在订单分配之后,说明订单分配已完成,不应标红 const stageOrder = [ '订单分配', '需求沟通', '方案确认', '建模', '软装', '渲染', '尾款结算', '客户评价', '投诉处理' ]; const currentIndex = stageOrder.indexOf(currentStage); const orderCreationIndex = stageOrder.indexOf('订单分配'); // 如果当前阶段在订单分配之后,说明订单分配已完成 return currentIndex <= orderCreationIndex; } // 返回工作台 backToWorkbench(): void { this.router.navigate(['/designer/dashboard']); } // 检查阶段是否已完成 isStageCompleted(stage: ProjectStage): boolean { if (!this.project) return false; // 定义阶段顺序 const stageOrder = [ '订单分配', '需求沟通', '方案确认', '建模', '软装', '渲染', '尾款结算', '客户评价', '投诉处理' ]; // 获取当前阶段和检查阶段的索引 const currentStageIndex = stageOrder.indexOf(this.project.currentStage); const checkStageIndex = stageOrder.indexOf(stage); // 如果检查阶段在当前阶段之前,则已完成 return checkStageIndex < currentStageIndex; } // 获取阶段状态:completed/active/pending getStageStatus(stage: ProjectStage): 'completed' | 'active' | 'pending' { const order = this.stageOrder; // 优先使用 currentStage 属性,如果没有则使用 project.currentStage const current = (this.currentStage as ProjectStage) || (this.project?.currentStage as ProjectStage | undefined); const currentIdx = current ? order.indexOf(current) : -1; const idx = order.indexOf(stage); if (idx === -1) return 'pending'; if (currentIdx === -1) return 'pending'; if (idx < currentIdx) return 'completed'; if (idx === currentIdx) return 'active'; return 'pending'; } // 切换阶段展开/收起,并保持单展开 toggleStage(stage: ProjectStage): void { // 已移除所有展开按钮,本方法保留以兼容模板其它引用,如无需可进一步删除调用点和方法 const exclusivelyOpen = true; if (exclusivelyOpen) { Object.keys(this.expandedStages).forEach((key) => (this.expandedStages[key as ProjectStage] = false)); this.expandedStages[stage] = true; } else { this.expandedStages[stage] = !this.expandedStages[stage]; } } // 查看阶段详情(已不再通过按钮触发,保留以兼容日志或未来调用) viewStageDetails(stage: ProjectStage): void { // 以往这里有 alert/导航行为,现清空用户交互,避免误触 return; } ngOnInit(): void { // 初始化表单 this.initializeForms(); // 初始化需求关键信息数据 this.ensureRequirementData(); // 重置方案分析状态,确保需求信息展示区域能够显示 this.resetProposalAnalysis(); // 初始化售后模块示例数据 this.initializeAftercareData(); this.route.paramMap.subscribe(params => { this.projectId = params.get('id') || ''; // 根据当前URL检测视图上下文 this.roleContext = this.detectRoleContextFromUrl(); this.loadProjectData(); this.loadExceptionHistories(); this.loadProjectMembers(); this.loadProjectFiles(); this.loadTimelineEvents(); // 启动客户信息自动同步 this.startAutoSync(); }); // 新增:监听查询参数,支持通过 activeTab 设置初始标签页和 currentStage 设置当前阶段 this.route.queryParamMap.subscribe(qp => { const raw = qp.get('activeTab'); const alias: Record = { requirements: 'progress', overview: 'progress' }; const tab = raw && (raw in alias ? alias[raw] : raw); if (tab === 'progress' || tab === 'members' || tab === 'files') { this.activeTab = tab; } // 处理 currentStage 查询参数 const currentStageParam = qp.get('currentStage'); if (currentStageParam) { this.currentStage = currentStageParam; // 根据当前阶段设置项目状态和展开相应区域 this.initializeStageFromRoute(currentStageParam as ProjectStage); // 根据当前阶段自动展开对应的区域 const sectionKey = this.getSectionKeyForStage(currentStageParam as ProjectStage); if (sectionKey) { this.expandedSection = sectionKey; } // 延迟滚动到对应阶段 setTimeout(() => { this.scrollToStage(currentStageParam as ProjectStage); }, 500); } // 处理从客服项目列表传递的同步数据 const syncDataParam = qp.get('syncData'); if (syncDataParam) { try { const syncData = JSON.parse(syncDataParam); console.log('接收到客服同步数据:', syncData); // 设置同步状态 this.isSyncingCustomerInfo = true; // 存储订单分配数据用于显示 this.orderCreationData = syncData; // 更新projectData以传递给子组件 this.projectData = { customerInfo: syncData.customerInfo, requirementInfo: syncData.requirementInfo, preferenceTags: syncData.preferenceTags }; // 同步客户信息到表单 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 || '', demandType: syncData.customerInfo.demandType || '', followUpStatus: syncData.customerInfo.followUpStatus || '待分配' }); // 设置选中的客户 this.selectedOrderCustomer = { id: syncData.customerInfo.id || 'temp-' + Date.now(), 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) { this.project.customerTags = syncData.preferenceTags.map((tag: string) => ({ source: '客服填写', needType: tag, preference: tag, colorAtmosphere: tag })); } // 模拟同步完成 setTimeout(() => { this.isSyncingCustomerInfo = false; this.lastSyncTime = new Date(); this.cdr.detectChanges(); console.log('客户信息同步完成'); }, 1500); // 触发界面更新 this.cdr.detectChanges(); console.log('客服数据同步完成,orderCreationData:', this.orderCreationData); } catch (error) { console.error('解析同步数据失败:', error); this.isSyncingCustomerInfo = false; } } }); // 添加点击事件监听器,当点击页面其他位置时关闭下拉菜单 document.addEventListener('click', this.closeDropdownOnClickOutside); // 初始化客户表单(与客服端保持一致) this.customerForm = this.fb.group({ name: ['', Validators.required], phone: ['', [Validators.required, Validators.pattern(/^1[3-9]\d{9}$/)]], wechat: [''], customerType: ['新客户'], source: [''], remark: [''], demandType: [''], followUpStatus: [''] }); // 自动生成下单时间 this.orderTime = new Date().toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }); } // 新增:根据路由参数初始化阶段状态 private initializeStageFromRoute(targetStage: ProjectStage): void { // 设置当前阶段 this.currentStage = targetStage; // 根据目标阶段设置之前的阶段为已完成 const targetIndex = this.stageOrder.indexOf(targetStage); if (targetIndex > 0) { // 将目标阶段之前的所有阶段设置为已完成 for (let i = 0; i < targetIndex; i++) { const stage = this.stageOrder[i]; this.expandedStages[stage] = false; // 已完成的阶段收起 } } // 展开当前阶段 this.expandedStages[targetStage] = true; // 如果项目对象存在,更新项目的当前阶段 if (this.project) { this.project.currentStage = targetStage; } // 触发变更检测以更新UI this.cdr.detectChanges(); } ngOnDestroy(): void { if (this.countdownInterval) { clearInterval(this.countdownInterval); } // 停止自动同步 this.stopAutoSync(); document.removeEventListener('click', this.closeDropdownOnClickOutside); // 释放所有 blob 预览 URL const revokeList: string[] = []; this.whiteModelImages.forEach(i => { if (i.url.startsWith('blob:')) revokeList.push(i.url); }); this.softDecorImages.forEach(i => { if (i.url.startsWith('blob:')) revokeList.push(i.url); }); this.renderLargeImages.forEach(i => { if (i.url.startsWith('blob:')) revokeList.push(i.url); }); this.pendingRenderLargeItems.forEach(i => { if (i.url.startsWith('blob:')) revokeList.push(i.url); }); revokeList.forEach(u => URL.revokeObjectURL(u)); } // ============ 角色视图与只读控制(新增) ============ 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; // 设计师和组长可以编辑所有阶段 } // 计算当前激活板块:优先用户点击的 expandedSection;否则取当前阶段所属板块;再否则回退首个板块 private getActiveSectionKey(): SectionKey { if (this.expandedSection) return this.expandedSection; const current = this.project?.currentStage as ProjectStage | undefined; return current ? this.getSectionKeyForStage(current) : this.sections[0].key; } // 返回当前板块的全部阶段(所有角色一致): // 设计师也可查看 订单分配/确认需求/售后 板块内容 getVisibleStages(): ProjectStage[] { const activeKey = this.getActiveSectionKey(); const sec = this.sections.find(s => s.key === activeKey); return sec ? sec.stages : []; } // ============ 组长:同步上传与审核(新增,模拟实现) ============ syncUploadedImages(phase: 'white' | 'soft' | 'render' | 'postProcess'): void { if (!this.isTeamLeaderView()) return; const markSynced = (arr: Array<{ reviewStatus?: 'pending'|'approved'|'rejected'; synced?: boolean }>) => { arr.forEach(img => { if (!img.synced) img.synced = true; if (!img.reviewStatus) img.reviewStatus = 'pending'; }); }; if (phase === 'white') markSynced(this.whiteModelImages); if (phase === 'soft') markSynced(this.softDecorImages); if (phase === 'render') markSynced(this.renderLargeImages); if (phase === 'postProcess') markSynced(this.postProcessImages); alert('已同步该阶段的图片信息(模拟)'); } reviewImage(imageId: string, phase: 'white' | 'soft' | 'render' | 'postProcess', status: 'approved' | 'rejected'): void { if (!this.isTeamLeaderView()) return; const setStatus = (arr: Array<{ id: string; reviewStatus?: 'pending'|'approved'|'rejected'; synced?: boolean }>) => { const target = arr.find(i => i.id === imageId); if (target) { target.reviewStatus = status; if (!target.synced) target.synced = true; // 审核时自动视为已同步 } }; if (phase === 'white') setStatus(this.whiteModelImages); if (phase === 'soft') setStatus(this.softDecorImages); if (phase === 'render') setStatus(this.renderLargeImages); if (phase === 'postProcess') setStatus(this.postProcessImages); } getImageReviewStatusText(img: { reviewStatus?: 'pending'|'approved'|'rejected'; synced?: boolean }): string { const synced = img.synced ? '已同步' : '未同步'; const map: Record = { 'pending': '待审', 'approved': '已通过', 'rejected': '已驳回' }; const st = img.reviewStatus ? map[img.reviewStatus] : '未标记'; return `${st} · ${synced}`; } // 点击页面其他位置时关闭下拉菜单 private closeDropdownOnClickOutside = (event: MouseEvent): void => { const targetElement = event.target as HTMLElement; const projectSwitcher = targetElement.closest('.project-switcher'); if (!projectSwitcher && this.showDropdown) { this.showDropdown = false; } }; loadProjectData(): void { if (this.projectId) { this.loadProjectDetails(); this.loadRenderProgress(); this.loadCustomerFeedbacks(); this.loadDesignerChanges(); this.loadSettlements(); this.loadRequirementChecklist(); } // 初始化项目列表数据(模拟) this.projects = [ { id: '1', name: '现代风格客厅设计', status: '进行中' }, { id: '2', name: '北欧风卧室装修', status: '已完成' }, { id: '3', name: '新中式书房改造', status: '进行中' }, { id: '4', name: '工业风餐厅设计', status: '待处理' } ]; } // 加载项目成员数据 loadProjectMembers(): void { // 模拟API请求获取项目成员数据 setTimeout(() => { this.projectMembers = [ { id: '1', name: '李设计师', role: '主设计师', avatar: '李', skillMatch: 95, progress: 65, contribution: 75 }, { id: '2', name: '陈设计师', role: '助理设计师', avatar: '陈', skillMatch: 88, progress: 80, contribution: 60 }, { id: '3', name: '王组长', role: '项目组长', avatar: '王', skillMatch: 92, progress: 70, contribution: 70 }, { id: '4', name: '赵建模师', role: '3D建模师', avatar: '赵', skillMatch: 90, progress: 90, contribution: 85 } ]; }, 600); } // 加载项目文件数据 loadProjectFiles(): void { // 模拟API请求获取项目文件数据 setTimeout(() => { this.projectFiles = [ { id: '1', name: '客厅设计方案V2.0.pdf', type: 'pdf', size: '2.5MB', date: '2024-02-10', url: '#' }, { id: '2', name: '材质库集合.rar', type: 'rar', size: '45.8MB', date: '2024-02-08', url: '#' }, { id: '3', name: '客厅渲染预览1.jpg', type: 'jpg', size: '3.2MB', date: '2024-02-14', url: '#' }, { id: '4', name: '3D模型文件.max', type: 'max', size: '87.5MB', date: '2024-02-12', url: '#' }, { id: '5', name: '客户需求文档.docx', type: 'docx', size: '1.2MB', date: '2024-01-15', url: '#' }, { id: '6', name: '客厅渲染预览2.jpg', type: 'jpg', size: '3.8MB', date: '2024-02-15', url: '#' } ]; }, 700); } // 加载团队协作时间轴数据 loadTimelineEvents(): void { // 模拟API请求获取时间轴数据 setTimeout(() => { this.timelineEvents = [ { id: '1', time: '2024-02-15 14:30', title: '渲染完成', action: '完成', description: '客厅主视角渲染已完成,等待客户确认' }, { id: '2', time: '2024-02-14 10:15', title: '材质调整', action: '更新', description: '根据客户反馈调整了沙发和窗帘材质' }, { id: '3', time: '2024-02-12 16:45', title: '模型优化', action: '优化', description: '优化了模型面数,提高渲染效率' }, { id: '4', time: '2024-02-10 09:30', title: '客户反馈', action: '收到', description: '收到客户关于颜色和储物空间的反馈意见' }, { id: '5', time: '2024-02-08 15:20', title: '模型提交', action: '提交', description: '完成3D模型搭建并提交审核' } ]; }, 800); } // 加载历史反馈记录 loadExceptionHistories(): void { this.projectService.getExceptionHistories(this.projectId).subscribe(histories => { this.exceptionHistories = histories; }); } loadProjectDetails(): void { console.log('=== loadProjectDetails 开始加载项目数据 ==='); console.log('当前项目ID:', this.projectId); this.projectService.getProjectById(this.projectId).subscribe(project => { console.log('获取到的项目数据:', project); if (!project) { console.error('未找到项目数据,项目ID:', this.projectId); // 如果找不到项目,尝试使用默认项目ID console.log('尝试使用默认项目ID: proj-001'); this.projectService.getProjectById('proj-001').subscribe(defaultProject => { console.log('默认项目数据:', defaultProject); if (defaultProject) { this.project = defaultProject; this.currentStage = defaultProject.currentStage || ''; console.log('使用默认项目,设置当前阶段:', this.currentStage); this.setupStageExpansion(defaultProject); } }); return; } this.project = project; // 设置当前阶段 if (project) { this.currentStage = project.currentStage || ''; console.log('设置当前阶段:', this.currentStage); this.setupStageExpansion(project); } // 检查技能匹配度 - 已注释掉以取消弹窗警告 // this.checkSkillMismatch(); }); } private setupStageExpansion(project: any): void { // 重置展开状态并默认展开当前阶段 this.stageOrder.forEach(s => this.expandedStages[s] = false); const currentStage = project.currentStage as ProjectStage; if (this.stageOrder.includes(currentStage)) { this.expandedStages[currentStage] = true; console.log('展开当前阶段:', currentStage); } // 新增:根据当前阶段默认展开所属板块 const currentSec = this.getSectionKeyForStage(currentStage); this.expandedSection = currentSec; console.log('展开板块:', currentSec); // 新增:如果当前阶段是建模、软装或渲染,自动展开对应的折叠面板 if (currentStage === '建模' || currentStage === '软装' || currentStage === '渲染') { const processTypeMap = { '建模': 'modeling', '软装': 'softDecor', '渲染': 'rendering' }; const processType = processTypeMap[currentStage] as 'modeling' | 'softDecor' | 'rendering'; const targetProcess = this.deliveryProcesses.find(p => p.type === processType); if (targetProcess) { // 展开对应的流程面板 targetProcess.isExpanded = true; // 展开第一个空间以便用户操作 if (targetProcess.spaces.length > 0) { targetProcess.spaces[0].isExpanded = true; // 关闭其他空间 targetProcess.spaces.slice(1).forEach(space => space.isExpanded = false); } console.log('自动展开折叠面板:', currentStage, processType); } } } // 整理项目详情 organizeProject(): void { // 模拟整理项目逻辑 alert('项目详情已整理'); } // 检查当前阶段是否显示特定卡片 shouldShowCard(cardType: string): boolean { // 改为始终显示:各阶段详情在看板下方就地展示,不再受当前阶段限制 return true; } loadRenderProgress(): void { this.isLoadingRenderProgress = true; this.errorLoadingRenderProgress = false; // 模拟API加载过程 setTimeout(() => { this.projectService.getRenderProgress(this.projectId).subscribe(progress => { this.renderProgress = progress; this.isLoadingRenderProgress = false; // 模拟API加载失败的情况 if (!progress) { this.errorLoadingRenderProgress = true; // 通知技术组长 this.notifyTeamLeader('render-failed'); } else { // 检查是否需要显示超时预警 this.checkRenderTimeout(); } }); }, 1000); } loadCustomerFeedbacks(): void { this.projectService.getCustomerFeedbacks().subscribe(feedbacks => { this.feedbacks = feedbacks.filter(f => f.projectId === this.projectId); // 为反馈添加分类标签 this.tagCustomerFeedbacks(); // 检查是否有需要处理的反馈并启动倒计时 this.checkFeedbackTimeout(); }); } loadDesignerChanges(): void { // 在实际应用中,这里应该从服务中获取设计师变更记录 // 这里使用模拟数据 this.designerChanges = [ { id: 'dc1', projectId: this.projectId, oldDesignerId: 'designer2', oldDesignerName: '设计师B', newDesignerId: 'designer1', newDesignerName: '设计师A', changeTime: new Date('2025-09-05'), acceptanceTime: new Date('2025-09-05'), historicalAchievements: ['完成初步建模', '确定色彩方案'], completedWorkload: 30 } ]; } loadSettlements(): void { this.projectService.getSettlements().subscribe(settlements => { this.settlements = settlements.filter(s => s.projectId === this.projectId); }); } loadRequirementChecklist(): void { this.projectService.generateRequirementChecklist(this.projectId).subscribe(checklist => { this.requirementChecklist = checklist; }); } updateFeedbackStatus(feedbackId: string, status: '处理中' | '已解决'): void { this.projectService.updateFeedbackStatus(feedbackId, status).subscribe(() => { this.loadCustomerFeedbacks(); // 重新加载反馈 // 清除倒计时 if (this.countdownInterval) { clearInterval(this.countdownInterval); this.feedbackTimeoutCountdown = 0; } }); } updateProjectStage(stage: ProjectStage): void { if (this.project) { this.projectService.updateProjectStage(this.projectId, stage).subscribe(() => { this.currentStage = stage; // 同步更新本地状态 this.project!.currentStage = stage; // 同步更新project对象的currentStage this.loadProjectDetails(); // 重新加载项目详情 this.cdr.detectChanges(); // 触发变更检测以更新导航栏颜色 }); } } // 新增:根据给定阶段跳转到下一阶段 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); // 更新展开状态,折叠当前、展开下一阶段,提升体验 if (this.expandedStages[afterStage] !== undefined) this.expandedStages[afterStage] = false as any; if (this.expandedStages[next] !== undefined) this.expandedStages[next] = true as any; // 更新板块展开状态 const nextSection = this.getSectionKeyForStage(next); this.expandedSection = nextSection; // 新增:自动展开对应阶段的折叠面板 if (next === '软装' || next === '渲染') { const processType = next === '软装' ? 'softDecor' : 'rendering'; const targetProcess = this.deliveryProcesses.find(p => p.type === processType); if (targetProcess) { // 展开对应的流程面板 targetProcess.isExpanded = true; // 展开第一个空间以便用户操作 if (targetProcess.spaces.length > 0) { targetProcess.spaces[0].isExpanded = true; // 关闭其他空间 targetProcess.spaces.slice(1).forEach(space => space.isExpanded = false); } } } // 触发变更检测以更新导航栏颜色 this.cdr.detectChanges(); } } generateReminderMessage(): void { this.projectService.generateReminderMessage('stagnation').subscribe(message => { this.reminderMessage = message; // 3秒后自动清除提醒 setTimeout(() => { this.reminderMessage = ''; }, 3000); }); } // ============ 新增:标准化阶段映射与紧急程度 ============ // 计算距离截止日期的天数(向下取整) getDaysToDeadline(): number | null { if (!this.project?.deadline) return null; const now = new Date(); const deadline = new Date(this.project.deadline); const diffMs = deadline.getTime() - now.getTime(); return Math.floor(diffMs / (1000 * 60 * 60 * 24)); } // 是否延期/临期/提示 getUrgencyBadge(): 'overdue' | 'due_3' | 'due_7' | null { const d = this.getDaysToDeadline(); if (d === null) return null; if (d < 0) return 'overdue'; if (d <= 3) return 'due_3'; if (d <= 7) return 'due_7'; return null; } // 是否存在不满意或待处理投诉/反馈 hasPendingComplaint(): boolean { return this.feedbacks.some(f => !f.isSatisfied || f.status === '待处理'); } // 将现有细分阶段映射为标准化阶段 mapToStandardPhase(stage: ProjectStage): '待分配' | '需求方案' | '项目执行' | '收尾验收' | '归档' { const mapping: Record = { '订单分配': '待分配', '需求沟通': '需求方案', '方案确认': '需求方案', '建模': '项目执行', '软装': '项目执行', '渲染': '项目执行', '后期': '项目执行', '尾款结算': '收尾验收', '客户评价': '收尾验收', '投诉处理': '收尾验收' }; return mapping[stage] ?? '待分配'; } getStandardPhaseIndex(): number { if (!this.project?.currentStage) return 0; const phase = this.mapToStandardPhase(this.project.currentStage); return this.standardPhases.indexOf(phase); } isStandardPhaseCompleted(phase: '待分配' | '需求方案' | '项目执行' | '收尾验收' | '归档'): boolean { return this.standardPhases.indexOf(phase) < this.getStandardPhaseIndex(); } isStandardPhaseCurrent(phase: '待分配' | '需求方案' | '项目执行' | '收尾验收' | '归档'): boolean { return this.standardPhases.indexOf(phase) === this.getStandardPhaseIndex(); } // ============ 新增:项目报告导出 ============ exportProjectReport(): void { if (!this.project) return; const lines: string[] = []; const d = this.getDaysToDeadline(); lines.push(`项目名称: ${this.project.name}`); lines.push(`当前阶段(细分): ${this.project.currentStage}`); lines.push(`当前阶段(标准化): ${this.mapToStandardPhase(this.project.currentStage)}`); if (this.project.deadline) { lines.push(`截止日期: ${this.formatDate(this.project.deadline)}`); lines.push(`剩余天数: ${d !== null ? d : '-'}天`); } lines.push(`技能需求: ${(this.project.skillsRequired || []).join('、')}`); lines.push('—— 渲染进度 ——'); lines.push(this.renderProgress ? `状态: ${this.renderProgress.status}, 完成度: ${this.renderProgress.completionRate}%` : '无渲染进度数据'); lines.push('—— 客户反馈 ——'); lines.push(this.feedbacks.length ? `${this.feedbacks.length} 条` : '暂无'); lines.push('—— 设计师变更 ——'); lines.push(this.designerChanges.length ? `${this.designerChanges.length} 条` : '暂无'); lines.push('—— 交付文件 ——'); lines.push(this.projectFiles.length ? this.projectFiles.map(f => `• ${f.name} (${f.type}, ${f.size})`).join('\n') : '暂无'); const content = lines.join('\n'); const blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${this.project.name || '项目'}-阶段报告.txt`; a.click(); URL.revokeObjectURL(url); } // ============ 新增:通用文件上传(含4K图片校验) ============ async onGeneralFilesSelected(event: Event): Promise { const input = event.target as HTMLInputElement; if (!input.files || input.files.length === 0) return; const files = Array.from(input.files); this.isUploadingFile = true; for (const file of files) { // 对图片进行4K校验(最大边 >= 4000px) if (/\.(jpg|jpeg|png)$/i.test(file.name)) { const ok = await this.validateImage4K(file).catch(() => false); if (!ok) { alert(`图片不符合4K标准(最大边需≥4000像素):${file.name}`); continue; } } // 简化:直接追加到本地列表(实际应上传到服务器) const fakeType = (file.name.split('.').pop() || '').toLowerCase(); const sizeMB = (file.size / (1024 * 1024)).toFixed(1) + 'MB'; const nowStr = this.formatDate(new Date()); this.projectFiles.unshift({ id: `file-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, name: file.name, type: fakeType, size: sizeMB, date: nowStr, url: '#' }); } this.isUploadingFile = false; // 清空选择 input.value = ''; } validateImage4K(file: File): Promise { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => { const img = new Image(); img.onload = () => { const maxSide = Math.max(img.width, img.height); resolve(maxSide >= 4000); }; img.onerror = () => reject('image load error'); img.src = reader.result as string; }; reader.onerror = () => reject('read error'); reader.readAsDataURL(file); }); } // 可选:列表 trackBy,优化渲染 trackById(_: number, item: { id: string }): string { return item.id; } retryLoadRenderProgress(): void { this.loadRenderProgress(); } // 检查是否所有模型检查项都已通过 // 获取技能匹配度警告 getSkillMismatchWarning(): string | null { if (!this.project) return null; // 模拟技能匹配度检查 const designerSkills = ['现代风格', '硬装']; const requiredSkills = this.project.skillsRequired; const mismatchedSkills = requiredSkills.filter(skill => !designerSkills.includes(skill)); if (mismatchedSkills.length > 0) { return `警告:您不擅长${mismatchedSkills.join('、')},建议联系组长协调`; } return null; } // 检查渲染是否超时 checkRenderTimeout(): void { if (!this.renderProgress || !this.project) return; // 模拟交付前3小时预警 const deliveryTime = new Date(this.project.deadline); const currentTime = new Date(); const timeDifference = deliveryTime.getTime() - currentTime.getTime(); const hoursRemaining = Math.floor(timeDifference / (1000 * 60 * 60)); if (hoursRemaining <= 3 && hoursRemaining > 0) { // 弹窗预警 alert('渲染进度预警:交付前3小时,请关注渲染进度'); } if (hoursRemaining <= 1 && hoursRemaining > 0) { // 更严重的预警 alert('渲染进度严重预警:交付前1小时,渲染可能无法按时完成!'); } } // 为客户反馈添加分类标签 tagCustomerFeedbacks(): void { this.feedbacks.forEach(feedback => { // 添加分类标签 if (feedback.content.includes('色彩') || feedback.problemLocation?.includes('色彩')) { (feedback as any).tag = '色彩问题'; } else if (feedback.content.includes('家具') || feedback.problemLocation?.includes('家具')) { (feedback as any).tag = '家具款式问题'; } else if (feedback.content.includes('光线') || feedback.content.includes('照明')) { (feedback as any).tag = '光线问题'; } else { (feedback as any).tag = '其他问题'; } }); } // 获取反馈标签的辅助方法 getFeedbackTag(feedback: CustomerFeedback): string { return (feedback as any).tag || ''; } // 检查反馈超时 checkFeedbackTimeout(): void { const pendingFeedbacks = this.feedbacks.filter(f => f.status === '待处理'); if (pendingFeedbacks.length > 0) { // 启动1小时倒计时 this.feedbackTimeoutCountdown = 3600; // 3600秒 = 1小时 this.startCountdown(); } } // 启动倒计时 startCountdown(): void { this.countdownInterval = setInterval(() => { if (this.feedbackTimeoutCountdown > 0) { this.feedbackTimeoutCountdown--; } else { clearInterval(this.countdownInterval); // 超时提醒 alert('客户反馈已超过1小时未响应,请立即处理!'); this.notifyTeamLeader('feedback-overdue'); } }, 1000); } // 通知技术组长 notifyTeamLeader(type: 'render-failed' | 'feedback-overdue' | 'skill-mismatch'): void { // 实际应用中应调用消息服务通知组长 console.log(`通知技术组长:${type} - 项目ID: ${this.projectId}`); } // 检查技能匹配度并提示 checkSkillMismatch(): void { const warning = this.getSkillMismatchWarning(); if (warning) { // 显示技能不匹配警告 if (confirm(`${warning}\n是否联系技术组长协调支持?`)) { this.notifyTeamLeader('skill-mismatch'); } } } // 发起设计师变更 initiateDesignerChange(reason: string): void { // 实际应用中应调用API发起变更 console.log(`发起设计师变更,原因:${reason}`); alert('已发起设计师变更申请,请等待新设计师承接'); } // 确认承接变更项目 acceptDesignerChange(changeId: string): void { // 实际应用中应调用API确认承接 console.log(`确认承接设计师变更:${changeId}`); alert('已确认承接项目,系统已记录时间戳和责任人'); } // 格式化倒计时显示 formatCountdown(seconds: number): string { const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const remainingSeconds = seconds % 60; return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`; } // 提交异常反馈 submitExceptionFeedback(): void { if (!this.exceptionDescription.trim() || this.isSubmittingFeedback) { alert('请填写异常类型和描述'); return; } this.isSubmittingFeedback = true; // 模拟提交反馈到服务器 setTimeout(() => { const newException: ExceptionHistory = { id: `exception-${Date.now()}`, type: this.exceptionType, description: this.exceptionDescription, submitTime: new Date(), status: '待处理' }; // 添加到历史记录中 this.exceptionHistories.unshift(newException); // 通知客服和技术支持 this.notifyTechnicalSupport(newException); // 清空表单 this.exceptionDescription = ''; this.clearExceptionScreenshot(); this.showExceptionForm = false; // 显示成功消息 alert('异常反馈已提交,技术支持将尽快处理'); this.isSubmittingFeedback = false; }, 1000); } // 上传异常截图 uploadExceptionScreenshot(event: Event): void { const input = event.target as HTMLInputElement; if (input.files && input.files[0]) { const file = input.files[0]; // 在实际应用中,这里应该上传文件到服务器 // 这里我们使用FileReader来生成一个预览URL const reader = new FileReader(); reader.onload = (e) => { this.exceptionScreenshotUrl = e.target?.result as string; }; reader.readAsDataURL(file); } } // 清除异常截图 clearExceptionScreenshot(): void { this.exceptionScreenshotUrl = null; const input = document.getElementById('screenshot-upload') as HTMLInputElement; if (input) { input.value = ''; } } // 联系组长 contactTeamLeader() { alert(`已联系${this.project?.assigneeName || '项目组长'}`); } // 处理渲染超时预警 handleRenderTimeout() { alert('已发送渲染超时预警通知'); } // 通知技术支持 notifyTechnicalSupport(exception: ExceptionHistory): void { // 实际应用中应调用消息服务通知技术支持和客服 console.log(`通知技术支持和客服:渲染异常 - 项目ID: ${this.projectId}`); console.log(`异常类型: ${this.getExceptionTypeText(exception.type)}, 描述: ${exception.description}`); } // 获取异常类型文本 getExceptionTypeText(type: string): string { const typeMap: Record = { 'failed': '渲染失败', 'stuck': '渲染卡顿', 'quality': '渲染质量问题', 'other': '其他问题' }; return typeMap[type] || type; } // 格式化日期 formatDate(date: Date | string): string { const d = typeof date === 'string' ? new Date(date) : date; const year = d.getFullYear(); const month = String(d.getMonth() + 1).padStart(2, '0'); const day = String(d.getDate()).padStart(2, '0'); const hours = String(d.getHours()).padStart(2, '0'); const minutes = String(d.getMinutes()).padStart(2, '0'); return `${year}-${month}-${day} ${hours}:${minutes}`; } // 将字节格式化为易读尺寸 private formatFileSize(bytes: number): string { if (bytes < 1024) return `${bytes}B`; const kb = bytes / 1024; if (kb < 1024) return `${kb.toFixed(1)}KB`; const mb = kb / 1024; if (mb < 1024) return `${mb.toFixed(1)}MB`; const gb = mb / 1024; return `${gb.toFixed(2)}GB`; } // 生成缩略图条目(并创建本地预览URL) private makeImageItem(file: File): { id: string; name: string; url: string; size: string } { const id = `img-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; const url = URL.createObjectURL(file); return { id, name: file.name, url, size: this.formatFileSize(file.size) }; } // 释放对象URL private revokeUrl(url: string): void { try { if (url && url.startsWith('blob:')) URL.revokeObjectURL(url); } catch {} } // =========== 建模阶段:白模上传 =========== onWhiteModelSelected(event: Event): void { const input = event.target as HTMLInputElement; if (!input.files || input.files.length === 0) return; const files = Array.from(input.files).filter(f => /\.(jpg|jpeg|png)$/i.test(f.name)); const items = files.map(f => this.makeImageItem(f)); this.whiteModelImages.unshift(...items); input.value = ''; } removeWhiteModelImage(id: string): void { const target = this.whiteModelImages.find(i => i.id === id); if (target) this.revokeUrl(target.url); this.whiteModelImages = this.whiteModelImages.filter(i => i.id !== id); } // 新增:建模阶段 确认上传并自动进入下一阶段(软装) confirmWhiteModelUpload(): void { // 检查建模阶段的图片数据 const modelingProcess = this.deliveryProcesses.find(p => p.id === 'modeling'); if (!modelingProcess) return; // 检查是否有任何空间上传了图片 const hasImages = modelingProcess.spaces.some(space => { const content = modelingProcess.content[space.id]; return content && content.images && content.images.length > 0; }); if (!hasImages) return; this.advanceToNextStage('建模'); } // =========== 软装阶段:小图上传(建议≤1MB,不强制) =========== onSoftDecorSmallPicsSelected(event: Event): void { const input = event.target as HTMLInputElement; if (!input.files || input.files.length === 0) return; const files = Array.from(input.files).filter(f => /\.(jpg|jpeg|png)$/i.test(f.name)); const warnOversize = files.filter(f => f.size > 1024 * 1024); if (warnOversize.length > 0) { // 仅提示,不阻断 console.warn('软装小图建议≤1MB,以下文件较大:', warnOversize.map(f => f.name)); } const items = files.map(f => this.makeImageItem(f)); this.softDecorImages.unshift(...items); input.value = ''; } // 拖拽上传相关属性 isDragOver: boolean = false; // 图片预览相关属性 showImagePreview: boolean = false; previewImageData: any = null; // 图片预览方法(含渲染大图加锁校验) previewImage(img: any): void { const isRenderLarge = !!this.renderLargeImages.find(i => i.id === img?.id); if (isRenderLarge && img?.locked) { alert('该渲染大图已加锁,需完成尾款结算并上传/识别支付凭证后方可预览。'); return; } this.previewImageData = img; this.showImagePreview = true; } closeImagePreview(): void { this.showImagePreview = false; this.previewImageData = null; } downloadImage(img: any): void { const isRenderLarge = !!this.renderLargeImages.find(i => i.id === img?.id); if (isRenderLarge && img?.locked) { alert('该渲染大图已加锁,需完成尾款结算并上传/识别支付凭证后方可下载。'); return; } if (img) { const link = document.createElement('a'); link.href = img.url; link.download = img.name; link.click(); } } removeImageFromPreview(): void { if (this.previewImageData) { // 首先检查新的 deliveryProcesses 结构 let imageFound = false; for (const process of this.deliveryProcesses) { for (const space of process.spaces) { if (process.content[space.id]?.images) { const imageIndex = process.content[space.id].images.findIndex(img => img.id === this.previewImageData.id); if (imageIndex > -1) { this.removeSpaceImage(process.id, space.id, this.previewImageData.id); imageFound = true; break; } } } if (imageFound) break; } // 如果在新结构中没找到,检查旧的图片数组 if (!imageFound) { if (this.whiteModelImages.find(i => i.id === this.previewImageData.id)) { this.removeWhiteModelImage(this.previewImageData.id); } else if (this.softDecorImages.find(i => i.id === this.previewImageData.id)) { this.removeSoftDecorImage(this.previewImageData.id); } else if (this.renderLargeImages.find(i => i.id === this.previewImageData.id)) { this.removeRenderLargeImage(this.previewImageData.id); } else if (this.postProcessImages.find(i => i.id === this.previewImageData.id)) { this.removePostProcessImage(this.previewImageData.id); } } this.closeImagePreview(); } } // 拖拽事件处理 onDragOver(event: DragEvent): void { event.preventDefault(); event.stopPropagation(); this.isDragOver = true; } onDragLeave(event: DragEvent): void { event.preventDefault(); event.stopPropagation(); this.isDragOver = false; } onFileDrop(event: DragEvent, type: 'whiteModel' | 'softDecor' | 'render' | 'postProcess'): void { event.preventDefault(); event.stopPropagation(); this.isDragOver = false; const files = event.dataTransfer?.files; if (!files || files.length === 0) return; // 创建模拟的input事件 const mockEvent = { target: { files: files } } as any; // 根据类型调用相应的处理方法 switch (type) { case 'whiteModel': this.onWhiteModelSelected(mockEvent); break; case 'softDecor': this.onSoftDecorSmallPicsSelected(mockEvent); break; case 'render': this.onRenderLargePicsSelected(mockEvent); break; case 'postProcess': this.onPostProcessPicsSelected(mockEvent); break; } } // 触发文件输入框 triggerFileInput(type: 'whiteModel' | 'softDecor' | 'render' | 'postProcess'): void { let inputId: string; switch (type) { case 'whiteModel': inputId = 'whiteModelFileInput'; break; case 'softDecor': inputId = 'softDecorFileInput'; break; case 'render': inputId = 'renderFileInput'; break; case 'postProcess': inputId = 'postProcessFileInput'; break; } const input = document.querySelector(`#${inputId}`) as HTMLInputElement; if (input) { input.click(); } } removeSoftDecorImage(id: string): void { const target = this.softDecorImages.find(i => i.id === id); if (target) this.revokeUrl(target.url); this.softDecorImages = this.softDecorImages.filter(i => i.id !== id); } // 新增:后期阶段图片上传处理 async onPostProcessPicsSelected(event: Event): Promise { const input = event.target as HTMLInputElement; if (!input.files || input.files.length === 0) return; const files = Array.from(input.files).filter(f => /\.(jpg|jpeg|png)$/i.test(f.name)); for (const f of files) { const item = this.makeImageItem(f); this.postProcessImages.unshift({ id: item.id, name: item.name, url: item.url, size: this.formatFileSize(f.size) }); } input.value = ''; } removePostProcessImage(id: string): void { const target = this.postProcessImages.find(i => i.id === id); if (target) this.revokeUrl(target.url); this.postProcessImages = this.postProcessImages.filter(i => i.id !== id); } // 新增:后期阶段 确认上传并自动进入下一阶段(尾款结算) confirmPostProcessUpload(): void { // 检查后期阶段的图片数据 const postProcessProcess = this.deliveryProcesses.find(p => p.id === 'post-processing'); if (!postProcessProcess) return; // 检查是否有任何空间上传了图片 const hasImages = postProcessProcess.spaces.some(space => { const content = postProcessProcess.content[space.id]; return content && content.images && content.images.length > 0; }); if (!hasImages) return; this.advanceToNextStage('后期'); } // 新增:尾款结算阶段确认并自动进入下一阶段(客户评价) confirmSettlement(): void { if (this.isConfirmingSettlement) return; this.isConfirmingSettlement = true; // 模拟API调用延迟 setTimeout(() => { this.isSettlementCompleted = true; this.isConfirmingSettlement = false; // 显示成功提示 alert('尾款结算已确认完成!'); // 进入下一阶段 this.advanceToNextStage('尾款结算'); }, 1500); } // 新增:投诉处理阶段确认并完成项目(基础版本,详细版本在售后模块中) confirmComplaintBasic(): void { console.log('确认投诉处理完成'); // 可以在这里添加更多逻辑,比如标记项目完成等 // 调用服务更新后端数据 // this.projectService.confirmComplaintResolution(this.projectId); this.advanceToNextStage('投诉处理'); } // 新增:软装阶段 确认上传并自动进入下一阶段(渲染) confirmSoftDecorUpload(): void { // 检查软装阶段的图片数据 const softDecorProcess = this.deliveryProcesses.find(p => p.id === 'soft-decoration'); if (!softDecorProcess) return; // 检查是否有任何空间上传了图片 const hasImages = softDecorProcess.spaces.some(space => { const content = softDecorProcess.content[space.id]; return content && content.images && content.images.length > 0; }); if (!hasImages) return; this.advanceToNextStage('软装'); } // 新增:渲染阶段 确认上传并自动进入下一阶段(后期) confirmRenderUpload(): void { // 检查渲染阶段的图片数据 const renderProcess = this.deliveryProcesses.find(p => p.id === 'rendering'); if (!renderProcess) return; // 检查是否有任何空间上传了图片 const hasImages = renderProcess.spaces.some(space => { const content = renderProcess.content[space.id]; return content && content.images && content.images.length > 0; }); if (!hasImages) return; this.advanceToNextStage('渲染'); } // =========== 渲染阶段:大图上传(弹窗 + 4K校验) =========== openRenderUploadModal(): void { this.showRenderUploadModal = true; this.pendingRenderLargeItems = []; } closeRenderUploadModal(): void { // 关闭时释放临时预览URL this.pendingRenderLargeItems.forEach(i => this.revokeUrl(i.url)); this.pendingRenderLargeItems = []; this.showRenderUploadModal = false; } async onRenderLargePicsSelected(event: Event): Promise { const input = event.target as HTMLInputElement; if (!input.files || input.files.length === 0) return; const files = Array.from(input.files).filter(f => /\.(jpg|jpeg|png)$/i.test(f.name)); for (const f of files) { const ok = await this.validateImage4K(f).catch(() => false); if (!ok) { alert(`图片不符合4K标准(最大边需≥4000像素):${f.name}`); continue; } const item = this.makeImageItem(f); // 直接添加到正式列表,不再使用待确认列表 this.renderLargeImages.unshift({ id: item.id, name: item.name, url: item.url, size: this.formatFileSize(f.size), locked: true }); } input.value = ''; } removeRenderLargeImage(id: string): void { const target = this.renderLargeImages.find(i => i.id === id); if (target) this.revokeUrl(target.url); this.renderLargeImages = this.renderLargeImages.filter(i => i.id !== id); } // 根据阶段映射所属板块 getSectionKeyForStage(stage: ProjectStage): SectionKey { switch (stage) { case '订单分配': return 'order'; case '需求沟通': case '方案确认': return 'requirements'; case '建模': case '软装': case '渲染': case '后期': return 'delivery'; case '尾款结算': case '客户评价': case '投诉处理': return 'aftercare'; default: return 'order'; } } // 获取板块状态:completed | 'active' | 'pending' getSectionStatus(key: SectionKey): 'completed' | 'active' | 'pending' { // 优先使用本地的currentStage,如果没有则使用project.currentStage const current = (this.currentStage || this.project?.currentStage) as ProjectStage | undefined; // 如果没有当前阶段(新创建的项目),默认订单分配板块为active(红色) if (!current || current === '订单分配') { return key === 'order' ? 'active' : 'pending'; } // 获取当前阶段所属的板块 const currentSection = this.getSectionKeyForStage(current); const sectionOrder = this.sections.map(s => s.key); const currentIdx = sectionOrder.indexOf(currentSection); const idx = sectionOrder.indexOf(key); if (idx === -1 || currentIdx === -1) return 'pending'; // 已完成的板块:当前阶段所在板块之前的所有板块 if (idx < currentIdx) return 'completed'; // 当前进行中的板块:当前阶段所在的板块 if (idx === currentIdx) return 'active'; // 未开始的板块:当前阶段所在板块之后的所有板块 return 'pending'; } // 切换四大板块(单展开) toggleSection(key: SectionKey): void { this.expandedSection = key; // 点击板块按钮时,滚动到该板块的第一个可见阶段卡片 const sec = this.sections.find(s => s.key === key); if (sec) { // 设计师仅滚动到可见的三大执行阶段,否则取该板块第一个阶段 const candidate = this.isDesignerView() ? sec.stages.find(st => ['建模', '软装', '渲染'].includes(st)) || sec.stages[0] : sec.stages[0]; this.scrollToStage(candidate); } } // 阶段到锚点的映射 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'}`; } // 平滑滚动到指定阶段卡片 scrollToStage(stage: ProjectStage): void { const anchor = this.stageToAnchor(stage); const el = document.getElementById(anchor); if (el) { el.scrollIntoView({ behavior: 'smooth', block: 'start' }); } } // 订单分配阶段:客户信息(迁移自客服端"客户信息"卡片) orderCreationMethod: 'miniprogram' | 'manual' = 'miniprogram'; isSyncing: boolean = false; orderTime: string = ''; // 客户信息实时同步相关变量 isSyncingCustomerInfo: boolean = false; lastSyncTime: Date | null = null; syncInterval: any = null; customerForm!: FormGroup; customerSearchKeyword: string = ''; customerSearchResults: Array<{ id: string; name: string; phone: string; wechat?: string; avatar?: string; customerType?: string; source?: string; remark?: string }> = []; selectedOrderCustomer: { id: string; name: string; phone: string; wechat?: string; avatar?: string; customerType?: string; source?: string; remark?: string } | null = null; demandTypes = [ { value: 'price', label: '价格敏感' }, { value: 'quality', label: '质量敏感' }, { value: 'comprehensive', label: '综合要求' } ]; followUpStatus = [ { value: 'quotation', label: '待报价' }, { value: 'confirm', label: '待确认需求' }, { value: 'lost', label: '已失联' } ]; // 需求关键信息同步数据 requirementKeyInfo = { colorAtmosphere: { description: '', mainColor: '', colorTemp: '', materials: [] as string[] }, spaceStructure: { lineRatio: 0, blankRatio: 0, flowWidth: 0, aspectRatio: 0, ceilingHeight: 0 }, materialWeights: { fabricRatio: 0, woodRatio: 0, metalRatio: 0, smoothness: 0, glossiness: 0 }, presetAtmosphere: { name: '', rgb: '', colorTemp: '', materials: [] as string[] } }; // 客户信息:搜索/选择/清空/同步/快速填写 逻辑 searchCustomer(): void { if (this.customerSearchKeyword.trim().length >= 2) { this.customerSearchResults = [ { id: '1', name: '张先生', phone: '138****5678', customerType: '老客户', source: '官网咨询', avatar: "data:image/svg+xml,%3Csvg width='64' height='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='100%25' height='100%25' fill='%23E6E6E6'/%3E%3Ctext x='50%25' y='50%25' font-family='Arial' font-size='13.333333333333334' font-weight='bold' text-anchor='middle' fill='%23555555' dy='0.3em'%3EIMG%3C/text%3E%3C/svg%3E" }, { id: '2', name: '李女士', phone: '139****1234', customerType: 'VIP客户', source: '推荐介绍', avatar: "data:image/svg+xml,%3Csvg width='65' height='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='100%25' height='100%25' fill='%23DCDCDC'/%3E%3Ctext x='50%25' y='50%25' font-family='Arial' font-size='13.333333333333334' font-weight='bold' text-anchor='middle' fill='%23555555' dy='0.3em'%3EIMG%3C/text%3E%3C/svg%3E" } ]; } else { this.customerSearchResults = []; } } selectCustomer(customer: { id: string; name: string; phone: string; wechat?: string; avatar?: string; customerType?: string; source?: string; remark?: string }): void { this.selectedOrderCustomer = customer; this.customerForm.patchValue({ name: customer.name, phone: customer.phone, wechat: customer.wechat || '', customerType: customer.customerType || '新客户', source: customer.source || '', remark: customer.remark || '' }); this.customerSearchResults = []; this.customerSearchKeyword = ''; } clearSelectedCustomer(): void { this.selectedOrderCustomer = null; this.customerForm.reset({ customerType: '新客户' }); } quickFillCustomerInfo(keyword: string): void { const k = (keyword || '').trim(); if (!k) return; // 模拟:若有搜索结果,选择第一条 if (this.customerSearchResults.length === 0) this.searchCustomer(); if (this.customerSearchResults.length > 0) { this.selectCustomer(this.customerSearchResults[0]); } } syncMiniprogramCustomerInfo(): void { if (this.isSyncing) return; this.isSyncing = true; setTimeout(() => { // 模拟从小程序同步到客户表单 this.customerForm.patchValue({ name: '小程序用户', phone: '13800001234', wechat: 'wx_user_001', customerType: '新客户', source: '小程序下单' }); this.isSyncing = false; // 触发客户信息同步显示 this.syncCustomerInfoDisplay(); }, 1000); } // 同步客户信息显示 syncCustomerInfoDisplay(): void { this.isSyncingCustomerInfo = true; // 模拟同步过程 setTimeout(() => { this.lastSyncTime = new Date(); this.isSyncingCustomerInfo = false; // 触发界面更新 this.cdr.detectChanges(); console.log('客户信息显示已同步:', this.lastSyncTime); }, 800); } // 启动自动同步 startAutoSync(): void { if (this.syncInterval) { clearInterval(this.syncInterval); } // 每30秒自动同步一次 this.syncInterval = setInterval(() => { this.syncCustomerInfoDisplay(); }, 30000); } // 停止自动同步 stopAutoSync(): void { if (this.syncInterval) { clearInterval(this.syncInterval); this.syncInterval = null; } } // 格式化时间显示 formatTime(date: Date): string { const now = new Date(); const diff = now.getTime() - date.getTime(); const minutes = Math.floor(diff / 60000); if (minutes < 1) { return '刚刚'; } else if (minutes < 60) { return `${minutes}分钟前`; } else { const hours = Math.floor(minutes / 60); return `${hours}小时前`; } } downloadFile(file: ProjectFile): void { // 实现文件下载逻辑 const link = document.createElement('a'); link.href = file.url; link.download = file.name; link.click(); } previewFile(file: ProjectFile): void { // 预览文件逻辑 console.log('预览文件:', file.name); } // 同步需求关键信息到客户信息卡片 syncRequirementKeyInfo(requirementData: any): void { if (requirementData) { // 同步色彩氛围信息 if (requirementData.colorIndicators) { this.requirementKeyInfo.colorAtmosphere = { description: requirementData.colorIndicators.colorRange || '', mainColor: `rgb(${requirementData.colorIndicators.mainColor?.r || 0}, ${requirementData.colorIndicators.mainColor?.g || 0}, ${requirementData.colorIndicators.mainColor?.b || 0})`, colorTemp: `${requirementData.colorIndicators.colorTemperature || 0}K`, materials: [] }; } // 同步空间结构信息 if (requirementData.spaceIndicators) { this.requirementKeyInfo.spaceStructure = { lineRatio: requirementData.spaceIndicators.lineRatio || 0, blankRatio: requirementData.spaceIndicators.blankRatio || 0, flowWidth: requirementData.spaceIndicators.flowWidth || 0, aspectRatio: requirementData.spaceIndicators.aspectRatio || 0, ceilingHeight: requirementData.spaceIndicators.ceilingHeight || 0 }; } // 同步材质权重信息 if (requirementData.materialIndicators) { this.requirementKeyInfo.materialWeights = { fabricRatio: requirementData.materialIndicators.fabricRatio || 0, woodRatio: requirementData.materialIndicators.woodRatio || 0, metalRatio: requirementData.materialIndicators.metalRatio || 0, smoothness: requirementData.materialIndicators.smoothness || 0, glossiness: requirementData.materialIndicators.glossiness || 0 }; } // 同步预设氛围信息 if (requirementData.selectedPresetAtmosphere) { this.requirementKeyInfo.presetAtmosphere = { name: requirementData.selectedPresetAtmosphere.name || '', rgb: requirementData.selectedPresetAtmosphere.rgb || '', colorTemp: requirementData.selectedPresetAtmosphere.colorTemp || '', materials: requirementData.selectedPresetAtmosphere.materials || [] }; } // 新增:实时更新左侧客户信息显示 if (this.project) { // 更新项目的客户信息 if (requirementData.colorIndicators && requirementData.colorIndicators.length > 0) { this.project.customerInfo = { ...this.project.customerInfo, colorPreference: requirementData.colorIndicators.map((indicator: any) => indicator.name).join(', ') }; } // 更新空间结构信息 if (requirementData.spaceIndicators && requirementData.spaceIndicators.length > 0) { this.project.customerInfo = { ...this.project.customerInfo, spaceRequirements: requirementData.spaceIndicators.map((indicator: any) => `${indicator.name}: ${indicator.value}`).join(', ') }; } // 更新材质偏好 if (requirementData.materialIndicators && requirementData.materialIndicators.length > 0) { this.project.customerInfo = { ...this.project.customerInfo, materialPreference: requirementData.materialIndicators.map((indicator: any) => `${indicator.name}: ${indicator.value}%`).join(', ') }; } // 触发变更检测以更新UI this.cdr.detectChanges(); } console.log('需求关键信息已同步:', this.requirementKeyInfo); } else { // 模拟数据用于演示 this.requirementKeyInfo = { colorAtmosphere: { description: '温馨暖调', mainColor: 'rgb(255, 230, 180)', colorTemp: '2700K', materials: ['木质', '布艺'] }, spaceStructure: { lineRatio: 60, blankRatio: 30, flowWidth: 0.9, aspectRatio: 1.6, ceilingHeight: 2.8 }, materialWeights: { fabricRatio: 50, woodRatio: 30, metalRatio: 20, smoothness: 7, glossiness: 4 }, presetAtmosphere: { name: '现代简约', rgb: '200,220,240', colorTemp: '5000K', materials: ['金属', '玻璃'] } }; } } // 新增:处理需求阶段完成事件 onRequirementsStageCompleted(event: { stage: string; allStagesCompleted: boolean }): void { console.log('需求阶段完成事件:', event); if (event.allStagesCompleted && event.stage === 'requirements-communication') { // 自动推进到方案确认阶段 this.currentStage = '方案确认'; this.expandedStages['方案确认'] = true; this.expandedStages['需求沟通'] = false; // 更新项目状态 this.updateProjectStage('方案确认'); console.log('自动推进到方案确认阶段'); } } // 新增:确认方案方法 confirmProposal(): void { console.log('确认方案按钮被点击'); // 使用统一的阶段推进方法 this.advanceToNextStage('方案确认'); console.log('已跳转到建模阶段'); } // 获取同步的关键信息摘要 getRequirementSummary(): string[] { const summary: string[] = []; if (this.requirementKeyInfo.colorAtmosphere.description) { summary.push(`色彩氛围: ${this.requirementKeyInfo.colorAtmosphere.description}`); } if (this.requirementKeyInfo.spaceStructure.aspectRatio > 0) { summary.push(`空间比例: ${this.requirementKeyInfo.spaceStructure.aspectRatio.toFixed(1)}`); } if (this.requirementKeyInfo.materialWeights.woodRatio > 0) { summary.push(`木质占比: ${this.requirementKeyInfo.materialWeights.woodRatio}%`); } if (this.requirementKeyInfo.presetAtmosphere.name) { summary.push(`预设氛围: ${this.requirementKeyInfo.presetAtmosphere.name}`); } return summary; } // 检查必需阶段是否全部完成(流程进度 > 确认需求 > 需求沟通四个流程) areRequiredStagesCompleted(): boolean { // 检查项目是否已经进入方案确认阶段或更后的阶段 if (!this.project) { return false; } const stageOrder = [ '订单分配', '需求沟通', '方案确认', '建模', '软装', '渲染', '尾款结算', '客户评价', '投诉处理' ]; const currentStageIndex = stageOrder.indexOf(this.project.currentStage); const proposalStageIndex = stageOrder.indexOf('方案确认'); const requirementStageIndex = stageOrder.indexOf('需求沟通'); // 如果当前阶段是方案确认或之后的阶段,则认为需求阶段已完成 if (currentStageIndex >= proposalStageIndex) { // 确保有基本的需求信息数据,如果没有则初始化模拟数据 this.ensureRequirementData(); return true; } // 如果当前阶段是需求沟通,检查需求沟通是否已完成 if (currentStageIndex === requirementStageIndex) { // 检查需求关键信息是否有数据,或者检查需求沟通组件的完成状态 const hasRequirementData = this.getRequirementSummary().length > 0 && (!!this.requirementKeyInfo.colorAtmosphere.description || this.requirementKeyInfo.spaceStructure.aspectRatio > 0 || this.requirementKeyInfo.materialWeights.woodRatio > 0 || !!this.requirementKeyInfo.presetAtmosphere.name); // 只有在真正有需求数据时才返回true,不再自动初始化模拟数据 if (hasRequirementData) { return true; } // 如果没有需求数据,返回false,不允许进入下一阶段 return false; } // 其他情况返回false return false; } // 确保有需求数据用于方案确认显示 private ensureRequirementData(): void { // console.log('=== ensureRequirementData 开始确保需求数据 ==='); // console.log('当前requirementKeyInfo:', this.requirementKeyInfo); // 修复条件判断:检查是否需要初始化数据 const needsInitialization = !this.requirementKeyInfo.colorAtmosphere.description || this.requirementKeyInfo.spaceStructure.aspectRatio === 0 || this.requirementKeyInfo.materialWeights.woodRatio === 0 || !this.requirementKeyInfo.presetAtmosphere.name; console.log('是否需要初始化数据:', needsInitialization); if (needsInitialization) { console.log('需求关键信息为空,初始化默认数据'); // 初始化模拟的需求数据 this.requirementKeyInfo = { colorAtmosphere: { description: '现代简约风格,以白色和灰色为主调', mainColor: '#F5F5F5', colorTemp: '冷色调', materials: ['木质', '金属', '玻璃'] }, spaceStructure: { lineRatio: 0.6, blankRatio: 0.4, flowWidth: 1.2, aspectRatio: 1.8, ceilingHeight: 2.8 }, materialWeights: { fabricRatio: 20, woodRatio: 45, metalRatio: 25, smoothness: 0.7, glossiness: 0.3 }, presetAtmosphere: { name: '现代简约', rgb: '#F5F5F5', colorTemp: '5000K', materials: ['木质', '金属'] } }; // console.log('初始化后的requirementKeyInfo:', this.requirementKeyInfo); } else { // console.log('需求关键信息已存在,无需初始化'); } } // 获取项目状态文本 getProjectStatusText(): string { const current = (this.currentStage || this.project?.currentStage) as ProjectStage | undefined; if (!current || current === '订单分配') { return '待开始'; } // 检查是否已完成所有阶段 const allStages: ProjectStage[] = ['订单分配', '需求沟通', '方案确认', '建模', '软装', '渲染', '尾款结算', '客户评价', '投诉处理']; const currentIndex = allStages.indexOf(current); if (currentIndex === allStages.length - 1) { return '已完成'; } return '进行中'; } // 获取当前阶段文本 getCurrentStageText(): string { const current = (this.currentStage || this.project?.currentStage) as ProjectStage | undefined; if (!current || current === '订单分配') { return '订单分配阶段'; } return `${current}阶段`; } // 获取整体进度百分比 getOverallProgress(): number { const current = (this.currentStage || this.project?.currentStage) as ProjectStage | undefined; if (!current) { return 0; } // 定义所有阶段及其权重 const stageWeights: Record = { '订单分配': 5, '需求沟通': 15, '方案确认': 25, '建模': 40, '软装': 55, '渲染': 70, '后期': 80, '尾款结算': 90, '客户评价': 95, '投诉处理': 100 }; return stageWeights[current] || 0; } // 获取必需阶段的完成进度百分比 getRequiredStagesProgress(): number { let completedCount = 0; const totalCount = 4; // 四个必需流程 // 检查各个关键信息是否已确认 if (this.requirementKeyInfo.colorAtmosphere.description) completedCount++; if (this.requirementKeyInfo.spaceStructure.aspectRatio > 0) completedCount++; if (this.requirementKeyInfo.materialWeights.woodRatio > 0 || this.requirementKeyInfo.materialWeights.fabricRatio > 0 || this.requirementKeyInfo.materialWeights.metalRatio > 0) completedCount++; if (this.requirementKeyInfo.presetAtmosphere.name) completedCount++; return Math.round((completedCount / totalCount) * 100); } // 订单金额 orderAmount: number = 0; // 报价明细 quotationDetails: Array<{ id: string; room: string; amount: number; description?: string; }> = []; // AI生成报价明细 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); } // 添加报价明细项 addQuotationItem(): void { this.quotationDetails.push({ id: `quote_${Date.now()}`, room: '', amount: 0, description: '' }); } // 删除报价明细项 removeQuotationItem(id: string): void { this.quotationDetails = this.quotationDetails.filter(item => item.id !== id); this.updateOrderAmount(); } // 更新订单总金额 updateOrderAmount(): void { this.orderAmount = this.quotationDetails.reduce((total, item) => total + item.amount, 0); } // 报价组件数据 quotationData: QuotationData = { items: [], totalAmount: 0, materialCost: 0, laborCost: 0, designFee: 0, managementFee: 0 }; // 设计师指派数据 designerAssignmentData?: DesignerAssignmentData; // 设计师日历弹窗状态与数据 showDesignerCalendar: boolean = false; selectedCalendarDate: Date = new Date(); calendarDesigners: CalendarDesigner[] = []; calendarGroups: CalendarProjectGroup[] = []; 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; } closeDesignerCalendar(): void { this.showDesignerCalendar = false; } onCalendarDesignerSelected(designer: CalendarDesigner): void { this.selectedDesigner = designer; this.closeDesignerCalendar(); } onCalendarAssignmentRequested(designer: CalendarDesigner): void { this.selectedDesigner = designer; this.closeDesignerCalendar(); } private mapAssignmentDesignerToCalendar(d: AssignmentDesigner): CalendarDesigner { return { id: d.id, name: d.name, avatar: d.avatar, groupId: d.teamId, groupName: d.teamName, isLeader: !!d.isTeamLeader, status: d.status === 'idle' ? 'available' : 'busy', currentProjects: Math.max(0, d.recentOrders || 0), lastOrderDate: d.lastOrderDate, idleDays: Math.max(0, d.idleDays || 0), completedThisMonth: Math.max(0, d.recentOrders || 0), averageCycle: 15, upcomingEvents: (d.reviewDates || []).map((dateStr, idx) => ({ id: `${d.id}-review-${idx}`, date: new Date(dateStr), title: '对图评审', type: 'review', projectId: undefined, duration: 2 })), workload: Math.max(0, d.workload || 0), nextAvailableDate: new Date() }; } // 处理咨询订单表单提交 // 存储订单分配时的客户信息和需求信息 onConsultationOrderSubmit(formData: any): void { console.log('咨询订单表单提交:', formData); // 保存订单分配数据 this.orderCreationData = formData; // 更新projectData以便传递给子组件(集成报价与指派信息) this.projectData = { customerInfo: formData.customerInfo, requirementInfo: formData.requirementInfo, preferenceTags: formData.preferenceTags, quotation: this.quotationData ? { ...this.quotationData } : undefined, assignment: this.designerAssignmentData ? { ...this.designerAssignmentData } : undefined, orderAmount: this.quotationData?.totalAmount ?? this.orderAmount ?? 0 }; // 实时更新左侧客户信息显示 this.updateCustomerInfoDisplay(formData); // 根据角色上下文处理数据同步 if (formData.roleContext === 'customer-service') { // 客服端创建的订单需要同步到设计师端 this.syncOrderToDesignerView(formData); } // 根据报价数据更新订单金额 this.orderAmount = this.quotationData?.totalAmount ?? this.orderAmount ?? 0; this.updateOrderAmount(); // 触发变更检测以更新UI this.cdr.detectChanges(); } // 新增:更新客户信息显示 - 优化后只更新实际存在的字段 private updateCustomerInfoDisplay(formData: any): void { if (formData.customerInfo) { // 更新项目对象中的客户信息 - 只更新实际存在的字段 if (this.project) { // 由于已移除客户姓名和手机号字段,只更新微信和客户类型 if (formData.customerInfo.wechat) { this.project.customerWechat = formData.customerInfo.wechat; } if (formData.customerInfo.customerType) { this.project.customerType = formData.customerInfo.customerType; } if (formData.customerInfo.source) { this.project.customerSource = formData.customerInfo.source; } if (formData.customerInfo.remark) { this.project.customerRemark = formData.customerInfo.remark; } } // 更新客户标签 if (formData.preferenceTags) { this.project = { ...this.project, customerTags: formData.preferenceTags } as any; } // 更新需求信息 - 只更新实际存在的字段 if (formData.requirementInfo) { this.project = { ...this.project, // 移除已删除的字段:decorationType, firstDraftDate, style, budget, area, houseType downPayment: formData.requirementInfo.downPayment, smallImageTime: formData.requirementInfo.smallImageTime, spaceRequirements: formData.requirementInfo.spaceRequirements, designAngles: formData.requirementInfo.designAngles, specialAreaHandling: formData.requirementInfo.specialAreaHandling, materialRequirements: formData.requirementInfo.materialRequirements, lightingRequirements: formData.requirementInfo.lightingRequirements } as any; } console.log('客户信息已实时更新:', this.project); } } // 新增:同步订单数据到设计师视图 private syncOrderToDesignerView(formData: any): void { // 创建项目数据 const projectData = { customerId: formData.customerInfo.id || 'customer-' + Date.now(), customerName: formData.customerInfo.name, requirement: formData.requirementInfo, referenceCases: [], tags: { demandType: formData.customerInfo.demandType, preferenceTags: formData.preferenceTags, followUpStatus: formData.customerInfo.followUpStatus }, // 新增:报价与指派信息(可选) quotation: this.quotationData ? { ...this.quotationData } : undefined, assignment: this.designerAssignmentData ? { ...this.designerAssignmentData } : undefined, orderAmount: this.quotationData?.totalAmount ?? this.orderAmount ?? 0 }; // 调用项目服务创建项目 this.projectService.createProject(projectData).subscribe( result => { if (result.success) { console.log('订单数据已同步到设计师端,项目ID:', result.projectId); // 可以在这里添加成功提示或其他处理逻辑 } }, error => { console.error('同步订单数据到设计师端失败:', error); } ); } // 确认团队分配 confirmTeamAssignment(designer: any): void { if (designer) { this.selectedDesigner = designer; console.log('团队分配确认:', designer); // 这里可以添加实际的团队分配逻辑 // 例如调用服务来分配设计师到项目 // 进入下一个阶段:需求沟通 this.updateProjectStage('需求沟通'); this.expandedStages['需求沟通'] = true; this.expandedStages['订单分配'] = false; // 显示成功消息 alert('团队分配成功!已进入需求沟通阶段'); console.log('团队分配完成,已跳转到需求沟通阶段'); } } // 项目创建完成事件处理 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); console.log('项目创建成功,已推进到需求沟通阶段,四大板块状态已更新'); } // 新增:处理实时需求数据更新 onRequirementDataUpdated(data: any): void { console.log('收到需求数据更新:', data); // 同步关键信息 this.syncRequirementKeyInfo(data); // 更新客户信息显示 if (data && this.project) { // 更新项目的客户信息 if (data.colorIndicators && data.colorIndicators.length > 0) { this.project.customerInfo = { ...this.project.customerInfo, colorPreference: data.colorIndicators.map((indicator: any) => indicator.name).join(', ') }; } // 更新空间结构信息 if (data.spaceIndicators && data.spaceIndicators.length > 0) { this.project.customerInfo = { ...this.project.customerInfo, spaceRequirements: data.spaceIndicators.map((indicator: any) => `${indicator.name}: ${indicator.value}`).join(', ') }; } // 更新材质偏好 if (data.materialIndicators && data.materialIndicators.length > 0) { this.project.customerInfo = { ...this.project.customerInfo, materialPreference: data.materialIndicators.map((indicator: any) => `${indicator.name}: ${indicator.value}%`).join(', ') }; } // 更新需求项目 if (data.requirementItems && data.requirementItems.length > 0) { this.project.requirements = data.requirementItems.map((item: any) => ({ id: item.id, description: item.description, status: item.status, priority: item.priority || 'medium' })); } // 接收色彩分析结果并存储用于右侧展示 if (data.colorAnalysisResult) { console.log('接收到色彩分析结果:', data.colorAnalysisResult); this.colorAnalysisResult = data.colorAnalysisResult as ColorAnalysisResult; console.log('设置colorAnalysisResult后:', this.colorAnalysisResult); // 计算主色:按占比最高的颜色 const colors = this.colorAnalysisResult?.colors || []; if (colors.length > 0) { const dominant = colors.reduce((max, cur) => cur.percentage > max.percentage ? cur : max, colors[0]); this.dominantColorHex = dominant.hex; console.log('计算出的主色:', this.dominantColorHex); } else { this.dominantColorHex = null; console.log('没有颜色数据,主色设为null'); } } else { console.log('没有接收到色彩分析结果'); } // 新增:处理详细的分析数据 if (data.detailedAnalysis) { console.log('接收到详细分析数据:', data.detailedAnalysis); // 存储各类分析结果 this.enhancedColorAnalysis = data.detailedAnalysis.enhancedColorAnalysis; this.formAnalysis = data.detailedAnalysis.formAnalysis; this.textureAnalysis = data.detailedAnalysis.textureAnalysis; this.patternAnalysis = data.detailedAnalysis.patternAnalysis; this.lightingAnalysis = data.detailedAnalysis.lightingAnalysis; console.log('详细分析数据已存储:', { enhancedColorAnalysis: this.enhancedColorAnalysis, formAnalysis: this.formAnalysis, textureAnalysis: this.textureAnalysis, patternAnalysis: this.patternAnalysis, lightingAnalysis: this.lightingAnalysis }); } // 新增:处理材料分析数据 if (data.materialAnalysisData && data.materialAnalysisData.length > 0) { console.log('接收到材料分析数据:', data.materialAnalysisData); this.materialAnalysisData = data.materialAnalysisData; } // 新增:根据上传来源拆分材料文件用于左右区展示 const materials = Array.isArray(data?.materials) ? data.materials : []; this.referenceImages = materials.filter((m: any) => m?.type === 'image'); this.cadFiles = materials.filter((m: any) => m?.type === 'cad'); // 触发变更检测以更新UI this.cdr.detectChanges(); console.log('客户信息已实时更新,当前colorAnalysisResult状态:', !!this.colorAnalysisResult); } } // 预览右侧色彩分析参考图 previewColorRefImage(): void { const url = this.colorAnalysisResult?.originalImage; if (url) { window.open(url, '_blank'); } } // 新增:点击预览参考图片与CAD文件 previewImageFile(url?: string): void { if (!url) return; window.open(url, '_blank'); } previewCadFile(url?: string): void { if (!url) return; window.open(url, '_blank'); } // 切换客户信息卡片展开状态 toggleCustomerInfo(): void { this.isCustomerInfoExpanded = !this.isCustomerInfoExpanded; } // 新增:重置方案分析状态的方法 resetProposalAnalysis(): void { this.proposalAnalysis = null; this.isAnalyzing = false; this.analysisProgress = 0; console.log('方案分析状态已重置'); } // 新增:模拟素材解析方法 startMaterialAnalysis(): void { this.isAnalyzing = true; this.analysisProgress = 0; const progressInterval = setInterval(() => { this.analysisProgress += Math.random() * 15; if (this.analysisProgress >= 100) { this.analysisProgress = 100; clearInterval(progressInterval); this.completeMaterialAnalysis(); } }, 500); } // 完成素材解析,生成方案数据 private completeMaterialAnalysis(): void { this.isAnalyzing = false; // 生成模拟的方案分析数据 this.proposalAnalysis = { id: 'proposal-' + Date.now(), name: '现代简约风格方案', version: 'v1.0', createdAt: new Date(), status: 'completed', materials: [ { category: '地面材料', specifications: { type: '复合木地板', grade: 'E0级', thickness: '12mm', finish: '哑光面', durability: '家用33级' }, usage: { area: '客厅、卧室', percentage: 65, priority: 'primary' }, properties: { texture: '木纹理', color: '浅橡木色', maintenance: '日常清洁' } }, { category: '墙面材料', specifications: { type: '乳胶漆', grade: '净味抗甲醛', finish: '丝光面', durability: '15年' }, usage: { area: '全屋墙面', percentage: 80, priority: 'primary' }, properties: { texture: '平滑', color: '暖白色', maintenance: '可擦洗' } }, { category: '软装面料', specifications: { type: '亚麻混纺', grade: 'A级', finish: '防污处理', durability: '5-8年' }, usage: { area: '沙发、窗帘', percentage: 25, priority: 'secondary' }, properties: { texture: '自然纹理', color: '米灰色系', maintenance: '干洗' } } ], designStyle: { primaryStyle: '现代简约', styleElements: [ { element: '线条设计', description: '简洁流畅的直线条,避免繁复装饰', influence: 85 }, { element: '色彩搭配', description: '以中性色为主,局部点缀暖色', influence: 75 }, { element: '材质选择', description: '天然材质与现代工艺结合', influence: 70 } ], characteristics: [ { feature: '空间感', value: '开放通透', importance: 'high' }, { feature: '功能性', value: '实用至上', importance: 'high' }, { feature: '装饰性', value: '简约精致', importance: 'medium' } ], compatibility: { withMaterials: ['木材', '金属', '玻璃', '石材'], withColors: ['白色', '灰色', '米色', '原木色'], score: 92 } }, colorScheme: { palette: [ { color: '暖白色', hex: '#F8F6F0', rgb: '248, 246, 240', percentage: 45, role: 'dominant' }, { color: '浅灰色', hex: '#E5E5E5', rgb: '229, 229, 229', percentage: 30, role: 'secondary' }, { color: '原木色', hex: '#D4A574', rgb: '212, 165, 116', percentage: 20, role: 'accent' }, { color: '深灰色', hex: '#4A4A4A', rgb: '74, 74, 74', percentage: 5, role: 'neutral' } ], harmony: { type: '类似色调和', temperature: 'warm', contrast: 65 }, psychology: { mood: '宁静舒适', atmosphere: '温馨自然', suitability: ['居住', '办公', '休闲'] } }, spaceLayout: { dimensions: { length: 12.5, width: 8.2, height: 2.8, area: 102.5, volume: 287 }, functionalZones: [ { zone: '客厅区域', area: 35.2, percentage: 34.3, requirements: ['会客', '娱乐', '休息'], furniture: ['沙发', '茶几', '电视柜', '边几'] }, { zone: '餐厅区域', area: 18.5, percentage: 18.0, requirements: ['用餐', '储物'], furniture: ['餐桌', '餐椅', '餐边柜'] }, { zone: '厨房区域', area: 12.8, percentage: 12.5, requirements: ['烹饪', '储存', '清洁'], furniture: ['橱柜', '岛台', '吧台椅'] }, { zone: '主卧区域', area: 25.6, percentage: 25.0, requirements: ['睡眠', '储衣', '梳妆'], furniture: ['床', '衣柜', '梳妆台', '床头柜'] }, { zone: '次卧区域', area: 10.4, percentage: 10.2, requirements: ['睡眠', '学习'], furniture: ['床', '书桌', '衣柜'] } ], circulation: { mainPaths: ['入户-客厅', '客厅-餐厅', '餐厅-厨房', '客厅-卧室'], pathWidth: 1.2, efficiency: 88 }, lighting: { natural: { direction: ['南向', '东向'], intensity: '充足', duration: '8-10小时' }, artificial: { zones: ['主照明', '局部照明', '装饰照明'], requirements: ['无主灯设计', '分层控制', '调光调色'] } } }, budget: { total: 285000, breakdown: [ { category: '基础装修', amount: 142500, percentage: 50 }, { category: '主材采购', amount: 85500, percentage: 30 }, { category: '软装配饰', amount: 42750, percentage: 15 }, { category: '设计费用', amount: 14250, percentage: 5 } ] }, timeline: [ { phase: '设计深化', duration: 7, dependencies: ['需求确认'] }, { phase: '材料采购', duration: 5, dependencies: ['设计深化'] }, { phase: '基础施工', duration: 30, dependencies: ['材料采购'] }, { phase: '软装进场', duration: 7, dependencies: ['基础施工'] }, { phase: '验收交付', duration: 3, dependencies: ['软装进场'] } ], feasibility: { technical: 95, budget: 88, timeline: 92, overall: 92 } }; } // 获取材质分类统计 getMaterialCategories(): { category: string; count: number; percentage: number }[] { if (!this.proposalAnalysis) return []; const categories = this.proposalAnalysis.materials.reduce((acc, material) => { acc[material.category] = (acc[material.category] || 0) + 1; return acc; }, {} as Record); const total = Object.values(categories).reduce((sum, count) => sum + count, 0); return Object.entries(categories).map(([category, count]) => ({ category, count, percentage: Math.round((count / total) * 100) })); } // 获取设计风格特征摘要 getStyleSummary(): string { if (!this.proposalAnalysis) return ''; const style = this.proposalAnalysis.designStyle; const topElements = style.styleElements .sort((a, b) => b.influence - a.influence) .slice(0, 2) .map(el => el.element) .join('、'); return `${style.primaryStyle}风格,主要体现在${topElements}等方面`; } // 获取色彩方案摘要 getColorSummary(): string { if (!this.proposalAnalysis) return ''; const scheme = this.proposalAnalysis.colorScheme; const dominantColor = scheme.palette.find(p => p.role === 'dominant')?.color || ''; const accentColor = scheme.palette.find(p => p.role === 'accent')?.color || ''; return `以${dominantColor}为主调,${accentColor}作点缀,营造${scheme.psychology.mood}的氛围`; } // 获取空间效率评分 getSpaceEfficiency(): number { if (!this.proposalAnalysis) return 0; return this.proposalAnalysis.spaceLayout.circulation.efficiency; } private handlePaymentProofUpload(file: File): void { // 显示上传进度 const uploadingMessage = `正在上传支付凭证:${file.name}...`; console.log(uploadingMessage); // 使用支付凭证识别服务处理上传 const settlementId = this.project?.id || 'default_settlement'; this.paymentVoucherService.processPaymentVoucherUpload(file, settlementId).subscribe({ next: (result) => { if (result.success && result.recognitionResult) { const recognition = result.recognitionResult; // 更新识别计数 this.voucherRecognitionCount++; // 显示识别结果 const successMessage = ` 支付凭证识别成功! 支付方式:${recognition.paymentMethod} 支付金额:¥${recognition.amount} 交易号:${recognition.transactionNumber} 置信度:${(recognition.confidence * 100).toFixed(1)}% `; alert(successMessage); console.log('支付凭证识别完成', recognition); // 自动标记验证通过并解锁渲染大图 this.isPaymentVerified = true; this.renderLargeImages = this.renderLargeImages.map(img => ({ ...img, locked: false })); // 触发自动通知流程 this.triggerPaymentCompletedNotification(recognition); } else { const errorMessage = `支付凭证识别失败:${result.error || '未知错误'}`; alert(errorMessage); console.error('支付凭证识别失败', result.error); } }, error: (error) => { const errorMessage = `支付凭证处理出错:${error.message || '网络错误'}`; alert(errorMessage); console.error('支付凭证处理出错', error); } }); } /** * 触发支付完成通知流程 */ private triggerPaymentCompletedNotification(recognition: any): void { console.log('触发支付完成自动通知流程...'); // 模拟调用通知服务发送多渠道通知 this.sendMultiChannelNotifications(recognition); // 模拟发送通知 setTimeout(() => { const notificationMessage = ` 🎉 尾款已到账,大图已解锁! 支付信息: • 支付方式:${recognition.paymentMethod} • 支付金额:¥${recognition.amount} • 处理时间:${new Date().toLocaleString()} 📱 系统已自动发送通知至: • 短信通知:138****8888 • 微信通知:已推送至微信 • 邮件通知:customer@example.com 🖼️ 高清渲染图下载链接已发送 您现在可以下载4K高清渲染图了! `; alert(notificationMessage); console.log('自动通知发送完成'); }, 1000); } /** * 发送多渠道通知 */ private sendMultiChannelNotifications(recognition: any): void { console.log('开始发送多渠道通知...'); // 更新通知发送计数 this.notificationsSent++; // 模拟发送短信通知 console.log('📱 发送短信通知: 尾款已到账,大图已解锁'); // 模拟发送微信通知 console.log('💬 发送微信通知: 支付成功,高清图片已准备就绪'); // 模拟发送邮件通知 console.log('📧 发送邮件通知: 包含下载链接的详细通知'); // 模拟发送应用内通知 console.log('🔔 发送应用内通知: 实时推送支付状态更新'); // 模拟通知发送结果 setTimeout(() => { console.log('✅ 所有渠道通知发送完成'); console.log(`通知发送统计: 短信✅ 微信✅ 邮件✅ 应用内✅ (总计: ${this.notificationsSent} 次)`); }, 500); } // 获取当前设计师名称 getCurrentDesignerName(): string { // 这里应该从用户服务或认证信息中获取当前用户名称 // 暂时返回一个默认值 return '张设计师'; } // ==================== 售后相关变量 ==================== // 售后标签页控制 activeAftercareTab: string = 'services'; // 当前激活的售后标签页 // 售后状态管理 afterSalesStage: string = '未开始'; // 售后阶段:未开始、进行中、已完成 afterSalesStatus: 'pending' | 'in_progress' | 'completed' | 'cancelled' = 'pending'; // 售后状态 afterSalesProgress: number = 0; // 售后进度百分比 // 售后服务数据 afterSalesServices: Array<{ id: string; type: string; // 服务类型:维修、保养、咨询、投诉处理 description: string; assignedTo: string; // 指派给的人员 scheduledDate?: Date; // 计划服务日期 completedDate?: Date; // 完成日期 status: 'pending' | 'scheduled' | 'in_progress' | 'completed' | 'cancelled'; priority: 'low' | 'medium' | 'high'; }> = []; // 售后评价和反馈 afterSalesRating: number = 0; // 售后评分(0-5) afterSalesFeedback: string = ''; // 售后反馈内容 afterSalesFeedbackDate?: Date; // 反馈日期 // 售后时间跟踪 afterSalesResponseTime?: Date; // 首次响应时间 afterSalesStartTime?: Date; // 售后开始时间 afterSalesCompletionTime?: Date; // 售后完成时间 afterSalesTotalDuration: number = 0; // 总耗时(小时) // 售后联系人信息 afterSalesContact: { name: string; phone: string; wechat?: string; email?: string; } = { name: '', phone: '' }; // 售后问题记录 afterSalesIssues: Array<{ id: string; title: string; description: string; severity: 'low' | 'medium' | 'high' | 'critical'; reportedDate: Date; resolvedDate?: Date; status: 'reported' | 'investigating' | 'resolved' | 'closed'; }> = []; // 售后文件记录 afterSalesDocuments: Array<{ id: string; name: string; type: string; // 文件类型:合同、报告、照片、视频 uploadDate: Date; url: string; }> = []; // 售后费用记录 afterSalesCosts: Array<{ id: string; description: string; amount: number; category: 'material' | 'labor' | 'transportation' | 'other'; date: Date; status: 'pending' | 'approved' | 'paid' | 'rejected'; }> = []; // 售后沟通记录 afterSalesCommunications: Array<{ id: string; type: 'phone' | 'wechat' | 'email' | 'visit'; date: Date; summary: string; participants: string[]; }> = []; // 新增:项目复盘相关属性 projectReview: ProjectReview | null = null; isGeneratingReview: boolean = false; // 项目复盘 Tab 切换 activeReviewTab: 'sop' | 'experience' | 'suggestions' = 'sop'; // SOP执行数据 sopMetrics: any = { communicationCount: 6, avgCommunication: 5, revisionCount: 3, avgRevision: 2, deliveryCycle: 18, standardCycle: 15, customerSatisfaction: 4.5 }; sopStagesData: any[] = [ { name: '订单分配', plannedDuration: 1, actualDuration: 1, score: 95, status: 'completed', statusText: '已完成', isDelayed: false, issues: [] }, { name: '需求沟通', plannedDuration: 3, actualDuration: 4, score: 75, status: 'completed', statusText: '已完成', isDelayed: true, issues: ['沟通次数超标'] }, { name: '方案确认', plannedDuration: 2, actualDuration: 2, score: 90, status: 'completed', statusText: '已完成', isDelayed: false, issues: [] }, { name: '建模', plannedDuration: 4, actualDuration: 5, score: 80, status: 'completed', statusText: '已完成', isDelayed: true, issues: ['细节调整耗时'] }, { name: '软装', plannedDuration: 2, actualDuration: 2, score: 92, status: 'completed', statusText: '已完成', isDelayed: false, issues: [] }, { name: '渲染', plannedDuration: 3, actualDuration: 4, score: 85, status: 'ongoing', statusText: '进行中', isDelayed: false, issues: [] } ]; // 经验复盘数据 experienceData: any = { customerNeeds: [ { text: '希望客厅采用现代简约风格,注重收纳功能', timestamp: '2024-01-15 10:30', source: '初次沟通' }, { text: '卧室需要温馨氛围,色调以暖色为主', timestamp: '2024-01-16 14:20', source: '需求确认' }, { text: '厨房要求实用性强,采用白色橱柜', timestamp: '2024-01-17 09:15', source: '细节讨论' } ], customerConcerns: [ { text: '担心渲染图与实际效果有差异', timestamp: '2024-01-18 16:40', resolved: true }, { text: '预算控制在合理范围内', timestamp: '2024-01-19 11:25', resolved: true } ], complaintPoints: [ { text: '首版方案色彩搭配不符合预期', timestamp: '2024-01-20 15:10', severity: 'medium', severityText: '中等', resolution: '已调整为客户偏好的配色方案' } ], projectHighlights: [ { text: '设计师对空间的功能分区把握精准', category: '设计亮点', praised: true }, { text: '材质选择符合客户品味且性价比高', category: '材质选择', praised: true }, { text: '渲染效果图质量优秀,客户非常满意', category: '技术表现', praised: true } ], communications: [ { timestamp: '2024-01-15 10:30', participant: '客户', type: 'requirement', typeText: '需求提出', message: '希望整体风格简约现代,注重实用性...', attachments: [] }, { timestamp: '2024-01-16 09:00', participant: '设计师', type: 'response', typeText: '设计反馈', message: '根据您的需求,我们建议采用...', attachments: ['方案草图.jpg'] }, { timestamp: '2024-01-20 14:30', participant: '客户', type: 'concern', typeText: '疑虑表达', message: '对配色方案有些疑虑...', attachments: [] } ] }; // 优化建议数据 optimizationSuggestions: any[] = [ { priority: 'high', priorityText: '高优先级', category: '需求沟通', expectedImprovement: '减少30%改图次数', problem: '该项目因需求理解不够深入导致改图3次,超出平均水平', dataPoints: [ { label: '实际改图次数', value: '3次', isWarning: true }, { label: '平均改图次数', value: '2次', isWarning: false }, { label: '超出比例', value: '+50%', isWarning: true } ], solution: '建议在需求沟通阶段增加确认环节,通过详细的需求确认清单和参考案例,确保设计师完全理解客户需求', actionPlan: [ '制定标准化的需求确认清单,覆盖风格、色彩、材质、功能等核心要素', '在方案设计前与客户进行一次需求复核会议', '准备3-5个同类型案例供客户参考,明确设计方向', '使用可视化工具(如情绪板)帮助客户表达偏好' ], references: ['项目A-123', '项目B-456'] }, { priority: 'medium', priorityText: '中优先级', category: '时间管理', expectedImprovement: '缩短15%交付周期', problem: '项目交付周期为18天,超出标准周期15天', dataPoints: [ { label: '实际周期', value: '18天', isWarning: true }, { label: '标准周期', value: '15天', isWarning: false }, { label: '延期', value: '+3天', isWarning: true } ], solution: '优化建模和渲染阶段的时间分配,采用并行工作模式提高效率', actionPlan: [ '建模和软装选择可以部分并行进行', '提前准备常用材质库,减少临时查找时间', '设置阶段里程碑提醒,避免单一阶段耗时过长' ], references: ['时间优化案例-001'] }, { priority: 'low', priorityText: '低优先级', category: '客户体验', expectedImprovement: '提升客户满意度至4.8分', problem: '当前客户满意度为4.5分,仍有提升空间', dataPoints: [ { label: '当前满意度', value: '4.5/5', isWarning: false }, { label: '目标满意度', value: '4.8/5', isWarning: false } ], solution: '增加交付过程中的沟通频率,及时展示阶段性成果', actionPlan: [ '每个关键节点完成后主动向客户汇报进度', '提供阶段性预览图,让客户参与到创作过程中', '建立客户反馈快速响应机制' ], references: [] } ]; switchAftercareTab(tab: string): void { this.activeAftercareTab = tab; console.log('切换到售后标签页:', tab); } // ==================== 自动结算相关 ==================== // 检查项目是否已完成验收 isProjectAccepted(): boolean { // 检查所有交付阶段是否完成 const deliveryStages: ProjectStage[] = ['建模', '软装', '渲染', '后期']; return deliveryStages.every(stage => this.getStageStatus(stage) === 'completed'); } // 启动自动结算(只有技术人员可触发) initiateAutoSettlement(): void { if (this.isAutoSettling) return; // 权限检查 if (!this.isTechnicalView()) { alert('⚠️ 仅技术人员可以启动自动化结算流程'); return; } // 验收状态检查 if (!this.isProjectAccepted()) { const confirmStart = confirm('项目尚未完成全部验收,确定要启动结算流程吗?'); if (!confirmStart) return; } this.isAutoSettling = true; console.log('启动自动化结算流程...'); // 模拟启动各个自动化功能 setTimeout(() => { // 1. 启动小程序支付监听 this.miniprogramPaymentStatus = 'active'; this.isSettlementInitiated = true; console.log('✅ 自动化结算已启动'); console.log('🟢 小程序支付监听已激活'); console.log('🔍 支付凭证智能识别已就绪'); console.log('📱 自动通知系统已就绪'); // 2. 自动生成尾款结算记录 this.createFinalPaymentRecord(); // 3. 通知客服跟进尾款 this.notifyCustomerServiceForFinalPayment(); // 4. 设置支付监听和自动化流程 this.setupPaymentAutomation(); this.isAutoSettling = false; // 显示启动成功消息 alert(`🚀 自动化结算已成功启动! ✅ 已启动功能: • 小程序支付自动监听 • 支付凭证智能识别 • 多渠道自动通知 • 大图自动解锁 • 已通知客服跟进尾款 系统将自动处理后续支付流程。`); }, 2000); } // 创建尾款结算记录 private createFinalPaymentRecord(): void { const finalPaymentRecord: Settlement = { id: `settlement_${Date.now()}`, projectId: this.projectId, projectName: this.project?.name || '', type: 'final_payment', amount: this.project?.finalPaymentAmount || 0, percentage: 30, status: 'pending', createdAt: new Date(), initiatedBy: 'technical', initiatedAt: new Date(), notifiedCustomerService: false, paymentReceived: false, imagesUnlocked: false }; // 添加到结算列表 if (!this.settlements.find(s => s.type === 'final_payment')) { this.settlements.unshift(finalPaymentRecord); } console.log('📝 已创建尾款结算记录:', finalPaymentRecord); } // 通知客服跟进尾款 private notifyCustomerServiceForFinalPayment(): void { const projectInfo = { projectId: this.projectId, projectName: this.project?.name || '未知项目', customerName: this.project?.customerName || '未知客户', customerPhone: this.project?.customerPhone || '', finalPaymentAmount: this.project?.finalPaymentAmount || 0, notificationTime: new Date(), status: 'pending_followup', priority: 'high', autoGenerated: true, message: `项目【${this.project?.name}】已完成技术验收,请及时跟进客户尾款支付。` }; // 发送通知到客服系统 console.log('📢 正在通知客服跟进尾款...', projectInfo); // 模拟API调用到客服通知系统 // this.customerServiceAPI.addPendingTask(projectInfo).subscribe(...) setTimeout(() => { console.log('✅ 客服通知已发送成功'); // 更新结算记录状态 const settlement = this.settlements.find(s => s.type === 'final_payment'); if (settlement) { settlement.notifiedCustomerService = true; } }, 500); } // 设置支付自动化流程 private setupPaymentAutomation(): void { console.log('⚙️ 设置支付自动化监听...'); // 模拟支付监听(实际应使用WebSocket或轮询) // 这里仅作演示 this.monitorPaymentStatus(); } // 监听支付状态 private monitorPaymentStatus(): void { // 实际应该使用WebSocket连接或定时轮询API // 这里仅作演示用setTimeout模拟 console.log('👀 开始监听支付状态...'); // 当检测到支付完成时,自动触发后续流程 // this.onPaymentReceived(); } // 支付到账回调 onPaymentReceived(paymentInfo?: any): void { console.log('💰 检测到支付到账:', paymentInfo); const settlement = this.settlements.find(s => s.type === 'final_payment'); if (!settlement) return; // 更新结算状态 settlement.status = '已结算'; settlement.settledAt = new Date(); settlement.paymentReceived = true; settlement.paymentReceivedAt = new Date(); // 自动解锁并发送大图 this.autoUnlockAndSendImages(); // 通知客户和客服 this.sendPaymentConfirmationNotifications(); } // 自动解锁并发送大图 private autoUnlockAndSendImages(): void { console.log('🔓 自动解锁大图...'); // 更新结算记录 const settlement = this.settlements.find(s => s.type === 'final_payment'); if (settlement) { settlement.imagesUnlocked = true; settlement.imagesUnlockedAt = new Date(); } // 自动发送大图给客户(通过客服) this.autoSendImagesToCustomer(); console.log('✅ 大图已解锁并准备发送'); } // 自动发送图片给客户 private autoSendImagesToCustomer(): void { console.log('📤 自动发送大图给客户...'); // 收集所有渲染大图 const renderProcess = this.deliveryProcesses.find(p => p.id === 'rendering'); const images: string[] = []; if (renderProcess) { Object.keys(renderProcess.content).forEach(spaceId => { const content = renderProcess.content[spaceId]; if (content.images) { content.images.forEach(img => { images.push(img.url); }); } }); } const sendInfo = { projectId: this.projectId, customerName: this.project?.customerName || '', images: images, sendMethod: 'wechat', autoGenerated: true }; console.log('📨 准备发送的大图信息:', sendInfo); // 调用客服系统API一键发图 // this.customerServiceAPI.sendImagesToCustomer(sendInfo).subscribe(...) } // 发送支付确认通知 private sendPaymentConfirmationNotifications(): void { console.log('📱 发送支付确认通知...'); // 通知客户 const customerMessage = `尊敬的${this.project?.customerName || '客户'},您的尾款支付已确认,大图已自动解锁并发送,请查收。感谢您的信任!`; // 通知客服 const csMessage = `项目【${this.project?.name}】尾款已到账,大图已自动解锁,请一键发送给客户。`; console.log('📧 客户通知:', customerMessage); console.log('📧 客服通知:', csMessage); // 实际发送通知 // this.notificationService.send({ to: 'customer', message: customerMessage }); // this.notificationService.send({ to: 'customer_service', message: csMessage }); } // ==================== 全景图合成相关 ==================== // 全景图合成数据 panoramicSyntheses: PanoramicSynthesis[] = []; isUploadingPanoramicImages: boolean = false; panoramicUploadProgress: number = 0; // 启动全景图合成流程 // 上传支付凭证 uploadPaymentProof(): void { console.log('📎 打开支付凭证上传...'); const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.accept = 'image/*'; fileInput.onchange = (event: any) => { const file = (event.target as HTMLInputElement).files?.[0]; if (!file) return; console.log('📄 上传的凭证文件:', file.name); alert(`📎 支付凭证已上传:${file.name}\n\n系统将自动识别支付金额和支付方式。`); // 模拟凭证识别和处理 setTimeout(() => { const mockPaymentInfo = { amount: this.project?.finalPaymentAmount || 5000, method: '微信支付', imageUrl: URL.createObjectURL(file), uploadTime: new Date() }; console.log('✅ 支付凭证识别完成:', mockPaymentInfo); this.onPaymentReceived(mockPaymentInfo); }, 1500); }; fileInput.click(); } startPanoramicSynthesis(): void { console.log('🎨 启动全景图合成...'); // 打开文件选择对话框,支持多文件选择 const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.accept = 'image/*'; fileInput.multiple = true; fileInput.onchange = (event: any) => { const files = Array.from(event.target.files || []) as File[]; if (files.length === 0) return; console.log(`📸 选择了 ${files.length} 张图片进行合成`); this.processPanoramicImages(files); }; fileInput.click(); } // 处理全景图片上传和合成 private processPanoramicImages(files: File[]): void { this.isUploadingPanoramicImages = true; this.panoramicUploadProgress = 0; console.log(`📸 开始处理 ${files.length} 张全景图片...`); // 模拟上传进度 const uploadInterval = setInterval(() => { this.panoramicUploadProgress += 10; if (this.panoramicUploadProgress >= 100) { clearInterval(uploadInterval); this.panoramicUploadProgress = 100; // 上传完成后开始合成 this.synthesizePanoramicView(files); } }, 300); } // 合成全景漫游 private synthesizePanoramicView(files: File[]): void { console.log('🔄 开始合成全景漫游...'); // 创建合成记录 const synthesis: PanoramicSynthesis = { id: 'panoramic-' + Date.now(), projectId: this.projectId, projectName: this.project?.name || '未知项目', spaces: [], status: 'processing', quality: 'high', createdAt: new Date(), updatedAt: new Date(), progress: 0 }; // 根据上传的文件创建空间列表 files.forEach((file, index) => { // 从文件名提取空间名称(如"客厅-角度1.jpg") const fileName = file.name.replace(/\.(jpg|jpeg|png|gif)$/i, ''); const match = fileName.match(/^(.+?)-/); const spaceName = match ? match[1] : `空间${index + 1}`; // 根据空间名称推断类型 let spaceType: 'living_room' | 'bedroom' | 'kitchen' | 'bathroom' | 'dining_room' | 'study' | 'balcony' = 'living_room'; if (spaceName.includes('客厅')) spaceType = 'living_room'; else if (spaceName.includes('卧室')) spaceType = 'bedroom'; else if (spaceName.includes('厨房')) spaceType = 'kitchen'; else if (spaceName.includes('卫生间') || spaceName.includes('浴室')) spaceType = 'bathroom'; else if (spaceName.includes('餐厅')) spaceType = 'dining_room'; else if (spaceName.includes('书房')) spaceType = 'study'; else if (spaceName.includes('阳台')) spaceType = 'balcony'; synthesis.spaces.push({ id: `space_${Date.now()}_${index}`, name: spaceName, type: spaceType, imageCount: 1, viewAngle: fileName }); }); this.panoramicSyntheses.unshift(synthesis); // 模拟KR Panel合成进度 let progress = 0; const synthesisInterval = setInterval(() => { progress += 15; synthesis.progress = Math.min(progress, 100); synthesis.updatedAt = new Date(); if (progress >= 100) { clearInterval(synthesisInterval); // 合成完成 synthesis.status = 'completed'; synthesis.completedAt = new Date(); synthesis.previewUrl = this.generateMockPanoramicUrl(synthesis.id); synthesis.downloadUrl = this.generateMockDownloadUrl(synthesis.id); synthesis.renderTime = 120 + Math.floor(Math.random() * 60); synthesis.fileSize = files.reduce((sum, f) => sum + f.size, 0); this.isUploadingPanoramicImages = false; console.log('✅ 全景图合成完成:', synthesis); // 自动生成分享链接 this.generatePanoramicShareLink(synthesis); } }, 500); } // 生成全景图分享链接 private generatePanoramicShareLink(synthesis: PanoramicSynthesis): void { const shareLink = `https://panoramic.example.com/view/${synthesis.id}`; synthesis.shareLink = shareLink; console.log('🔗 全景图分享链接:', shareLink); // 自动通知客服发送给客户 this.notifyCustomerServiceForPanoramicLink(synthesis); } // 通知客服发送全景图链接 private notifyCustomerServiceForPanoramicLink(synthesis: PanoramicSynthesis): void { const notification = { type: 'panoramic_ready', projectId: this.projectId, projectName: synthesis.projectName, shareLink: synthesis.shareLink, message: `项目【${synthesis.projectName}】的全景漫游已生成完成,请发送给客户查看。`, timestamp: new Date() }; console.log('📢 通知客服发送全景图链接:', notification); // 调用客服通知API // this.customerServiceAPI.notifyPanoramicReady(notification).subscribe(...) } // 生成模拟全景图URL private generateMockPanoramicUrl(id: string): string { return `https://panoramic.example.com/preview/${id}`; } // 生成模拟下载URL private generateMockDownloadUrl(id: string): string { return `https://panoramic.example.com/download/${id}`; } // 查看全景图画廊 viewPanoramicGallery(): void { console.log('打开全景图画廊'); if (this.panoramicSyntheses.length === 0) { alert('暂无全景图记录'); return; } // 显示全景图列表 const galleryInfo = this.panoramicSyntheses.map((s, i) => `${i + 1}. ${s.projectName} - ${s.status === 'completed' ? '已完成' : '处理中'} (${s.spaces.length}个空间)` ).join('\n'); alert(`全景图画廊\n\n${galleryInfo}\n\n点击查看详情功能开发中...`); } // 复制全景图链接 copyPanoramicLink(synthesis: PanoramicSynthesis): void { if (!synthesis.shareLink) { alert('全景图链接尚未生成'); return; } navigator.clipboard.writeText(synthesis.shareLink).then(() => { alert(`✅ 全景图链接已复制!\n\n${synthesis.shareLink}`); }).catch(() => { alert(`全景图链接:\n${synthesis.shareLink}`); }); } // ==================== 评价统计相关 ==================== // 评价统计数据 reviewStats: { overallScore: number; timelinessScore: number; qualityScore: number; communicationScore: number; } = { overallScore: 4.8, timelinessScore: 4.7, qualityScore: 4.9, communicationScore: 4.6 }; // ==================== 客户评价相关 ==================== // 生成评价链接 generateReviewLink(): void { console.log('📋 生成客户评价链接...'); // 生成唯一的评价令牌 const reviewToken = this.generateReviewToken(); const reviewLink = `https://review.yss.com/project/${this.projectId}?token=${reviewToken}`; // 保存评价链接记录 const reviewRecord = { projectId: this.projectId, projectName: this.project?.name || '', customerName: this.project?.customerName || '', reviewLink: reviewLink, token: reviewToken, createdAt: new Date(), expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30天有效期 status: 'active', accessed: false }; console.log('✅ 评价链接已生成:', reviewRecord); // 复制到剪贴板 navigator.clipboard.writeText(reviewLink).then(() => { alert(`✅ 评价链接已复制到剪贴板!\n\n链接:${reviewLink}\n\n有效期:30天\n\n请通过企业微信发送给客户`); }).catch(() => { alert(`评价链接:\n\n${reviewLink}\n\n有效期:30天\n\n请通过企业微信发送给客户`); }); // 通知客服发送评价链接 this.notifyCustomerServiceForReviewLink(reviewRecord); } // 生成评价令牌 private generateReviewToken(): string { return `review_${this.projectId}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } // 通知客服发送评价链接 private notifyCustomerServiceForReviewLink(reviewRecord: any): void { const notification = { type: 'review_link_ready', projectId: this.projectId, projectName: reviewRecord.projectName, customerName: reviewRecord.customerName, reviewLink: reviewRecord.reviewLink, message: `项目【${reviewRecord.projectName}】的客户评价链接已生成,请发送给客户。`, timestamp: new Date() }; console.log('📢 通知客服发送评价链接:', notification); // 调用客服通知API // this.customerServiceAPI.notifyReviewLinkReady(notification).subscribe(...) } // 确认客户评价完成 confirmCustomerReview(): void { console.log('✅ 确认客户评价完成'); // 更新项目状态 if (this.project) { this.project.customerReviewCompleted = true; this.project.customerReviewCompletedAt = new Date(); } alert('✅ 客户评价已确认完成!'); // 可选:自动进入下一阶段 // this.advanceToNextStage('客户评价'); } // ==================== 投诉管理相关 ==================== // 关键词监控配置 complaintKeywords: string[] = ['不满意', '投诉', '退款', '差评', '质量问题', '延期', '态度差']; isKeywordMonitoringActive: boolean = false; // 手动创建投诉 createComplaintManually(): void { console.log('📝 手动创建投诉'); // 弹出创建投诉表单 const complaintReason = prompt('请输入投诉原因:'); if (!complaintReason || complaintReason.trim() === '') return; const complaintStage = prompt('请输入投诉环节(如:需求沟通、建模、渲染等):') || '未指定'; // 创建投诉记录 const complaint: any = { id: `complaint_${Date.now()}`, projectId: this.projectId, projectName: this.project?.name || '', customerName: this.project?.customerName || '', type: '人工创建', stage: complaintStage, reason: complaintReason, severity: 'medium', status: 'pending', createdBy: 'manual', createdAt: new Date(), handler: '', resolution: '', resolvedAt: null }; // 智能标注核心问题 complaint.tags = this.analyzeComplaintTags(complaintReason); // 添加到投诉列表 this.exceptionHistories.unshift(complaint); console.log('✅ 投诉记录已创建:', complaint); alert(`✅ 投诉记录已创建!\n\n投诉环节:${complaintStage}\n核心问题:${complaint.tags.join('、')}\n\n系统将自动跟踪处理进度。`); // 通知相关人员 this.notifyComplaintHandlers(complaint); } // 分析投诉标签 private analyzeComplaintTags(reason: string): string[] { const tags: string[] = []; const tagPatterns = { '需求理解': ['需求', '理解', '沟通', '误解'], '设计质量': ['质量', '效果', '不好', '不满意'], '交付延期': ['延期', '超时', '慢', '着急'], '服务态度': ['态度', '不礼貌', '敷衍', '回复慢'], '价格问题': ['价格', '费用', '贵', '退款'] }; Object.entries(tagPatterns).forEach(([tag, keywords]) => { if (keywords.some(keyword => reason.includes(keyword))) { tags.push(tag); } }); return tags.length > 0 ? tags : ['其他']; } // 通知投诉处理人员 private notifyComplaintHandlers(complaint: any): void { const notification = { type: 'new_complaint', projectId: this.projectId, complaintId: complaint.id, projectName: complaint.projectName, customerName: complaint.customerName, severity: complaint.severity, tags: complaint.tags, message: `项目【${complaint.projectName}】收到新投诉,请及时处理。`, timestamp: new Date() }; console.log('📢 通知投诉处理人员:', notification); // 调用通知API // this.complaintService.notifyHandlers(notification).subscribe(...) } // 设置关键词监控 setupKeywordMonitoring(): void { console.log('⚙️ 设置关键词监控'); if (this.isKeywordMonitoringActive) { const confirmStop = confirm('关键词监控已激活,是否停止监控?'); if (confirmStop) { this.isKeywordMonitoringActive = false; alert('✅ 关键词监控已停止'); } return; } // 显示当前监控关键词 const currentKeywords = this.complaintKeywords.join('、'); const newKeywords = prompt(`当前监控关键词:\n\n${currentKeywords}\n\n请输入要添加的关键词(多个关键词用逗号分隔):`); if (newKeywords && newKeywords.trim()) { const keywords = newKeywords.split(/[,,、]/).map(k => k.trim()).filter(k => k); this.complaintKeywords = [...new Set([...this.complaintKeywords, ...keywords])]; } // 激活监控 this.isKeywordMonitoringActive = true; this.startKeywordMonitoring(); alert(`✅ 关键词监控已激活!\n\n监控关键词:${this.complaintKeywords.join('、')}\n\n系统将自动检测企业微信群消息中的关键词并创建投诉记录。`); } // 开始关键词监控 private startKeywordMonitoring(): void { console.log('👀 开始关键词监控...'); console.log('监控关键词:', this.complaintKeywords); // 模拟监控企业微信消息(实际应使用企业微信API或webhook) // 这里仅作演示 // 监控到关键词后自动创建投诉 // this.onKeywordDetected(message, keyword); } // 关键词检测回调 onKeywordDetected(message: string, keyword: string): void { console.log('🚨 检测到关键词:', keyword); console.log('消息内容:', message); // 自动创建投诉记录 const complaint: any = { id: `complaint_auto_${Date.now()}`, projectId: this.projectId, projectName: this.project?.name || '', customerName: this.project?.customerName || '', type: '关键词自动抓取', keyword: keyword, message: message, severity: this.assessComplaintSeverity(message), status: 'pending', createdBy: 'auto', createdAt: new Date(), handler: '', resolution: '', resolvedAt: null }; // 智能标注问题环节和核心问题 complaint.stage = this.identifyComplaintStage(message); complaint.tags = this.analyzeComplaintTags(message); // 添加到投诉列表 this.exceptionHistories.unshift(complaint); console.log('✅ 自动投诉记录已创建:', complaint); // 实时通知相关人员 this.notifyComplaintHandlers(complaint); } // 评估投诉严重程度 private assessComplaintSeverity(message: string): 'low' | 'medium' | 'high' { const highSeverityKeywords = ['退款', '投诉', '举报', '律师', '曝光']; const mediumSeverityKeywords = ['不满意', '差评', '质量问题']; if (highSeverityKeywords.some(k => message.includes(k))) return 'high'; if (mediumSeverityKeywords.some(k => message.includes(k))) return 'medium'; return 'low'; } // 识别投诉环节 private identifyComplaintStage(message: string): string { const stageKeywords = { '需求沟通': ['需求', '沟通', '理解'], '方案确认': ['方案', '设计', '效果'], '建模': ['建模', '模型', '白模'], '软装': ['软装', '家具', '配饰'], '渲染': ['渲染', '出图', '大图'], '交付': ['交付', '发送', '收到'] }; for (const [stage, keywords] of Object.entries(stageKeywords)) { if (keywords.some(k => message.includes(k))) { return stage; } } return '未指定'; } // 确认投诉处理完成 confirmComplaint(): void { console.log('✅ 确认投诉处理完成'); // 检查是否有未处理的投诉 const pendingComplaints = this.exceptionHistories.filter(c => c.status === '待处理'); if (pendingComplaints.length > 0) { const confirmAnyway = confirm(`还有 ${pendingComplaints.length} 个投诉未处理,确定要标记为已完成吗?`); if (!confirmAnyway) return; } alert('✅ 所有投诉已确认处理完成!'); } // 处理评价表单提交 onReviewSubmitted(reviewData: any): void { console.log('客户评价已提交:', reviewData); // 这里应该调用API将评价数据保存到服务器 // 模拟API调用 setTimeout(() => { alert('客户评价提交成功!评价数据已保存。'); // 更新本地反馈数据 const newFeedback: CustomerFeedback = { id: Date.now().toString(), customerName: '客户', // 应该从项目信息中获取 rating: reviewData.overallRating, content: reviewData.improvementSuggestions || '无具体建议', createdAt: new Date(), status: '已解决', isSatisfied: reviewData.overallRating >= 4, projectId: this.projectId }; this.feedbacks = [...this.feedbacks, newFeedback]; // 自动标记客户评价完成 this.confirmCustomerReview(); }, 1000); } // 处理评价表单保存草稿 onReviewSaved(reviewData: any): void { console.log('客户评价草稿已保存:', reviewData); // 这里应该调用API将草稿数据保存到服务器 // 模拟API调用 setTimeout(() => { alert('评价草稿保存成功!您可以稍后继续完善。'); }, 500); } // ============ 缺少的方法实现 ============ // 初始化售后模块数据 private initializeAftercareData(): void { // 初始化一些示例全景图合成记录 this.panoramicSyntheses = [ { id: 'panoramic_001', projectId: this.projectId, projectName: '示例项目', spaces: [ { id: 'space_001', name: '客厅', type: 'living_room' as const, imageCount: 3, viewAngle: '客厅-角度1' }, { id: 'space_002', name: '卧室', type: 'bedroom' as const, imageCount: 2, viewAngle: '卧室-角度1' } ], status: 'completed' as const, quality: 'high' as const, createdAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000), updatedAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000), completedAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000), previewUrl: 'https://panoramic.example.com/preview/panoramic_001', downloadUrl: 'https://panoramic.example.com/download/panoramic_001', shareLink: 'https://panoramic.example.com/view/panoramic_001', renderTime: 135, fileSize: 52428800, progress: 100 } ]; // 初始化一些示例结算记录 if (this.settlements.length === 0) { this.settlements = [ { id: 'settlement_001', projectId: this.projectId, projectName: '示例项目', type: 'deposit', amount: 5000, percentage: 30, status: '已结算', createdAt: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000), settledAt: new Date(Date.now() - 28 * 24 * 60 * 60 * 1000) }, { id: 'settlement_002', projectId: this.projectId, projectName: '示例项目', type: 'progress', amount: 7000, percentage: 40, status: '已结算', createdAt: new Date(Date.now() - 15 * 24 * 60 * 60 * 1000), settledAt: new Date(Date.now() - 13 * 24 * 60 * 60 * 1000) } ]; } // 初始化一些示例客户反馈 if (this.feedbacks.length === 0) { this.feedbacks = [ { id: 'feedback_001', projectId: this.projectId, customerName: '张先生', rating: 5, content: '设计师非常专业,效果图很满意!', isSatisfied: true, status: '已解决', createdAt: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000) } ]; } // 初始化一些示例投诉记录 if (this.exceptionHistories.length === 0) { this.exceptionHistories = []; } } // 初始化表单 initializeForms(): void { // 初始化订单分配表单(必填项) this.orderCreationForm = this.fb.group({ orderAmount: ['', [Validators.required, Validators.min(0)]], smallImageDeliveryTime: ['', Validators.required], decorationType: ['', Validators.required], requirementReason: ['', Validators.required], isMultiDesigner: [false] // 移除requiredTrue验证,改为普通布尔值 }); // 初始化可选信息表单 this.optionalForm = this.fb.group({ largeImageDeliveryTime: [''], spaceRequirements: [''], designAngles: [''], specialAreaHandling: [''], materialRequirements: [''], lightingRequirements: [''] }); } // 检查是否可以创建订单 canCreateOrder(): boolean { return this.orderCreationForm ? this.orderCreationForm.valid : false; } // 创建订单 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); // 这里应该调用API创建订单 // 模拟API调用 setTimeout(() => { alert('订单分配成功!'); // 订单分配成功后自动切换到下一环节 this.advanceToNextStage('订单分配'); }, 500); } // 处理空间文件选择 onSpaceFileSelected(event: Event, processId: string, spaceId: string): void { const input = event.target as HTMLInputElement; if (!input.files || input.files.length === 0) return; const files = Array.from(input.files); const process = this.deliveryProcesses.find(p => p.id === processId); if (!process || !process.content[spaceId]) return; files.forEach(file => { if (/\.(jpg|jpeg|png)$/i.test(file.name)) { const imageItem = this.makeImageItem(file); process.content[spaceId].images.push({ id: imageItem.id, name: imageItem.name, url: imageItem.url, size: this.formatFileSize(file.size) }); } }); // 清空输入 input.value = ''; } // 更新模型检查项状态 updateModelCheckItem(itemId: string, isPassed: boolean): void { const item = this.modelCheckItems.find(i => i.id === itemId); if (item) { item.isPassed = isPassed; console.log(`模型检查项 ${item.name} 状态更新为: ${isPassed ? '已通过' : '待处理'}`); } } // 删除空间图片 removeSpaceImage(processId: string, spaceId: string, imageId: string): void { const process = this.deliveryProcesses.find(p => p.id === processId); if (process && process.content[spaceId]) { const images = process.content[spaceId].images; const imageIndex = images.findIndex(img => img.id === imageId); if (imageIndex > -1) { // 释放URL资源 const image = images[imageIndex]; if (image.url && image.url.startsWith('blob:')) { URL.revokeObjectURL(image.url); } // 从数组中移除 images.splice(imageIndex, 1); console.log(`已删除空间图片: ${processId}/${spaceId}/${imageId}`); } } } // 项目复盘相关方法 getReviewStatus(): 'not_started' | 'generating' | 'completed' { if (this.isGeneratingReview) return 'generating'; if (this.projectReview) return 'completed'; return 'not_started'; } getReviewStatusText(): string { const status = this.getReviewStatus(); switch (status) { case 'not_started': return '未开始'; case 'generating': return '生成中'; case 'completed': return '已完成'; default: return '未知'; } } getScoreClass(score: number): string { if (score >= 90) return 'excellent'; if (score >= 80) return 'good'; if (score >= 70) return 'average'; return 'poor'; } getExecutionStatusText(status: 'excellent' | 'good' | 'average' | 'poor'): string { switch (status) { case 'excellent': return '优秀'; case 'good': return '良好'; case 'average': return '一般'; case 'poor': return '较差'; default: return '未知'; } } generateReviewReport(): void { if (this.isGeneratingReview) return; this.isGeneratingReview = true; // 基于真实项目数据生成复盘报告 setTimeout(() => { const sopAnalysisData = this.analyzeSopExecution(); const experienceInsights = this.generateExperienceInsights(); const performanceMetrics = this.calculatePerformanceMetrics(); const plannedBudget = this.quotationData.totalAmount || 150000; const actualBudget = this.calculateActualBudget(); this.projectReview = { id: 'review_' + Date.now(), projectId: this.projectId, generatedAt: new Date(), overallScore: this.calculateOverallScore(), sopAnalysis: sopAnalysisData, keyHighlights: experienceInsights.keyHighlights, improvementSuggestions: experienceInsights.improvementSuggestions, customerSatisfaction: { overallRating: this.reviewStats.overallScore, feedback: this.detailedReviews.length > 0 ? this.detailedReviews[0].overallFeedback : '客户反馈良好,对项目整体满意', responseTime: this.calculateAverageResponseTime(), completionTime: this.calculateProjectDuration() }, teamPerformance: performanceMetrics, budgetAnalysis: { plannedBudget: plannedBudget, actualBudget: actualBudget, variance: this.calculateBudgetVariance(plannedBudget, actualBudget), costBreakdown: [ { category: '设计费', planned: plannedBudget * 0.3, actual: actualBudget * 0.3 }, { category: '材料费', planned: plannedBudget * 0.6, actual: actualBudget * 0.57 }, { category: '人工费', planned: plannedBudget * 0.1, actual: actualBudget * 0.13 } ] }, lessonsLearned: experienceInsights.lessonsLearned, recommendations: experienceInsights.recommendations }; this.isGeneratingReview = false; alert('复盘报告生成完成!基于真实SOP执行数据和智能分析生成。'); }, 3000); } regenerateReviewReport(): void { this.projectReview = null; this.generateReviewReport(); } exportReviewReport(): void { if (!this.projectReview) return; const exportRequest: ReviewReportExportRequest = { projectId: this.projectId, reviewId: this.projectReview.id, format: 'pdf', includeCharts: true, includeDetails: true, language: 'zh-CN' }; this.projectReviewService.exportReviewReport(exportRequest).subscribe({ next: (response) => { if (response.success && response.downloadUrl) { // 创建下载链接 const link = document.createElement('a'); link.href = response.downloadUrl; link.download = response.fileName || `复盘报告_${this.project?.name || '项目'}_${new Date().toISOString().split('T')[0]}.pdf`; document.body.appendChild(link); link.click(); document.body.removeChild(link); alert('复盘报告导出成功!'); } else { alert('导出失败:' + (response.message || '未知错误')); } }, error: (error) => { console.error('导出复盘报告失败:', error); alert('导出失败,请稍后重试'); } }); } shareReviewReport(): void { if (!this.projectReview) return; const shareRequest: ReviewReportShareRequest = { projectId: this.projectId, reviewId: this.projectReview.id, shareType: 'link', expirationDays: 30, allowDownload: true, requirePassword: false }; this.projectReviewService.shareReviewReport(shareRequest).subscribe({ next: (response) => { if (response.success && response.shareUrl) { // 复制到剪贴板 if (navigator.clipboard) { navigator.clipboard.writeText(response.shareUrl).then(() => { alert(`复盘报告分享链接已复制到剪贴板!\n链接有效期:${response.expirationDate ? new Date(response.expirationDate).toLocaleDateString() : '30天'}`); }).catch(() => { alert(`复盘报告分享链接:\n${response.shareUrl}\n\n链接有效期:${response.expirationDate ? new Date(response.expirationDate).toLocaleDateString() : '30天'}`); }); } else { alert(`复盘报告分享链接:\n${response.shareUrl}\n\n链接有效期:${response.expirationDate ? new Date(response.expirationDate).toLocaleDateString() : '30天'}`); } } else { alert('分享失败:' + (response.message || '未知错误')); } }, error: (error) => { console.error('分享复盘报告失败:', error); alert('分享失败,请稍后重试'); } }); } // 项目复盘工具方法 getMaxDuration(): number { if (!this.sopStagesData || this.sopStagesData.length === 0) return 1; return Math.max(...this.sopStagesData.map(s => Math.max(s.plannedDuration, s.actualDuration))); } getAverageScore(): number { if (!this.sopStagesData || this.sopStagesData.length === 0) return 0; const sum = this.sopStagesData.reduce((acc, s) => acc + s.score, 0); return Math.round(sum / this.sopStagesData.length); } getSuggestionCountByPriority(priority: string): number { if (!this.optimizationSuggestions) return 0; return this.optimizationSuggestions.filter(s => s.priority === priority).length; } getAverageImprovementPercent(): number { if (!this.optimizationSuggestions || this.optimizationSuggestions.length === 0) return 0; return 25; } acceptSuggestion(suggestion: any): void { console.log('采纳建议:', suggestion); } viewSuggestionDetail(suggestion: any): void { console.log('查看建议详情:', suggestion); } // 分析SOP执行情况 private analyzeSopExecution(): any[] { const sopStages = [ { name: '需求沟通', planned: 3, actual: 2.5 }, { name: '方案确认', planned: 5, actual: 4 }, { name: '建模', planned: 7, actual: 8 }, { name: '软装', planned: 3, actual: 3.5 }, { name: '渲染', planned: 5, actual: 4.5 }, { name: '后期', planned: 2, actual: 2 } ]; return sopStages.map(stage => { const variance = ((stage.actual - stage.planned) / stage.planned) * 100; let executionStatus: 'excellent' | 'good' | 'average' | 'poor'; let score: number; if (variance <= -10) { executionStatus = 'excellent'; score = 95; } else if (variance <= 0) { executionStatus = 'good'; score = 85; } else if (variance <= 20) { executionStatus = 'average'; score = 70; } else { executionStatus = 'poor'; score = 50; } const issues: string[] = []; if (variance > 20) { issues.push('执行时间超出计划较多'); } if (stage.name === '建模' && variance > 0) { issues.push('建模阶段需要优化流程'); } return { stageName: stage.name, plannedDuration: stage.planned, actualDuration: stage.actual, score, executionStatus, issues: issues.length > 0 ? issues : undefined }; }); } // 生成经验洞察 private generateExperienceInsights(): { keyHighlights: string[]; improvementSuggestions: string[]; lessonsLearned: string[]; recommendations: string[] } { return { keyHighlights: [ '需求沟通阶段效率显著提升,客户满意度高', '渲染质量获得客户高度认可', '团队协作配合默契,沟通顺畅', '项目交付时间控制良好' ], improvementSuggestions: [ '建模阶段可以进一步优化工作流程', '加强前期需求确认的深度和准确性', '建立更完善的质量检查机制', '提升跨部门协作效率' ], lessonsLearned: [ '充分的前期沟通能显著减少后期修改', '标准化流程有助于提高执行效率', '及时的客户反馈对项目成功至关重要', '团队技能匹配度直接影响项目质量' ], recommendations: [ '建议在类似项目中复用成功的沟通模式', '可以将本项目的渲染标准作为团队参考', '建议建立项目经验知识库', '推荐定期进行团队技能培训' ] }; } // 计算绩效指标 private calculatePerformanceMetrics(): { designerScore: number; communicationScore: number; timelinessScore: number; qualityScore: number } { return { designerScore: 88, communicationScore: 92, timelinessScore: 85, qualityScore: 90 }; } // 计算总体评分 private calculateOverallScore(): number { const metrics = this.calculatePerformanceMetrics(); return Math.round((metrics.designerScore + metrics.communicationScore + metrics.timelinessScore + metrics.qualityScore) / 4); } // 计算平均响应时间 private calculateAverageResponseTime(): number { // 模拟计算平均响应时间(小时) return 2.5; } // 计算项目持续时间 private calculateProjectDuration(): number { // 模拟计算项目持续时间(天) return 28; } // 计算实际预算 private calculateActualBudget(): number { // 基于订单金额计算实际预算 return this.orderAmount || 150000; } // 计算预算偏差 private calculateBudgetVariance(plannedBudget: number, actualBudget: number): number { return ((actualBudget - plannedBudget) / plannedBudget) * 100; } formatDateTime(date: Date): string { return date.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); } // ============ 空间管理相关方法 ============ // 添加新空间 addSpace(processId: string): void { const spaceName = this.newSpaceName[processId]?.trim(); if (!spaceName) return; const process = this.deliveryProcesses.find(p => p.id === processId); if (!process) return; // 生成新的空间ID const spaceId = `space_${Date.now()}`; // 添加到spaces数组 const newSpace: DeliverySpace = { id: spaceId, name: spaceName, isExpanded: false, order: process.spaces.length + 1 }; process.spaces.push(newSpace); // 初始化content数据 process.content[spaceId] = { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() }; // 清空输入框并隐藏 this.newSpaceName[processId] = ''; this.showAddSpaceInput[processId] = false; console.log(`已添加空间: ${spaceName} 到流程 ${process.name}`); } // 取消添加空间 cancelAddSpace(processId: string): void { this.newSpaceName[processId] = ''; this.showAddSpaceInput[processId] = false; } // 获取指定流程的活跃空间列表 getActiveProcessSpaces(processId: string): DeliverySpace[] { const process = this.deliveryProcesses.find(p => p.id === processId); if (!process) return []; return process.spaces.sort((a, b) => a.order - b.order); } // 切换空间展开状态 toggleSpace(processId: string, spaceId: string): void { const process = this.deliveryProcesses.find(p => p.id === processId); if (!process) return; const space = process.spaces.find(s => s.id === spaceId); if (space) { space.isExpanded = !space.isExpanded; } } // 获取空间进度 getSpaceProgress(processId: string, spaceId: string): number { const process = this.deliveryProcesses.find(p => p.id === processId); if (!process || !process.content[spaceId]) return 0; return process.content[spaceId].progress || 0; } // 删除空间 removeSpace(processId: string, spaceId: string): void { const process = this.deliveryProcesses.find(p => p.id === processId); if (!process) return; // 从spaces数组中移除 const spaceIndex = process.spaces.findIndex(s => s.id === spaceId); if (spaceIndex > -1) { const spaceName = process.spaces[spaceIndex].name; process.spaces.splice(spaceIndex, 1); // 清理content数据 if (process.content[spaceId]) { // 释放图片URL资源 process.content[spaceId].images.forEach(img => { if (img.url && img.url.startsWith('blob:')) { URL.revokeObjectURL(img.url); } }); delete process.content[spaceId]; } console.log(`已删除空间: ${spaceName} 从流程 ${process.name}`); } } // 触发空间文件输入 triggerSpaceFileInput(processId: string, spaceId: string): void { const inputId = `space-file-input-${processId}-${spaceId}`; const input = document.getElementById(inputId) as HTMLInputElement; if (input) { input.click(); } } // 处理空间文件拖拽 onSpaceFileDrop(event: DragEvent, processId: string, spaceId: string): void { event.preventDefault(); event.stopPropagation(); const files = event.dataTransfer?.files; if (!files || files.length === 0) return; this.handleSpaceFiles(Array.from(files), processId, spaceId); } // 处理空间文件 private handleSpaceFiles(files: File[], processId: string, spaceId: string): void { const process = this.deliveryProcesses.find(p => p.id === processId); if (!process || !process.content[spaceId]) return; files.forEach(file => { if (/\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(file.name)) { const imageItem = this.makeImageItem(file); process.content[spaceId].images.push({ id: imageItem.id, name: imageItem.name, url: imageItem.url, size: this.formatFileSize(file.size), reviewStatus: 'pending' }); // 更新进度 this.updateSpaceProgress(processId, spaceId); } }); } // 获取空间图片列表 getSpaceImages(processId: string, spaceId: string): Array<{ id: string; name: string; url: string; size?: string; reviewStatus?: 'pending' | 'approved' | 'rejected' }> { const process = this.deliveryProcesses.find(p => p.id === processId); if (!process || !process.content[spaceId]) return []; return process.content[spaceId].images || []; } // 确认阶段上传并推进工作流 confirmStageUpload(stageId: string): void { const stageMap: { [key: string]: ProjectStage } = { 'modeling': '建模', 'softDecor': '软装', 'rendering': '渲染', 'postProduction': '后期' }; const currentStage = stageMap[stageId]; if (!currentStage) return; // 检查当前阶段是否有上传的图片 const process = this.deliveryProcesses.find(p => p.id === stageId); if (!process) return; const hasImages = Object.values(process.content).some(space => space.images && space.images.length > 0 ); if (!hasImages) { alert('请先上传图片再确认'); return; } // 推进到下一阶段 const currentIndex = this.stageOrder.indexOf(currentStage); if (currentIndex < this.stageOrder.length - 1) { const nextStage = this.stageOrder[currentIndex + 1]; // 更新当前阶段 this.currentStage = nextStage; if (this.project) { this.project.currentStage = nextStage; } // 展开下一阶段 this.expandedStages[nextStage] = true; // 滚动到下一阶段 setTimeout(() => { this.scrollToStage(nextStage); }, 100); console.log(`阶段推进: ${currentStage} -> ${nextStage}`); } else { // 如果是最后一个阶段,标记为完成 console.log(`交付执行阶段完成: ${currentStage}`); alert('交付执行阶段已完成!'); } } // 检查是否可以确认阶段上传 canConfirmStageUpload(stageId: string): boolean { // 检查是否有上传的图片 const process = this.deliveryProcesses.find(p => p.id === stageId); if (!process) return false; return Object.values(process.content).some(space => space.images && space.images.length > 0 ); } // 获取空间备注 getSpaceNotes(processId: string, spaceId: string): string { const process = this.deliveryProcesses.find(p => p.id === processId); if (!process || !process.content[spaceId]) return ''; return process.content[spaceId].notes || ''; } // 更新空间备注 updateSpaceNotes(processId: string, spaceId: string, notes: string): void { const process = this.deliveryProcesses.find(p => p.id === processId); if (!process || !process.content[spaceId]) return; process.content[spaceId].notes = notes; process.content[spaceId].lastUpdated = new Date(); console.log(`已更新空间备注: ${processId}/${spaceId}`); } // 更新空间进度 private updateSpaceProgress(processId: string, spaceId: string): void { const process = this.deliveryProcesses.find(p => p.id === processId); if (!process || !process.content[spaceId]) return; const content = process.content[spaceId]; const imageCount = content.images.length; // 根据图片数量和状态计算进度 if (imageCount === 0) { content.progress = 0; content.status = 'pending'; } else if (imageCount < 3) { content.progress = Math.min(imageCount * 30, 90); content.status = 'in_progress'; } else { content.progress = 100; content.status = 'completed'; } content.lastUpdated = new Date(); } // ==================== 功能卡片点击事件 ==================== /** * 显示功能详情 * @param title 功能标题 * @param description 功能详细描述 */ showFeatureDetail(title: string, description: string): void { console.log(`📋 功能详情: ${title}`); console.log(`📝 ${description}`); alert(`✨ ${title}\n\n${description}\n\n点击确定关闭`); } }