contact-selector.component.ts 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
  2. import { CommonModule } from '@angular/common';
  3. import { FormsModule } from '@angular/forms';
  4. import { IonicModule } from '@ionic/angular';
  5. import { FmodeParse, FmodeObject } from 'fmode-ng/parse';
  6. import { WxworkCorp } from 'fmode-ng/core';
  7. import { CustomerProfileComponent } from '../../pages/contact/contact.component';
  8. const Parse: any = FmodeParse.with('nova');
  9. @Component({
  10. selector: 'app-contact-selector',
  11. standalone: true,
  12. imports: [CommonModule, FormsModule, IonicModule, CustomerProfileComponent],
  13. templateUrl: './contact-selector.component.html',
  14. styleUrls: ['./contact-selector.component.scss']
  15. })
  16. export class CustomerSelectorComponent implements OnInit {
  17. @Input() project: FmodeObject | null = null;
  18. @Input() groupChat: FmodeObject | null = null;
  19. @Input() currentUser: FmodeObject | null = null;
  20. @Input() placeholder: string = '请选择项目客户';
  21. @Input() disabled: boolean = false;
  22. @Input() showCreateButton: boolean = true;
  23. @Output() contactSelected = new EventEmitter<{ contact: FmodeObject; isNewCustomer: boolean; action: 'selected' | 'created' | 'updated' }>();
  24. loading: boolean = false;
  25. searchKeyword: string = '';
  26. currentCustomer: FmodeObject | null = null;
  27. availableCustomers: FmodeObject[] = [];
  28. externalMembers: Array<{ userid: string; name?: string }> = [];
  29. unbuiltExternalMembers: Array<{ userid: string; name?: string }> = [];
  30. showCustomerPanel: boolean = false;
  31. get canViewSensitiveInfo(): boolean {
  32. const role = this.currentUser?.get?.('roleName') || '';
  33. return ['客服', '组长', '管理员'].includes(role);
  34. }
  35. async ngOnInit() {
  36. await this.init();
  37. }
  38. private async init() {
  39. if (!this.project || !this.groupChat) return;
  40. try {
  41. this.loading = true;
  42. await this.checkProjectCustomer();
  43. await this.loadExternalMembers();
  44. await this.loadAvailableCustomers();
  45. this.computeUnbuiltMembers();
  46. } finally {
  47. this.loading = false;
  48. }
  49. }
  50. private async checkProjectCustomer() {
  51. const ptr = this.project!.get('contact');
  52. if (!ptr) { this.currentCustomer = null; return; }
  53. try {
  54. if (ptr.id && (ptr as any).get) {
  55. this.currentCustomer = ptr as any;
  56. } else if (ptr.id) {
  57. const query = new Parse.Query('ContactInfo');
  58. this.currentCustomer = await query.get(ptr.id);
  59. }
  60. } catch {
  61. this.currentCustomer = null;
  62. }
  63. }
  64. private async loadExternalMembers() {
  65. const list = this.groupChat!.get('member_list') || [];
  66. const external = Array.isArray(list) ? list.filter((m: any) => m && m.type === 2) : [];
  67. this.externalMembers = external.map((m: any) => ({ userid: m.userid, name: m.name }));
  68. }
  69. private async loadAvailableCustomers() {
  70. const companyId = this.project!.get('company')?.id || localStorage.getItem('company');
  71. if (!companyId) return;
  72. const extIds = this.externalMembers.map(m => m.userid);
  73. if (extIds.length === 0) { this.availableCustomers = []; return; }
  74. const query = new Parse.Query('ContactInfo');
  75. query.equalTo('company', companyId);
  76. query.containedIn('external_userid', extIds);
  77. query.notEqualTo('isDeleted', true);
  78. this.availableCustomers = await query.find();
  79. }
  80. private computeUnbuiltMembers() {
  81. const builtIds = new Set<string>(
  82. this.availableCustomers.map((c: any) => c.get('external_userid')).filter(Boolean)
  83. );
  84. this.unbuiltExternalMembers = this.externalMembers.filter(m => !builtIds.has(m.userid));
  85. }
  86. private getMemberInfo(userid: string): any {
  87. const list = this.groupChat!.get('member_list') || [];
  88. return (list || []).find((m: any) => m && m.userid === userid) || null;
  89. }
  90. get filteredCustomers(): FmodeObject[] {
  91. const kw = (this.searchKeyword || '').trim().toLowerCase();
  92. const base = this.availableCustomers;
  93. if (!kw) return base;
  94. return base.filter(c => {
  95. const name = (c.get('name') || c.get('data')?.name || '').toLowerCase();
  96. const mobile = (c.get('mobile') || '').toLowerCase();
  97. return name.includes(kw) || mobile.includes(kw);
  98. });
  99. }
  100. async selectExistingCustomer(contact: FmodeObject) {
  101. if (this.disabled || !this.project) return;
  102. const nameMissing = !contact.get('name') && !contact.get('data')?.name && !contact.get('data')?.external_contact?.name;
  103. const extid = contact.get('external_userid');
  104. if (nameMissing && extid) {
  105. await this.refreshContactInfo(contact);
  106. }
  107. this.project.set('contact', contact.toPointer());
  108. await this.project.save();
  109. this.currentCustomer = contact;
  110. this.contactSelected.emit({ contact, isNewCustomer: false, action: 'selected' });
  111. }
  112. switchToSelecting() {
  113. this.currentCustomer = null;
  114. this.searchKeyword = '';
  115. }
  116. async createFromMember(memberUserid: string) {
  117. if (this.disabled || !this.project) return;
  118. const companyId = this.project.get('company')?.id || localStorage.getItem('company');
  119. if (!companyId) throw new Error('无法获取企业信息');
  120. const query = new Parse.Query('ContactInfo');
  121. query.equalTo('external_userid', memberUserid);
  122. query.equalTo('company', companyId);
  123. let contactInfo = await query.first();
  124. if (!contactInfo) {
  125. const corp = new WxworkCorp(companyId);
  126. const extData = await corp.externalContact.get(memberUserid);
  127. const ext = (extData && extData.external_contact) ? extData.external_contact : {};
  128. const follow = (extData && extData.follow_user) ? extData.follow_user : [];
  129. const ContactInfo = Parse.Object.extend('ContactInfo');
  130. contactInfo = new ContactInfo();
  131. contactInfo.set('name', ext.name || this.getMemberInfo(memberUserid)?.name || '客户');
  132. contactInfo.set('external_userid', memberUserid);
  133. const company = new Parse.Object('Company');
  134. company.id = companyId;
  135. contactInfo.set('company', company.toPointer());
  136. const mapped = {
  137. external_contact: ext,
  138. follow_user: follow,
  139. member: this.getMemberInfo(memberUserid),
  140. name: ext.name || this.getMemberInfo(memberUserid)?.name,
  141. avatar: ext.avatar,
  142. gender: ext.gender,
  143. type: ext.type
  144. } as any;
  145. contactInfo.set('data', mapped);
  146. contactInfo = await contactInfo.save();
  147. await this.loadAvailableCustomers();
  148. this.computeUnbuiltMembers();
  149. }
  150. this.project.set('contact', contactInfo.toPointer());
  151. await this.project.save();
  152. this.currentCustomer = contactInfo;
  153. this.contactSelected.emit({ contact: contactInfo, isNewCustomer: true, action: 'created' });
  154. }
  155. async refreshContactInfo(contact: any) {
  156. const externalUserId = contact.get('external_userid');
  157. const companyId = this.project?.get('company')?.id || localStorage.getItem('company');
  158. if (!externalUserId || !companyId) return;
  159. const corp = new WxworkCorp(companyId);
  160. const extData = await corp.externalContact.get(externalUserId);
  161. const ext = (extData && extData.external_contact) ? extData.external_contact : {};
  162. const follow = (extData && extData.follow_user) ? extData.follow_user : [];
  163. if (ext.name) contact.set('name', ext.name);
  164. const mapped = {
  165. external_contact: ext,
  166. follow_user: follow,
  167. member: this.getMemberInfo(externalUserId),
  168. name: ext.name || this.getMemberInfo(externalUserId)?.name,
  169. avatar: ext.avatar,
  170. gender: ext.gender,
  171. type: ext.type
  172. } as any;
  173. contact.set('data', mapped);
  174. await contact.save();
  175. }
  176. viewCustomerDetail() { this.showCustomerPanel = true; }
  177. closeCustomerDetail() { this.showCustomerPanel = false; }
  178. }