流程方案
重定向静默授权
ios 使用 location.href 跳转返回空白页
内部页面跳转
1丶需使用Router跳转
this.$Router.push({ path: '/pages/test/test.vue`,params:{}})
2丶外部页面跳转解决ios白屏
window.history.pushState({},null,document.URL)
window.addEventListener('popstate',()=>{
WeixinJSBridge.call('closeWindow');
});
3丶使用iframe嵌套页面(最好的方法,但是不适合公众号文章和禁止iframe访问的页面)。
分享链接和打开链接不一致
用途嘛,懂的都懂
1丶配置router
//此时只要链接路径带有"testaliasPath "页面就会跳转到 "/pages/test/test.vue"这个页面
//pages.json和route都需要配置 pages/test/test.vue
//注意 router的页面path第一个字符为'/'
const aliasPath = "testaliasPath ";
{
"path": "/pages/test/test.vue", // "*"为页面路径或文件名
"aliasPath":*${aliasPath}*`//路由别名
}
2丶使用微信分享jssdk
注意:ios分享链接传到后台验证,需要使用刚打开单页面的初始链接,安卓直接使用localtion.href就行了
可以存在本地或者全局
uni.getSystemInfoSync().platform === 'ios'?this.$store.state.shareUrl:location.href
这个方法我封在utils里面
export function setPageConfig(pageConfig,success,fail) {
try{
const jsApiList = ['updateAppMessageShareData', 'updateTimelineShareData'];
if (!isMobile()) {
fail&&fail()
return;
}
const res = pageConfig; //后台接口返回的参数;
this.$wx.config({
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: res.appId, // 必填,公众号的唯一标识
timestamp: res.timestamp, // 必填,生成签名的时间戳
nonceStr: res.nonceStr, // 必填,生成签名的随机串
signature: res.signature, // 必填,签名
jsApiList // 必填,需要使用的JS接口列表
})
this.$wx.ready(res => {
success && success(res)
});
this.$wx.error(function(err) {
fail && fail(err)
});
}catch(err){
fail && fail(err)
}
}
这是一个随机字符串函数,方便随机分享路径
export const randomString = e => {
e = e || 32;
var t = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678",
a = t.length,
n = "";
for (let i = 0; i < e; i++) n += t.charAt(Math.floor(Math.random() * a));
return n
}
页面内部调用
setPageConfig.call(this,await wxGetJsConfig({ url }),()=>{
//config加载完毕执行
//好友分享
this.$wx.updateAppMessageShareData({
title: `【${this.info.title}】- ${this.info.share_memo}`, // 分享标题
desc: this.info.share_memo, // 分享描述
link: `${window.location.protocol}//${window.location.host}/'打包路径,为空则不写'/${randomString(3) + aliasPath + randomString(3)}?awaid=${this.info.id}`, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: encodeURI(this.info.share_img), // 分享图标
success: () => {
// 设置成功
}
});
//朋友圈
this.$wx.updateTimelineShareData({
title: `标题`, // 分享标题
link: `${window.location.protocol}/${window.location.host}/'打包路径,为空则不写'/${randomString(3) + aliasPath + randomString(3)}`, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: encodeURI(this.info.share_img), // 分享图标
success: () => {
// 设置成功
}
});
},()=>{
//失败执行
});
这样每个人的分享链接大部分都不一致
通用方法
获取经纬度
//获取当前经纬度 请不要重复调用存在全局状态
export const getLoc = () =>{
return new Promise(reslove=>{
let getLoc = () =>{
getLoc = null;
var geolocation = new BMap.Geolocation(); //获取位置定位
geolocation.getCurrentPosition(r => {
//r返回经纬度还有省市信息
console.log(r)
if(!r){
showToast('用户拒绝访问位置信息');
reslove(false)
}else{
reslove(r)
}
}, {
enableHighAccuracy: true
});
}
try{
if(!window.BMap_loadScriptTime){
window.BMap_loadScriptTime = (new Date).getTime();
let script = document.createElement('script');
script.src = 'https://api.map.baidu.com/getscript?v=3.0&ak=Ueqsa2Zlp0WeEWTM5jLRHnfPQYunzi33&services=&t=20200918142623'
script.onload = function(){
getLoc()
}
document.querySelector('head').appendChild(script)
}else{
getLoc()
}
}catch(e){
showToast('用访问位置信息失败');
reslove(false)
}
})
}
//防止xss 转义字符
export const refundXss = (str='') =>{
//防止script 和 a标签
return str.replace(/(<script)|(<a)/g,(item,$1,$2)=>{
if($1){
return '<script'
}
if($2){
return '<a'
}
})
}
加解密
npm i crypto-js
/**
* 订单号token的加解密
*/
export const OrderTokenhashObj = {
/**
* 加密
* @param {String} token
* @param {Number} id
*/
encrypt(token,id){
let str = token;
let tokenLength = str.length;
let idLength = id.toString().length;
let strEnd = randomString(1);
let idOffsetL = strEnd.charCodeAt() % tokenLength;
str = token.slice(0,idOffsetL) + id.toString() + token.slice(idOffsetL)
str += (idLength + strEnd);
const _cipherText = crypto.getAES(str);
let cipherText = _cipherText.slice(-1) + _cipherText.slice(0,-1);
console.log(`token:${token} id:${id},str:${str}`)
return cipherText
},
/**
* 解密
* @param {String} cipherText 密文
* @returns {Object}
*/
decipher(cipherText){
cipherText = cipherText.slice(1) + cipherText.slice(0,1);
console.log(cipherText)
let webCipher = crypto.getDAes(cipherText);
let idLength = Number(webCipher.substr(-2,1));
let tokenLength = webCipher.length - 2 - idLength;
let idOffsetL = webCipher.slice(-1).charCodeAt() % tokenLength;
let id = webCipher.substr(idOffsetL,idLength);
let token = webCipher.slice(0,idOffsetL) + webCipher.slice(idOffsetL+idLength,-2)
return {
id,
token
}
}
}
export const crypto = {
options() {
return {
key: CryptoJS.enc.Utf8.parse('shunliuliu168168'),
iv: CryptoJS.enc.Utf8.parse('shunliuliu168168')
}
},
//加密
getAES(data) {
const { key, iv } = this.options()
const encrypted = CryptoJS.AES.encrypt(data, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
})
return encrypted.toString()
},
//解密
getDAes(encrypted) {
const { key, iv } = this.options()
const decrypted = CryptoJS.AES.decrypt(encrypted, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
})
return decrypted.toString(CryptoJS.enc.Utf8)
}
}
调起微信支付
//调起微信支付
/**
* https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6
*/
/**
* @param {String} param0
* @param {String} param0.appId //公众号id
* @param {*} param0.timeStamp //时间戳,自1970年以来的秒数
* @param {*} param0.nonceStr //随机串
* @param {*} param0.package //订单详情扩展字符串
* @param {*} param0.signType //微信签名方式:
* @param {*} param0.paySign //微信签名
*/
export const payMoney = param0 => new Promise(resolve => {
let {appId, timestamp, nonceStr, signType, paySign} = param0;
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
appId, //公众号名称,由商户传入
timeStamp:timestamp, //时间戳,自1970年以来的秒数
nonceStr, //随机串
package:param0.package, //订单详情扩展字符串
signType, //微信签名方式:
paySign //微信签名
},
function (res) {
if (res.err_msg == "get_brand_wcpay_request:ok") {
resolve(1) //支付成功
} else {
resolve(0); //支付失败
}
});
});
速度优化
由于做的静默授权需要重定向,判断本地存储或者cookie没有token,就重定向到微信。
我们不应该uniap页面逻辑js代码里面调用,不然会加载更多的dom节点和其它js逻辑代码,从而新用户的加载速度。
解决方案
建立一个template.h5.html文件,(这个可以到官方去搜索怎么配置)
然后在DOM最顶部放入下面内容
//注意这里不支持es6的 `` 字符串
<script type="text/javascript">
//获取参数
var token,domain,pathname,reg,search,param,url,hascode;
hascode = window.location.search.indexOf('code=') != -1;
// localStorage.removeItem('userToken')
token = localStorage.getItem("userToken");
if(!token && !hascode){
//解决ios白屏
window.history.pushState({},null,document.URL)
window.addEventListener('popstate',()=>{
WeixinJSBridge.call('closeWindow');
});
//获取重定向参数
domain = window.location.protocol + "//" + window.location.host;
pathname = window.location.pathname.substring(4);
reg = /[&]{0,1}code\=[a-zA-Z_0-9\&\=]+/g;
search = window.location.search.replace(reg,"");
param = encodeURIComponent(domain + '/hsh' + (pathname || 'index/index') + (search === '?' ? "" : search));
url = domain + "/api/wx/index?url=" + param;
//重定向
window.location.replace(url);
}
</script>
为什么我要放到DOM的顶部。网上也有人说脚本放在body里面最好,其实也有一定的道理,但是呢,要符合业务场景,做优化的时候,我们也可以舍弃一些规则,网站打开速度快才是关键!
知识点是死的,运用起来是活的,很多知识点我们都是知道的,如何合理的利用到项目中才时关键。
优化点
了解dom渲染机制
回流-绘画-复合。
1丶多使用this.$nextTick(()=>{})防止布局抖动,利用缓存
2丶动画尽量使用opacity和tansform,利用复合完成动画
3丶资源图片压缩。利用npm开源包
4丶资源整合(串行改为并行)。雪碧图,单文件,内容较少的几个文件可以合并成一个文件
5丶节流和防抖,长列表优化。
6丶利用canvas前端压缩图片上传
7丶DOM批量读写 fastDom
8丶异步加载组件和懒加载图片
9丶经常做动画的组件单独放一个层级。
10丶节点先display:none,在改变dom属性,减少绘画和回流
11丶减少dom节点嵌套,减少css选择器嵌套
其他实现:
实现了多个公众号一个收款公众号,利用加解密解决url参数传递过长的问题
Api通用封装
async function awaitToken() {
return new Promise(resolve=>{
let i = 0;
let newT = new Date().getTime();
let key = 'awaitTokenTime'+newT;
if(window[key])clearInterval(window[key]);
window[key] = setInterval(()=>{
if(getToken()){
resolve(1);//获取token成功
clearInterval(window[key]);
window[key] = null
}
if(i>20){//10秒未获取到token,超时
resolve(0);//获取token失败
clearInterval(window[key])
window[key] = null
}
i++;
},500)
})
}
function hanldfn(method, {
url,
data,
isAuth = false,
header = {}
}) {
return new Promise(async (resolve, reject) => {
let success = ({data}) => {
try{
data = JSON.parse(data);
}catch(e){}
if (data.code == 200) {
resolve(data);
} else {
let e = '网络错误';
if(data.code == -1 || data.code == -2){
if(isAuth){
return tokenExpired();
}
alert("未设置token")
}
if(data.msg){
e = data.msg;
}else if (String(data) == data) {
e = data;
} else {
try {
e = JSON.stringify(data);
} catch (e) {}
}
success = null;//清除闭包
reject(e?e.slice(0,255):'网络错误');
}
};
let fail = err => {
let e = "网络错误";
console.log(err)
if (err && (err.msg || err.errMsg)) {
e = err.msg || err.errMsg;
} else if (String(err) == err) {
e = err;
} else {
try {
e = JSON.stringify(err);
} catch (e) {}
}
fail = null;//清除闭包
reject(e)
};
//验证权限是否足够
if (isAuth) {
header.token = header.token || getToken();
if(!header.token){
//等待获取token请求完毕
let res = await awaitToken();
if(!res){
return reject('获取token超时');
}
//延时获取token
header.token = getToken();
}
}
const requestUrl = (window.$store().state.apiUrl || apiUrl)+url;
console.log(requestUrl)
if(method == 'PUT'){
uni.uploadFile({
url:requestUrl,
file: data.fileList[0],
fail,
success
});
}else{
uni.request({
url:requestUrl,
data,
method,
header,
fail,
success
})
}
});
}
/**
* 验证请求权限是否足够
*/
function getToken() {
return window.$store().state.token;
}
var api = {
get(url, data, isAuth,header) {
return hanldfn("GET", {
url,
data,
isAuth,
header
});
},
post(url, data, isAuth, header) {
return hanldfn("POST", {
url,
data,
isAuth,
header
});
},
//文件上传
put(url, data, isAuth, header) {
return hanldfn("PUT", {
url,
data,
isAuth,
header
});
},
}