uni-app企业微信跳转指定页面如何处理?

499 阅读7分钟

记录一次实际项目中,uni-app对接企业微信跳转应用页面的处理方案

若想让企业微信的信息点击后,能跳转到原先做的APP中的某个页面,该怎么处理?


前言

  • 我上面提到的需求,在很多小型公司里应该挺常见的吧
  • 毕竟小公司首要目的是节约开支,包括开发人员上的开支,所以会选择uni-app开发移动端应用,希望一处开发多平台部署,比如即要跑在原生移动设备上、又要跑在微信之类的小程序上,然后有希望能够从企业微信之类的公众号通知信息中跳转到指定的h5页面上
  • 这篇文章就是针对“从企业微信之类的公众号通知信息中跳转到指定的h5页面上”这种需求的,由于原先的APP是通过从登录页,登录成功后获取token,然后让用户正常进入应用进行操作的,而直接跳转到指定页面这种需求,我们为了尽量避免麻烦,当然就选择把uni-app打包成h5项目直接部署到web上进行访问,然后由于是直接跳转到某个页面的,当然就需要解决token问题
  • 下面就开始展示处理方案了
  • 【本文仅是我的个人见解,若写的不好欢迎评论(特别是我的实际项目属于老项目,都没写Promise,都粗暴得使用了回调嵌套,当然你可以改用Promise,这样代码就优雅些)】

方案关键处展示(源码在文章最后)

  • 1 - http请求函数封装
    • 在 commmon/ 目录下分别为 App 和 H5 分开封装了两个 http 请求函数
    • 因为在 App 和 H5 两个环境下,token处理以及请求拦截的处理逻辑完全不同
      • (1)App环境下是正常登录逻辑,那么请求函数中主要包含的逻辑大概是:token由用户登录时进行存储,若无token时,拦截器需要重定向路由到登录页;
      • (2)H5环境下,需要实现企业微信通过url链接直接跳转到某个页面,那么请求函数中主要逻辑包含的是:进入页面时,必须先通过url中的code(企业微信提供用于获取用户信息的东东)向后端相应接口获取一次token和用户数据,前端存储起来,且若token若过期了,要为用户实现无感知的token刷新功能;

http请求函数封装

    • request.js
// request.js
import requestApp from './requestApp.js'
import requestWebWxwork from './requestWebWxwork.js'

// 若从企业微信跳转到指定页面,则一定会带上此参数,此参数需要用于获取token
export * from './weChatWorkHttp.js'

/**
 * 适用于一些需要多平台上展示的页面(目前仅支持 企业微信 和 本APP应用两种来源)
 * @param {*} options 
 * @returns 
 */
export default function request (options) {
    // #ifdef H5
    // --------------- 备注 -------------------
    // 这里我根据实际项目情况写的,我遇到的项目本身没有H5的需求,原先的代码中使用的都是 requestApp.js 中的请求函数,后来有了让部分页面需要直接从企业微信跳转链接的需求,
    // 那么我只需要强行判定凡是用这个新 request.js 请求函数的话,在H5环境下都一律使用 requestWebWxwork.js 请求函数即可
    // 至于平时开发在h5环境下调试的话,只需要转存用户登录的token,就能模拟出企业微信跳转到指定页的效果了
    // 若你的项目比较复杂,即本身就有h5环境下需求,那么推荐可以根据白名单进行判定,比如凡事记录在白名单中的路由,都使用 requestWebWxwork.js 请求函数,因为凡是有关链接跳转的路由,一定需要加入到白名单中的,不然就会走入登录流程,就会遇到被重定向到登录页的情况
    // ----------------------------------
    
    // 可暴力解决针对企业微信跳转过来时需要使用的http函数(凡是web都认为是用于企业微信,反正暂时也没有完整移动h5APP的需求,只需要部分页面满足企业微信能够直接跳转即可)
    // 对于平时开发在 web 上调试开发时,无视弹窗提示登录成功之后,把token转存一下即可:window.localStorage.setItem('wechartwork_token',window.localStorage.getItem('token'))
    return requestWebWxwork(options)
    // #endif

    // #ifdef APP-PLUS
    return requestApp(options)
    // #endif
}

  • requestWebWxwork.js
// requestWebWxwork.js
import { BASE_URL } from './requestConfig.js'
import { setWxworkUserInfo, getWxworkUserInfo, setWxworkToken, getWxworkToken } from './wxworkInfo.js'
import { getUrlParams, readRouteQuery } from './utils.js'

