前端uniapp生成海报绘制canvas画布并且保存到相册【实战/带源码/最新】

目录

    • 插件市场
    • 效果如下图
    • 注意
    • 使用my-share.vue
    • 插件文件如下图片
    • hch-poster
      • utils
        • index.js
      • draw-demo.vue
      • hch-poster.vue
    • 最后

插件市场

插件市场

效果如下图

请添加图片描述

注意

主要:使用my-share.vue和绘制canvas的hch-poster.vue这两个使用

使用my-share.vue

<template><!-- my-share --><view class="container"><!-- 标题 --><!-- <view class="top-stylrify"><view class="title"><view class="title-back" @click="backPrivious">&lt;</view><view>邀请推荐</view><view></view></view></view> --><!-- 轮播图 --><view class="carsoul"><swiper :current="current" @change="swiperChange" :circular="true"><swiper-item v-for="(poster, index) in posters" :key="index"><image class="carsoul_bg" :src="poster.file_path" mode="aspectFill" /><view class="qrcode-container"><view class="qrcode-container-lft"><image src="../../../../static/wx.png" mode="aspectFill"></image></view><view class="qrcode-container-ctr">{{titleText}}</view><view class="qrcode-container-img"><image class="qrcode" :src="qrcodeUrl" mode="aspectFit" /></view></view></swiper-item></swiper><view class="progress-wrapper"><view v-for="(item, index) in posters" :key="index" class="progress-item":class="{ 'active': index === current }"></view></view></view><view class="sharepicturesto"><view class="sharepicturesto-lft"></view><view class="sharepicturesto-ctr">分享图片到 </view><view class="sharepicturesto-rgt"></view></view><!-- 按钮区 --><!-- 只在H5显示 --><!-- #ifdef H5 --><view class="btns-wrap"><view class="wrapBtn" type="default" @click="downloadPoster"><image src="/static/user/share/user-share-down.png" mode="aspectFill"></image><text>保存海报</text></view><view class="wrapBtn" type="default" @click="copyLink"><image src="/static/user/share/user-share-copy.png" mode="aspectFill"></image><text>复制链接</text></view></view><!-- #endif --><!-- 除了H5都显示 --><!-- #ifndef H5 --><view class="btns-wrap"><view class="wrapBtn" type="default" @click="share(0, 'WXSceneSession')"><image src="/static/user/share/user-share-weixin.png" mode="aspectFill"></image><text>微信好友</text></view><view class="wrapBtn" type="default" @click="share(0, 'WXSenceTimeline')"><image src="/static/user/share/user-share-circle.png" mode="aspectFill"></image><text>朋友圈</text></view><view class="wrapBtn" type="default" @click="downloadPoster"><image src="/static/user/share/user-share-down.png" mode="aspectFill"></image><text>保存海报</text></view></view><!-- #endif --><!-- 插件位置 https://ext.dcloud.net.cn/plugin?id=5770 --><!-- <hch-poster ref="hchPoster" @cancel="handleCancel" :posterData.sync="posterData" /> --><hch-poster ref="hchPoster" :posterData.sync="posterData" /></view>
</template><script>import HchPoster from "../../../../components/hch-poster/hch-poster.vue"// import config from '@/config.js'; // export default {components: {HchPoster},data() {return {posters: [],shareLink: '', // 设置分享链接qrcodeUrl: '', // 设置二维码链接headerImgUrl: '',titleText: '分享人昵称',current: 0, // 设置轮播图标识deliveryFlag: false,posterData: {poster: {//根据屏幕大小自动生成海报背景大小url: '', //图片地址r: 10, //圆角半径w: 300, //海报宽度h: 480, //海报高度p: 20 //海报内边距padding},mainImg: {//海报主商品图url: 'https://huangchunhongzz.gitee.io/imgs/poster/product.png', //图片地址r: 10, //圆角半径w: 250, //宽度h: 200, //高度// w: 250, //宽度// h: 100, //高度// mt: 20, //margin-top// r: 50 //圆角半径},// 分享人昵称文字设置title: {//商品标题text: '', //文本fontSize: 16, //字体大小color: '#FFFFFF', //颜色lineHeight: 25, //行高mt: 10, //margin-top},// 二维码图片// 控制二维码图片移动找import HchPoster from "../../../../components/hch-poster/hch-poster.vue"组件里面的await drawSquarePic这个方法里面codeImg: {//小程序码// url: 'https://huangchunhongzz.gitee.io/imgs/poster/code.png', //图片地址url: '', //图片地址w: 90, //宽度h: 90, //高度mt: 20, //margin-topr: 50, //圆角半径},// 头像图片// 控制头像图片移动找import HchPoster from "../../../../components/hch-poster/hch-poster.vue"组件里面的await drawSquarePic这个方法里面headerImg: {// url: 'https://huangchunhongzz.gitee.io/imgs/poster/code.png', //图片地址url: '', //图片地址w: 50, //宽度h: 50, //高度mt: 10, //margin-topr: 50 //圆角半径},tips: [//提示信息{text: '', //文本fontSize: 16, //字体大小color: '#FFFFFF', //字体颜色align: 'center', //对齐方式lineHeight: 10, //行高mt: 0 //margin-top},{text: '', //文本fontSize: 12, //字体大小color: '#2f1709', //字体颜色align: 'center', //对齐方式lineHeight: 25, //行高mt: 20 //margin-top}]},// 微信好友和朋友圈参数新加参考 产品详情里面// http://localhost:8081/h5/#/pages/product/detail/detailappParams: {title: '',summary: '',path: ''},detail: {},/*分享配置*/shareConfig: {},// logologo: ''}},// 获取初始数据mounted() {},onShow() {// 下面全是引入swiper数据 start!!!this.getCavasSwiperImgData();// 上面全是引入swiper数据 end!!!this.getShareData()},methods: {// 返回资产页面backPrivious() {uni.navigateBack({delta: 1});},// 轮播图标识swiperChange(e) {const {current,source} = e.detail;if (source === 'touch' && current === this.posters.length) {// 如果是用户通过滑动触发的,并且当前滑动到最后一张图this.current = 0; // 将当前索引重置为第一张图的索引} else {this.current = current;}},// 获取邀请海报,二维码和邀请链接async getCavasSwiperImgData() {let self = this;let source = self.getPlatform();uni.showLoading({title: '加载中',});self._get('plus.agent.qrcode/poster', {source: source},res => {uni.hideLoading();if (res.data) {// swiper轮播图数据图片self.posters = res.data.poster;// #ifdef H5// 二维码图片路径self.qrcodeUrl = res.data.qrcode;// #endif// 小程序二维码还没有图片!!!!!!,现在用的是二维码的图片// 除了H5都显示小程序图片// #ifndef H5self.qrcodeUrl = res.data.qrcode;// #endif// 复制链接路径self.shareLink = res.data.url;// 假设头像头像路径self.headerImgUrl = res.data.qrcode;// console.log(res.data.poster,'poster');// console.log(res.data.qrcode,'qrcode');let refereeId = res.data.url.split('?')[1].split('&')[0].split('=')[1];uni.setStorageSync('referee_id', refereeId);// http://localhost:8081/h5/#/pages/product/detail/detail// 之前产品详情是通过,点击打开弹窗触发参数// 调完接口触发微信好友和朋友圈参数//#ifndef H5self.appParams.title = self.detail.product_name;self.appParams.summary = self.detail.product_name;// // 构建页面参数// 这个应该是user_id可能或者图片idlet params = self.getShareUrlParams({product_id: self.product_id});self.appParams.path = '/pages/user/newIndex/my-share/my-share?' + params;self.appParams.image = self.detail.image[0].file_path;self.isAppShare = true;//#endif// self.taskFunc();//分享成功接口方法}});},taskFunc() {let self = this;self._post('plus.task.Task/dayTask', {task_type: 'product'},res => {console.log('分享成功');});},// 点击下载保存相册按钮downloadPoster() {this.posterData.poster.url = this.posters[this.current].file_path;this.posterData.codeImg.url = this.qrcodeUrl// 下面新加this.posterData.headerImg.url = this.headerImgUrlthis.posterData.title.text = this.titleTextthis.$refs.hchPoster.posterShow()this.deliveryFlag = false;},// 取消弹出页面handleClose() {this.deliveryFlag = false},// 点击按钮后复制链接copyLink() {let self = this;uni.setClipboardData({data: self.shareLink,success() {uni.showToast({title: '链接已复制',icon: 'success'});},fail() {uni.showToast({title: '复制失败',icon: 'none'});}});},// 获取分享配置应该是getShareData() {let self = this;self._get('settings/appShare', {},function(res) {self.shareConfig = res.data.appshare;self.logo = res.data.logo;});},// 分享方法share: function(shareType, scene) {let shareOPtions = {provider: "weixin",scene: scene, //WXSceneSession”分享到聊天界面,“WXSenceTimeline”分享到朋友圈type: shareType,success: function(res) {console.log("success:" + JSON.stringify(res));},fail: function(err) {console.log("fail:" + JSON.stringify(err));}}if (this.shareConfig.type != 2) {shareOPtions.summary = this.appParams.summary;shareOPtions.imageUrl = this.logo;shareOPtions.title = this.appParams.title;// 公众号/h5if (this.shareConfig.type == 1) {shareOPtions.href = this.shareConfig.open_site + this.appParams.path;} else if (this.shareConfig.type == 3) {//下载页if (this.shareConfig.bind_type == 1) {shareOPtions.href = this.shareConfig.down_url;} else {shareOPtions.href = config.app_url + "/index.php/api/user.useropen/invite?app_id=" + config.app_id + "&referee_id=" + uni.getStorageSync('user_id');}}} else {// 分享到小程序shareOPtions.scene = 'WXSceneSession';shareOPtions.type = 5;shareOPtions.imageUrl = this.appParams.image ? this.appParams.image : this.logo;shareOPtions.title = this.appParams.title;shareOPtions.miniProgram = {id: this.shareConfig.gh_id,path: this.appParams.path,webUrl: this.shareConfig.web_url,type: 0};}uni.share(shareOPtions);},}};
</script><style lang="scss" scoped>.container {padding: 10rpx;}.title {text-align: center;font-size: 36rpx;margin-bottom: 20rpx;display: flex;align-items: center;justify-content: space-between;}/** 轮播图效果开始 **/.carsoul {// margin-top: 100rpx;margin-top: 20rpx;}swiper {// width: 80%;// height: 450px;width: 662rpx;height: 1054rpx;// background: #D8D8D8;border-radius: 24rpx 24rpx 24rpx 24rpx;opacity: 1;/* 根据需求调整高度 */margin: 0 auto;}/deep/ swiper uni-image {border-radius: 24rpx 24rpx 24rpx 24rpx !important;}/deep/ swiper image {border-radius: 24rpx 24rpx 24rpx 24rpx !important;}swiper-item {width: 100%;}.qrcode {width: 200rpx;/* 调整二维码的宽度 */height: 200rpx;/* 调整二维码的高度 */margin-bottom: 60rpx;}.progress-wrapper {position: relative;display: flex;justify-content: center;align-items: center;margin-top: -20px;}.progress-item {width: 20px;height: 5px;margin: 0 5px;border-radius: 2.5px;background-color: #d0d0d0;/* 暗白色 */}.progress-item.active {background-color: #fff;/* 白色 */}/** 轮播图效果结束 **//** 功能按钮区位置开始 **/.btns-wrap {width: 100%;// height: 300rpx;margin-top: 54rpx;margin-bottom: 72rpx;display: flex;justify-content: space-around;align-items: center;}.wrapBtn {width: 100%;text-align: center;}.wrapBtn image {width: 96rpx;height: 96rpx;opacity: 1;text-align: center;margin: 0 auto;margin-bottom: 20rpx;}.wrapBtn text {margin-top: 20rpx;font-size: 36rpx;font-family: Source Han Sans-Regular, Source Han Sans;font-weight: 400;color: #3D3D3D;line-height: 50rpx;text-align: center;}/** 功能按钮区位置结束 **/.sharepicturesto {display: flex;justify-content: center;align-items: center;margin-top: 50rpx;}.sharepicturesto-lft {width: 48rpx;height: 4rpx;background: linear-gradient(270deg, #000000 0%, rgba(216, 216, 216, 0) 100%);border-radius: 0rpx 0rpx 0rpx 0rpx;opacity: 1;}.sharepicturesto-ctr {font-size: 36rpx;font-family: Source Han Sans-Regular, Source Han Sans;font-weight: 400;color: #3D3D3D;line-height: 50rpx;margin-left: 20rpx;margin-right: 20rpx;}.sharepicturesto-rgt {width: 48rpx;height: 4rpx;background: linear-gradient(270deg, #000000 0%, rgba(216, 216, 216, 0) 100%);border-radius: 0rpx 0rpx 0rpx 0rpx;opacity: 1;transform: rotate(180deg);}// 底部整体位置移动.qrcode-container {position: absolute;bottom: 0px;/* 调整二维码距离底部的位置 */left: 0;right: 0;display: flex;justify-content: center;align-items: center;}.qrcode-container-lft {width: 80rpx;height: 80rpx;opacity: 1;position: relative;left: 50rpx;}.qrcode-container-lft image {width: 80rpx;height: 80rpx;opacity: 1;}.qrcode-container-ctr {font-size: 32rpx;font-family: Source Han Sans-Regular, Source Han Sans;font-weight: 400;color: #FFFFFF;line-height: 50rpx;padding-left: 18rpx;padding-right: 24rpx;position: relative;left: 50rpx;}.qrcode-container-img {position: relative;top: 20rpx;left: 50rpx;}.qrcode-container-img image {width: 182rpx;height: 182rpx;border-radius: 0rpx 0rpx 0rpx 0rpx;opacity: 1;}.carsoul .carsoul_bg{width: 100%;height: 100%;}
</style>

插件文件如下图片

components/hch-poster
看好文件之间等级

请添加图片描述

hch-poster

utils

index.js
/** @Description: 公共方法* @Version: 1.0.0* @Autor: hch* @Date: 2021-07-22 00:01:09*/
/*** @description: 绘制正方形(可以定义圆角),并且有图片地址的话填充图片* @param {CanvasContext} ctx canvas上下文* @param {number} x 圆角矩形选区的左上角 x坐标* @param {number} y 圆角矩形选区的左上角 y坐标* @param {number} w 圆角矩形选区的宽度* @param {number} h 圆角矩形选区的高度* @param {number} r 圆角的半径* @param {String} url 图片的url地址*/
export function drawSquarePic(ctx, x, y, w, h, r, url) {ctx.save()ctx.beginPath()// 绘制左上角圆弧ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5)// 绘制border-top// 画一条线 x终点、y终点ctx.lineTo(x + w - r, y)// 绘制右上角圆弧ctx.arc(x + w - r, y + r, r, Math.PI * 1.5, Math.PI * 2)// 绘制border-rightctx.lineTo(x + w, y + h - r)// 绘制右下角圆弧ctx.arc(x + w - r, y + h - r, r, 0, Math.PI * 0.5)// 绘制左下角圆弧ctx.arc(x + r, y + h - r, r, Math.PI * 0.5, Math.PI)// 绘制border-leftctx.lineTo(x, y + r)// 填充颜色(需要可以自行修改)ctx.setFillStyle('#ffffff')ctx.fill()// 剪切,剪切之后的绘画绘制剪切区域内进行,需要save与restore 这个很重要 不然没办法保存ctx.clip()// 绘制图片return new Promise((resolve, reject) => {if (url) {wx.getImageInfo({src: url,success(res) {ctx.drawImage(res.path, x, y, w, h)ctx.restore() //恢复之前被切割的canvas,否则切割之外的就没办法用ctx.draw(true)resolve()},fail(res) {console.log('fail -> res', res)uni.showToast({title: '图片下载异常',duration: 2000,icon: 'none'})}})} else {ctx.draw(true)resolve()}})
}/*** @description: 获取设备信息* @param {type}* @return {type}* @author: hch*/
export function getSystem() {let system = wx.getSystemInfoSync()let scale = system.windowWidth / 375 //按照苹果留 375*667比例 其他型号手机等比例缩放 显示return {w: system.windowWidth,h: system.windowHeight,scale: scale}
}/*** @description: 绘制文本时文本的总体高度* @param {Object} ctx canvas上下文* @param {String} text 需要输入的文本* @param {Number} x X轴起始位置* @param {Number} y Y轴起始位置* @param {Number} maxWidth 单行最大宽度* @param {Number} fontSize 字体大小* @param {String} color 字体颜色* @param {Number} lineHeight 行高* @param {String} textAlign 字体对齐方式*/
export function drawTextReturnH(ctx,text,x,y,maxWidth = 375,fontSize = 14,color = '#000',lineHeight = 30,// textAlign = 'left'textAlign = 'center' //文本中心点位置设置
) {if (textAlign) {ctx.setTextAlign(textAlign) //设置文本的水平对齐方式  ctx.setTextAlign这个可以兼容百度小程序 ,注意:ctx.textAlign百度小程序有问题switch (textAlign) {case 'center':x = getSystem().w / 2breakcase 'right':x = (getSystem().w - maxWidth) / 2 + maxWidthbreakdefault:// 左对齐x = (getSystem().w - maxWidth) / 2break}}let arrText = text.split('')let line = ''for (let n = 0; n < arrText.length; n++) {let testLine = line + arrText[n]ctx.font = fontSize + 'px sans-serif' //设置字体大小,注意:百度小程序 用ctx.setFontSize设置字体大小后,计算字体宽度会无效ctx.setFillStyle(color) //设置字体颜色let metrics = ctx.measureText(testLine) //measureText() 方法返回包含一个对象,该对象包含以像素计的指定字体宽度。let testWidth = metrics.widthif (testWidth > maxWidth && n > 0) {ctx.fillText(line, x, y)line = arrText[n]y += lineHeight} else {line = testLine}}ctx.fillText(line, x, y)ctx.draw(true) //本次绘制是否接着上一次绘制。即 reserve 参数为 false,则在本次调用绘制之前 native 层会先清空画布再继续绘制;若 reserve 参数为 true,则保留当前画布上的内容,本次调用 drawCanvas 绘制的内容覆盖在上面,默认 false。return y
}

draw-demo.vue

<!--* @Description: 生成海报组件* @Version: 1.0.0* @Autor: hch* @Date: 2020-08-07 14:48:41* @LastEditors: Please set LastEditors* @LastEditTime: 2021-07-30 09:25:07* 保存海报按钮和关闭按钮 在html代码中写出来 绑定点击方法然后透明 再用canvas 覆盖
--><template><view class="content"><view class="btn" @tap="handleDraw('square1')">正方形</view><view class="btn" @tap="handleDraw('square2')">圆角方形</view><view class="btn" @tap="handleDraw('square3')">圆形</view><view class="btn" @tap="handleDraw('pic1')">图片</view><view class="btn" @tap="handleDraw('text1')">左对齐文本</view><view class="btn" @tap="handleDraw('text2')">居中对齐文本</view><view class="btn" @tap="handleDraw('text3')">右对齐文本</view><viewclass="canvas-content"v-show="canvasShow":style="'width:' + system.w + 'px; height:' + system.h + 'px;'"><!-- 遮罩层 --><view class="canvas-mask"></view><!-- :width="system.w" :height="system.h" 支付宝必须要这样设置宽高才有效果 --><canvasclass="canvas":canvas-id="canvasId":id="canvasId":style="'width:' + system.w + 'px; height:' + system.h + 'px;'":width="system.w":height="system.h"></canvas><view class="button-wrapper"><!-- 保存海报按钮 --><!-- #ifndef MP-QQ --><!-- cover-view 标签qq小程序有问题 --><cover-view class="save-btn cancel-btn" @tap="handleCancel">取消</cover-view><!-- #endif --><!-- #ifdef MP-QQ --><view class="save-btn cancel-btn" @tap="handleCancel">取消</view><!-- #endif --></view></view></view>
</template><script>import { drawSquarePic, drawTextReturnH, getSystem } from './utils'export default {data() {return {canvasId: 'canvas',system: {},canvasShow: false,square1: {//正方形x: 40,y: 40,r: 0, //圆角半径w: 80, //宽度h: 80 //高度},square2: {//圆角方形x: 40,y: 40,r: 10, //圆角半径w: 80, //宽度h: 80 //高度},square3: {//圆形x: 40,y: 40,r: 40, //圆角半径w: 80, //宽度h: 80 //高度},pic1: {x: 40,y: 40,url: 'https://huangchunhongzz.gitee.io/imgs/poster/product.png',r: 0, //圆角半径w: 250, //宽度h: 200 //高度},text1: {x: 0,y: 40,text: '今日上新水果,牛奶草莓',fontSize: 16, //字体大小color: '#000', //颜色lineHeight: 25, //行高mt: 0 //margin-top},text2: {x: 0,y: 40,text: '今日上新水果,牛奶草莓',fontSize: 16, //字体大小color: 'blue', //颜色lineHeight: 25, //行高mt: 0, //margin-topalign: 'center' //对齐方式},text3: {x: 0,y: 40,text: '今日上新水果,牛奶草莓',fontSize: 16, //字体大小color: 'red', //颜色lineHeight: 25, //行高mt: 0, //margin-topalign: 'right' //对齐方式}}},created() {// 获取设备信息this.system = getSystem()},methods: {/*** @description: 展示海报* @param {type}* @return {type}* @author: hch*/handleDraw(type) {console.log('handleDraw -> type', type)this.canvasShow = truethis.draw(type)},/*** @description: 绘制* @author: hch*/draw(type) {uni.showLoading({title: '绘制中...'})if (this.ctx) {this.ctx.clearRect(0, 0, this.system.w, this.system.h) //清空之前的海报this.ctx.restore() //恢复之前被切割的canvas,否则切割之外的就没办法用} else {this.ctx = uni.createCanvasContext(this.canvasId, this)}let drawData = this[type]if (type === 'square1' || type === 'square2' || type === 'square3' || type === 'pic1') {// 绘制图像/图片drawSquarePic(this.ctx,drawData.x,drawData.y,drawData.w,drawData.h,drawData.r,drawData.url)} else {// 绘制文本let textY = drawTextReturnH(this.ctx,drawData.text,drawData.x,drawData.y,this.system.w,drawData.fontSize,drawData.color,drawData.lineHeight,drawData.align)}uni.hideLoading()},/*** @description: 取消海报* @param {type}* @return {type}* @author: hch*/handleCancel() {this.canvasShow = false}}}
</script><style lang="scss">.content {margin-bottom: 80rpx;overflow: hidden;border-bottom: 1rpx solid $uni-border-color;.btn {float: left;width: 30%;margin: 10rpx;font-size: 30rpx;line-height: 72rpx;color: #fff;text-align: center;background: $uni-btn-color;border-radius: 45rpx;border-radius: 36rpx;}}.canvas-content {position: absolute;top: 0;z-index: 9;.canvas-mask {position: fixed;top: 0;right: 0;bottom: 0;left: 0;z-index: 9;width: 100%;height: 100%;background: $uni-btn-color;}.canvas {z-index: 10;}.button-wrapper {position: fixed;bottom: 20rpx;z-index: 16;display: flex;width: 100%;height: 72rpx;justify-content: space-around;}.save-btn {z-index: 16;width: 40%;height: 100%;font-size: 30rpx;line-height: 72rpx;color: #fff;text-align: center;background: $uni-btn-color;border-radius: 45rpx;border-radius: 36rpx;}.cancel-btn {color: $uni-btn-color;background: #fff;}.canvas-close-btn {position: fixed;top: 30rpx;right: 0;z-index: 12;width: 60rpx;height: 60rpx;padding: 20rpx;}}
</style>

hch-poster.vue

<!--* @Description: 生成海报组件* @Version: 1.0.0* @Autor: hch* @Date: 2020-08-07 14:48:41* @LastEditors: Please set LastEditors* @LastEditTime: 2021-07-31 18:11:35* 保存海报按钮和关闭按钮 在html代码中写出来 绑定点击方法然后透明 再用canvas 覆盖* https://ext.dcloud.net.cn/plugin?id=5770
--><template><view class="canvas-content" v-show="canvasShow" :style="'width:' + system.w + 'px; height:' + system.h + 'px;'"><!-- 遮罩层 --><view class="canvas-mask"></view><!-- 海报 --><!-- :width="system.w" :height="system.h" 支付宝必须要这样设置宽高才有效果 --><canvas class="canvas" canvas-id="myCanvas" id="myCanvas":style="'width:' + system.w + 'px; height:' + system.h + 'px;'" :width="system.w":height="system.h"></canvas><view class="button-wrapper"><!-- 保存海报按钮 --><!-- #ifndef MP-QQ --><!-- cover-view 标签qq小程序有问题 --><!-- 之前用的这个在手机不显示文字 --><!--  <cover-view class="save-btn" @tap="handleSaveCanvasImage">保存海报</cover-view><cover-view class="save-btn cancel-btn" @tap="handleCanvasCancel">取消</cover-view> --><view class="save-btn" @tap="handleSaveCanvasImage">保存海报</view><view class="save-btn cancel-btn" @tap="handleCanvasCancel">取消</view><!-- #endif --><!-- #ifdef MP-QQ --><view class="save-btn" @tap="handleSaveCanvasImage">保存海报</view><view class="save-btn cancel-btn" @tap="handleCanvasCancel">取消</view><!-- #endif --></view></view>
</template><script>import {drawSquarePic,drawTextReturnH,getSystem} from './utils'export default {data() {return {system: {},canvasShow: false}},props: {posterData: {type: Object,default: () => {return {}}}},computed: {/*** @description: 计算海报背景数据* @param {*}* @return {*}* @author: hch*/poster() {let data = this.posterDatalet system = this.systemlet posterBg = {url: data.poster.url,r: data.poster.r * system.scale,w: data.poster.w * system.scale,h: data.poster.h * system.scale,x: (system.w - data.poster.w * system.scale) / 2,y: (system.h - data.poster.h * system.scale) / 2,p: data.poster.p * system.scale}return posterBg},/*** @description: 计算海报头部主图* @param {*}* @return {*}* @author: hch*/mainImg() {let data = this.posterDatalet system = this.systemlet posterMain = {url: data.mainImg.url,r: data.mainImg.r * system.scale,w: data.mainImg.w * system.scale,h: data.mainImg.h * system.scale,x: (system.w - data.mainImg.w * system.scale) / 2,y: this.poster.y + data.poster.p * system.scale}return posterMain},/*** @description: 计算海报标题* @param {*}* @return {*}* @author: hch*/title() {let data = this.posterDatalet system = this.systemlet posterTitle = data.titleposterTitle.x = this.mainImg.xposterTitle.y = this.mainImg.y + this.mainImg.h + data.title.mt * system.scalereturn posterTitle},/*** @description: 计算小程序码* @param {*}* @return {*}* @author: hch*/codeImg() {let data = this.posterDatalet system = this.systemlet posterCode = {url: data.codeImg.url,r: data.codeImg.r * system.scale,w: data.codeImg.w * system.scale,h: data.codeImg.h * system.scale,x: (system.w - data.codeImg.w * system.scale) / 2,y: data.codeImg.mt * system.scale //y需要加上绘图后文本的y}return posterCode},/*** @description: 计算小程序码* @param {*}* @return {*}* @author: hch*/headerImg() {let data = this.posterDatalet system = this.systemlet posterCode = {url: data.headerImg.url,r: data.headerImg.r * system.scale,w: data.headerImg.w * system.scale,h: data.headerImg.h * system.scale,x: (system.w - data.headerImg.w * system.scale) / 2,y: data.headerImg.mt * system.scale //y需要加上绘图后文本的y}return posterCode}},created() {// 获取设备信息this.system = getSystem()},methods: {/*** @description: 展示海报* @param {type}* @return {type}* @author: hch*/posterShow() {this.canvasShow = truethis.creatPoster()},/*** @description: 生成海报* @author: hch*/async creatPoster() {uni.showLoading({title: '生成海报中...'})const ctx = uni.createCanvasContext('myCanvas', this)this.ctx = ctxctx.clearRect(0, 0, this.system.w, this.system.h) //清空之前的海报ctx.draw() //清空之前的海报// 根据设备屏幕大小和距离屏幕上下左右距离,及圆角绘制背景let poster = this.posterlet mainImg = this.mainImglet codeImg = this.codeImglet headerImg = this.headerImglet title = this.titleawait drawSquarePic(ctx, poster.x, poster.y, poster.w, poster.h, poster.r, poster.url)// 位置移动方法// 先看有没有文字,有文字,按照如下步骤,// 步骤一:先看文本位置,靠左,中,右,然后找到下面方法设置// import {drawTextReturnH} from './utils' 找到drawTextReturnH方法 textAlign = 'center'// 步骤二:根据文本位置设置定位图片// 绘制标题 textY 绘制文本的y位置// 我感觉应该是以这个文本textY位中心点移动,现在文本是居中状态,之前靠左边,文本定位设置在// 这个方法里面drawTextReturnH引入的js方法里面// import {drawTextReturnH} from './utils' 找到drawTextReturnH方法 textAlign = 'center'console.log('creatPoster -> title.x', title.x)// 整体移动 以文本为中心点Y轴上下整体移动,X轴是左,中,右,设置在// import {drawTextReturnH} from './utils' 找到drawTextReturnH方法 textAlign = 'center' right,left里面控制位置let textY = drawTextReturnH(ctx,title.text,title.x,title.y + 180,mainImg.w,title.fontSize,title.color,title.lineHeight)// 步骤二// 头像顶部// 这里控制图片移动位置,X轴,Y轴移动await drawSquarePic(ctx,headerImg.x - 70, //控制X轴移动headerImg.y + textY - 40,//步骤二 Y轴必须加textY这个,应为要以文本为中心点		 //控制Y轴移动	根据文本textY这个为中心点移动headerImg.w,headerImg.h,0,headerImg.url)// 头像底部// 步骤二// 绘制小程序码// 现在更换接口是二维码图片// 这里控制图片移动位置,X轴,Y轴移动await drawSquarePic(ctx,codeImg.x + 90, //控制X轴移动codeImg.y + textY - 70, //步骤二 Y轴必须加textY这个,应为要以文本为中心点		 //控制Y轴移动		根据文本textY这个为中心点移动// codeImg.x, //控制X轴移动// codeImg.y, //控制Y轴移动codeImg.w,codeImg.h,0,codeImg.url)// 小程序的名称// 长按/扫描识别查看商品let y = 0this.posterData.tips.forEach((element, i) => {if (i == 0) {y = codeImg.y + textY + element.mt + codeImg.h} else {y += element.mt}y = drawTextReturnH(ctx,element.text,title.x,y,mainImg.w,element.fontSize,element.color,element.lineHeight,element.align)})uni.hideLoading()},/*** @description: 保存到系统相册* @param {type}* @return {type}* @author: hch*/handleSaveCanvasImage() {uni.showLoading({title: '保存中...'})let _this = this// 把画布转化成临时文件// #ifndef MP-ALIPAY// 支付宝小程序外,其他都是用这个方法 canvasToTempFilePathuni.canvasToTempFilePath({x: this.poster.x,y: this.poster.y,width: this.poster.w, // 画布的宽height: this.poster.h, // 画布的高destWidth: this.poster.w * 5,destHeight: this.poster.h * 5,canvasId: 'myCanvas',success(res) {//保存图片至相册// #ifndef H5// 除了h5以外的其他端uni.saveImageToPhotosAlbum({filePath: res.tempFilePath,success(res) {uni.hideLoading()uni.showToast({title: '图片保存成功,可以去分享啦~',duration: 2000,icon: 'none'})_this.handleCanvasCancel()},fail() {uni.showToast({title: '保存失败,稍后再试',duration: 2000,icon: 'none'})uni.hideLoading()}})// #endif// #ifdef H5// h5的时候uni.showToast({title: '请长按保存',duration: 3000,icon: 'none'})_this.handleCanvasCancel()_this.$emit('previewImage', res.tempFilePath)// #endif},fail(res) {console.log('fail -> res', res)uni.showToast({title: '保存失败,稍后再试',duration: 2000,icon: 'none'})uni.hideLoading()}},this)// #endif// #ifdef MP-ALIPAY// 支付宝小程序条件下 toTempFilePaththis.ctx.toTempFilePath({x: this.poster.x,y: this.poster.y,width: this.poster.w, // 画布的宽height: this.poster.h, // 画布的高destWidth: this.poster.w * 5,destHeight: this.poster.h * 5,success(res) {//保存图片至相册my.saveImage({url: res.apFilePath,showActionSheet: true,success(res) {uni.hideLoading()uni.showToast({title: '图片保存成功,可以去分享啦~',duration: 2000,icon: 'none'})_this.handleCanvasCancel()},fail() {uni.showToast({title: '保存失败,稍后再试',duration: 2000,icon: 'none'})uni.hideLoading()}})},fail(res) {console.log('fail -> res', res)uni.showToast({title: '保存失败,稍后再试',duration: 2000,icon: 'none'})uni.hideLoading()}},this)// #endif},/*** @description: 取消海报* @param {type}* @return {type}* @author: hch*/handleCanvasCancel() {this.canvasShow = falsethis.$emit('cancel', true)}}}
</script><style lang="scss">$uni-btn-color: #007aff;.content {height: 100%;text-align: center;}.canvas-content {position: absolute;top: 0;.canvas-mask {position: fixed;top: 0;right: 0;bottom: 0;left: 0;z-index: 9;width: 100%;height: 100%;background: rgba(0, 0, 0, 0.5);}.canvas {z-index: 10;}.button-wrapper {position: fixed;bottom: 20rpx;z-index: 16;display: flex;width: 100%;height: 72rpx;justify-content: space-around;}.save-btn {z-index: 16;width: 40%;height: 100%;font-size: 30rpx;line-height: 72rpx;color: #fff;text-align: center;background: $uni-btn-color;border-radius: 45rpx;border-radius: 36rpx;}.cancel-btn {color: $uni-btn-color;background: #fff;}.canvas-close-btn {position: fixed;top: 30rpx;right: 0;z-index: 12;width: 60rpx;height: 60rpx;padding: 20rpx;}}
</style>

最后

感觉文章好的话记得点个心心和关注和收藏,有错的地方麻烦指正一下,如果需要转载,请标明出处,多谢!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/155528.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

时序预测 | MATLAB实现基于LSTM-AdaBoost长短期记忆网络结合AdaBoost时间序列预测

时序预测 | MATLAB实现基于LSTM-AdaBoost长短期记忆网络结合AdaBoost时间序列预测 目录 时序预测 | MATLAB实现基于LSTM-AdaBoost长短期记忆网络结合AdaBoost时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 x 基本介绍 1.Matlab实现LSTM-Adaboost时间序列预测…

SQL基础理论篇(八):视图

文章目录 简介创建视图修改视图删除视图总结参考文献 简介 视图&#xff0c;即VIEW&#xff0c;是SQL中的一个重要概念&#xff0c;它其实是一种虚拟表(非实体数据表&#xff0c;本身不存储数据)。 视图类似于编程中的函数&#xff0c;也可以理解成是一个访问数据的接口。 从…

数据分析思维与模型:群组分析法

群组分析法&#xff0c;也称为群体分析法或集群分析法&#xff0c;是一种研究方法&#xff0c;用于分析和理解群体内的动态、行为模式、意见、决策过程等。这种方法在社会科学、心理学、市场研究、组织行为学等领域有广泛应用。它可以帮助研究人员或组织更好地理解特定群体的特…

C# Onnx DIS高精度图像二类分割

目录 介绍 效果 模型信息 项目 代码 下载 介绍 github地址&#xff1a;https://github.com/xuebinqin/DIS This is the repo for our new project Highly Accurate Dichotomous Image Segmentation 对应的paper是ECCV2022的一篇文章《Highly Accurate Dichotomous Imag…

Windows + Syslog-ng 发送eventlog 到Splunk indexer

1: 背景: 装了window Splunk universal forwarder 的 window server 要把event log 送到linux 的splunk indexer 上,由于网络的原因,不能直接发送数据到splunk indexer的话,要利用跳板机来实现: 2:架构: 3: 先说明每个类型server 上的安装情况: Window server: 安装S…

Tomcat 9.0.54源码环境搭建

一. 问什么要学习tomcat tomcat是目前非常流行的web容器&#xff0c;其性能和稳定性也是非常出色的&#xff0c;学习其框架设计和底层的实现&#xff0c;不管是使用、性能调优&#xff0c;还是应用框架设计方面&#xff0c;肯定会有很大的帮助 二. 运行源码 1.下载源…

DeepMind 推出 OPRO 技术,可用于优化 ChatGPT 提示

本心、输入输出、结果 文章目录 DeepMind 推出 OPRO 技术&#xff0c;可用于优化 ChatGPT 提示前言消息摘要OPRO的工作原理DeepMind的研究相关链接花有重开日&#xff0c;人无再少年实践是检验真理的唯一标准 DeepMind 推出 OPRO 技术&#xff0c;可用于优化 ChatGPT 提示 编辑…

股票池(三)

3-股票池 文章目录 3-股票池一. 查询股票池支持的类型二. 查询目前股票池对应的股票信息三 查询股票池内距离今天类型最少/最多的股票数据四. 查询股票的池统计信息 一. 查询股票池支持的类型 接口描述: 接口地址:/StockApi/stockPool/listPoolType 请求方式&#xff1a;GET…

Figma 是什么软件?为什么能被Adobe收购

很多人一定早就听说过Figma的名字了。看到很多设计同行推荐&#xff0c;用了很久&#xff0c;疯狂的安利朋友用。是什么让这么多设计师放弃了FigmaSketch的魅力&#xff1f;下面的内容将详细分享一些与Figma相关的知识点&#xff0c;并介绍这个经常听到但不熟悉的工具。 Figma…

MindSpore基础教程:使用 MindCV和 Gradio 创建一个图像分类应用

MindSpore基础教程&#xff1a;使用 MindCV和 Gradio 创建一个图像分类应用 官方文档教程使用已经弃用的MindVision模块&#xff0c;本文是对官方文档的更新 在这篇博客中&#xff0c;我们将探索如何使用 MindSpore 框架和 Gradio 库来创建一个基于深度学习的图像分类应用。我…

股票基础数据(二)

二. 股票基础数据 文章目录 二. 股票基础数据一. 查询股票融资信息数据二. 查询所有的股票信息三. 查询所有的股票类型信息四. 根据类型查询所有的股票数据信息五. 查询股票当前的基本信息六. 查询股票的K线图, 返回对应的 base64 信息七. 展示股票的K线图数据, 对应的是数据信…

大模型的实践应用7-阿里的多版本通义千问Qwen大模型的快速应用与部署

大家好,我是微学AI,今天给大家介绍一下大模型的实践应用7-阿里的多版本通义千问Qwen大模型的快速应用与部署。阿里云开源了Qwen系列模型,即Qwen-7B和Qwen-14B,以及Qwen的聊天模型,即Qwen-7B-Chat和Qwen-14B-Chat。通义千问模型针对多达 3 万亿个 token 的多语言数据进行了…

LLM之Prompt(二):清华提出Prompt 对齐优化技术BPO

论文题目&#xff1a;《Black-Box Prompt Optimization: Aligning Large Language Models without Model Training》 论文链接&#xff1a;https://arxiv.org/abs/2311.04155 github地址&#xff1a;https://github.com/thu-coai/BPO BPO背景介绍 最近&#xff0c;大型语言模…

米哈游大数据云原生实践

云布道师 近年来&#xff0c;容器、微服务、Kubernetes 等各项云原生技术的日渐成熟&#xff0c;越来越多的公司开始选择拥抱云原生&#xff0c;并将企业应用部署运行在云原生之上。随着米哈游业务的高速发展&#xff0c;大数据离线数据存储量和计算任务量增长迅速&#xff0c…

中大型企业网搭建(毕设类型)

毕业设计类别 某大学网络规划与部署 目录 某大学网络规划与部署 第一章项目概述 1.1 项目背景 1.2 网络需求分析 第二章网络总体设计方案 2.1 网络整体架构 2.2 网络设计思路 第三章 网络技术应用 3.1 DHCP 3.2 MSTP 3.3 VRRP 3.4 OSPF 3.5 VLAN 3.6 NAT 3.7 WLAN 3…

78基于matlab的BiLSTM分类算法,输出迭代曲线,测试集和训练集分类结果和混淆矩阵

基于matlab的BiLSTM分类算法&#xff0c;输出迭代曲线&#xff0c;测试集和训练集分类结果和混淆矩阵&#xff0c;程序有详细注释&#xff0c;数据可更换自己的&#xff0c;程序已调通&#xff0c;可直接运行。

全面解析IEC 60364三种接地系统的概念、特点及应用

根据IEC 60364规定的各种保护方式、术语概念&#xff0c;低压配电系统按接地方式的不同分为三类&#xff0c;即 TT 、 TN 和 IT 系统。 1.TT系统TT grounding system TT供电系统&#xff1a;是指将电气设备的金属外壳直接接地的保护系统&#xff0c;称为保护接地系统&#xff…

C#WPF用户控件及自定义控件实例

本文演示C#WPF自定义控件实例 用户控件(UserControl)和自定义控件(CustomControl)都是对UI控件的一种封装方式,目的都是实现封装后控件的重用。 只不过各自封装的实现方式和使用的场景上存在差异。 1 基于UserControl 创建 创建控件最简单一个方法就是基于UserControl …

如何使用API接口对接淘宝获取店铺销量排序,店铺名称等参数

要接入淘宝官方开放平台API接口获取店铺销量排序&#xff0c;店铺名称等参数&#xff0c;需要按照以下步骤进行操作&#xff1a; 找到可用的API接口&#xff1a;首先&#xff0c;需要找到支持查询店铺信息的API接口。可以在电商数据平台的开放平台上查找相应的API接口。注册并…

YOLOv8更换骨干网络HorNet:递归门控卷积的高效高阶空间交互——涨点神器!

🗝️YOLOv8实战宝典--星级指南:从入门到精通,您不可错过的技巧   -- 聚焦于YOLO的 最新版本, 对颈部网络改进、添加局部注意力、增加检测头部,实测涨点 💡 深入浅出YOLOv8:我的专业笔记与技术总结   -- YOLOv8轻松上手, 适用技术小白,文章代码齐全,仅需 …