手把手教你撸一个小程序带太阳码的海报分享

2021-03-24 10:43:27 浏览数 (1)

1、前言

我们都知道,微信小程序目前还不支持转发朋友圈,可能现在Android是支持了,iOS还不支持,但总的来说还不能支持普遍机型。这样假如我们需要推荐某个心仪的商品到朋友圈就没法分享出去,于是就可以使用生成海报的形式,让商品详情页的信息显示在一张图片上,保存到手机相册,然后发朋友圈,朋友可以长按识别海报上的小程序码直达该商品详情页面,从而达到如同直接分享商品详情页的效果。

2、思路

之所以用户扫码能直接进入到我们指定的页面,主要就是要靠海报上的那个太阳码,也就是小程序码,其他海报上的信息或者漂亮的样式纯粹就是为了渲染海报图片而已。

1、当用户点击按钮生成海报时,我们要通过后台接口生成并返回小程序码,要记得传给后台要塞入小程序码的参数,比如这里要进入商品详情页就需要塞入商品id,如果还需要知道是谁分享的,那还要塞入用户id。(这里有个问题留给大家思考,因为生成小程序码的接口scene字段长度最多是32位,如果我们需要传的参数位数超过32该怎么办,特别是数据库主键是以uuid形式存储的时候,那么传2个参数的时候就会超过而无法调用接口)

2、保存海报按钮,要检测用户是否已授权保存到手机相册,如果已授权,则显示保存海报按钮,如果未授权则显示授权并保存海报按钮

3、用户扫小程序码,解析码中scene参数,获取到先前塞入的参数,比如商品id,用户id,再调用商品详情接口渲染数据即可。

3、效果

4、实现

分享图片的components组件

index.wxml

代码语言:javascript复制
<v-mask>
  <view class="container">
    <view class="canvas-wrapper">
      <image class="icon" src="/images/icons/close.png" bindtap="onClose"></image>
      <image class="shareImage" src="{{tempFilePath}}"></image>
    </view>
    <view class="button-wrapper">
      <ly-button type="warning" i-class="custom-button" shape="circle" lang wx:if="{{saveImageAuth !== '1'}}" bindtap="saveImage" circle>保存海报</ly-button>
      <ly-button type="warning" i-class="custom-button" shape="circle" lang wx:else bindtap="bindOpenSetting" circle>授权并保存海报</ly-button>
    </view>
  </view>
</v-mask>
<view class="hideCanvas">
  <canvas canvas-id="shareCanvas" style="width: 750px;height: 1125px; zoom: {{unit}}"></canvas>
</view>

index.js