const RECALL_API_MAX = 10 // 重复发起请求最大次数
let recallApiCount = 0

export function getWxworkCodeFromUrl() {
    // 第一次从企业微信跳转过来必须带上此 redirect 参数,其中藏了code,且此code仅可使用一次,否则将无法获取token
    const routeQuery = readRouteQuery()
    // 若路由参数中没有code参数,再尝试找是否存在 redirect 参数,若有则从此参数中尝试获取 code来调用接口获取token,若还是没有则直接打印错误信息
    let wxworkCode = routeQuery.code
    if(!wxworkCode) {
        if(!!routeQuery.redirect) {
            const redirectJson = getUrlParams(decodeURIComponent(redirect))
            wxworkCode = redirectJson.code
        }
    }
    return wxworkCode
}

export function stroeWxworkToken({params={},callback=(_params)=>{}}) {
	let token = getWxworkToken()
	if(!!token) { // 若已存在token,则不再重复执行
		callback(params)
		return
	}
    // 第一次从企业微信跳转过来必须带上此 redirect 参数,其中藏了code,且此code仅可使用一次,否则将无法获取token
    const wxworkCode = getWxworkCodeFromUrl()
    if(!wxworkCode) {
        uni.showModal({
            title: '提示',
            content: 'URL中缺少code参数,无法获取token',
            showCancel: false
        })
        console.error('** 获取 token 发生异常: 【WxworkHttp】redirect 中缺少必要的 code 参数,无法获取token,目前收到的请求参数是 => ',params)
        return
    }
    uni.request({
        url: BASE_URL + '/wxworkSign/' + wxworkCode,
        method: 'POST',
        timeout: 60000,
        success:(res)=>{
            console.log('----- wxlogin res = ', res)
            if(res.data.code !== 200) {
                uni.showModal({
                    title: '提示',
                    content: res.data.msg,
                    showCancel: false
                })
                return
            }
            // 获取的用户信息存入,以便全局使用
            setWxworkUserInfo(res.data.user)
            // 存入token
            setWxworkToken(res.data.token)
            // 获取token后,回调新参数
            const newParams = {...params, token: res.data.token}
            // --
            callback(newParams)
        },
        fail: (_err) => {
            uni.showModal({
                title: '提示',
                content: '令牌获取出错',
                showCancel: false
            })
        }
    })
}

// 专用于企业微信跳转指定路由时使用的 http 请求
function WxworkHttp (params) {
	let url = BASE_URL + params.url;
	let method = params.method;
	let data = params.data || {};
	let header = { 'content-type': 'application/json' }
	let token = params.token || getWxworkToken()
	if(!!token) {
		header['Authorization'] = token
        // 重制重发请求次数
        recallApiCount = 0
	} else {
        // 控制尝试重复发起请求的次数,避免死循环
        if(recallApiCount > RECALL_API_MAX) return
        recallApiCount += 1

        // 第一次从企业微信跳转过来必须带上此 redirect 参数,其中藏了code,且此code仅可使用一次,否则将无法获取token
        stroeWxworkToken({
            params,
            callback:(newParams)=>{
                WxworkHttp(newParams)
            }
        })
        return
    }

	//	发起网络请求
	return uni.request({
		url: url,
		method: method || "GET",
		data: data,
		timeout: 60000,
		header,
		sslVerify: false, //	是否验证ssl证书
		success: res => {
            if (res.data.code == 401) { // 401 = token 过期,刷新接口后在重新发起请求
                // 控制尝试重复发起请求的次数,避免死循环
                if(recallApiCount > RECALL_API_MAX) return
                recallApiCount += 1
				// 刷新token的接口
                const userInfo = getWxworkUserInfo()
                uni.request({
                    url: BASE_URL.BASE_URL() + '/qyWxRefresh',
                    method: 'POST',
                    timeout: 60000,
                    data: {
                        wxUserId: userInfo.wxUserId,
                        uuid: '1'
                    },
                    success:(refreshRes)=>{
                        // 存入新token
                        setWxworkToken(refreshRes.data.token)
                        // 获取token后,再重新请求接口
                        const newParams = {...params, token: refreshRes.data.token}
                        WxworkHttp(newParams)
                    },
                    fail: (_err) => {
                        uni.showModal({
                            title: '提示',
                            content: '令牌更新出错',
                            showCancel: false
                        })
                    }
                })
				return;
			}
            // 重制重发请求次数
            recallApiCount = 0
			typeof params.success == "function" && params.success(res.data);
		},
		fail: err => {
            uni.showModal({
				title: '提示',
				content: '网络请求出错',
                showCancel: false
			})
			typeof params.fail == "function" && params.fail(err.data);
		},
		complete: (e) => {
			typeof params.complete == "function" && params.complete(e.data);
			return;
		}
	})
}

