跳转到内容

Uniapp

一、Uniapp 基础认知

1.1 什么是 Uniapp?

Uniapp = Vue 语法 + 跨端编译。 你写 Vue 代码,Uniapp 编译器帮你转成各平台代码。一套代码 → H5网页 + 微信小程序 + 支付宝小程序 + iOS App + Android App。

核心优势:

  • 使用 Vue 语法开发,学习成本低
  • 一套代码编译到 10+ 平台
  • 丰富的插件市场(数千款插件)
  • 内置组件和 API 自动适配各平台
  • 支持条件编译,灵活处理平台差异

Uniapp vs Taro:

特性

Uniapp

Taro

语法

Vue 2/3

React / Vue 3

IDE

HBuilderX(强绑定)

VS Code 等任意编辑器

App 端

性能优异(独立渲染引擎)

依赖 React Native

小程序

支持微信/支付宝/百度/字节/QQ

支持微信/支付宝/百度/字节/QQ

社区生态

插件市场 + DCloud 生态

NPM 生态为主

适合人群

Vue 开发者

React 开发者

1.2 环境搭建

方式一:HBuilderX(推荐新手)

  1. 下载 HBuilderX:https://www.dcloud.io/hbuilderx.html
  2. 文件 → 新建 → 项目 → 选择 uni-app 模板
  3. 运行 → 运行到浏览器/小程序模拟器

方式二:CLI 命令行

# 创建项目(Vue 3 版本)
npx degit dcloudio/uni-preset-vue#vite-ts my-uni-app

cd my-uni-app
npm install

# 启动 H5 开发
npm run dev:h5

# 启动微信小程序开发(需打开微信开发者工具)
npm run dev:mp-weixin

# 启动 App 开发
npm run dev:app

# 构建各平台
npm run build:h5
npm run build:mp-weixin

微信小程序调试配置:

  1. 打开微信开发者工具 → 设置 → 安全设置 → 开启服务端口
  2. 运行 npm run dev:mp-weixin
  3. 微信开发者工具会自动打开项目

1.3 项目结构

my-uni-app/
├── pages/                  # 页面目录(自动注册路由)
│   ├── index/
│   │   └── index.vue
│   └── detail/
│       └── detail.vue
├── components/             # 组件目录
├── static/                 # 静态资源(图片、字体等,不编译)
├── store/                  # 状态管理(Pinia / Vuex)
├── utils/                  # 工具函数
│   ├── request.js          # 请求封装
│   └── auth.js             # 认证工具
├── api/                    # 接口API层
├── pages.json              # ⭐ 核心配置:路由、窗口样式、tabBar
├── manifest.json           # 应用配置:AppID、权限、SDK
├── uni.scss                # 全局样式变量(无需引入即可使用)
├── App.vue                 # 应用入口:生命周期、全局样式
├── main.js                 # 主入口:注册插件
└── uni_modules/            # 插件市场下载的插件

二、pages.json — 路由与页面核心配置

pages.json 是 Uniapp 最核心的配置文件,所有页面必须在这里注册才能访问,它替代了 Vue Router 的功能。

2.1 基础配置示例

{
  "pages": [
    {
      "path": "pages/index/index",
      "style": {
        "navigationBarTitleText": "首页",
        "navigationBarBackgroundColor": "#ffffff",
        "enablePullDownRefresh": true,
        "backgroundColor": "#f5f5f5"
      }
    },
    {
      "path": "pages/detail/detail",
      "style": {
        "navigationBarTitleText": "详情"
      }
    },
    {
      "path": "pages/user/login",
      "style": {
        "navigationBarTitleText": "登录"
      }
    }
  ],
  "globalStyle": {
    "navigationBarTextStyle": "black",
    "navigationBarTitleText": "我的应用",
    "navigationBarBackgroundColor": "#ffffff",
    "backgroundColor": "#f5f5f5"
  },
  "tabBar": {
    "color": "#999999",
    "selectedColor": "#007AFF",
    "backgroundColor": "#ffffff",
    "borderStyle": "black",
    "list": [
      {
        "pagePath": "pages/index/index",
        "text": "首页",
        "iconPath": "static/tab/home.png",
        "selectedIconPath": "static/tab/home-active.png"
      },
      {
        "pagePath": "pages/mine/mine",
        "text": "我的",
        "iconPath": "static/tab/mine.png",
        "selectedIconPath": "static/tab/mine-active.png"
      }
    ]
  },
  "subPackages": [
    {
      "root": "pages-sub/order",
      "pages": [
        { "path": "list", "style": { "navigationBarTitleText": "订单列表" } },
        { "path": "detail", "style": { "navigationBarTitleText": "订单详情" } }
      ]
    }
  ],
  "preloadRule": {
    "pages/index/index": {
      "network": "all",
      "packages": ["pages-sub/order"]
    }
  }
}

