为了一套代码,适应多个平台,我司决定用uni-app来开发微信小程序。
一、从头开始项目搭建
1、HBuilder官网下载开发工具,我下载的是全功能的200多M的那一版
2、下载微信开发者工具后,找到设置-安全设置,打开“服务端口”,否则无法与HBuilder连接。
3、HBulder新建uni-app项目,运行的时候会选择微信开发者工具的路径。
一般会安装在C盘:C:\Program Files (x86)\Tencent\微信web开发者工具
新建项目:
4、运行后可以查看项目,在微信开发工具中看到下面的界面,算是运行成功了。
4、查看新建的项目模板后,发现和官方demo中很多的UI样式都没有,于是打算直接下载官方demo,然后去删删改改,直接套用了。
地址:https://github.com/dcloudio/hello-uniapp
下载在HBuilder中导入项目,然后运行就好了。
二、项目开发基本API记录
1、需要了解的技术栈
在开发uni-app前,必须掌握的技术栈,vue。当然,前端基础知识也必不可少!
如果要做app开发,对weex要有一定了解。
开发中会发现,很多和vue一样的方式,比如this.$store.
2、开发中使用的API
项目开发中使用的组件和API在官方文档都有,东西不少,从头看两遍也需要几天。
3、封装全局请求接口
①项目架构
下面是官方给的一张架构图:https://uniapp.dcloud.io/frame
开发小程序主要注意下面几个文件:
┌─cloudfunctions 云函数目录(阿里云为aliyun,腾讯云为tcb,详见uniCloud)
│─components 符合vue组件规范的uni-app组件目录
│ └─comp-a.vue 可复用的a组件
├─hybrid 存放本地网页的目录,详见
├─platforms 存放各平台专用页面的目录,详见
├─pages 业务页面文件存放的目录
│ ├─index
│ │ └─index.vue index页面
│ └─list
│ └─list.vue list页面
├─common 一些公共资源的存放,如公共样式等
│ ├─uni-css 公共样式
│ ├─utils.js 一些公共函数处理方法
├─static 存放应用引用静态资源(如图片、视频等)的目录,注意:静态资源只能存放于此
├─wxcomponents 存放小程序组件的目录,编译后的文件
├─main.js Vue初始化入口文件
├─App.vue 应用配置,用来配置App全局样式以及监听 应用生命周期
├─manifest.json 配置应用名称、appid、logo、版本等打包信息,详见
└─pages.json 配置页面路由、导航条、选项卡等页面类信息,里面会配置首页
②、uni.request封装
之前做vue项目,axios都会做全局的请求配置和拦截。发现uni-app中并没有提供。而且好像也无法做全局的接口拦截。
requestAPI地址:https://uniapp.dcloud.io/api/request/request
这样就不得不做一个全局的request,大致思路如下:
在common文件夹下创建 config.js
let url_config = ""if(process.env.NODE_ENV === 'development'){// 开发环境url_config = 'https://taobao.com/'}else{// 生产环境url_config = 'https://taobao.com/'}export default url_config
在common文件夹下创建request.js,用来封装uni.request
import urlConfig from './config.js'import store from '../store'import { deepClone } from './util.js'const request = {}const headers = {'content-type': 'application/json'}request.globalRequest = (url, method, data, power) => {switch (power){case 1:headers['Authorization'] = 'Basic a3N1ZGk6a3N1ZGk='break;case 2:responseType = 'blob'break;default:headers['Authorization'] = store.getters.accessTokenbreak;}// 经销商请求的接口都要携带openid作为身份标记,客服和销售有tokenif (store.getters.identity && Number(store.getters.identity) === 0 && data && store.getters.openid) {data.openid = store.getters.openid}return uni.request({url: urlConfig + url,method,data: data,dataType: 'json',header: headers,}).then(res => {if (res && res[1] && res[1].statusCode && res[1].statusCode === 200) {// 带有返回全部参数标记的返回全部,默认只返回接口参数if(data && data.getAllRes) {return res[1]}return res[1].data} else {throw res}}).catch(parmas => {console.log('接口拦截错误:')console.log(parmas)if (parmas[0] === null && parmas[1] && parmas[1].data) {const { code, message } = parmas[1].dataconst codes = [10000, 10001, 10013, 10051, 10100, 401]if (code && codes.includes(code)) {uni.showToast({title: '当前登录已过期',icon: 'none'})this.$store.commit('logout')} else {uni.showModal({content: message,showCancel: false})return Promise.reject()}} else if (parmas[0] && parmas[0].errMsg){const { errMsg } = parmas[0]uni.showModal({content: errMsg,showCancel: false})return Promise.reject()} else {uni.showModal({content: JSON.stringift(parmas),showCancel: false})return Promise.reject()}})}export default request
附:res中共返回全部数据类似这样
里面有很多参数,具体用到的话,按照自己实际需求进行封装。
在上面的封装中,我们看到对于返回数据和错误返回,我们使用的是 .then和.catch ;这样做的好处,是在组件中使用接口时候就和axios类似了,还是使用习惯的问题。当然,这种封装方式也存在一些问题,接口“红色”也会走.then方法,在该方法中抛出错误,然后才会抛出到catch
我们也可以通过这样的方式进行封装:
uni.request({url: 'https://www.example.com/request',data: {text: 'uni.request'},header: {'custom-header': 'hello'},success: (res) => {console.log(res.data)},fail: (err) => {console.log(err)}});
③根目录下建立api/index.js
import request from '@/common/request.js'// import { formatGetUri } from '@/common/util.js'const api = {}const PORT1 = 'baseinfo'// POST请求方式 //必须大写,为了兼容其他应用api.submitForm = params => request.globalRequest(`${PORT1}/mobile/signUp`, 'POST', params, 1)// 请求列表接口api.queryList = params => request.globalRequest(`${PORT1}/service/list`, 'GET', params)export default api
④comon/util.js文件中添加函数formatGetUri
// 这个函数是formdata序列化,因为我开发时候,后端需要的都是json格式,所以我实际上没有用到export function formatGetUri(obj) {const params = []Object.keys(obj).forEach((key) => {let value = obj[key]if (typeof value !== 'undefined' || value !== null) {params.push([key, encodeURIComponent(value)].join('='))}})return '?' + params.join('&')}module.exports = {formatGetUri: formatGetUri}
⑤main.js中引入
import request from './common/request.js'import api from './api/index.js'import url from './common/config.js'Vue.prototype.$request = requestVue.prototype.$api = apiVue.prototype.$url = url
⑥组件中使用
const params = {pageId: 1,pageCount: 10}this.$api.queryList(params).then(res => {// ...}).catch(res => {// ...})
参考文档:https://www.yuque.com/docs/share/79ba2a9c-fb1f-41d5-a1dc-18a6e2d9eda4
4、esaycom模式引入uni-ui
uni-app框架自带了很多组件,但是开发中某些你想用的组件可能并没有,比如说折叠面板、卡片等。这个时候你需要引入扩展组件——uni-ui
官方推荐的引入方式是,esaycom方式引入。
三、项目开发问题记录
1、scss插件安装
因为项目开发使用的是包含uni-ui(style使用的scss)的项目(就是template中自带插件),但是插件引入后编译报错,查看错误,是没有scss编译的插件。需要手动安装一下。
新建项目时候选择了自带uni-ui的。

在HBuilder中选择工具-插件安装-前往插件市场安装,然后就打开了如下页面:
然后点击该插件,选择右侧的使用Hubilder导入插件。
之后插件就安装完成了,然后重新启动一下项目就运行OK了!
2、获取微信用户信息失败
好久没错小程序开发了,不知道微信小程序做了变化,用scope.userInfo获取用户信息不会弹窗询问了(目前正式版使用的不受影响),默认会直接失败,并且陆续不再支持该功能。
详见这里:https://developers.weixin.qq.com/community/develop/doc/0000a26e1aca6012e896a517556c01
看了官方的解释,才知道,从两年前就已经不支持了。
因此,uni-app官方的uni.getUserInfo方法获取userInfo并不好用了。会直接报错errMsg: “getUserInfo:fail scope unauthorized”
按照官方的说法,用button组件设置open-type=”getUserInfo”。但是问题又来了,这样的方法,导致我很难和uni-app一些方法统一起来。
看到这里,感觉微信之所以这么做是不是为了屏蔽第三方框架,诸如uni-app之类的。
废话少说,最后是通过这样一段代码来实现的获取用户信息。
<buttonopen-type="getUserInfo"@getuserinfo="wxGetUserInfo"type="primary">我是经销商</button>// 在methods中回调函数获取信息wxGetUserInfo() {uni.getUserInfo({provider: 'weixin',success: function (infoRes) {console.log(infoRes)},fail: function(error) {console.log(error)}});},
3、微信开发者工具有个大黑块
开发过程中发现微信开发者工具中有个大黑块。有时候刷下一些就没了,有时候刷新还在,十分难受!
去问了度娘,然后在微信开发社区看到官方回答:
https://developers.weixin.qq.com/community/develop/doc/000c8cfa49c14893763a9b4d856800
下载后找到文件(文件名:localstorage_b72da75d79277d2f5f9c30c9177be57e.json)里面就一行代码
{"general": {"enableGPU": false}}
于是没有去替换,看这个语句应该是关闭CPU渲染的语句。
直接找到对应文件去修改:C:\Users\用户名\AppData\Local\微信开发者工具\User Data\1a695ca2de1a85735f93a43fb366c83f\WeappLocalData
在该目录下找到对应文件:
直接搜索enableGPU,
直接修改了这个为false,没有修改其他的。然后重启HBuilder的uni-app项目,问题解决了!
4、设置tabBar后不显示的问题
在pages.json中设置tabBar后不显示,查阅资料有人说pages中第一项必须和tabBar中第一项相同,否则无法显示,但是经过验证,这种说法并不正确。
只要切换到tabBar中包含的页面,tabBar就会显示出来。所以说这个逻辑是没有问题的。
我看到有人这样提问——如果想要所有页面都有tabBar该怎么设置呢?答案是自己写tabBar!
也就是说tabBar只会在对应包含的页面存在的时候才会显示!
5、uni-app封装省市区三级联动组件
之前在vue项目中自己做过封装,省市数据是从github上获取的,都带有code。现在要在nui-app上做封装,首先明确,需要使用picker而是不select,小程序中也没有select。
因为uni-app的语法和vue很相似,其实封装起来也很简单,直接说上代码吧。
<template><view><view class="uni-form-item uni-column"><view class="title">省份:</view><view class="uni-list-cell-db"><picker@change="bindPickerChangeProvince":value="index":range="provinces"range-key="name"><view class="uni-input">{{map.province}}</view></picker></view></view><view class="uni-form-item uni-column"><view class="title">市:</view><view class="uni-list-cell-db"><picker@change="bindPickerChangeCity":value="index":range="cities"range-key="name"><view class="uni-input">{{map.city}}</view></picker></view></view><view class="uni-form-item uni-column" v-if="showCounty"><view class="title">县(区):</view><view class="uni-list-cell-db"><picker@change="bindPickerChangeCounty":value="index":range="counties"range-key="name"><view class="uni-input">{{map.county}}</view></picker></view></view></view></template><script>// 省市县(区)三级联动地图import provinceMap from './province'export default {name: 'ProvinceSelect',props: {showCounty: {type: Boolean,default: true}},data() {return {map: {province: '',city: '',county: '',},index: 0,provinces: provinceMap,cities: [],counties: [],}},methods: {updateMap(val) {this.map = val},bindPickerChangeProvince: function(e) {this.index = e.detail.valuethis.map.province = this.provinces[this.index].namethis.cities = this.provinces[this.index].cityListthis.$emit('mapData', this.map)},bindPickerChangeCity: function(e) {this.index = e.detail.valuethis.map.city = this.cities[this.index].namethis.counties = this.cities[this.index].areaListthis.$emit('mapData', this.map)},bindPickerChangeCounty: function(e) {this.index = e.detail.valuethis.map.county = this.counties[this.index].namethis.counties = this.counties[this.index].areaListthis.$emit('mapData', this.map)}},}</script>
6、微信小程序的图片上传
在uni-app中上传图片api是uni.chooseImage(OBJECT),在回调函数success中会返回上传图片的临时文件地址。
res返回的一个本地资源的临时文件路径后是这样的: http://tmp/qs1x1G8K84nF61672963284aa67648d51bb3f16196c7.png
我们看到用一个tmp替代了域名。
得到这个路径后,我们需要调用uni.uploadFile(OBJECT)去上传图片。
7、uni-app小程序事件冒泡
因为uni-app支持vue语法,因此点击事件我都写的@click,然后遇到在父元素、子元素都有点击事件,而子元素的点击事件因为冒泡,触发了父元素的点击事件。
搜索“冒泡”,找到了官方给的解决方案。
因此,将子元素改为这种方式的点击触发就Ok了!
但是@tap这种写法是只支持小程序的,这明显不符合一套代码多处兼容的要求。再找一下:
搜索事件后,我们找到事件映射表:
发现stop是可以兼容多端的,因此,@click.stop是可以的哦。
8、小程序登录获取openid
以前开发过微信公众号,已经记不太清了,记得那时候openid是可以直接获取的。
现在开发微信小程序,调用uni.login获取code后,将code返给后端接口,后端再算出openid返给前端。盗用官方的一张流程图如下:
9、小程序获取手机号
现在腾讯对小程序的安全性管控越来越严格了,想要获取手机号无法用api触发,只能用户通过button手动触发。
官方文档:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getPhoneNumber.html
由于该接口针对已注册的小程序,所以需要先去注册认证,然后调用该接口才能使用。
11、微信开发者工具接口请求正常,微信扫码模拟时候不行
开发中按照上面的方法,封装了统一的接口请求组件,在模拟器上做联调,一切正常,然后想着用微信扫码模拟一下吧,结果接口没反应,打开调试工具,发现权限headers[‘Authorization’] = store.getters.accessToken 根本没有带上去。这是什么情况呢。
首先,做手机扫码测试时候,要去掉对合法域名校验。
之后点击“预览”,会生成二维码,扫码后进行测试。
之后,在小程序中打开调试面板。


打开调试面板后,可以在面板中查看你console的数据。
经过查看发现,接口没有将权限token带上去,直接报401。查看了一下资料,发现,虽然接口请求默认为’content-type’: ‘application/json’。但是还需要配置一下。
const headers = {'content-type': 'application/json'}
加上这样一句代码后,重新预览,神奇的事情发生了,我的天啊,这么神奇吗?
数据就这样出来了。
12、uni-app的v-for循环中无法携带item参数
开发中发现的,打印item永远是undefined。查了一下资料,可以获取index,因此只能折中传递index了
13、获取用户头像(授权)
以前的微信获取头像有很多种方法:
1、手动授权通过button时候有open-type=”getUserInfo”
2、已授权的可以直接通过uni.getUserInfo
3、还有直接使用
4、uni.authorize唤起授权然后调用uni.getUserInfo等方法..
但是随着微信安全性的增加,以上方法逐渐废弃。
详见:
https://developers.weixin.qq.com/community/develop/doc/000e881c7046a8fa1f4d464105b001
https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/authorize.html#scope-%E5%88%97%E8%A1%A8


目前微信官方推荐的方法只有一种:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/userProfile.html
通过button手动授权获取临时头像地址,可以在小程序中展示。
<button type="primary"open-type="chooseAvatar"@chooseavatar="onChooseAvatar">一键登录</button>
四、uni-app小程序发布
官方教程:https://uniapp.dcloud.net.cn/quickstart?id=发布为小程序

总体来说很简单,发布的时候输入小程序appid和名称,在微信开发者工具上选择上传,然后去微信开发者平台去申请认证就OK了。
发布的时候需要输入版本号和备注,在备注里面一般需要填写好你的测试账号密码等,方便小程序的审核人员去审核你的小程序~~,当然具体需求你可以先发布,到时候有什么要求再去修改。
在微信开发者后台可以选择“提交审核”和“设置为体验版”。按照自己的具体需求来。
五、微信的消息订阅
微信有消息订阅功能,现在的消息订阅都需要手动触发,而且只能触发一次了,这个也是我采坑之后才知道的~
官方文档:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/subscribe-message.html
开发之初,有印象这个功能可以选择只发送一次还是一直授权,然后写了授权代码:
// 最多可以放3个模板iduni.requestSubscribeMessage({tmplIds: ['abcdeg', '8xrhA-abcdeg'],success (res) {console.log('消息订阅授权成功')console.log(res)},fail(err) {console.log('消息订阅授权失败')console.log(err)}})
弹出页面如下:
傻傻的以为选择了“总是保持以上选择,不再询问”就是可以一只发送消息通知了(印象中是这样,很久没做这个功能了)。
但是在后端同事调用接口后发现,调用一次后,再次调用就会报错“43101”。查询后得知是授权失效了。
官方文档:https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.send.html
于是赶紧查询了微信开发社区的问题。看到了很多类似的问题。
https://developers.weixin.qq.com/community/develop/doc/000200a631c0c0a8680a0c24056800
https://developers.weixin.qq.com/community/develop/article/doc/0006ac060e4e80183bc9654b856013
查询资料才恍然大明白,原来现在的消息订阅早就不是以前了。申请永久模板是不太现实的(京东的发票通知是永久模板,人家是关系户,咱没办法)
只好绕道,改为用微信公众号内的通知:
这样一来工作就都交给后端了,公众号的通知是不需要授权的,只要你关注了就能发送。我这里提交表单,请求接口,后端自己去请求公众号发送消息的方法就好了~
另,附公众号的消息推送文档地址:
https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Template_Message_Interface.html
模板消息的发送方式和小程序很类似的。
