|
@@ -0,0 +1,1126 @@
|
|
|
+# 企业微信群聊与联系人管理
|
|
|
+
|
|
|
+## 概述
|
|
|
+
|
|
|
+本文档介绍如何在企业微信聊天窗口的工具栏入口页面中,获取当前会话的群组信息或联系人信息,并同步到 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 自动处理!
|