404 lines
7.8 KiB
Markdown
404 lines
7.8 KiB
Markdown
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
# 多端适配方案
|
|||
|
|
|
|||
|
|
## 一、平台差异处理
|
|||
|
|
|
|||
|
|
### 1.1 条件编译
|
|||
|
|
使用 UniApp 的条件编译处理不同平台的差异:
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// #ifdef MP-WEIXIN
|
|||
|
|
// 微信小程序特有代码
|
|||
|
|
wx.login()
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #ifdef H5
|
|||
|
|
// H5特有代码
|
|||
|
|
window.location.href = '/login'
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #ifdef APP-PLUS
|
|||
|
|
// App特有代码
|
|||
|
|
plus.runtime.restart()
|
|||
|
|
// #endif
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 1.2 平台判断工具
|
|||
|
|
使用封装的平台判断工具:
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
import { getPlatform, isMiniProgram, isH5, isApp } from '@/utils/platform'
|
|||
|
|
|
|||
|
|
if (isMiniProgram()) {
|
|||
|
|
// 小程序端逻辑
|
|||
|
|
} else if (isH5()) {
|
|||
|
|
// H5端逻辑
|
|||
|
|
} else if (isApp()) {
|
|||
|
|
// App端逻辑
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 二、API 适配
|
|||
|
|
|
|||
|
|
### 2.1 统一 API 封装
|
|||
|
|
对不同平台的 API 进行统一封装:
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// 跨端导航
|
|||
|
|
export const navigateTo = (url, params = {}) => {
|
|||
|
|
const query = Object.keys(params)
|
|||
|
|
.map(key => `${key}=${encodeURIComponent(params[key])}`)
|
|||
|
|
.join('&')
|
|||
|
|
const fullUrl = query ? `${url}?${query}` : url
|
|||
|
|
|
|||
|
|
uni.navigateTo({ url: fullUrl })
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 跨端存储
|
|||
|
|
export const setStorage = (key, value) => {
|
|||
|
|
// #ifdef MP-WEIXIN
|
|||
|
|
wx.setStorageSync(key, value)
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #ifdef H5
|
|||
|
|
localStorage.setItem(key, JSON.stringify(value))
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #ifdef APP-PLUS
|
|||
|
|
plus.storage.setItem(key, JSON.stringify(value))
|
|||
|
|
// #endif
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.2 第三方登录适配
|
|||
|
|
不同平台的第三方登录实现:
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// 微信登录
|
|||
|
|
export const wechatLogin = () => {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
// #ifdef MP-WEIXIN
|
|||
|
|
wx.login({
|
|||
|
|
success: (res) => {
|
|||
|
|
resolve(res.code)
|
|||
|
|
},
|
|||
|
|
fail: reject
|
|||
|
|
})
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #ifdef H5
|
|||
|
|
// H5微信授权登录
|
|||
|
|
const appId = 'YOUR_APP_ID'
|
|||
|
|
const redirectUri = encodeURIComponent(window.location.href)
|
|||
|
|
window.location.href = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${redirectUri}&response_type=code&scope=snsapi_userinfo#wechat_redirect`
|
|||
|
|
// #endif
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 三、样式适配
|
|||
|
|
|
|||
|
|
### 3.1 尺寸单位适配
|
|||
|
|
- **小程序端**:使用 `rpx`(750rpx = 屏幕宽度)
|
|||
|
|
- **H5端**:使用 `rem` 或 `vw`
|
|||
|
|
- **App端**:使用 `rpx` 或 `upx`
|
|||
|
|
|
|||
|
|
```scss
|
|||
|
|
// 统一使用 rpx,UniApp 会自动转换
|
|||
|
|
.container {
|
|||
|
|
width: 750rpx;
|
|||
|
|
padding: 30rpx;
|
|||
|
|
font-size: 28rpx;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.2 安全区域适配
|
|||
|
|
处理刘海屏、底部安全区域:
|
|||
|
|
|
|||
|
|
```scss
|
|||
|
|
.page {
|
|||
|
|
// 顶部安全区域
|
|||
|
|
padding-top: constant(safe-area-inset-top);
|
|||
|
|
padding-top: env(safe-area-inset-top);
|
|||
|
|
|
|||
|
|
// 底部安全区域
|
|||
|
|
padding-bottom: constant(safe-area-inset-bottom);
|
|||
|
|
padding-bottom: env(safe-area-inset-bottom);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.3 状态栏高度适配
|
|||
|
|
```javascript
|
|||
|
|
// 获取状态栏高度
|
|||
|
|
const systemInfo = uni.getSystemInfoSync()
|
|||
|
|
const statusBarHeight = systemInfo.statusBarHeight
|
|||
|
|
|
|||
|
|
// 在样式中使用
|
|||
|
|
const navBarHeight = statusBarHeight + 44 // 44为导航栏高度
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 四、功能适配
|
|||
|
|
|
|||
|
|
### 4.1 分享功能
|
|||
|
|
```javascript
|
|||
|
|
// 小程序分享
|
|||
|
|
// #ifdef MP-WEIXIN
|
|||
|
|
onShareAppMessage() {
|
|||
|
|
return {
|
|||
|
|
title: '分享标题',
|
|||
|
|
path: '/pages/index/index',
|
|||
|
|
imageUrl: '/static/share.png'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// H5分享
|
|||
|
|
// #ifdef H5
|
|||
|
|
const shareToWechat = () => {
|
|||
|
|
// 调用微信 JS-SDK
|
|||
|
|
wx.updateAppMessageShareData({
|
|||
|
|
title: '分享标题',
|
|||
|
|
desc: '分享描述',
|
|||
|
|
link: window.location.href,
|
|||
|
|
imgUrl: 'https://example.com/share.png'
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
// #endif
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.2 支付功能
|
|||
|
|
```javascript
|
|||
|
|
// 统一支付接口
|
|||
|
|
export const pay = (orderInfo) => {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
// #ifdef MP-WEIXIN
|
|||
|
|
wx.requestPayment({
|
|||
|
|
timeStamp: orderInfo.timeStamp,
|
|||
|
|
nonceStr: orderInfo.nonceStr,
|
|||
|
|
package: orderInfo.package,
|
|||
|
|
signType: 'MD5',
|
|||
|
|
paySign: orderInfo.paySign,
|
|||
|
|
success: resolve,
|
|||
|
|
fail: reject
|
|||
|
|
})
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #ifdef H5
|
|||
|
|
// H5微信支付
|
|||
|
|
WeixinJSBridge.invoke('getBrandWCPayRequest', orderInfo, (res) => {
|
|||
|
|
if (res.err_msg === 'get_brand_wcpay_request:ok') {
|
|||
|
|
resolve(res)
|
|||
|
|
} else {
|
|||
|
|
reject(res)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #ifdef APP-PLUS
|
|||
|
|
// App支付
|
|||
|
|
plus.payment.request('wxpay', orderInfo, resolve, reject)
|
|||
|
|
// #endif
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.3 定位功能
|
|||
|
|
```javascript
|
|||
|
|
// 获取位置
|
|||
|
|
export const getLocation = () => {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
uni.getLocation({
|
|||
|
|
type: 'gcj02',
|
|||
|
|
success: (res) => {
|
|||
|
|
resolve({
|
|||
|
|
latitude: res.latitude,
|
|||
|
|
longitude: res.longitude
|
|||
|
|
})
|
|||
|
|
},
|
|||
|
|
fail: (err) => {
|
|||
|
|
// #ifdef H5
|
|||
|
|
// H5使用浏览器定位API
|
|||
|
|
if (navigator.geolocation) {
|
|||
|
|
navigator.geolocation.getCurrentPosition(
|
|||
|
|
(position) => {
|
|||
|
|
resolve({
|
|||
|
|
latitude: position.coords.latitude,
|
|||
|
|
longitude: position.coords.longitude
|
|||
|
|
})
|
|||
|
|
},
|
|||
|
|
reject
|
|||
|
|
)
|
|||
|
|
} else {
|
|||
|
|
reject(new Error('浏览器不支持定位'))
|
|||
|
|
}
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #ifndef H5
|
|||
|
|
reject(err)
|
|||
|
|
// #endif
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 五、性能适配
|
|||
|
|
|
|||
|
|
### 5.1 小程序分包
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"pages": [
|
|||
|
|
"pages/index/index",
|
|||
|
|
"pages/user/index"
|
|||
|
|
],
|
|||
|
|
"subPackages": [
|
|||
|
|
{
|
|||
|
|
"root": "subPackages/order",
|
|||
|
|
"pages": [
|
|||
|
|
"list/index",
|
|||
|
|
"detail/index"
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"root": "subPackages/product",
|
|||
|
|
"pages": [
|
|||
|
|
"list/index",
|
|||
|
|
"detail/index"
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"preloadRule": {
|
|||
|
|
"pages/index/index": {
|
|||
|
|
"network": "all",
|
|||
|
|
"packages": ["subPackages/order"]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.2 图片懒加载
|
|||
|
|
```vue
|
|||
|
|
<template>
|
|||
|
|
<!-- 小程序端使用 lazy-load -->
|
|||
|
|
<image
|
|||
|
|
:src="imageSrc"
|
|||
|
|
lazy-load
|
|||
|
|
mode="aspectFill"
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
<!-- H5端使用 Intersection Observer -->
|
|||
|
|
<img
|
|||
|
|
v-lazy="imageSrc"
|
|||
|
|
alt="图片"
|
|||
|
|
/>
|
|||
|
|
</template>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.3 长列表优化
|
|||
|
|
```vue
|
|||
|
|
<template>
|
|||
|
|
<!-- 使用虚拟列表 -->
|
|||
|
|
<recycle-list :list="dataList">
|
|||
|
|
<template v-slot="{ item }">
|
|||
|
|
<view class="list-item">
|
|||
|
|
{{ item.title }}
|
|||
|
|
</view>
|
|||
|
|
</template>
|
|||
|
|
</recycle-list>
|
|||
|
|
</template>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 六、兼容性处理
|
|||
|
|
|
|||
|
|
### 6.1 API 兼容性检查
|
|||
|
|
```javascript
|
|||
|
|
// 检查API是否支持
|
|||
|
|
if (uni.canIUse('getSystemInfoSync')) {
|
|||
|
|
const systemInfo = uni.getSystemInfoSync()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 版本号比较
|
|||
|
|
const compareVersion = (v1, v2) => {
|
|||
|
|
const arr1 = v1.split('.')
|
|||
|
|
const arr2 = v2.split('.')
|
|||
|
|
const len = Math.max(arr1.length, arr2.length)
|
|||
|
|
|
|||
|
|
for (let i = 0; i < len; i++) {
|
|||
|
|
const num1 = parseInt(arr1[i] || 0)
|
|||
|
|
const num2 = parseInt(arr2[i] || 0)
|
|||
|
|
|
|||
|
|
if (num1 > num2) return 1
|
|||
|
|
if (num1 < num2) return -1
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 使用示例
|
|||
|
|
const systemInfo = uni.getSystemInfoSync()
|
|||
|
|
if (compareVersion(systemInfo.SDKVersion, '2.10.0') >= 0) {
|
|||
|
|
// 支持新API
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6.2 样式兼容性
|
|||
|
|
```scss
|
|||
|
|
// 使用 CSS 变量实现主题切换
|
|||
|
|
:root {
|
|||
|
|
--primary-color: #1890ff;
|
|||
|
|
--text-color: #333;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.button {
|
|||
|
|
background-color: var(--primary-color);
|
|||
|
|
color: var(--text-color);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 使用 autoprefixer 自动添加前缀
|
|||
|
|
.flex-container {
|
|||
|
|
display: flex;
|
|||
|
|
// 自动添加 -webkit-flex
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 七、调试技巧
|
|||
|
|
|
|||
|
|
### 7.1 多端调试
|
|||
|
|
- **微信小程序**:使用微信开发者工具
|
|||
|
|
- **H5**:使用浏览器开发者工具
|
|||
|
|
- **App**:使用 HBuilderX 真机调试
|
|||
|
|
|
|||
|
|
### 7.2 日志输出
|
|||
|
|
```javascript
|
|||
|
|
// 开发环境输出日志
|
|||
|
|
if (process.env.NODE_ENV === 'development') {
|
|||
|
|
console.log('调试信息:', data)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 使用条件编译输出平台信息
|
|||
|
|
// #ifdef MP-WEIXIN
|
|||
|
|
console.log('当前平台:微信小程序')
|
|||
|
|
// #endif
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 7.3 错误监控
|
|||
|
|
```javascript
|
|||
|
|
// 全局错误捕获
|
|||
|
|
uni.onError((error) => {
|
|||
|
|
console.error('全局错误:', error)
|
|||
|
|
// 上报到错误监控平台
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 网络错误监控
|
|||
|
|
uni.onNetworkStatusChange((res) => {
|
|||
|
|
if (!res.isConnected) {
|
|||
|
|
uni.showToast({
|
|||
|
|
title: '网络已断开',
|
|||
|
|
icon: 'none'
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
```
|