前言
前段时间公司要开发个小程序的项目,于是开始了自己的小程序之旅,一切从头看文档开始,边看边做,遇到了不少坑,因为笔者一直习惯使用vue,于是就使用了mpvue框架开发;我会从构建项目开始以及开发中遇到的问题全部讲述一遍,希望能够对你有帮助,或许你觉得简单,但再简单也是要从零走过来;在开发中有好几个问题要注意的:
- 小程序主包不能超过2M问题;
- 单位使用问题;
- 页面缓存问题;
- 图片路径问题;
- 有两个网站会你会经常用到:
开发平台:mp.weixin.qq.com/
开发文档:developers.weixin.qq.com/miniprogram…
小问题就更多了,接下来会一一罗列,虽然有点啰嗦,但只要看下去,基本可以解决遇到的坑;
1、开发前准备
- 首先要到微信开发平台注册账号,这步很简单,网址:mp.weixin.qq.com/
登录进去后可以到处逛逛,然后开发时基本要用到的是appid,在左边的开发中点进去,在开发设置中可以取到appid;具体其它的就要自己看看其它配置了;

- 下载微信小程序开发者工具,这个要到小程序开发文档官网上可以下载;
网址:developers.weixin.qq.com/miniprogram…
- 开发者工具就像谷歌浏览器一样,模拟手机,还有可以方便我们的调试等等 ;
2、构建项目
- 构建项目具体根据mpvue的官网来就可以了,网址:mpvue.com/mpvue/#_1
- 里面人家写得清清楚楚,这里就不多讲了,如下:
# 全局安装 vue-cli
$ npm install --global vue-cli
# 创建一个基于 mpvue-quickstart 模板的新项目
$ vue init mpvue/mpvue-quickstart my-project
# 安装依赖
$ cd my-project
$ npm install
# 启动构建
$ npm run dev
- 在npm install 及 npm start 后,会启动一个项目出来,然后可以在项目的文件夹中看到有个dist文件,那个就是webpack打包出来的小程序文件,开发工具只要引入它就可以打开项目了:


3、分包策略
- 先解释一下什么叫分包?
一开始我也感觉到很陌生,后来查了好多资料,资料上说小程序为了保证它的流畅性,就把主包的大小控制在2M,超过了2M就发布不上去,连预览都不可以;
主包包括 :- 在pages里面的页面
这三个就是主包中的页面,subPackages是分包,分包接下来会讲;为什么要将这三个页面放到主包中呢?因为我用了小程序默认的底部tabbar,小程序要求tabbar上的页面必须要在主包中,所以我也没办法;当然tabbar是可以自己写一个的,我没有自定义,就用它的可以了;
2. static里面的所有静态资源,所以最好不要将图片跟体积大的插件放进来,不然后面难于开发;
- 在pages里面的页面
- 知道了主包,这下就可以理解分包了,我们会有很多页面要开发,不可能不超过2M的,所以我们可以将其它页面写在分包中,分包可以有非常多个,一个分包的大小也是不可以超过2M的:

- 分包的创建:mpvue创建分包是非常简单的,步骤如下:
1. 在pages文件夹下创建一个文件夹,名字叫subPackages(文件名可以随意配); 2. 然后将页面写在里面就可以了: 3. 在app.json中写上刚才添加的方件路径就可以了:

4、tabbar的配置
小程序底部的tabbar还是很好用的,方法很简单,在app.json中,如下:
- 将页面放在主包中(最少要有两个页面)
- 将页面路径配在pages 中;
- 将页面配置在tabbar里的list中

- 具体细节看文: developers.weixin.qq.com/miniprogram…
5、配置less
小程序创建出来的裸版是不带less、sass的,需要自己去配置:
- 先安装:npm install less less-loader --save-dev
- 配置 : 在webpack.base.conf.js中配置

