import { Component, OnInit, Input } from '@angular/core'; import { CommonModule } from '@angular/common'; import { Router, ActivatedRoute, RouterModule } from '@angular/router'; import { IonicModule } from '@ionic/angular'; import { WxworkSDK, WxworkCorp } from 'fmode-ng/core'; import { FmodeParse, FmodeObject } from 'fmode-ng/parse'; import { ProfileService } from '../../../../app/services/profile.service'; import { ProjectBottomCardComponent } from '../../components/project-bottom-card/project-bottom-card.component'; import { ProjectFilesModalComponent } from '../../components/project-files-modal/project-files-modal.component'; import { ProjectMembersModalComponent } from '../../components/project-members-modal/project-members-modal.component'; const Parse = FmodeParse.with('nova'); /** * 项目详情核心组件 * * 功能: * 1. 展示四阶段导航(订单分配、确认需求、交付执行、售后归档) * 2. 根据角色控制权限 * 3. 子路由切换阶段内容 * 4. 支持@Input和路由参数两种数据加载方式 * * 路由:/wxwork/:cid/project/:projectId */ @Component({ selector: 'app-project-detail', standalone: true, imports: [CommonModule, IonicModule, RouterModule, ProjectBottomCardComponent, ProjectFilesModalComponent, ProjectMembersModalComponent], templateUrl: './project-detail.component.html', styleUrls: ['./project-detail.component.scss'] }) export class ProjectDetailComponent implements OnInit { // 输入参数(支持组件复用) @Input() project: FmodeObject | null = null; @Input() groupChat: FmodeObject | null = null; @Input() currentUser: FmodeObject | null = null; // 路由参数 cid: string = ''; projectId: string = ''; groupId: string = ''; profileId: string = ''; chatId: string = ''; // 从企微进入时的 chat_id // 企微SDK wxwork: WxworkSDK | null = null; wecorp: WxworkCorp | null = null; wxAuth: any = null; // WxworkAuth 实例 // 加载状态 loading: boolean = true; error: string | null = null; // 项目数据 customer: FmodeObject | null = null; assignee: FmodeObject | null = null; // 当前阶段 currentStage: string = 'order'; // order | requirements | delivery | aftercare stages = [ { id: 'order', name: '订单分配', icon: 'document-text-outline', number: 1 }, { id: 'requirements', name: '确认需求', icon: 'checkmark-circle-outline', number: 2 }, { id: 'delivery', name: '交付执行', icon: 'rocket-outline', number: 3 }, { id: 'aftercare', name: '售后归档', icon: 'archive-outline', number: 4 } ]; // 权限 canEdit: boolean = false; canViewCustomerPhone: boolean = false; role: string = ''; // 模态框状态 showFilesModal: boolean = false; showMembersModal: boolean = false; constructor( private router: Router, private route: ActivatedRoute, private profileService: ProfileService ) {} async ngOnInit() { // 获取路由参数 this.cid = this.route.snapshot.paramMap.get('cid') || ''; this.projectId = this.route.snapshot.paramMap.get('projectId') || ''; this.groupId = this.route.snapshot.queryParamMap.get('groupId') || ''; this.profileId = this.route.snapshot.queryParamMap.get('profileId') || ''; this.chatId = this.route.snapshot.queryParamMap.get('chatId') || ''; // 监听路由变化 this.route.firstChild?.url.subscribe((segments) => { if (segments.length > 0) { this.currentStage = segments[0].path; } }); // 初始化企微授权(不阻塞页面加载) this.initWxworkAuth(); await this.loadData(); } /** * 初始化企微授权(不阻塞页面) */ async initWxworkAuth() { if (!this.cid) return; try { // 动态导入 WxworkAuth 避免导入错误 const { WxworkAuth } = await import('fmode-ng/core'); this.wxAuth = new WxworkAuth({ cid: this.cid, appId: 'crm' }); // 静默授权并同步 Profile,不阻塞页面 const { profile } = await this.wxAuth.authenticateAndLogin(); if (profile) { this.profileService.setCurrentProfile(profile); } } catch (error) { console.warn('企微授权失败:', error); // 授权失败不影响页面加载,继续使用其他方式加载数据 } } /** * 加载数据 */ async loadData() { try { this.loading = true; // 1. 初始化SDK(用于企微API调用,不需要等待授权) if (!this.wxwork && this.cid) { this.wxwork = new WxworkSDK({ cid: this.cid, appId: 'crm' }); this.wecorp = new WxworkCorp(this.cid); } // 2. 获取当前用户(优先从全局服务获取) if (!this.currentUser) { // 优先级1: 使用 profileId 参数 if (this.profileId) { this.currentUser = await this.profileService.getProfileById(this.profileId); } // 优先级2: 从全局服务获取当前 Profile if (!this.currentUser) { this.currentUser = await this.profileService.getCurrentProfile(this.cid); } // 优先级3: 企微环境下尝试从SDK获取 if (!this.currentUser && this.wxwork) { try { this.currentUser = await this.wxwork.getCurrentUser(); } catch (err) { console.warn('无法从企微SDK获取用户:', err); } } } // 设置权限 this.role = this.currentUser?.get('roleName') || ''; this.canEdit = ['客服', '组员', '组长', '管理员'].includes(this.role); this.canViewCustomerPhone = ['客服', '组长', '管理员'].includes(this.role); // 3. 加载项目 if (!this.project) { if (this.projectId) { // 通过 projectId 加载(从后台进入) const query = new Parse.Query('Project'); query.include('customer', 'assignee','department','department.leader'); this.project = await query.get(this.projectId); } else if (this.chatId) { // 通过 chat_id 查找项目(从企微群聊进入) const companyId = this.currentUser?.get('company')?.id; if (companyId) { // 先查找 GroupChat const gcQuery = new Parse.Query('GroupChat'); gcQuery.equalTo('chat_id', this.chatId); gcQuery.equalTo('company', companyId); let groupChat = await gcQuery.first(); if(!groupChat?.id){ const gcQuery2 = new Parse.Query('GroupChat'); gcQuery2.equalTo('project', this.projectId); gcQuery2.equalTo('company', companyId); groupChat = await gcQuery2.first(); } if (groupChat) { this.groupChat = groupChat; const projectPointer = groupChat.get('project'); if (projectPointer) { const pQuery = new Parse.Query('Project'); pQuery.include('customer', 'assignee','department','department.leader'); this.project = await pQuery.get(projectPointer.id); } } if (!this.project) { throw new Error('该群聊尚未关联项目,请先在后台创建项目'); } } } } if (!this.project) { throw new Error('无法加载项目信息'); } this.customer = this.project.get('customer'); this.assignee = this.project.get('assignee'); // 4. 加载群聊(如果没有传入且有groupId) if (!this.groupChat && this.groupId) { try { const gcQuery = new Parse.Query('GroupChat'); this.groupChat = await gcQuery.get(this.groupId); } catch (err) { console.warn('加载群聊失败:', err); } } // 5. 根据项目当前阶段设置默认路由 const projectStage = this.project.get('currentStage'); const stageMap: any = { '订单分配': 'order', '确认需求': 'requirements', '方案确认': 'requirements', '方案深化': 'requirements', '交付执行': 'delivery', '建模': 'delivery', '软装': 'delivery', '渲染': 'delivery', '后期': 'delivery', '尾款结算': 'aftercare', '客户评价': 'aftercare', '投诉处理': 'aftercare' }; const targetStage = stageMap[projectStage] || 'order'; // 如果当前没有子路由,跳转到对应阶段 if (!this.route.firstChild) { this.router.navigate([targetStage], { relativeTo: this.route, replaceUrl: true }); } } catch (err: any) { console.error('加载失败:', err); this.error = err.message || '加载失败'; } finally { this.loading = false; } } /** * 切换阶段 */ switchStage(stageId: string) { this.currentStage = stageId; this.router.navigate([stageId], { relativeTo: this.route }); } /** * 获取阶段状态 */ getStageStatus(stageId: string): 'completed' | 'active' | 'pending' { const projectStage = this.project?.get('currentStage') || ''; const stageOrder = ['订单分配', '确认需求', '建模', '软装', '渲染', '后期', '尾款结算', '客户评价']; const currentIndex = stageOrder.indexOf(projectStage); const stageIndexMap: any = { 'order': 0, 'requirements': 1, 'delivery': 3, 'aftercare': 6 }; const targetIndex = stageIndexMap[stageId]; if (currentIndex > targetIndex) { return 'completed'; } else if (this.currentStage === stageId) { return 'active'; } else { return 'pending'; } } /** * 返回 */ goBack() { let ua = navigator.userAgent.toLowerCase(); let isWeixin = ua.indexOf("micromessenger") != -1; if(isWeixin){ this.router.navigate(['/wxwork', this.cid, 'project-loader']); }else{ history.back(); } } /** * 更新项目阶段 */ async updateProjectStage(stage: string) { if (!this.project || !this.canEdit) return; try { this.project.set('currentStage', stage); await this.project.save(); // 添加阶段历史 const data = this.project.get('data') || {}; const stageHistory = data.stageHistory || []; stageHistory.push({ stage, startTime: new Date(), status: 'current', operator: { id: this.currentUser!.id, name: this.currentUser!.get('name'), role: this.role } }); this.project.set('data', { ...data, stageHistory }); await this.project.save(); } catch (err) { console.error('更新阶段失败:', err); alert('更新失败'); } } /** * 发送企微消息 */ async sendWxMessage(message: string) { if (!this.groupChat || !this.wecorp) return; try { const chatId = this.groupChat.get('chat_id'); await this.wecorp.appchat.sendText(chatId, message); } catch (err) { console.error('发送消息失败:', err); } } /** * 选择客户(从群聊成员中选择外部联系人) */ async selectCustomer() { if (!this.canEdit || !this.groupChat) return; try { const memberList = this.groupChat.get('member_list') || []; const externalMembers = memberList.filter((m: any) => m.type === 2); if (externalMembers.length === 0) { alert('当前群聊中没有外部联系人'); return; } // 简单实现:选择第一个外部联系人 // TODO: 实现选择器UI const selectedMember = externalMembers[0]; await this.setCustomerFromMember(selectedMember); } catch (err) { console.error('选择客户失败:', err); alert('选择客户失败'); } } /** * 从群成员设置客户 */ async setCustomerFromMember(member: any) { if (!this.wecorp) return; try { const companyId = this.currentUser?.get('company')?.id; if (!companyId) throw new Error('无法获取企业信息'); // 1. 查询是否已存在 ContactInfo const query = new Parse.Query('ContactInfo'); query.equalTo('external_userid', member.userid); query.equalTo('company', companyId); let contactInfo = await query.first(); // 2. 如果不存在,通过企微API获取并创建 if (!contactInfo) { const externalContactData = await this.wecorp.externalContact.get(member.userid); const ContactInfo = Parse.Object.extend('ContactInfo'); contactInfo = new ContactInfo(); contactInfo.set('name', externalContactData.name); contactInfo.set('external_userid', member.userid); const company = new Parse.Object('Company'); company.id = companyId; const companyPointer = company.toPointer(); contactInfo.set('company', companyPointer); contactInfo.set('data', { avatar: externalContactData.avatar, type: externalContactData.type, gender: externalContactData.gender, follow_user: externalContactData.follow_user }); await contactInfo.save(); } // 3. 设置为项目客户 if (this.project) { this.project.set('customer', contactInfo.toPointer()); await this.project.save(); this.customer = contactInfo; alert('客户设置成功'); } } catch (err) { console.error('设置客户失败:', err); throw err; } } /** * 显示文件模态框 */ showFiles() { this.showFilesModal = true; } /** * 显示成员模态框 */ showMembers() { this.showMembersModal = true; } /** * 关闭文件模态框 */ closeFilesModal() { this.showFilesModal = false; } /** * 关闭成员模态框 */ closeMembersModal() { this.showMembersModal = false; } }