字段 |
类型 |
说明 |
external_userid |
String |
外部联系人ID(唯一标识) |
company |
Pointer
| 所属企业 |
name |
String |
联系人姓名 |
mobile |
String |
手机号 |
data |
Object |
完整的企微联系人信息(包含所有字段) |
createdAt |
Date |
创建时间 |
updatedAt |
Date |
更新时间 |
⭐ 数据结构说明:
- 独立字段:仅
name
(姓名)、mobile
(手机号)、external_userid
(外部用户ID)为独立字段
- data 字段:其他所有企微联系人信息统一存储在
data
Object 类型字段中
data 字段包含的信息:
{
"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 字段示例:
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:群聊消息发送页面
从聊天栏工具入口,自动识别当前群聊并发送消息。
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:联系人详情页面
从联系人侧边栏工具入口,自动识别当前联系人并显示详情。
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() 的最简模板(推荐)
适用于快速开发企微工具栏页面,使用统一方法一次性获取会话对象。
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() 分步处理的方式。
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
页面功能:
- 显示当前登录员工信息(Profile/UserSocial)
- 显示当前聊天上下文(群聊或联系人)
- 根据场景展示不同的信息卡片
核心代码:
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):
<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>
关键特性:
- 统一方法调用:使用
getCurrentChatObject()
一次性获取会话对象
- 自动场景识别:根据返回的
GroupChat
或 Contact
自动判断场景
- data 字段访问:通过
getContactData()
统一访问 Contact.data 中的信息
- Helper 方法:封装数据访问逻辑,简化模板代码
- 响应式设计:适配移动端和桌面端
访问链接示例:
入口场景识别
企微工具栏入口有多种场景,可通过 entry
字段判断:
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:群发消息工具
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:群成员管理
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:添加客户标签
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:查看客户跟进记录
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. 按需同步
// ✅ 推荐:仅在需要时同步
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. 错误处理
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. 缓存策略
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;
}
}
权限说明
需要的企微权限
- 获取客户详情 -
externalcontact:get
- 获取客户群详情 -
externalcontact_groupchat:get
- 发送应用消息 -
appchat:send
- 企业客户标签管理 -
externalcontact_tag:manage
权限检查
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
是专门为企微开发设计的调试工具,会在控制台输出并弹窗显示调试信息。
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):
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. 查看当前会话信息
const currentChat = await this.wework.getCurrentChat();
console.log("会话类型:", currentChat.type);
console.log("会话ID:", currentChat.id);
console.log("详细信息:", currentChat);
3. 查看同步后的数据
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
注意事项
getCurrentChat() 依赖企微环境
- 必须在企微客户端内使用
- 需要先调用
registerCorpWithSuite()
注册 JSAPI
syncGroupChat() 会生成入群方式
- 首次同步会调用企微API生成入群链接和二维码
- 可能需要一定时间,建议显示加载状态
syncContact() 包含跟进人信息
follow_user
数组可能为空或包含多个跟进人
- 跟进人信息包含备注、标签等敏感数据
数据同步是增量的
syncGroupChat
和 syncContact
只在数据变化时保存
- 避免频繁调用,建议根据业务需要同步
总结
通过 getCurrentChat()
、syncGroupChat()
和 syncContact()
三个核心方法,可以轻松实现企微工具栏页面的开发:
✅ 一行获取上下文 - await this.wework.getCurrentChat()
✅ 自动同步数据 - syncGroupChat()
/ syncContact()
✅ 统一数据格式 - Parse Server 标准化存储
✅ 支持离线查询 - 数据持久化到 Parse
开发企微工具栏功能时,只需关注业务逻辑,数据同步由 SDK 自动处理!