2.2 pages.json 关键配置项说明

配置项

说明

关键属性

pages

页面路由表(数组,第一项为首页)

path, style(导航栏样式、下拉刷新等)

globalStyle

全局窗口样式

navigationBarBackgroundColor, enablePullDownRefresh

tabBar

底部导航栏(最少2项,最多5项)

color, selectedColor, list

subPackages

分包配置(优化首屏加载)

root(分包根目录), pages

preloadRule

分包预加载规则

network(all/wifi), packages

condition

启动模式配置(开发调试用)

current(当前模式), list(模式列表)


三、页面开发核心语法

3.1 页面跳转(5种方式)

// 1. navigateTo — 打开新页面(保留当前页,可返回,最多10层)
uni.navigateTo({
  url: '/pages/detail/detail?id=123&name=hello'
})

// 2. redirectTo — 关闭当前页,打开新页面(不可返回)
uni.redirectTo({
  url: '/pages/login/login'
})

// 3. switchTab — 跳转到 tabBar 页面(关闭所有非 tabBar 页面)
uni.switchTab({
  url: '/pages/index/index'
})

// 4. reLaunch — 关闭所有页面,打开新页面
uni.reLaunch({
  url: '/pages/index/index'
})

// 5. navigateBack — 返回上一页
uni.navigateBack({
  delta: 1  // 返回层数,默认1
})

// ========== 接收参数(目标页面) ==========
export default {
  onLoad(options) {
    console.log(options.id)   // '123'
    console.log(options.name) // 'hello'
  }
}

跳转方法对比:

方法

是否保留当前页

是否可返回

适用场景

navigateTo

列表→详情

redirectTo

登录成功→首页

switchTab

切换 tabBar 页

reLaunch

重新启动应用

navigateBack

-

返回上一页

3.2 页面生命周期(完整顺序)

export default {
  // 1. 页面加载时触发(只触发一次,可获取页面参数)
  onLoad(options) {
    console.log('页面加载', options)
    // 适合:发起初始数据请求
  },

  // 2. 页面显示时触发(每次显示都会触发)
  onShow() {
    console.log('页面显示')
    // 适合:从其他页面返回时刷新数据
  },

  // 3. 页面首次渲染完成(只触发一次)
  onReady() {
    console.log('页面渲染完成')
    // 适合:获取节点信息、初始化地图/图表
  },

  // 4. 页面隐藏时触发(跳转到其他页面或切后台)
  onHide() {
    console.log('页面隐藏')
    // 适合:暂停播放/计时器
  },

  // 5. 页面卸载时触发(redirectTo / navigateBack / reLaunch)
  onUnload() {
    console.log('页面卸载')
    // 适合:清除定时器、取消网络请求
  },

  // 下拉刷新(需在 pages.json 配置 enablePullDownRefresh)
  onPullDownRefresh() {
    // 刷新数据...
    uni.stopPullDownRefresh()  // 停止刷新动画
  },

  // 页面触底(可用来加载更多)
  onReachBottom() {
    console.log('触底了')
    // 加载下一页数据
  },

  // 页面滚动
  onPageScroll(e) {
    console.log('滚动距离', e.scrollTop)
  },

  // 分享(小程序)
  onShareAppMessage() {
    return {
      title: '分享标题',
      path: '/pages/index/index'
    }
  }
}

生命周期触发顺序(完整链路):

进入页面: onLoad → onShow → onReady
切后台:   onHide
切前台:   onShow
跳转他页: onHide
返回本页: onShow
卸载页面: onUnload (redirectTo/reLaunch/navigateBack)

3.3 常用组件速查

