# 企业微信群聊与联系人管理

## 概述

本文档介绍如何在企业微信聊天窗口的工具栏入口页面中，获取当前会话的群组信息或联系人信息，并同步到 Parse Server 的 `GroupChat` 和 `Contact` 表中。

## 引用方式

```typescript
import { WxworkSDK, WxworkCorp } from 'fmode-ng/core';
import { FmodeParse, FmodeObject } from 'fmode-ng/parse';
import { WxworkAuthGuard } from 'fmode-ng';
```

## 核心功能

### 1. 获取当前聊天上下文
使用 `WxworkSDK.getCurrentChat()` 方法获取当前企微窗口的会话信息。

### 2. 同步群组信息
使用 `WxworkSDK.syncGroupChat()` 方法同步群聊信息到 `GroupChat` 表。

### 3. 同步联系人信息
使用 `WxworkSDK.syncContact()` 方法同步外部联系人信息到 `Contact` 表。

### 4. ⭐ 统一获取方法（推荐）
使用 `WxworkSDK.getCurrentChatObject()` 一次性获取当前会话对象（群聊或联系人）。

## WxworkSDK 核心方法

### getCurrentChat() - 获取当前会话上下文

```typescript
/**
 * 获取当前企微窗口的会话信息
 * @returns WxworkCurrentChat 对象
 */
async getCurrentChat(): Promise<WxworkCurrentChat>

interface WxworkCurrentChat {
  type: "chatId" | "userId";  // 会话类型
  id?: string;                 // 群聊ID或用户ID
  group?: any;                 // 群聊详细信息
  contact?: any;               // 联系人详细信息
  follow_user?: any;           // 跟进人信息
}
```

**使用场景**：
- 从企微聊天栏工具进入页面时，自动识别当前会话
- 支持群聊和单聊两种场景

### syncGroupChat() - 同步群组信息

```typescript
/**
 * 同步群聊信息到 GroupChat 表
 * @param groupInfo 企微群聊信息
 * @returns GroupChat Parse Object
 */
async syncGroupChat(groupInfo: any): Promise<FmodeObject>
```

**功能**：
- 根据 `chat_id` 查询或创建 GroupChat 记录
- 自动生成入群链接 (`joinUrl`) 和入群二维码 (`joinQrcode`)
- 同步群聊基本信息（名称、群主、公告等）
- 同步成员列表 (`member_list`) 和成员版本 (`member_version`)
- 仅在数据变化时保存，避免不必要的数据库写入

### syncContact() - 同步联系人信息

```typescript
/**
 * 同步外部联系人信息到 Contact 表
 * @param contactInfo 企微外部联系人信息（包含 external_contact 和 follow_user）
 * @returns Contact Parse Object
 */
async syncContact(contactInfo: any): Promise<FmodeObject>
```

**功能**：
- 根据 `external_userid` 和 `company` 查询或创建 Contact 记录
- **独立字段**: `name`（姓名）、`mobile`（手机号）、`external_userid`（外部用户ID）
- **data 字段**: 其他所有信息统一存储在 `data` Object 类型字段中
- 包含完整的企微联系人信息和 `follow_user` 跟进人列表
- 仅在数据变化时保存，避免不必要的数据库写入

**参数兼容**：
```typescript
// 方式1：传入完整的 contactInfo（推荐）
const contactInfo = await wecorp.externalContact.get(externalUserId);
await wxwork.syncContact(contactInfo);

// 方式2：只传入 external_contact
await wxwork.syncContact(contactInfo.external_contact);
```

### ⭐ getCurrentChatObject() - 统一获取方法（推荐）

```typescript
/**
 * 获取当前聊天对象（群聊或联系人）
 * @returns { GroupChat?, Contact?, currentChat }
 */
async getCurrentChatObject(): Promise<{
  GroupChat?: FmodeObject,
  Contact?: FmodeObject,
  currentChat: WxworkCurrentChat | null
}>
```

**功能**：
- 一次调用同时获取会话上下文和对应的 Parse Object
- 自动识别群聊或联系人场景
- 自动调用 `syncGroupChat` 或 `syncContact` 同步数据
- 返回结果包含 `GroupChat` 或 `Contact`，用户自主判断后续逻辑

