已封装好的代码
<template>
<view class="canvas-content">
<!-- 海报 -->
<canvas class="canvas" :style="'width: ' + canvasWith + 'px; height: ' + canvasHeight + 'px;'" canvas-id="myCanvas"></canvas>
<view class="button-wrapper">
<!-- 保存海报按钮 -->
<button class="save-btn" @click="saveCanvasImage" v-if="isPhoto">保存海报</button>
<button class="save-btn" open-type="openSetting" @opensetting="getPhotoSetting" v-else>保存海报</button>
</view>
<view class="del-icon"><image @click="handleCanvasCancel" src="../../static/canvas/del.png" mode=""></image></view>
</view>
</template>
<script>
export default {
data() {
return {
bgScale: 0.914, // 头部背景缩放比例 320 / canvasWith(350)
aScale: 0.2, // 二维码缩放比例 120 / canvasWith(350)
screenWidth: 750,
canvasWith: 650,
canvasHeight: 900,
bgPic: {
// 圆角背景宽高
w: 320,
h: 320
},
codePic: {
// 二维码宽高
w: 120,
h: 120
},
codeImg: 'https://a.mushihulian.com/group1/M00/00/AE/rBGrr1-Pox-AJ6wLAAGlJGxOb4E997.png',
isPhoto: true,
canvasBgLink: '',
canvasShow: false,
system: null,
attrs: {}
};
},
props: {
canvasAttr: {
type: Object
}
},
// 监听页面显示
onShow() {
},
mounted() {
this.isSavePic();
},
methods: {
// 保存图片的授权设置(二次授权情况下触发)
getPhotoSetting(e) {
const isTrue = e.detail.authSetting['scope.writePhotosAlbum'];
this.isPhoto = isTrue ? true : false;
},
// 检测是否有授权保存图片功能,以显示不同按钮
isSavePic() {
uni.getSetting({
success: res => {
const isTrue = res.authSetting['scope.writePhotosAlbum'];
if (isTrue === undefined) {
this.isPhoto = true;
} else {
this.isPhoto = isTrue ? true : false;
}
}
});
},
// 获取设备信息
getSystem() {
const system = uni.getSystemInfoSync();
// console.log(system, '设备信息');
const ratio = this.screenWidth / system.windowWidth;
this.canvasHeight = this.canvasHeight / ratio;
this.canvasWith = this.canvasWith / ratio;
this.bgPic = {
// 圆角白色背景宽高
w: this.canvasWith * this.bgScale,
h: this.canvasWith * this.bgScale
};
this.codePic = {
// 二维码宽高
w: this.canvasWith * this.aScale,
h: this.canvasWith * this.aScale
};
},
// 展示海报
posterShow() {
this.getSystem();
// this.isSavePic();
console.log(this.codeImg,this.canvasAttr, '咯');
Object.assign(this.attrs, this.canvasAttr);
this.creatPoster(this.attrs);
},
// 生成canvas海报
creatPoster(canvasAttr) {
const canvasWith = this.canvasWith;
const canvasHeight = this.canvasHeight;
const ctx = uni.createCanvasContext('myCanvas', this);
// console.log(canvasAttr, '娃哈哈');
ctx.draw(); //清空之前的海报
ctx.save();
// 圆角背景图
ctx.restore();
this.roundRect(ctx, 0,0, canvasWith, canvasHeight, 10, '#fff', canvasAttr.bgImg, true);
ctx.save();
// 白色背景
ctx.restore();
// this.roundRect(ctx,this.canvasWith*0.044, canvasHeight-172, this.bgPic.h, 160, 10, '#fff');
this.roundRect(ctx,12, canvasHeight-172, this.bgPic.h, 160, 10, '#fff');
ctx.save();
// 画圆形头像
ctx.restore();
this.drawCircular(
ctx,canvasAttr,
this.canvasWith*0.1,
this.canvasHeight*0.55,
40 ,40
);
ctx.save();
// 文字
ctx.setFillStyle('#000000'); // 文字颜色
ctx.setFontSize(15); // 文字字号
ctx.textAlign="start";
ctx.fillText('昵称一样', 76, canvasHeight-135); // 绘制文字
ctx.setFillStyle('#666666'); // 文字颜色
ctx.setFontSize(15); // 文字字号
ctx.textAlign="start";
ctx.fillText('2020-09-16', 76, canvasHeight-116); // 绘制文字
// ctx.beginPath();
// // 设置线宽
// ctx.lineWidth = 1;
// 移动画笔至坐标 x20 y20 的位置
ctx.moveTo(29, canvasHeight-100);
// 绘制到坐标 x20 y100 的位置
ctx.lineTo(this.bgPic.h-6, canvasHeight-100);
// 填充颜色
ctx.strokeStyle="#C4C4C4";
// // 开始填充
// ctx.stroke();
// ctx.closePath();
// ctx.save();
// 画二维码
ctx.restore();
ctx.drawImage(canvasAttr.codeImg, this.canvasWith*0.69, canvasHeight-164, this.codePic.w, this.codePic.w);
ctx.save();
ctx.setFillStyle('#666666');
ctx.setFontSize(12);
ctx.textAlign="start";
ctx.fillText('累计打卡', 32, canvasHeight-76);
ctx.setFillStyle('#000000'); //
ctx.setFontSize(26);
ctx.textAlign="end";
ctx.fillText('210', this.bgPic.h-20, canvasHeight-70);
ctx.setFillStyle('#666666'); //
ctx.setFontSize(12);
ctx.textAlign="end";
ctx.fillText('天', this.bgPic.h-6, canvasHeight-70);
ctx.strokeStyle="#C4C4C4";//左
ctx.moveTo(29, canvasHeight-60);
// 绘制到坐标 x20 y100 的位置
ctx.lineTo(this.bgPic.h-6, canvasHeight-60);
ctx.stroke();
ctx.setFillStyle('#666666');
ctx.setFontSize(12);
ctx.textAlign="start";
ctx.fillText('完成一亿部', 32, canvasHeight-40);
ctx.setFillStyle('#666666');
ctx.setFontSize(12);
ctx.textAlign="start";
ctx.fillText('累计打卡', 32, canvasHeight-23);//经书名称
ctx.setFillStyle('#000000');
ctx.setFontSize(26);
ctx.textAlign="end";
ctx.fillText('210', this.bgPic.h-20, canvasHeight-28); //经书数量
ctx.setFillStyle('#666666');
ctx.setFontSize(12);
ctx.textAlign="end";
ctx.fillText('部', this.bgPic.h-6, canvasHeight-28);
//左右边距线
// ctx.strokeStyle="red";//右
// ctx.moveTo(this.bgPic.h-6,canvasHeight-180);
// ctx.lineTo(this.bgPic.h-6,canvasHeight);
// ctx.stroke();
// ctx.strokeStyle="red";//右
// ctx.moveTo(this.bgPic.h-20,canvasHeight-100);
// ctx.lineTo(this.bgPic.h-20,canvasHeight);
// ctx.stroke();
// ctx.strokeStyle="red";//左
// ctx.moveTo(29,canvasHeight-180);
// ctx.lineTo(29,canvasHeight);
// ctx.stroke();
//绘制结束
ctx.draw(true);
},
/**
* 画圆形头像
* @param { object } ctx : canvas上下文
* @param { number } x : 圆心 x 轴坐标
* @param { number } y : 圆心 y 轴坐标
* @param { number } width : canvas上下文
* @param { number } ctx:canvas上下文
* 是否逆时针旋转: false 代表顺时针旋转
* @author: wudidi
*/
drawCircular(ctx, canvasAttr, x, y, width, height) {
ctx.save();
let avatar_width = width,
avatar_heigth = height,
avatar_x = x,
avatar_y = y;
// 开始绘制路径
ctx.beginPath();
// 绘制圆的路径 0 即 0°是从三点钟方向开始的
ctx.arc(avatar_width / 2 + avatar_x, avatar_heigth + avatar_y, avatar_width / 2, 0, Math.PI * 2, false);
ctx.closePath();
ctx.clip();
ctx.setFillStyle('#F1F1F1');
ctx.fill();
ctx.drawImage(canvasAttr.handUrl, avatar_x, avatar_heigth / 2 + avatar_y, avatar_width, avatar_heigth);
ctx.restore();
},
/**
* 圆角背景
* @param {CanvasContext} ctx canvas上下文
* @param {number} x 圆角矩形选区的左上角 x坐标
* @param {number} y 圆角矩形选区的左上角 y坐标
* @param {number} w 圆角矩形选区的宽度
* @param {number} h 圆角矩形选区的高度
* @param {number} r 圆角的半径
* @param {String} fillColor 填充颜色
* @author: wudidi
*/
roundRect(ctx, x, y, w, h, r, fillColor = '#ffffff', url, isBg) {
ctx.save();
// 开始绘制
ctx.beginPath();
// 因为边缘描边存在锯齿,最好指定使用 transparent 填充
ctx.setFillStyle('transparent');
// 绘制左上角圆弧
ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5);
// 绘制border-top
ctx.moveTo(x + r, y);
ctx.lineTo(x + w - r, y);
ctx.lineTo(x + w, y + r);
// 绘制右上角圆弧
ctx.arc(x + w - r, y + r, r, Math.PI * 1.5, Math.PI * 2);
// 绘制border-right
ctx.lineTo(x + w, y + h - r);
ctx.lineTo(x + w - r, y + h);
// 绘制右下角圆弧
ctx.arc(x + w - r, y + h - r, r, 0, Math.PI * 0.5);
// 绘制border-bottom
ctx.lineTo(x + r, y + h);
ctx.lineTo(x, y + h - r);
// 绘制左下角圆弧
ctx.arc(x + r, y + h - r, r, Math.PI * 0.5, Math.PI);
// 绘制border-left
ctx.lineTo(x, y + r);
ctx.lineTo(x + r, y);
ctx.fill();
ctx.closePath();
// 剪切
ctx.clip();
ctx.setFillStyle(fillColor);
ctx.fillRect(x, y, w, h);
if (isBg) {
ctx.drawImage(url, 0,0, this.canvasWith, this.canvasHeight);
}
ctx.restore();
},
// 保存图片到相册
async saveCanvasImage() {
uni.showLoading({
title: '海报保存中'
});
await this.createCanvasLink();
console.log(this.canvasBgLink, '图片链接');
if (this.canvasBgLink) {
uni.getSetting({
success: res => {
if (res.authSetting['scope.writePhotosAlbum']) {
this.savaPhotos();
} else {
uni.authorize({
scope: 'scope.writePhotosAlbum',
success: res => {
this.savaPhotos();
},
fail: err => {
this.isPhoto = false;
}
});
}
}
});
} else {
uni.hideLoading();
uni.showToast({
title: '海报保存失败',
duration: 2000,
icon: 'none'
});
}
},
// 把画布转化成临时文件
createCanvasLink() {
return new Promise((resolve, reject) => {
uni.canvasToTempFilePath({
x: 0,
y: 0,
width: this.canvasWith, // 画布的宽
height: this.canvasHeight, // 画布的高
canvasId: 'myCanvas',
success: res => {
this.canvasBgLink = res.tempFilePath;
resolve({ res });
},
fail: () => {
// uni.hideLoading();
// uni.showToast({
// title: '海报生成失败 稍后再试',
// duration: 2000,
// icon: 'none'
// });
}
},this);
});
},
//保存图片至相册
savaPhotos() {
uni.saveImageToPhotosAlbum({
filePath: this.canvasBgLink,
success: res => {
this.isPhoto = true;
uni.hideLoading();
this.handleCanvasCancel();
},
fail: err => {
this.handleCanvasCancel();
uni.hideLoading();
uni.showToast({
title: '海报保存失败',
duration: 2000,
icon: 'none'
});
}
});
},
// 取消海报
handleCanvasCancel() {
this.$emit('canvasCancel', false);
}
}
};
</script>
<style lang="scss">
.content {
text-align: center;
height: 100%;
}
.canvas-content {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.del-icon {
display: flex;
width: 100%;
height: 50rpx;
justify-content: center;
margin-top: 20rpx;
> image {
width: 50rpx;
height: 50rpx;
margin-right: 16rpx;
margin-bottom: 10rpx;
border-radius: 50%;
}
}
.button-wrapper {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 72rpx;
margin-top: 15px;
z-index: 16;
}
.save-btn {
width: 40%;
height: 100%;
font-size: 30rpx;
line-height: 72rpx;
color: #fff;
background: #ffbe1a;
text-align: center;
border-radius: 45rpx;
border-radius: 36rpx;
z-index: 10;
}
.cancel-btn {
background: #fff;
}
.canvas-close-btn {
position: fixed;
width: 60rpx;
height: 60rpx;
padding: 20rpx;
top: 30rpx;
right: 0;
z-index: 12;
}
.wisdom-dialog {
.wisdom-dialog-text {
display: flex;
flex-direction: column;
align-items: center;
width: 600rpx;
height: 400rpx;
padding: 10px 15px;
border-radius: 10px;
background-color: #ffffff;
border: 1px solid #f1f1f1;
> text {
text-align: center;
font-size: 14px;
margin-bottom: 30px;
}
.wisdom-text-title {
margin-top: 15px;
font-size: 16px;
color: #000000;
font-weight: 700;
}
> button {
width: 80%;
font-size: 15px;
color: #000000;
font-weight: 600;
border-radius: 30px;
background-color: #ffbe1a;
}
}
}
}
</style>
使用
图片地址记得替换一下
<template>
<view class="mine">
<view @click="isSavePic">显示</view>
<view class="canvas-box" v-if="isCanvas">
<hchPoster ref="hchPoster" @canvasCancel="canvasCancel" :canvasAttr.sync="posterData" />
</view>
</view>
</template>
<script>
import hchPoster from '../hch-poster/hch-poster.vue';
export default {
components: {
hchPoster
},
data() {
return {
isCanvas: false, // 显示canvas
posterData: {
nickName: '兔子',
handUrl: '../../static/scriptures/tim2.jpg',
bgImg: '../../static/scriptures/timg.jpg',
codeImg: '../../static/scriptures/qr_code.png',
},
};
},
onShow(){
this.isSavePic();
this.getShare()
},
methods: {
// 检测是否有授权保存图片功能,以显示不同按钮
isSavePic(){
this.getShare()
this.isCanvas =true;
this.$nextTick(() => {
this.$refs.hchPoster.posterShow();
});
},
// 关闭海报生成弹窗
canvasCancel(val) {
this.isCanvas = val;
},
getShare(){
let par = {
method: 'POST',
url: '接口url',
status: true,
show: false
};
this.$request(par)
.then(res => {
if (res.data.code === '200') {
this.posterData=res.data.content;
}
})
.catch(err => {
console.log(err, '获取失败');
});
},
}
};
</script>
<style lang="scss">
.canvas-box {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
width: 100%;
display: flex;
justify-content: flex-start;
align-items: center;
background-color: rgba(0, 0, 0, 0.7);
z-index: 99999;
}
</style>
使用时以下新建为2个文件,放在同一目录下,运行看看