2025年,UniApp已经成为国内最流行的跨平台开发框架之一。它基于Vue.js,一套代码可以同时发布到iOS、Android、H5以及各类小程序。本文通过一个完整的任务管理应用案例,带你掌握UniApp的项目架构、状态管理、多端适配以及发布流程。
1. 为什么选择UniApp?
UniApp的核心优势在于“一次开发,多端运行”。与React Native或Flutter相比,它更贴近前端开发者的Vue生态,且对国内小程序生态支持极好。
- 多端覆盖:App、H5、微信/支付宝/百度/抖音小程序
- Vue生态:支持Vue3 Composition API,上手快
- 热重载:开发体验接近H5开发
- 插件丰富:原生插件市场提供大量功能模块
2. 环境搭建与项目创建
首先安装HBuilderX(UniApp官方IDE)或使用CLI方式:
# 使用CLI创建项目(需要Node.js 18+) npx @dcloudio/uni-app-cli create task-manager cd task-manager npm install npm run dev:mp-weixin # 运行到微信小程序 npm run dev:h5 # 运行到H5
项目目录结构:
task-manager/ ├── src/ │ ├── pages/ # 页面文件 │ ├── components/ # 公共组件 │ ├── store/ # 状态管理(Pinia) │ ├── api/ # API请求 │ ├── utils/ # 工具函数 │ ├── App.vue # 根组件 │ ├── main.js # 入口文件 │ ├── pages.json # 页面路由配置 │ ├── manifest.json # 应用配置 │ └── uni.scss # 全局样式变量 ├── package.json └── vite.config.js
3. 页面路由与TabBar配置
在 pages.json 中配置页面路径和底部导航:
{
"pages": [
{"path": "pages/index/index", "style": {"navigationBarTitleText": "任务列表"}},
{"path": "pages/add/add", "style": {"navigationBarTitleText": "添加任务"}},
{"path": "pages/profile/profile", "style": {"navigationBarTitleText": "个人中心"}}
],
"tabBar": {
"color": "#999",
"selectedColor": "#007aff",
"list": [
{"pagePath": "pages/index/index", "text": "任务", "iconPath": "static/task.png", "selectedIconPath": "static/task_hl.png"},
{"pagePath": "pages/add/add", "text": "添加", "iconPath": "static/add.png", "selectedIconPath": "static/add_hl.png"},
{"pagePath": "pages/profile/profile", "text": "我的", "iconPath": "static/profile.png", "selectedIconPath": "static/profile_hl.png"}
]
}
}
4. 状态管理:使用Pinia构建任务Store
UniApp官方推荐使用Pinia进行状态管理。安装:
npm install pinia
创建 src/store/task.js:
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useTaskStore = defineStore('task', () => {
// 状态
const tasks = ref([
{ id: 1, title: '学习UniApp', done: false, createdAt: '2025-01-15' },
{ id: 2, title: '编写技术文章', done: true, createdAt: '2025-01-14' }
])
// 计算属性
const pendingTasks = computed(() => tasks.value.filter(t => !t.done))
const completedTasks = computed(() => tasks.value.filter(t => t.done))
// 动作
function addTask(title) {
tasks.value.push({
id: Date.now(),
title,
done: false,
createdAt: new Date().toISOString().slice(0, 10)
})
}
function toggleTask(id) {
const task = tasks.value.find(t => t.id === id)
if (task) task.done = !task.done
}
function deleteTask(id) {
tasks.value = tasks.value.filter(t => t.id !== id)
}
return { tasks, pendingTasks, completedTasks, addTask, toggleTask, deleteTask }
})
在 main.js 中注册Pinia:
import { createSSRApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
export function createApp() {
const app = createSSRApp(App)
app.use(createPinia())
return { app }
}
5. 主页面:任务列表(index.vue)
<template>
<view class="container">
<view class="header">
<text class="title">我的任务</text>
<text class="count">待办: {{ store.pendingTasks.length }}</text>
</view>
<view class="task-list">
<view class="task-item" v-for="task in store.tasks" :key="task.id">
<view class="task-left">
<view
class="checkbox"
:class="{ checked: task.done }"
@click="store.toggleTask(task.id)"
>
<text v-if="task.done">✓</text>
</view>
<text class="task-title" :class="{ done: task.done }">{{ task.title }}</text>
</view>
<text class="delete-btn" @click="store.deleteTask(task.id)">删除</text>
</view>
<view v-if="store.tasks.length === 0" class="empty">
<text>暂无任务,点击下方添加</text>
</view>
</view>
</view>
</template>
<script setup>
import { useTaskStore } from '@/store/task'
const store = useTaskStore()
</script>
6. 添加任务页面(add.vue)
<template>
<view class="container">
<view class="form">
<input
class="input"
v-model="title"
placeholder="请输入任务标题"
@confirm="handleSubmit"
/>
<button class="btn" @click="handleSubmit">添加任务</button>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import { useTaskStore } from '@/store/task'
import { useRouter } from '@/utils/router' // uni-app内置路由
const store = useTaskStore()
const title = ref('')
function handleSubmit() {
const trimmed = title.value.trim()
if (!trimmed) {
uni.showToast({ title: '请输入任务内容', icon: 'none' })
return
}
store.addTask(trimmed)
uni.showToast({ title: '添加成功', icon: 'success' })
title.value = ''
// 返回上一页
setTimeout(() => uni.navigateBack(), 500)
}
</script>
7. 多端适配技巧
UniApp使用条件编译处理平台差异,文件后缀或代码中的条件宏:
<!-- 条件编译示例 --> <!-- #ifdef MP-WEIXIN --> <view class="wx-only">仅微信小程序显示</view> <!-- #endif --> <!-- #ifdef H5 --> <view class="h5-only">仅H5显示</view> <!-- #endif --> <!-- #ifndef APP-PLUS --> <view>非App端显示</view> <!-- #endif -->
在JavaScript中:
// #ifdef H5
console.log('仅在H5环境执行')
// #endif
// #ifdef APP-PLUS
console.log('仅在App环境执行')
// #endif
样式适配建议:
- 使用rpx单位(类似小程序的rpx),自动适配屏幕宽度
- 避免使用固定px,改用百分比或flex布局
- 通过
uni.getSystemInfoSync()获取设备信息动态调整
8. 网络请求与API封装
创建 src/api/request.js:
const BASE_URL = 'https://api.example.com'
export function request(config) {
return new Promise((resolve, reject) => {
uni.request({
url: BASE_URL + config.url,
method: config.method || 'GET',
data: config.data || {},
header: {
'Content-Type': 'application/json',
'Authorization': uni.getStorageSync('token') || ''
},
success: (res) => {
if (res.statusCode === 200) {
resolve(res.data)
} else {
uni.showToast({ title: '请求失败', icon: 'error' })
reject(res)
}
},
fail: (err) => {
uni.showToast({ title: '网络错误', icon: 'none' })
reject(err)
}
})
})
}
// 使用示例
export function fetchTasks() {
return request({ url: '/tasks', method: 'GET' })
}
export function createTask(data) {
return request({ url: '/tasks', method: 'POST', data })
}
9. 打包与发布
| 平台 | 打包命令 | 产物路径 |
|---|---|---|
| H5 | npm run build:h5 |
dist/build/h5 |
| 微信小程序 | npm run build:mp-weixin |
dist/build/mp-weixin |
| App (Android) | HBuilderX -> 发行 -> 原生App-云打包 | apk或ipa |
| 支付宝小程序 | npm run build:mp-alipay |
dist/build/mp-alipay |
发布前检查清单:
- 在
manifest.json中配置应用名称、图标、版本号 - 检查各平台权限配置(如相机、定位)
- 使用
uni-app 性能分析工具优化包体积 - 测试不同屏幕尺寸下的UI表现
10. 性能优化与最佳实践
- 组件按需加载:使用
uni.lazyLoad或动态导入 - 列表性能:长列表使用
uni-virtual-list或scroll-view的增强模式 - 图片优化:使用
image组件的lazy-load属性和WebP格式 - 状态管理:避免在Pinia中存储大量数据,使用持久化插件
pinia-plugin-persistedstate - 分包加载:微信小程序超过2MB时使用分包
// 分包配置示例 (pages.json)
{
"subPackages": [
{
"root": "subPages",
"pages": [
{"path": "detail/detail", "style": {}}
]
}
]
}
11. 总结
通过本文的案例,你掌握了UniApp跨平台开发的核心技能:
- 项目创建与目录结构
- 页面路由与TabBar配置
- Pinia状态管理实战
- 多端条件编译与适配
- 网络请求封装
- 打包发布全流程
UniApp让“一套代码,多端运行”成为现实。结合Vue3的Composition API和Pinia,你可以高效构建跨平台应用。现在就开始你的第一个UniApp项目吧!
本文原创,基于UniApp 3.12+和Vue 3.4+。所有代码均在HBuilderX 4.0中测试通过。