**返回值**：
- `GroupChat`: 群聊对象（群聊场景下存在）
- `Contact`: 联系人对象（单聊场景下存在）
- `currentChat`: 原始会话上下文

**使用示例**：
```typescript
const { GroupChat, Contact, currentChat } = await wxwork.getCurrentChatObject();

if (GroupChat) {
  // 群聊场景
  console.log("群名:", GroupChat.get("name"));
  console.log("成员数:", GroupChat.get("member_list").length);
} else if (Contact) {
  // 联系人场景
  console.log("姓名:", Contact.get("name"));
  console.log("企业:", Contact.get("data").corp_name);
}
```

## 数据范式

### GroupChat 表（群聊）

| 字段 | 类型 | 说明 |
|------|------|------|
| chat_id | String | 企微群聊ID（唯一标识） |
| name | String | 群聊名称 |
| owner | String | 群主 userid |
| notice | String | 群公告 |
| member_list | Array | 群成员列表 |
| member_version | String | 成员版本号（用于判断成员变化） |
| joinUrl | String | 入群链接（scene=1） |
| joinQrcode | String | 入群二维码链接（scene=2） |
| createdAt | Date | 创建时间 |
| updatedAt | Date | 更新时间 |

**member_list 结构**：
```json
[
  {
    "userid": "zhangsan",
    "type": 1,
    "join_time": 1605171726,
    "join_scene": 1,
    "invitor": {
      "userid": "lisi"
    }
  }
]
```

### Contact 表（联系人）

| 字段 | 类型 | 说明 |
|------|------|------|
| external_userid | String | 外部联系人ID（唯一标识） |
| company | Pointer<Company> | 所属企业 |
| name | String | 联系人姓名 |
| mobile | String | 手机号 |
| data | Object | 完整的企微联系人信息（包含所有字段） |
| createdAt | Date | 创建时间 |
| updatedAt | Date | 更新时间 |

**⭐ 数据结构说明**：
- **独立字段**：仅 `name`（姓名）、`mobile`（手机号）、`external_userid`（外部用户ID）为独立字段
- **data 字段**：其他所有企微联系人信息统一存储在 `data` Object 类型字段中

**data 字段包含的信息**：
```json
{
  "external_userid": "wmxxx",
  "name": "张三",
  "avatar": "http://...",
  "type": 1,
  "gender": 1,
  "unionid": "oxxx",
  "position": "产品经理",
  "corp_name": "ABC公司",
  "corp_full_name": "ABC科技有限公司",
  "external_profile": {
    "external_attr": [
      {
        "type": 0,
        "name": "职位",
        "text": { "value": "产品经理" }
      }
    ]
  },
  "follow_user": [
    {
      "userid": "zhangsan",
      "remark": "备注名",
      "description": "描述",
      "createtime": 1605171726,
      "tags": [
        {
          "group_name": "标签组",
          "tag_name": "标签名",
          "type": 1
        }
      ],
      "remark_corp_name": "备注企业名称",
      "remark_mobiles": ["13800138000"]
    }
  ]
}
```

**访问 data 字段示例**：
```typescript
const contact = await wxwork.syncContact(contactInfo);

// 独立字段直接访问
const name = contact.get("name");
const mobile = contact.get("mobile");

// data 字段内容需通过 .get("data") 访问
const data = contact.get("data");
const corpName = data.corp_name;
const avatar = data.avatar;
const followUsers = data.follow_user;
```

## 页面开发示例

### 示例1：群聊消息发送页面

从聊天栏工具入口，自动识别当前群聊并发送消息。

