前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手把手教你撸一个小程序带太阳码的海报分享

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

原创
作者头像
悟空码字
修改2021-03-24 10:43:27
1.3K0
修改2021-03-24 10:43:27
举报

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.myqcloud.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>

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云开发 CloudBase
云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为200万+企业和开发者提供高可用、自动弹性扩缩的后端云服务,可用于云端一体化开发多种端应用(小程序、公众号、Web 应用等),避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档