小程序分享朋友圈、好友、图片分享
小程序分享的三种方式
小程序分享基本就三种:分享好友、小程序码图片分享以及朋友圈分享,日常开发比较常见的是分享好友;
由于第一次做小程序分享,网上又大多只有好友分享,或者只有零星的分享攻略,所以踩了n多坑后,自己整理一下巩固的同时能希望帮到其他人吧,毕竟百度上暂时还找不到这么全的攻略。
希望能帮助到大家
分享好友
1、唤起分享好友有两种方式,页面右上角...和属性openType="share"的Button组件。
2、两种唤起分享的方式都会触发页面的onShareAppMessage的方法,方法返回的参数中from字段返回表示分享的途径:按钮button或者非按钮(右上角分享)。
**要注意的是,如果通过按钮分享并且按钮是封装在子组件中,可以通过button的id属性传递一些数据给页面
1、按钮分享
<!-- 通过id可以传递参数 -->
<Button
className="flex ali-item-center jus-content-center share"
openType="share"
id={props.shareInfo.shareParam || 'share'}
>
<Text className="iconfont icon-wechat" />
</Button>
2、参数获取
// 参数获取
onShareAppMessage(share) {
console.error('from', share);
let shareObj = {},
target;
if (share.from === 'button') {
target = share.target.id; // 这个id获取到的就是上面button的id属性
} else {
target = `classId=${this.state.classId}&dynamicId=${this.state.dynamicId}`;
}
shareObj = {
title: default_share_Title,// 分享标题
// desc: '分享描述',
path: `/分享地址?${target}`,
imageUrl: default_share_circle, // 分享封面图
};
shareObj = {
...shareObj,
success: (res) => {
console.error('分享成功', res);
},
fail: (error) => {
console.error('分享失败', error);
},
};
console.error('shareObj', shareObj);
return shareObj;
}
3、注意⚠️
需要注意的是,小程序首页底部栏的几个tab页面公用的是同一个页面也就是index页面,但我们通常是一个tab对应一个路由页面,所以底部栏所有的tab页面对应按钮分享和右上角分享,触发的都是index页面的onShareAppMessage。
小程序码分享
主要是先通过调用后端接口获取小程序码,然后用canvas绘图,保存绘制的图片最后分享图片。
1、获取小程序码
- 后端调用小程序的api后返回文档流或者base64数据流,如果是返回文档流,在请求接口的时候需要把responseType设置为arraybuffer
let option = {
isShowLoading: false,
loadingText: '正在加载',
url: isBaseUrl ? base + requestUrl : requestUrl,
data: data,
method: method,
responseType: responseType || 'text', // 默认是text,如果请求文档流,设置为arraybuffer
header: {
'Content-Type': contentType,
'Access-Control-Allow-Origin': '*',
token: txtInfo.unionId
? txtInfo.unionId + '@@@' + Taro.getStorageSync('accountId')
: '@@@',
},
2、然后解析:如果是文档流,需要调用wx的api转base64
/** 获取小程序码 */
getMiniCode({scene, pagePath}) {
return new Promise((resolve, reject) => {
wx.showLoading({
title: '生成图片中...',
});
const accountId = Taro.getStorageSync('accountId');
$Request
.getMiniCodeUnlimit({
accountId,
scene,
page: pagePath,
})
.then((res) => {
// 获取小程序码(图片流),转base64 ==> const qrcode = wx.arrayBufferToBase64(res);
if (res.data && res.success) {
const base64 = 'data:image/jpg;base64,' + res.data;
resolve(base64);
} else {
setTimeout(() => {
WxActions.fnShowToast(res.errorMsg);
}, 300);
}
setTimeout(() => {
wx.hideLoading();
}, 300);
})
.catch((err) => {
console.log('请求失败', err);
setTimeout(() => {
wx.hideLoading();
WxActions.fnShowToast('获取小程序码失败');
}, 300);
reject(err);
});
});
},
3、获取到对应到base64码后,绘制canvas,当然绘制之前还有很多信息要处理:
const {classId, dynamic} = this.props;
WxActions.getMiniCode({
scene: `dynamicId=${dynamic.id}&classId=${classId}&v=1`,
pagePath: 'pages/schoolCircleDetail/schoolCircleDetail',
}).then((base64) => {
wx.showLoading({
title: '生成图片中...',
});
const canvas: any = WxActions.getShareCanvas(
{
shareFrom: this.state.shareFrom,
shareCode: base64,
shareTitle: '你的好友邀请你一起参与',
shareCover: this.state.coverImg,
},
'shareCanvas',
this.$scope,
);
canvas
.then((canvasSrc) => {
setTimeout(() => {
wx.hideLoading();
}, 300);
this.setState({
isShowModal: true,
canvasSrc: canvasSrc,
});
})
.catch(() => {
setTimeout(() => {
wx.hideLoading();
}, 300);
});
});
其中生成canvas图片逻辑如下:
(获取图片信息,由于后来后端同事换成二进制编码返回,所以要处理)
const fsm = wx.getFileSystemManager();
const FILE_BASE_NAME = 'tmp_base64src';
const base64src = function(base64data) {
return new Promise((resolve, reject) => {
const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64data) || [];
if (!format) {
reject(new Error('ERROR_BASE64SRC_PARSE'));
}
const filePath = `${wx.env.USER_DATA_PATH}/${FILE_BASE_NAME}.${format}`;
const buffer = wx.base64ToArrayBuffer(bodyData);
fsm.writeFile({
filePath,
data: buffer,
encoding: 'binary',
success() {
resolve(filePath);
},
fail() {
reject(new Error('ERROR_BASE64SRC_WRITE'));
},
});
});
};
// 。。。。。。。
/** 分享canvas */
initTime: 1, // 获取图片信息可能会失败,所以失败后可以再次获取,最多获取canvasObj.maxTime或3次
getShareCanvas(canvasObj, canvasId, otx) {
WxActions.initTime = 1; // 每次点击分享,重置当前次数为默认值1.
return new Promise((resolve, reject) => {
WxActions.getCanvasInfo(canvasObj, canvasId, otx)
.then((res) => {
resolve(res);
})
.catch((err) => {
reject(err);
});
});
},
/** 获取小程序码的imageinfo */
async getCanvasInfo(canvasObj, canvasId, otx) {
return new Promise((resolve, reject) => {
let drawWidth;
wx.getSystemInfo({
success: async (sys) => {
console.error('getSystemInfo', sys);
const rapito = 0.71;
const startX = 2,
startY = 80;
drawWidth = (await sys.screenWidth) * rapito;
let drawHeight = (drawWidth * 4) / 5;
console.error('drawWidth', drawWidth, drawHeight);
drawHeight = drawHeight > 150 ? 150 : drawHeight;
const src = await base64src(canvasObj.shareCode);
wx.getImageInfo({
src,
success: (codeInfo) => {
// console.error('1111', codeInfo);
wx.getImageInfo({
src: canvasObj.shareCover,
success: (res) => {
console.error('图片信息', res);
let cutWidth = res.width,
cutHeight = (res.width * 4) / 5;
cutHeight = cutHeight > res.height ? res.height : cutHeight;
console.error('图片信息===', res.width, cutHeight);
WxActions.initTime = 1;
// console.error('获取封面图成功', res);
//res.path是网络图片的本地地址
const canvasCtx = wx.createCanvasContext(canvasId, otx);
// console.error('canvasCtx', canvasCtx);
//绘制背景 ...
canvasCtx.fillStyle = '#fff';
canvasCtx.fillRect(0, 0, sys.screenWidth, sys.screenWidth);
//绘制分享的标题文字
canvasCtx.setFontSize(17);
canvasCtx.setFillStyle('#333');
canvasCtx.fillText(canvasObj.shareTitle, 15, 35);
//绘制分享的第二行标题文字
canvasCtx.setFontSize(13);
canvasCtx.setFillStyle('#999');
canvasCtx.fillText(`来自:${canvasObj.shareFrom}`, 15, 60);
//绘制图片
// drawImage(imageResource, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
// (图片地址, 绘制图片在画布的起始x, 绘制图片在画布的起始y, 截取图的宽度(原), 截取图的高度(原), 图片放置位置x, 图片放置位置y, 在canva上绘制的长度,在canvas上绘制的高度)
canvasCtx.drawImage(
res.path,
startX,
startY,
cutWidth,
cutHeight, //res.height,
startX,
startY,
drawWidth,
drawHeight,
);
//绘制小程序码
// canvasCtx.drawImage(canvasObj.shareCode, 15, 250, 60, 60);
canvasCtx.drawImage(codeInfo.path, 15, drawHeight + 90, 60, 60);
//绘制分享的说明
canvasCtx.setFontSize(13);
canvasCtx.setFillStyle('#333');
canvasCtx.fillText('长按小程序码看详情', 85, drawHeight + 130);
canvasCtx.stroke();
canvasCtx.draw(false, () => {
wx.canvasToTempFilePath(
{
canvasId: canvasId,
success: (ctx) => {
// console.error('save的canvas图片', ctx);
// 获得图片临时路径,用来保存到本地
return resolve(ctx.tempFilePath);
},
fail: (err) => {
//失败回调
console.error('save的canvas图片失败', err);
WxActions.fnShowToast('获取小程序码失败');
return reject();
},
},
otx, // 必须要有当前文档的实例
);
});
},
fail: (res) => {
//失败回调
console.error('获取封面图失败', res);
if (WxActions.initTime < 3) {
WxActions.initTime++;
// 获取失败后再次调用自身,重新获取,最多次数为canvasObj.maxTime或3次
WxActions.getCanvasInfo(canvasObj, canvasId, otx);
} else {
setTimeout(() => {
WxActions.fnShowToast('获取封面图失败');
}, 350);
}
return reject();
},
});
},
fail: (res) => {
//失败回调
console.error('2222', res);
},
});
},
fail: (err) => {
return reject(err);
},
});
});
},
4、最后保存图片
WxActions.fnWxSetting('scope.writePhotosAlbum').then((/** 授权 */) => {
this.setState({
canvasSrc: '',
isShowShare: false,
});
WxActions.fnSaveImageToAlbum(this.state.canvasSrc).then(() => {
this.onCancelModal();
});
});
// 。。。。。。。。
/** 保存图片到本地相册 */
fnSaveImageToAlbum(filePath) {
return new Promise((resolve, reject) => {
wx.saveImageToPhotosAlbum({
filePath,
success(yes) {
console.log(yes.errMsg);
WxActions.fnShowToast('保存图片成功');
return resolve();
},
fail(error) {
console.log(error.errMsg);
WxActions.fnShowToast('保存图片失败');
return reject();
},
});
});
},
到此,基本完成小程序码生成canvas并保存图片到手机相册到需求,由于时间比较紧,很多细节没处理好。
5、扫码进入的参数获取
接下来就是大坑了
在正常的小程序里面,扫码进入后获取参数是这样的
onLoad:function(options){
if(options.scene){
let scene=decodeURIComponent(options.scene);
//&是我们定义的参数链接方式
let userId=scene.split("&")[0];
let recommendId=scene.split('&')[1];
//其他逻辑处理。。。。。
}
}
但由于本项目用的是taro,也就是用react编写,由于查找了网上的攻略,于是先入为主的一直想要获取scene参数,找到一片文章说在componentWillMount生命周期里获取
componentWillMount(ops: any) {
console.error('ops', ops);
if (ops && ops.scene) {
let shareOps = WxActions.getMiniCodeScene(ops.scene);
this.setState({
isScene: true,
shareScene: shareOps,
});
}
}
结果发现这是不对的!不对的!不对的!!!必须要在componentdidmount里面获取,并且要通过this.$router.params.scene获取才是正常的,所以如果正常的页面和分享后扫码进入的是同一个页面,就要区分参数的获取了。
componentDidMount() {
if (this.$router.params.scene) {
// 扫码进入
}else{
// 正常小程序页面进入
}
}
朋友圈分享
分享朋友圈,微信暂时没有提供可以直接的分享方式,都是通过客服会话返回h5链接实现的,相当于曲线救国,下面是相关逻辑。
1、html代码,唤起客服会话
ps:h5地址域名必须是https的
<Button
sendMessageTitle={shareInfo.shareTitle ? shareInfo.shareTitle : '分享朋友圈'} // 分享的客服会话标题
sendMessageImg={shareInfo.shareImg ? shareInfo.shareImg : default_share_circle} // 分享的客服会话的封面图
sendMessagePath={shareInfo.sharePath || default_share_CircleUrl} // 对应的需要分享到朋友圈的h5链接
sessionFrom={'wkbbapp'} // 暂时发现没啥用
openType="contact" // 必填,表示唤起客服会话
showMessageCard // 必填,唤起会话右下角的截图弹出
plain
className="flex ali-item-center jus-content-center share"
style={{border: 'none'}}
onContact={(res) => {
console.error('onContact', res);
}}
>
<Text className="iconfont icon-quan" />
</Button>
2、后端推送会话卡片
前端只需要上面的button对应的代码,后端会获取到wx对应的推送,然后根据推送返回的内容,再由后端推送卡片消息到会话窗口,用户只要点击对应的窗口,就能进入对应的链接地址(其实就是一个h5地址,点击后会进入微信浏览器),然后点击右上角,分享朋友圈。
3、H5页面分享处理
显然,这已经是另外一个项目了。
这个项目要做的,就是平时公众号要做的事情了。
- 首先是在html页面引入微信sdk
<script type="text/javascript" src="https://res2.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
- 然后是注册(封装)
//微信分享
export function wxShare(_option: {
title: string; // 分享标题
desc: string; // 分享描述
link: string; // 分享链接
imgUrl: string; // 分享图标
success?: any;
cancel?: any;
}) {
configWxApi()
.then((res: any) => {
console.log('_option', _option);
console.log(res);
wx.config(res.data);
wx.ready(function() {
//分享给朋友
wx.onMenuShareAppMessage(_option);
// wx.updateAppMessageShareData(_option); //(1.4.0)
//分享到朋友圈
wx.onMenuShareTimeline(_option);
// wx.updateTimelineShareData(_option); //(1.4.0)
});
wx.error((error: any) => {
console.error(error);
Toast.info(`${error.errMsg}`, 3);
// config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
// alert("接口验证失败,详细信息:\n" + JSON.stringify(error));
});
// 提示服务器异常暂时取反
})
.catch((err: any) => {
console.log(err);
});
}
- 页面分享:
async componentDidMount() {
wxShare({
title: this.props.noticeDetail.title || default_circle_title, // 分享标题
desc: default_share_desc, // 分享描述
link: window.location.href, // 分享链接
imgUrl: this.props.noticeDetail.coverUrl || default_share_notice, // 分享图标
});
}
ps:注册微信分享失败的问题基本是签名错误获取域名不在合法域名列表,在此不再多描述了。
总结
主要是公司没同事做过相关业务,网上资料不全以及本人愚笨对官方文档理解不够,前后端都摸着石头过河,走了很多弯路,不过也正是如此,才能认识到自己还有很多不足,努力吧骚年💪。