```typescript
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { WxworkSDK, WxworkCorp, WxworkCurrentChat } from 'fmode-ng/core';
import { FmodeParse, FmodeObject } from 'fmode-ng/parse';

const Parse = FmodeParse.with("nova");

export class PageGroupMessageComponent implements OnInit {
  cid: string = '';
  gid: string = '';
  chatId: string = '';

  wework: WxworkSDK;
  wecorp: WxworkCorp;
  currentChat: WxworkCurrentChat | null = null;
  groupChat: FmodeObject | null = null;

  constructor(private route: ActivatedRoute) {}

  async ngOnInit() {
    this.route.paramMap.subscribe(async params => {
      this.cid = params.get('cid') || '';
      this.gid = params.get('gid') || '';

      if (this.cid) {
        this.wework = new WxworkSDK({ cid: this.cid, appId: 'crm' });
        this.wecorp = new WxworkCorp(this.cid);
        await this.loadGroupChat();
      }
    });
  }

  /**
   * 加载群聊信息
   */
  async loadGroupChat() {
    try {
      if (this.gid) {
        // 方式1：通过路由参数 gid 加载
        let query = new Parse.Query("GroupChat");
        this.groupChat = await query.get(this.gid);
        this.chatId = this.groupChat.get("chat_id");

        // 从企微API获取最新群聊信息
        let chatInfo = await this.wecorp.externalContact.groupChat.get(this.chatId);
        this.currentChat = {
          id: chatInfo?.chat_id,
          type: "chatId",
          group: chatInfo?.group_chat || chatInfo
        };
      } else {
        // 方式2：从当前企微窗口获取上下文
        this.currentChat = await this.wework.getCurrentChat();

        if (this.currentChat?.group) {
          this.chatId = this.currentChat.id || '';

          // ⭐ 同步群聊信息到 Parse Server
          this.groupChat = await this.wework.syncGroupChat(this.currentChat.group);
          this.gid = this.groupChat.id;
        }
      }

      console.log("群聊ID:", this.chatId);
      console.log("群聊名称:", this.groupChat?.get("name"));
      console.log("群成员数:", this.groupChat?.get("member_list")?.length);
    } catch (error) {
      console.error('加载群聊失败:', error);
    }
  }

  /**
   * 发送文本消息
   */
  async sendTextMessage() {
    if (!this.chatId) {
      alert('请先加载群聊信息');
      return;
    }

    const content = "这是一条测试消息";
    const result = await this.wecorp.appchat.sendText(this.chatId, content);

    if (result?.errcode === 0) {
      console.log('发送成功');
    } else {
      console.error('发送失败:', result?.errmsg);
    }
  }
}
```

### 示例2：联系人详情页面

从联系人侧边栏工具入口，自动识别当前联系人并显示详情。

```typescript
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { WxworkSDK, WxworkCorp, WxworkCurrentChat } from 'fmode-ng/core';
import { FmodeParse, FmodeObject } from 'fmode-ng/parse';

const Parse = FmodeParse.with("nova");

export class PageContactDetailComponent implements OnInit {
  cid: string = '';
  contactId: string = '';
  externalUserId: string = '';

  wework: WxworkSDK;
  wecorp: WxworkCorp;
  currentChat: WxworkCurrentChat | null = null;
  contact: FmodeObject | null = null;

  constructor(private route: ActivatedRoute) {}

  async ngOnInit() {
    this.route.paramMap.subscribe(async params => {
      this.cid = params.get('cid') || '';
      this.contactId = params.get('contactId') || '';

      if (this.cid) {
        this.wework = new WxworkSDK({ cid: this.cid, appId: 'crm' });
        this.wecorp = new WxworkCorp(this.cid);
        await this.loadContact();
      }
    });
  }

  /**
   * 加载联系人信息
   */
  async loadContact() {
    try {
      if (this.contactId) {
        // 方式1：通过路由参数 contactId 加载
        let query = new Parse.Query("Contact");
        this.contact = await query.get(this.contactId);
        this.externalUserId = this.contact.get("external_userid");
      } else {
        // 方式2：从当前企微窗口获取上下文
        this.currentChat = await this.wework.getCurrentChat();

        if (this.currentChat?.contact) {
          this.externalUserId = this.currentChat.id || '';

          // 获取完整的联系人信息
          const contactInfo = await this.wecorp.externalContact.get(this.externalUserId);

          // ⭐ 同步联系人信息到 Parse Server
          this.contact = await this.wework.syncContact(contactInfo.external_contact);
          this.contactId = this.contact.id;
        }
      }

      console.log("联系人ID:", this.externalUserId);
      console.log("联系人姓名:", this.contact?.get("name"));

      // ⭐ 访问 data 字段中的信息
      const data = this.contact?.get("data");
      console.log("所属企业:", data?.corp_name);
      console.log("跟进人:", data?.follow_user);
    } catch (error) {
      console.error('加载联系人失败:', error);
    }
  }

  /**
   * 更新联系人备注
   */
  async updateRemark(remark: string) {
    if (!this.contact) return;

    this.contact.set("remark", remark);
    await this.contact.save();

    console.log('备注更新成功');
  }
}
```

