12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616 |
- import { Component, OnInit, OnDestroy } 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 {
- Project,
- RenderProgress,
- ModelCheckItem,
- CustomerFeedback,
- DesignerChange,
- Settlement,
- ProjectStage
- } from '../../../models/project.model';
- import { OrderCreationCardComponent } from '../../../shared/components/order-creation-card/order-creation-card';
- import { RequirementsConfirmCardComponent } from '../../../shared/components/requirements-confirm-card/requirements-confirm-card';
- import { SettlementCardComponent } from '../../../shared/components/settlement-card/settlement-card';
- import { CustomerReviewCardComponent } from '../../../shared/components/customer-review-card/customer-review-card';
- import { ComplaintCardComponent } from '../../../shared/components/complaint-card/complaint-card';
- import { VerticalNavComponent } from './components/vertical-nav/vertical-nav.component';
- 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';
- @Component({
- selector: 'app-project-detail',
- standalone: true,
- imports: [CommonModule, FormsModule, ReactiveFormsModule, OrderCreationCardComponent, RequirementsConfirmCardComponent, SettlementCardComponent, CustomerReviewCardComponent, ComplaintCardComponent, VerticalNavComponent],
- templateUrl: './project-detail.html',
- styleUrls: ['./project-detail.scss', './debug-styles.scss']
- })
- export class ProjectDetail implements OnInit, OnDestroy {
- // 项目基本数据
- projectId: string = '';
- project: Project | undefined;
- renderProgress: RenderProgress | undefined;
- modelCheckItems: ModelCheckItem[] = [];
- feedbacks: CustomerFeedback[] = [];
- 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 = '';
- // 新增:10阶段顺序(串式流程)
- stageOrder: ProjectStage[] = ['订单创建', '需求沟通', '方案确认', '建模', '软装', '渲染', '尾款结算', '客户评价', '投诉处理'];
- // 新增:阶段展开状态(默认全部收起,当前阶段在数据加载后自动展开)
- expandedStages: Partial<Record<ProjectStage, boolean>> = {
- '订单创建': 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 = [
- { 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[] = [];
- // ============ 阶段图片上传状态(新增) ============
- 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; reviewStatus?: 'pending' | 'approved' | 'rejected'; synced?: boolean }> = [];
- showRenderUploadModal: boolean = false;
- pendingRenderLargeItems: Array<{ id: string; name: string; url: string; file: File }> = [];
- // 视图上下文:根据路由前缀识别角色视角(客服/设计师/组长)
- private roleContext: 'customer-service' | 'designer' | 'team-leader' = 'designer';
- constructor(
- private route: ActivatedRoute,
- private projectService: ProjectService,
- private router: Router,
- private fb: FormBuilder,
- ) {}
- // 切换标签页
- 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 } });
- }
- // 返回工作台
- 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;
- const current = 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.route.paramMap.subscribe(params => {
- this.projectId = params.get('id') || '';
- // 根据当前URL检测视图上下文
- this.roleContext = this.detectRoleContextFromUrl();
- this.loadProjectData();
- this.loadExceptionHistories();
- this.loadProjectMembers();
- this.loadProjectFiles();
- this.loadTimelineEvents();
- });
- // 新增:监听查询参数,支持通过 activeTab 设置初始标签页
- this.route.queryParamMap.subscribe(qp => {
- const raw = qp.get('activeTab');
- const alias: Record<string, 'progress' | 'members' | 'files'> = {
- requirements: 'progress',
- overview: 'progress'
- };
- const tab = raw && (raw in alias ? alias[raw] : raw);
- if (tab === 'progress' || tab === 'members' || tab === 'files') {
- this.activeTab = tab;
- }
- });
-
- // 添加点击事件监听器,当点击页面其他位置时关闭下拉菜单
- 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'
- });
- }
- // 在组件销毁时移除事件监听器和清理资源
- ngOnDestroy(): void {
- if (this.countdownInterval) {
- clearInterval(this.countdownInterval);
- }
- 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' {
- const url = this.router.url || '';
- if (url.includes('/customer-service/')) return 'customer-service';
- if (url.includes('/team-leader/')) return 'team-leader';
- return 'designer';
- }
- isDesignerView(): boolean { return this.roleContext === 'designer'; }
- isTeamLeaderView(): boolean { return this.roleContext === 'team-leader'; }
- isCustomerServiceView(): boolean { return this.roleContext === 'customer-service'; }
- // 只读规则:客服视角为只读
- isReadOnly(): boolean { return this.isCustomerServiceView(); }
- // 计算当前激活板块:优先用户点击的 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'): 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);
- alert('已同步该阶段的图片信息(模拟)');
- }
- reviewImage(imageId: string, phase: 'white' | 'soft' | 'render', 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);
- }
- getImageReviewStatusText(img: { reviewStatus?: 'pending'|'approved'|'rejected'; synced?: boolean }): string {
- const synced = img.synced ? '已同步' : '未同步';
- const map: Record<string, string> = {
- '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.loadModelCheckItems();
- 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 {
- this.projectService.getProjectById(this.projectId).subscribe(project => {
- this.project = project;
- // 设置当前阶段
- if (project) {
- this.currentStage = project.currentStage || '';
- // 重置展开状态并默认展开当前阶段
- this.stageOrder.forEach(s => this.expandedStages[s] = false);
- if (this.stageOrder.includes(project.currentStage)) {
- this.expandedStages[project.currentStage] = true;
- }
- // 新增:根据当前阶段默认展开所属板块
- const currentSec = this.getSectionKeyForStage(project.currentStage as ProjectStage);
- this.expandedSection = currentSec;
- }
- // 检查技能匹配度
- this.checkSkillMismatch();
- });
- }
-
- // 整理项目详情
- 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);
- }
- loadModelCheckItems(): void {
- this.projectService.getModelCheckItems().subscribe(items => {
- this.modelCheckItems = items;
- });
- }
- 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;
- });
- }
- updateModelCheckItem(itemId: string, isPassed: boolean): void {
- this.projectService.updateModelCheckItem(itemId, isPassed).subscribe(() => {
- this.loadModelCheckItems(); // 重新加载检查项
- });
- }
- 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.loadProjectDetails(); // 重新加载项目详情
- });
- }
- }
- // 新增:根据给定阶段跳转到下一阶段
- 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;
- }
- }
- 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<ProjectStage, '待分配' | '需求方案' | '项目执行' | '收尾验收' | '归档'> = {
- '订单创建': '待分配',
- '需求沟通': '需求方案',
- '方案确认': '需求方案',
- '建模': '项目执行',
- '软装': '项目执行',
- '渲染': '项目执行',
- '后期': '项目执行',
- '尾款结算': '收尾验收',
- '客户评价': '收尾验收',
- '投诉处理': '收尾验收'
- };
- 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<void> {
- 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<boolean> {
- 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();
- }
- // 检查是否所有模型检查项都已通过
- areAllModelChecksPassed(): boolean {
- return this.modelCheckItems.every(item => item.isPassed);
- }
- // 获取技能匹配度警告
- 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<string, string> = {
- '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 {
- if (this.whiteModelImages.length === 0) 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 {
- this.previewImageData = img;
- this.showImagePreview = true;
- }
- closeImagePreview(): void {
- this.showImagePreview = false;
- this.previewImageData = null;
- }
- downloadImage(img: any): void {
- if (img) {
- const link = document.createElement('a');
- link.href = img.url;
- link.download = img.name;
- link.click();
- }
- }
- removeImageFromPreview(): void {
- if (this.previewImageData) {
- // 根据图片类型调用相应的删除方法
- 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);
- }
- 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'): 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;
- }
- }
- // 触发文件输入框
- triggerFileInput(type: 'whiteModel' | 'softDecor' | 'render'): void {
- let inputId: string;
- switch (type) {
- case 'whiteModel':
- inputId = 'whiteModelFileInput';
- break;
- case 'softDecor':
- inputId = 'softDecorFileInput';
- break;
- case 'render':
- inputId = 'renderFileInput';
- 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);
- }
- // 新增:软装阶段 确认上传并自动进入下一阶段(渲染)
- confirmSoftDecorUpload(): void {
- if (this.softDecorImages.length === 0) 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<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));
- 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)
- });
- }
- input.value = '';
- }
-
- confirmRenderUpload(): void {
- // 将待确认的图片加入正式列表(此处模拟上传成功)
- const toAdd = this.pendingRenderLargeItems.map(i => ({ id: i.id, name: i.name, url: i.url, size: this.formatFileSize(i.file.size) }));
- this.renderLargeImages.unshift(...toAdd);
- this.closeRenderUploadModal();
- // 新增:渲染阶段确认后,自动进入下一阶段(后期)
- this.advanceToNextStage('渲染');
- }
-
- 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 '渲染':
- return 'delivery';
- case '尾款结算':
- case '客户评价':
- case '投诉处理':
- return 'aftercare';
- default:
- return 'order';
- }
- }
- // 获取板块状态:completed | 'active' | 'pending'
- getSectionStatus(key: SectionKey): 'completed' | 'active' | 'pending' {
- const current = this.project?.currentStage as ProjectStage | undefined;
- if (!current) return '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<ProjectStage, string> = {
- '订单创建': 'order',
- '需求沟通': 'requirements-talk',
- '方案确认': 'proposal-confirm',
- '建模': 'modeling',
- '软装': 'softdecor',
- '渲染': 'render',
- '后期': 'aftercare',
- '尾款结算': '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 = '';
- 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;
- }, 1000);
- }
- 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 || []
- };
- }
- 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: ['金属', '玻璃']
- }
- };
- }
- }
- // 获取同步的关键信息摘要
- 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;
- }
- }
|