乐于分享
好东西不私藏

只会傻傻的用Uniapp写微信小程序?快来收下这份跨平台开发避坑指南和实战教程吧

只会傻傻的用Uniapp写微信小程序?快来收下这份跨平台开发避坑指南和实战教程吧

书接上篇,我们今天继续来学习一下 UniApp 聊一个让无数跨端开发者头秃的话题:不同平台下的功能和API差异,附带实现思路和所有源码示例,以及常见避坑指南和开发实践。

此系列文章将带领你从移动端跨平台开发入门到精通,如果你也喜欢关注APP、小程序、公众号、H5等等应用的开发,可以持续关注后续更新,避免错过宝贵的知识分享。

致开发者的忠告: AI编程盛行的今天,我们并不是不需要学习技术,而是更应该专研技术,拥有把控全局的架构设计思维才能在AI盛行的未来有立足之地。

言归正传,咱们今天继续来聊一聊如何区分不同平台下用uniapp开发时的UI差异、功能特性差异、API差异等等问题,带你提前避坑,轻松驾驭使用Uniapp解决环境兼容问题!

你可能会想:“UniApp不是一套代码多端运行吗?为啥还要关心这个?”但等你真的开始上线,就会遇到:

  • 在H5跑得好好的定位,到微信小程序里没反应!

  • iOS上丝般顺滑的滚动,到安卓卡成PPT!

  • 微信小程序能用的API,到公众号里直接报错!

  • 安卓打包没问题,iOS一运行就白屏!

  • 鸿蒙手机上,页面布局全乱套!

别慌!今天我就带你系统梳理一遍——H5、安卓APP、iOS APP、微信小程序、微信公众号、鸿蒙APP,这六大平台的差异点、坑点、解决方案,一次性讲透。以后遇到跨端问题,你就能像老中医一样,“望闻问切”对症下药。


一、先搞明白:UniApp是怎么“跨端”的?

在讲差异之前,咱得先理解UniApp的跨端原理。这样才能明白,为什么会有差异。

UniApp = 编译器 + 运行时

  • 编译器:把你的.vue文件,根据不同平台“翻译”成对应平台的代码。比如编译到微信小程序,就生成wxml/wxss/js;编译到H5,就生成HTML/CSS/JS。

  • 运行时:在每个平台都有一个“壳”,负责解析你的代码,调用平台原生能力。

关键点来了UniApp虽然封装了很多跨端API,但底层调用的还是各平台的原生能力。这就意味着:

  1. 同一套API,在不同平台上的实现方式可能不同

  2. 各平台的能力边界不同(小程序不能操作DOM,APP可以)

  3. 各平台的UI规范不同(iOS和安卓的导航栏高度就不一样)

理解了这一点,咱们再来看差异,就不会觉得奇怪了。


二、六大平台差异全景图

咱们先画个图,把这六兄弟的“性格特点”摸清楚:

平台 运行环境 核心限制 特有优势 常见坑点
H5 浏览器 浏览器沙箱 可以直接操作DOM,URL直接访问 兼容各种浏览器内核
安卓APP Android系统 需要各种权限申请 原生能力最强,可调用所有硬件 机型碎片化严重
iOS APP iOS系统 苹果审核严格 性能优化好,用户付费意愿高 隐私权限要求苛刻
微信小程序 微信环境 包大小限制2MB 微信生态,分享方便 很多Web API不能用
微信公众号 微信内置浏览器 基于WebView 可复用H5代码 跳转授权逻辑特殊
鸿蒙APP HarmonyOS 新系统,API可能变化 万物互联 部分API兼容性待验证

三、UI差异:别让布局在不同手机上“变形”

1️⃣ 导航栏高度(最坑爹的差异)

这是新人最容易踩的坑——写死了导航栏高度

/* 错误写法 */.nav-bar {height44px;  /* iOS上合适,安卓上可能偏小 */}