### 示例3：⭐ 使用 getCurrentChatObject() 的最简模板（推荐）

适用于快速开发企微工具栏页面，使用统一方法一次性获取会话对象。

```typescript
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { WxworkSDK, WxworkCorp } from 'fmode-ng/core';
import { FmodeObject } from 'fmode-ng/parse';

export class MyToolComponent implements OnInit {
  cid: string = '';
  wework: WxworkSDK;
  wecorp: WxworkCorp;

  groupChat: FmodeObject | null = null;
  contact: FmodeObject | null = null;

  constructor(private route: ActivatedRoute) {}

  async ngOnInit() {
    this.cid = this.route.snapshot.paramMap.get('cid') || '';
    this.wework = new WxworkSDK({ cid: this.cid, appId: 'crm' });
    this.wecorp = new WxworkCorp(this.cid);

    // ⭐ 一次性获取当前会话对象
    const { GroupChat, Contact } = await this.wework.getCurrentChatObject();

    if (GroupChat) {
      // 群聊场景
      this.groupChat = GroupChat;
      console.log("当前群聊:", GroupChat.get("name"));
      console.log("成员数:", GroupChat.get("member_list").length);
    } else if (Contact) {
      // 联系人场景
      this.contact = Contact;
      console.log("当前联系人:", Contact.get("name"));

      // ⭐ 访问 data 字段
      const data = Contact.get("data");
      console.log("企业:", data.corp_name);
      console.log("跟进人:", data.follow_user);
    }

    // 开始业务逻辑...
  }
}
```

### 示例4：传统方式（兼容旧代码）

使用 getCurrentChat() 分步处理的方式。

```typescript
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { WxworkSDK, WxworkCorp, WxworkCurrentChat } from 'fmode-ng/core';
import { FmodeObject } from 'fmode-ng/parse';

export class MyToolComponent implements OnInit {
  cid: string = '';
  wework: WxworkSDK;
  wecorp: WxworkCorp;
  currentChat: WxworkCurrentChat | null = null;

  // 群聊或联系人 Parse Object
  chatObject: FmodeObject | null = null;

  constructor(private route: ActivatedRoute) {}

  async ngOnInit() {
    this.cid = this.route.snapshot.paramMap.get('cid') || '';
    this.wework = new WxworkSDK({ cid: this.cid, appId: 'crm' });
    this.wecorp = new WxworkCorp(this.cid);

    // 获取当前会话
    this.currentChat = await this.wework.getCurrentChat();

    if (this.currentChat?.type === "chatId") {
      // 群聊场景
      this.chatObject = await this.wework.syncGroupChat(this.currentChat.group);
      console.log("当前群聊:", this.chatObject.get("name"));
    } else if (this.currentChat?.type === "userId") {
      // 单聊场景
      const contactInfo = await this.wecorp.externalContact.get(this.currentChat.id);
      this.chatObject = await this.wework.syncContact(contactInfo);
      console.log("当前联系人:", this.chatObject.get("name"));
    }

    // 开始业务逻辑...
  }
}
```

### 示例5：⭐ 完整的聊天上下文页面（page-chat-context）

这是一个完整的演示页面，展示了如何同时获取和展示员工信息及聊天上下文（群聊或联系人）。

**路由配置**：
- `chat/:cid/context`
- `chat/:cid/:appId/context`

**页面功能**：
1. 显示当前登录员工信息（Profile/UserSocial）
2. 显示当前聊天上下文（群聊或联系人）
3. 根据场景展示不同的信息卡片

**核心代码**：