<template>
  <view>
    <!-- 基础容器(类似 div) -->
    <view class="box" @click="handleClick">
      <text>纯文本(类似 span,长按可选)</text>
    </view>

    <!-- 图片(mode 控制裁剪方式) -->
    <image
      src="/static/logo.png"
      mode="aspectFill"
      style="width: 200rpx; height: 200rpx;"
    />

    <!-- 输入框 -->
    <input
      v-model="form.name"
      placeholder="请输入姓名"
      type="text"
      maxlength="20"
    />

    <!-- 文本域 -->
    <textarea
      v-model="form.remark"
      placeholder="请输入备注"
      :maxlength="200"
    />

    <!-- 按钮 -->
    <button type="primary" @click="handleSubmit">提交</button>
    <button type="default" plain>普通按钮</button>
    <button type="warn" size="mini">警告按钮</button>

    <!-- 滚动区域(scroll-view) -->
    <scroll-view scroll-y style="height: 500rpx;" @scrolltolower="loadMore">
      <view v-for="item in list" :key="item.id">
        {{ item.name }}
      </view>
    </scroll-view>

    <!-- 轮播图 -->
    <swiper :indicator-dots="true" :autoplay="true" :interval="3000">
      <swiper-item v-for="(item, index) in banners" :key="index">
        <image :src="item.image" mode="aspectFill" />
      </swiper-item>
    </swiper>

    <!-- 图标(内置图标库) -->
    <uni-icons type="home" size="20" color="#333" />

    <!-- 表单组件 -->
    <picker mode="selector" :range="options" @change="onPickerChange">
      <view>当前选择:{{ options[selected] }}</view>
    </picker>

    <picker mode="date" @change="onDateChange">
      <view>日期:{{ date }}</view>
    </picker>
  </view>
</template>

图片 mode 属性值:

说明

scaleToFill

拉伸填满(默认,会变形)

aspectFit

等比缩放,完整显示(留白)

aspectFill

等比缩放,填满裁剪(推荐)

widthFix

宽度不变,高度自适应


四、常用 API 调用

4.1 网络请求(request)

// 基础请求
uni.request({
  url: 'https://api.example.com/users',
  method: 'GET',
  data: { page: 1, size: 10 },
  header: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer ' + uni.getStorageSync('token')
  },
  success: (res) => {
    console.log('成功', res.data)
  },
  fail: (err) => {
    console.error('失败', err)
  },
  complete: () => {
    console.log('请求完成(不管成功失败)')
  }
})

// Promise 写法(推荐)
async function getUsers() {
  try {
    const res = await uni.request({
      url: 'https://api.example.com/users',
      method: 'GET'
    })
    console.log(res.data)
  } catch (err) {
    console.error(err)
  }
}

请求拦截器封装(推荐在每个项目中使用):

// utils/request.js
const BASE_URL = 'https://api.example.com'

const request = (options) => {
  return new Promise((resolve, reject) => {
    const token = uni.getStorageSync('token')

    uni.request({
      url: BASE_URL + options.url,
      method: options.method || 'GET',
      data: options.data || {},
      header: {
        'Content-Type': 'application/json',
        ...(token ? { 'Authorization': 'Bearer ' + token } : {}),
        ...options.header
      },
      success: (res) => {
        if (res.statusCode === 200) {
          if (res.data.code === 0) {
            resolve(res.data.data)
          } else {
            uni.showToast({ title: res.data.msg, icon: 'none' })
            reject(res.data)
          }
        } else if (res.statusCode === 401) {
          uni.removeStorageSync('token')
          uni.reLaunch({ url: '/pages/login/login' })
          reject(res)
        } else {
          uni.showToast({ title: '请求失败', icon: 'none' })
          reject(res)
        }
      },
      fail: (err) => {
        uni.showToast({ title: '网络异常', icon: 'none' })
        reject(err)
      }
    })
  })
}

// 封装 GET / POST
export const get = (url, data) => request({ url, method: 'GET', data })
export const post = (url, data) => request({ url, method: 'POST', data })
export const put = (url, data) => request({ url, method: 'PUT', data })
export const del = (url, data) => request({ url, method: 'DELETE', data })

export default request

4.2 本地存储(Storage)