真实差异

  • iOS:状态栏20pt(非全面屏)或44pt(全面屏),导航栏44pt

  • 安卓:状态栏24-30dp,导航栏48dp

  • 微信小程序:胶囊菜单高度32px,右侧边距也有差异

✅ 解决方案:动态获取

// 获取状态栏高度constsystemInfo=uni.getSystemInfoSync()conststatusBarHeight=systemInfo.statusBarHeight// 状态栏高度// 获取胶囊信息(仅微信小程序)letmenuButtonInfo= {}// #ifdef MP-WEIXINmenuButtonInfo=uni.getMenuButtonBoundingClientRect()// #endif// 计算导航栏高度letnavHeight// #ifdef H5navHeight=44// H5固定,或者从CSS变量获取// #endif// #ifdef APP-PLUSnavHeight=systemInfo.platform==='ios'?44 : 48// #endif// #ifdef MP-WEIXINnavHeight= (menuButtonInfo.top-statusBarHeight*2+menuButtonInfo.height// #endif

2️⃣ 底部安全区域(全面屏适配)

iPhone X以后的全面屏,底部有个“小黑条”,安卓也有虚拟导航栏。

✅ 解决方案:使用env()constant()

.safe-bottom {/* 兼容iOS 11.2+ */padding-bottomconstant(safe-area-inset-bottom);/* 兼容iOS 11.2+ */padding-bottomenv(safe-area-inset-bottom);}/* 或者用条件编译动态设置 */// #ifdefAPP-IOS || APP-ANDROIDconstsafeArea = uni.getSystemInfoSync().safeArea// 计算底部安全距离// #endif

3️⃣ 字体单位:rpx vs px

UniApp提供了rpx(响应式px),但要注意:

  • 小程序和APPrpx完美适配,750rpx就是屏幕宽度

  • H5rpx也能用,但底层会转换为vw,某些复杂布局可能有误差

  • 公众号:同H5

✅ 最佳实践

  • 一般使用rpx,简单省事

  • 对于需要精确1px边框的场景,用1px配合transform: scale(0.5)

  • 对于字体大小,建议使用px,因为大多数设计稿字体就是固定的


四、API差异:同一段代码,不同命运

1️⃣ 登录授权:三大流派

平台 登录方式 获取用户信息 注意事项
H5 短信/密码登录 表单提交 不能静默获取手机号
APP uni.login + 后端 uni.getUserProfile(需弹窗) iOS需要配置签名,其实也可以跟H5端一样通过账号登录
微信小程序 uni.login拿code uni.getUserProfile(按钮触发) 不能直接弹窗,必须用户点击
微信公众号 OAuth2.0跳转 静默授权只能拿openid 需配置网页授权域名

示例:微信小程序登录(与APP不同!)

// 微信小程序:必须先通过按钮触发<buttonopen-type="getUserProfile"@click="getUserProfile">获取头像昵称</button>// 然后在方法里getUserProfile() {uni.getUserProfile({desc'用于完善会员资料',success: (res=> {// 这里拿到用户信息,但openid还得uni.login拿    }  })}// APP登录:可以直接调// #ifdef APP-PLUSuni.login({provider'apple'// 或 'weixin'success: (loginRes=> {// 直接拿到授权信息  }})// #endif

2️⃣ 定位API:权限要求不同

// 看似相同的代码,在不同平台行为不同uni.getLocation({type'wgs84',success: (res=> {console.log('经度:'+res.longitude)  }})

