uni-app微信小程序保存页面到相册;canvas保存小程序页面;微信小程序保存二维码活动页面到相册;微信小程序canvas 生成海报保存到相册;canvas绘制小程序页面保存及分享;

文末代码可以直接复制运行(只需要将中间的二维码图片、底部的微信和相册图片和微信头像配置白名单 改成你项目内的img图片即可成功运行)

一、场景:在微信小程序 个人名片页面 含有微信头像和个人信息二维码(识别可跳转小程序指定页面并携带参数),要求点击 保存到相册 按钮,将此页面上半部分进行截图保存;还有分享功能,和识别二维码一样,文末代码逻辑也有;

个人名片页面:
在这里插入图片描述
保存到相册页面:
在这里插入图片描述

二、需求分析:
–2.1:二维码:生成动态二维码图片及携带参数跳转指定小程序页面(点此查看),这些功能和二维码图片都是后端实现的。前端只调用接口拿图即可;
–2.2:实现小程序保存图片到系统相册(点此查看,需要有相应权限和图片域名白名单配置;
–2.3:前端如何将对应页面部分保存成图片?使用canvas画布生成图片(最稳定但也是麻烦的方式)!只需要将UI设计稿的内容,按照对应的比例画到画布上,最后使用画布生成图片;
.
总体思路就是:通过后端接口拿到二维码图片–正常写一个 个人名片页面(同时需要用canvas绘制出一个 个人名片页面,且这个canvas画布绘制的页面图片需要隐藏掉,而不是清除掉)–最后点击 保存到相册 按钮时候调用微信或者uni-app的保存图片方法即可;
.
–注意上述的两个点击查看链接一定要看下,避免很多坑!

三、针对可能遇到的问题和文末代码的部分解释:
3.1代码内图片替换: 页面最底部 分享到微信~@/static/icon-weixin.png和保存到相册~@/static/icon-pics.png两张图片需要替换成你自己的static静态图片;
二维码图片imgUrl: '../static/iconimg/codeimg.png',也要替换成后端接口给你的真实二维码图片;
3.2头像替换:uni.getStorageSync('avatarUrl')是你自己存的微信头像,需要配置download域名白名单,否则报错getImageInfo:fail download image fail. reason: downloadFile:fail createDownloadTask:fail url not in domain list;导致的下载失败;
–3.3canvas画布,需要存在,但是要隐藏在页面中;画布只能绘制本地图片和临时路径图片,不能绘制网络图片(所以我们需要用uni.getImageInfo()获取微信头像的网络图片,然后把这个绘制到画布上);
3.4画布的内容绘制方法,都是有大小和颜色和定位位置的:使用画布绘制同样的UI 页面时候,需要计算比例:计算UI设计稿和你手机的屏幕宽度比例(例如UI设计稿是750宽度 你手机是350宽度 比例就是2;那么你画布画图时候 所有的尺寸大小、宽高、位置、定位左右上下都需要除以 / 比例2;此时假如UI设计稿上的二维码图片宽高是340,图片距离UI设计稿顶部是400,距离最左边是60,那么你的画布上设置都是直接 340/2 , 400/2 , 60/2 )
–另附上画布canvas使用方法
–3.5如果你想直接看到画布绘制图片结果:可以打开代码524行的三行注释 点击一下 保存到相册 按钮就会看到绘制的图片 可能小程序模拟器上有误差 真机基本没误差
–3.6onShareAppMessage是分享功能,配合<button class="share_btn" open-type="share"></button>实现分享;

四、以下代码可以直接复制使用运行(注意上述的3.1和3.2替换图片以及配置微信头像的白名单)
无论真机还是模拟器都可以正常保存页面图片到相册
文中的2.1和2.2最好看一下

<template>
  <view class="percard">
    <view class="top_card_box">
      <view class="top_info">
        <img class="t1" :src="myObj.head_image" alt="">
        <view class="t2">
          <view class="t3">
            <uni-icons class="icons_btn" type="arrowright" size="22" color="#ccc" />
            <view>
              {{myObj.nickname}}
            </view>
            <view>
              {{myObj.personal_signature?myObj.personal_signature:'未设置个性签名'}}
            </view>
          </view>
        </view>
      </view>

      <view class="bot_info">
        <img class="erweima_img" :src="imgUrl" alt="">
        <view class="t5">用微信扫描二维码</view>
        <view class="t6">加入保客多多,加入我的团队</view>
      </view>

    </view>

    <canvas canvas-id="myCanvas" :style="{ width: canvasWidth, height: canvasHeight }" v-if="true"></canvas>

    <view class="bot_card_box">
      <view class="fl">
        <img @click="aa" src="~@/static/icon-weixin.png" alt="">
        <view @click="aa">分享到微信</view>
        <button class="share_btn" open-type="share"></button>
      </view>
      <view class="fl">
        <img @click="myimg" src="~@/static/icon-pics.png" alt="">
        <view @click="myimg">保存到相册</view>
      </view>
    </view>

  </view>
</template>

<script>

// var base64src = require('./base64.js')
// 导入外部JS库
export default {
  data () {
    return {
      myObj: {
        nickname: '喜喜', //微信昵称
        head_image: uni.getStorageSync('avatarUrl'),// 获取缓存内的微信头像--并且需要在你自己的小程序后台配置download域名白名单--否则会获取失败
        personal_signature: '个人名片二维码,携带个人的唯一标识参数id;他人识别此二维码,可以跳转至首页,并拿到此id', //个性签名
        user_code: "rjfhkb", //用户码---自定义二维码传递的动态参数
      },

      imgHeadNow: '',//微信头像网络图片下载本地的临时图片--画布只能绘制本地图片不能是网络图片
      imgUrl: '../static/iconimg/codeimg.png',//二维码图片(在这里我是直接引用了本地二维码图片  正常逻辑是后端返回二维码图片 getCodeImg方法就是)

      canvasWidth: '',//画布宽度
      canvasHeight: '',//画布高度
      ratio: 0,//计算UI设计稿和你手机的屏幕宽度比例(例如UI设计稿是750宽度 你手机是350宽度 比例就是2  那么你画布画图时候 所有的尺寸大小、宽高、位置、定位左右上下都需要除以 / 比例2 )
    }
  },
  // 分享函数
  onShareAppMessage (res) {
    if (res.from === 'button') {// 来自页面内分享按钮
      console.log(res.target)
    }
    return {
      title: '诚邀您使用保客多多,开启客户管理轻松之旅!',
      path: `pages/tabBar/home/index?user_code=${this.myObj.user_code}`
      // 分享跳转页面和二维码跳转携带参数页面是一样传参和逻辑(也都会打开跳转页的onLoad函数 接收参数)
    }
  },
  onLoad () {
    let that = this
    uni.getSystemInfo({
      success: res => {
        // console.log(res)
        that.canvasWidth = res.screenWidth + 'px'
        that.ratio = 750 / res.screenWidth
        that.canvasHeight = 1000 / that.ratio + 'px'
      }
    })

    this.getCodeImg()
  },
  methods: {
    // 通过后端获取二维码图片
    getCodeImg () {
      // let user_code = this.myObj.user_code
      // let home_url = `/pages/tabBar/home/index?user_code=${user_code}`
      // let redirect_url = encodeURI(home_url)
      // getMinQrcode({ redirect_url: redirect_url }).then(res => {
      //   uni.showLoading({
      //     title: '加载中...',
      //     mask: true
      //   })
      //   // 解码后端返回的base64二维码图片
      //   var shareQrImg = `data:image/jpg;base64,` + res.data.base64
      //   base64src(shareQrImg, resCurrent => {
      //     this.imgUrl = resCurrent
      //     uni.hideLoading()
      //   })
      // })
    },


    // 绘制圆角矩形
    /**
     *
     * @param {*} x 起始x坐标
     * @param {*} y 起始y坐标
     * @param {*} width 矩形宽度
     * @param {*} height 矩形高度
     * @param {*} r 矩形圆角
     * @param {*} bgcolor 矩形填充颜色
     * @param {*} lineColor 矩形边框颜色
     */
    rectangle (ctx, x, y, width, height, r, bgcolor, lineColor) {
      ctx.beginPath()
      ctx.moveTo(x + r, y)
      ctx.lineTo(x + width - r, y)
      ctx.arc(x + width - r, y + r, r, Math.PI * 1.5, Math.PI * 2)
      ctx.lineTo(x + width, y + height - r)
      ctx.arc(x + width - r, y + height - r, r, 0, Math.PI * 0.5)
      ctx.lineTo(x + r, y + height)
      ctx.arc(x + r, y + height - r, r, Math.PI * 0.5, Math.PI)
      ctx.lineTo(x, y + r)
      ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5)
      ctx.fillStyle = bgcolor
      ctx.strokeStyle = lineColor
      ctx.fill()
      ctx.stroke()
      ctx.closePath()
    },
    // 使用画布绘制页面
    drawPageImg () {
      let _this = this
      // 生成画布
      const ctx = uni.createCanvasContext('myCanvas')

      // 获取微信头像的临时地址
      let headImg = this.imgHeadNow || '../static/kdd.jpg'

      // 绘制矩形
      this.rectangle(ctx, (55 / _this.ratio), (50 / _this.ratio), (640 / _this.ratio), (860 / _this.ratio), (8 / _this.ratio), '#fff', "#e4e4e4")


      // 绘制直线
      ctx.beginPath()  //开始绘制
      ctx.moveTo((55 / _this.ratio), (264 / _this.ratio))      //起点
      ctx.lineTo((695 / _this.ratio), (264 / _this.ratio))    //终点
      ctx.lineWidth = 1 // 设置线的宽度,单位是像素
      ctx.strokeStyle = '#e4e4e4'  //设置线的颜色
      ctx.stroke() //进行绘制


      // 绘制头像
      ctx.save() // 先保存状态 已便于画完圆再用
      ctx.beginPath() //开始绘制
      //先画个圆
      ctx.arc((130 / _this.ratio), (157 / _this.ratio), (45 / _this.ratio), 0, Math.PI * 2, false)
      ctx.clip()//画了圆 再剪切  原始画布中剪切任意形状和尺寸。一旦剪切了某个区域,则所有之后的绘图都会被限制在被剪切的区域内
      ctx.drawImage(headImg, (85 / _this.ratio), (112 / _this.ratio), (90 / _this.ratio), (90 / _this.ratio), (85 / _this.ratio), (112 / _this.ratio))//描绘图片 // 第一个参数是图片 第二、三是图片在画布位置 第四、五是将图片绘制成多大宽高(不写四五就是原图宽高)
      ctx.restore() //恢复之前保存的绘图上下文 恢复之前保存的绘图上下午即状态 可以继续绘制

      // 绘制微信名称
      ctx.font = (32 / _this.ratio) + "px"
      ctx.fillStyle = '#212121'
      ctx.fillText(_this.myObj.nickname, (205 / _this.ratio), (110 / _this.ratio))//描绘文本


      // 绘制个性签名以及个性签名自动换行
      var temp = ""
      var row = []

      let gxqm = ''
      if (this.myObj.personal_signature) {
        gxqm = this.myObj.personal_signature
      } else {
        gxqm = '未设置个性签名'
      }

      let gexingqianming = gxqm.split("")
      let x = 205 / _this.ratio
      let y = 110 / _this.ratio
      let w = 320 / _this.ratio
      for (var a = 0; a < gexingqianming.length; a++) {
        if (ctx.measureText(temp).width < w) {
          ;
        } else {
          row.push(temp)
          temp = ""
        }
        temp += gexingqianming[a]
      }
      row.push(temp)
      ctx.font = (24 / _this.ratio) + "px"
      ctx.fillStyle = "#9E9E9E"
      for (var b = 0; b < row.length; b++) {
        ctx.fillText(row[b], x, y + (b + 1) * 20)
      }

      // 把二维码图片绘制到画布中
      ctx.drawImage(_this.imgUrl, (205 / _this.ratio), (375 / _this.ratio), (340 / _this.ratio), (360 / _this.ratio), (190 / _this.ratio), (375 / _this.ratio))//描绘图片

      // 绘制文字
      ctx.font = (26 / _this.ratio) + "px PingFangSC-Light"
      ctx.fillStyle = "#212121"
      ctx.textAlign = 'center'
      ctx.fillText('用微信扫描二维码', (375 / _this.ratio), (750 / _this.ratio))//描绘文本

      // 绘制文字
      ctx.font = (28 / _this.ratio) + "px PingFangSC-Regular"
      ctx.fillStyle = "#212121"
      ctx.textAlign = 'center'
      ctx.fillText('加入保客多多,加入我的团队', (375 / _this.ratio), (788 / _this.ratio))//描绘文本


      // 渲染画布
      ctx.draw(false, (() => {
        setTimeout(() => {
          uni.canvasToTempFilePath({
            canvasId: 'myCanvas',
            destWidth: _this.cropW * 2,   //展示图片尺寸=画布尺寸1*像素比2
            destHeight: _this.cropH * 2,
            quality: 1,
            fileType: 'jpg',
            success: (res1) => {
              uni.hideLoading()
              console.log('通过画布绘制出的图片--保存的就是这个图', res1.tempFilePath)

              // 真正的保存图片画布绘制的图片到相册
              uni.saveImageToPhotosAlbum({
                filePath: res1.tempFilePath,
                success: function () {
                  uni.showToast({
                    icon: 'none',
                    position: 'bottom',
                    title: "已保存到系统相册",
                  })
                },
                fail: function (error) {
                  uni.showModal({
                    title: '提示',
                    content: '若点击不授权,将无法使用保存图片功能',
                    cancelText: '不授权',
                    cancelColor: '#999',
                    confirmText: '授权',
                    confirmColor: '#f94218',
                    success (res4) {
                      console.log(res4)
                      if (res4.confirm) {
                        // 选择弹框内授权
                        uni.openSetting({
                          success (res4) {
                            console.log(res4.authSetting)
                          }
                        })
                      } else if (res4.cancel) {
                        // 选择弹框内 不授权
                        console.log('用户点击不授权')
                      }
                    }
                  })
                }
              })
            },
            fail: function (error) {
              uni.hideLoading()
              console.log(error)
              uni.showToast({
                icon: 'none',
                position: 'bottom',
                title: "绘制图片失败", // res.tempFilePath
              })
            }
          }, _this)

        }, 500)
      })())
    },

    myimg () {
      // 头像网络图片下载本地的临时图片--画布只能绘制本地图片不能是网络图片
      uni.getImageInfo({
        src: this.myObj.head_image,
        success: (res) => {
          console.log('微信头像的临时路径', res.path)
          this.imgHeadNow = res.path

          let that = this
          // 获取用户是否开启 授权保存图片。
          uni.getSetting({
            success (res) {
              console.log(res)

              // 如果没有授权
              if (!res.authSetting['scope.writePhotosAlbum']) {
                // 则拉起授权窗口
                uni.authorize({
                  scope: 'scope.writePhotosAlbum',
                  success () {
                    uni.showLoading({
                      title: '加载中...',
                      mask: true
                    })
                    //点击允许后--就一直会进入成功授权的回调 就可以使用获取的方法了
                    that.drawPageImg()
                  },
                  fail (error) {
                    //点击了拒绝授权后--就一直会进入失败回调函数--此时就可以在这里重新拉起授权窗口
                    console.log('点击了拒绝授权', error)

                    uni.showModal({
                      title: '提示',
                      content: '若点击不授权,将无法使用保存图片功能',
                      cancelText: '不授权',
                      cancelColor: '#999',
                      confirmText: '授权',
                      confirmColor: '#f94218',
                      success (res) {
                        console.log(res)
                        if (res.confirm) {
                          // 选择弹框内授权
                          uni.openSetting({
                            success (res) {
                              console.log(res.authSetting)
                            }
                          })
                        } else if (res.cancel) {
                          // 选择弹框内 不授权
                          console.log('用户点击不授权')
                        }
                      }
                    })
                  }
                })
              } else {
                uni.showLoading({
                  title: '加载中...',
                  mask: true
                })
                // 有权限则直接获取
                that.drawPageImg()
              }
            },
            fail: (error) => {
              console.log('获取用户是否开启保存图片 接口失败', error)
              uni.hideLoading()
              uni.showToast({
                title: error.errMsg,
                icon: 'none',
              })
            }
          })

        },
        fail: (error) => {
          console.log('临时图片获取失败', error)
          uni.hideLoading()
          uni.showToast({
            title: error.errMsg,
            icon: 'none',
          })
        }
      })






    }
  }
}
</script>


