微信小程序 onShareAppMessage 分享 转发等场景 uni-app封装,解决打开或者跳转页面参数混乱的问题

1,657 阅读7分钟

环境说明

  • 基于uni-app+vue2

文档链接

进入应用页面场景

  • 正常通过微信搜索进入(只会进入首页)(参数长度限制2M)
  • 通过微信聊天的卡片进入(任何页面)(参数长度限制2M)
  • 通过微信小程序码进入(任何页面)(参数长度限制128字符)
  • 通过普通方形二维码扫码进入(任何页面)(原则上是不限制大小,但是数据多会造成生成的二维码特别密,难以识别等问题。保证传递数据越少越好)
  • 通过其他小程序半屏/全屏打开进入(任何页面)(通过referrerInfo.extraData没测试出大小限制)
  • 微信朋友圈快照进入(任何页面)(参数长度限制2M)
  • 小程序冷启动或者热启动(任何页面)

来源:微信小程序官网.png

封装前结构设计

根据多场景,多种参数,多情况的前提条件进行如下设计

    1. 使用最少的属性,最小的数据解决参数问题,最多限制128字符
    1. 能应对多种进入应用场景
    1. 根据页面或者场景不同情况,能自定义参数,完成不同功能设计
    1. 使用简单,引用方法后简单配置便可
    1. 能解决开发时新增的页面,该页面没有发布过的场景时,页面分享或者扫码提示没有该页面的问题
    1. 分享记录,能记录每次的分享参数
    1. 针对出去的二维码或者卡片链接能做版本管理
    1. 避免二维码长时间对外使用后,项目中页面已经删除的场景
    1. 我还没想到,想到再加

设计架构图

泳道图

分享泳道图.png

流程图

分享流程图.png

关键代码设计

文件结构

简易工程结构
├── api -- 混入文件
├    └── common.js -- 分享接口
├── pages -- 小程序页面 - 主包
├    ├── goods -- 商品详情
├    ├── home -- 首页
├    └── user -- 个人信息
├── mixins -- 混入文件
├    └── share.js -- 分享功能封装
├── utils -- 常用工具方法js
├    ├── request.js -- 接口地址及接口调用
├    └── util.js -- 一般常用方法
├── app.css -- 项目自定义css
├── App.vue -- 项目入口:包括App自定义数据,更新,全局样式设置,IM等。
├── main.js -- 项目入口:全局组件引入,路由跳转等。
├── manifest.json -- UniApp配置文件
├── package.json -- npm安装的第三方库
├── pages.json -- UniApp页面设置和路由等
└── vue.config.js 

api封装 【api/common.js】

import {request} from '@/utils/request.js'

/**
 * 存储参数映射关系
 * 分享转发参数映射管理
 * data: {
 *     appId: 小程序appid
 *     shareMapType: 分享场景 1: 微信卡片消息,2:微信小程序码,3:普通方形二维码,4:短链 5:小程序间跳转 6:...
 *     serviceType: 业务场景 1:分销员拉新,2:渠道拉新,3:机构拉新  ...
 *     version: 版本
 *     content: 前端需要使用的封装字段 pageShareDataFun方法返回的字段 和后端约定好 这个字段会很长
 *     ...
 * }
 */
saveParamShare: (data) => {
      return request('/memberapp/share-map/save-param-map', 'post', data, false)
},

/**
 * 查询参数映射关系
 * 分享转发参数映射管理
 * shareId: 分享id
 */
queryParamShare: (shareId) => {
    return request('/memberapp/share-map/query-param-map', 'get', shareId, false)
},

/**
 * 可以将shareId返回给后端做一些后续业务逻辑操作
 */
shareRelation: (data) => {
    return request('/mallapi/hs-distribution-user/shareRelation', 'get', data, false)
},

request封装 【utils/request.js】

