乐于分享
好东西不私藏

Uniapp 一键适配多端:H5 / 小程序 / APP 统一适配方案

Uniapp 一键适配多端:H5 / 小程序 / APP 统一适配方案

Uniapp 一键适配多端:H5 / 小程序 / APP 统一适配方案

作为移动端适配系列的收官之作,这篇讲透 Uniapp 如何用一套代码搞定所有端。


一、Uniapp 为什么是多端适配的最优解

做移动端开发,最头疼的不是写业务,而是适配。H5 要考虑浏览器兼容,小程序要处理各个平台差异,APP 还要区分 iOS 和 Android。维护三四套代码,bug 都能给你写出一本书来。

Uniapp 就是来解决这个问题的。

核心优势

一套代码 → 编译输出 → H5 + 微信小程序 + 支付宝小程序 + 抖音小程序 + APP (iOS/Android)
方案
学习成本
开发效率
性能
适用场景
Uniapp
低(Vue 开发者直接上手)
中等
业务型项目、快速交付
Taro
中(React 语法)
中等
需要 React 生态
React Native
原生性能要求高
Flutter
高(Dart 语言)
高度定制、复杂动画

Uniapp 适合的场景:

  • 业务驱动的项目,追求快速迭代

  • 不想维护多套代码

  • 主要用户在小程序端,需要快速扩展到 H5 和 APP

  • 团队熟悉 Vue 技术栈


二、Uniapp 适配核心概念

upx 单位:Uniapp 的私有神器

upx 是 Uniapp 的核心适配单位,类似于 rem,但更简单。

核心规则:

  • 设计稿宽度默认 750upx(对应 iPhone 6 的 375 逻辑像素)

  • upx 会自动转换为各端适配的单位

upx 转换比例 = 屏幕宽度 / 750实际换算示例:┌─────────────┬──────────────┬────────────┐│   屏幕宽度  │   1upx = ?   │  100upx = │ ├─────────────┼──────────────┼────────────┤│   375px     │   0.5px      │   50px    ││   414px     │   0.552px    │   55.2px  ││   375px     │   0.5rem     │   50px    │└─────────────┴──────────────┴────────────┘

为什么设计稿用 750upx?

  • 历史原因:早期 iPhone 屏幕宽度 640,用 750 方便换算

  • 750 是偶数,除以 2 就是 375,方便

  • 现在的 upx 公式是动态的,不用死记硬背

使用方式:

/* 直接写数值,Uniapp 会自动转换 */.box {  width: 750upx;  /* 占满整个屏幕宽度 */  height: 200upx;  padding: 20upx;  font-size: 28upx;}

三、统一适配配置(核心章节)

pages.json 全局配置

这是所有适配的起点,配置好这个就成功了一半。

{  "globalStyle": {    "navigationBarTextStyle": "black",    "navigationBarTitleText": "项目名称",    "navigationBarBackgroundColor": "#ffffff",    "backgroundColor": "#f5f5f5",    "app-plus": {      "titleNView": {        "autoBackButton": true,        "buttonsWithSefArea": true      },      "safeArea": "auto"    }  }}

安全区域配置

各平台安全区域配置存在差异,需要分别处理:

{  "app-plus": {    "titleNView": {      "buttonsWithSefArea": true    },    "safeArea": "auto"  },  "mp-weixin": {    "appid": "your-appid",    "setting": {      "style": "v2"    },    "usingComponents": true  },  "mp-alipay": {    "defaultTitle": "项目名称"  }}

四、H5 端适配

H5 特殊处理

// App.vueexport default {  onLaunch() {    // #ifdef H5    if (process.env.VUE_APP_PLATFORM === 'h5') {      document.addEventListener('DOMContentLoaded', () => {        document.documentElement.style.fontSize = 'calc(100vw / 7.5)px'      })    }    // #endif  }}

H5 下 rem 方案(可选用)