```typescript
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { WxworkSDK, WxworkCorp, WxworkCurrentChat } from 'fmode-ng/core';
import { FmodeParse, FmodeObject } from 'fmode-ng/parse';

const Parse = FmodeParse.with("nova");

@Component({
  selector: 'app-page-chat-context',
  standalone: true,
  templateUrl: './page-chat-context.component.html'
})
export class PageChatContextComponent implements OnInit {
  cid: string = '';
  appId: string = '';

  loading: boolean = true;
  wxwork: WxworkSDK | null = null;
  wecorp: WxworkCorp | null = null;

  // 当前登录的员工信息
  currentUser: FmodeObject | null = null;

  // 当前会话信息
  currentChat: WxworkCurrentChat | null = null;
  chatType: 'group' | 'contact' | 'none' = 'none';

  // 群聊或联系人对象
  groupChat: FmodeObject | null = null;
  contact: FmodeObject | null = null;

  constructor(private route: ActivatedRoute) {}

  async ngOnInit() {
    this.route.paramMap.subscribe(async params => {
      this.cid = params.get('cid') || '';
      this.appId = params.get('appId') || 'crm';

      if (!this.cid) {
        alert('缺少企业ID参数');
        return;
      }

      await this.loadData();
    });
  }

  async loadData() {
    try {
      this.loading = true;

      // 初始化 SDK
      this.wxwork = new WxworkSDK({ cid: this.cid, appId: this.appId });
      this.wecorp = new WxworkCorp(this.cid);

      // 1️⃣ 加载当前登录员工信息（由 WxworkAuthGuard 自动登录）
      this.currentUser = await this.wxwork.getCurrentUser();

      // 2️⃣ 加载当前聊天对象（群聊或联系人）
      const chatObject = await this.wxwork.getCurrentChatObject();
      this.currentChat = chatObject.currentChat;

      if (chatObject.GroupChat) {
        this.chatType = 'group';
        this.groupChat = chatObject.GroupChat;
      } else if (chatObject.Contact) {
        this.chatType = 'contact';
        this.contact = chatObject.Contact;
      }

    } catch (error) {
      console.error('加载数据失败:', error);
      alert('加载失败: ' + error);
    } finally {
      this.loading = false;
    }
  }

  // Helper: 获取当前员工姓名
  getCurrentUserName(): string {
    if (!this.currentUser) return '未知';
    return this.currentUser.get('name') || this.currentUser.get('userid') || '未知';
  }

  // Helper: 获取当前员工类型
  getCurrentUserType(): string {
    if (!this.currentUser) return '';
    return this.currentUser.className === 'Profile' ? '企业员工' : '外部用户';
  }

  // Helper: 获取联系人详细数据
  getContactData(): any {
    return this.contact?.get('data') || {};
  }

  // Helper: 获取联系人企业信息
  getContactCorpName(): string {
    const data = this.getContactData();
    return data.corp_name || data.corp_full_name || '未知';
  }

  // Helper: 获取联系人头像
  getContactAvatar(): string {
    const data = this.getContactData();
    return data.avatar || '';
  }

  // Helper: 获取跟进人列表
  getFollowUsers(): any[] {
    const data = this.getContactData();
    return data.follow_user || [];
  }

  // Helper: 获取群成员列表
  getGroupMembers(): any[] {
    const memberList = this.groupChat?.get('member_list');
    return Array.isArray(memberList) ? memberList : [];
  }
}
```

**模板示例（HTML）**：

