小程序分享朋友圈、好友、图片分享

小程序分享的三种方式

小程序分享基本就三种:分享好友、小程序码图片分享以及朋友圈分享,日常开发比较常见的是分享好友;

由于第一次做小程序分享,网上又大多只有好友分享,或者只有零星的分享攻略,所以踩了n多坑后,自己整理一下巩固的同时能希望帮到其他人吧,毕竟百度上暂时还找不到这么全的攻略。
希望能帮助到大家

分享好友

1、唤起分享好友有两种方式,页面右上角...和属性openType="share"的Button组件。
2、两种唤起分享的方式都会触发页面的onShareAppMessage的方法,方法返回的参数中from字段返回表示分享的途径:按钮button或者非按钮(右上角分享)。

这里有点小技巧,如果通过按钮分享并且按钮是封装在子组件中,可以通过button的id属性传递一些数据给页面

1、按钮分享

<!-- 通过id可以传递参数 -->
<Button
   className="flex ali-item-center jus-content-center share"
   openType="share"
   id={props.shareInfo.shareParam || 'share'}
>
  <Text className="iconfont icon-wechat" />
</Button>

2、参数获取

// 参数获取
 onShareAppMessage(share) {
    console.error('from', share);
    let shareObj = {},
      target;
    if (share.from === 'button') {
      target = share.target.id; // 这个id获取到的就是上面button的id属性值,也就是 props.shareInfo.shareParam || 'share'
    } else {
      target = `classId=${this.state.classId}&dynamicId=${this.state.dynamicId}`;
    }
    shareObj = {
      title: default_share_Title,// 分享标题
      // desc: '分享描述',
      path: `/分享地址?${target}`,
      imageUrl: default_share_circle, // 分享封面图
    };
    shareObj = {
      ...shareObj,
      success: (res) => {
        console.error('分享成功', res);
      },
      fail: (error) => {
        console.error('分享失败', error);
      },
    };
    console.error('shareObj', shareObj);
    return shareObj;
  }

3、注意⚠️

需要注意的是,小程序首页底部栏的几个tab页面公用的是同一个页面也就是index页面,但我们通常是一个tab对应一个路由页面,所以底部栏所有的tab页面对应按钮分享和右上角分享,触发的都是index页面的onShareAppMessage。

小程序码分享

主要是先通过调用后端接口获取小程序码,然后用canvas绘图,保存绘制的图片最后分享图片。

1、获取小程序码

  • 后端调用小程序的api后返回文档流或者base64数据流,如果是返回文档流,在请求接口的时候需要把responseType设置为arraybuffer
