Parcourir la source

feat: contact select in project detail

ryanemax il y a 1 jour
Parent
commit
2b92eabab9

+ 80 - 80
docs/prd/组件-客户选择.md

@@ -2,7 +2,7 @@
 
 ## 概述
 
-**组件名称**: `app-customer-selector`
+**组件名称**: `app-contact-selector`
 **功能定位**: 基于企微群聊成员的项目客户选择组件
 **应用场景**: 项目管理中为项目指定客户的场景,支持从群聊成员中选择外部用户并自动创建或关联ContactInfo
 
@@ -48,7 +48,7 @@ interface Project {
   objectId: string;
   title: string;
   company: Pointer<Company>;
-  customer?: Pointer<ContactInfo>;  // 项目客户(可选)
+  contact?: Pointer<ContactInfo>;  // 项目客户(可选)
   assignee?: Pointer<Profile>;      // 负责设计师
   // ... 其他字段
 }
@@ -82,8 +82,8 @@ interface CustomerSelectorInputs {
 ```typescript
 interface CustomerSelectorOutputs {
   // 客户选择事件
-  customerSelected: EventEmitter<{
-    customer: Parse.Object;       // 选中的客户对象
+  contactSelected: EventEmitter<{
+    contact: Parse.Object;       // 选中的客户对象
     isNewCustomer: boolean;        // 是否为新创建的客户
     action: 'selected' | 'created' | 'updated'; // 操作类型
   }>;
@@ -125,7 +125,7 @@ graph TD
     C -->|否| E[创建新客户]
     E --> F[设置项目客户]
     D --> F
-    F --> G[触发customerSelected事件]
+    F --> G[触发contactSelected事件]
     G --> H[更新UI状态]
 ```
 
@@ -157,12 +157,12 @@ interface ComponentData {
 
 #### 1.1 项目已有客户状态
 ```html
-<div class="customer-selector has-customer">
-  <div class="current-customer">
+<div class="contact-selector has-contact">
+  <div class="current-contact">
     <ion-avatar>
       <img [src]="currentCustomerAvatar" />
     </ion-avatar>
-    <div class="customer-info">
+    <div class="contact-info">
       <h3>{{ currentCustomer.name }}</h3>
       <p>{{ currentCustomer.mobile }}</p>
     </div>
@@ -175,25 +175,25 @@ interface ComponentData {
 
 #### 1.2 选择客户状态
 ```html
-<div class="customer-selector selecting">
+<div class="contact-selector selecting">
   <ion-searchbar
     [(ngModel)]="searchKeyword"
     placeholder="搜索客户姓名或手机号"
     (ionInput)="onSearchChange($event)">
   </ion-searchbar>
 
-  <div class="customer-list">
+  <div class="contact-list">
     <ion-item
-      *ngFor="let customer of filteredCustomers"
-      (click)="selectCustomer(customer)">
+      *ngFor="let contact of filteredCustomers"
+      (click)="selectCustomer(contact)">
       <ion-avatar slot="start">
-        <img [src]="customerAvatar(customer)" />
+        <img [src]="contactAvatar(contact)" />
       </ion-avatar>
       <ion-label>
-        <h2>{{ customer.name }}</h2>
-        <p>{{ customer.mobile || '未绑定手机' }}</p>
+        <h2>{{ contact.name }}</h2>
+        <p>{{ contact.mobile || '未绑定手机' }}</p>
       </ion-label>
-      <ion-icon name="checkmark" slot="end" *ngIf="isSelected(customer)"></ion-icon>
+      <ion-icon name="checkmark" slot="end" *ngIf="isSelected(contact)"></ion-icon>
     </ion-item>
   </div>
 
@@ -206,7 +206,7 @@ interface ComponentData {
 
 #### 1.3 加载状态
 ```html
-<div class="customer-selector loading">
+<div class="contact-selector loading">
   <ion-spinner name="dots"></ion-spinner>
   <p>{{ loadingText }}</p>
 </div>
@@ -237,7 +237,7 @@ interface ComponentData {
 
 ```typescript
 @Component({
-  selector: 'app-customer-selector',
+  selector: 'app-contact-selector',
   standalone: true,
   imports: [
     CommonModule,
@@ -245,8 +245,8 @@ interface ComponentData {
     IonicModule,
     // 其他依赖
   ],
-  template: './customer-selector.component.html',
-  styleUrls: ['./customer-selector.component.scss']
+  template: './contact-selector.component.html',
+  styleUrls: ['./contact-selector.component.scss']
 })
 export class CustomerSelectorComponent implements OnInit, OnChanges {
   // 输入输出属性
@@ -256,7 +256,7 @@ export class CustomerSelectorComponent implements OnInit, OnChanges {
   @Input() disabled: boolean = false;
   @Input() showCreateButton: boolean = true;
 
-  @Output() customerSelected = new EventEmitter<CustomerSelectedEvent>();
+  @Output() contactSelected = new EventEmitter<CustomerSelectedEvent>();
   @Output() loadingChange = new EventEmitter<boolean>();
   @Output() error = new EventEmitter<ErrorEvent>();
 
@@ -305,9 +305,9 @@ export class CustomerSelectorComponent implements OnInit, OnChanges {
   }
 
   private async checkProjectCustomer(): Promise<void> {
-    const customer = this.project.get('customer');
-    if (customer) {
-      this.currentCustomer = await this.parseService.fetchFullObject(customer);
+    const contact = this.project.get('contact');
+    if (contact) {
+      this.currentCustomer = await this.parseService.fetchFullObject(contact);
     }
   }
 
@@ -336,20 +336,20 @@ export class CustomerSelectorComponent implements OnInit, OnChanges {
     this.availableCustomers = await query.find();
   }
 
-  async selectCustomer(customer: Parse.Object): Promise<void> {
+  async selectCustomer(contact: Parse.Object): Promise<void> {
     this.state = ComponentState.LOADING;
     this.loadingChange.emit(true);
 
     try {
       // 关联客户到项目
-      this.project.set('customer', customer);
+      this.project.set('contact', contact);
       await this.project.save();
 
-      this.currentCustomer = customer;
+      this.currentCustomer = contact;
       this.state = ComponentState.CUSTOMER_EXISTS;
 
-      this.customerSelected.emit({
-        customer,
+      this.contactSelected.emit({
+        contact,
         isNewCustomer: false,
         action: 'selected'
       });
@@ -371,20 +371,20 @@ export class CustomerSelectorComponent implements OnInit, OnChanges {
     });
 
     modal.onDidDismiss().then(async (result) => {
-      if (result.data?.customer) {
-        await this.handleCustomerCreated(result.data.customer);
+      if (result.data?.contact) {
+        await this.handleCustomerCreated(result.data.contact);
       }
     });
 
     await modal.present();
   }
 
-  private async handleCustomerCreated(customer: Parse.Object): Promise<void> {
-    this.currentCustomer = customer;
+  private async handleCustomerCreated(contact: Parse.Object): Promise<void> {
+    this.currentCustomer = contact;
     this.state = ComponentState.CUSTOMER_EXISTS;
 
-    this.customerSelected.emit({
-      customer,
+    this.contactSelected.emit({
+      contact,
       isNewCustomer: true,
       action: 'created'
     });
@@ -393,7 +393,7 @@ export class CustomerSelectorComponent implements OnInit, OnChanges {
   async changeCustomer(): Promise<void> {
     // 重新进入选择状态
     this.currentCustomer = undefined;
-    this.project.unset('customer');
+    this.project.unset('contact');
     await this.project.save();
 
     await this.loadAvailableCustomers();
@@ -417,7 +417,7 @@ export class CustomerSelectorComponent implements OnInit, OnChanges {
 
 ```typescript
 @Component({
-  selector: 'app-create-customer-modal',
+  selector: 'app-create-contact-modal',
   standalone: true,
   imports: [CommonModule, FormsModule, IonicModule],
   template: `
@@ -431,12 +431,12 @@ export class CustomerSelectorComponent implements OnInit, OnChanges {
     </ion-header>
 
     <ion-content class="ion-padding">
-      <form #customerForm="ngForm" (ngSubmit)="onSubmit()">
+      <form #contactForm="ngForm" (ngSubmit)="onSubmit()">
         <ion-item>
           <ion-label position="stacked">客户姓名 *</ion-label>
           <ion-input
             name="name"
-            [(ngModel)]="customerData.name"
+            [(ngModel)]="contactData.name"
             required
             placeholder="请输入客户姓名">
           </ion-input>
@@ -446,7 +446,7 @@ export class CustomerSelectorComponent implements OnInit, OnChanges {
           <ion-label position="stacked">手机号码</ion-label>
           <ion-input
             name="mobile"
-            [(ngModel)]="customerData.mobile"
+            [(ngModel)]="contactData.mobile"
             type="tel"
             placeholder="请输入手机号码">
           </ion-input>
@@ -456,7 +456,7 @@ export class CustomerSelectorComponent implements OnInit, OnChanges {
           <ion-label position="stacked">来源渠道</ion-label>
           <ion-select
             name="source"
-            [(ngModel)]="customerData.source"
+            [(ngModel)]="contactData.source"
             placeholder="请选择来源渠道">
             <ion-select-option value="朋友圈">朋友圈</ion-select-option>
             <ion-select-option value="信息流">信息流</ion-select-option>
@@ -468,7 +468,7 @@ export class CustomerSelectorComponent implements OnInit, OnChanges {
         <ion-button
           type="submit"
           expand="block"
-          [disabled]="!customerForm.valid || loading"
+          [disabled]="!contactForm.valid || loading"
           class="ion-margin-top">
           <ion-spinner *ngIf="loading" name="dots" slot="start"></ion-spinner>
           创建客户
@@ -481,7 +481,7 @@ export class CreateCustomerModalComponent {
   @Input() company!: Parse.Object;
   @Input() project!: Parse.Object;
 
-  customerData = {
+  contactData = {
     name: '',
     mobile: '',
     source: ''
@@ -502,21 +502,21 @@ export class CreateCustomerModalComponent {
     try {
       // 创建ContactInfo记录
       const ContactInfo = Parse.Object.extend('ContactInfo');
-      const customer = new ContactInfo();
+      const contact = new ContactInfo();
 
-      customer.set('name', this.customerData.name);
-      customer.set('mobile', this.customerData.mobile);
-      customer.set('source', this.customerData.source);
-      customer.set('company', this.company);
+      contact.set('name', this.contactData.name);
+      contact.set('mobile', this.contactData.mobile);
+      contact.set('source', this.contactData.source);
+      contact.set('company', this.company);
 
-      await customer.save();
+      await contact.save();
 
       // 关联到项目
-      this.project.set('customer', customer);
+      this.project.set('contact', contact);
       await this.project.save();
 
       this.modalController.dismiss({
-        customer,
+        contact,
         action: 'created'
       });
     } catch (error) {
@@ -536,21 +536,21 @@ export class CreateCustomerModalComponent {
 ### 3. 样式设计
 
 ```scss
-.customer-selector {
+.contact-selector {
   border: 1px solid var(--ion-color-light);
   border-radius: 8px;
   overflow: hidden;
   background: var(--ion-background-color);
 
   // 已有客户状态
-  &.has-customer {
-    .current-customer {
+  &.has-contact {
+    .current-contact {
       display: flex;
       align-items: center;
       padding: 12px 16px;
       gap: 12px;
 
-      .customer-info {
+      .contact-info {
         flex: 1;
 
         h3 {
@@ -570,7 +570,7 @@ export class CreateCustomerModalComponent {
 
   // 选择客户状态
   &.selecting {
-    .customer-list {
+    .contact-list {
       max-height: 300px;
       overflow-y: auto;
 
@@ -639,14 +639,14 @@ export class CreateCustomerModalComponent {
   standalone: true,
   imports: [CustomerSelectorComponent],
   template: `
-    <div class="project-customer-section">
+    <div class="project-contact-section">
       <h3>项目客户</h3>
-      <app-customer-selector
+      <app-contact-selector
         [project]="project"
         [groupChat]="groupChat"
-        (customerSelected)="onCustomerSelected($event)"
+        (contactSelected)="onCustomerSelected($event)"
         (error)="onSelectorError($event)">
-      </app-customer-selector>
+      </app-contact-selector>
     </div>
   `
 })
@@ -655,12 +655,12 @@ export class ProjectDetailComponent {
   @Input() groupChat!: Parse.Object;
 
   onCustomerSelected(event: CustomerSelectedEvent) {
-    console.log('客户已选择:', event.customer);
+    console.log('客户已选择:', event.contact);
     console.log('是否为新客户:', event.isNewCustomer);
 
     if (event.isNewCustomer) {
       // 新客户创建成功后的处理
-      this.showWelcomeMessage(event.customer);
+      this.showWelcomeMessage(event.contact);
     }
   }
 
@@ -679,15 +679,15 @@ export class ProjectDetailComponent {
   standalone: true,
   imports: [CustomerSelectorComponent],
   template: `
-    <app-customer-selector
+    <app-contact-selector
       [project]="project"
       [groupChat]="groupChat"
       placeholder="请为项目指定客户"
       [showCreateButton]="true"
       [filterCriteria]="filterOptions"
       [disabled]="isProjectLocked"
-      (customerSelected)="handleCustomerChange($event)">
-    </app-customer-selector>
+      (contactSelected)="handleCustomerChange($event)">
+    </app-contact-selector>
   `
 })
 export class ProjectSetupComponent {
@@ -755,9 +755,9 @@ private filterCustomers(keyword: string): Parse.Object[] {
   if (!keyword) return this.availableCustomers;
 
   const lowerKeyword = keyword.toLowerCase();
-  return this.availableCustomers.filter(customer => {
-    const name = (customer.get('name') || '').toLowerCase();
-    const mobile = (customer.get('mobile') || '').toLowerCase();
+  return this.availableCustomers.filter(contact => {
+    const name = (contact.get('name') || '').toLowerCase();
+    const mobile = (contact.get('mobile') || '').toLowerCase();
     return name.includes(lowerKeyword) || mobile.includes(lowerKeyword);
   });
 }
@@ -765,19 +765,19 @@ private filterCustomers(keyword: string): Parse.Object[] {
 
 #### 2.2 缓存策略
 ```typescript
-private customerCache = new Map<string, Parse.Object>();
+private contactCache = new Map<string, Parse.Object>();
 
 private async getCachedCustomer(externalUserId: string): Promise<Parse.Object | null> {
-  if (this.customerCache.has(externalUserId)) {
-    return this.customerCache.get(externalUserId)!;
+  if (this.contactCache.has(externalUserId)) {
+    return this.contactCache.get(externalUserId)!;
   }
 
-  const customer = await this.queryCustomerByExternalId(externalUserId);
-  if (customer) {
-    this.customerCache.set(externalUserId, customer);
+  const contact = await this.queryCustomerByExternalId(externalUserId);
+  if (contact) {
+    this.contactCache.set(externalUserId, contact);
   }
 
-  return customer;
+  return contact;
 }
 ```
 
@@ -810,11 +810,11 @@ describe('CustomerSelectorComponent', () => {
     component = fixture.componentInstance;
   });
 
-  it('should load existing customer when project has customer', async () => {
+  it('should load existing contact when project has contact', async () => {
     // 测试项目已有客户的情况
   });
 
-  it('should show customer list when project has no customer', async () => {
+  it('should show contact list when project has no contact', async () => {
     // 测试显示客户列表的情况
   });
 
@@ -827,11 +827,11 @@ describe('CustomerSelectorComponent', () => {
 ### 2. 集成测试
 ```typescript
 describe('Customer Integration', () => {
-  it('should create new customer and associate with project', async () => {
+  it('should create new contact and associate with project', async () => {
     // 测试创建新客户并关联到项目的完整流程
   });
 
-  it('should select existing customer and update project', async () => {
+  it('should select existing contact and update project', async () => {
     // 测试选择现有客户并更新项目的流程
   });
 });
@@ -839,7 +839,7 @@ describe('Customer Integration', () => {
 
 ## 总结
 
-`app-customer-selector` 组件提供了完整的项目客户选择解决方案:
+`app-contact-selector` 组件提供了完整的项目客户选择解决方案:
 
 ✅ **智能识别**: 自动从群聊成员中识别外部用户
 ✅ **数据同步**: 支持创建和关联ContactInfo记录

+ 5 - 5
rules/schemas.md

@@ -110,7 +110,7 @@ TABLE(Project, "Project\n项目表") {
     FIELD(objectId, String)
     FIELD(title, String)
     FIELD(company, Pointer→Company)
-    FIELD(customer, Pointer→ContactInfo)
+    FIELD(contact, Pointer→ContactInfo)
     FIELD(assignee, Pointer→Profile)
     FIELD(status, String)
     FIELD(currentStage, String)
@@ -211,7 +211,7 @@ TABLE(ProjectPayment, "ProjectPayment\n项目付款表") {
 TABLE(ProjectFeedback, "ProjectFeedback\n客户反馈表") {
     FIELD(objectId, String)
     FIELD(project, Pointer→Project)
-    FIELD(customer, Pointer→ContactInfo)
+    FIELD(contact, Pointer→ContactInfo)
     FIELD(product, Pointer→Product)
     FIELD(stage, String)
     FIELD(feedbackType, String)
@@ -379,7 +379,7 @@ GroupChat "n" --> "1" Project : 关联项目(可选)
 | objectId | String | 是 | 主键ID | "proj001" |
 | title | String | 是 | 项目标题 | "李总现代简约全案" |
 | company | Pointer | 是 | 所属企业 | → Company |
-| customer | Pointer | 是 | 客户 | → ContactInfo |
+| contact | Pointer | 是 | 客户 | → ContactInfo |
 | assignee | Pointer | 否 | 负责设计师 | → Profile |
 | status | String | 是 | 项目状态 | "进行中" |
 | currentStage | String | 是 | 当前阶段 | "建模" |
@@ -784,7 +784,7 @@ payment.set("amount", 35000);
 payment.set("currency", "CNY");
 payment.set("percentage", 30);
 payment.set("dueDate", new Date("2024-12-01"));
-payment.set("paidBy", customer.toPointer());
+payment.set("paidBy", contact.toPointer());
 payment.set("recordedBy", profile.toPointer());
 payment.set("status", "pending");
 payment.set("description", "项目首期款");
@@ -829,7 +829,7 @@ const totalPaid = payments.reduce((sum, payment) => {
 | objectId | String | 是 | 主键ID | "feedback001" |
 | project | Pointer | 是 | 所属项目 | → Project |
 | **product** | **Pointer** | **否** | **关联空间产品** | **→ Product** |
-| customer | Pointer | 是 | 反馈客户 | → ContactInfo |
+| contact | Pointer | 是 | 反馈客户 | → ContactInfo |
 | stage | String | 是 | 反馈阶段 | "建模" / "渲染" |
 | feedbackType | String | 是 | 反馈类型 | "suggestion" / "complaint" |
 | content | String | 是 | 反馈内容 | "客厅颜色希望再暖一些" |

+ 18 - 17
src/modules/project/pages/project-detail/project-detail.component.html

@@ -52,13 +52,13 @@
   <!-- 项目详情内容 -->
   @if (!loading && !error && project) {
     <!-- 客户信息快速查看卡片 -->
-    <div class="customer-quick-view">
+    <div class="contact-quick-view">
       <div class="card">
         <div class="card-content">
-          <div class="customer-info">
+          <div class="contact-info">
             <div class="avatar">
-              @if (customer?.get('data')?.avatar) {
-                <img [src]="customer?.get('data')?.avatar" alt="客户头像" />
+              @if (contact?.get('data')?.avatar) {
+                <img [src]="contact?.get('data')?.avatar" alt="客户头像" />
               } @else {
                 <svg class="icon avatar-icon" viewBox="0 0 512 512">
                   <path fill="currentColor" d="M256 48C141.31 48 48 141.31 48 256s93.31 208 208 208 208-93.31 208-208S370.69 48 256 48zm0 60a60 60 0 11-60 60 60 60 0 0160-60zm0 336c-63.6 0-119.92-36.47-146.39-89.68C109.74 329.09 176.24 296 256 296s146.26 33.09 146.39 58.32C376.92 407.53 319.6 444 256 444z"/>
@@ -66,27 +66,28 @@
               }
             </div>
             <div class="info-text">
-              <h3>{{ customer?.get('name') || '待设置' }}</h3>
-              @if (customer && canViewCustomerPhone) {
-                <p>{{ customer.get('mobile') }}</p>
-                <p class="wechat-id">微信: {{ customer.get('data')?.wechat || customer.get('external_userid') }}</p>
-              } @else if (customer) {
-                <p class="info-limited">仅客服可查联系方式</p>
+              <h3>{{ contact?.get('name') || contact?.get('data')?.name || '待设置' }}</h3>
+              @if (contact && canViewCustomerPhone) {
+                <p>{{ contact.get('mobile') }}</p>
+                <p class="wechat-id">ID: {{ contact.get('data')?.wechat || contact.get('external_userid') }}</p>
+              } @else if (contact) {
+                <p class="info-limited">仅客服可查联系方式</p>
               }
               <div class="tags">
-                @if (customer?.get('source')) {
-                  <span class="badge badge-primary">{{ customer?.get('source') }}</span>
+                @if (contact?.get('source')) {
+                  <span class="badge badge-primary">{{ contact?.get('source') }}</span>
                 }
                 <span class="badge" [class.badge-success]="project.get('status') === '进行中'" [class.badge-warning]="project.get('status') !== '进行中'">
                   {{ project.get('status') }}
                 </span>
               </div>
-              @if (!customer && canEdit && role === '客服') {
-                <button class="btn btn-sm btn-primary" (click)="selectCustomer()">
-                  选择客户
-                </button>
-              }
+              
             </div>
+            @if (!contact?.id && role == '客服') {
+              <button class="btn btn-sm btn-primary" (click)="selectCustomer()">
+                选择客户
+              </button>
+            }
           </div>
         </div>
       </div>

+ 6 - 6
src/modules/project/pages/project-detail/project-detail.component.scss

@@ -290,7 +290,7 @@
 }
 
 // 客户快速查看
-.customer-quick-view {
+.contact-quick-view {
   padding: 12px 12px 0;
 
   .card {
@@ -303,7 +303,7 @@
     .card-content {
       padding: 12px;
 
-      .customer-info {
+      .contact-info {
         display: flex;
         align-items: center;
         gap: 12px;
@@ -843,7 +843,7 @@
     margin: 0 auto;
   }
 
-  .customer-quick-view,
+  .contact-quick-view,
   .stage-content {
     max-width: 800px;
     margin: 0 auto;
@@ -862,7 +862,7 @@
     max-width: 800px;
   }
 
-  .customer-quick-view,
+  .contact-quick-view,
   .stage-content {
     max-width: 1000px;
   }
@@ -912,14 +912,14 @@
     }
   }
 
-  .customer-quick-view {
+  .contact-quick-view {
     padding: 8px 8px 0;
 
     .card {
       .card-content {
         padding: 10px;
 
-        .customer-info {
+        .contact-info {
           gap: 10px;
 
           .avatar {

+ 35 - 70
src/modules/project/pages/project-detail/project-detail.component.ts

@@ -2,7 +2,7 @@ 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 { WxworkSDK, WxworkCorp, WxworkAuth } 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';
@@ -45,14 +45,14 @@ export class ProjectDetailComponent implements OnInit {
   // 企微SDK
   wxwork: WxworkSDK | null = null;
   wecorp: WxworkCorp | null = null;
-  wxAuth: any = null; // WxworkAuth 实例
+  wxAuth: WxworkAuth | null = null; // WxworkAuth 实例
 
   // 加载状态
   loading: boolean = true;
   error: string | null = null;
 
   // 项目数据
-  customer: FmodeObject | null = null;
+  contact: FmodeObject | null = null;
   assignee: FmodeObject | null = null;
 
   // 当前阶段
@@ -95,7 +95,7 @@ export class ProjectDetailComponent implements OnInit {
     });
 
     // 初始化企微授权(不阻塞页面加载)
-    this.initWxworkAuth();
+    await this.initWxworkAuth();
 
     await this.loadData();
   }
@@ -104,23 +104,10 @@ export class ProjectDetailComponent implements OnInit {
    * 初始化企微授权(不阻塞页面)
    */
   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);
-      // 授权失败不影响页面加载,继续使用其他方式加载数据
-    }
+    let cid = this.cid || localStorage.getItem("company") || "";
+    this.wxAuth = new WxworkAuth({ cid: cid});
+    this.wxwork = new WxworkSDK({ cid: cid, appId: 'crm' });
+    this.wecorp = new WxworkCorp(cid);
   }
 
   /**
@@ -130,49 +117,28 @@ export class ProjectDetailComponent implements OnInit {
     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);
-          }
-        }
+      if (!this.currentUser?.id) {
+        this.currentUser = await this.wxAuth?.currentProfile();
       }
+      console.log("777",this.currentUser)
 
       // 设置权限
+      console.log(this.currentUser)
       this.role = this.currentUser?.get('roleName') || '';
       this.canEdit = ['客服', '组员', '组长', '管理员'].includes(this.role);
       this.canViewCustomerPhone = ['客服', '组长', '管理员'].includes(this.role);
 
-      // 3. 加载项目
+      const companyId = this.currentUser?.get('company')?.id || localStorage?.getItem("company");
+          // 3. 加载项目
       if (!this.project) {
         if (this.projectId) {
           // 通过 projectId 加载(从后台进入)
           const query = new Parse.Query('Project');
-          query.include('customer', 'assignee','department','department.leader');
+          query.include('contact', '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');
@@ -180,12 +146,6 @@ export class ProjectDetailComponent implements OnInit {
             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;
@@ -193,7 +153,7 @@ export class ProjectDetailComponent implements OnInit {
 
               if (projectPointer) {
                 const pQuery = new Parse.Query('Project');
-                pQuery.include('customer', 'assignee','department','department.leader');
+                pQuery.include('contact', 'assignee','department','department.leader');
                 this.project = await pQuery.get(projectPointer.id);
               }
             }
@@ -203,13 +163,21 @@ export class ProjectDetailComponent implements OnInit {
             }
           }
         }
+
+      }
+
+      if(!this.groupChat?.id){
+        const gcQuery2 = new Parse.Query('GroupChat');
+        gcQuery2.equalTo('project', this.projectId);
+        gcQuery2.equalTo('company', companyId);
+        this.groupChat = await gcQuery2.first();
       }
 
       if (!this.project) {
         throw new Error('无法加载项目信息');
       }
 
-      this.customer = this.project.get('customer');
+      this.contact = this.project.get('contact');
       this.assignee = this.project.get('assignee');
 
       // 4. 加载群聊(如果没有传入且有groupId)
@@ -351,7 +319,8 @@ export class ProjectDetailComponent implements OnInit {
    * 选择客户(从群聊成员中选择外部联系人)
    */
   async selectCustomer() {
-    if (!this.canEdit || !this.groupChat) return;
+    console.log(this.canEdit, this.groupChat)
+    if (!this.groupChat) return;
 
     try {
       const memberList = this.groupChat.get('member_list') || [];
@@ -362,6 +331,7 @@ export class ProjectDetailComponent implements OnInit {
         return;
       }
 
+      console.log(externalMembers)
       // 简单实现:选择第一个外部联系人
       // TODO: 实现选择器UI
       const selectedMember = externalMembers[0];
@@ -380,7 +350,7 @@ export class ProjectDetailComponent implements OnInit {
     if (!this.wecorp) return;
 
     try {
-      const companyId = this.currentUser?.get('company')?.id;
+      const companyId = this.currentUser?.get('company')?.id || localStorage.getItem("company");
       if (!companyId) throw new Error('无法获取企业信息');
 
       // 1. 查询是否已存在 ContactInfo
@@ -391,10 +361,11 @@ export class ProjectDetailComponent implements OnInit {
 
       // 2. 如果不存在,通过企微API获取并创建
       if (!contactInfo) {
+        contactInfo = new Parse.Object("ContactInfo");
+      }
         const externalContactData = await this.wecorp.externalContact.get(member.userid);
-
+        console.log("externalContactData",externalContactData)
         const ContactInfo = Parse.Object.extend('ContactInfo');
-        contactInfo = new ContactInfo();
         contactInfo.set('name', externalContactData.name);
         contactInfo.set('external_userid', member.userid);
 
@@ -403,20 +374,14 @@ export class ProjectDetailComponent implements OnInit {
         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
-        });
+        contactInfo.set('data', externalContactData);
         await contactInfo.save();
-      }
 
       // 3. 设置为项目客户
       if (this.project) {
-        this.project.set('customer', contactInfo.toPointer());
+        this.project.set('contact', contactInfo.toPointer());
         await this.project.save();
-        this.customer = contactInfo;
+        this.contact = contactInfo;
         alert('客户设置成功');
       }
     } catch (err) {

+ 1 - 1
src/modules/project/pages/project-loader/project-loader.component.ts

@@ -197,7 +197,7 @@ export class ProjectLoaderComponent implements OnInit {
       let pid = projectPointer.id || projectPointer.objectId
       try {
         const query = new Parse.Query('Project');
-        query.include('customer', 'assignee');
+        query.include('contact', 'assignee');
         this.project = await query.get(pid);
 
         wxdebug('找到项目', this.project.toJSON());