6、页面的跳转及参数的获取
-
小程序的页面跳转开发文档上已经说的很详细了,网址:
developers.weixin.qq.com/miniprogram… -
在此,有几个要注意的:
(1). 如果是小程序自带的tabbar上的页面,在任何其它页面跳转到tabbar 的其中一个页面时,要使用wx.switchTab({url : '/pages/xxx/main'})(2). 关闭其它曾经打开的页面,打开新的页面,也就是没有返回按钮的,使用
wx.reLaunch({url : '/pages/subPackages/xxx/main'})(3).普通页面的跳转,可以使用 wx.navigateTo({url: ' '})
-
页面参数的传递以及获取
(1). 页面参数的传递,我们可以通过地址栏带过去,很简单:wx.switchTab({url : '/pages/xxx/main?id=123&key=456'}) wx.switchTab({url : `/pages/xxx/main?id=${this.id}&key=${this.key}`})(2). 页面参数的获取:
mpvue的文档有也有说,网址:mpvue.com/mpvue/#_3可以在onLoad钩子以后的其它钩子中获取,参数就在options里,如果没有参数,取不到时为undefined:
wx.onLoad(options){ console.log( options )} wx.onShow(options){ console.log( options )}如果要在其它方法中获取,也可以使用方法:
this.$root.$mp.query
7、单位rpx
在小程序中,我们可以直接使用rpx作为单位,建议设计稿也是按照iphone6的尺寸来设计,因为rpx是按照 750px iphone6上的像素来设计的,文档上也有说明,网址:
developers.weixin.qq.com/miniprogram…
8、单位PX自适应换算
-
有时我们会使用到第三方的库,可能它的大小不能使用rpx,这时可能就要我们自己去换算 px 自适应了,比如echarts,它就是要你传入单位为px的,我就遇到了这个问题;
-
换算公式:
自适应尺寸 / 设备屏幕宽度 = 设计稿上元素宽度 / 设计稿宽度
也就是: 自适应尺寸 = 设计稿上元素宽度 / 设计稿宽度 * 设备屏幕宽度 -
例如:获取宽度
const echartWidth = 700 / 750 * wx.getSystemInfoSync().windowWidth; const echartHeight = 540 / 750 * wx.getSystemInfoSync().windowWidth;其中,700与540是设计稿上量出来的尺寸; 750是设计稿的宽度; wx.getSystemInfoSync().windowWidth 这个方法是获取屏幕宽度;
结果就是宽度跟高度会根据机型的不同会自适应,单位是px;
9、请求方法的封装
- 小程序已经封装好的请求的api :wx.request()
网址: developers.weixin.qq.com/miniprogram… 但我们还是要对它进行二次封装,因为根据各项目的业务需求不同,要添加请求头上的信息等等,肯定是只封装一次,处处调用; - 以下是基本的封装方法,具体还是要根据后端要求的请求头配置参数来配:
export default function request({
url,
data = '',
header = { 'Content-Type': 'application/json' },
method = 'GET',
dataType = 'json',
responseType = 'text',
}) {
wx.request({
url: `${baseUrl}${url}`,
method,
data,
header: header,
dataType,
responseType,
success: res => {
// 对响应进行统一处理,具体规则要与后端协调
const responseData = res.data;
if ([200].indexOf(res.statusCode) !== -1) {
response.success = responseData;
} else {
response.fail = res;
}
},
fail: error => {
response.fail = error;
},
complete() {
resolve(response.success);
},
});
}
- 接下来可以在单独文件夹里引入将所有的请求都写在那个文件里,方便管理; 如:
import request from './request';
export function getList() {
return request({
url: '/index/hotcity',
});
}
- 在页面中调用 :
import { getList} from './api'
getList().then(res => {}).catch(err => {})
10、MD5加密库
- 这个MD5加密的库也许你会用得上,可以直接复制过去用:
var rotateLeft = function(lValue, iShiftBits) {
return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));
};
var addUnsigned = function(lX, lY) {
var lX4, lY4, lX8, lY8, lResult;
lX8 = lX & 0x80000000;
lY8 = lY & 0x80000000;
lX4 = lX & 0x40000000;
lY4 = lY & 0x40000000;
lResult = (lX & 0x3fffffff) + (lY & 0x3fffffff);
if (lX4 & lY4) return lResult ^ 0x80000000 ^ lX8 ^ lY8;
if (lX4 | lY4) {
if (lResult & 0x40000000) return lResult ^ 0xc0000000 ^ lX8 ^ lY8;
else return lResult ^ 0x40000000 ^ lX8 ^ lY8;
} else {
return lResult ^ lX8 ^ lY8;
}
};
var F = function(x, y, z) {
return (x & y) | (~x & z);
};
var G = function(x, y, z) {
return (x & z) | (y & ~z);
};
var H = function(x, y, z) {
return x ^ y ^ z;
};
var I = function(x, y, z) {
return y ^ (x | ~z);
};
var FF = function(a, b, c, d, x, s, ac) {
a = addUnsigned(a, addUnsigned(addUnsigned(F(b, c, d), x), ac));
return addUnsigned(rotateLeft(a, s), b);
};
var GG = function(a, b, c, d, x, s, ac) {
a = addUnsigned(a, addUnsigned(addUnsigned(G(b, c, d), x), ac));
return addUnsigned(rotateLeft(a, s), b);
};
var HH = function(a, b, c, d, x, s, ac) {
a = addUnsigned(a, addUnsigned(addUnsigned(H(b, c, d), x), ac));
return addUnsigned(rotateLeft(a, s), b);
};
var II = function(a, b, c, d, x, s, ac) {
a = addUnsigned(a, addUnsigned(addUnsigned(I(b, c, d), x), ac));
return addUnsigned(rotateLeft(a, s), b);
};
var convertToWordArray = function(string) {
var lWordCount;
var lMessageLength = string.length;
var lNumberOfWordsTempOne = lMessageLength + 8;
var lNumberOfWordsTempTwo =
(lNumberOfWordsTempOne - lNumberOfWordsTempOne % 64) / 64;
var lNumberOfWords = (lNumberOfWordsTempTwo + 1) * 16;
var lWordArray = Array(lNumberOfWords - 1);
var lBytePosition = 0;
var lByteCount = 0;
while (lByteCount < lMessageLength) {
lWordCount = (lByteCount - lByteCount % 4) / 4;
lBytePosition = (lByteCount % 4) * 8;
lWordArray[lWordCount] =
lWordArray[lWordCount] | (string.charCodeAt(lByteCount) << lBytePosition);
lByteCount++;
}
lWordCount = (lByteCount - lByteCount % 4) / 4;
lBytePosition = (lByteCount % 4) * 8;
lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition);
lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
return lWordArray;
};
var wordToHex = function(lValue) {
var WordToHexValue = '',
WordToHexValueTemp = '',
lByte,
lCount;
for (lCount = 0; lCount <= 3; lCount++) {
lByte = (lValue >>> (lCount * 8)) & 255;
WordToHexValueTemp = '0' + lByte.toString(16);
WordToHexValue =
WordToHexValue +
WordToHexValueTemp.substr(WordToHexValueTemp.length - 2, 2);
}
return WordToHexValue;
};
var uTF8Encode = function(string) {
string = string.replace(/\x0d\x0a/g, '\x0a');
var output = '';
for (var n = 0; n < string.length; n++) {
var c = string.charCodeAt(n);
if (c < 128) {
output += String.fromCharCode(c);
} else if (c > 127 && c < 2048) {
output += String.fromCharCode((c >> 6) | 192);
output += String.fromCharCode((c & 63) | 128);
} else {
output += String.fromCharCode((c >> 12) | 224);
output += String.fromCharCode(((c >> 6) & 63) | 128);
output += String.fromCharCode((c & 63) | 128);
}
}
return output;
};
function md5(string) {
var x = Array();
var k, AA, BB, CC, DD, a, b, c, d;
var S11 = 7,
S12 = 12,
S13 = 17,
S14 = 22;
var S21 = 5,
S22 = 9,
S23 = 14,
S24 = 20;
var S31 = 4,
S32 = 11,
S33 = 16,
S34 = 23;
var S41 = 6,
S42 = 10,
S43 = 15,
S44 = 21;
string = uTF8Encode(string);
x = convertToWordArray(string);
a = 0x67452301;
b = 0xefcdab89;
c = 0x98badcfe;
d = 0x10325476;
for (k = 0; k < x.length; k += 16) {
AA = a;
BB = b;
CC = c;
DD = d;
a = FF(a, b, c, d, x[k + 0], S11, 0xd76aa478);
d = FF(d, a, b, c, x[k + 1], S12, 0xe8c7b756);
c = FF(c, d, a, b, x[k + 2], S13, 0x242070db);
b = FF(b, c, d, a, x[k + 3], S14, 0xc1bdceee);
a = FF(a, b, c, d, x[k + 4], S11, 0xf57c0faf);
d = FF(d, a, b, c, x[k + 5], S12, 0x4787c62a);
c = FF(c, d, a, b, x[k + 6], S13, 0xa8304613);
b = FF(b, c, d, a, x[k + 7], S14, 0xfd469501);
a = FF(a, b, c, d, x[k + 8], S11, 0x698098d8);
d = FF(d, a, b, c, x[k + 9], S12, 0x8b44f7af);
c = FF(c, d, a, b, x[k + 10], S13, 0xffff5bb1);
b = FF(b, c, d, a, x[k + 11], S14, 0x895cd7be);
a = FF(a, b, c, d, x[k + 12], S11, 0x6b901122);
d = FF(d, a, b, c, x[k + 13], S12, 0xfd987193);
c = FF(c, d, a, b, x[k + 14], S13, 0xa679438e);
b = FF(b, c, d, a, x[k + 15], S14, 0x49b40821);
a = GG(a, b, c, d, x[k + 1], S21, 0xf61e2562);
d = GG(d, a, b, c, x[k + 6], S22, 0xc040b340);
c = GG(c, d, a, b, x[k + 11], S23, 0x265e5a51);
b = GG(b, c, d, a, x[k + 0], S24, 0xe9b6c7aa);
a = GG(a, b, c, d, x[k + 5], S21, 0xd62f105d);
d = GG(d, a, b, c, x[k + 10], S22, 0x2441453);
c = GG(c, d, a, b, x[k + 15], S23, 0xd8a1e681);
b = GG(b, c, d, a, x[k + 4], S24, 0xe7d3fbc8);
a = GG(a, b, c, d, x[k + 9], S21, 0x21e1cde6);
d = GG(d, a, b, c, x[k + 14], S22, 0xc33707d6);
c = GG(c, d, a, b, x[k + 3], S23, 0xf4d50d87);
b = GG(b, c, d, a, x[k + 8], S24, 0x455a14ed);
a = GG(a, b, c, d, x[k + 13], S21, 0xa9e3e905);
d = GG(d, a, b, c, x[k + 2], S22, 0xfcefa3f8);
c = GG(c, d, a, b, x[k + 7], S23, 0x676f02d9);
b = GG(b, c, d, a, x[k + 12], S24, 0x8d2a4c8a);
a = HH(a, b, c, d, x[k + 5], S31, 0xfffa3942);
d = HH(d, a, b, c, x[k + 8], S32, 0x8771f681);
c = HH(c, d, a, b, x[k + 11], S33, 0x6d9d6122);
b = HH(b, c, d, a, x[k + 14], S34, 0xfde5380c);
a = HH(a, b, c, d, x[k + 1], S31, 0xa4beea44);
d = HH(d, a, b, c, x[k + 4], S32, 0x4bdecfa9);
c = HH(c, d, a, b, x[k + 7], S33, 0xf6bb4b60);
b = HH(b, c, d, a, x[k + 10], S34, 0xbebfbc70);
a = HH(a, b, c, d, x[k + 13], S31, 0x289b7ec6);
d = HH(d, a, b, c, x[k + 0], S32, 0xeaa127fa);
c = HH(c, d, a, b, x[k + 3], S33, 0xd4ef3085);
b = HH(b, c, d, a, x[k + 6], S34, 0x4881d05);
a = HH(a, b, c, d, x[k + 9], S31, 0xd9d4d039);
d = HH(d, a, b, c, x[k + 12], S32, 0xe6db99e5);
c = HH(c, d, a, b, x[k + 15], S33, 0x1fa27cf8);
b = HH(b, c, d, a, x[k + 2], S34, 0xc4ac5665);
a = II(a, b, c, d, x[k + 0], S41, 0xf4292244);
d = II(d, a, b, c, x[k + 7], S42, 0x432aff97);
c = II(c, d, a, b, x[k + 14], S43, 0xab9423a7);
b = II(b, c, d, a, x[k + 5], S44, 0xfc93a039);
a = II(a, b, c, d, x[k + 12], S41, 0x655b59c3);
d = II(d, a, b, c, x[k + 3], S42, 0x8f0ccc92);
c = II(c, d, a, b, x[k + 10], S43, 0xffeff47d);
b = II(b, c, d, a, x[k + 1], S44, 0x85845dd1);
a = II(a, b, c, d, x[k + 8], S41, 0x6fa87e4f);
d = II(d, a, b, c, x[k + 15], S42, 0xfe2ce6e0);
c = II(c, d, a, b, x[k + 6], S43, 0xa3014314);
b = II(b, c, d, a, x[k + 13], S44, 0x4e0811a1);
a = II(a, b, c, d, x[k + 4], S41, 0xf7537e82);
d = II(d, a, b, c, x[k + 11], S42, 0xbd3af235);
c = II(c, d, a, b, x[k + 2], S43, 0x2ad7d2bb);
b = II(b, c, d, a, x[k + 9], S44, 0xeb86d391);
a = addUnsigned(a, AA);
b = addUnsigned(b, BB);
c = addUnsigned(c, CC);
d = addUnsigned(d, DD);
}
var tempValue = wordToHex(a) + wordToHex(b) + wordToHex(c) + wordToHex(d);
return tempValue.toLowerCase();
}
export default md5;
- 在要使用它的文件里引入使用:
import md5 from '@/utils/md5';
md5('要加密的字符串');
11、拼接key与value生成字符串
- key与value生成字符串方法,也许你在配置请求头时用得上:
export function joinParams(obj) {
let str = '';
const keys = Object.keys(obj);
const values = Object.values(obj);
keys.forEach((item, index, arr) => {
str +=
arr.length - 1 === index
? `${item}=${values[index]}`
: `${item}=${values[index]}&`;
});
return str;
}
- 在要使用的地方引入使用:
import { randomString, joinParams } from '@/utils';
const key = prk;
const nonce = randomString(4);
const timestamp = new Date().getTime();
joinParams({ key, nonce, timestamp })
12、生成随机字符串
- 生成随机字符串
说明: length 生成随机字符串的长度, chars 字符集
export function randomString(
length,
chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
) {
let result = '';
for (let i = length; i > 0; i -= 1)
result += chars[Math.floor(Math.random() * chars.length)];
return result;
}
- 在要使用的页面中引入使用:
import { randomString } from '@/utils';
const nonce = randomString(4);
13、生命周期钩子
- mpvue的生命周期跟vue的一样,它也结合了小程序的生命周期,可以直接使用,但是mpvue的生命周期钩子在小程序页面加载进来时只会执行一次,就是说在页面切换重新渲染时是不会再执行了的,如果需要每当页面打开都需要执行的话,要使用小程序的钩子,注意以下几点应该够用了:
(1)、 只会执行一次的钩子:created、mounted、onLoad
(2)、 页面每次进来都会执行的钩子:onShow、onHide - 其中比较常用的有:
(1)、页面进来只要获取一次数据时可以用: onLoad
(2)、页面进来每次都要获取数据时可以用: onShow - 具体可以自己在页面中打印检测执行次数;
- 个人建议:
因为mpvue在页面切换时data里面的数据不会自动清除,每一次切换数据状态都会保留;所以获取后端数据渲染页面用onLoad 钩子获取一次就好了,不然如果使用onShow的话,页面每次切换都要去重新获取数据再渲染,页面会出现重渲染闪烁;
数据的获取我们可以这样做:
(1)、用onLoad首次获取数据进行页面渲染;
(2)、使用下拉刷新或者根据业务需要切换列表时去重新获取数据;
(3)、这样的话页面切换时就不用去请求数据,不会造成给人感觉好卡,又会闪;
14、页面数据清除
- mpvue对页面的数据状态在页面切换时是不会自动清除的,也就是在页面切换后数据状态不会像vue一样清除,依然存在的,如果业务需求要清除页面数据状态的话,可以在页面hide或页面打开时清除数据状态:
方法:
Object.assign(this.$data, this.$options.data());
- 例:
onShow(){
Object.assign(this.$data, this.$options.data());
}
onHide(){
Object.assign(this.$data, this.$options.data());
}
15、检测用户是否授权、白名单
- 小程序打开后,根据各个项目的需求不同,第一步可能就是要检测用户是否已经授权,如果已经授权,就去拿token,然后跳转到首页;如果没授权,就直接去授权页面;检测小程序是否授权使用:wx.checkSession()
官网: developers.weixin.qq.com/miniprogram… - 此方法可以在 app.vue页面的created钩子中调用,如果进入成功回调,说明登录态没有失效,处于授权状态,如果进入fail,说明登录态失效,这时可以直接跳转到登录页面:
wx.checkSession({
success: () => {
console.log('授权状态,可以去获取token,如果没有token,说明没有登录,还是要去授权登录 ')
},
fail: () => {
console.log('没有授权,可以跳转到登录页面')
}
})
- 配置页面白名单:
有时候,可能有几个页面是不用授权登录就可以进去的,这时我们可以在app.vue页面检测授权登录态时去做判断;
具体方法可以这样:
(1). 在单独文件夹里配个方法,方便统一管理,注意:安卓与IOS获取回来的路径不一样,然后在app.vue页面引入使用:
// 注意:安卓与IOS获取回来的路径不一样,在使用mp-vue时
// 在安卓中
const list = [
'pages/subPackages/cardDetail/main',
'pages/subPackages/invitationRegister/main',
];
// 在IOS中, 在头部多一个"/"
const list = [
'/pages/subPackages/cardDetail/main',
'/pages/subPackages/invitationRegister/main',
];
// 所以引用时要做一下判断系统的判断,这里就不详细讲了
export function permission(val, callback) {
if (list.indexOf(val) === -1) {
callback();
}
}
// 在app.vue页面中
import { permission } from '@/permission/index.js';
onShow() {
this.checkLogin();
},
methods: {
checkLogin() {
// 获取当前页面的路径 this.$root.$mp.appOptions.path;
const path = this.$root.$mp.appOptions.path;
permission(path, () => {
// 执行检测代码
wx.checkSession({
success: () => {
console.log('授权状态,可以去获取token,如果没有token,说明没有登录,还是要去授权登录 ')
},
fail: () => {
console.log('没有授权,可以跳转到登录页面')
}
})
});
},
},
16、授权登录
- 通过上一步的检测后,这一步进行小程序的授权登录了;
官方网址:developers.weixin.qq.com/miniprogram… - 小程序的授权流程有点繁锁,但还是要一步一步走:
- 通过 wx.login({}) 获取用户的code,二次请求获取用户信息时要用到
- 通过wx.getSetting({})检测用户是否授权,如果进入了success说明已授权,如果进入 fail说明未授权,要拉起授权窗口;
- 通过wx.getUserInfo({})获取用户基本信息跟code一起传给后台进行二次请求,前端也可以做二次请求;
- 很重要的一点,要拉起授权窗口,必须要使用小程序的button,且type为getUserInfo;
- 看如下代码:
<button open-type="getUserInfo" @getuserinfo="toAuth" class="button_auth">授权登录</button> methods:{ toAuth(){ let code = ''; // 1、获取code wx.login({ success: res => { if (res.code) { code = res.code; // 2、获取判断用户是否授权 wx.getSetting({ success: res1 => { // 用户已授权,会自动执行此处 if (res1.authSetting['scope.userInfo']) { wx.showLoading({ title: '授权中...', }); // 3、获取用户信息 wx.getUserInfo({ success: res2 => { console.log(res2) // 用户信息,二次请求解密用的,传给后台,前端也可以做二次请求 }, }); } else { // 用户没有授权,需要授权,重新拉起授权 wx.authorize({ scope: 'scope.userInfo', }); } }, fail: () => { }, }); } }, fail: () => { // console.log(err); }, }); } } - 将上一步的代码下接复制过去是能用的了,但笔者为了老铁使用mpvue时更方便,已经将此授权封装了一个库,名字是: mp-auth-button;所有的授权逻辑都有,你只要去npm/github上下载下来直接引入使用就可以了,它是一个button组件,你下载下来就是一个button,获取到有用户信息会在方法中的参数里回调给你,上面有写使用方法;
github网址:github.com/search?q=mp…
npm网址:www.npmjs.com/package/mp-…
17、二次请求获取session_key解密
- 先讲一遍授权登录的流程吧,再来说二次请求:
- wx.login获取到code,在二次请求时要作为请求参数;
- wx.getUserInfo获取到用户信息,其中信息包括:
rawData 不包括敏感信息的原始数据字符串,用于计算签名
signature 使用 sha1( rawData + sessionkey ) 得到字符串
encryptedData 包括敏感数据在内的完整用户信息的加密数据
iv 加密算法的初始向量
其中encryptedData 是加密后的用户数据,解密后可以得到,解密要用到iv , 及二次请求获取回来的 session_key,接下来就讲二次请求; - 二次请求是官方提供的接口,照着文档走就可以了,官方网址:
developers.weixin.qq.com/miniprogram…
前端可以使用wx.request这个api来请求获取:
// 二次验证获取successkey/open_id wx.request({ url: "https://api.weixin.qq.com/sns/jscode2session", header: { "Content-Type": "application/json" }, method: "GET", data: { appid: "", // 开发平台上的appid secret: "", // 开发平台上的secret js_code: code, // 第一步获取到的code grant_type: "authorization_code" // 这个是官方要求固定的 }, success: e => { e.data.session_key // session_key就是解密要用到的key } });- 解密看下一步;
18、 解密用户信息
- 用户信息解密官方提供了多种编程语言的示例代码,官方网址:
developers.weixin.qq.com/miniprogram…
接下来就一步一步进行解密: - 下载解密包,里面有案例:

- 下载下来后解压,得到:

- 我们可以使用node,跟js最接近;用vscode打开Node那个文件夹后,可以看到我们还要下载一个库:

- 于是就要下载这个库: npm install crypto --save
- 将WXBizDataCrypt.js和demo.js这两个文件复制到项目中,单独起个文件夹放;
- WXBizDataCrypt.js这个文件不用管,我们只需在demo.js文件中修改decode.js就可以了,将encryptedData , iv 传进去,具体也可以看官网:
var WXBizDataCrypt = require('./WXBizDataCrypt')
let appId = 'wx168b9fc4343434434'
// var sessionKey = wx.getStorageSync("openId_key").session_key
const encryptedData =
'CiyLU1Aw2KjvrjMdj8YKliAjtP4gsMZM'+
'QmRzooG2xrDcvSnxIMXFufNstNGTyaGS'+
'9uT5geRa0W4oTOb1WT7fJlAC+oNPdbB+'+
'3hVbJSRgv+4lGOETKUQz6OYStslQ142d'+
'NCuabNPGBzlooOmB231qMM85d2/fV6Ch'+
'evvXvQP8Hkue1poOFtnEtpyxVLW1zAo6'+
'/1Xx1COxFvrc2d7UL/lmHInNlxuacJXw'+
'u0fjpXfz/YqYzBIBzD6WUfTIF9GRHpOn'+
'/Hz7saL8xz+W//FRAUid1OksQaQx4CMs'+
'8LOddcQhULW4ucetDf96JcR3g0gfRK4P'+
'C7E/r7Z6xNrXd2UIeorGj5Ef7b1pJAYB'+
'6Y5anaHqZ9J6nKEBvB4DnNLIVWSgARns'+
'/8wR2SiRS7MNACwTyrGvt9ts8p12PKFd'+
'lqYTopNHR1Vf7XjfhQlVsAJdNiKdYmYV'+
'oKlaRv85IfVunYzO0IKXsyl7JCUjCpoG'+
'20f0a04COwfneQAGGwd5oa+T8yO5hzuy'+
'Db/XcxxmK01EpqOyuxINew=='
// var pc = new WXBizDataCrypt(appId, sessionKey)
// var data = pc.decryptData(encryptedData , iv)
// console.log('解密后 data: ', data)
const decode = (sessionKey,encryptedData,iv) => {
let pc = new WXBizDataCrypt(appId, sessionKey)
let data = pc.decryptData(encryptedData , iv)
// 获取到的用户信息,可以存起来
wx.setStorage({
key:"openId",
data:data.openId
})
console.log(data);
}
export default decode
- 在页面引入使用:
import decode from "@/utils/node_decode/decode.js";
decode(
e.data.session_key,
userinfo.encryptedData,
userinfo.iv
);
19、图片保存
- 图片的保存也是需要用户进行授权的,如果用户拒绝了再点击,还是要再次拉起用户授权窗口,所以也要用button,具体网址: developers.weixin.qq.com/miniprogram…
- 小程序项目中是不太可能放图片进去的,因为很占体积,要知道,放在static里面的图片也算是主包里的东西,所以一般图片都是由后端提供;保存的图片有两种:
一、普通图片:
图片一般由后端返回前端渲染,如果想要保存图片就要使用到canvas,利用canvas画布输出临时路径再保存图片,canvas可以用样式将它移出屏幕之外的地方就看不见了:二、图片内容有动态数据填充:HTML: <cover-view class="text-2" @click="saveQrcode" >保存到手机相册</cover-view> <canvas canvas-id="saveCanvasId" class="canvas-style" style="width:200px;height:200px" ></canvas> JS: data:{ return{ isShowAuthSaveImg: true } } methods: saveQrcode() { if (this.isShowAuthSaveImg) { let that = this; wx.getImageInfo({ src: this.qrCodeSrc, success: e => { const ctx = wx.createCanvasContext("saveCanvasId"); ctx.drawImage(e.path, 0, 0, 200, 200); ctx.draw(false, that.drawCallback()); } }); } else { wx.openSetting({ success: e => { if (e.authSetting["scope.writePhotosAlbum"]) { this.isShowAuthSaveImg = true; } } }); } }, drawCallback() { setTimeout(() => { wx.canvasToTempFilePath({ canvasId: "saveCanvasId", fileType: "png", x: 0, y: 0, success: res => { wx.saveImageToPhotosAlbum({ filePath: res.tempFilePath, success: e => { this.onShowDownLoadImg = false; }, fail: e => { wx.getSetting({ success: e => { if (!e.authSetting["scope.writePhotosAlbum"]) { this.isShowAuthSaveImg = false; } } }); } }); } }); }, 1000); },
1.如果想要保存的图片是动态数据的,可以前端写一个H5页面给后端,让后端去填充数据(如果有数据的话),然后由后端将H5生成一个图片返回,前端用wx.downloadFile将图片当作文件下载下来;
2. 还有个问题,如果用户拒绝了授权,然后又来保存图片的话,我们要将open-type设为 openSetting,才能打开授权页面,让用户去打开保存图片的授权;但是如果将open-type设为 openSetting,那么每一次用户点击保存图片按钮时,都会打开授权页面,这样是不可以的,所以我们要在用户点击按钮前,可以在onShow里将open-type设为空;
3. 看代码:HTML: <button class="picture_share" :open-type="openTypebt" @click="pictureShare" plain="true" >图片分享</button> JS: data:{ retun{ openTypebt: '', } }, onShow() { wx.getSetting({ success: res => { if (res.authSetting['scope.writePhotosAlbum']) { this.openTypebt = ''; } }, }); }, methods:{ pictureShare() { wx.showLoading({ title: '图片保存中...', }); wx.downloadFile({ // 文件下载到本地 url: `${baseUrl}/view/screenShot?url=${ this.hunterInfoData.view_url }&with=540&height=1350`, // 图片在后端的地址 header: headers, // 后端要求的请求头 success: e => { if (e.statusCode === 500) { wx.hideLoading(); $Toast({ content: '服务器繁忙,稍候重试', type: 'warning', }); return; } wx.saveImageToPhotosAlbum({ // 调用保存图片方法 filePath: e.tempFilePath, // 图片的临时路径 success: () => { wx.hideLoading(); $Toast({ content: '图片保存成功', type: 'success', }); }, fail: () => { // 用户拒绝,在这个回调中将type设为 // openSetting,下次再点时调起授权 wx.hideLoading(); wx.getSetting({ success: res => { if (!res.authSetting['scope.writePhotosAlbum']) { this.openTypebt = 'openSetting'; } }, }); }, }); }, }); }, }
20、打开一个H5页面
- 小程序要打开小程序以外的页面可以使用自带的web-view,具体我们可以新建一个页面,用户点击时跳转到此页面,然后将链接带过去给web-view,
网址: developers.weixin.qq.com/miniprogram…
参考代码 :<template> <div class="web_view"> <web-view :src="url"></web-view> </div> </template> <script> export default { name: 'WebView', data() { return { url: '', }; }, onShow() { this.setTitle(); }, methods: { setTitle() { this.url = this.$root.$mp.query.url; wx.setNavigationBarTitle({ // 动态设置标题 title: this.$root.$mp.query.title, }); }, }, }; </script> <style lang="less"> </style>
21、去除button默认边框
- 在使用了授权按钮button后,它有个默认的边框,一般情况是不符合项目样式的,可以在样式中通过设置伪类去除:
button::after { border-radius: none; border: none; }
22、动态设置页面标题
- 页面标题栏除了在app.json设置外,还可以在页面中动态更改的,使用api:
wx.setNavigationBarTitle({ title: '标题栏', });
23、绑定style
- mpvue不支持style绑定一个对象,如果想要这样是不行的:
不过我们可能通过computed将对象转换成字符串就可以解决问题:<div :style="styleObj"></div> data:{ return{ styleObj:{ color: '#fff' } } }<div :style="btnStyle"></div> data:{ return{ style: { width: '690rpx', height: '88rpx', 'background-color': '#09bb07', 'border-radius': '10rpx', 'line-height': '88rpx', 'text-align': 'center', color: '#ffffff', 'font-size': '36rpx', margin: '57rpx auto', }, } } computed:{ btnStyle(){ let s = '' let arr = [] for(let i in this.style){ arr.push(i+':'+this.style[i]); } s = arr.join(';') return s } }
24、识别二维码打开小程序
- 此功能是在微信中长按图片识别图片中的二维码打开一个小程序,当然要在开发平台上去配置打开的链接,这里主要讨论前端打开后获取二维码上的参数;注意了,它只能在体验版以上才可以实现这个功能,在开发版中是无法调试的,所以我们可以将代码发布到体验版中,然后识别二维码打开小程序后只能在手机上的调试工具中打印二维码上获取到的参数,参数可以在onLoad的参数中获取,在q对象里面,拿到后要用 decodeURIComponent() 方法进行解析出来;
具体配置及获取参考网址:
developers.weixin.qq.com/miniprogram…
参考代码:
onLoad(options) {
// options 中的 scene 需要使用 decodeURIComponent
// 才能获取到生成二维码时传入的 scene
Object.assign(this.$data, this.$options.data());
if (JSON.stringify(options) !== '{}') {
const scene = decodeURIComponent(options.q);
console.log(scene)
// 截取参数
this.code = scene.substring(scene.indexOf('code=') + 5);
}
},
25、使用echarts图表
使用图表,必然会想到百度的echarts,但它的体积惊人,吓得赶紧喝了两口水压压惊;不可能将整个包下载下来使用,随便都超过2M无法打包,我们可以到echarts的官网上去定制自己要用到的,而且要压缩版的,然后因为我们使用mpvue,所以这里可以引入mpvue-echarts来开发,下面就来讲下如何使用: 具体步骤:
- 下载mpvue-echarts,网址:www.npmjs.com/package/mpv…
npm install mpvue-charts --save - 到Echarts官网定制下载要使用到的图表,网址:echarts.baidu.com/builder.htm…
- 选择要使用的图表:

- 选择要使用到的图片上的组件,如果用不到可以不下载

- 下载要压缩:

- 点击下载,然后下载中:


- 将下载包复制到项目的static文件夹中

- 选择要使用的图表:
- 在页面中引入echarts及mpvue-echarts
注意:echarts的路径根据实际的文件位置引入;import mpvueEcharts from 'mpvue-echarts'; import * as echarts from '../../../static/echarts.min .js'; - 注册组件:
components: { mpvueEcharts }, - 在html中使用:
<mpvue-echarts :echarts="echarts" :on-init="initChart" ></mpvue-echarts> - 在data中:
data() { return { echarts, // echarts定制组件 // 横轴数据 AxisXData: [], // 图表动态数据 echartsData: [], }; } - 在methods中:
methods:{ // 图表 initChart(canvas) { let chart = null; // 动态获取图表宽度 const echartWidth = 700 / 750 * wx.getSystemInfoSync().windowWidth; // 动态获取图表高度 const echartHeight = 540 / 750 * wx.getSystemInfoSync().windowWidth; chart = echarts.init(canvas, null, { width: echartWidth, height: echartHeight, }); canvas.setChart(chart); const option = { // 横轴配置 xAxis: { type: 'category', data: this.AxisXData, axisLabel: { interval: 0, fontSize: 12, }, splitLine: { show: true, lineStyle: { color: '#f2f2f2', }, }, axisTick: { show: false, }, axisLine: { lineStyle: { color: '#3488ea', width: 2, }, }, }, // 纵轴配置 yAxis: { // 纵轴标尺固定 type: 'value', scale: true, name: '简历数', nameGap: 15, max: 100, min: 0, boundaryGap: [0.2, 0.2], splitLine: { show: true, lineStyle: { color: '#f2f2f2', }, }, axisTick: { show: false, }, axisLine: { lineStyle: { color: '#3488ea', width: 2, }, }, }, // 图表类型及填充数据 series: [ { data: [{value:20},{value:40}], type: 'bar', itemStyle: { color: '#3488ea', }, barWidth: 20, }, ], grid: { left: '6%', right: '0', // bottom: '0', top: '30', containLabel: true, }, }; chart.setOption(option); return chart; // 返回 chart 后可以自动绑定触摸操作 }, } - 你只要照着以上步骤应该可以搞定,不过具体配置跟echarts官网上的一模一样的,mpvue-echarts只是一个壳而已,具体样式配置及series的设置可以按照echarts官网的案例来;
具体网址: echarts.baidu.com/examples/
26、图片路径问题
- 当图片路径放到data里作为变量去绑定时,可能会出现图片不出来的情况,可以这样解决试下:
一、将路径改为绝对路径或用require引入:二、到webpack.base.conf.js配置里将图片限制大小改大:data(){ return{ img_1: "/static/images/xxx.png", img_2: require('../../../static/images/xxx.png') } }{ test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: 'url-loader', options: { limit: 1000000000, // 这里改大点 name: utils.assetsPath('img/[name].[ext]'), }, },
27、this指向问题
- 关于这个this是说使用箭头函数的问题,老掉牙了,有些老铁会在使用了小程序的api后,在成功函数里用了箭头会造成this丢失,可以使用匿名函数来代替:
wx.showLoading({ success: () => { // 这样就可以了 this.data = 123 } })
28、页面下拉刷新
- 配置小程序页面下拉刷新请求接口,在页面的json文件中配置:
{ "enablePullDownRefresh": true, "backgroundTextStyle": "dark", // 配置小圆点颜色 } - 在页面中调用此钩子,与onShow同级
onPullDownRefresh() { wx.showLoading({ title: '加载中...', }); // 请求数据 }, - 在请求数据完成后要把这个下拉的调用停止掉,不然在时间内会一直在下拉状态,调用此方法停止:
wx.stopPullDownRefresh();
29、使用scroll-view组件
- scroll-view这里主要讲纵向滚动时的使用及注意点:
- 页面切换时,想要回到顶部,可以设 :scroll-top="0"
- 要给scroll-view设一个固定的高度,这里有个问题,就是小程序虽然支持vw,vh这个单位自适应,但mpvue一直使用rpx,所以给scroll-view设vh这个自适应单位是不可行的,而且每一款手机的屏幕高度都不一样,要保证scroll-view与其它元素加起来刚好是一整屏,我们可以这样做:
- 通过如下方法获取到手机屏幕的总高度:
wx.getSystemInfoSync().windowHeight - 再通过下面这个api获取页面中其它元素的高度,然后在rests 中可以拿到所选元素的高度,它是个数组,其中val参数是其它元素的类名,id也可以,其它元素是指页面中除了scroll-view以外的元素,也就是总高度减去的其它元素高度:
wx.createSelectorQuery().selectAll(val).boundingClientRect(rects =>{ console.log(rects) // rects是所选的元素属性,包括宽、高等等 }) - 遍历出来后得到的都是px的单位,所以我们可以将总高度减去其它元素高度的和,剩下就是scroll-view的高度了,直接传进去就可以了;
- 计算scroll-view的高度应该是在其它元素都渲染完后再去计算,不然会不准确,特别是如果其它元素上的内容是有异步请求数据填充的,数据请求回来后才显示的,那这个计算最好是放在请求数据成功回来后再去计算;
- 通过如下方法获取到手机屏幕的总高度:
- 代码奉献:
- 计算高度的方法,我是放在了一个公共方法文件里导出,在callback回调里把计算出来的值传出去,下面的代码直接拿去用就可以了:
export function scrollHeight(val, callback) { let clientHeight = 0; // 获取屏幕高度与宽度 try { const res = wx.getSystemInfoSync(); clientHeight = res.windowHeight; } catch (e) { // Do something when catch error } setTimeout(() => { let allHeight = 0; wx .createSelectorQuery() .selectAll(val) .boundingClientRect(rects => { rects.forEach(rect => { allHeight += rect.height; }); if (allHeight) { callback(`${clientHeight - allHeight}px`); } else { wx .createSelectorQuery() .selectAll(val) .boundingClientRect(res => { res.forEach(rec => { allHeight += rec.height; }); callback(`${clientHeight - allHeight}px`); }); } }) .exec(); }, 400); } - 在使用的页面里引入使用:
import { scrollHeight } from '@/你的文件名'; data(){ return { height: 0 } } methods:{ // 动态获取sroll-view高度 getScrollHeight() { scrollHeight('.carousel_wrap,.send_tab',this.scrollCallBack); }, scrollCallBack(val) { this.height = val; }, } - 注意,元素的类名是个字符串,多个可以用逗号隔开,如 ' .abc,.bcd,.efg'
- 在html中, @scrolltolower是下拉触底的事件,可以用它去触发再次请求数据:
<scroll-view @scrolltolower="触底的事件,用来请求下一页数据" scroll-y :style="{height:scrollHeight,overFlow:'hidden'}" v-if="height" scroll-top="0" > <ul> <li></li> </ul> </scroll-view> - 触底事件要注意:
- 应该做个开关,防止用户狂拉不断请求,应该 是数据请求回来后才把开关打开再次请求;
- 当没有数据返回时,也应该做个开关,禁止下 拉触发请求,提示没有数据即可;
- 计算高度的方法,我是放在了一个公共方法文件里导出,在callback回调里把计算出来的值传出去,下面的代码直接拿去用就可以了:
30、分享页面
- 分享调用到的小程度api用 onShareAppMessage() ,与onShow同级:
网址:developers.weixin.qq.com/miniprogram…<button open-type="share"></button> onShareAppMessage() { return { title: '分享页面名字', path: `分享页面路径`, // 如 '/pages/xxx/main' imageUrl: '图片路径,不能网络图片', // 如:'/static/images/xxx.jpg' }; }, - 只要设了onShareAppMessage,小程序右上角点击就会出现转发按钮
31、formid收集
- 后端可能会要求你在提交表单后收集formid给后端,formid会在from提交表单后在回调参数中返回,在接口没有配置到开发平台前,是拿不到formid的;
- 代码奉献:
// 其中 report-submit="true" 表示是否返回formid <form report-submit="true" @submit="bindSubmit"> // 表单 </form> methods:{ bindSubmit(e) { console.log( e.mp.detail.formId); }, }
32、复制到剪切板
- 复制内容到剪切板非常简单;
具体网址:developers.weixin.qq.com/miniprogram… - 调用apiwx.setClipboardData()进行复制;
- 调用 wx.getClipboardData()进行获取;
- 具体可以参照文档;
33、图片预览
- 图片预览调用 api: wx.previewImage()
- 注意图片预览是打开了一个新的页面,关闭预览时原来的页面相当于重新打开,会调用onShow钩子;
34、图片高度自适应
- 如果我们设置了图片的宽度,高度想要它自适应的话,可以这样做:
在img标签内 加上 mode="widthFix" 即可:
<img src="" mode="widthFix" width="200"/>
35、弹窗阻止页面滚动
- 有时我们会自己写一个弹框,自己定义一个遮罩层,但页面还可以触发滚动,我们可以使用这个去阻止页面滚动:
给弹框的最外层元素加上这个属性 catchtouchmove,请在真机上预览,调试工具上是看不出效果的;
36、生成二维码
- mpvue开发小程序生成二维码,可以借用 weapp-qrcode 这个插件,
具体网址:www.npmjs.com/package/wea…
这个是npm上的网址,具体配置也在里面 - 使用方法:
- 下载:
npm install weapp-qrcode --save - 在要使用到的页面引入 :
import drawQrcode from "weapp-qrcode"; HTML: <canvas canvas-id="saveCanvasId" ></canvas> methods:{ getQrcode(){ drawQrcode({ width: width, height: height, canvasId: canvasId, text: text, correctLevel:3, typeNumber:8, callback: () => { // 将画布中的元素转成图片临时路径 wx.canvasToTempFilePath({ canvasId: canvasId, fileType:"png", x:0, y:0, success(res) { console.log(res); // 得到图片路径,可以展示 } }) } }) } }
- 下载:
37、使用iview-weapp插件库
- mpvue如果想要使用ui库的话,可以使用ivew-weapp;
npm网址:www.npmjs.com/package/ivi…
官网 :weapp.iviewui.com/docs/guide/… - 具体使用细节可以参考官网上的使用方式,但它是下载后是要将整个包复制到static文件下,然后作为静态资源引入使用的,具体使用这里演示一下:
- 下载 :
npm install iview-weapp --save-dev - 到node_modules文件里面将它dist里面的文件复制到static文件里,把整个都复制过去,因为有些文件是搭配使用的,想要精简,那就要看着文档来选择复制使用:

- 在页面中可以根据官网的说明来使用,这里就拿toast 提
示框来举例说明使用:
- 在页面的json文件中配置:

- 在页面中HTML中写上元素:
<div> <i-toast id="toast"/> </div>- 在js中引入,路径使用相对路径:
import { $Toast } from '../../../static/iview/base/index.js';- 调用:
$Toast({ content: '此内容必填', type: 'warning', }); - 在页面的json文件中配置:
- 下载 :