let option = {
        isShowLoading: false,
        loadingText: '正在加载',
        url: isBaseUrl ? base + requestUrl : requestUrl,
        data: data,
        method: method,
        responseType: responseType || 'text', // 默认是text,如果请求文档流,设置为arraybuffer
        header: {
          'Content-Type': contentType,
          'Access-Control-Allow-Origin': '*',
          token: txtInfo.unionId
            ? txtInfo.unionId + '@@@' + Taro.getStorageSync('accountId')
            : '@@@',
        },

2、然后解析:如果是文档流,需要调用wx的api转base64

  /** 获取小程序码 */
  getMiniCode({scene, pagePath}) {
    return new Promise((resolve, reject) => {
      wx.showLoading({
        title: '生成图片中...',
      });
      const accountId = Taro.getStorageSync('accountId');
      $Request
        .getMiniCodeUnlimit({
          accountId,
          scene,
          page: pagePath,
        })
        .then((res) => {
          // 获取小程序码(图片流),转base64 ==> const qrcode = wx.arrayBufferToBase64(res);
          if (res.data && res.success) {
            const base64 = 'data:image/jpg;base64,' + res.data;
            resolve(base64);
          } else {
            setTimeout(() => {
              WxActions.fnShowToast(res.errorMsg);
            }, 300);
          }
          setTimeout(() => {
            wx.hideLoading();
          }, 300);
        })
        .catch((err) => {
          console.log('请求失败', err);
          setTimeout(() => {
            wx.hideLoading();
            WxActions.fnShowToast('获取小程序码失败');
          }, 300);
          reject(err);
        });
    });
  },

3、获取到对应到base64码后,绘制canvas,当然绘制之前还有很多信息要处理:

const {classId, dynamic} = this.props;
    WxActions.getMiniCode({
      scene: `dynamicId=${dynamic.id}&classId=${classId}&v=1`,
      pagePath: 'pages/schoolCircleDetail/schoolCircleDetail',
    }).then((base64) => {
      wx.showLoading({
        title: '生成图片中...',
      });
      const canvas: any = WxActions.getShareCanvas(
        {
          shareFrom: this.state.shareFrom,
          shareCode: base64,
          shareTitle: '你的好友邀请你一起参与',
          shareCover: this.state.coverImg,
        },
        'shareCanvas',
        this.$scope,
      );
      canvas
        .then((canvasSrc) => {
          setTimeout(() => {
            wx.hideLoading();
          }, 300);
          this.setState({
            isShowModal: true,
            canvasSrc: canvasSrc,
          });
        })
        .catch(() => {
          setTimeout(() => {
            wx.hideLoading();
          }, 300);
        });
    });

其中生成canvas图片逻辑如下:
(获取图片信息,由于后来后端同事换成二进制编码返回,所以要处理)

const fsm = wx.getFileSystemManager();
const FILE_BASE_NAME = 'tmp_base64src';

const base64src = function(base64data) {
  return new Promise((resolve, reject) => {
    const [format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64data) || [];
    if (!format) {
      reject(new Error('ERROR_BASE64SRC_PARSE'));
    }
    const filePath = `${wx.env.USER_DATA_PATH}/${FILE_BASE_NAME}.${format}`;
    const buffer = wx.base64ToArrayBuffer(bodyData);
    fsm.writeFile({
      filePath,
      data: buffer,
      encoding: 'binary',
      success() {
        resolve(filePath);
      },
      fail() {
        reject(new Error('ERROR_BASE64SRC_WRITE'));
      },
    });
  });
};

// 。。。。。。。

  /** 分享canvas */
  initTime: 1, // 获取图片信息可能会失败,所以失败后可以再次获取,最多获取canvasObj.maxTime或3次
  getShareCanvas(canvasObj, canvasId, otx) {
    WxActions.initTime = 1; // 每次点击分享,重置当前次数为默认值1.
    return new Promise((resolve, reject) => {
      WxActions.getCanvasInfo(canvasObj, canvasId, otx)
        .then((res) => {
          resolve(res);
        })
        .catch((err) => {
          reject(err);
        });
    });
  },

  /** 获取小程序码的imageinfo */
  async getCanvasInfo(canvasObj, canvasId, otx) {
    return new Promise((resolve, reject) => {
      let drawWidth;
      // 
      wx.getSystemInfo({
        success: async (sys) => {
          console.error('getSystemInfo', sys);
          const rapito = 0.71;
          const startX = 2,
            startY = 80;
          drawWidth = (await sys.screenWidth) * rapito;
          let drawHeight = (drawWidth * 4) / 5;
          console.error('drawWidth', drawWidth, drawHeight);
          drawHeight = drawHeight > 150 ? 150 : drawHeight;
          const src = await base64src(canvasObj.shareCode);
          wx.getImageInfo({
            src,
            success: (codeInfo) => {
              // console.error('1111', codeInfo);
              wx.getImageInfo({
                src: canvasObj.shareCover,
                success: (res) => {
                  console.error('图片信息', res);
                  let cutWidth = res.width,
                    cutHeight = (res.width * 4) / 5;
                  cutHeight = cutHeight > res.height ? res.height : cutHeight;
                  console.error('图片信息===', res.width, cutHeight);
                  WxActions.initTime = 1;
                  // console.error('获取封面图成功', res);
                  //res.path是网络图片的本地地址
                  const canvasCtx = wx.createCanvasContext(canvasId, otx);
                  // console.error('canvasCtx', canvasCtx);
                  
                  // canvas的绘制看自己情况定制
                  //绘制背景 ...
                  canvasCtx.fillStyle = '#fff';
                  canvasCtx.fillRect(0, 0, sys.screenWidth, sys.screenWidth);
                  //绘制分享的标题文字
                  canvasCtx.setFontSize(17);
                  canvasCtx.setFillStyle('#333');
                  canvasCtx.fillText(canvasObj.shareTitle, 15, 35);
                  //绘制分享的第二行标题文字
                  canvasCtx.setFontSize(13);
                  canvasCtx.setFillStyle('#999');
                  canvasCtx.fillText(`来自:${canvasObj.shareFrom}`, 15, 60);
                  //绘制图片
                  // drawImage(imageResource, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
                  // (图片地址, 绘制图片在画布的起始x,  绘制图片在画布的起始y, 截取图的宽度(原), 截取图的高度(原), 图片放置位置x, 图片放置位置y, 在canva上绘制的长度,在canvas上绘制的高度)
                  canvasCtx.drawImage(
                    res.path,
                    startX,
                    startY,
                    cutWidth,
                    cutHeight, //res.height,
                    startX,
                    startY,
                    drawWidth,
                    drawHeight,
                  );
                  //绘制小程序码
                  // canvasCtx.drawImage(canvasObj.shareCode, 15, 250, 60, 60);
                  canvasCtx.drawImage(codeInfo.path, 15, drawHeight + 90, 60, 60);
                  //绘制分享的说明
                  canvasCtx.setFontSize(13);
                  canvasCtx.setFillStyle('#333');
                  canvasCtx.fillText('长按小程序码看详情', 85, drawHeight + 130);
                  canvasCtx.stroke();
                  canvasCtx.draw(false, () => {
                    wx.canvasToTempFilePath(
                      {
                        canvasId: canvasId,
                        success: (ctx) => {
                          // console.error('save的canvas图片', ctx);
                          // 获得图片临时路径,用来保存到本地
                          return resolve(ctx.tempFilePath);
                        },
                        fail: (err) => {
                          //失败回调
                          console.error('save的canvas图片失败', err);
                          WxActions.fnShowToast('获取小程序码失败');
                          return reject();
                        },
                      },
                      otx, // 必须要有当前文档的实例
                    );
                  });
                },
                fail: (res) => {
                  //失败回调
                  console.error('获取封面图失败', res);
                  if (WxActions.initTime < 3) {
                    WxActions.initTime++;
                    // 获取失败后再次调用自身,重新获取,最多次数为canvasObj.maxTime或3次
                    WxActions.getCanvasInfo(canvasObj, canvasId, otx);
                  } else {
                    setTimeout(() => {
                      WxActions.fnShowToast('获取封面图失败');
                    }, 350);
                  }
                  return reject();
                },
              });
            },
            fail: (res) => {
              //失败回调
              console.error('2222', res);
            },
          });
        },
        fail: (err) => {
          return reject(err);
        },
      });
    });
  },


4、最后保存图片

 WxActions.fnWxSetting('scope.writePhotosAlbum').then((/** 授权 */) => {
      this.setState({
        canvasSrc: '',
        isShowShare: false,
      });
      WxActions.fnSaveImageToAlbum(this.state.canvasSrc).then(() => {
        this.onCancelModal();
      });
    });

// 。。。。。。。。


  /** 保存图片到本地相册 */
  fnSaveImageToAlbum(filePath) {
    return new Promise((resolve, reject) => {
      wx.saveImageToPhotosAlbum({
        filePath,
        success(yes) {
          console.log(yes.errMsg);
          WxActions.fnShowToast('保存图片成功');
          return resolve();
        },
        fail(error) {
          console.log(error.errMsg);
          WxActions.fnShowToast('保存图片失败');
          return reject();
        },
      });
    });
  },

到此,基本完成小程序码生成canvas并保存图片到手机相册到需求,由于时间比较紧,很多细节没处理好。

5、扫码进入的参数获取

接下来就是大坑了
在正常的小程序里面,扫码进入后获取参数是这样的

onLoad:function(options){
  if(options.scene){
    let scene=decodeURIComponent(options.scene);
    //&是我们定义的参数链接方式
    let userId=scene.split("&")[0];
    let recommendId=scene.split('&')[1];
    //其他逻辑处理。。。。。
  }
}

但由于本项目用的是taro,也就是用react编写,由于查找了网上的攻略,于是先入为主的一直想要获取scene参数,找到一片文章说在componentWillMount生命周期里获取

 componentWillMount(ops: any) {
    console.error('ops', ops);
    if (ops && ops.scene) {
      let shareOps = WxActions.getMiniCodeScene(ops.scene);
      this.setState({
        isScene: true,
        shareScene: shareOps,
      });
    }
  }

结果发现这是不对的!不对的!不对的!!!必须要在componentdidmount里面获取,并且要通过this.$router.params.scene获取才是正常的,所以如果正常的页面和分享后扫码进入的是同一个页面,就要区分参数的获取了。

componentDidMount() {
    if (this.$router.params.scene) {
      // 扫码进入
  }else{
    // 正常小程序页面进入
  }
}

朋友圈分享

分享朋友圈,微信暂时没有提供可以直接的分享方式,都是通过客服会话返回h5链接实现的,相当于曲线救国,下面是相关逻辑。

1、html代码,唤起客服会话

ps:h5地址域名必须是https的

<Button
  sendMessageTitle={shareInfo.shareTitle ? shareInfo.shareTitle : '分享朋友圈'} // 分享的客服会话标题
  sendMessageImg={shareInfo.shareImg ? shareInfo.shareImg : default_share_circle} // 分享的客服会话的封面图
  sendMessagePath={shareInfo.sharePath || default_share_CircleUrl} // 对应的需要分享到朋友圈的h5链接
  sessionFrom={'wkbbapp'} // 暂时发现没啥用
  openType="contact" // 必填,表示唤起客服会话
  showMessageCard // 必填,唤起会话右下角的截图弹出
  plain
  className="flex ali-item-center jus-content-center share"
  style={{border: 'none'}}
  onContact={(res) => {
    console.error('onContact', res);
  }}
  >
  <Text className="iconfont icon-quan" />
</Button>

2、后端推送会话卡片

前端只需要上面的button对应的代码,后端会获取到wx对应的推送,然后根据推送返回的内容,再由后端推送卡片消息到会话窗口,用户只要点击对应的窗口,就能进入对应的链接地址(其实就是一个h5地址,点击后会进入微信浏览器),然后点击右上角,分享朋友圈。

3、H5页面分享处理

显然,这已经是另外一个项目了。
这个项目要做的,就是平时公众号要做的事情了。

  • 首先是在html页面引入微信sdk
<script type="text/javascript" src="https://res2.wx.qq.com/open/js/jweixin-1.6.0.js"></script>

  • 然后是注册(封装)
//微信分享
export function wxShare(_option: {
  title: string; // 分享标题
  desc: string; // 分享描述
  link: string; // 分享链接
  imgUrl: string; // 分享图标
  success?: any;
  cancel?: any;
}) {
  configWxApi()
    .then((res: any) => {
      console.log('_option', _option);
      console.log(res);
      wx.config(res.data);
      wx.ready(function() {
        //分享给朋友
        wx.onMenuShareAppMessage(_option);
        // wx.updateAppMessageShareData(_option); //(1.4.0)
        //分享到朋友圈
        wx.onMenuShareTimeline(_option);
        // wx.updateTimelineShareData(_option); //(1.4.0)
      });
      wx.error((error: any) => {
        console.error(error);
        Toast.info(`${error.errMsg}`, 3);
        // config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
        // alert("接口验证失败,详细信息:\n" + JSON.stringify(error));
      });
      //  提示服务器异常暂时取反
    })
    .catch((err: any) => {
      console.log(err);
    });
}

  • 页面分享:
async componentDidMount() {
  wxShare({
    title: this.props.noticeDetail.title || default_circle_title, // 分享标题
    desc: default_share_desc, // 分享描述
    link: window.location.href, // 分享链接
    imgUrl: this.props.noticeDetail.coverUrl || default_share_notice, // 分享图标
  });
}

ps:注册微信分享失败的问题基本是签名错误获取域名不在合法域名列表,在此不再多描述了。

总结

开始公司没同事做过相关业务,网上资料也不全,前后端都摸着石头过河,走了很多弯路,所以把这整套流程走通很不容易,
也正因如此,完成后得到的成就感也是最大的。
最后,本文是原创,希望对你有所帮助~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352