```html
<div class="chat-context-container">
  <!-- 加载状态 -->
  <div *ngIf="loading" class="loading-container">
    <ion-spinner></ion-spinner>
    <p>加载中...</p>
  </div>

  <!-- 内容展示 -->
  <div *ngIf="!loading" class="content">
    <!-- 卡片1: 当前员工信息 -->
    <ion-card>
      <ion-card-header>
        <ion-card-title>
          <ion-icon name="person"></ion-icon>
          当前员工
        </ion-card-title>
      </ion-card-header>
      <ion-card-content>
        <ion-item>
          <ion-label>
            <h3>姓名</h3>
            <p>{{ getCurrentUserName() }}</p>
          </ion-label>
        </ion-item>
        <ion-item>
          <ion-label>
            <h3>类型</h3>
            <p>{{ getCurrentUserType() }}</p>
          </ion-label>
        </ion-item>
      </ion-card-content>
    </ion-card>

    <!-- 卡片2: 群聊信息 -->
    <ion-card *ngIf="chatType === 'group' && groupChat">
      <ion-card-header>
        <ion-card-title>
          <ion-icon name="people"></ion-icon>
          群聊信息
        </ion-card-title>
      </ion-card-header>
      <ion-card-content>
        <ion-item>
          <ion-label>
            <h3>群名</h3>
            <p>{{ groupChat.get('name') }}</p>
          </ion-label>
        </ion-item>
        <ion-item>
          <ion-label>
            <h3>成员数</h3>
            <p>{{ getGroupMembers().length }}</p>
          </ion-label>
        </ion-item>
        <ion-item>
          <ion-label>
            <h3>群主</h3>
            <p>{{ groupChat.get('owner') }}</p>
          </ion-label>
        </ion-item>
      </ion-card-content>
    </ion-card>

    <!-- 卡片3: 联系人信息 -->
    <ion-card *ngIf="chatType === 'contact' && contact">
      <ion-card-header>
        <ion-card-title>
          <ion-icon name="person"></ion-icon>
          联系人信息
        </ion-card-title>
      </ion-card-header>
      <ion-card-content>
        <!-- 头像和基本信息 -->
        <div class="contact-header">
          <ion-avatar *ngIf="getContactAvatar()">
            <img [src]="getContactAvatar()" />
          </ion-avatar>
          <div class="contact-basic">
            <h2>{{ contact.get('name') }}</h2>
            <p>{{ getContactCorpName() }}</p>
          </div>
        </div>

        <!-- 详细信息 -->
        <ion-item>
          <ion-label>
            <h3>手机号</h3>
            <p>{{ contact.get('mobile') || '未绑定' }}</p>
          </ion-label>
        </ion-item>

        <!-- ⭐ 访问 data 字段中的信息 -->
        <h3>企业信息</h3>
        <ion-item>
          <ion-label>
            <h3>企业</h3>
            <p>{{ getContactCorpName() }}</p>
          </ion-label>
        </ion-item>
        <ion-item>
          <ion-label>
            <h3>职位</h3>
            <p>{{ getContactData().position || '未知' }}</p>
          </ion-label>
        </ion-item>

        <!-- 跟进人列表 -->
        <h3>跟进人</h3>
        <div class="follow-users">
          <ion-item *ngFor="let follow of getFollowUsers()">
            <ion-label>
              <h3>{{ follow.userid }}</h3>
              <p *ngIf="follow.remark">备注: {{ follow.remark }}</p>
              <p *ngIf="follow.description">描述: {{ follow.description }}</p>
            </ion-label>
          </ion-item>
        </div>
      </ion-card-content>
    </ion-card>
  </div>
</div>
```

**关键特性**：

1. **统一方法调用**：使用 `getCurrentChatObject()` 一次性获取会话对象
2. **自动场景识别**：根据返回的 `GroupChat` 或 `Contact` 自动判断场景
3. **data 字段访问**：通过 `getContactData()` 统一访问 Contact.data 中的信息
4. **Helper 方法**：封装数据访问逻辑，简化模板代码
5. **响应式设计**：适配移动端和桌面端

**访问链接示例**：
- 脑控科技: https://app.fmode.cn/dev/crm/chat/E4KpGvTEto/context
- 映三色: https://app.fmode.cn/dev/crm/chat/cDL6R1hgSi/crm/context

## 入口场景识别

企微工具栏入口有多种场景，可通过 `entry` 字段判断：

```typescript
const context = await this.wework.getContext();

switch (context.entry) {
  case "group_chat_tools":
    // 群聊侧边栏工具
    console.log("从群聊工具栏进入");
    break;

  case "single_chat_tools":
    // 单聊侧边栏工具
    console.log("从单聊工具栏进入");
    break;

  case "contact_profile":
    // 联系人详情页
    console.log("从联系人详情进入");
    break;

  case "normal":
    // 普通应用首页
    console.log("从应用首页进入");
    break;
}
```

