uniapp开发微信小程序,我踩了大家都会踩的坑
关注公众号,“技术干货”及时达!?最近使用uniapp开发了一个微信小程序(本项目技术栈是uniapp + vue3 + ts,用了最近比较火的模板unibest。),踩了一些大家普遍都会踩的坑,下面做一些总结。文章多处「引用到权威官方内容」和一些「比较可靠的文章」。如有错误,欢迎指正。
?1. 使用微信昵称填写能力遇到的问题自 2022 年 10 月 25 日 24 时后,wx.getUserProfile 和 wx.getUserInfo的接口被收回,要想获取微信的昵称头像需要使用微信的头像昵称填写能力。
我们的设计稿中「没有编辑确认按钮」,所以应该「失焦后」就「调用」后端的变更昵称接口:
但是失焦之后,微信会对昵称内容「做合规性校验」,导致「失焦后不能立马获取到输入的内容」:
uv-inputv-model="form.name"type="nickname"placeholder="请输入内容"@blur="handleSubmit"/uv-input
asyncfunctionhandleSubmit(){
console.log('form.value.name',form.value.name)//测试用户001
console.log('rawName',rawName)//测试用户001
if(form.value.name===rawName)
return
//...
}
因此最开始的想法是「等待」校验结束:
asyncfunctionhandleSubmit(){
//微信会对type="nickname"的输入框失焦时进行昵称违规校验,这个校验是异步的,所以需要等待一下
awaitnewPromise((resolve)=setTimeout(resolve,0))
console.log('form.value.name',form.value.name)//Jude
console.log('rawName',rawName)//测试用户001
if(form.value.name===rawName){
return
}
//...
}
但如果真的输入了违规昵称,微信将「自动清空」输入框内容,而「在此之前」我的提交「请求已经发送」:
因此需要用到官方新加的一个回调事件bindnicknamereview(文档):
uv-inputv-model="form.name"type="nickname"placeholder="请输入内容"@nicknamereview="handleSubmit"/uv-input
functiononNickNameReview(e){
console.log('onNickNameReview',e)
if(e.detail.pass){
//校验通过
handleSubmit()
}else{
form.value.name=rawName
}
}
但发现 uv-ui 并没有提供这个事件,还是没有生效,只能改node_modules的uv-input源码,并给uv-ui提个pr:
2. 自定义导航栏原生导航栏配置方面有很多限制,比如不允许修改字体大小等。所以有的时候需要自定义导航栏。
「首先注意,webview的页面无法自定义导航栏!」
所以:「导航栏高度 = 状态栏到胶囊的间距(胶囊上坐标位置-状态栏高度) * 2 + 胶囊高度 + 状态栏高度」
「第一步」:配置当前页面的json文件
//pages.json
{navigationStyle:"custom"}
「第二步」:获取状态栏和导航栏高度,只需要获取一次即可,获取到可以放到pinia里
//自定义导航栏
conststatusBarHeight=ref(0)
constnavBarHeight=ref(0)
statusBarHeight.value=uni.getSystemInfoSync().statusBarHeight
letmenuButtonInfo=uni.getMenuButtonBoundingClientRect()
navBarHeight.value=menuButtonInfo.height+(menuButtonInfo.top-statusBarHeight.value)*2
「第三步」:自定义导航栏
viewclass="nav-bar"
!--状态栏占位--
view:style="{height:statusBarHeight+'px'}"/view
!--真正的导航栏内容,请按照自己的需求自行定义--
viewclass="nav-bar-content"style="font-size:34rpx;":style="{height:navBarHeight+'px'}"导航栏标题/view
/view
「问题」:微信小程序原生导航栏会根据微信设置(字体大小,是否开启深色模式)等变化,深色模式是页面是可以获取到的,但字体大小等目前没有开放接口,所以无法根据微信设置动态变化。
3. 自定义tabbar由于原生底部tabbar的局限性,未能满足产品需求,所以需要自定义tabbar。
「首先」,自定义tabbar的第一步配置pages.json:
//pages.json
tabBar:{
custom:true,
//...
},
「然后」,我们只需要「在项目根目录(src)创建custom-tab-bar目录,uniapp编译器会直接它拷贝到小程序中:」
!--src/custom-tab-bar/index.wxml--
viewclass="tab-bar"
viewclass="tab-bar-border"/view
viewwx:for="{{list}}"wx:key="index"class="tab-bar-item"data-path="{{item.pagePath}}"data-index="{{index}}"bindtap="switchTab"
imageclass="tab-bar-item-img"src="{{selected===index?item.selectedIconPath:item.iconPath}}"/image
viewclass="tab-bar-item-text"style="color:{{selected===index?selectedColor:color}}"{{item.text}}/view
/view
/view
//src/custom-tab-bar/index.js
Component({
data:{
selected:0,
color:"#8d939f",
selectedColor:"#e3eaf9",
list:[{
pagePath:"/pages/index/index",
iconPath:"../static/tabbar/home01.png",
selectedIconPath:"../static/tabbar/home02.png",
text:"首页"
},{
pagePath:"/pages/my/my",
iconPath:"../static/tabbar/user01.png",
selectedIconPath:"../static/tabbar/user02.png",
text:"我的"
}]
},
attached(){
},
methods:{
switchTab(e){
constdata=e.currentTarget.dataset
consturl=data.path
wx.switchTab({url})
this.setData({
selected:data.index
})
}
}
})
//src/custom-tab-bar/index.json
{
"component":true
}
// src/custom-tab-bar/index.wxss
.tab-bar {
position: fixed;
bottom: calc(16rpx + env(safe-area-inset-bottom));
left: 0;
right: 0;
height: 100rpx;
background: linear-gradient(180deg, rgba(13, 15, 26, 0.95) 0%, rgba(42, 50, 76, 0.95) 100%);
box-shadow: 0rpx 4rpx 16rpx 0px rgba(0, 0, 0, 0.12);
display: flex;
width: calc(100% - 2 * 36rpx);
border-radius: 36rpx;
margin: 0 auto;
}
.tab-bar-item {
flex: 1;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.tab-bar-item .tab-bar-item-img {
width: 32rpx;
height: 32rpx;
}
.tab-bar-item .tab-bar-item-text {
margin-top: 10rpx;
font-size: 20rpx;
}
最后,「关键坑」注意:每个tab页都有自己的tabbar实例:
因此需要每个tab页渲染时设置一下自定义tabbar组件的 activeIndex(我这里变量名是selected):
如果是原生小程序开发像官网那样写就好,如果是uniapp开发,需要:
onShow(()={
constcurrentPage=getCurrentPages()[0];//获取当前页面实例
constcurrentTabBar=currentPage?.getTabBar?.();
//设置当前tab页的下标index
currentTabBar?.setData({selected:0
})
效果:
4. IOS适配安全距离当用户使用圆形设备访问页面时,就存在“安全区域”和“安全距离”的概念。安全区域指的是一个可视窗口范围,处于安全区域的内容不受圆角(corners)、齐刘海(sensor housing)、小黑条(Home Indicator)的影响。
上图来自designing-websites-for-iphone-x
「uniapp适配:」
uniapp适配安全距离有三个方法:
a. manifest.json配置安全距离//manifest.json
{
"app-plus":{
"safearea":{//可选,JSON对象,安全区域配置
"background":"#RRGGBB",//可选,字符串类型,#RRGGBB格式,安全区域背景颜色
"backgroundDark":"#RRGGBB",//可选,字符串类型,#RRGGBB格式,暗黑模式安全区域背景颜色
"bottom":{//可选,JSON对象,底部安全区域配置
"offset":"auto"//可选,字符串类型,安全区域偏移值,可取值auto、none
},
"left":{//可选,JSON对象,左侧安全区域配置
"offset":"none"//可选,字符串类型,安全区域偏移值,可取值auto、none
},
"right":{//可选,JSON对象,左侧安全区域配置
"offset":"none"//可选,字符串类型,安全区域偏移值,可取值auto、none
}
},
}
}
「问题:」 这种方式显然***不够灵活***,它设置的是单独的背景色,如果需要下方一个区域是背景图,延伸到底部安全区就满足不了了。
所以,我是将以上的配置设置成none,然后手动适配页面的安全距离:
b. js获取安全距离letapp=uni.getSystemInfoSync()
app.statusBarHeight//手机状态栏的高度
app.bottom//底部安全距离
c. 使用苹果官方推出的css函数env()、constant()适配padding-bottom:constant(safe-area-inset-bottom);/*兼容IOS11.2*/
padding-bottom:env(safe-area-inset-bottom);/*兼容IOS11.2*/
「注意:」 constant和env不能调换位置
可以配合calc使用:
padding-bottom:calc(constant(safe-area-inset-bottom)+20rpx);/*兼容IOS11.2*/
padding-bottom:calc(env(safe-area-inset-bottom)+20rpx);/*兼容IOS11.2*/
「h5适配」
网页适配安全距离的前提是需要将meta name="viewport"标签设置viewport-fit:cover;:
metaname='viewport'content='initial-scale=1,viewport-fit=cover'
这是MDN上关于viewport-fit的解释:
image.png直观一点就是:
上图来自移动端安全区域适配方案
然后再使用env和constant
padding-bottom:constant(safe-area-inset-bottom);/*兼容IOS11.2*/
padding-bottom:env(safe-area-inset-bottom);/*兼容IOS11.2*/
5. 列表滚动相关问题列表滚动如果使用overflow: auto; 在「首次下拉」时(即使触控点在列表内)也「会使整个页面下拉」:
「解决这个问题只需要将内容使用 scroll-view 包裹即可:」
scroll-viewscroll-yclass="max-h-[800rpx]overflow-auto"/scroll-view
「下拉刷新将列表滚动到顶部:」
小程序默认使用webview渲染,如果需要Skyline渲染引擎需要配置,而srcoll-view标签在webview中有个独有的属性enhanced,启用后可通过ScrollViewContext操作 scroll-view:
scroll-viewid="scrollview":enhanced="true"scroll-yclass="max-h-[800rpx]overflow-auto"/scroll-view
/**将scrollview滚动到顶部*/
functionscrollToTop(id:string){
wx.createSelectorQuery()
.select(id)
.node()
.exec((res)={
constscrollView=res[0].node;
scrollView.scrollTo({
top:0,
animated:true
})
}
onPullDownRefresh(async()={
console.log('下拉刷新')
try{
awaitfetchList()
}catch(error){
console.log(error)
}finally{
uni.stopPullDownRefresh()
scrollToTop('#scrollview')
}
})
6. 配置小程序用户隐私保护指引文档:小程序隐私协议开发指南
什么时候要配置:但凡你的小程序「用到」上图中「任何一种用户信息」就得配置,否则可能小程序审核不通过,或者以前通过了后面不知道什么时候就会被下架了。
配置的是什么:配置的是将来你的程序打开让用户确认授权的「隐私协议内容」。
如何配置:登录微信公众平台 - 设置 - 服务内容声明 - 用户隐私保护指引 - 修改
隐私弹框触发的流程是什么:程序调用隐私相关接口 —— 微信判断该接口「是否」需要隐私授权 —— 如果「需要」隐私授权「且」开发者没有对其响应(注册onNeedPrivacyAuthorization的监听事件)「则」主动弹出官方弹框(此时隐私相关接口调用处于pending状态,如果用户拒绝将会报{"errMsg":" getLocation:fail privacy permission is not authorized", "errno":104})。
代码逻辑:配置并「等待审核通过」后,进行以下步骤:
「1. 配置 __usePrivacyCheck__: true」
尽管官方文档说明2023年10月17日之后无论是否配置改字段,隐私相关功能都会启用,但是实际尝试后发现还是得「配置上才生效」。
//manifest.config.ts
'mp-weixin':{
__usePrivacyCheck__:true
},
「2. 自定义隐私弹框组件」
尽管官方提供了官方隐私弹框组件,但是真机上没有生效,于是还是使用了自定义隐私弹框。
我是直接在插件市场找了一个下载量最多的插件,兼容vue2和vue3。
在小程序对应的页面:
WsWxPrivacyid="privacy-popup"@agree="onAgree"@disagree="onDisAgree"/WsWxPrivacy
functiononAgree(){}
functiononDisAgree(){}
tip: 这部分逻辑相对于业务是「几乎没有耦合」的,甚至如果没有特殊需求agree和disagree事件都不用写。如果将来官方主动弹框没问题了,那这个逻辑可以直接删掉。
「3. 业务代码」
举个例子,我这里隐私相关接口是uni.getLocation获取用户地理位置。
functionhandleCheckLocation(){
returnnewPromise((resolve,reject)={
uni.getLocation({
type:'gcj02',
success:async(res)={
console.log('当前位置:',res)
try{
letr=awaitcheckLocation({
lon:res.longitude.toString(),
lat:res.latitude.toString(),
})
//...
resolve('success')
}catch(error){
reject(error)
}
},
fail:(error)={
console.log('获取位置失败:',error)
reject(error)
}
})
})
}
以上代码,在调用uni.getLocation时,微信自动「发起位置授权」,发起位置授权「之前」又会自动发起「隐私授权」。到此,这一流程是ok的。但是,「如果用户拒绝了隐私授权,或者拒绝了位置授权」,该怎么办?
「如果拒绝了隐私授权」,下次调用隐私相关接口时还会再次弹出隐私授权弹框。
「如果拒绝了位置授权」,下次调用就不会弹出位置授权弹框,但可以通过uni.getSetting来判断用户是否拒绝过,再通过wx.openSetting让用户打开设置界面手动开启授权。代码如下:
functiongetLocationSetting(){
uni.getSetting({
success:(res)={
console.log('获取设置:',res)
if(res.authSetting['scope.userLocation']){
//已经授权,可以直接调用getLocation获取位置
handleCheckLocation()
}elseif(res.authSetting['scope.userLocation']===false){
//用户已拒绝授权,引导用户到设置页面开启
wx.showModal({
title:'您未开启地理位置授权',
content:'请在设置中开启授权',
success:res={
if(res.confirm){
wx.openSetting({
success(settingRes){
if(settingRes.authSetting['scope.userLocation']){
//用户打开了授权,再次获取地理位置
handleCheckLocation()
}
}
})
}
}
})
}else{
//首次使用功能,请求授权
uni.authorize({
scope:'scope.userLocation',
success(){
handleCheckLocation()
}
})
}
}
})
}
当然你也可以「封装」一下:
functiongetSetting(scopeName:string,cb:()=any){
uni.getSetting({
success:(res)={
console.log('获取设置:',res)
if(res.authSetting[scopeName]){
//已经授权,可以直接调用
cb()
}elseif(res.authSetting[scopeName]===false){
//用户已拒绝授权,引导用户到设置页面开启
wx.showModal({
title:'您未开启相关授权',
content:'请在设置中开启授权',
success:res={
if(res.confirm){
wx.openSetting({
success(settingRes){
if(settingRes.authSetting[scopeName]){
//用户打开了授权,再次获取地理位置
cb()
}
}
})
}
}
})
}else{
//首次使用功能,请求授权
uni.authorize({
scope:scopeName,
success(){
cb()
}
})
}
}
})
}
这样,整个隐私协议指引流程就完整了。
关注公众号,“技术干货”及时达!
阅读原文
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。 项目经理在线