之前做一个uniapp开发的微信小程序需要生成商品海报,在网上学习后完成,记录一下。
主要使用了canvas
,uniapp官网链接:canvas使用方法
自定义生成海报组件tr-xqgenrate.vue
<template>
<view>
<u-mask :z-index="90" :show="share_qrcode_flag" :zoom="true" :custom-style="{ background: 'rgba(0,0,0,.6)' }"
:duration="300">
<view class="sq_box">
<view class="tz_box qrcode_box">
<view class="icon-close-text flex-column justify-content-conter align-items-conter"
v-if="canvasToTempFilePath!=null">
<text class="text-t">长按保存海报到相册即可分享~</text>
<text class="iconfont iconshenheweitongguo slone-iconfont" @tap="Close"></text>
</view>
<view class="share_qrcode">
<canvas canvas-id="myCanvas"
style="width: 690px;height:1060px; position: fixed;top: -10000px;"></canvas>
<image @longpress="this.showSaveImgWin = true"
style="width: 100%; height: 100%;border-radius: 10rpx;" :src="canvasToTempFilePath"></image>
</view>
</view>
</view>
</u-mask>
<u-modal v-model="showSaveImgWin" content="确定要保存图片吗" @confirm="saveShareImg(canvasToTempFilePath)"
@cancel="this.showSaveImgWin = false" :show-cancel-button="true"></u-modal>
</view>
</template>
<script>
export default {
name: 'tr-xqgenrate',
data() {
return {
ratio: 1,
ctx: null, // 创建canvas对象
canvasToTempFilePath: null, // 保存最终生成的导出的图片地址
openStatus: true, // 声明一个全局变量判断是否授权保存到相册
share_qrcode_flag: false,
showSaveImgWin: false, //保存图片到相册
};
},
methods: {
Close() {
this.share_qrcode_flag = false
setTimeout(() => {
this.$emit('IsbuttomBuy', true)
}, 180)
},
share_qrcode(XqOption) {
if (!this.canvasToTempFilePath) {
this.createCanvasImage(XqOption);
} else {
this.$emit('IsbuttomBuy', false)
}
this.share_qrcode_flag = true;
},
//获取图片信息
downloadFileImg(url) {
return new Promise(resolve => {
uni.getImageInfo({
src: url,
success: res => {
resolve(res.path);
},
fail: err => {
uni.showToast({
title: '网络错误请重试',
icon: 'loading'
});
}
});
});
},
// 生成海报
async createCanvasImage(option) {
if (!this.ctx) {
uni.showLoading({
title: '正在生成海报...'
});
let code = this.downloadFileImg(option.codeUrl); //小程序太阳码
let cover = this.downloadFileImg(option.GoodsImage); //商品图片
let headImg = this.downloadFileImg(option.StoreHeadUrl); //店铺头像
let bgUrl = ''; //背景图片
if (option.bgUrl) {
bgUrl = new Promise(resolve => {
uni.downloadFile({
url: option.bgUrl,
success: res => {
resolve(res.tempFilePath);
},
fail: erros => {
uni.showToast({
title: '网络错误请重试',
icon: 'loading'
});
}
});
});
}
Promise.all([headImg, code, cover, bgUrl]).then(result => {
const ctx = uni.createCanvasContext('myCanvas', this);
let canvasWidthPx = 620 * this.ratio,
canvasHeightPx = 1060 * this.ratio,
codeurl_width = 130, //小程序太阳码宽度
codeurl_heigth = 130, //小程序太阳码高度
codeurl_x = 525, //小程序太阳码在画布上的位置
codeurl_y = 760, //小程序太阳码在画布上的位置
coverurl_width = 630, //封面宽度
coverurl_heigth = 635, //封面高度
coverurl_x = 30, //封面在画布上的位置
coverurl_y = 30, //封面在画布上的位置
avatarurl_width = 80, //店铺头像宽度
avatarurl_heigth = 80, //店铺头像高度
avatarurl_x = 50, //头像在画布上的位置
avatarurl_y = 930; //头像在画布上的位置
//绘制圆角矩形
ctx.save();
ctx.translate(0, 0);
//绘制圆角矩形的各个边
this.drawRoundRectPath(ctx, 690, 1060, 10);
ctx.fillStyle = option.fillStyle || '#ffffff';
ctx.fill();
ctx.restore();
ctx.save();
ctx.beginPath(); //开始绘制
ctx.arc(avatarurl_width / 2 + avatarurl_x, avatarurl_heigth / 2 + avatarurl_y,
avatarurl_width / 2, 0, Math.PI * 2, false);
this.drawRoundRect(ctx, avatarurl_x, avatarurl_y, avatarurl_width, avatarurl_heigth,
10)
ctx.clip();
ctx.drawImage(result[0], avatarurl_x, avatarurl_y, avatarurl_width,
avatarurl_heigth); // 推进去图片
ctx.restore();
ctx.setFillStyle('#333333');
if (option.GoodsName) {
this.dealWords({
ctx: ctx,
fontSize: 30,
word: option.GoodsName,
maxWidth: 640,
x: 30,
y: 670,
maxLine: 2
});
}
ctx.font = 'normal 500 27px sans-serif';
ctx.setFillStyle('#E09212');
ctx.fillText('¥', 30, 805);
ctx.font = 'normal bold 42px sans-serif'; //现价
ctx.setFillStyle('#E09212');
ctx.fillText(option.money, 62, 805);
ctx.font = 'normal 500 24px sans-serif'; //已拼 or 已售
ctx.setFillStyle('#808080');
ctx.fillText(option.Sold, option.SoldElementLeft, 801);
ctx.font = 'normal 500 24px sans-serif'; //原价
ctx.setFillStyle('#808080');
ctx.fillText(option.primaryMoney, 30, 860);
ctx.setFillStyle('#333333');
if (option.StoreName) {
this.dealWords({
ctx: ctx,
fontSize: 28,
word: option.StoreName,
maxWidth: 480,
x: 150,
y: 920,
maxLine: 1
});
}
ctx.setFillStyle('#999999');
if (option.SoreAddress) {
this.dealWords({
ctx: ctx,
fontSize: 24,
word: option.SoreAddress,
maxWidth: 480,
x: 150,
y: 960,
maxLine: 1
});
}
ctx.drawImage(result[2], coverurl_x, coverurl_y, coverurl_width,
coverurl_heigth);
ctx.drawImage(result[1], codeurl_x, codeurl_y, codeurl_width, codeurl_heigth);
// 绘制矩形
ctx.lineWidth = 2;
ctx.setStrokeStyle('rgba(0, 0, 0, 0.05)');
this.drawRoundRect(ctx, 30, 910, 620, 120, 10)
ctx.stroke();
ctx.closePath();
ctx.draw(false, () => {
uni.canvasToTempFilePath({
canvasId: 'myCanvas',
success: res => {
this.canvasToTempFilePath = res.tempFilePath;
uni.showToast({
title: '绘制成功'
});
},
fail: err => {
uni.showToast({
title: '绘制失败'
});
},
complete: () => {
uni.hideLoading();
uni.hideToast();
this.$emit('IsbuttomBuy', false)
},
},
this
);
});
});
}
},
drawRoundRect(ctx, x, y, width, height, radius) { //圆角
ctx.beginPath();
ctx.arc(x + radius, y + radius, radius, Math.PI, Math.PI * 3 / 2);
ctx.lineTo(width - radius + x, y);
ctx.arc(width - radius + x, radius + y, radius, Math.PI * 3 / 2, Math.PI * 2);
ctx.lineTo(width + x, height + y - radius);
ctx.arc(width - radius + x, height - radius + y, radius, 0, Math.PI * 1 / 2);
ctx.lineTo(radius + x, height + y);
ctx.arc(radius + x, height - radius + y, radius, Math.PI * 1 / 2, Math.PI);
ctx.closePath();
},
drawRoundRectPath(cxt, width, height, radius) {
cxt.beginPath(0);
//从右下角顺时针绘制,弧度从0到1/2PI
cxt.arc(width - radius, height - radius, radius, 0, Math.PI / 2);
//矩形下边线
cxt.lineTo(radius, height);
//左下角圆弧,弧度从1/2PI到PI
cxt.arc(radius, height - radius, radius, Math.PI / 2, Math.PI);
//矩形左边线
cxt.lineTo(0, radius);
//左上角圆弧,弧度从PI到3/2PI
cxt.arc(radius, radius, radius, Math.PI, (Math.PI * 3) / 2);
//上边线
cxt.lineTo(width - radius, 0);
//右上角圆弧
cxt.arc(width - radius, radius, radius, (Math.PI * 3) / 2, Math.PI * 2);
//右边线
cxt.lineTo(width, height - radius);
cxt.closePath();
},
//处理文字多出省略号显示
dealWords(options) {
options.ctx.setFontSize(options.fontSize); //设置字体大小
options.ctx.setFont; //设置字体大小
let allRow = Math.ceil(options.ctx.measureText(options.word).width / options.maxWidth); //实际总共能分多少行
let count = allRow >= options.maxLine ? options.maxLine : allRow; //实际能分多少行与设置的最大显示行数比,谁小就用谁做循环次数
let endPos = 0; //当前字符串的截断点
for (let j = 0; j < count; j++) {
let nowStr = options.word.slice(endPos); //当前剩余的字符串
let rowWid = 0; //每一行当前宽度
if (options.ctx.measureText(nowStr).width > options.maxWidth) {
//如果当前的字符串宽度大于最大宽度,然后开始截取
for (let m = 0; m < nowStr.length; m++) {
rowWid += options.ctx.measureText(nowStr[m]).width; //当前字符串总宽度
if (rowWid > options.maxWidth) {
if (j === options.maxLine - 1) {
//如果是最后一行
options.ctx.fillText(nowStr.slice(0, m - 1) + '...', options.x, options.y + (j + 1) *
40); //(j+1)*18这是每一行的高度
} else {
options.ctx.fillText(nowStr.slice(0, m), options.x, options.y + (j + 1) * 40);
}
endPos += m; //下次截断点
break;
}
}
} else {
//如果当前的字符串宽度小于最大宽度就直接输出
options.ctx.fillText(nowStr.slice(0), options.x, options.y + (j + 1) * 40);
}
}
},
// 保存到系统相册
saveShareImg(canvasToTempFilePath) {
uni.saveImageToPhotosAlbum({
filePath: canvasToTempFilePath,
success: () => {
this.$u.toast('保存成功,快去分享到朋友圈吧~');
},
fail: () => {
this.$u.toast('保存失败~');
}
});
}
}
};
</script>
使用组件
调用海报组件的方法给子组件传需要渲染的值
async SetBill() { //生成海报
let url = '',
data = {},
SoldElementLeft = 62;
if (this.codeUrl.length <= 0) {
if (this.GoodsData.GoodsType == 1) { //普通商品获取小程序太阳码
url = this.$api.goods.paramCode
data = {
product_id: this.GoodsData.GoodsId,
child_store_id: this.StoreDataList.id
}
} else if (this.GoodsData.GoodsType == 0) { //拼团商品获取小程序太阳码
url = this.$api.Collage.spellParamCode
data = {
spell_goods_id: this.GoodsData.GoodsId,
child_store_id: this.StoreDataList.id
}
}
let ReauestList = await this.$request({
url: url,
method: "GET",
data,
loading: true
});
uni.hideLoading()
if (ReauestList.code != 200) {
uni.showToast({
title: "获取小程序太阳码失败",
icon: "none",
duration: 3000
})
return false
} else {
this.codeUrl = ReauestList.data.qrcode
}
}
SoldElementLeft += (this.GoodaDetails.price.toString().length * 30)
this.$refs.bottomShare.close()
let MyXqOption = {
codeUrl: this.codeUrl,
GoodsImage: (this.GoodsData.GoodsType == 1 ? this.GoodaDetails.pic_url[0].url : this
.GoodaDetails
.images[0].url),
StoreHeadUrl: this.StoreDataList.logo,
fillStyle: '#FFFFFF',
money: this.GoodaDetails.price,
primaryMoney: '原价' + (this.GoodsData.GoodsType == 0 ? this.GoodaDetails
.shop_price : this
.GoodaDetails
.original_price),
Sold: (this.GoodsData.GoodsType == 0 ? '已拼' + this.GoodaDetails.bum_number + '人' : '已售' +
this
.GoodaDetails
.sale_number),
GoodsName: this.GoodaDetails.title,
StoreName: this.StoreDataList.cs_name,
SoreAddress: this.StoreDataList.address,
SoldElementLeft: SoldElementLeft
}
this.$refs.TrXqgen.share_qrcode(MyXqOption)
},