代码语言:javascript复制
Component({
  /**
   * 组件的属性列表
   */
  properties: {
    detail: Object
  },

  /**
   * 组件的初始数据
   */
  data: {
    unit: 1, // 比例
    saveImageAuth: '1', // 权限
    tempFilePath: '', // 临时图片地址
  },
  lifetimes: {
    attached() {
      // 检查授权权限
      this.checkSaveImageAuth();
      // //获取用户设备信息,屏幕宽度
      wx.getSystemInfo({
        success: res => {
          console.log('getSystemInfo', res)
          this.setData({
            unit: res.windowWidth / 750 * 0.8,
            ratio: res.pixelRatio
          })
        }
      })
      this.drawCanvas()
    }
  },
  /**
   * 组件的方法列表
   */
  methods: {
    // 封装的下载图片函数
    downLoadImage(url) {
      return new Promise((resolve, reject) => {
        wx.getImageInfo({
          src: url,
          success(res) {
            resolve(res.path)
          },
          fail(err) {
            reject(err)
          },
          complete() {
            console.log('complete')
          }
        })
      })
    },
    // 封装的下载云存储文件函数
    downLoadCloudFile(id) {
      return new Promise((resolve, reject) => {
        wx.cloud.downloadFile({
          fileID: id,
          success: res => {
            // 返回临时文件路径
            resolve(res.tempFilePath)
          },
          fail: err => {
            console.log('err', err)
          }
        })
      })
    },
    bindOpenSetting(e) {
      let _this = this
      wx.openSetting({
        success(res) {
          if (res.authSetting['scope.writePhotosAlbum']) {
            _this.saveImage()
          } else {
            wx.showToast({
              title: '请授权保存相册权限,才能为您生成分享图',
              icon: 'none'
            })
          }
        }
      })
      console.log('检查权限', e)
    },
    // 检查用户授权权限
    checkSaveImageAuth() {

      let _this = this
      wx.getSetting({
        success(res) {
          console.log('检查用户授权权限', res)
          let auth = ''
          // 有权限
          if (res.authSetting['scope.writePhotosAlbum'] === true) {
            console.log('2')
            auth = '2'
            // 无权限
          } else if (res.authSetting['scope.writePhotosAlbum'] === false) {
            console.log('1')
            auth = '1'
          } else {
            // 未设置
            console.log('0')
            auth = '0'
          }
          _this.setData({
            saveImageAuth: auth
          })
        }
      })
    },
    drawCanvas() {
      let detail = this.properties.detail;
      let mallType = detail.mallType;
      console.log('drawCanvas',detail);
      let ctx = wx.createCanvasContext('shareCanvas', this)
      let titleImage = this.downLoadImage('https://ysd-1300312604.cos.ap-shanghai.myzijiebao.com/goods/goods_editor/20201022/c7bda103dcc24ce688743ec824b3dea0.png');
      let productImage = this.downLoadImage(detail.productImage);
      let erCodeImage = this.downLoadImage(detail.erCodeImage);
      Promise.all([titleImage, productImage, erCodeImage]).then(imgs => {
        console.log('imgs', imgs)
        // 全部图片下载成功
        let bgWidth = 750;
        let bgHeight = 1.5 * 750;
        // 绘制白底背景
        ctx.setFillStyle('#fff')
        ctx.fillRect(0, 0, bgWidth, bgHeight)
        console.log('全部图片下载成功')
        // 绘制顶部标题图片
        let titleOffLeft = (750 - 652) / 2;
        let titleOffTop = 60;
        let titleWidth = 652;
        let titleHeight = 50;
        // todo 偏移量需要再处理一下
        ctx.drawImage(imgs[0], titleOffLeft, titleOffTop, titleWidth, titleHeight);
        // 绘制产品图片
        let productOffLeft = 30;
        let productOffTop = 155;
        let productWidth = 690;
        let productHeight = 380;
        ctx.drawImage(imgs[1], productOffLeft, productOffTop, productWidth, productHeight)
        // 绘制矩形边框
        ctx.lineWidth = 1;
        ctx.strokeStyle = '#ccc';
        ctx.rect(30, 156, 690, 670);
        ctx.stroke();
        // 绘制标题
        ctx.setFillStyle('#333') // 文字颜色

        ctx.font = `${32}px PingFang`; // 文字字号

        let title1 = detail.title;
        let title2 = '';
        if (title1.length > 40) {
          title2 = title1.substring(20, 39)   '...'
        } else if (title1.length > 20) {
          title2 = title1.substring(20)
        }
        title1 = title1.substring(0, 20)
        let textWidth = ctx.measureText(title1).width;
        let canvasWidthNoPadding = 750 - 56 * 2;
        ctx.fillText(title1, 56, 595)
        ctx.fillText(title2, 56, 630)
        // 绘制副标题
        ctx.setFillStyle('#ccc');
        ctx.font = `28px PingFang`;
        let subTitle = detail.subTitle;
        if (subTitle.length > 23) {
          subTitle = subTitle.substring(0, 22)   '...'
        }
        ctx.fillText(subTitle, 56, 695)
        // 绘制价格
        let priceIcon = '券后¥ ';
        let price = String(detail.price); // 价格
        let priceTail = '优惠券¥ ';
        if (mallType == 3) {
          priceIcon = '折后 ';
        }
        let couponAmount = String(detail.couponAmount);
        ctx.font = '28px PingFang';

        ctx.setFillStyle('#08B4DE');
        ctx.fillText(priceIcon, 56, 770 );
        let offsetPriceIcon = ctx.measureText(priceIcon).width;
        ctx.font = '36px PingFang';
        ctx.fillText(price, (56   offsetPriceIcon), 770 );

        if (detail.couponAmount != 0) {
          let offsetPrice = ctx.measureText(price).width;
          console.log('offsetPrice', offsetPrice)
          if (mallType !=3) {
            ctx.font = '28px PingFang';
            ctx.fillText(priceTail, (360   offsetPriceIcon   offsetPrice), 770 );
          }
          
          ctx.font = '36px PingFang';
          let offsetPriceTailIcon = ctx.measureText(priceTail).width;
          ctx.fillText(mallType !=3 ? couponAmount : couponAmount   ' 折', (330   offsetPriceIcon   offsetPrice   offsetPriceTailIcon), 770 );
        }
      
        let linePriceIcon = (mallType != 4 ? mallType != 1 ? mallType != 2 ? mallType != 3 ? '拼多多': '唯品会' :'苏宁': '京东': '淘宝')   '价 ';
        let linePrice = String(detail.linePrice); // 价格
        let saleNum = String(detail.saleNum)   '人已购';
        ctx.font = '28px PingFang';
        ctx.setFillStyle('#666');
        ctx.fillText(linePriceIcon, 56, 818 );
        let offsetLinePriceIcon = ctx.measureText(linePriceIcon).width;
        ctx.font = '36px PingFang';
        ctx.fillText(linePrice, (56   offsetLinePriceIcon), 818 );
        if (mallType != 3 && detail.saleNum != 0) {
          let offsetLinePrice = ctx.measureText(linePrice).width;
          ctx.font = '28px PingFang';
          ctx.fillText(saleNum, (330   offsetLinePriceIcon   offsetLinePrice), 818 );
        }
      
        // 绘制海报语信息
        let saleTitle = '先领券再购物,即领即用,让你立省到家';
        let saleName = detail.saleName;
        let salePhone = String(detail.salePhone);
        ctx.font = '24px PingFang';
        ctx.setFillStyle('#08B4DE');
        ctx.fillText(saleTitle, 30 , 930 );
        ctx.setFillStyle('#666');
        ctx.fillText(saleName, 30, 972 );
        let saleNameWidth = ctx.measureText(saleName).width;
        ctx.fillText(salePhone, 50   saleNameWidth, 972 );
        // 绘店铺信息
        ctx.setFillStyle('#08B4DE');
        let supplierName = `店铺名称:${detail.mallName}`;
        let supplierNameWidth = ctx.measureText(supplierName).width;
        ctx.fillText(supplierName, 50, 1036 );
        this.roundRect(ctx, 30, 1006 , (supplierNameWidth   40), 40, 20)
        // 绘制二维码
        let ercodeDesc = '长按识别小程序码领券';
        ctx.setFillStyle('#bbb');
        ctx.font = '22px PingFang';
        let ercodeDescWidth = ctx.measureText(ercodeDesc).width;
        let offsetErCode = (750 - 30) - ercodeDescWidth;
        ctx.fillText(ercodeDesc, offsetErCode, 1078);
        console.log('imgs', imgs[2])
        ctx.drawImage(imgs[2], 540 , 860, 165, 165)
        ctx.draw(true, () => {
          let _this = this;
          wx.canvasToTempFilePath({
            x: 0,
            y: 0,
            canvasId: 'shareCanvas',
            quality: 1.0,
            fileType: 'jpg',
            success(res) {
              console.log('生成海报成功')
              console.log('res', res)
              _this.setData({
                tempFilePath: res.tempFilePath
              })
            },
            fail(err) {
              console.log('err', err)
              wx.showToast({
                title: '海报生成失败',
                icon: 'none',
              })
              _this.triggerEvent('complete');
            },
            complete() {
              
            }
          }, _this)
        })
      }).catch(err => {
        console.log(err)
      })
    },
    onClose() {
      wx.hideLoading();
      this.triggerEvent('complete');
    },
    // 保存图片
    saveImage() {
      let _this = this;
      let tempFilePath = this.data.tempFilePath;
      if (!tempFilePath) {
        wx.showToast({
          title: '海报生成失败',
          icon: 'none',
        })
        return;
      }
      wx.saveImageToPhotosAlbum({
        filePath: tempFilePath,
        success() {
          wx.showToast({
            title: '已保存到相册,您可将海报分享到朋友圈',
            icon: 'none'
          })
          _this.triggerEvent('complete');
        },
        fail() {
          wx.showToast({
            title: '海报保存失败',
            icon: 'none',
          })
          _this.checkSaveImageAuth()
        },
        complete() {
        }
      })
    },
    // 绘制圆角矩形
    roundRect(ctx, x, y, w, h, r) {
      if (w < 2 * r) {
        r = w / 2;
      }
      if (h < 2 * r) {
        r = h / 2;
      }
      ctx.beginPath();
      ctx.setStrokeStyle('#ff7800');
      ctx.setFillStyle('transparent')
      ctx.setLineWidth(0.5);
      ctx.moveTo(x   r, y);
      ctx.arcTo(x   w, y, x   w, y   h, r);
      ctx.arcTo(x   w, y   h, x, y   h, r);
      ctx.arcTo(x, y   h, x, y, r);
      ctx.arcTo(x, y, x   w, y, r);
      ctx.stroke();
      ctx.closePath();
    },
  },

})

在需要海报的页面引入组件,比如要在detail模块引入,则需要在detail.json文件声明组件

代码语言:javascript复制
{
  "usingComponents": {
    "v-share-image": "/components/share-image/index"
  }
}

然后在页面detail.wxml引用即可

代码语言:javascript复制
<v-share-image detail="{{tempInfo}}" wx:if="{{showShareImage}}" bind:close="onCloseShareImage" bind:complete="onConfirmShareImage"></v-share-image>

上一篇:抖音开放平台用户授权获取用户的粉丝统计和短视频数据

0 人点赞