export const request = (...arges) => {
    const {
        url,
        method,
        data,
        showLoading = true,
        showFailMessage = true,
        requestId,
        isGoLogin = true,
        headerObject
    } = processParameters(arges);
    let _url = url;
    //#ifndef H5
    _url = url.toLowerCase().includes('https://')? url: __config.basePath + url
    //#endif
    return new Promise((resolve, reject) => {
        if (showLoading) {
            uni.showLoading({
                title: '加载中'
            });
        }
        let headerConfig = {
           // header参数 看自己的项目
        }
        if (headerObject) {
            headerConfig = Object.assign(headerConfig, headerObject)
        }
        let requestTask = uni.request({
            url: _url,
            method: method,
            data: data,
            withCredentials: true,
            header: headerConfig,
            success(res) {
                errCount = 0
                const severCode = [404, 500, 502, 503]
                const severMsg = {
                    '404': '服务出错,请稍后再试',
                    '500': '服务出错,请稍后再试',
                    '502': '服务器维护中,请稍后再来',
                    '503': '503错误,服务未启动',
                }
                if (res.statusCode == 200) {
                    resolve(res.data);
                } else {
                     uni.showModal({
                        title: '提示',
                        content: '错误:' + severMsg[res.statusCode],
                        success(res) {}
                    });
                    reject();
                }
            },
            fail(error) {
                uni.showModal({
                    title: '温馨提示',
                    content: '服务异常,稍后再试'
                });
            },
            complete(res) {
                if (showLoading) {
                    uni.hideLoading();
                }
            }
        });
    });
};

分享功能封装 【mixins/share.js】

import request from 'utils/request'
import common from '@/api/common.js'
const util = require("utils/util.js");
const app = getApp();