差异点

  • H5:浏览器会弹权限框,需要https环境,部分浏览器不支持

  • APP:需要在manifest配置定位权限,安卓6.0+需要动态申请

  • 微信小程序:需在pages.json配置permission字段,用户首次使用会弹窗

  • 公众号:同H5,但微信内置浏览器有特殊定位API(wx.getLocation

✅ 安全写法

asyncfunctionsafeGetLocation() {// 先判断平台能力if (!uni.canIUse('getLocation')) {uni.showToast({ title'当前环境不支持定位'icon'none' })return  }// #ifdef APP-PLUS// APP需要先申请权限constpermResult=awaitrequestLocationPermission()if (!permResultreturn// #endif// #ifdef MP-WEIXIN// 小程序需要先检查是否已授权constauth=awaitcheckLocationAuth()if (!auth) {uni.openSetting() // 打开设置页return  }// #endif// 调用定位uni.getLocation({success: (res=> {},fail: (err=> {// #ifdef H5// H5降级方案:用第三方地图API// #endif    }  })}

3️⃣ 存储API:容量限制

平台 单个key上限 总上限 同步API
H5 取决于浏览器 5-10MB 支持
APP 不受限 不受限 支持
微信小程序 1MB 10MB 支持,但超限会报错

微信小程序特殊坑 :

// 在微信小程序中,这样写可能会崩uni.setStorageSync('bigData'largeObject// 超过1MB直接报错// ✅ 解决方案:分片存储functionsaveLargeData(keydata) {conststr=JSON.stringify(data)constMAX_SIZE=900*1024// 留100KB缓冲if (str.length<MAX_SIZE) {uni.setStorageSync(keystr)  } else {// 分片存储逻辑constchunks= []for (leti=0i<str.lengthi+=MAX_SIZE) {chunks.push(str.substr(iMAX_SIZE))    }// 存储分片...  }}

4️⃣ 支付:完全不同

  • H5:只能调起支付宝/微信的网页支付,体验差

  • APP:可调起微信/支付宝APP支付,需配置universal link(iOS)或应用签名(安卓)

  • 微信小程序uni.requestPayment调起微信支付,最简单

  • 公众号:JSAPI支付,需要用户的openid

其实推荐使用开源的聚合支付服务PddPay,支持私有化部署、Web支付、H5支付、微信公众号支付、小程序支付、APP支付都支持、支付宝/微信支付都支持,web支付和H5支付可以自动检测用户访问环境,比如在支付宝中打开、微信中打开、PC web页面打开、手机浏览器打开等等的页面风格和支付方式会自动最优匹配,简化用户操作,提高支付成功率,支付测试地址: https://pddon.cn/payment-demo/index.html

✅ 最佳实践:支付逻辑全部交给后端,前端只负责调起

// 后端返回调起支付的参数constpayParams=awaitrequestPay(orderId)// #ifdef MP-WEIXINuni.requestPayment({timeStamppayParams.timeStamp,nonceStrpayParams.nonceStr,packagepayParams.package,signType'MD5',paySignpayParams.paySign,success: () => {}})// #endif// #ifdef APP-PLUSuni.requestPayment({provider'wxpay'// 或 'alipay'orderInfopayParams// 不同支付平台格式不同success: () => {}})// #endif// #ifdef H5// 跳转到支付链接window.location.href=payParams.payUrl// #endif

五、生命周期差异:有的执行,有的不执行

有个学员曾经崩溃地问我:“前辈,我的onLoad在iOS上好好的,到安卓就不执行了!” 

真相是:不同平台的生命周期触发时机有细微差异。

常见差异点:

  1. onLoad传参:小程序和H5没问题,但APP端如果从推送通知打开,参数可能不一样

  2. onShow:小程序切后台再回来会触发,H5从其他tab返回也会触发,但APP端行为可能不同

  3. onReady获取DOM:在小程序里,onReady里用uni.createSelectorQuery是安全的,但在H5里可能DOM还没完全渲染

✅ 解决方案:延迟执行或重试机制

onReady() {// #ifdef MP-WEIXINthis.getDomInfo()// #endif// #ifdef H5this.$nextTick(() => {this.getDomInfo()  })// #endif}// 更稳妥的方案:重试几次functionqueryWithRetry(selectormaxRetry=3) {returnnewPromise((resolve=> {letretry=0constquery= () => {constview=uni.createSelectorQuery().select(selector)view.boundingClientRect(data=> {if (data) {resolve(data)        } elseif (retry<maxRetry) {retry++setTimeout(query100*retry// 逐渐增加延迟        } else {resolve(null)        }      }).exec()    }query()  })}

六、配置差异:manifest.json里的秘密

1️⃣ 微信小程序特有配置

// manifest.json{"mp-weixin": {"appid""wx1234567890","setting": {"urlCheck"true,      // 开发时关闭域名校验"es6"true,"minified"true    },"permission": {"scope.userLocation": {"desc""你的位置信息将用于查找附近门店"      }    },"requiredPrivateInfos": ["getLocation"// 声明需要获取位置  }}

2️⃣ APP特有配置

{"app-plus": {"distribute": {"android": {"permissions": ["<uses-permission android:name=\"android.permission.CAMERA\"/>","<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>"        ]      },"ios": {"plistcmds": ["Add :NSLocationWhenInUseUsageDescription string '需要您的位置信息来查找附近门店'"        ]      }    }  }}

注意:iOS的隐私描述必须写清楚,否则审核会被拒!


七、实战:条件编译——处理差异的终极武器

说了这么多差异,那怎么在代码里优雅地处理呢?答案是:条件编译

1️⃣ 什么是条件编译?

就是用特殊的注释,告诉编译器:这段代码只编译到特定平台。

// #ifdef H5console.log('这段代码只在H5平台出现')// #endif// #ifndef MP-WEIXINconsole.log('这段代码不在微信小程序平台出现')// #endif// #ifdef APP-PLUS || MP-WEIXINconsole.log('在APP或微信小程序平台出现')// #endif

2️⃣ 支持的文件类型 

  • .vue / .nvue / .uvue

  • .js / .uts

  • .css

  • pages.json

  • 各种预编译语言(.scss.less.ts等)

3️⃣ 平台取值一览表 

平台 取值 说明
APP APP-PLUS 或 APP 所有APP(含安卓/iOS)
APP-安卓 APP-ANDROID 仅安卓APP
APP-iOS APP-IOS 仅iOS APP
APP-鸿蒙 APP-HARMONY 仅鸿蒙APP
H5 H5 或 WEB H5/Web
微信小程序 MP-WEIXIN 微信小程序
支付宝小程序 MP-ALIPAY 支付宝小程序
百度小程序 MP-BAIDU 百度小程序
抖音小程序 MP-TOUTIAO 抖音小程序
QQ小程序 MP-QQ QQ小程序
公众号 H5 公众号本质是H5,需单独处理

4️⃣ 实际应用场景

场景1:不同平台的跳转逻辑

functionhandleLogin() {// #ifdef H5window.location.href='/login.html'// #endif// #ifdef MP-WEIXINuni.navigateTo({ url'/pages/login/login' })// #endif// #ifdef APP-PLUS// APP可能用原生登录页plus.runtime.launchApplication({pname'com.example.login'  })// #endif}

场景2:样式差异

/* #ifdef H5 */.box {height100vh/* H5用视口高度 */}/* #endif *//* #ifdef APP-PLUS */.box {height100%/* APP用百分比 */}/* #endif */

场景3:pages.json配置差异

{"pages": [    {"path""pages/index/index","style": {"navigationBarTitleText""首页"      }    }  ],// #ifdef MP-WEIXIN"usingComponents": {"custom""/components/custom"  },"permission": {"scope.userLocation": {"desc""你的位置信息将用于小程序"    }  }// #endif}

5️⃣ 条件编译的注意事项 

  • 嵌套深度限制:微信开发者工具对条件编译嵌套层数限制为5层,超过会导致编译异常

  • 语法正确性:无论条件编译是否生效,代码本身必须语法正确(JSON不能有多余逗号)

  • 静态资源路径:微信小程序要求相对路径,H5可以用绝对路径,建议统一用相对路径或别名


八、常见错误及解决方案(血泪经验)

💥 错误1:在微信小程序里用了window对象

现象:代码中有window.locationdocument,小程序编译报错。原因:小程序的逻辑层不在浏览器环境,没有DOM/BOM。✅ 解决方案:用条件编译包裹,或用uni API替代。

// 错误constwidth=window.innerWidth// 正确// #ifdef H5constwidth=window.innerWidth// #endif// #ifndef H5constwidth=uni.getSystemInfoSync().windowWidth// #endif

💥 错误2:在APP里用了localStorage,退出登录后数据还在

现象:APP用户退出登录,用uni.setStorageSync存的用户数据还在。原因:APP的存储是持久化的,需要手动清除。✅ 解决方案:封装存储方法,区分登录态。

// storage.jsexportconststorage= {set(keyvalueisLoginData=false) {if (isLoginData) {// 登录态数据存到独立命名空间constuserId=store.getters.userIduni.setStorageSync(`user_${userId}_${key}`value)    } else {uni.setStorageSync(keyvalue)    }  },logout() {// 退出登录时清除所有登录态数据constuserId=store.getters.userId// 遍历清除...  }}

💥 错误3:iOS和安卓的日期解析差异

现象new Date('2024-05-06 12:00:00')在安卓上正常,在iOS上返回Invalid Date。原因:iOS不支持中划线格式的日期字符串,需要替换为斜杠。✅ 解决方案:统一格式化。

functionsafeParseDate(dateStr) {// iOS只支持 yyyy/mm/dd 格式returnnewDate(dateStr.replace(/-/g'/'))}

💥 错误4:H5和微信小程序的页面跳转混用

现象:在H5里用uni.navigateTo跳转正常,到小程序里点了没反应。原因:小程序页面栈最多10层,超过后跳转会失败。✅ 解决方案:封装跳转方法,自动降级。

functionsafeNavigateTo(url) {// #ifdef MP-WEIXINconstpages=getCurrentPages()if (pages.length>=10) {uni.redirectTo({ url }) // 改用替换return  }// #endifuni.navigateTo({ url })}

💥 错误5:鸿蒙手机上安全区域获取不到

现象uni.getSystemInfoSync().safeArea返回undefined。原因:部分鸿蒙版本API兼容性问题。✅ 解决方案:降级处理。

constsystemInfo=uni.getSystemInfoSync()constsafeArea=systemInfo.safeArea|| {// 降级方案:使用屏幕尺寸减去默认值bottomsystemInfo.screenHeight-50,topsystemInfo.statusBarHeight||30}

九、完整示例:一个兼容6大平台的定位组件

最后,我写一个完整的定位组件,把所有技巧都用上:

<!-- components/LocationPicker.vue --><template><viewclass="location-picker"@click="chooseLocation"><viewclass="location-icon i-carbon:location"></view><textclass="location-text">{{ address || '点击选择位置' }}</text><viewclass="arrow-icon i-carbon:chevron-right"></view></view></template><scriptsetup>import { ref } from'vue'constaddress=ref('')constlatitude=ref(0)constlongitude=ref(0)// 选择位置constchooseLocation=async () => {// 先检查平台能力if (!checkLocationSupport()) {uni.showToast({ title'当前环境不支持定位'icon'none' })return  }// #ifdef H5 || MP-WEIXINawaitrequestLocationAuth()// #endif// #ifdef APP-PLUSawaitrequestAppPermission()// #endif// 调用定位uni.chooseLocation({success: (res=> {address.value=res.namelatitude.value=res.latitudelongitude.value=res.longitude// 根据平台不同,可能需要额外处理// #ifdef MP-WEIXIN// 微信小程序返回的地址可能不完整,可以再调用逆地理编码getAddressByLocation(res.latituderes.longitude)// #endif    },fail: (err=> {// #ifdef H5// H5降级方案:使用第三方地图APIuseThirdPartyMap()// #endif// #ifdef MP-WEIXINif (err.errMsg.includes('auth deny')) {uni.showModal({title'提示',content'需要授权位置信息',success: (res=> {if (res.confirm) {uni.openSetting() // 打开设置页            }          }        })      }// #endif    }  })}// 检查平台是否支持定位constcheckLocationSupport= () => {// #ifdef H5return'geolocation'innavigator// #endif// #ifdef MP-WEIXINreturnuni.canIUse('chooseLocation')// #endif// #ifdef APP-PLUSreturntrue// APP基本都支持// #endif// 默认返回falsereturnfalse}// 微信小程序检查授权constrequestLocationAuth= () => {returnnewPromise((resolve=> {// #ifdef MP-WEIXINuni.getSetting({success: (res=> {if (!res.authSetting['scope.userLocation']) {uni.authorize({scope'scope.userLocation',success: () =>resolve(true),fail: () => {uni.showModal({title'提示',content'需要授权位置信息',success: (modalRes=> {if (modalRes.confirm) {uni.openSetting({success: (settingRes=> {resolve(settingRes.authSetting['scope.userLocation'])                      }                    })                  } else {resolve(false)                  }                }              })            }          })        } else {resolve(true)        }      }    })// #endif// #ifndef MP-WEIXINresolve(true)// #endif  })}// APP权限申请constrequestAppPermission= () => {returnnewPromise((resolve=> {// #ifdef APP-PLUSplus.android?.requestPermissions?.(['android.permission.ACCESS_FINE_LOCATION'], (e=> {resolve(e.deniedAlways?.length===0)    })// #endif// #ifndef APP-PLUSresolve(true)// #endif  })}// 逆地理编码(仅示例,实际需调用地图API)constgetAddressByLocation= (latlng=> {// 调用高德或腾讯地图API}// H5降级方案constuseThirdPartyMap= () => {// #ifdef H5window.open('https://map.baidu.com/mobile/webapp/index/index/')// #endif}</script><stylescoped>.location-picker {displayflex;align-itemscenter;padding24rpx30rpx;background-color#fff;border-radius12rpx;border1rpxsolid#e5e5e5;}.location-icon {font-size32rpx;color#007aff;margin-right16rpx;}.location-text {flex1;font-size28rpx;color#333;}.arrow-icon {font-size28rpx;color#999;}/* 不同平台微调 *//* #ifdef APP-PLUS */.location-picker {border-width1px/* APP上1rpx可能太细 */}/* #endif *//* #ifdef H5 */.location-picker {cursorpointer;}/* #endif */</style>

十、最后的叮嘱

小老弟,跨端开发的本质是在“一套代码”和“平台特性”之间找平衡。我的建议是:

  1. 能用uni API的,尽量用uni API——框架帮你处理了大部分差异

  2. 遇到平台特有需求,用条件编译隔离——不要试图写兼容所有平台的“万能代码”

  3. 提前规划好测试方案——至少要在iOS、安卓、微信小程序、H5上跑一遍

  4. 关注官方更新——各平台每年都会出新版本,API可能变化

最后送你一句口诀:UI用rpx,逻辑用条件,权限动态要,存储分大小,定位兜底保,测试少不了。

好了,去把你的项目跑一遍吧,看看哪些地方还能优化。遇到奇怪的问题,欢迎随时带着报错来找我。

—— 一个在跨端坑里爬出来、现在能躺着过的老鸟 🕊️

加油,未来的全栈大佬!💪如果你也对移动端跨端开发感兴趣,关注我,后续还有更多优质文章分享!

往期相关文章推荐

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » 只会傻傻的用Uniapp写微信小程序?快来收下这份跨平台开发避坑指南和实战教程吧

评论 抢沙发

3 + 3 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