## 常见应用场景

### 场景1：群发消息工具

```typescript
async broadcastMessage(message: string) {
  // 获取当前群聊
  const currentChat = await this.wework.getCurrentChat();

  if (currentChat?.type === "chatId") {
    const groupChat = await this.wework.syncGroupChat(currentChat.group);
    const chatId = groupChat.get("chat_id");

    // 发送消息
    await this.wecorp.appchat.sendText(chatId, message);
    console.log("消息已发送到群聊:", groupChat.get("name"));
  }
}
```

### 场景2：群成员管理

```typescript
async listGroupMembers() {
  const currentChat = await this.wework.getCurrentChat();

  if (currentChat?.type === "chatId") {
    const groupChat = await this.wework.syncGroupChat(currentChat.group);
    const memberList = groupChat.get("member_list");

    console.log("群成员总数:", memberList.length);
    memberList.forEach(member => {
      console.log("成员:", member.userid, "加入时间:", member.join_time);
    });
  }
}
```

### 场景3：添加客户标签

```typescript
async addTagToContact(tagIds: string[]) {
  // ⭐ 使用统一方法
  const { Contact } = await this.wework.getCurrentChatObject();

  if (Contact) {
    const externalUserId = Contact.get("external_userid");

    // ⭐ 从 data 字段获取跟进人
    const data = Contact.get("data");
    const userid = data.follow_user[0].userid;

    // 调用企微API添加标签
    await this.wecorp.externalContact.markTag({
      userid: userid,
      external_userid: externalUserId,
      add_tag: tagIds
    });

    console.log("标签已添加到联系人:", Contact.get("name"));
  }
}
```

### 场景4：查看客户跟进记录

```typescript
async showFollowHistory() {
  // ⭐ 使用统一方法
  const { Contact } = await this.wework.getCurrentChatObject();

  if (Contact) {
    // ⭐ 从 data 字段获取跟进人列表
    const data = Contact.get("data");
    const followUsers = data.follow_user || [];

    followUsers.forEach(follow => {
      console.log("跟进人:", follow.userid);
      console.log("备注:", follow.remark);
      console.log("描述:", follow.description);
      console.log("添加时间:", new Date(follow.createtime * 1000));
    });
  }
}
```

## 数据同步最佳实践

### 1. 按需同步

```typescript
// ✅ 推荐：仅在需要时同步
async loadGroupChat() {
  const currentChat = await this.wework.getCurrentChat();

  if (currentChat?.group) {
    // syncGroupChat 会自动判断是否需要保存
    const groupChat = await this.wework.syncGroupChat(currentChat.group);
    return groupChat;
  }
}

// ❌ 不推荐：频繁无意义的同步
setInterval(async () => {
  const currentChat = await this.wework.getCurrentChat();
  await this.wework.syncGroupChat(currentChat.group); // 浪费资源
}, 1000);
```

### 2. 错误处理

```typescript
async loadContact() {
  try {
    const currentChat = await this.wework.getCurrentChat();

    if (currentChat?.contact) {
      const contactInfo = await this.wecorp.externalContact.get(currentChat.id);
      const contact = await this.wework.syncContact(contactInfo.external_contact);
      return contact;
    } else {
      console.warn("当前不在联系人会话中");
      return null;
    }
  } catch (error) {
    console.error("加载联系人失败:", error);

    // 友好提示
    if (error.code === 84061) {
      alert("无权限访问该联系人信息");
    } else {
      alert("加载失败，请重试");
    }

    return null;
  }
}
```

### 3. 缓存策略

```typescript
export class MyComponent {
  private cachedGroupChat: FmodeObject | null = null;
  private cacheTime: number = 0;
  private CACHE_DURATION = 5 * 60 * 1000; // 5分钟缓存

  async getGroupChat(): Promise<FmodeObject> {
    const now = Date.now();

    // 使用缓存
    if (this.cachedGroupChat && (now - this.cacheTime < this.CACHE_DURATION)) {
      return this.cachedGroupChat;
    }

    // 重新加载
    const currentChat = await this.wework.getCurrentChat();
    this.cachedGroupChat = await this.wework.syncGroupChat(currentChat.group);
    this.cacheTime = now;

    return this.cachedGroupChat;
  }
}
```