/* App.vue 或全局样式 *//* H5 端用 rem,Uniapp 默认 upx */page {  font-size: calc(100vw / 7.5);  /* 设计稿 750upx 基准 */  background-color: #f5f5f5;}

⚠️ 注意:100vw 包含滚动条,需要减去滚动条宽度:

page {  font-size: calc((100vw - 17px) / 7.5);  /* 17px 是滚动条宽度 */}

五、小程序端适配

微信小程序安全区域配置

// pages.json{  "mp-weixin": {    "appid": "your-appid",    "setting": {      "style": "v2",      "postcss": true    },    "usingComponents": true,    "permission": {      "scope.userLocation": {        "desc": "你的位置信息将用于小程序定位"      }    }  }}

动态获取安全区域

onLoad() {  // #ifdef MP-WEIXIN  const systemInfo = uni.getSystemInfoSync()  const safeArea = systemInfo.safeArea  this.safeAreaInsets = {    top: safeArea.top,    bottom: safeArea.bottom,    left: safeArea.left,    right: safeArea.right  }  // #endif}

完整示例:带安全区域的页面模板

<template>  <view class="page">    <!-- 顶部安全区域 -->    <view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>    <!-- 页面内容 -->    <view class="content">内容</view>    <!-- 底部安全区域 -->    <view class="tabbar" :style="{ paddingBottom: safeAreaInsets.bottom + 'px' }">      TabBar    </view>  </view></template><script>export default {  data() {    return {      statusBarHeight: 0,      safeAreaInsets: { top: 0, bottom: 0, left: 0, right: 0 }    }  },  onLoad() {    this.initSafeArea()  },  methods: {    initSafeArea() {      const systemInfo = uni.getSystemInfoSync()      this.statusBarHeight = systemInfo.statusBarHeight || 20      if (systemInfo.safeArea) {        this.safeAreaInsets = systemInfo.safeArea      } else {        // 兼容旧版        this.safeAreaInsets = {          top: systemInfo.statusBarHeight || 20,          bottom: systemInfo.windowHeight - systemInfo.safeArea.bottom,          left: 0,          right: systemInfo.screenWidth        }      }    }  }}</script>

条件编译:处理各平台差异

// 条件编译,处理平台差异// #ifdef H5const platform = 'h5'// #endif// #ifdef MP-WEIXINconst platform = 'weixin'// #endif// #ifdef APP-PLUSconst platform = 'app'// #endifswitch (platform) {  case 'h5':    // H5 端处理    break  case 'weixin':    // 微信小程序处理    break  case 'app':    // APP 端处理    break}

六、APP 端适配

iOS 安全区域

// App.vueexport default {  onLaunch() {    // #ifdef APP-PLUS    const systemInfo = uni.getSystemInfoSync()    if (systemInfo.platform === 'ios') {      console.log('iOS 平台')    }    // #endif  }}

APP 状态栏配置

// pages.json{  "pages": [    {      "path": "pages/index/index",      "style": {        "navigationBarTitleText": "首页",        "navigationBarBackgroundColor": "#ffffff",        "navigationBarTextStyle": "black",        "enablePullDownRefresh": true,        "app-plus": {          "titleNView": {            "type": "transparent",            "buttonsWithSefArea": true          }        }      }    }  ]}

APP 刘海屏适配(CSS)

/* App.vue 或全局样式 *//* iOS 安全区域 */page {  padding-top: constant(safe-area-inset-top);  padding-bottom: constant(safe-area-inset-bottom);}/* Android 安全区域 */page {  padding-top: env(safe-area-inset-top);  padding-bottom: env(safe-area-inset-bottom);}

七、统一适配工具函数

封装一个跨端适配 hook,哪里用到哪里引入:

// utils/safeArea.jsexport function useSafeArea() {  const systemInfo = uni.getSystemInfoSync()  const isH5 = process.env.VUE_APP_PLATFORM === 'h5'  const isMp = ['mp-weixin', 'mp-alipay', 'mp-baidu', 'mp-toutiao',                 'mp-lark', 'mp-qq', 'mp-kuaishou'].includes(systemInfo.platform)  // 获取安全区域  const safeArea = systemInfo.safeArea || {    top: systemInfo.statusBarHeight || 20,    bottom: systemInfo.windowHeight - (systemInfo.screenHeight - systemInfo.windowHeight),    left: 0,    right: systemInfo.screenWidth  }  // 获取状态栏高度  const statusBarHeight = systemInfo.statusBarHeight || 20  // 获取平台  const platform = systemInfo.platform // ios / android  return {    safeArea,    statusBarHeight,    platform,    isH5,    isMp,    screenWidth: systemInfo.screenWidth,    screenHeight: systemInfo.screenHeight,    windowWidth: systemInfo.windowWidth,    windowHeight: systemInfo.windowHeight  }}

使用方式:

<template>  <view class="page" :style="{ paddingTop: safeArea.top + 'px' }">    <view class="header">顶部</view>    <view class="content">内容</view>    <view class="footer" :style="{ paddingBottom: safeArea.bottom + 'px' }">      底部    </view>  </view></template><script>import { useSafeArea } from '@/utils/safeArea'export default {  setup() {    const { safeArea, statusBarHeight, platform } = useSafeArea()    return {      safeArea,      statusBarHeight,      platform    }  }}</script>

八、常见坑与避坑指南

🔴 坑一:upx 在 H5 端不生效

问题
原因
解决
H5 端 upx 不生效
H5 端 upx 基于 viewport
H5 端改用 rem 或动态设置 font-size
/* 解决方案:H5 端单独处理 */page {  font-size: calc(100vw / 7.5);}

🔴 坑二:安全区域在 APP 端不准

问题
原因
解决
部分 Android 机型安全区域不准
系统兼容性问题
写死一个 fallback 值
const safeArea = systemInfo.safeArea || {  top: systemInfo.statusBarHeight || 20,  bottom: 34,  // fallback 值  left: 0,  right: systemInfo.screenWidth}

🔴 坑三:小程序端样式不生效

问题
原因
解决
:hover 等伪类不生效
小程序不支持部分 CSS 选择器
用条件编译或 class 控制
/* 小程序不支持 hover,用其他方式处理 */

🔴 坑四:多端样式差异

问题
原因
解决
各端样式不一致
各端渲染引擎不同
用条件编译加载不同样式文件
/* app.vue 中使用条件编译 *//* #ifdef H5 */@import './styles/h5.css'/* #endif *//* #ifdef MP-WEIXIN */@import './styles/weixin.css'/* #endif *//* #ifdef APP-PLUS */@import './styles/app.css'/* #endif */

🔴 坑五:APP 端键盘弹起问题

<!-- 解决方案:使用 adjust-position 属性 --><input   adjust-position   :cursor-spacing="100"  @focus="onInputFocus"  @blur="onInputBlur"/>

九、Uniapp 多端适配检查清单

✅ 开发前检查这些配置,开发后对照验收

检查项
说明
状态
pages.json 安全区域配置
app-plus.safeArea = "auto"
状态栏高度获取
uni.getSystemInfoSync().statusBarHeight
底部安全区域
safeArea.bottom
upx 单位使用
设计稿 750upx,直接写数值
条件编译
平台差异用 #ifdef 处理
1px 边框
各端统一用 transform: scaleY(0.5)
字体大小
用偶数,避免奇数模糊
H5 端 viewport
检查是否有溢出
APP 键盘适配
input 加 adjust-position
多端样式兼容
避免使用小程序不支持的选择器

🚀 快速开发模板

<template>  <view class="page">    <!-- 自动适配状态栏 -->    <view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>    <!-- 固定顶部导航 -->    <view class="nav-bar">导航</view>    <!-- 页面内容 -->    <scroll-view class="scroll-view" scroll-y>      内容区域    </scroll-view>    <!-- 固定底部按钮 -->    <view class="fixed-bottom" :style="{ paddingBottom: safeArea.bottom + 'px' }">      提交按钮    </view>  </view></template><script>export default {  data() {    return {      statusBarHeight: 20,      safeArea: { top: 0, bottom: 0 }    }  },  onLoad() {    this.initDevice()  },  methods: {    initDevice() {      const info = uni.getSystemInfoSync()      this.statusBarHeight = info.statusBarHeight || 20      if (info.safeArea) {        this.safeArea = info.safeArea      }    }  }}</script>

总结

Uniapp 的适配核心就三点:

  1. upx 单位 - 设计稿 750upx,直接写数值,全端自动适配

  2. 安全区域 - 用 getSystemInfoSync() 获取,动态设置 padding

  3. 条件编译 - 平台差异用 #ifdef 处理,一套代码多端运行

记住这个公式:

成功适配 = 750upx + safeArea + 条件编译

把这三板斧用好,多端适配就是手到擒来的事。