|
@@ -0,0 +1,540 @@
|
|
|
+import { Injectable } from '@angular/core';
|
|
|
+import { FmodeParse, FmodeObject } from 'fmode-ng/parse';
|
|
|
+import * as ww from '@wecom/jssdk';
|
|
|
+import { WxworkCorp } from 'fmode-ng/core';
|
|
|
+
|
|
|
+const Parse = FmodeParse.with('nova');
|
|
|
+
|
|
|
+export interface WxworkCurrentChat {
|
|
|
+ type: 'chatId' | 'userId';
|
|
|
+ id?: string;
|
|
|
+ contact?: any;
|
|
|
+ follow_user?: any;
|
|
|
+ group?: any;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 企微SDK服务
|
|
|
+ * 封装企业微信JSSDK功能
|
|
|
+ */
|
|
|
+@Injectable({
|
|
|
+ providedIn: 'root'
|
|
|
+})
|
|
|
+export class WxworkSDKService {
|
|
|
+ // 企业配置映射
|
|
|
+ private companyMap: any = {
|
|
|
+ 'cDL6R1hgSi': { // 映三色
|
|
|
+ corpResId: 'SpL6gyD1Gu'
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 应用套件映射
|
|
|
+ private suiteMap: any = {
|
|
|
+ 'crm': {
|
|
|
+ suiteId: 'dk2559ba758f33d8f5'
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ cid: string = '';
|
|
|
+ appId: string = '';
|
|
|
+ corpId: string = '';
|
|
|
+ wecorp: WxworkCorp | null = null;
|
|
|
+ ww = ww;
|
|
|
+ registerUrl: string = '';
|
|
|
+
|
|
|
+ constructor() {}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 初始化SDK
|
|
|
+ */
|
|
|
+ async initialize(cid: string, appId: string): Promise<void> {
|
|
|
+ this.cid = cid;
|
|
|
+ this.appId = appId;
|
|
|
+ this.wecorp = new WxworkCorp(cid);
|
|
|
+ await this.registerCorpWithSuite();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 注册企业微信JSAPI
|
|
|
+ */
|
|
|
+ async registerCorpWithSuite(apiList?: string[]): Promise<boolean> {
|
|
|
+ if (this.platform() !== 'wxwork') return false;
|
|
|
+
|
|
|
+ // 如果URL未变化且已注册,直接返回
|
|
|
+ if (!apiList?.length && this.registerUrl === location.href) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ apiList = apiList || this.getDefaultApiList();
|
|
|
+
|
|
|
+ try {
|
|
|
+ const corpConfig = await this.getCorpByCid(this.cid);
|
|
|
+ const suiteId = this.suiteMap[this.appId]?.suiteId;
|
|
|
+
|
|
|
+ const now = new Date();
|
|
|
+
|
|
|
+ return new Promise((resolve) => {
|
|
|
+ ww.register({
|
|
|
+ corpId: corpConfig.corpId,
|
|
|
+ suiteId: suiteId,
|
|
|
+ agentId: corpConfig.agentId,
|
|
|
+ jsApiList: apiList!,
|
|
|
+ getAgentConfigSignature: async () => {
|
|
|
+ const jsapiTicket = await this.wecorp!.ticket.get();
|
|
|
+ return ww.getSignature({
|
|
|
+ ticket: jsapiTicket,
|
|
|
+ nonceStr: '666',
|
|
|
+ timestamp: (now.getTime() / 1000).toFixed(0),
|
|
|
+ url: location.href
|
|
|
+ });
|
|
|
+ },
|
|
|
+ onAgentConfigSuccess: () => {
|
|
|
+ this.registerUrl = location.href;
|
|
|
+ resolve(true);
|
|
|
+ },
|
|
|
+ onAgentConfigFail: (err: any) => {
|
|
|
+ console.error('Agent config failed:', err);
|
|
|
+ resolve(false);
|
|
|
+ },
|
|
|
+ onConfigFail: (err: any) => {
|
|
|
+ console.error('Config failed:', err);
|
|
|
+ resolve(false);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Register failed:', error);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取当前聊天对象
|
|
|
+ */
|
|
|
+ async getCurrentChatObject(): Promise<{
|
|
|
+ GroupChat?: FmodeObject;
|
|
|
+ Contact?: FmodeObject;
|
|
|
+ currentChat: WxworkCurrentChat | null;
|
|
|
+ }> {
|
|
|
+ const currentChat = await this.getCurrentChat();
|
|
|
+
|
|
|
+ if (!currentChat) {
|
|
|
+ return { currentChat: null };
|
|
|
+ }
|
|
|
+
|
|
|
+ let GroupChat: FmodeObject | undefined;
|
|
|
+ let Contact: FmodeObject | undefined;
|
|
|
+
|
|
|
+ try {
|
|
|
+ if (currentChat.type === 'chatId' && currentChat.group) {
|
|
|
+ GroupChat = await this.syncGroupChat(currentChat.group);
|
|
|
+ } else if (currentChat.type === 'userId' && currentChat.id) {
|
|
|
+ const contactInfo = await this.wecorp!.externalContact.get(currentChat.id);
|
|
|
+ Contact = await this.syncContact(contactInfo);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('getCurrentChatObject error:', error);
|
|
|
+ }
|
|
|
+
|
|
|
+ return { GroupChat, Contact, currentChat };
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取当前聊天场景
|
|
|
+ */
|
|
|
+ async getCurrentChat(): Promise<WxworkCurrentChat | null> {
|
|
|
+ const isRegister = await this.registerCorpWithSuite();
|
|
|
+ if (!isRegister) return null;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const context = await ww.getContext();
|
|
|
+ const entry = context?.entry;
|
|
|
+
|
|
|
+ let type: 'chatId' | 'userId';
|
|
|
+ let id: string | undefined;
|
|
|
+ let contact: any;
|
|
|
+ let chat: any;
|
|
|
+
|
|
|
+ if (entry === 'group_chat_tools') {
|
|
|
+ type = 'chatId';
|
|
|
+ id = (await ww.getCurExternalChat())?.chatId;
|
|
|
+ chat = await this.wecorp!.externalContact.groupChat.get(id!);
|
|
|
+ } else if (entry === 'contact_profile' || entry === 'single_chat_tools') {
|
|
|
+ type = 'userId';
|
|
|
+ id = (await ww.getCurExternalContact())?.userId;
|
|
|
+ contact = await this.wecorp!.externalContact.get(id!);
|
|
|
+ } else {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ type,
|
|
|
+ id,
|
|
|
+ group: chat?.group_chat,
|
|
|
+ contact: contact?.external_contact,
|
|
|
+ follow_user: contact?.follow_user
|
|
|
+ };
|
|
|
+ } catch (error) {
|
|
|
+ console.error('getCurrentChat error:', error);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取当前用户信息
|
|
|
+ */
|
|
|
+ async getCurrentUser(): Promise<FmodeObject | null> {
|
|
|
+ const userInfo = await this.getUserinfo();
|
|
|
+ if (!userInfo) return null;
|
|
|
+
|
|
|
+ return await this.getContactOrProfile(userInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取用户信息
|
|
|
+ */
|
|
|
+ async getUserinfo(code?: string): Promise<any> {
|
|
|
+ // 优先检查缓存
|
|
|
+ if (!code) {
|
|
|
+ const userInfoStr = localStorage.getItem(`${this.cid}/USERINFO`);
|
|
|
+ if (userInfoStr) {
|
|
|
+ const userInfo = JSON.parse(userInfoStr);
|
|
|
+ userInfo.cid = this.cid;
|
|
|
+ return userInfo;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从URL获取code
|
|
|
+ const url = new URL(location.href);
|
|
|
+ code = url.searchParams.get('code') || code;
|
|
|
+ if (!code) return null;
|
|
|
+
|
|
|
+ const result = await this.wecorp!.auth.getuserinfo(code);
|
|
|
+ if (result?.errcode) {
|
|
|
+ console.error(result?.errmsg);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 补全外部用户信息
|
|
|
+ if (result?.external_userid) {
|
|
|
+ const euser = await this.wecorp!.externalContact.get(result.external_userid);
|
|
|
+ if (euser?.external_contact) {
|
|
|
+ Object.assign(result, euser.external_contact);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ result.cid = this.cid;
|
|
|
+
|
|
|
+ // 缓存用户信息
|
|
|
+ localStorage.setItem(`${this.cid}/USERINFO`, JSON.stringify(result));
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 同步群聊信息
|
|
|
+ */
|
|
|
+ async syncGroupChat(groupInfo: any): Promise<FmodeObject> {
|
|
|
+ let query = new Parse.Query('GroupChat');
|
|
|
+ query.equalTo('chat_id', groupInfo?.chat_id);
|
|
|
+ let group = await query.first();
|
|
|
+
|
|
|
+ if (!group?.id) {
|
|
|
+ group = new Parse.Object('GroupChat');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成入群方式
|
|
|
+ if (!group?.get('joinUrl')) {
|
|
|
+ const config_id1 = (await this.wecorp!.externalContact.groupChat.addJoinWay({
|
|
|
+ scene: 1,
|
|
|
+ chat_id_list: [groupInfo.chat_id]
|
|
|
+ }))?.config_id;
|
|
|
+ const joinUrl = (await this.wecorp!.externalContact.groupChat.getJoinWay(config_id1))?.join_way;
|
|
|
+ group.set('joinUrl', joinUrl);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!group?.get('joinQrcode')) {
|
|
|
+ const config_id2 = (await this.wecorp!.externalContact.groupChat.addJoinWay({
|
|
|
+ scene: 2,
|
|
|
+ chat_id_list: [groupInfo.chat_id]
|
|
|
+ }))?.config_id;
|
|
|
+ const joinQrcode = (await this.wecorp!.externalContact.groupChat.getJoinWay(config_id2))?.join_way;
|
|
|
+ group.set('joinQrcode', joinQrcode);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新群聊数据
|
|
|
+ let needSave = false;
|
|
|
+
|
|
|
+ if (group.get('chat_id') !== groupInfo.chat_id) needSave = true;
|
|
|
+ if (group.get('name') !== groupInfo.name) needSave = true;
|
|
|
+ if (group.get('owner') !== groupInfo.owner) needSave = true;
|
|
|
+ if (group.get('notice') !== groupInfo.notice) needSave = true;
|
|
|
+ if (group.get('member_version') !== groupInfo.member_version) {
|
|
|
+ needSave = true;
|
|
|
+ group.set('member_list', groupInfo.member_list);
|
|
|
+ group.set('member_version', groupInfo.member_version);
|
|
|
+ }
|
|
|
+
|
|
|
+ group.set({
|
|
|
+ chat_id: groupInfo.chat_id,
|
|
|
+ name: groupInfo.name,
|
|
|
+ owner: groupInfo.owner,
|
|
|
+ notice: groupInfo.notice
|
|
|
+ });
|
|
|
+
|
|
|
+ if (this.cid) {
|
|
|
+ group.set('company', { __type: 'Pointer', className: 'Company', objectId: this.cid });
|
|
|
+ }
|
|
|
+
|
|
|
+ if (needSave) {
|
|
|
+ group = await group.save();
|
|
|
+ }
|
|
|
+
|
|
|
+ return group;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 同步联系人信息
|
|
|
+ */
|
|
|
+ async syncContact(contactInfo: any): Promise<FmodeObject> {
|
|
|
+ const externalContact = contactInfo.external_contact || contactInfo;
|
|
|
+ const externalUserId = externalContact.external_userid;
|
|
|
+
|
|
|
+ let query = new Parse.Query('ContactInfo');
|
|
|
+ query.equalTo('external_userid', externalUserId);
|
|
|
+
|
|
|
+ const Company = new Parse.Object('Company');
|
|
|
+ Company.id = this.cid;
|
|
|
+ query.equalTo('company', Company);
|
|
|
+
|
|
|
+ let contact = await query.first();
|
|
|
+
|
|
|
+ if (!contact?.id) {
|
|
|
+ contact = new Parse.Object('ContactInfo');
|
|
|
+ if (Company?.id) {
|
|
|
+ contact.set('company', Company.toPointer());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const name = externalContact.name || '';
|
|
|
+ const mobile = externalContact.mobile || '';
|
|
|
+
|
|
|
+ const data: any = {
|
|
|
+ ...externalContact,
|
|
|
+ follow_user: contactInfo.follow_user || externalContact.follow_user || []
|
|
|
+ };
|
|
|
+
|
|
|
+ let needSave = false;
|
|
|
+
|
|
|
+ if (contact.get('external_userid') !== externalUserId) needSave = true;
|
|
|
+ if (contact.get('name') !== name && name) needSave = true;
|
|
|
+ if (contact.get('mobile') !== mobile && mobile) needSave = true;
|
|
|
+
|
|
|
+ const oldData = contact.get('data');
|
|
|
+ if (JSON.stringify(oldData) !== JSON.stringify(data)) needSave = true;
|
|
|
+
|
|
|
+ contact.set('external_userid', externalUserId);
|
|
|
+ contact.set('name', name);
|
|
|
+ contact.set('mobile', mobile);
|
|
|
+ contact.set('data', data);
|
|
|
+
|
|
|
+ if (needSave) {
|
|
|
+ contact = await contact.save();
|
|
|
+ }
|
|
|
+
|
|
|
+ return contact;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取Profile或UserSocial
|
|
|
+ */
|
|
|
+ async getContactOrProfile(userInfo: any): Promise<FmodeObject> {
|
|
|
+ let UserType: string;
|
|
|
+
|
|
|
+ if (userInfo.openid || userInfo.external_userid) {
|
|
|
+ UserType = 'UserSocial';
|
|
|
+ } else if (userInfo.userid) {
|
|
|
+ UserType = 'Profile';
|
|
|
+ } else {
|
|
|
+ throw new Error('Invalid user info');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建查询条件
|
|
|
+ const userCondition: any[] = [];
|
|
|
+ const prefix = UserType === 'UserSocial' ? 'data.' : '';
|
|
|
+
|
|
|
+ if (userInfo.openid) userCondition.push({ [`${prefix}openid`]: { $regex: userInfo.openid } });
|
|
|
+ if (userInfo.userid) userCondition.push({ [`${prefix}userid`]: { $regex: userInfo.userid } });
|
|
|
+ if (userInfo.mobile) userCondition.push({ [`${prefix}mobile`]: { $regex: userInfo.mobile } });
|
|
|
+ if (userInfo.email) userCondition.push({ [`${prefix}email`]: { $regex: userInfo.email } });
|
|
|
+ if (userInfo.external_userid) {
|
|
|
+ userCondition.push({ [`${prefix}external_userid`]: { $regex: userInfo.external_userid } });
|
|
|
+ }
|
|
|
+
|
|
|
+ const query = Parse.Query.fromJSON(UserType, {
|
|
|
+ where: {
|
|
|
+ $or: userCondition
|
|
|
+ }
|
|
|
+ });
|
|
|
+ query.equalTo('company', this.cid);
|
|
|
+
|
|
|
+ let thisUser = await query.first();
|
|
|
+
|
|
|
+ if (!thisUser?.id) {
|
|
|
+ thisUser = new Parse.Object(UserType);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 关联当前登录用户
|
|
|
+ const current = Parse.User.current();
|
|
|
+ if (current?.id && !thisUser?.get('user')?.id) {
|
|
|
+ thisUser.set('user', current.toPointer());
|
|
|
+ }
|
|
|
+
|
|
|
+ return thisUser;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建群聊
|
|
|
+ */
|
|
|
+ async createGroupChat(options: {
|
|
|
+ groupName: string;
|
|
|
+ userIds?: string[];
|
|
|
+ externalUserIds?: string[];
|
|
|
+ }): Promise<any> {
|
|
|
+ const isRegister = await this.registerCorpWithSuite();
|
|
|
+ if (!isRegister) return null;
|
|
|
+
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ ww.createCorpGroupChat({
|
|
|
+ groupName: options.groupName,
|
|
|
+ userIds: options.userIds,
|
|
|
+ externalUserIds: options.externalUserIds,
|
|
|
+ success: (data) => {
|
|
|
+ resolve(data);
|
|
|
+ },
|
|
|
+ fail: (err) => {
|
|
|
+ reject(err);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 添加成员到群聊
|
|
|
+ */
|
|
|
+ async addUserToGroup(options: {
|
|
|
+ chatId: string;
|
|
|
+ userIds?: string[];
|
|
|
+ externalUserIds?: string[];
|
|
|
+ }): Promise<any> {
|
|
|
+ const isRegister = await this.registerCorpWithSuite();
|
|
|
+ if (!isRegister) return null;
|
|
|
+
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ (ww as any).updateCorpGroupChat({
|
|
|
+ chatId: options.chatId,
|
|
|
+ userIds: options.userIds,
|
|
|
+ externalUserIds: options.externalUserIds,
|
|
|
+ success: (data:any) => {
|
|
|
+ resolve(data);
|
|
|
+ },
|
|
|
+ fail: (err:any) => {
|
|
|
+ reject(err);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 打开指定群聊
|
|
|
+ */
|
|
|
+ async openChat(chatId: string): Promise<void> {
|
|
|
+ const isRegister = await this.registerCorpWithSuite();
|
|
|
+ if (!isRegister) return;
|
|
|
+
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ ww.openEnterpriseChat({
|
|
|
+ externalUserIds: [],
|
|
|
+ groupName: '',
|
|
|
+ chatId: chatId,
|
|
|
+ success: () => {
|
|
|
+ resolve();
|
|
|
+ },
|
|
|
+ fail: (err) => {
|
|
|
+ reject(err);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 选择企业联系人
|
|
|
+ */
|
|
|
+ async selectEnterpriseContact(options?: {
|
|
|
+ mode?: 'single' | 'multi';
|
|
|
+ type?: Array<'department' | 'user'>;
|
|
|
+ }): Promise<any> {
|
|
|
+ const isRegister = await this.registerCorpWithSuite();
|
|
|
+ if (!isRegister) return null;
|
|
|
+
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ (ww as any).selectEnterpriseContact({
|
|
|
+ fromDepartmentId: -1,
|
|
|
+ mode: options?.mode || 'multi',
|
|
|
+ type: options?.type || ['department', 'user'],
|
|
|
+ success: (data:any) => {
|
|
|
+ resolve(data);
|
|
|
+ },
|
|
|
+ fail: (err:any) => {
|
|
|
+ reject(err);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取企业配置
|
|
|
+ */
|
|
|
+ private async getCorpByCid(cid: string): Promise<any> {
|
|
|
+ if (this.corpId) return { corpId: this.corpId };
|
|
|
+
|
|
|
+ const query = new Parse.Query('CloudResource');
|
|
|
+ const res = await query.get(this.companyMap[cid]?.corpResId);
|
|
|
+ const config: any = res.get('config');
|
|
|
+ return config;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断平台
|
|
|
+ */
|
|
|
+ private platform(): string {
|
|
|
+ const ua = navigator.userAgent.toLowerCase();
|
|
|
+ if (ua.indexOf('wxwork') > -1) return 'wxwork';
|
|
|
+ if (ua.indexOf('wechat') > -1) return 'wechat';
|
|
|
+ return 'h5';
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取默认API列表
|
|
|
+ */
|
|
|
+ private getDefaultApiList(): string[] {
|
|
|
+ return [
|
|
|
+ 'getContext',
|
|
|
+ 'getCurExternalChat',
|
|
|
+ 'getCurExternalContact',
|
|
|
+ 'createCorpGroupChat',
|
|
|
+ 'updateCorpGroupChat',
|
|
|
+ 'openEnterpriseChat',
|
|
|
+ 'selectEnterpriseContact',
|
|
|
+ 'checkJsApi',
|
|
|
+ 'chooseImage',
|
|
|
+ 'previewImage',
|
|
|
+ 'uploadImage',
|
|
|
+ 'downloadImage',
|
|
|
+ 'getLocation',
|
|
|
+ 'openLocation',
|
|
|
+ 'scanQRCode',
|
|
|
+ 'closeWindow'
|
|
|
+ ];
|
|
|
+ }
|
|
|
+}
|