背景:
基于uniapp實(shí)現(xiàn)微信小程序中商品詳情的海報(bào)生成與保存
效果圖:

思路:
首先把海報(bào)上需要的內(nèi)容準(zhǔn)備好,比如用戶頭像,商品信息,二維碼等。需要注意的是,因?yàn)槎S碼是動(dòng)態(tài)生成的,所以需要后端傳給我們,前端需要把路徑和參數(shù)傳給后端,后端請(qǐng)求微信服務(wù)接口,通過配置對(duì)應(yīng)的參數(shù)就可以生成一個(gè)二維碼啦,再將二維碼發(fā)送給前端。
圖片不能是網(wǎng)絡(luò)圖片,我們需要的是本地圖片,如非本地圖片,那我們需要對(duì)圖片進(jìn)行處理。uniapp可以通過uni.getImageInfo獲取圖片本地路徑(uni.chooseImage(OBJECT) | uni-app官網(wǎng) (dcloud.net.cn))
如果通過臨時(shí)路徑來保存圖片,canvas一倍會(huì)比較模糊,我們可以加到2或3倍,太大安卓機(jī)容易出問題,我們可以采用設(shè)備的像素作為倍率,uni.getSystemInfo獲取設(shè)備信息
創(chuàng)建canvas繪制上下文,在繪制canvas的時(shí)候,把兩個(gè)圖片都切割成圓形的圖片,然后中間就出現(xiàn)了一條線,在之后對(duì)原價(jià)進(jìn)行劃線刪除狀態(tài)的時(shí)候設(shè)置劃線的顏色,會(huì)影響圓形的邊線顏色。然后才知道,canvas繪圖是在JS中完成,只能通過JS代碼來控制,JS提供了beginPath()和closePath()兩個(gè)函數(shù)來控制canvas中模塊的開始和結(jié)束,這樣就可以避免屬性覆蓋。
補(bǔ)充:和后端交互生成二維碼,需要傳參數(shù),要注意頁面路徑不能再根目錄前不增加/,攜帶的參數(shù)最大為32個(gè)可見字符,且字符有要求限制,如果不符合規(guī)范就會(huì)生成二維碼失敗
在頁面的onLoad生命周期參數(shù)options判斷options.scene是否存在,存在就是掃描二維碼進(jìn)入,需要對(duì)二維碼返回的參數(shù)進(jìn)行處理,先根據(jù)官方文檔來進(jìn)行解碼,decodeURIComponent會(huì)將%3D解碼成=,我傳參的時(shí)候是通過&符號(hào)將參數(shù)之間隔開的,所以我這里使用split('&'),將解碼后的字符串通過&符號(hào)分割成數(shù)組,循環(huán)數(shù)組得到鍵名和鍵值,代碼如下(掃普通鏈接二維碼打開小程序 | 微信開放文檔 (qq.com))
生成、獲取二維碼代碼
onLoad: function(options) {
? ? if (options.scene) { //二維碼進(jìn)入
const scene1 = decodeURIComponent(options.scene);
let a = scene1.toString().split('&');
let obj = {}
for (let i = 0; i < a.length; i++) {
// 獲取=前
let index = a[i].indexOf("=")
let name = a[i].substring(0, index);//鍵名
// 獲取=后
let index1 = a[i].indexOf("=")
let value = a[i].substring(index1 + 1, a[i].length);
obj[name] = value //鍵值
}
this.goodsId = obj.id //獲取商品id
this.getType = obj.type; //獲取進(jìn)入頁面的方式
} else{//非二維碼進(jìn)入
? ? ? ?}
}
//生成二維碼向后端傳遞的參數(shù)
let data = {
page: 'page_my/pages/chengbei-goods-details/index',//路徑
scene: `id=${that.goodsId}&code=${that.userCode}&type=${that.getType}`, //參數(shù)
}
HTML代碼如下文章來源:http://www.zghlxwxcb.cn/news/detail-532625.html
<!-- 海報(bào) -->
<canvas :style="{height: pupopHeight + 'px',width: pupopWidth + 'px'}" canvas-id="myCanvas"></canvas>
<uni-popup ref="popup" type="center">
<view class="popup-wrap" :style="'width:' + width + 'px'">
<!-- <view class="popup-head">
生成海報(bào)
<view @click="close_popup()" class="close_icon"></view>
</view> -->
<image :src="posterImg" mode="widthFix" style="width: 100%;"></image>
<view class="popup-footer">
<view class="buttons margin_l30 marTop20" @click="saveToLocal()">保存到相冊(cè)</view>
<view class="buttonNo margin_l30 marTop20" @click="closeToLocal()">取消</view>
<!-- <view class="tips">保存圖片到相冊(cè),你就可以分享啦!</view> -->
</view>
</view>
</uni-popup>
<!-- 海報(bào)end -->
JS代碼如下文章來源地址http://www.zghlxwxcb.cn/news/detail-532625.html
data(){
? ? return{
? ? ? ? ? ? ? ? // 海報(bào)
pupopWidth: 590,
pupopHeight: 900,
pixelRatio: 3, //屏幕像數(shù)密度
inviteQR: '', //動(dòng)態(tài)二維碼
posterImg: '', //最后生成的海報(bào)
width: 100, //當(dāng)前手機(jī)寬度
// 海報(bào)end ?
? ? }
},
onLoad(){
? ? ? ? ? ? let that = this
uni.getSystemInfo({
success: function(res) {
that.width = res.windowWidth * 0.8 //獲取海報(bào)彈出層的寬度
that.pixelRatio = res.pixelRatio //獲取像素比
}
})
this.pupopWidth = this.pupopWidth * this.pixelRatio
this.pupopHeight = this.pupopHeight * this.pixelRatio
console.log("屏幕像素密度", this.pixelRatio, this.pupopWidth, this.pupopHeight)
},
methods:{
? ? ? ? ? ? // 生成海報(bào)
shareing() {
this.isHideSharePopor()
uni.showLoading({
title: "海報(bào)生成中...",
mask: true
})
//#ifdef MP-WEIXIN
//這里參數(shù)是前端和后端商議好,生成二維碼需要前端傳那些對(duì)應(yīng)的值,這里我傳了當(dāng)前頁面的路徑和邀請(qǐng)碼及當(dāng)前頁面的參數(shù)
this.inviteQR = this.ewmImgUrl //這里是獲取后端傳來的二維碼,先給寫死了
this.createPoster();
//#endif
},
//生成海報(bào)--微信端
createPoster() {
let _this = this
_this.headImg = uni.getStorageSync('avatarUrl') //去本地緩存獲取頭像
uni.getImageInfo({
src: _this.headImg,
success(image) {
const canvasId = "myCanvas"
let ctx = uni.createCanvasContext(canvasId, _this) // 自定義組件中 一定要傳this
// 填充背景
ctx.setFillStyle('#FAFAFA')
ctx.fillRect(0, 0, _this.pupopWidth, _this.pupopHeight);
ctx.save()
// 頭像和二維碼大小都需要在規(guī)定大小的基礎(chǔ)上放大像素比的比例后面都會(huì)*this.pixelRatio
let headerW = 102 * _this.pixelRatio
let headerX = 40 * _this.pixelRatio
let headerY = 40 * _this.pixelRatio
// 控制頭像為圓形
ctx.beginPath()
ctx.setStrokeStyle('rgba(0,0,0,.2)') //設(shè)置線條顏色,如果不設(shè)置默認(rèn)是黑色,頭像四周會(huì)出現(xiàn)黑邊框
ctx.arc(headerX + headerW / 2, headerY + headerW / 2, headerW / 2, 0, 2 * Math.PI)
ctx.stroke() //畫出當(dāng)前路徑的邊框
ctx.clip()//畫完之后執(zhí)行clip()方法,否則不會(huì)出現(xiàn)圓形效果
ctx.drawImage(image.path, headerX, headerY, headerW, headerW)// 將頭像畫到畫布上
ctx.restore()
ctx.strokeStyle = '#EEEEEE';
ctx.save()
ctx.closePath()
//繪制小程序名字
const uniqueCode = "木之本櫻";
let invateCode = `${uniqueCode}`
let invateCodeX = headerX + headerW + 14 * _this.pixelRatio
let invateCodeY = headerY + (40 * _this.pixelRatio)
ctx.setFontSize(26 * _this.pixelRatio);
ctx.setFillStyle('#333333');
ctx.fillText(invateCode, invateCodeX, invateCodeY);
ctx.stroke();
//繪制廣告語
let invateCode1 = "廣告語"
let invateCodeX1 = headerX + headerW + 14 * _this.pixelRatio
let invateCodeY1 = headerY + (84 * _this.pixelRatio)
ctx.setFontSize(26 * _this.pixelRatio);
ctx.setFillStyle('#333333');
ctx.fillText(invateCode1, invateCodeX1, invateCodeY1);
ctx.stroke();
//生成banner圖
uni.getImageInfo({
src: _this.goodsThumbnailUrl, //這里的banner是展示的商品圖
success(image) {
let bannerW = 510 * _this.pixelRatio
let bannerH = 510 * _this.pixelRatio
let bannerX = 40 * _this.pixelRatio
let bannerY = 40 * _this.pixelRatio + headerW + 24 * _this.pixelRatio
// 控制商品圖片為圓形
ctx.beginPath()
ctx.setStrokeStyle('rgba(0,0,0,.2)') //設(shè)置線條顏色,如果不設(shè)置默認(rèn)是黑色,頭像四周會(huì)出現(xiàn)黑邊框
ctx.arc(bannerX + bannerW / 2, bannerY + bannerW / 2, bannerW / 2, 0, 2 * Math.PI)
// ctx.strokeStyle = 'red'; //設(shè)置線的顏色狀態(tài)
ctx.stroke()
//畫完之后執(zhí)行clip()方法,否則不會(huì)出現(xiàn)圓形效果
ctx.clip()
// 將商品主圖畫到畫布上
ctx.drawImage(image.path, bannerX, bannerY, bannerW, bannerH)
ctx.restore()
ctx.save()
ctx.closePath()
//現(xiàn)價(jià)
let bannerTextX = 40 * _this.pixelRatio
let bannerTextY = bannerY + bannerH + 20 * _this.pixelRatio + 50 * _this.pixelRatio //這里的y軸起始值是頂上的距離還要特意加上文字的行高
let chr = `¥${_this.oldPrice}`; //這個(gè)方法是將一個(gè)字符串分割成字符串?dāng)?shù)組
ctx.setFontSize(50 * _this.pixelRatio);
ctx.setFillStyle('#EA5506');
ctx.fillText(chr, bannerTextX, bannerTextY);
ctx.stroke();
// 測試當(dāng)前現(xiàn)價(jià)的寬度
let metrics = ctx.measureText(`¥${_this.oldPrice}`)
// 劃線價(jià)
let bannerTextX1 = 40 * _this.pixelRatio + metrics.width + 20 * _this
.pixelRatio
let bannerTextY1 = bannerY + bannerH + 20 * _this.pixelRatio + 50 * _this
.pixelRatio //這里的y軸起始值是頂上的距離還要特意加上文字的行高
let chr1 = `¥${_this.newPrice}`; //這個(gè)方法是將一個(gè)字符串分割成字符串?dāng)?shù)組
ctx.setFontSize(26 * _this.pixelRatio);
ctx.setFillStyle('#999999');
ctx.fillText(chr1, bannerTextX1, bannerTextY1);
ctx.stroke();
// 測試原價(jià)的寬度
let metrics1 = ctx.measureText(`¥${_this.newPrice}`)
//畫字體刪除線
ctx.beginPath()
ctx.moveTo(bannerTextX1, bannerTextY1 - 10 * _this.pixelRatio); //移動(dòng)到指定位置 X Y
//設(shè)置起點(diǎn)狀態(tài)
ctx.lineTo(bannerTextX1 + metrics1.width, bannerTextY1 - 10 * _this
.pixelRatio);
//設(shè)置末端狀態(tài)
ctx.lineWidth = 1 * _this.pixelRatio; //設(shè)置線寬狀態(tài)
ctx.strokeStyle = '#999999'; //設(shè)置線的顏色狀態(tài)
// ctx.setStrokeStyle('#999999')
ctx.stroke();
ctx.closePath()
//二維碼
uni.getImageInfo({
src: _this.inviteQR,
success(res) {
// 畫當(dāng)前頁面的二維碼 _this.pupopWidth
const img_w = 160 * _this.pixelRatio
const img_x = _this.pupopWidth - 40 * _this.pixelRatio -
img_w
const img_y = bannerY + bannerH + 20 * _this.pixelRatio
ctx.drawImage(res.path, img_x, img_y, img_w, img_w)
// 商品名字 goodsName
//這里會(huì)處理多行顯示文字,超出顯示省略號(hào)的效果
let bannerTextX = 40 * _this.pixelRatio
let bannerTextY = bannerY + bannerH + 20 * _this.pixelRatio + 150 * _this.pixelRatio //這里的y軸起始值是頂上的距離還要特意加上文字的行高
let chr = _this.goodsName.split(""); //這個(gè)方法是將一個(gè)字符串分割成字符串?dāng)?shù)組
let temp = "";
let row = [];
ctx.setFontSize(30 * _this.pixelRatio)
ctx.setFillStyle("#333333")
for (var a = 0; a < chr.length; a++) {
if (ctx.measureText(temp).width < 300 * _this.pixelRatio) {
temp += chr[a];
} else {
a--; //這里添加了a-- 是為了防止字符丟失,效果圖中有對(duì)比
row.push(temp);
temp = "";
}
}
row.push(temp);
if (row.length > 2) {
let rowCut = row.slice(0, 1);
let rowPart = rowCut[0];
let test = "";
let empty = [];
for (var a = 0; a < rowPart.length; a++) {
if (ctx.measureText(test).width < 300 * _this.pixelRatio) {
test += rowPart[a];
} else {
break;
}
}
empty.push(test);
var group = empty[0] + "..." //這里只顯示1行,超出的用...表示
rowCut.splice(0, 1, group);
row = rowCut;
}
for (var b = 0; b < row.length; b++) {
ctx.fillText(row[b], bannerTextX, bannerTextY + b *50 * _this.pixelRatio, 510 * _this.pixelRatio);
}
ctx.draw(false, () => {
uni.canvasToTempFilePath({
width: _this.pupopWidth,
height: _this.pupopHeight,
destWidth: _this.pupopWidth,
destHeight: _this.pupopHeight,
canvasId: canvasId,
fileType: 'png',
quality: 1,
success: function(res) {
_this.posterImg = res
.tempFilePath; //最終將canvas轉(zhuǎn)換為圖片
_this.$refs.popup.open();
uni.hideLoading()
},
fail(error) {
console.log('4', error)
// appEv.arrTips("生成海報(bào)失敗,請(qǐng)稍后重試!")
setTimeout(() => {
uni.hideLoading()
}, 2000)
}
}, _this)
})
},
fail(error) {
console.log('獲取二維碼失敗', error)
// appEv.arrTips("生成海報(bào)失敗,獲取二維碼失敗")
setTimeout(() => {
uni.hideLoading()
}, 2000)
}
})
},
fail(error) {
console.log('生成商品圖失敗', error)
// appEv.arrTips("生成海報(bào)失敗,獲取商品圖失敗")
setTimeout(() => {
uni.hideLoading()
}, 2000)
}
});
},
fail(error) {
console.log('生成頭像失敗', error)
// appEv.arrTips("生成海報(bào)失敗,獲取頭像失敗")
setTimeout(() => {
uni.hideLoading()
}, 2000)
}
})
},
// 取消保存
closeToLocal() {
this.$refs.popup.close()
},
//將圖片保存到本地相冊(cè)
saveToLocal() {
//#ifdef MP-WEIXIN
uni.saveImageToPhotosAlbum({
filePath: this.posterImg,
success: () => {
console.log('保存到相冊(cè)成功')
// appEv.arrTips("保存到相冊(cè)成功")
this.$refs.popup.close()
},
fail: (err) => {
console.log("保存到相冊(cè)失敗", err)
}
});
//#endif
},
},
到了這里,關(guān)于uniapp實(shí)現(xiàn)微信小程序端動(dòng)態(tài)生成海報(bào)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!