1, the preface

As we all know, wechat small program does not support forwarding circle of friends at present, maybe now Android is supported, iOS is not supported, but in general, it does not support common models. So if we need to recommend some goods to the right friends can not be Shared out, so he can use to generate in the form of a poster, let the product details page information display on the picture, save to phone photo albums, and circle of friends, friends can grow according to identify small program code to the goods from the details of the poster page, To achieve the same effect as directly sharing the product details page.

2, train of thought

The reason why the user scan code can directly enter the page we specify is mainly to rely on the sun code on the poster, that is, the small program code, the information on other posters or beautiful style is purely to render the poster picture.

1. When the user clicks the button to generate the poster, we need to generate and return the small program code through the background interface. Remember to pass the parameters of the small program code to the background. (Here is a problem for you to consider, because the interface that generates the applet code scene field length is at most 32 bits, what if we need to pass more than 32 bits of parameter, especially if the primary key of the database is stored in the form of UUID, then we can pass more than two parameters and cannot call the interface)

2, save the poster button, to detect whether the user is authorized to save to the phone album, if authorized, display save the poster button, if not, display authorization and save the poster button

3. The user scans the small program code, parses the scene parameter in the code, obtains the previously inserted parameters, such as commodity ID and user ID, and then invokes the commodity details interface to render data.

Effect of 3,

4, implementation,

Share image components

index.wxml

<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 ! </ly-button> <ly-button type=" Warning "i-class="custom-button" shape="circle" Lang wx:else bindtap="bindOpenSetting" Circle > License and save poster </ly-button> </view> </view> </ V-mask > <view class="hideCanvas"> <canvas canvas-id="shareCanvas" style="width: 750px; height: 1125px; zoom: {{unit}}"></canvas> </view>Copy the code

index.js

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();
    },
  },

})
Copy the code

To introduce components on the page where the poster is needed, such as in the detail module, you declare the component in the detail.json file

{
  "usingComponents": {
    "v-share-image": "/components/share-image/index"
  }
}
Copy the code

Then reference it in the detail.wxml page

<v-share-image detail="{{tempInfo}}" wx:if="{{showShareImage}}" bind:close="onCloseShareImage" bind:complete="onConfirmShareImage"></v-share-image>
Copy the code

5, at the end of the article

I haven’t posted all the codes due to my limited space. If you need them, please feel free to contact me

A field, you can judge whether to pay attention to the public number