export default WxworkHttp

  • requestApp.js
import { BASE_URL } from './requestConfig.js'

module.exports = (params) => {
	let url = BASE_URL + params.url;
	let method = params.method;
	let header = params.header || {};
	let data = params.data || {};
	//	请求方式 GET POST
	//	发起网络请求
	uni.request({
		url: url,
		method: method || "GET",
		data: data,
		timeout: 60000,
		header: { 
			'content-type': 'application/json',
			'Authorization': uni.getStorageSync('token')
		},
		sslVerify: false, //	是否验证ssl证书
		success: res => {
			uni.hideLoading();
			if (res.data.code == 401) {
				uni.removeStorageSync('token')
				uni.removeStorageSync('user');
				uni.reLaunch({
					url: '/pages/login'
				});
				return;
			}
			if (res.data.status == 403) {
				uni.removeStorageSync('token')
				return;
			}
			if(res.data.code ==500){
				uni.showToast({
					title:res.data.msg,
					icon:'none'
				})

				uni.hideLoading();
				return
			}
			typeof params.success == "function" && params.success(res.data);
		},
		fail: err => {
			uni.showModal({
				title: '提示',
				content: '请求失败',
				success: (res) => {
					uni.removeStorageSync('token')
					uni.reLaunch({
						url: '/pages/login'
					})
				}
			})
			uni.hideLoading();
			typeof params.fail == "function" && params.fail(err.data);
		},
		complete: (e) => {
			typeof params.complete == "function" && params.complete(e.data);
			return;
		}
	})
}

  • main.js 中把请求封装到全局,组件中根据情况选用相对应的http请求函数
// main.js 关键代码如下

// #ifdef H5 || APP-PLUS
...其他代码

import Vue from 'vue'

// 组件中根据自身情况选用请求函数即可
import request from './common/request.js'
Vue.prototype.$request = request;
import requestWebWxwork from './common/requestWebWxwork.js'
Vue.prototype.$requestWebWxwork = requestWebWxwork;
import requestApp from './common/requestApp.js'
Vue.prototype.$requestApp = requestApp;

...其他代码
// #endif

...其他代码
  • 页面组件代码案例 pages/demoA
    • pages/demoA/wxwork.vue (针对企业微信通过url链接跳转到指定页的 组件代码 案例展示)
<template>
	<view class="content">
		<fullscreenLoading v-if="tokenLoading"></fullscreenLoading>
		<view v-else>
			<image class="logo" src="/static/logo.png"></image>
			<u-button type="primary">uview ui h5 组件使用测试</u-button>
			<view class="text-area">
				<text class="title">{{title}}</text>
			</view>
		</view>
	</view>
</template>

<script>
	export default {
		data() {
			return {
				// 企业微信直接跳转到该页面,需要优先检查并处理token问题,再挂载业务组件
				tokenLoading: true,
				title: 'Hello'
			}
		},
		onLoad(_options) {
			// 由于企业微信跳转的url带参数可能出现幺蛾子,所以这里通过自己写的路由解析函数来处理
			const routeQuery = this.$readRouteQuery()
			console.log('routeQuery = ', routeQuery)
			this.initWxworkToken()
		},
		methods: {
			initWxworkToken() {
				this.$stroeWxworkToken({
					callback:(_newParams)=>{
						// 这里自定义一份
						this.onComponentCreate()
					}
				})
			},
			onComponentCreate(){
				// 至于这个 this.tokenLoading 什么时候解除可以根据情况而定
				this.tokenLoading = false
			}
		}
	}
</script>

<style>
	.content {
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;
	}

	.logo {
		height: 200rpx;
		width: 200rpx;
		margin-top: 200rpx;
		margin-left: auto;
		margin-right: auto;
		margin-bottom: 50rpx;
	}

	.text-area {
		display: flex;
		justify-content: center;
	}

	.title {
		font-size: 36rpx;
		color: #8f8f94;
	}
</style>

源码链接在此!!!