<style lang="less" scope>
.percard {
  overflow-x: hidden; //解决因为画布浮动超出导致的滚动条--需要隐藏掉
  height: calc(100vh - 90rpx);
  padding: 50rpx 55rpx;
  background-color: rgba(245, 247, 250, 1);
  // 加这两行代码是为了固定定位 解决此页面上下可滑动回弹问题
  position: fixed;
  width: calc(100vw - 110rpx);
  .top_card_box {
    border: 1px solid #e4e4e4;
    border-radius: 8rpx;
    background-color: #fff;
    .top_info {
      // height: 214rpx;
      display: flex;
      padding: 30rpx 72rpx 30rpx 30rpx;
      box-sizing: border-box;
      position: relative;
      .t1 {
        display: inline-block;
        width: 90rpx;
        height: 90rpx;
        position: absolute;
        top: 50%;
        transform: translate(0, -50%);
        border-radius: 50%;
        // margin-top: 37rpx;
      }
      .t2 {
        margin-left: 120rpx;
        flex: 1;
        // background-color: #1fff;
        // padding-right: 50rpx;
        display: inline-block;
        .t3 {
          flex: 1;
          view {
            font-family: PingFangSC-Medium;
            font-size: 32rpx;
            color: #212121;
            letter-spacing: 0;
          }
          view:last-child {
            margin-top: 20rpx;
            font-family: PingFangSC-Regular;
            font-size: 22rpx;
            color: #9e9e9e;
          }
        }

        .icons_btn {
          width: 40rpx;
          position: absolute;
          top: 50%;
          right: 30rpx;
          transform: translate(0, -50%);
          border-radius: 50%;
        }
      }
    }

    .bot_info {
      border-top: 1px solid #e4e4e4;
      height: 644rpx;
      // background-color: #1fff;
      text-align: center;

      .erweima_img {
        margin-top: 90rpx;
        display: block;
        width: 340rpx;
        height: 360rpx;
        // margin-left: 165rpx;
        margin-left: 150rpx;
      }
      .t5 {
        font-family: PingFangSC-Light;
        font-size: 26rpx;
        color: #212121;
        text-align: center;
        margin-top: 30rpx;
      }
      .t6 {
        font-family: PingFangSC-Regular;
        font-size: 28rpx;
        color: #212121;
        letter-spacing: 0;
        text-align: center;
        margin-top: 16rpx;
      }
    }
  }

  .bot_card_box {
    position: fixed;
    bottom: 31rpx;
    overflow: hidden;
    .fl {
      float: left;
      text-align: center;
      width: 335rpx;
      .share_btn {
        width: 120rpx;
        height: 165rpx;
        position: absolute;
        top: 0;
        left: 111rpx;
        background-color: rgba(255, 255, 255, 0);
      }
      button {
        border: none;
      }
      button::after {
        border: none;
      }
      img {
        margin-left: 111rpx;
        display: block;
        width: 112rpx;
        height: 112rpx;
      }
      view {
        margin-top: 22rpx;
        font-family: PingFangSC-Regular;
        font-size: 26rpx;
        color: #212121;
        letter-spacing: 0;
        text-align: center;
      }
    }
  }
}
</style>
<style>
canvas {
  float: left;
  margin-left: 1155rpx;
  margin-top: -911rpx;

  /* 打开以下注释 点击一下 保存到相册 按钮就会看到绘制的图片 可能小程序模拟器上有误差 真机基本没误差 */
  /* background-color: rgb(117, 250, 250);
  margin-left: -55rpx;
  margin-top: 0rpx; */
}
</style>


  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 15
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值