## 权限说明

### 需要的企微权限

1. **获取客户详情** - `externalcontact:get`
2. **获取客户群详情** - `externalcontact_groupchat:get`
3. **发送应用消息** - `appchat:send`
4. **企业客户标签管理** - `externalcontact_tag:manage`

### 权限检查

```typescript
async checkPermissions() {
  try {
    // 尝试获取当前会话
    const currentChat = await this.wework.getCurrentChat();

    if (!currentChat) {
      console.error("无法获取会话信息，可能缺少权限");
      return false;
    }

    return true;
  } catch (error) {
    console.error("权限检查失败:", error);
    return false;
  }
}
```

## 调试技巧

### 1. 使用 wxdebug 进行调试

`wxdebug` 是专门为企微开发设计的调试工具，会在控制台输出并弹窗显示调试信息。

```typescript
import { wxdebug } from 'fmode-ng/social';

// 调试单个值
const currentChat = await this.wework.getCurrentChat();
wxdebug('当前会话信息', currentChat);

// 调试多个值
wxdebug('步骤1', '初始化完成', { cid: this.cid });

// 调试 Parse Object
const groupChat = await this.wxwork.syncGroupChat(currentChat.group);
wxdebug('群聊同步完成', groupChat.toJSON());
```

**推荐调试流程**（参考 page-chat-context）：

```typescript
async loadData() {
  this.wxwork = new WxworkSDK({ cid: this.cid, appId: this.appId });
  wxdebug('1. SDK初始化完成', { cid: this.cid });

  try {
    this.currentChat = await this.wxwork.getCurrentChat();
    wxdebug('2. getCurrentChat返回', this.currentChat);
  } catch (err) {
    wxdebug('2. getCurrentChat失败', err);
  }

  if (this.currentChat?.type === "chatId" && this.currentChat?.group) {
    wxdebug('3. 检测到群聊场景', this.currentChat.group);
    this.groupChat = await this.wxwork.syncGroupChat(this.currentChat.group);
    wxdebug('4. 群聊同步完成', this.groupChat?.toJSON());
  }
}
```

### 2. 查看当前会话信息

```typescript
const currentChat = await this.wework.getCurrentChat();
console.log("会话类型:", currentChat.type);
console.log("会话ID:", currentChat.id);
console.log("详细信息:", currentChat);
```

### 3. 查看同步后的数据

```typescript
const groupChat = await this.wxwork.syncGroupChat(currentChat.group);
console.log("GroupChat Parse Object:", groupChat.toJSON());
```

### 4. 模拟不同入口场景

在开发环境可以通过路由参数模拟：

```
# 群聊场景
/demo/:cid/group/:gid/message

# 联系人场景
/crm/:cid/contact/:contactId/detail

# 聊天上下文演示
/chat/:cid/context
```

## 注意事项

1. **getCurrentChat() 依赖企微环境**
   - 必须在企微客户端内使用
   - 需要先调用 `registerCorpWithSuite()` 注册 JSAPI

2. **syncGroupChat() 会生成入群方式**
   - 首次同步会调用企微API生成入群链接和二维码
   - 可能需要一定时间，建议显示加载状态

3. **syncContact() 包含跟进人信息**
   - `follow_user` 数组可能为空或包含多个跟进人
   - 跟进人信息包含备注、标签等敏感数据

4. **数据同步是增量的**
   - `syncGroupChat` 和 `syncContact` 只在数据变化时保存
   - 避免频繁调用，建议根据业务需要同步

## 总结

通过 `getCurrentChat()`、`syncGroupChat()` 和 `syncContact()` 三个核心方法，可以轻松实现企微工具栏页面的开发：

✅ **一行获取上下文** - `await this.wework.getCurrentChat()`
✅ **自动同步数据** - `syncGroupChat()` / `syncContact()`
✅ **统一数据格式** - Parse Server 标准化存储
✅ **支持离线查询** - 数据持久化到 Parse

开发企微工具栏功能时，只需关注业务逻辑，数据同步由 SDK 自动处理！