export default {
    data() {
        return {
            mixinsShareData: {  // 命名的时候千万别和页面中的data中重复
                title: config.wxName || '默认的卡片分享标题',
                path: 'pages/home/index',  // 默认的分享页面落地页,我这觉得首页会简单一些,这里可以单独写一个分享落地页面
                imageUrl: this.staticPath + '/image/logo-share.png' // 默认的卡片分享时的图片地址
                type: 0, // 转发形式(0 - 微信小程序正式版 ;1 - 微信小程序开发版;2 - 微信小程序体验版;京东App9.0.0开始不填或者其他值都会先判断是否有url参数,如果有打开分享后显示url对应页面,否则默认生成京东小程序官方的一个分享中间页面,点击可跳到京东app里面的对应小程序。)
                shareMapType: 1, // 分享场景 1: 微信卡片消息,2:微信小程序码,3:普通方形二维码,4:短链 5:小程序间跳转 6:...
                serviceType: 0, // 业务场景 1:分销员拉新,2:渠道拉新,3:机构拉新  ...
            }
        }
    },
    onShareAppMessage() {
        let params = this.shareMassage() 
        return {
            params
        }
    },
    onShareTimeline() {
        let params = this.shareMassage() 
        return {
           
    },
    
    methods: {
        // shareID 参数格式化
        // 合并分享的数据(有三种,1.当前mixins中data默认的mixinsShareData,2.该方法内一些的默认参数, 3.页面内配置的自定义的参数)
        pageShareDataFun() {
            let val = this.pageShareData  // 来自于页面的自定义配置的分享内容
            let dataJson = Object.assign(
                {
                    url: this.mixinsShareData.path, // 二次跳转地址
                    userCode: uni.getStorageSync('user_info').userCode, // 当前分享人的邀请码
                    userPhone: uni.getStorageSync('user_info').phone,// 当前分享人的手机号
                    userName: uni.getStorageSync('user_info').nickName, // 当前分享人的用户名(昵称)
                    userHeaderImg: uni.getStorageSync('user_info').headimgUrl, // 当前分享人的用户头像
                    ...  // 这里可以放很多东西
                },
                this.pageShareData // 来自于页面的自定义配置的分享内容,会更新上方默认的,这里边可能会有商品id,文章id,专题id,活动id等等
            )
            return dataJson
        },
        
        
        // 请求shareId,组装分享对象
        shareMassage() {
            let that = this
            return promise = new Promise((resolve, reject) => {
                let dataJson = that.pageShareDataFun()
                let val = that.pageShareData // 页面中的自定义配置项
                common.saveParamShare({
                    appId: 'wx...........',
                    content: that.pageShareDataFun(),
                    shareMapType: (val ? val.shareMapType : false) || that.mixinsShareData.shareMapType,
                    serviceType: (val ? val.serviceType : false) || that.mixinsShareData.serviceType,
                }).then(res => {
                    let shareId = res.data.shareId
                    resolve({
                        title: dataJson.title,
                        path: ((val ? val.url : false) || dataJson.url)+ '?shareId=' + shareId,
                        summary: (val ? val.summary : false) || dataJson.summary,
                        imageUrl: (val ? val.imageUrl : false) || dataJson.imageUrl,
                        content: (val ? val.content : false) || dataJson.content,
                        success: function(res) {
                            if (res.errMsg == 'shareAppMessage:ok') {
                                    console.log(res.errMsg);
                            }
                        },
                        fail: function(res) { // 转发失败
                        }
                    })
                }).catch(ero => {
                    reject({})
                })
            )}
        },
        
        /**
         * 将返回的shreId回传,做相关业务处理
         * @param {number} id 分享的数据id
         */
        getshareRelation(shareId) {
            // 判断是否登录  不加此判断会影响新用户和退出登录用户点击分享卡片进入相关页面的逻辑  
            console.log('判断是否登录:', uni.getStorageSync('user_info').id);
            if (!uni.getStorageSync('user_info').id) {
                return false
            }
            if (shareId) {
                common.shareRelation({
                        shareId
                }).then(res => {
                }).catch(ero => {})
            }
        },
        
        /**
         * 查询参数映射关系
         * @param {number} param 获取生命周期返回的参数
         */
        async queryShareId(param) {
            let shareId = param.query.scene || param.query.q || param.query.shareId // 场景不同 返回的就不同
            if (shareId === 'undefined') {
                return
            }
            if (shareId) {
                await common.queryParamShare({
                    shareId: shareId
                }).then(async res => {
                    console.log('返回的分享数据', res);
                    // 可以在此处做业务逻辑 版本管理等
                    let content = res.data.content // 获取前端封存的全部数据
                    this.shareJumpPage(content)
                })
            }
        },
        
        //页面跳转
        shareJumpPage(data) {
            if (data.sku) {
                // 如果有sku代表分享的商品
                uni.reLaunch({
                    url: `/${data.page}?id=${data.sku}&已有的其他参数拼接`
                });
            } else if (data.broadcast) {
                // 分享的直播间
                uni.reLaunch({
                    url: `/${data.page}?id=${data.broadcast}`
                });
            } else {
                // 其他页面  正常应该都是这样,但是仅适用于项目刚开始开发,不然好多页面都得调整
                uni.reLaunch({
                    url: `/${data.page}?data=${encodeURIComponent(data)}`
                });
            }
        },
        
    }
}

应用生命周期 【app.vue】

import share from '@/mixins/share';

export default {
    mixins: [share],
    
    
    onShow(option) {
        // 打开应用的时候直接将传递的参数丢入封装的share.js
        this.queryShareId(option)
    },
}

商详页面分享 【pages/goods】

简单的使用方式,只需要将mixins/share引入,配置好页面自定义pageShareData便可

<template>
    <button
        class="img-box"
        style="background: none;padding: 0;margin: 0;border:none;"
        open-type="share"
    >分享按钮</button>
</template>

<script>
    import share from '@/mixins/share';

    export default {
        mixins: [share],


        data() {
            return {
                pageShareData: {
                    title: '快来来看看这个商品,有惊喜!',
                    path: 'pages/goods/index',
                    activity: '',
                    sku: '3245612347856'
                    imageUrl: this.staticPath + '/2-0/static/turntable-share.png',
                },
            }
        },
    }

</script>

其他说明

  • 上述代码大部分是缩减版,删除了很多逻辑处理的代码,提供出来的也需要谨慎复制使用,如果存在未覆盖到的场景,欢迎留言讨论
  • 关于获取shareId本文是放在onShow的生命周期钩子中执行,因为我们有半屏打开其他小程序的逻辑,发现关闭半屏的时候onShow中就没有参数反返回,解决办法是在onLaunch中存储一份,在onShow判断(option.scene == 1038)再走相关逻辑
  • 可以考虑将该分享数据存入本地存储,或者挂载到vue上