前端wx-jssdk的使用及企微和微信下分享等功能自定义处理
前端wx-jssdk的使用及企微和微信下分享等功能自定义处理
一、前端wx-jssdk的使用
wx-jssdk使用需要微信公众平台内进行设置(进入公众号设置的“功能设置”里填写“JS接口安全域名“的操作)及后端配合才能真正使用,下文是讲述前端如何使用。 wx-jssdk的接口文档
1、导包
在main.ts中(以vite+react项目中为例),为什么在main.ts中进行使用呢?其实在index.html文件中调用也是可以的,因为我要用到一些公共方法,并且保证要完成加载在线wx-jssdk包完后,再进行挂载等步,实现异步变同步处理更方便,就放在main.ts中。放在index.html 并保证异步变同步完全加载完wx-jssdk包就行,即:支持使用 AMD/CMD 标准模块加载方法加载
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import App from './app';
import { getEnv,WECHAT_ENV } from '@/utils';
//import '@/utils/setup.ts';
//import '@/assets/css/reset.less';
//import '@/assets/css/base.less';
//import '@/assets/font/iconfont.css';
const script = document.createElement('script');
// @ts-ignore
script.crossorigin = 'anonymous';
const sdkVerion = getEnv() === WECHAT_ENV.qyWechat ? 'jweixin-1.2.0.js' : 'jweixin-1.6.0.js';
// 开发环境和线上环境的wxsdk的路径有所区别
// script.src = process.env.NODE_ENV === 'development' ? `./public/lib/${sdkVerion}` : `./lib/${sdkVerion}`;
script.src = `https://res.wx.qq.com/open/js/${sdkVerion}`;
script.onerror = () => {
console.log('qy-wx-sdk:loadError');
};
script.onload = () => {
console.log({ wx });
// 必须等wxsdk 加载完之后,才能渲染页面
ReactDOM.render(
<BrowserRouter basename={`/${import.meta.env.VITE_APP_NAME}`}>
<App />
</BrowserRouter>,
document.getElementById('root')
);
};
document.head.appendChild(script);
2、使用
wx-jssdk提供了企微和微信环境下的一些方法进行使用:(wx-jssdk的接口文档、企业微信-wxjssdk)
- wx.config(obj):config接口注入权限验证配置
- wx.ready():config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
- wx.error():通过error接口处理失败验证
- wx.checkJsApi({ jsApiList: [],success(res) {console.log(res);},}):判断当前客户端版本是否支持指定JS接口,jsApiList: 需要检测的JS接口列表,所有JS接口列表,见JS接口列表;企业微信-wxjssdk
- wx.agentConfig(): config注入的是企业的身份与权限,而agentConfig注入的是应用的身份与权限。尤其是当调用者为第三方服务商时,通过config无法准确区分出调用者是哪个第三方应用,而在部分场景下,又必须严谨区分出第三方应用的身份,此时即需要通过agentConfig来注入应用的身份信息。企业微信-wxjssdk内有详细讲解
- wx.invoke():获取进入H5页面的入口环境,企业微信-wxjssdk
封装的getTicket
import ajax from '@/utils/ajax';
import { ConfigOptions, JSApiList } from 'wx-jssdk';
import { getEnv,WECHAT_ENV } from '@/utils';
// 普通签名
export const getTicket = async (jsApiList, callback, isShowError = true, maxRequestCount = 3) => {
// 开发+真机调试模式,要初始化JS_SDK
if (process.env.NODE_ENV === 'development' && !process.env.WX_JS_SDK_ENABLED) return;
let url = '';
// 如果是 iOS 设备,则使用第一次进入App时的 URL 去请求 wxConfig,不然的话会导致 iOS 中分享的链接描述信息或者图标不对
if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent) && env === 2) {
url = encodeURIComponent(localStorage.getItem('entryUrl'));// 有保存在localstorage中的页面路径
} else {
// console.log('签名地址=======================', window.location.href)
url = encodeURIComponent(window.location.href);
}
const params = {
appId: newAppId || appId,
corpId: env === 2 ? authCorpId : currentCorpId,
wechatType: env,
jsUrl: url,
agentId,
};
console.log('getTicket', `path=${location.pathname} url=${decodeURIComponent(params.jsUrl)}`);
try {
const res = await ajax('getTicket', params, isShowError); // 后端定义的接口 这里的ajax是自定义的
const { retdata = {} } = res;
const { appId, noncestr, signature, timestamp, nextUpdateTime } = retdata;
const obj = {
debug: false, // 是否开启调试模式
appId, // appid
timestamp, // 时间戳
nonceStr: noncestr, // 随机字符串
signature, // 签名
jsApiList,
openTagList: ['wx-open-launch-weapp'],
};
if (env === 1) {
obj.beta = true;
obj.appId = currentCorpId;
}
wx.config(obj);
wx.ready(function () {
if (typeof callback === 'function') callback(jsApiList);
});
wx.error((res) => {
console.log('%c zjs wx.error res:', 'color: #0e93e0;background: #aaefe5;', res);
if (typeof callback === 'function' && maxRequestCount > 0) callback(new Error('error'));
});
} catch (e) {
console.log('%c zjs getTicket err:', 'color: #0e93e0;background: #aaefe5;', e);
setTimeout(() => {
maxRequestCount > 0 && getTicket(jsApiList, callback, isShowError, --maxRequestCount);
}, 1000);
}
};
// 企业微信下一些自建应用签名
export const agentConfig = async (
jsApiList: JSApiList[],
callback: Function,
shareUrl = '',
isShowError = true,
maxRequestCount = 3
) => {
getTicket(
[...jsApiList, 'agentConfig'],
async (error: string) => {
if (error === 'error') {
// 过期或者签名错误 重新获取
setTimeout(() => {
maxRequestCount > 0 && agentConfig(jsApiList, callback, shareUrl, isShowError, --maxRequestCount);
}, 1000);
} else {
const params = {
type: WECHAT_ENV.qyWechat,
jsUrl: window.location.href,
};
const res = await ajax({ api: 'getTicket', params });
const { retdata = {} } = res;
const { corpId: corpid, noncestr: nonceStr, agentid, signature, timestamp }: any = retdata;
wx.checkJsApi({
jsApiList: ['agentConfig'],
success(res) {
console.log(res);
},
});
const obj = {
corpid, // 必填,企业微信的corpid,必须与当前登录的企业一致
agentid, // 必填,企业微信的应用id
timestamp, // 必填,生成签名的时间戳
nonceStr, // 必填,生成签名的随机串
signature, // 必填,签名,见附录1
jsApiList,
// openTagList: ['wx-open-launch-weapp'],
success: (res: ILooseStrObj) => {
console.log('agentConfig ok', res);
if (typeof callback === 'function') callback(jsApiList);
},
fail(res: ILooseStrObj) {
console.log('agentConfig fail', res);
if (typeof callback === 'function') callback('error');
},
};
console.log('agentConfig obj', obj);
wx.agentConfig(obj);
}
},
shareUrl
);
};
上面公用的一些方法
export const WECHAT_ENV = {
qyWechat: 1, // 企业微信
wechat: 2, // 微信
};
// 判断当前是微信环境还是企业微信环境
export const getEnv = () => {
const ua = window.navigator.userAgent.toLowerCase();
// eslint-disable-next-line
if (Boolean(ua.match(/MicroMessenger/i)) && Boolean(ua.match(/wxwork/i))) {
// 企业微信
// console.log('企业微信环境-1')
return WECHAT_ENV.qyWechat;
// eslint-disable-next-line
} else if (Boolean(ua.match(/micromessenger/i))) {
// 微信
// console.log('微信环境-2')
return WECHAT_ENV.wechat;
}
};
// 判断是pc端还是移动端
export const isPC = !/Android|webOS|iPhone|iPod|BlackBerry|SymbianOS|Windows Phone/i.test(navigator.userAgent);
/**
* [changeSearch description]
* @param {[type]} oldName 需要修改的search字段
* @param {[type]} newStr 替换的新串
* @param {[type]} url 当前要替换的link地址 不传默认是 window.location.search
* @return {[type]} [description]
*/
export const changeSearch = (oldName: string, newStr: string, url: string) => {
const jsonObj: any = searchToJson(url || window.location.search);
if (!oldName) {
return url;
}
jsonObj[oldName] = newStr;
const linkUrl = url.split('?')[0] + jsonToSearch(jsonObj);
return linkUrl;
};
/** 将url的search部分转化为json
* @param url:String -- url地址
* @param codeURI:Boolean -- 是否解码
*/
export const searchToJson = (url = window.location.href, codeURI = false) => {
let setUrl: string = url || '';
const search = setUrl.split('?');
let result = {};
search.forEach((item, index) => {
if (index !== 0) {
result = item.split('&').reduce((obj, item) => {
const arr = item.split('=');
return { ...obj, [arr[0]]: codeURI ? decodeURIComponent(arr[1]) : arr[1] };
}, result);
}
});
return result;
};
// 获取url参数
export const getUrlQueryString = (search: string, name: string) => {
const reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)');
const r = search.substr(1).match(reg);
if (r !== null) {
return r[2];
}
return '';
};
注意bug
背景:在使用到这个ssdk过程中(我使用js-weixin的包是1.2.0版本),我遇到了一个兼容Android和ios的问题,就是在调用jssdk无论是ios还是Android都可以调用,但是当在企业微信中使用到分享相关的api时,在ios是无法调用,在Android是没有问题的, 处理方案:更改调用的js-weixin包,将js-weixin包改为1.0.0的包 ,两个包地址是不一样的: res.wx.qq.com/wwopen/js/j… res.wx.qq.com/open/js/jwe…
二、 处理系统分享出去自定标题、描述、背景及相关系统功能禁用等
场景一:使用系统携带的分享等功能,但自定义分享的标题、描述等内容
在使用系统带的分享功能时,需要自定义分享出去的背景、背景图、描述及分享的链接时,可以将下面的shareFuc导入,初始化(放componentDidMount或useEffect中)携带对应的参数即可。
const shareObj = {
title: '客户认证',
desc: '客户自己完成认证',
imgUrl: '',
linkUrl: ``,//分享的链接,可携带一些参数
};
useEffect(() => {
shareFuc(shareObj);// 下面封装已给出
}, [userId]);
场景二:弃用系统自带的分享,自己写弹框,触发分享API
出现这个场景,一方面可能可客户需要,另一方面,可能是分享要进行埋点,记录分享出去的次数。 实现:写一个弹框(如下),将下面每个分享或转发调用相应的API
- 转发:shareAppMessage,微信好友:shareWechatMessage,微信朋友圈:shareTimeline,这几个直接使用wx.invoke即可
// 微信好友
const forwardWeChat = () => {
const shareConfig = {
title, // 分享标题
desc, // 分享描述
link: "linkUrl", // 分享链接
imgUrl: "imgUrl" , // 分享封面
};
console.log('企微-微信好友 shareConfig', shareConfig);
wx.invoke('shareWechatMessage', shareConfig, (res: any) => {
console.log('企微分享微信好友-分享回调', res);
if (res.err_msg === 'shareWechatMessage:ok') {
//
}
});
};
- 企微朋友圈:shareToExternalMoments,群发客户:shareToExternalContact;群发客户群:shareToExternalChat,比微信下多了agentConfig处理,是因为这三个api需要配置客户联系功能与版本
import { JSApiList } from 'wx-jssdk';
import { agentConfig } from '@/utils/getTicket';// 上面有封装
// 群发客户群
const groupShareGroup = async (e: any) => {
const arrAgent: JSApiList[] = ['shareToExternalChat'];
agentConfig(
arrAgent,
() => {
wxInvokeShare('shareToExternalChat', {
title, // 分享标题
desc,
link: "linkUrl", // 分享链接
imgUrl:"imgUrl", // 分享封面
});
},
linkUrl
);
};
- 注意 问题:在我处理分享的时候踩到了一个坑,就是设置分享的imgUrl参数的时候写的是相对路径(../../../images/common/shareIconImg.png),分享出去的图标无法显示 原因:官方似乎没有给出解释,我觉得是不是图片是本地内,分享出去后,显示的这个内容并没有加载整个项目代码,只是作为参数传过去,由于是图片是相对路径,这样导致加载不到图片, 处理方案:使用base64方法得到图片作为imgUrl参数
场景三:禁用个人微信或企微右上角分享等功能
见下列方法:
- 企微:hiddenWxQyShareOption,使用API:wx.hideOptionMenu(), wx.showMenuItems(
- 微信:hiddenWxShareOption ,使用:WeixinJSBridge.call('hideToolbar'); WeixinJSBridge.call('hideOptionMenu');
函数封装shareFuc、企微和个人微信系统分享等功能禁用封装
// @ts-nocheck
import globalData from '@/config/globalData';
import keyDict from '@/config/keyDict';
import point from '@/config/point';
import { changeSearch, getEnv, searchToJson, getUrlQueryString, isPC, WECHAT_ENV } from '@/utils';//上面公用方法
import ajax from '@/utils/ajax';
import { getTicket } from '@/utils/getTicket';// 上面定义封装处理
import { JSApiList } from 'wx-jssdk';
interface IShareProps {
title: string;
desc: string;
linkUrl: string;
imgUrl: string;
userId: string;
cb?: () => void;
}
interface IShareFunProps {
shareObj: IShareProps;
jsApiList: JSApiList[];
maxRequestCount: number;
}
/**
* [shareFuc description]
* @param {[type]} shareObj { title: 标题, desc: 描述, linkUrl: 分享地址(会对地址做拼接,默认不传为当前url), imgUrl: 分享icon, staffId: 经理id,拼接给分享地址}
* @param {Array} [jsApiList=[微信api]]
* @return {Promise} [description]
* @param {type} maxRequestCount 最大失败请求次数,默认 为 3次
*/
export const shareFuc = async (shareObj: IShareProps, jsApiList = [], maxRequestCount = 3) => {
// console.log('shareObj===》', shareObj);
const defaultJsApi: JSApiList[] = ['onMenuShareAppMessage', 'onMenuShareTimeline', 'getLocation', 'previewImage'];
// 不支持在PC端微信使用js-sdk功能
if (isPC) return;
const ua = navigator.userAgent.match(/MicroMessenger\/([\d\\.]+)/i);
const lowerWeChat = ua ? ua[1] < '6.7.2' : true;
// 微信
if (getEnv() === 2) {
defaultJsApi.push('updateAppMessageShareData', 'updateTimelineShareData');
}
getTicket(
[...defaultJsApi, ...jsApiList],
async (error: string) => {
if (error === 'error') {
// 过期或者签名错误 重新获取
setTimeout(() => {
if (maxRequestCount > 0) {
maxRequestCount = --maxRequestCount;
shareFuc(shareObj, jsApiList, maxRequestCount);
}
}, 1000);
} else {
// 企业微信(微信)下隐藏部分不需要的菜单功能--如分享到同事吧,收藏,转发,微信,朋友圈
if (getEnv() === 2) {
wx.hideOptionMenu();
wx.showMenuItems({
menuList: [
'menuItem:copyUrl', // 复制链接
],
// wx.hideMenuItems({
// menuList: [
// // 'menuItem:setFont', // 字体
// // 'menuItem:openWithSafari', // Safari
// // 'menuItem:share:email', // 邮件
// // 'menuItem:openWithQQBrowser', // QQBrowser
// // 'menuItem:share:appMessage', // 转发
// // 'menuItem:share:timeline', // 朋友圈
// // 'menuItem:share:wechat', // 微信
// ], // 要隐藏的菜单项
// });
});
}
const { title = '', desc = '', imgUrl = '', linkUrl, cb } = shareObj;
// 个人微信处理
if (getEnv() === 2) {
// 分享给朋友
if (wx.updateAppMessageShareData && !lowerWeChat) {
wx.updateAppMessageShareData({
title, // 分享标题
desc, // 分享描述
link: linkUrl, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl, // 分享图标
success: () => {
console.log('微信分享给朋友,新Api,没有成功回调');
},
});
} else {
wx.onMenuShareAppMessage({
title, // 分享标题
desc, // 分享描述
link: linkUrl, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl, // 分享图标
type: undefined, // 分享类型,music、video或link,不填默认为link
dataUrl: '', // 如果type是music或video,则要提供数据链接,默认为空
success: () => {
// 用户点击了分享后执行的回调函数
console.log('微信分享给朋友,旧Api');
},
});
}
// 分享给朋友圈
if (wx.updateTimelineShareData && !lowerWeChat) {
wx.updateTimelineShareData({
title, // 分享标题
link: linkUrl, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl, // 分享图标
success: () => {
// 设置成功
console.log('微信分享给朋友圈,新Api');
},
});
} else {
wx.onMenuShareTimeline({
title, // 分享标题
link: linkUrl, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl, // 分享图标
success: () => {
// 设置成功
console.log('微信分享给朋友圈,旧Api');
},
});
}
} else {
// 企业微信
wx.onMenuShareAppMessage({
title, // 分享标题
desc, // 分享描述
link: linkUrl, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl, // 分享图标
success: () => {},
cancel: () => {},
});
wx.onMenuShareTimeline({
title, // 分享标题
link: linkUrl, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl, // 分享图标
success: () => {
// 设置成功
console.log('企业微信分享给朋友圈,旧Api');
},
cancel: () => {
// 用户取消分享后执行的回调函数
console.log('企业微信分享给朋友圈,旧Api,cancel');
},
});
}
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
cb && cb();
}
},
shareObj.linkUrl,
false,
maxRequestCount
);
};
/**
* 禁止企业微信右上角分享(新版本企微是下方)
* **/
let wxConfigTimer: number;
export const hiddenWxQyShareOption = () => {
if (getEnv() === WECHAT_ENV.qyWechat) {
wxConfigTimer && clearTimeout(wxConfigTimer);
wxConfigTimer = window.setTimeout(() => {
getTicket([], () => {
console.log('企业微信环境屏蔽右上角分享');
wx.hideOptionMenu();
wx.showMenuItems({
menuList: ['menuItem:copyUrl'], //保留复制链接
});
});
}, 200);
}
};
/** 禁止微信右上角分享按钮 */
export const hiddenWxShareOption = () => {
if (getEnv() === WECHAT_ENV.wechat) {
console.log('禁用微信右上角分享和状态栏');
if (typeof WeixinJSBridge === 'undefined') {
// 这个可以禁用安卓系统的右上角分享 (只针对微信端)
document.addEventListener(
'WeixinJSBridgeReady',
function () {
WeixinJSBridge.call('hideToolbar');
WeixinJSBridge.call('hideOptionMenu');
},
false
);
} else {
// 这个可以禁用ios系统的右上角分享 (只针对微信端)
WeixinJSBridge.call('hideToolbar');
WeixinJSBridge.call('hideOptionMenu');
}
}
};
JS接口列表
type JSApiList =
| 'agentConfig'
| 'updateAppMessageShareData'
| 'updateTimelineShareData'
| 'onMenuShareTimeline'
| 'onMenuShareAppMessage'
| 'onMenuShareQQ'
| 'onMenuShareWeibo'
| 'onMenuShareQZone'
| 'startRecord'
| 'stopRecord'
| 'onVoiceRecordEnd'
| 'playVoice'
| 'pauseVoice'
| 'stopVoice'
| 'onVoicePlayEnd'
| 'uploadVoice'
| 'downloadVoice'
| 'chooseImage'
| 'previewImage'
| 'uploadImage'
| 'downloadImage'
| 'translateVoice'
| 'getNetworkType'
| 'openLocation'
| 'getLocation'
| 'hideOptionMenu'
| 'showOptionMenu'
| 'hideMenuItems'
| 'showMenuItems'
| 'hideAllNonBaseMenuItem'
| 'showAllNonBaseMenuItem'
| 'closeWindow'
| 'scanQRCode'
| 'chooseWXPay'
| 'openProductSpecificView'
| 'addCard'
| 'chooseCard'
| 'openCard'
| 'shareToExternalContact'
| 'shareToExternalChat'
| 'selectExternalContact'
| 'navigateToAddCustomer';