// 同步存储(简单直接)
uni.setStorageSync('token', 'abc123')
uni.setStorageSync('userInfo', { name: '张三', age: 25 })

// 读取
const token = uni.getStorageSync('token')
const userInfo = uni.getStorageSync('userInfo')

// 删除
uni.removeStorageSync('token')

// 清空
uni.clearStorageSync()

// ========== 异步存储 ==========
uni.setStorage({ key: 'cache', data: { ... }, success: () => {} })
uni.getStorage({ key: 'cache', success: (res) => { console.log(res.data) } })
uni.removeStorage({ key: 'cache' })

// ========== 存储信息 ==========
uni.getStorageInfoSync()
// 返回: { keys: ['token', 'userInfo'], currentSize: 5, limitSize: 10240 }
// limitSize 单位 KB(小程序限制 10MB)

4.3 常用 UI API

// 提示框(Toast)
uni.showToast({
  title: '操作成功',
  icon: 'success',     // success / error / loading / none
  duration: 2000,
  mask: true           // 是否透明遮罩(防止重复点击)
})
uni.hideToast()

// 加载中
uni.showLoading({ title: '加载中...', mask: true })
uni.hideLoading()

// 确认弹窗
uni.showModal({
  title: '提示',
  content: '确定要删除吗?',
  success: (res) => {
    if (res.confirm) {
      console.log('用户点击确定')
    } else {
      console.log('用户点击取消')
    }
  }
})

// ActionSheet(底部菜单)
uni.showActionSheet({
  itemList: ['拍照', '从相册选择', '取消'],
  success: (res) => {
    console.log('选择了第' + res.tapIndex + '项')
  }
})

// 导航栏加载
uni.showNavigationBarLoading()
uni.hideNavigationBarLoading()

// 设置导航栏标题
uni.setNavigationBarTitle({ title: '新标题' })

// 页面滚动到顶部
uni.pageScrollTo({ scrollTop: 0, duration: 300 })

// 拨打电话
uni.makePhoneCall({ phoneNumber: '10086' })

// 复制到剪贴板
uni.setClipboardData({ data: '要复制的文本' })

4.4 媒体 API

// 选择图片
uni.chooseImage({
  count: 1,           // 最多选择数量
  sizeType: ['compressed'], // original 原图 / compressed 压缩
  sourceType: ['album', 'camera'], // album 相册 / camera 拍照
  success: (res) => {
    const tempFilePaths = res.tempFilePaths
    // 上传图片
    uni.uploadFile({
      url: 'https://api.example.com/upload',
      filePath: tempFilePaths[0],
      name: 'file',
      success: (uploadRes) => {
        console.log(JSON.parse(uploadRes.data))
      }
    })
  }
})

// 预览图片
uni.previewImage({
  urls: ['https://xxx.com/img1.jpg', 'https://xxx.com/img2.jpg'],
  current: 0          // 当前显示的索引
})

4.5 位置与地图

// 获取位置(需用户授权)
uni.getLocation({
  type: 'gcj02',  // wgs84(GPS坐标)/ gcj02(国测局坐标)
  success: (res) => {
    console.log('经度', res.longitude)
    console.log('纬度', res.latitude)
  }
})

// 选择位置(打开地图选点)
uni.chooseLocation({
  success: (res) => {
    console.log('位置名称', res.name)
    console.log('详细地址', res.address)
    console.log('经纬度', res.longitude, res.latitude)
  }
})

五、条件编译:一套代码适配多端

条件编译 让同一份代码在不同平台执行不同逻辑,是 UniApp 的核心能力。

// ========== JS 中的条件编译 ==========
// #ifdef H5
console.log('这段只在 H5 平台执行')
// #endif

// #ifdef MP-WEIXIN
console.log('这段只在微信小程序执行')
// #endif

// #ifdef MP-ALIPAY
console.log('这段只在支付宝小程序执行')
// #endif

// #ifdef APP-PLUS
console.log('这段只在 App 端执行')
// #endif

// #ifndef H5
console.log('非 H5 平台执行')
// #endif

// ========== 模板中的条件编译 ==========
<!-- #ifdef MP-WEIXIN -->
<button open-type="getUserInfo">微信专属按钮</button>
<!-- #endif -->

