为了一套代码,适应多个平台,我司决定用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.accessToken
break;
}
// 经销商请求的接口都要携带openid作为身份标记,客服和销售有token
if (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].data
const 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 = request
Vue.prototype.$api = api
Vue.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之类的。
废话少说,最后是通过这样一段代码来实现的获取用户信息。
<button
open-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.value
this.map.province = this.provinces[this.index].name
this.cities = this.provinces[this.index].cityList
this.$emit('mapData', this.map)
},
bindPickerChangeCity: function(e) {
this.index = e.detail.value
this.map.city = this.cities[this.index].name
this.counties = this.cities[this.index].areaList
this.$emit('mapData', this.map)
},
bindPickerChangeCounty: function(e) {
this.index = e.detail.value
this.map.county = this.counties[this.index].name
this.counties = this.counties[this.index].areaList
this.$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个模板id
uni.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
模板消息的发送方式和小程序很类似的。