1、前言
我们都知道,微信小程序目前还不支持转发朋友圈,可能现在Android是支持了,iOS还不支持,但总的来说还不能支持普遍机型。这样假如我们需要推荐某个心仪的商品到朋友圈就没法分享出去,于是就可以使用生成海报的形式,让商品详情页的信息显示在一张图片上,保存到手机相册,然后发朋友圈,朋友可以长按识别海报上的小程序码直达该商品详情页面,从而达到如同直接分享商品详情页的效果。
2、思路
之所以用户扫码能直接进入到我们指定的页面,主要就是要靠海报上的那个太阳码,也就是小程序码,其他海报上的信息或者漂亮的样式纯粹就是为了渲染海报图片而已。
1、当用户点击按钮生成海报时,我们要通过后台接口生成并返回小程序码,要记得传给后台要塞入小程序码的参数,比如这里要进入商品详情页就需要塞入商品id,如果还需要知道是谁分享的,那还要塞入用户id。(这里有个问题留给大家思考,因为生成小程序码的接口scene字段长度最多是32位,如果我们需要传的参数位数超过32该怎么办,特别是数据库主键是以uuid形式存储的时候,那么传2个参数的时候就会超过而无法调用接口)
2、保存海报按钮,要检测用户是否已授权保存到手机相册,如果已授权,则显示保存海报按钮,如果未授权则显示授权并保存海报按钮
3、用户扫小程序码,解析码中scene参数,获取到先前塞入的参数,比如商品id,用户id,再调用商品详情接口渲染数据即可。
3、效果
4、实现
分享图片的components组件
index.wxml
代码语言:javascript复制<v-mask>
<view class="container">
<view class="canvas-wrapper">
<image class="icon" src="/images/icons/close.png" bindtap="onClose"></image>
<image class="shareImage" src="{{tempFilePath}}"></image>
</view>
<view class="button-wrapper">
<ly-button type="warning" i-class="custom-button" shape="circle" lang wx:if="{{saveImageAuth !== '1'}}" bindtap="saveImage" circle>保存海报</ly-button>
<ly-button type="warning" i-class="custom-button" shape="circle" lang wx:else bindtap="bindOpenSetting" circle>授权并保存海报</ly-button>
</view>
</view>
</v-mask>
<view class="hideCanvas">
<canvas canvas-id="shareCanvas" style="width: 750px;height: 1125px; zoom: {{unit}}"></canvas>
</view>
index.js
代码语言:javascript复制Component({
/**
* 组件的属性列表
*/
properties: {
detail: Object
},
/**
* 组件的初始数据
*/
data: {
unit: 1, // 比例
saveImageAuth: '1', // 权限
tempFilePath: '', // 临时图片地址
},
lifetimes: {
attached() {
// 检查授权权限
this.checkSaveImageAuth();
// //获取用户设备信息,屏幕宽度
wx.getSystemInfo({
success: res => {
console.log('getSystemInfo', res)
this.setData({
unit: res.windowWidth / 750 * 0.8,
ratio: res.pixelRatio
})
}
})
this.drawCanvas()
}
},
/**
* 组件的方法列表
*/
methods: {
// 封装的下载图片函数
downLoadImage(url) {
return new Promise((resolve, reject) => {
wx.getImageInfo({
src: url,
success(res) {
resolve(res.path)
},
fail(err) {
reject(err)
},
complete() {
console.log('complete')
}
})
})
},
// 封装的下载云存储文件函数
downLoadCloudFile(id) {
return new Promise((resolve, reject) => {
wx.cloud.downloadFile({
fileID: id,
success: res => {
// 返回临时文件路径
resolve(res.tempFilePath)
},
fail: err => {
console.log('err', err)
}
})
})
},
bindOpenSetting(e) {
let _this = this
wx.openSetting({
success(res) {
if (res.authSetting['scope.writePhotosAlbum']) {
_this.saveImage()
} else {
wx.showToast({
title: '请授权保存相册权限,才能为您生成分享图',
icon: 'none'
})
}
}
})
console.log('检查权限', e)
},
// 检查用户授权权限
checkSaveImageAuth() {
let _this = this
wx.getSetting({
success(res) {
console.log('检查用户授权权限', res)
let auth = ''
// 有权限
if (res.authSetting['scope.writePhotosAlbum'] === true) {
console.log('2')
auth = '2'
// 无权限
} else if (res.authSetting['scope.writePhotosAlbum'] === false) {
console.log('1')
auth = '1'
} else {
// 未设置
console.log('0')
auth = '0'
}
_this.setData({
saveImageAuth: auth
})
}
})
},
drawCanvas() {
let detail = this.properties.detail;
let mallType = detail.mallType;
console.log('drawCanvas',detail);
let ctx = wx.createCanvasContext('shareCanvas', this)
let titleImage = this.downLoadImage('https://ysd-1300312604.cos.ap-shanghai.myzijiebao.com/goods/goods_editor/20201022/c7bda103dcc24ce688743ec824b3dea0.png');
let productImage = this.downLoadImage(detail.productImage);
let erCodeImage = this.downLoadImage(detail.erCodeImage);
Promise.all([titleImage, productImage, erCodeImage]).then(imgs => {
console.log('imgs', imgs)
// 全部图片下载成功
let bgWidth = 750;
let bgHeight = 1.5 * 750;
// 绘制白底背景
ctx.setFillStyle('#fff')
ctx.fillRect(0, 0, bgWidth, bgHeight)
console.log('全部图片下载成功')
// 绘制顶部标题图片
let titleOffLeft = (750 - 652) / 2;
let titleOffTop = 60;
let titleWidth = 652;
let titleHeight = 50;
// todo 偏移量需要再处理一下
ctx.drawImage(imgs[0], titleOffLeft, titleOffTop, titleWidth, titleHeight);
// 绘制产品图片
let productOffLeft = 30;
let productOffTop = 155;
let productWidth = 690;
let productHeight = 380;
ctx.drawImage(imgs[1], productOffLeft, productOffTop, productWidth, productHeight)
// 绘制矩形边框
ctx.lineWidth = 1;
ctx.strokeStyle = '#ccc';
ctx.rect(30, 156, 690, 670);
ctx.stroke();
// 绘制标题
ctx.setFillStyle('#333') // 文字颜色
ctx.font = `${32}px PingFang`; // 文字字号
let title1 = detail.title;
let title2 = '';
if (title1.length > 40) {
title2 = title1.substring(20, 39) '...'
} else if (title1.length > 20) {
title2 = title1.substring(20)
}
title1 = title1.substring(0, 20)
let textWidth = ctx.measureText(title1).width;
let canvasWidthNoPadding = 750 - 56 * 2;
ctx.fillText(title1, 56, 595)
ctx.fillText(title2, 56, 630)
// 绘制副标题
ctx.setFillStyle('#ccc');
ctx.font = `28px PingFang`;
let subTitle = detail.subTitle;
if (subTitle.length > 23) {
subTitle = subTitle.substring(0, 22) '...'
}
ctx.fillText(subTitle, 56, 695)
// 绘制价格
let priceIcon = '券后¥ ';
let price = String(detail.price); // 价格
let priceTail = '优惠券¥ ';
if (mallType == 3) {
priceIcon = '折后 ';
}
let couponAmount = String(detail.couponAmount);
ctx.font = '28px PingFang';
ctx.setFillStyle('#08B4DE');
ctx.fillText(priceIcon, 56, 770 );
let offsetPriceIcon = ctx.measureText(priceIcon).width;
ctx.font = '36px PingFang';
ctx.fillText(price, (56 offsetPriceIcon), 770 );
if (detail.couponAmount != 0) {
let offsetPrice = ctx.measureText(price).width;
console.log('offsetPrice', offsetPrice)
if (mallType !=3) {
ctx.font = '28px PingFang';
ctx.fillText(priceTail, (360 offsetPriceIcon offsetPrice), 770 );
}
ctx.font = '36px PingFang';
let offsetPriceTailIcon = ctx.measureText(priceTail).width;
ctx.fillText(mallType !=3 ? couponAmount : couponAmount ' 折', (330 offsetPriceIcon offsetPrice offsetPriceTailIcon), 770 );
}
let linePriceIcon = (mallType != 4 ? mallType != 1 ? mallType != 2 ? mallType != 3 ? '拼多多': '唯品会' :'苏宁': '京东': '淘宝') '价 ';
let linePrice = String(detail.linePrice); // 价格
let saleNum = String(detail.saleNum) '人已购';
ctx.font = '28px PingFang';
ctx.setFillStyle('#666');
ctx.fillText(linePriceIcon, 56, 818 );
let offsetLinePriceIcon = ctx.measureText(linePriceIcon).width;
ctx.font = '36px PingFang';
ctx.fillText(linePrice, (56 offsetLinePriceIcon), 818 );
if (mallType != 3 && detail.saleNum != 0) {
let offsetLinePrice = ctx.measureText(linePrice).width;
ctx.font = '28px PingFang';
ctx.fillText(saleNum, (330 offsetLinePriceIcon offsetLinePrice), 818 );
}
// 绘制海报语信息
let saleTitle = '先领券再购物,即领即用,让你立省到家';
let saleName = detail.saleName;
let salePhone = String(detail.salePhone);
ctx.font = '24px PingFang';
ctx.setFillStyle('#08B4DE');
ctx.fillText(saleTitle, 30 , 930 );
ctx.setFillStyle('#666');
ctx.fillText(saleName, 30, 972 );
let saleNameWidth = ctx.measureText(saleName).width;
ctx.fillText(salePhone, 50 saleNameWidth, 972 );
// 绘店铺信息
ctx.setFillStyle('#08B4DE');
let supplierName = `店铺名称:${detail.mallName}`;
let supplierNameWidth = ctx.measureText(supplierName).width;
ctx.fillText(supplierName, 50, 1036 );
this.roundRect(ctx, 30, 1006 , (supplierNameWidth 40), 40, 20)
// 绘制二维码
let ercodeDesc = '长按识别小程序码领券';
ctx.setFillStyle('#bbb');
ctx.font = '22px PingFang';
let ercodeDescWidth = ctx.measureText(ercodeDesc).width;
let offsetErCode = (750 - 30) - ercodeDescWidth;
ctx.fillText(ercodeDesc, offsetErCode, 1078);
console.log('imgs', imgs[2])
ctx.drawImage(imgs[2], 540 , 860, 165, 165)
ctx.draw(true, () => {
let _this = this;
wx.canvasToTempFilePath({
x: 0,
y: 0,
canvasId: 'shareCanvas',
quality: 1.0,
fileType: 'jpg',
success(res) {
console.log('生成海报成功')
console.log('res', res)
_this.setData({
tempFilePath: res.tempFilePath
})
},
fail(err) {
console.log('err', err)
wx.showToast({
title: '海报生成失败',
icon: 'none',
})
_this.triggerEvent('complete');
},
complete() {
}
}, _this)
})
}).catch(err => {
console.log(err)
})
},
onClose() {
wx.hideLoading();
this.triggerEvent('complete');
},
// 保存图片
saveImage() {
let _this = this;
let tempFilePath = this.data.tempFilePath;
if (!tempFilePath) {
wx.showToast({
title: '海报生成失败',
icon: 'none',
})
return;
}
wx.saveImageToPhotosAlbum({
filePath: tempFilePath,
success() {
wx.showToast({
title: '已保存到相册,您可将海报分享到朋友圈',
icon: 'none'
})
_this.triggerEvent('complete');
},
fail() {
wx.showToast({
title: '海报保存失败',
icon: 'none',
})
_this.checkSaveImageAuth()
},
complete() {
}
})
},
// 绘制圆角矩形
roundRect(ctx, x, y, w, h, r) {
if (w < 2 * r) {
r = w / 2;
}
if (h < 2 * r) {
r = h / 2;
}
ctx.beginPath();
ctx.setStrokeStyle('#ff7800');
ctx.setFillStyle('transparent')
ctx.setLineWidth(0.5);
ctx.moveTo(x r, y);
ctx.arcTo(x w, y, x w, y h, r);
ctx.arcTo(x w, y h, x, y h, r);
ctx.arcTo(x, y h, x, y, r);
ctx.arcTo(x, y, x w, y, r);
ctx.stroke();
ctx.closePath();
},
},
})
在需要海报的页面引入组件,比如要在detail模块引入,则需要在detail.json文件声明组件
代码语言:javascript复制{
"usingComponents": {
"v-share-image": "/components/share-image/index"
}
}
然后在页面detail.wxml引用即可
代码语言:javascript复制<v-share-image detail="{{tempInfo}}" wx:if="{{showShareImage}}" bind:close="onCloseShareImage" bind:complete="onConfirmShareImage"></v-share-image>
上一篇:抖音开放平台用户授权获取用户的粉丝统计和短视频数据