| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403 |
- <template>
- <view class="content">
- <u-mask :show="share_qrcode_flag" :zoom="false" :custom-style="{ background: 'rgba(0,0,0,.8)' }" :duration="0">
- <view class="sq_box">
- <view class="tz_box qrcode_box">
- <view class="close_box" @click="share_qrcode_flag = false"><u-icon name="close" color="#ffffff"></u-icon></view>
- <view class="share_qrcode">
- <canvas canvas-id="myCanvas" style="width: 690px;height:1040px; position: fixed;top: -10000px;"></canvas>
- <image @longpress="showSaveImgWin = true" style="width: 100%; height: 100%;" :src="canvasToTempFilePath"></image>
- </view>
- </view>
- </view>
- </u-mask>
- <u-modal
- v-model="showSaveImgWin"
- content="确定要保存图片吗"
- @confirm="saveShareImg(canvasToTempFilePath)"
- @cancel="showSaveImgWin = false"
- :show-cancel-button="true"
- ></u-modal>
- </view>
- </template>
- <script>
- export default {
- name: 'XQGeneratePoster',
- data() {
- return {
- ratio: 1,
- ctx: null, // 创建canvas对象
- canvasToTempFilePath: null, // 保存最终生成的导出的图片地址
- openStatus: true, // 声明一个全局变量判断是否授权保存到相册
- share_qrcode_flag: false,
- showSaveImgWin: false //保存图片到相册
- };
- },
- methods: {
- share_qrcode(option) {
- if (option) {
- if (!this.canvasToTempFilePath) {
- this.createCanvasImage(option);
- }
- this.share_qrcode_flag = true;
- }
- },
- //获取图片信息
- downloadFileImg(url) {
- return new Promise(resolve => {
- uni.getImageInfo({
- src: url,
- success: res => {
- resolve(res.path);
- },
- fail: err => {
- console.log(err);
- uni.showToast({
- title: 'Lỗi mạng Vui lòng thử lại',
- icon: 'loading'
- });
- }
- });
- });
- },
- // 生成海报
- async createCanvasImage(option) {
- // 点击生成海报数据埋点
- if (!this.ctx) {
- uni.showLoading({
- title: '生成中...'
- });
- let code = this.downloadFileImg(option.codeUrl);
- let cover = this.downloadFileImg(option.coverUrl);
- let headImg = this.downloadFileImg(option.headUrl);
- let bgUrl = '';
- if (option.bgUrl) {
- bgUrl = new Promise(resolve => {
- uni.downloadFile({
- //生成临时地址
- url: option.bgUrl,
- success: res => {
- resolve(res.tempFilePath);
- },
- fail: erros => {
- uni.showToast({
- title: 'Lỗi mạng Vui lòng thử lại',
- icon: 'loading'
- });
- }
- });
- });
- }
- Promise.all([headImg, code, cover, bgUrl]).then(result => {
- const ctx = uni.createCanvasContext('myCanvas', this);
- let canvasWidthPx = 640 * this.ratio,
- canvasHeightPx = 1040 * this.ratio,
- avatarurl_width = 120, //绘制的头像宽度
- avatarurl_heigth = 120, //绘制的头像高度
- avatarurl_x = 40, //绘制的头像在画布上的位置
- avatarurl_y = 28, //绘制的头像在画布上的位置
- codeurl_width = 180, //绘制的二维码宽度
- codeurl_heigth = 180, //绘制的二维码高度
- codeurl_x = 70, //绘制的二维码在画布上的位置
- codeurl_y = 800, //绘制的二维码在画布上的位置
- coverurl_width = 610, //绘制的封面宽度
- coverurl_heigth = 350, //绘制的封面高度
- coverurl_x = 40, //绘制的封面在画布上的位置
- coverurl_y = 190; //绘制的封面在画布上的位置
- if (option.bgUrl) {
- ctx.drawImage(result[3], 0, 0, 690, 1040); // 背景图片需要本地
- } else {
- //绘制圆角矩形
- ctx.save();
- ctx.translate(0, 0);
- //绘制圆角矩形的各个边
- this.drawRoundRectPath(ctx, 690, 1040, 14);
- ctx.fillStyle = option.fillStyle || '#0688ff'; //若是给定了值就用给定的值否则给予默认值
- ctx.fill();
- ctx.restore();
- }
- // 白底
- ctx.setFillStyle('#ffffff');
- ctx.fillRect(25, 175, 640, 840);
- ctx.save(); // 先保存状态 已便于画完圆再用
- ctx.beginPath(); //开始绘制
- //先画个圆 前两个参数确定了圆心 (x,y) 坐标 第三个参数是圆的半径 四参数是绘图方向 默认是false,即顺时针
- ctx.arc(avatarurl_width / 2 + avatarurl_x, avatarurl_heigth / 2 + avatarurl_y, avatarurl_width / 2, 0, Math.PI * 2, false);
- ctx.clip(); //画了圆 再剪切 原始画布中剪切任意形状和尺寸。一旦剪切了某个区域,则所有之后的绘图都会被限制在被剪切的区域内
- ctx.drawImage(result[0], avatarurl_x, avatarurl_y, avatarurl_width, avatarurl_heigth); // 推进去图片
- ctx.restore(); //恢复之前保存的绘图上下文状态 可以继续绘制
- ctx.font = 'normal bold 45px sans-serif';
- ctx.setFillStyle('#ffffff'); // 文字颜色
- if (option.nickName) {
- this.dealWords({
- ctx: ctx, //画布上下文
- fontSize: 45, //字体大小
- word: option.nickName, //需要处理的文字
- maxWidth: 480, //一行文字最大宽度
- x: 190, //文字在x轴要显示的位置
- y: 40, //文字在y轴要显示的位置
- maxLine: 1 //文字最多显示的行数
- });
- }
- ctx.setFillStyle('#ffffff'); // 文字颜色
- ctx.setFontSize(30); // 文字字号
- ctx.fillText(option.miniName, 190, 130); // 绘制文字
- ctx.setFillStyle('#222222');
- if (option.tkName) {
- this.dealWords({
- ctx: ctx, //画布上下文
- fontSize: 32, //字体大小
- word: option.tkName, //需要处理的文字
- maxWidth: 610, //一行文字最大宽度
- x: 40, //文字在x轴要显示的位置
- y: 550, //文字在y轴要显示的位置
- maxLine: 2 //文字最多显示的行数
- });
- }
- ctx.font = 'normal normal 26px sans-serif';
- ctx.setFillStyle('#555555'); // 文字颜色
- ctx.fillText('题库作者:', 40, 670); // 绘制文字
- ctx.font = 'normal normal 26px sans-serif';
- ctx.setFillStyle('#555555'); // 文字颜色
- if (option.tkAuthor) {
- this.dealWords({
- ctx: ctx, //画布上下文
- fontSize: 26, //字体大小
- word: option.tkAuthor, //需要处理的文字
- maxWidth: 490, //一行文字最大宽度
- x: 170, //文字在x轴要显示的位置
- y: 630, //文字在y轴要显示的位置
- maxLine: 1 //文字最多显示的行数
- });
- }
- // 白底
- ctx.setFillStyle('#CBE6FE');
- ctx.fillRect(40, 690, 125, 40);
- ctx.font = 'normal normal 24px sans-serif';
- ctx.setFillStyle('#1F8DFE'); // 文字颜色
- ctx.fillText(option.tkType, 78, 720); // 绘制文字
- // 白底
- ctx.setFillStyle('#FDE5D2');
- ctx.fillRect(180, 690, 125, 40);
- ctx.setFillStyle('#F37F26'); // 文字颜色
- ctx.fillText(option.cost, 218, 720); // 绘制文字
- // 白底
- ctx.setFillStyle('#D2F1EF');
- ctx.fillRect(320, 690, 125, 40);
- ctx.setFillStyle('#2EBBB4'); // 文字颜色
- ctx.fillText(option.isPub, 360, 720); // 绘制文字
- ctx.beginPath();
- // 设置线宽
- ctx.lineWidth = 1;
- // 设置间距(参数为无限数组,虚线的样式会随数组循环)
- ctx.setLineDash([10, 10]);
- // 移动画笔至坐标 x20 y20 的位置
- ctx.moveTo(30, 760);
- // 绘制到坐标 x20 y100 的位置
- ctx.lineTo(660, 760);
- // 填充颜色
- ctx.strokeStyle = '#aaaaaa';
- // 开始填充
- ctx.stroke();
- ctx.closePath();
- ctx.font = 'normal normal 36px sans-serif';
- ctx.setFillStyle('#E65449'); // 文字颜色
- ctx.fillText('长按识别', 300, 870); // 绘制孩子百分比
- ctx.font = 'normal normal 36px sans-serif';
- ctx.setFillStyle('#222222'); // 文字颜色
- ctx.fillText('小程序码', 444, 870); // 绘制孩子百分比
- ctx.font = 'normal normal 36px sans-serif';
- ctx.setFillStyle('#222222'); // 文字颜色
- ctx.fillText('查看题库详细信息', 300, 920); // 绘制孩子百分比
- 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.draw(false, () => {
- // canvas画布转成图片并返回图片地址
- uni.canvasToTempFilePath(
- {
- canvasId: 'myCanvas',
- width: 690,
- height: 1040,
- destWidth: 690,
- destHeight: 1040,
- success: res => {
- this.canvasToTempFilePath = res.tempFilePath;
- this.showShareImg = true;
- uni.showToast({
- title: 'Vẽ thành công'
- });
- },
- fail: err => {
- uni.showToast({
- title: 'Vẽ thất bại'
- });
- },
- complete: () => {
- uni.hideLoading();
- uni.hideToast();
- }
- },
- this
- );
- });
- });
- }
- },
- 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); //设置字体大小
- 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>
- <style lang="scss">
- .sq_box {
- display: flex;
- height: 100%;
- justify-content: center;
- color: #333333;
- padding: 60rpx;
- .tz_box {
- background-color: #ffffff;
- border-radius: 14rpx;
- padding: 40rpx;
- display: flex;
- flex-direction: column;
- align-items: center;
- position: relative;
- width: 100%;
- text-align: center;
- line-height: 50rpx;
- height: 80vh;
- .close_box {
- position: absolute;
- left: 50%;
- bottom: -100rpx;
- border: 4rpx solid #ffffff;
- width: 60rpx;
- height: 60rpx;
- margin-left: -30rpx;
- border-radius: 50%;
- display: flex;
- justify-content: center;
- align-items: center;
- }
- .share_qrcode {
- width: 100%;
- height: 100%;
- }
- .tz_title {
- font-size: 34rpx;
- font-weight: 600;
- }
- .content_box {
- margin: 50rpx 0;
- text-align: left;
- .inf {
- font-weight: bold;
- font-size: 36rpx;
- .copy {
- font-weight: 0;
- color: rgb(85, 104, 147);
- font-size: 30rpx;
- }
- }
- }
- }
- .qrcode_box {
- padding: 0;
- }
- }
- </style>
|