import { Component, OnInit, Input, Output, EventEmitter, HostBinding } from '@angular/core'; import { CommonModule } from '@angular/common'; import { Router, ActivatedRoute } from '@angular/router'; import { IonicModule } from '@ionic/angular'; import { WxworkSDK, WxworkCorp } from 'fmode-ng/core'; import { FmodeParse, FmodeObject } from 'fmode-ng/parse'; import { WxworkSDKService } from '../../services/wxwork-sdk.service'; import { ProfileService } from '../../../../app/services/profile.service'; const Parse = FmodeParse.with('nova'); /** * 客户画像组件 * * 功能: * 1. 展示客户基础信息(权限控制:手机号仅客服/组长可见) * 2. 客户画像标签(风格偏好、预算、色彩氛围) * 3. 所在群聊列表(点击可跳转企微群聊) * 4. 历史项目列表 * 5. 跟进记录时间线 * * 路由:/wxwork/:cid/customer/:contactId */ @Component({ selector: 'app-contact', standalone: true, imports: [CommonModule, IonicModule], templateUrl: './contact.component.html', styleUrls: ['./contact.component.scss'] }) export class CustomerProfileComponent implements OnInit { // 输入参数(支持组件复用) @Input() customer: FmodeObject | null = null; @Input() currentUser: FmodeObject | null = null; // 新增:嵌入模式与项目过滤 @Input() embeddedMode: boolean = false; @Input() projectIdFilter: string | null = null; @Output() close = new EventEmitter(); @HostBinding('class.embedded') get embeddedClass() { return this.embeddedMode; } // 路由参数 cid: string = ''; contactId: string = ''; profileId: string = ''; externalUserId: string = ''; // 从企微进入时的 external_userid // 企微SDK wxwork: WxworkSDK | null = null; wecorp: WxworkCorp | null = null; wxAuth: any = null; // WxworkAuth 实例 // 加载状态 loading: boolean = true; error: string | null = null; refreshing: boolean = false; // 客户数据 contactInfo: FmodeObject | null = null; // 客户画像数据 profile: { basic: { name: string; mobile: string; wechat: string; avatar: string; source: string; tags: string[]; }; preferences: { style: string[]; budget: { min: number; max: number }; colorAtmosphere: string; demandType: string; }; groups: Array<{ groupChat: FmodeObject; project: FmodeObject | null; }>; projects: FmodeObject[]; followUpRecords: Array<{ time: Date; type: string; content: string; operator: string; }>; } = { basic: { name: '', mobile: '', wechat: '', avatar: '', source: '', tags: [] }, preferences: { style: [], budget: { min: 0, max: 0 }, colorAtmosphere: '', demandType: '' }, groups: [], projects: [], followUpRecords: [] }; // 权限控制 canViewSensitiveInfo: boolean = false; constructor( private router: Router, private route: ActivatedRoute, private wxworkService: WxworkSDKService, private profileService: ProfileService ) {} async ngOnInit() { // 获取路由参数 this.cid = this.route.snapshot.paramMap.get('cid') || ''; this.contactId = this.route.snapshot.paramMap.get('contactId') || ''; this.profileId = this.route.snapshot.queryParamMap.get('profileId') || ''; this.externalUserId = this.route.snapshot.queryParamMap.get('externalUserId') || ''; // 如果有Input传入,直接使用 if (this.customer) { this.contactInfo = this.customer; } // 初始化企微授权(不阻塞页面加载) 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); } } } // 检查权限 const role = this.currentUser?.get('roleName'); this.canViewSensitiveInfo = ['客服', '组长', '管理员'].includes(role); // 3. 加载客户信息 if (!this.contactInfo) { if (this.contactId) { // 通过 contactId 加载(从后台进入) const query = new Parse.Query('ContactInfo'); this.contactInfo = await query.get(this.contactId); } else if (this.externalUserId) { // 通过 external_userid 查找(从企微进入) const companyId = this.currentUser?.get('company')?.id; if (companyId) { const query = new Parse.Query('ContactInfo'); query.equalTo('external_userid', this.externalUserId); query.equalTo('company', companyId); this.contactInfo = await query.first(); if (!this.contactInfo) { throw new Error('未找到客户信息,请先在企微中添加该客户'); } } } } // 4. 构建客户画像 if (this.contactInfo) { await this.buildCustomerProfile(); } else { throw new Error('无法加载客户信息'); } } catch (err: any) { console.error('加载失败:', err); this.error = err.message || '加载失败'; } finally { this.loading = false; } } /** * 构建客户画像 */ async buildCustomerProfile() { if (!this.contactInfo) return; const data = this.contactInfo.get('data') || {}; // 基础信息 this.profile.basic = { name: this.contactInfo.get('name') || '', mobile: this.canViewSensitiveInfo ? (this.contactInfo.get('mobile') || '') : '***', wechat: this.canViewSensitiveInfo ? (data.wechat || '') : '***', avatar: data.avatar || '', source: this.contactInfo.get('source') || '其他', tags: data.tags?.preferenceTags || [] }; // 客户画像 this.profile.preferences = { style: data.tags?.preference ? [data.tags.preference] : [], budget: data.tags?.budget || { min: 0, max: 0 }, colorAtmosphere: data.tags?.colorAtmosphere || '', demandType: data.demandType || '' }; // 加载所在群聊 await this.loadGroupChats(); // 加载历史项目 await this.loadProjects(); // 加载跟进记录 await this.loadFollowUpRecords(); } /** * 加载所在群聊 */ async loadGroupChats() { try { // 查询包含该客户的群聊 const query = new Parse.Query('GroupChat'); query.include("project"); query.equalTo('company', this.currentUser!.get('company')); query.notEqualTo('isDeleted', true); const groups = await query.find(); // 过滤包含该客户的群聊 const externalUserId = this.contactInfo!.get('external_userid'); const filteredGroups = groups.filter((g: any) => { const memberList = g.get('member_list') || []; return memberList.some((m: any) => m.type === 2 && m.userid === externalUserId ); }); // 加载群聊关联的项目 this.profile.groups = await Promise.all( filteredGroups.map(async (groupChat: any) => { let project = groupChat.get('project'); return { groupChat, project }; }) ); } catch (err) { console.error('加载群聊失败:', err); } } /** * 加载历史项目 */ async loadProjects() { try { const query = new Parse.Query('Project'); query.equalTo('customer', this.contactInfo!.toPointer()); query.notEqualTo('isDeleted', true); query.descending('updatedAt'); query.limit(10); this.profile.projects = await query.find(); } catch (err) { console.error('加载项目失败:', err); } } /** * 加载跟进记录 */ async loadFollowUpRecords() { try { // 使用 ContactFollow 表,默认按项目过滤 const query = new Parse.Query('ContactFollow'); query.equalTo('contact', this.contactInfo!.toPointer()); query.notEqualTo('isDeleted', true); if (this.projectIdFilter) { const project = new Parse.Object('Project'); project.id = this.projectIdFilter; query.equalTo('project', project.toPointer()); } query.descending('createdAt'); query.limit(50); const records = await query.find(); this.profile.followUpRecords = records.map((rec: any) => ({ time: rec.get('createdAt'), type: rec.get('type') || 'message', content: rec.get('content') || '', operator: rec.get('sender')?.get('name') || '系统' })); // 若无 ContactFollow 记录,则兼容 data.follow_user if (this.profile.followUpRecords.length === 0) { const data = this.contactInfo!.get('data') || {}; const followUsers = data.follow_user || []; this.profile.followUpRecords = followUsers.map((fu: any) => { // 处理操作员名称,避免显示企微userid let operatorName = '企微用户'; if (fu.remark && fu.remark.length < 50) { operatorName = fu.remark; } else if (fu.name && fu.name.length < 50 && !fu.name.startsWith('woAs2q')) { operatorName = fu.name; } return { time: fu.createtime ? new Date(fu.createtime * 1000) : new Date(), type: 'follow', content: '添加客户', operator: operatorName }; }); } } catch (err) { console.error('加载跟进记录失败:', err); } } /** * 跳转到群聊 */ async navigateToGroupChat(chatId: string) { try { // 使用企微SDK服务跳转到群聊 await this.wxworkService.openChat(chatId); } catch (error: any) { console.error('跳转群聊失败:', error); window?.fmode?.alert('跳转失败,请在企业微信中操作'); } } /** * 跳转到项目详情 */ navigateToProject(project: FmodeObject) { this.router.navigate(['/wxwork', this.cid, 'project', project.id], { queryParams: { profileId: this.currentUser!.id } }); } /** * 返回 */ goBack() { // 嵌入模式下不跳转,触发关闭 if (this.embeddedMode) { this.close.emit(); return; } this.router.navigate(['/wxwork', this.cid, 'project-loader']); } /** * 获取来源图标 */ getSourceIcon(source: string): string { const iconMap: any = { '朋友圈': 'logo-wechat', '信息流': 'newspaper-outline', '转介绍': 'people-outline', '其他': 'help-circle-outline' }; return iconMap[source] || 'help-circle-outline'; } /** * 获取项目状态类 */ getProjectStatusClass(status: string): string { const classMap: any = { '待分配': 'status-pending', '进行中': 'status-active', '已完成': 'status-completed', '已暂停': 'status-paused', '已取消': 'status-cancelled' }; return classMap[status] || 'status-default'; } /** * 获取跟进类型图标 */ getFollowUpIcon(type: string): string { const iconMap: any = { 'message': 'chatbubble-outline', 'call': 'call-outline', 'meeting': 'people-outline', 'email': 'mail-outline', 'follow': 'person-add-outline' }; return iconMap[type] || 'ellipsis-horizontal-outline'; } /** * 格式化日期 */ formatDate(date: Date): string { if (!date) return ''; const d = new Date(date); const now = new Date(); const diff = now.getTime() - d.getTime(); const days = Math.floor(diff / (1000 * 60 * 60 * 24)); if (days === 0) { return '今天'; } else if (days === 1) { return '昨天'; } else if (days < 7) { return `${days}天前`; } else { return `${d.getMonth() + 1}/${d.getDate()}`; } } /** * 格式化预算 */ formatBudget(budget: { min: number; max: number }): string { if (!budget || (!budget.min && !budget.max)) return '未设置'; if (budget.min === budget.max) return `¥${budget.min}`; return `¥${budget.min} - ¥${budget.max}`; } /** 刷新客户数据(基于 external_userid 拉取企微数据并保存) */ async refreshContactData() { try { if (!this.contactInfo) return; const externalUserId = this.contactInfo.get('external_userid'); const companyId = this.currentUser?.get('company')?.id || this.contactInfo.get('company')?.id || localStorage.getItem('company'); if (!externalUserId || !companyId) { window?.fmode?.alert('无法刷新:缺少企业或external_userid'); return; } this.refreshing = true; const corp = new WxworkCorp(companyId); const extData = await corp.externalContact.get(externalUserId); const ext = (extData && extData.external_contact) ? extData.external_contact : {}; const follow = (extData && extData.follow_user) ? extData.follow_user : []; if (ext.name) this.contactInfo.set('name', ext.name); const prev = this.contactInfo.get('data') || {}; const mapped = { ...prev, external_contact: ext, follow_user: follow, name: ext.name, avatar: ext.avatar, gender: ext.gender, type: ext.type } as any; this.contactInfo.set('data', mapped); await this.contactInfo.save(); await this.buildCustomerProfile(); } catch (e) { console.warn('刷新客户数据失败:', e); window?.fmode?.alert('刷新失败,请稍后重试'); } finally { this.refreshing = false; } } }