<!-- #ifdef H5 -->
<a href="/login">H5 专属链接</a>
<!-- #endif -->

<!-- 各平台都会显示 -->
<view>通用内容</view>

// ========== 样式中的条件编译 ==========
/* #ifdef MP-WEIXIN */
.title { font-size: 28rpx; }
/* #endif */

/* #ifdef H5 */
.title { font-size: 14px; }
/* #endif */

平台代码对照表:

代码

平台

H5

移动端网页

MP-WEIXIN

微信小程序

MP-ALIPAY

支付宝小程序

MP-BAIDU

百度小程序

MP-TOUTIAO

字节小程序

MP-QQ

QQ小程序

APP-PLUS

App 端(Android + iOS)

APP-PLUS-NVUE

App 端 weex 模式


六、状态管理

6.1 Pinia(Vue 3,推荐)

npm install pinia
// main.js
import { createSSRApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

export function createApp() {
  const app = createSSRApp(App)
  app.use(createPinia())
  return { app }
}
// store/user.js
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    token: uni.getStorageSync('token') || '',
    userInfo: null
  }),
  getters: {
    isLogin: (state) => !!state.token
  },
  actions: {
    async login(username, password) {
      // 小程序端使用 uni.login()
      // #ifdef MP-WEIXIN
      const { code } = await uni.login()
      // #endif

      const res = await uni.request({
        url: 'https://api.example.com/login',
        method: 'POST',
        data: { username, password }
      })

      this.token = res.data.token
      this.userInfo = res.data.userInfo
      uni.setStorageSync('token', this.token)
    },
    logout() {
      this.token = ''
      this.userInfo = null
      uni.removeStorageSync('token')
      uni.reLaunch({ url: '/pages/login/login' })
    }
  }
})

6.2 Vuex(Vue 2)

// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    token: uni.getStorageSync('token') || '',
    userInfo: null
  },
  getters: {
    isLogin: (state) => !!state.token
  },
  mutations: {
    SET_TOKEN(state, token) {
      state.token = token
      uni.setStorageSync('token', token)
    },
    SET_USER(state, user) {
      state.userInfo = user
    },
    LOGOUT(state) {
      state.token = ''
      state.userInfo = null
      uni.removeStorageSync('token')
    }
  },
  actions: {
    async login({ commit }, { username, password }) {
      const res = await uni.request({
        url: 'https://api.example.com/login',
        method: 'POST',
        data: { username, password }
      })
      commit('SET_TOKEN', res.data.token)
      commit('SET_USER', res.data.userInfo)
    }
  }
})

七、组件化开发

7.1 自定义组件

<!-- components/UserCard.vue -->
<template>
  <view class="user-card" @click="handleClick">
    <image :src="avatar" class="avatar" mode="aspectFill" />
    <view class="info">
      <text class="name">{{ name }}</text>
      <text class="desc">{{ desc }}</text>
    </view>
  </view>
</template>

<script>
export default {
  name: 'UserCard',
  props: {
    name: { type: String, default: '' },
    avatar: { type: String, default: '/static/default-avatar.png' },
    desc: { type: String, default: '' }
  },
  // 声明组件内事件
  emits: ['click'],
  methods: {
    handleClick() {
      this.$emit('click', { name: this.name })
    }
  }
}
</script>

<style scoped>
.user-card {
  display: flex;
  align-items: center;
  padding: 20rpx;
}
.avatar { width: 80rpx; height: 80rpx; border-radius: 50%; }
.name { font-size: 30rpx; font-weight: bold; }
</style>

7.2 组件通信方式

方式

方向

示例

props

父→子

&lt;user-card :name="userName" /&gt;

$emit

子→父

this.$emit('change', value)

$refs

父→子(调用子方法)

this.$refs.child.method()

provide/inject

祖先→后代

provide('theme', 'dark')

uni.$on / $emit

全局

uni.$on('updateList', callback)

// 全局事件总线(使用 uni 内置的事件系统)
// A 页面触发
uni.$emit('userUpdated', { id: 1, name: '张三' })

// B 页面监听
export default {
  onLoad() {
    uni.$on('userUpdated', (data) => {
      console.log('收到更新', data)
    })
  },
  onUnload() {
    uni.$off('userUpdated')  // 页面卸载时移除监听,防止内存泄漏
  }
}

