uniapp 微信小程序 分享海报的实现

在这里插入图片描述
在这里插入图片描述
主页面

<template><view class="page"><!-- 自定义导航栏--><Navbar title="我的海报"></Navbar><view class="container"><poster ref="poster" :imageUrl="image" :imageWidth="750" :imageHeight="1000" :drawData="drawData":config="config" :wechatCode="false" @wechatCodeConfig="wechatCodeConfig" @click="createdPoster"@loading="onLoading" @success="onSuccess" @fail="onFail" class="poster"><!-- <template v-slot:save><view @click="saveToAlbum">保存</view></template> --></poster><u-image width="222rpx" height="222rpx" :src="code" class="container_image"></u-image></view><!-- <view class="bottom"> --><!-- <view class="bottom_item" @click="share('微信好友')"><u-image width="74rpx" height="74rpx" src="xxxxxxxxxxxxxxstatic/poster/%E7%BC%96%E7%BB%84%206%402x.png"></u-image><text>微信好友</text></view> --><!-- <view class="bottom_item" @click="share('朋友圈')"><u-image width="74rpx" height="74rpx"src="xxxxxxxxxxxposter/%E7%BC%96%E7%BB%84%208%402x.png"></u-image><text>朋友圈</text></view> --><!-- <view class="bottom_item" @click="share('保存图片')"><u-image width="74rpx" height="74rpx"  src="xxxxxxxxxoster/%E7%BC%96%E7%BB%84%2012%402x.png"></u-image><text>保存图片</text></view> --><!-- </view> --></view>
</template><script>import poster from "@/components/poster/index";import {saveImageToPhotosAlbum} from '@/utils/poster.js';export default {components: {poster},data() {return {code: '',canvasImages: '',image: 'xxxxxxter/static/poster/%E7%BC%96%E7%BB%84%205%402x.png',config: {imageMode: 'aspectFit',posterHeight: '100%',// canvasWidth 和 convasHeight使用的是px,防止不同设备Dpr不统一,导致最后图片留白边问题canvasWidth: 275,convasHeight: 600,},drawData: [],wechatCodeConfig: {serverUrl: '',scene: '123123',config: {x: 84.5,y: 320,w: 100,h: 100}}}},created() {this.usercode()// 模拟异步请求获得到的数据// setTimeout(() => {// this.wechatCodeConfig.scene = '456787';// this.drawData = [{// 		type: 'image',// 		config: {// 			url: 'https://horifon.oss-cn-shanghai.aliyuncs.com/hongyunartcenter/static/poster/%E7%BC%96%E7%BB%84%205.png',// 			x: 0,// 			y: 0,// 			w: 275,// 			h: 490// 		},// 	},// 	{// 		type: 'image',// 		config: {// 			url: this.code,// 			x: 40,// 			y: 380,// 			w: 40,// 			h: 40// 		},// 	},// 	{// 		type: 'text',// 		config: {// 			text: '',// 			x: 140,// 			y: 60,// 			color: '#E60012',// 			font: 'normal bold 16px 仿宋',// 			textAlign: 'center'// 		}// 	}// ];// }, 1000)// this.createdPoster();},methods: {//二维码usercode() {let $this = thisuni.request({url: "xxxxxxx/mobile/index.php?m=user&c=indexapi&a=affiliate",method: 'POST',header: {'Content-Type': 'application/x-www-form-urlencoded'},data: {"user_id": parseInt(uni.getStorageSync('USER_ID'))},success(res) {console.log(res.data.data, '数据');$this.code = res.data.data$this.wechatCodeConfig.serverUrl = res.data.data}});},// 保存到相册saveToAlbum() {this.$refs.poster.saveToAlbum()},onLoading() {console.log('Loading:正在加载静态资源')},onSuccess(e) {console.log('Success:创建海报成功', e)this.canvasImages = ewx.showShareImageMenu({path: this.canvasImages,style:"background-color:'black'"})},onFail(e) {console.log('Fail:创建海报失败', e)},createdPoster() {console.log('点击')// 调用 createImage 开始创建海报// this.$refs.poster.createImage()this.$refs.poster.cjian()// wx.showShareImageMenu({// 	path: this.canvasImages// })},share(e) {if (e === '微信好友') {wx.showShareImageMenu({path: this.canvasImages})} else if (e === '朋友圈') {uni.share({provider: "weixin",scene: "WXSceneTimeline",type: 2,imageUrl: this.canvasImages,success: function(res) {console.log("success:" + JSON.stringify(res));},fail: function(err) {console.log("fail:" + JSON.stringify(err));}});} else if (e === '保存图片') {saveImageToPhotosAlbum(this.canvasImages).then(res => {uni.showToast({icon: 'none',title: '保存成功'})}).catch(err => {})}}}}
</script><style scoped lang="scss">.container {position: relative;width: 100%;height: 100%;display: flex;flex-direction: column;align-items: center;justify-content: center;.poster {width: 100%;height: 100%;}.container_image {position: absolute;bottom: 214rpx;left: 76rpx;}}.bottom {display: flex;justify-content: space-evenly;align-items: center;position: absolute;bottom: 0;width: 750rpx;height: 218rpx;background: #FFFFFF;border-radius: 40rpx 40rpx 0rpx 0rpx;// border: 2rpx solid #FF583D;}
</style>

components/poster/index 组件

<template><view class="poster_wrapper"><slot name="header"></slot><!-- 要生成海报的图片 --><image :src="imageUrl" mode="aspectFill" :style="{width:imageWidth + 'rpx'}"  @click="click" class="imagebig"></image><!-- 这里把canvas移到了屏幕外面,如果需要查看canvas的话把定位去掉 --><!-- position:'fixed',left:'9999px',top:'0' --><canvas :style="{width:canvasWidth + 'px',height:canvasHeight + 'px',position:'fixed',left:'9999px',top:'0'}"canvas-id="myCanvas" id="myCanvas" class="canvas"></canvas><!-- 遮罩层 --><!-- <view class="mask" v-if="showMask" @click="hideMask"> --><!-- 生成的海报图 --><!-- <image :style="posterSize" :src="lastPoster" :mode="config.imageMode" @click.stop=""></image> --><!-- <view class="btn_wrapper" @click.stop><slot name="save"><button type="primary" @click="saveToAlbum">保存至相册</button></slot></view> --><!-- </view> --></view>
</template><script>import {loadImage,createPoster,canvasToTempFilePath,saveImageToPhotosAlbum} from '@/utils/poster.js';import {getWechatCode} from "@/utils/appletCode.js";export default {props: {// 展示图片的宽 单位 rpximageWidth: {type: [String, Number],default: 550},// 展示图片的高 单位 rpximageHeight: {type: [String, Number],default:980},// 展示图片的urlimageUrl: {type: String,default: '',required: true},// 绘制海报的数据参数// drawData: {// 	type: Array,// 	default: () => ([]),// 	required: true// },// 海报的配置参数config: {type: Object,default: () => ({imageMode: 'aspectFit',posterHeight:1000,}),},// 是否需要小程序二维码wechatCode: {type: Boolean,default: false},// 小程序二维码的配置参数wechatCodeConfig: {type: Object,default: () => ({serverUrl: '',scene: '',config: {x: 0,y: 0,w: 100,h: 100}}),}},data() {return {// 资源是否加载成功的标志readyed: false,// 将网络图片转成静态图片后的绘制参数imageMap: [],// 最后生成的海报的本地缓存地址lastPoster: '',// 是否展示遮罩showMask: false,// 是否加载资源的标志loadingShow: false,// 是否可以创建海报disableCreatePoster:false,drawData :[{type: 'image',config: {url: '',x: 0,y: 0,w: 275,h: 490},},{type: 'image',config: {url: '',x: 40,y: 380,w: 40,h: 40},},{type: 'text',config: {text: '',x: 140,y: 60,color: '#E60012',font: 'normal bold 16px 仿宋',textAlign: 'center'}}],}},computed: {// 所生成海报图的大小posterSize() {let str = '';this.config.posterWidth && (str += `width:${this.config.posterWidth};`);this.config.posterHeight && (str += `height:${this.config.posterHeight};`);return str},// 画布的宽,优先使用配置的宽,如果没用配置默认使用图片的宽// 需要主要的是这里canvas和image的单位不同,不过并不影响// 我们在绘制时(配置drawData)以px为基准进行绘制就行,用px的原因时防止不同设备Dpr不同导致无法确定画布的具体宽高,使得最后的图片可能会留白边canvasWidth(){return this.config.canvasWidth ? this.config.canvasWidth : this.imageWidth},// 画布的高,优先使用配置的高,如果没用配置默认使用图片的高canvasHeight(){return this.config.convasHeight ? this.config.convasHeight : this.imageHeight}},watch: {// 监听外部绘制参数的变化,重新加载资源// drawData(newVlaue) {// 	this.loadingResources(newVlaue)// },// 监听readyed变化// readyed(newVlaue) {// 	if (newVlaue == true && this.loadingShow == true) {// 		uni.hideLoading()// 		this.loadingShow = false;// 		this.createImage();// 	}// }// 会存在异步问题,还没解决。// 目前的解决方法 1.在绘制之前先改变 scene 2.改变scene后手动调用this.loadingResources 函数,但是资源会重新加载// "wechatCodeConfig.scene":function (newVlaue){// 	console.log('wechatCodeConfig.scene',this.imageMap)// 	this.loadingWechatCode(this.imageMap)// }},created() {this.usercode()// this.$emit('click')// this.loadingResources(this.drawData)},methods: {cjian(){this.usercode()},//二维码usercode() {let $this=thisuni.request({url: "xxxxxxx?m=user&c=indexapi&a=affiliate",method: 'POST',header: {'Content-Type': 'application/x-www-form-urlencoded'},data: {"user_id": parseInt(uni.getStorageSync('USER_ID'))},success(res) {console.log(res.data.data,'数据');$this.drawData=[{type: 'image',config: {url: 'xxxxxxx/static/poster/%E7%BC%96%E7%BB%84%205%403x.png',x: 0,y: 0,w: 275,h: 490},},{type: 'image',config: {url: res.data.data,x: 35,y: 350,w: 70,h:70},},{type: 'text',config: {text: '',x: 140,y: 60,color: '#E60012',font: 'normal bold 16px 仿宋',textAlign: 'center'}}]// $this.wechatCodeConfig.serverUrl=res.data.data$this.loadingResources($this.drawData)}});},// 加载静态资源,创建或更新组件内所下载本地图片集合async loadingResources(drawData) {// this.readyed = false;if (!drawData.length || drawData.length <= 0) return;// 加载静态图片,将所以图片的网络地址替换成本地缓存地址const tempMap = [];for (let i = 0; i < drawData.length; i++) {let tempif (drawData[i].type === "image") {temp = await loadImage(drawData[i].config.url);drawData[i].config.url = temp;}tempMap.push({ ...drawData[i],url: temp})}// 加载小程序二维码await this.loadingWechatCode(tempMap);// 赋值给imageMap保存this.imageMap = tempMap;setTimeout(() => {// this.readyed = true;this.createImage()}, 100)},// 绘制海报图async createImage() {// console.log('点击执行',this.imageMap)// 禁用生成海报,直接返回// if(this.disableCreatePoster) return// this.disableCreatePoster = true;try {// if (!this.readyed) {// 	uni.showLoading({// 		title: '静态资源加载中...'// 	});// 	this.loadingShow = true;// 	this.$emit('loading')// 	return// }// 获取上下文对象,组件内一定要传thisconst ctx = uni.createCanvasContext('myCanvas', this);await createPoster(ctx, this.imageMap);this.lastPoster = await canvasToTempFilePath('myCanvas', this);this.showMask = true;console.log('点击执行',this.imageMap,this.lastPoster)// this.disableCreatePoster = false;// 创建成功函数this.$emit('success',this.lastPoster)} catch (e) {// 创建失败函数this.disableCreatePoster = false;this.$emit('fail', e)}},// 加载或更新小程序二维码async loadingWechatCode(tempMap) {if (this.wechatCode) {if (this.wechatCodeConfig.serverUrl) {const code = await getWechatCode(this.wechatCodeConfig.serverUrl, this.wechatCodeConfig.scene || '');// 记录替换的索引,没有就替换length位,即最后加一个let targetIndex = tempMap.length;for (let i = 0; i < tempMap.length; i++) {if (tempMap[i].wechatCode) targetIndex = i;}tempMap.splice(targetIndex, 1, {type: 'image',url: code.path,// 标记是小程序二维码wechatCode: true,config: this.wechatCodeConfig.config,})} else {throw new Error('serverUrl请求二维码服务器地址不能为空')}}return tempMap},// 保存到相册saveToAlbum() {saveImageToPhotosAlbum(this.lastPoster).then(res => {this.showMask = false;uni.showToast({icon: 'none',title: '保存成功'})}).catch(err => {})},click() {this.$emit('click')this.showMask = false;this.$emit('hidemask')},hideMask(){this.showMask = false;this.$emit('hidemask')}},}
</script><style scoped>.poster_wrapper {width: 100%;height:100vh;display: flex;flex-direction: column;align-items: center;}.imagebig{height:100%;}.canvas {border: 1px solid #333333;}.mask {width: 100vw;height: 100vh;position: fixed;background-color: rgba(0,0,0,.4);left: 0;top: 0;display: flex;flex-direction: column;justify-content: space-around;align-items: center;}
</style>

@/utils/poster.js

// 错误提示集合
const errMsgMap = {'arc': {'x': '请指定圆的起始位置 x','y': '请指定圆的起始位置 y','r': '请指定圆的半径 r','sAngle': '请指定圆的起始弧度 sAngle','eAngle': '请指定圆的终止弧度 eAngle',},'rect': {'x': '请指定矩形的起始位置 x','y': '请指定矩形的起始位置 y','w': '请指定矩形的宽度 w','h': '请指定矩形的高度 h',},'round_rect': {'x': '请指定矩形边框的起始位置 x','y': '请指定矩形边框的起始位置 y','w': '请指定矩形边框的宽度 w','h': '请指定矩形边框的高度 h',},'stroke_rect': {'x': '请指定矩形边框的起始位置 x','y': '请指定矩形边框的起始位置 y','w': '请指定矩形边框的宽度 w','h': '请指定矩形边框的高度 h',},'stroke_round_rect': {'x': '请指定矩形边框的起始位置 x','y': '请指定矩形边框的起始位置 y','w': '请指定矩形边框的宽度 w','h': '请指定矩形边框的高度 h',},'text': {'x': '请指定文本的起始位置 x','y': '请指定文本的起始位置 y','text': '请指定文本的内容 text'},'image': {'x': '请指定图片的起始位置 x','y': '请指定图片的起始位置 y','w': '请指定图片的宽度 w','h': '请指定图片的高度 h','url': '请指定图片的路径 url'},'line': {'path': '请指定线的路径 path'},'points': {'points': '请指定点集合 points'}
};
// 绘制方法集合
const DrawFuncMap = {drawLine(ctx, config, i, isClip) {// clip 图形默认不需要 fill 和 stroke 颜色。const defaultColor = isClip ? 'transparent' : '#333333'// 检验必传参数checkNecessaryParam(config, 'line', i, 'path');// 每一个path就描述了一组线的开始到结束,这一组线段不一定是连续的,根据配置属性来具体描述这个线// 他们的形态是一样的(线的粗细,颜色),形状不一样且不一定是连续的for (let j = 0; j < config.path.length; j++) {const path = config.path[j];checkNecessaryParam(path, 'points', `${i}-${j}`, 'points');const lineWidth = path.lineWidth || 1;const lineJoin = path.lineJoin || 'round';const lineCap = path.lineCap || 'round';// ctx.beginPath();// 设置颜色ctx.setStrokeStyle(path.strokeStyle || defaultColor);// 设置填充色ctx.setFillStyle(path.fillStyle || defaultColor);// 设置粗细ctx.setLineWidth(lineWidth);// 设置线条交点样式ctx.setLineJoin(lineJoin);// 设置线条的断点样式ctx.setLineCap(lineCap);// 遍历线的点集合,根据每个点的不同属性来绘制成线for (let k = 0; k < path.points.length; k++) {// 拿到每一个点const pointSet = path.points[k];// 如果该点是一个数组集合,则点的类型直接当 lineTo 处理if (Object.prototype.toString.call(pointSet) === "[object Array]") {if (k === 0) ctx.moveTo(...pointSet);else ctx.lineTo(...pointSet);} else {// 默认的第一个点一定是起始点,且点类型为 moveTo 则执行 ctx.moveTo 移动画笔if (k === 0 || pointSet.type === 'moveTo') {ctx.moveTo(...pointSet.point);// 点的类型为 lineTo 或 没有 type 属性也默认为 lineTo 至执行 ctx.lineTo 连线} else if (pointSet.type === 'lineTo' || pointSet.type === undefined) {ctx.lineTo(...pointSet.point);} else if (pointSet.type === 'bezierCurveTo') {const P2 = pointSet.P2 ? pointSet.P2 : pointSet.P1;ctx.bezierCurveTo(...pointSet.P1, ...P2, ...pointSet.point);} else if (pointSet.type === 'closePath') {ctx.closePath()ctx.fill();}}}// 每一组点集合(path)结束 strokectx.stroke();}},// 绘制图片drawImage(ctx, config, i) {checkNecessaryParam(config, 'image', i, 'x', 'y', 'w', 'h', 'url');ctx.drawImage(config.url, config.x, config.y, config.w, config.h);},// 绘制圆drawArc(ctx, config, i, isClip) {const defaultColor = isClip ? 'transparent' : '#333333'checkNecessaryParam(config, 'arc', i, 'x', 'y', 'r', 'sAngle', 'eAngle');// ctx.beginPath();ctx.arc(config.x, config.y, config.r, config.sAngle, config.eAngle);ctx.setFillStyle(config.fillStyle || defaultColor);ctx.fill();ctx.setLineWidth(config.lineWidth || 1);ctx.setStrokeStyle(config.strokeStyle || defaultColor);ctx.stroke();},// 绘制文字drawText(ctx, config, i) {checkNecessaryParam(config, 'text', i, 'x', 'y', 'text');ctx.font = config.font || '10px sans-serif';ctx.setFillStyle(config.color || '#333333');ctx.setTextAlign(config.textAlign || 'center');ctx.fillText(config.text, config.x, config.y);ctx.stroke();},// 绘制矩形drawRect(ctx, config, i, isClip) {const defaultColor = isClip ? 'transparent' : '#333333'checkNecessaryParam(config, 'rect', i, 'x', 'y', 'w', 'h');// ctx.beginPath();ctx.rect(config.x, config.y, config.w, config.h);ctx.setFillStyle(config.fillStyle || defaultColor);ctx.setLineWidth(config.lineWidth || 1);ctx.setStrokeStyle(config.strokeStyle || defaultColor);ctx.stroke();ctx.fill();},// 绘制非填充矩形drawStrokeRect(ctx, config, i, isClip) {checkNecessaryParam(config, 'stroke_rect', i, 'x', 'y', 'w', 'h');// ctx.beginPath();ctx.setStrokeStyle(config.strokeStyle || '#333333');ctx.setLineWidth(config.lineWidth || 1);ctx.strokeRect(config.x, config.y, config.w, config.h);ctx.stroke();},// 绘制填充圆角矩形drawRoundRect(ctx, config, i, isClip) {// 当为裁剪图形,需要把 fill 和 stroke 颜色设置为透明const defaultColor = isClip ? 'transparent' : '#333333'checkNecessaryParam(config, 'stroke_rect', i, 'x', 'y', 'w', 'h');ctx.setFillStyle(config.fillStyle || defaultColor);this.drawRoundRectPath(ctx, config.x, config.y, config.w, config.h, config.r)ctx.fill();},// 绘制非填充圆角矩形drawStrokeRoundRect(ctx, config, i, isClip) {checkNecessaryParam(config, 'stroke_rect', i, 'x', 'y', 'w', 'h');ctx.setStrokeStyle(config.strokeStyle || '#333333');ctx.setLineWidth(config.lineWidth || 1);this.drawRoundRectPath(ctx, config.x, config.y, config.w, config.h, config.r)ctx.stroke();},// 绘制圆角矩形路径drawRoundRectPath(ctx, x, y, w, h, r) {// ctx.beginPath();// ctx.strokeRect(config.x, config.y, config.w, config.h);//从右下角顺时针绘制,弧度从0到1/2PI  ctx.arc(x + w - r, y + h - r, r, 0, Math.PI / 2);//矩形下边线  ctx.lineTo(x + r, y + h);//左下角圆弧,弧度从1/2PI到PI  ctx.arc(x + r, y + h - r, r, Math.PI / 2, Math.PI);//矩形左边线  ctx.lineTo(x, y + r);//左上角圆弧,弧度从PI到3/2PI  ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 3 / 2);//上边线  ctx.lineTo(x + w - r, y);//右上角圆弧  ctx.arc(x + w - r, y + r, r, Math.PI * 3 / 2, Math.PI * 2);//右边线  ctx.lineTo(x + w, y + h - r);ctx.closePath();}
}/*** 检测绘制的必要属性* @param {Object} configObj 配置对象* @param {String} type 对应校验的类型* @param {String|Number} index 当前的错误位置 从0开始对应绘画(drawData)配置中的索引,* 当为 String 类型时会以'-'间隔出第几层的第几个,如1-2 表示是绘画(drawData)配置中第一个配置里的第二个子配置对象有问题,依次类推* @param {Array} keyArr 搜集到的所以需要进行检验的键名**/
function checkNecessaryParam(configObj, type, index, ...keyArr) {// 这里要注意由于,绘画配置有些参数可能会漏写,所以 errMsgMap[type] 作为遍历对象进行比较for (let prop in errMsgMap[type]) {if (configObj[prop] === undefined) {throw new Error(`${index}顺位:${errMsgMap[type][prop]}`)}}
}
/*** 根据不同的图形类型绘制图形* @param {String} type 图形的类型* @param {Object} ctx 当前 canvas 的上下文对象* @param {Object} config 图形对应的配置属性对象,* @param {String|Number} index 从0开始对应绘画(drawData)配置中的索引* @param {Boolean} isClip 是否应用于剪切**/
function drawFigureByType(type, ctx, config, index, isClip) {if (type === 'image') {!isClip && DrawFuncMap.drawImage(ctx, config, index);} else if (type === 'text') {!isClip && DrawFuncMap.drawText(ctx, config, index);} else if (type === 'arc') {DrawFuncMap.drawArc(ctx, config, index, isClip);} else if (type === 'rect') {DrawFuncMap.drawRect(ctx, config, index, isClip);} else if (type === 'stroke_rect') {// 这里非填充矩形也按照矩形的方式绘制裁剪区域isClip ? DrawFuncMap.drawRect(ctx, config, index, isClip) : DrawFuncMap.drawStrokeRect(ctx, config, index,isClip);} else if (type === 'stroke_round_rect') {// 这里非填充圆角矩形也按照圆角矩形的方式绘制裁剪区域isClip ? DrawFuncMap.drawRoundRect(ctx, config, index, isClip) : DrawFuncMap.drawStrokeRoundRect(ctx, config,index, isClip);} else if (type === 'round_rect') {DrawFuncMap.drawRoundRect(ctx, config, index, isClip);} else if (type === 'line') {DrawFuncMap.drawLine(ctx, config, index, isClip)}
}
/*** 不同类型的图形都可以绘制不同的剪切图形* @param {String} type 绘制图形的类型* @param {Object} ctx 当前 canvas 的上下文对象* @param {Object} config 绘制图形对应的配置属性对象,* @param {String|Number} index 绘制图形的索引 从0开始对应绘画(drawData)配置中的索引* @param {String} clipType 剪切图形的类型* @param {Object} clipConfig 剪切图形对应的配置属性对象,**/
function drawClipFigure(type, ctx, config, index, clipType, clipConfig) {ctx.beginPath();drawFigureByType(clipType, ctx, clipConfig, index, true)ctx.clip()ctx.beginPath();drawFigureByType(type, ctx, config, index, false)
}// 获取图片信息,这里主要要获取图片缓存地址
export function loadImage(url) {return new Promise((resolve, reject) => {wx.getImageInfo({src: url,success(res) {resolve(res.path)},fail(err) {reject('海报图资源加载失败')}})})
}
// 解析海报对象,绘制canvas海报
export function createPoster(ctx, posterItemList) {return new Promise((resolve, reject) => {try {for (let i = 0; i < posterItemList.length; i++) {const temp = posterItemList[i];// 如果有 clip 属性需要先建立 clip 剪切区域if (temp.clip) {ctx.save()// 绘制剪切图形drawClipFigure(temp.type, ctx, temp.config, i, temp.clip.type, temp.clip.config)ctx.restore()} else {ctx.beginPath();//正常绘制图形drawFigureByType(temp.type, ctx, temp.config, i, false)}}ctx.draw();resolve({result: 'ok',msg: '绘制成功'})} catch (e) {console.error(e)reject({result: 'fail',msg: e})}})
}
// canvas转image图片
export function canvasToTempFilePath(canvasId, vm, delay = 50) {return new Promise((resolve, reject) => {// 这里canvas绘制完成之后想要存缓存需要一定时间,这里设置了50毫秒setTimeout(() => {uni.canvasToTempFilePath({canvasId: canvasId,x:0,y:0,width: 300,height: 490,destWidth: 300,destHeight:490,success(res) {if (res.errMsg && res.errMsg.indexOf('ok') != -1) resolve(res.tempFilePath);else reject(res)},fail(err) {reject(err)}}, vm);}, delay)})
}
// 保存图片到相册
export function saveImageToPhotosAlbum(imagePath) {return new Promise((resolve, reject) => {uni.saveImageToPhotosAlbum({filePath: imagePath,success(res) {resolve(res)},fail(err) {reject(err)}})})
}

@/utils/appletCode.js

import {base64src} from "@/utils/base64src.js";
/*** 微信获取小程序二维码* @param {String} url 微信服务器地址* @param {String} scene 二维码所携带的信息* @return {Object} 返回的二维码对象**/
export function getWechatCode (url,scene) {return new Promise((resolve,reject)=>{wx.request({url: url,method: 'POST',header: {'content-type': 'application/x-www-form-urlencoded'},// 二维码携带的信息data: {scene: scene},success(res) {//将base64图片转换成本地路径base64src("data:image/PNG;base64," + res.data.qcode, res => { // 获取图片信息wx.getImageInfo({src: res,success(res) {resolve(res);},fail(err) {reject(err);}})})},fail(err){reject(err);}})})
}

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

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

相关文章

NGINX发布动态页面的方法

一、建立 [rootserver100 html]# vim index.php [rootserver100 html]# pwd /usr/share/nginx/html 二、下载PHP文件 [rootserver100 conf.d]# dnf install php.x86_64 -y 正在更新 Subscription Management 软件仓库。 无法读取客户身份 本系统尚未在权利服务器中注册。可…

Latex入门教学——常用语句介绍

目录 一、导言 二、正文 三、图片 四、公式 五、表格 六、参考文献 LaTex模板下载 IEEE模板&#xff1a;IEEE Article Templates - IEEE Author Center Journals通用模板&#xff1a;Overleaf, Online LaTeX Editor其他方法&#xff1a;百度&#xff0c;CSDN等。 一、导…

力扣题目:寻找数组的中心下标

力扣题目&#xff1a;寻找数组的中心下标 题目链接: 724.寻找数组的中心下标 题目描述 代码思路 根据题目内容&#xff0c;维护好前后缀和&#xff0c;然后从左到右遍历寻找合适的下标 代码纯享版 class Solution {public int pivotIndex(int[] nums) {int sumleft 0, su…

Go 语言数组

Go 语言提供了数组类型的数据结构。 数组是具有相同唯一类型的一组已编号且长度固定的数据项序列&#xff0c;这种类型可以是任意的原始类型例如整型、字符串或者自定义类型。 相对于去声明 number0, number1, ..., number99 的变量&#xff0c;使用数组形式 numbers[0], num…

【踩坑日记】SpringBoot集成Kafka,消息没有按照顺序消息问题【已解决】

背景 作为一个合格的码农&#xff0c;当然要学会CV大法了&#xff0c;可是CV也是有风险的&#xff0c;别以为前任写的已经上线那么久了没有问题… 我们需要将埋点信息上报到一个三方平台&#xff08;S2S&#xff09;接口&#xff0c;三方平台对时间有要求&#xff0c;同一个用…

Oracle 数据迁移同步优化(三)

简述 CloudCanal 最近再次对其 Oracle 源端数据同步进行了一系列优化&#xff0c;这些优化基于用户在真实场景中的反馈&#xff0c;具备很强的生产级别参考意义。 本文将简要介绍这些优化项&#xff0c;希望带给读者一些收获。 增量事件 SCN 乱序问题MISSING_SCN 事件干扰新…

clickhouse与oracle传输数据

参考 https://github.com/ClickHouse/clickhouse-jdbc-bridge https://github.com/ClickHouse/clickhouse-jdbc-bridge/blob/master/docker/README.md clickhouse官方提供了一种方式&#xff0c;可以实现clickhouse与oracle之间传输数据&#xff0c;不仅仅是oracle&#xff0…

使用JavaScript及HTML、CSS完成秒表计时器

案例要求 1.界面为一个显示计时面板和三个按钮分别为:开始&#xff0c;暂停&#xff0c;重置 2.点击开始&#xff0c;面板开始计时&#xff0c; 3.点击暂停&#xff0c;面板停止 4.点击重置&#xff0c;计时面板重新为0 案例源码 <!DOCTYPE html> <html lang"…

淘宝(天猫)|京东|1688商品详情数据接口在自有电商平台的应用!

在电商市场的日益成熟下&#xff0c;越来越多的电商参与者上线了自主研发的电商平台。这主要是因为&#xff0c;在电商销售中&#xff0c;品牌在自有电商平台售卖商品的优势颇多&#xff1a; 自有的电商平台能够赋予品牌更大的灵活性和自由度等&#xff0c;品牌商品销售时无需…

Linux驱动开发——(九)platform设备驱动

目录 一、Linux驱动的分离 二、Linux驱动的分层 三、platform平台驱动模型简介 3.1 platform_driver结构体 3.2 device_driver结构体 3.3 platform驱动API函数 四、驱动代码 一、Linux驱动的分离 对于Linux这种庞大而复杂的系统&#xff0c;需要非常注重代码的重用性&a…

IntelliJ IDEA - Lombok supports: OpenJDK javac, ECJ

问题描述 java: You arent using a compiler supported by lombok, so lombok will not work and has been disabled.Your processor is: com.sun.proxy.$Proxy26Lombok supports: OpenJDK javac, ECJ 解决方案 在 IDEA 设置中 File -> Settings 中找到配置如下&#xff1…

从系统到模块,逐步深入PLL设计

锁相环&#xff08;PLL&#xff09;电路广泛存在于各种应用之中&#xff0c;大到手机&#xff0c;服务器&#xff0c;小到智能手表&#xff0c;家用MCU。时钟信号的合成&#xff0c;数据的采样还原都需要PLL电路的深度参与。 何为锁相环&#xff08;PLL&#xff09;&#xff1…

由于找不到xinput1_3.dll,无法继续执行代码的详细修复方法

在日常使用电脑进行工作或娱乐时&#xff1a;系统突然弹出一个错误提示&#xff0c;明确指出“xinput1_3.dll文件丢失”。这个问题可能会导致游戏无法正常运行。为了解决这个问题&#xff0c;我通过查阅资料和实践总结出了以下五种解决方法&#xff0c;希望能对遇到类似问题的朋…

Mysql基础(三)DDL之create table语句

一 create table 创表 说明&#xff1a; create table相关语句从功能上进行讲解补充&#xff1a; 前面已经讲解过相关的约束,已进行相关的铺垫声明&#xff1a; 参考价值较少,了解即可 ① 基本语法 思考&#xff1a; 约束加在哪里? ② 创建新表 强调&#xff1a;使…

任务调度xxljob的使用记录

1.基本使用 a.下载代码&#xff0c;地址&#xff1a;https://gitee.com/xuxueli0323/xxl-job.git b.执行sql&#xff0c;修改配置&#xff0c;启动任务调度中心的代码 启动代码后任务调度中心访问地址&#xff1a;http://localhost:8080/xxl-job-admin&#xff08;自己机器…

岚图汽车与东软睿驰签署战略合作协议

4月26日,东软睿驰与岚图汽车正式签署战略合作协议,双方将结合在各自领域拥有的产业资源、技术研发和资本运作等优势,聚焦智能化产品和应用,建立长期共赢的战略合作伙伴关系,通过不断探索未来新技术、新产业、新业态和新模式,围绕用户需求共同打造极致的智能出行体验。 图为岚图…

Rust Web开发实战:打造高效稳定的服务端应用

Rust Web开发实战&#xff1a;打造高效稳定的服务端应用 本书将带领您从零开始构建Web应用程序&#xff0c;无论是API、微服务还是单体应用&#xff0c;都将一一涵盖。您将学到如何优雅地对外开放API&#xff0c;如何连接数据库以安全存储数据&#xff0c;以及如何对应用程序进…

VPN的基本概念

随着互联网的普及和应用的广泛&#xff0c;网络安全和隐私保护越来越受到人们的关注。在这个信息爆炸的时代&#xff0c;我们的个人信息、数据通信可能会受到各种威胁&#xff0c;如何保护自己的隐私和数据安全成为了一个迫切的问题。而VPN&#xff08;Virtual Private Network…

Quarto Dashboards 教程 3:Dashboard Data Display

「写在前面」 学习一个软件最好的方法就是啃它的官方文档。本着自己学习、分享他人的态度&#xff0c;分享官方文档的中文教程。软件可能随时更新&#xff0c;建议配合官方文档一起阅读。推荐先按顺序阅读往期内容&#xff1a; 1.quarto 教程 1&#xff1a;Hello, Quarto 2.qu…

混合注意力 ACmix | On the Integration of Self-Attention and Convolution

论文名称&#xff1a;《On the Integration of Self-Attention and Convolution》 论文地址&#xff1a;2111.14556 (arxiv.org) 卷积和自注意力是两种强大的表示学习技术&#xff0c;通常被认为是两种截然不同的并列方法。在本文中&#xff0c;我们展示了它们之间存在一种强烈…