八、常用插件与组件库

8.1 社区推荐组件库

组件库

说明

安装

uni-ui

DCloud 官方组件库,与 UniApp 深度集成

HBuilderX 插件市场导入

uView UI

最流行的 UniApp 组件库

npm install uview-ui

FirstUI

高性能跨端组件库

npm install firstui-uni

ThorUI

轻量组件库

插件市场安装

8.2 uView UI 使用

npm install uview-ui
// main.js
import uView from 'uview-ui'
Vue.use(uView)

// uni.scss 中引入
@import 'uview-ui/theme.scss';
// pages.json 配置 easycom 自动引入
{
  "easycom": {
    "^u-(.*)": "uview-ui/components/u-$1/u-$1.vue"
  }
}
<template>
  <!-- 之后可以在任意页面直接使用 uView 组件,无需 import -->
  <u-button type="primary">确定</u-button>
  <u-input v-model="value" placeholder="请输入" />
  <u-cell-group>
    <u-cell title="设置" icon="setting" />
  </u-cell-group>
  <u-empty text="暂无数据" />
  <u-loadmore :status="loadStatus" />
</template>

九、打包发布

9.1 各平台发布命令

# H5 网页 — 打包到 dist/build/h5
npm run build:h5

# 微信小程序 — 打包到 dist/dev/mp-weixin(开发)/ dist/build/mp-weixin(生产)
npm run build:mp-weixin
# 然后在微信开发者工具中上传

# App 端
npm run build:app-plus
# 使用 HBuilderX 云打包或本地打包(需 Android Studio / Xcode)

9.2 manifest.json 关键配置

{
  "mp-weixin": {
    "appid": "wx你的小程序AppID",        // 微信小程序 AppID
    "setting": {
      "es6": true,
      "minified": true,
      "postcss": true
    }
  },
  "h5": {
    "title": "网站标题",
    "router": {
      "mode": "hash",                   // hash / history
      "base": "/"
    }
  },
  "app-plus": {
    "distribute": {
      "android": {
        "packageName": "com.example.app",
        "permissions": []
      },
      "ios": {
        "bundleIdentifier": "com.example.app"
      }
    }
  }
}

9.3 小程序发布流程

  1. 配置 AppID:在 manifest.json 中填写微信小程序 AppID
  2. 构建生产版本npm run build:mp-weixin
  3. 微信开发者工具上传:打开 dist/build/mp-weixin → 点击"上传"
  4. 微信公众平台提交审核:登录 mp.weixin.qq.com → 版本管理 → 提交审核
  5. 审核通过 → 点击"发布"上线

十、UniApp 开发最佳实践

10.1 性能优化

  • 分包加载:主包只放首页和公共组件,其余页面放分包
  • 路由懒加载:pages.json 中无需特殊配置,UniApp 默认按页面拆分
  • 长列表:使用 &lt;recycle-view&gt; 虚拟列表组件
  • 图片优化:使用 WebP 格式,合理使用 mode="aspectFill"
  • 减少 setData:避免一次性 set 大量数据,数据做 diff 后再更新

10.2 适配方案

// 设计稿通常为 750px 宽度
// UniApp 推荐使用 rpx 单位:1rpx = 屏幕宽度/750
// 在 375px 屏幕上 1rpx = 0.5px
// 在 414px 屏幕上 1rpx ≈ 0.55px

// uni.scss 中定义全局变量
$primary-color: #007AFF;
$text-color: #333;
$bg-color: #f5f5f5;
$border-color: #eee;
$padding-base: 20rpx;

10.3 安全注意

  • AppSecret 只能放在服务端,禁止前端硬编码
  • 数据加密传输(HTTPS)
  • 请求参数做好校验
  • 敏感数据不要存在 Storage 中(小程序端 Storage 是明文存储)

10.4 学习资源

  • UniApp 官方文档:https://uniapp.dcloud.net.cn
  • 插件市场:https://ext.dcloud.net.cn
  • uView UI:https://www.uviewui.com
  • 微信小程序文档:https://developers.weixin.qq.com/miniprogram/dev
  • HBuilderX 下载:https://www.dcloud.io/hbuilderx.html