UniApp入门教程 - 跨平台开发从零开始完整指南
UniApp入门教程 - 跨平台开发从零开始完整指南
目录
- UniApp简介
- 环境搭建
- 创建第一个UniApp项目
- 项目结构详解
- 基础语法和规范
- 常用组件
- 页面路由和导航
- API使用
- 样式和布局
- 数据绑定和事件处理
- 生命周期
- 条件编译
- 插件和扩展
- 打包发布
- 实际开发案例
- 常见问题和最佳实践
- 总结与进阶
1. UniApp简介
UniApp(Universal Application)是一个使用Vue.js开发所有前端应用的框架,开发者编写一套代码,可以发布到iOS、Android、Web(响应式)、以及各种小程序(微信/支付宝/百度/头条/QQ/快手/钉钉/淘宝)、快应用等多个平台。
核心优势:
✅ 一套代码,多端运行:编写一次,发布到多个平台
✅ 基于Vue.js:使用熟悉的Vue语法,学习成本低
✅ 丰富的组件库:内置大量常用组件
✅ 强大的API:封装了各平台的API,统一调用
✅ 条件编译:可以针对不同平台编写特定代码
✅ 性能优秀:接近原生应用的性能
✅ 生态丰富:插件市场提供大量扩展
移动端:iOS、Android
小程序:微信、支付宝、百度、头条、QQ、快手、钉钉、淘宝
Web端:H5、响应式网页
快应用:华为、小米、OPPO、vivo等
| 特性 | UniApp | Taro | React Native | Flutter |
|---|---|---|---|---|
| 开发语言 | Vue.js | React | JavaScript | Dart |
| 学习曲线 | 低 | 中 | 中 | 高 |
| 性能 | 高 | 中 | 高 | 极高 |
| 生态 | 丰富 | 丰富 | 丰富 | 一般 |
| 跨平台 | 是 | 是 | 是 | 是 |
- ✅ 需要同时发布多个平台的应用
- ✅ 中小型项目快速开发
- ✅ 电商、社交、工具类应用
- ✅ 企业内部应用
- ✅ 快速原型开发
2. 环境搭建
- Node.js:版本 >= 12.0.0(推荐使用LTS版本)
- npm或yarn:包管理工具
- HBuilderX:DCloud官方IDE(推荐)
- 微信开发者工具:开发微信小程序时需要
- Android Studio:开发Android应用时需要
- Xcode:开发iOS应用时需要(仅macOS)
Windows系统
- 访问Node.js官网:https://nodejs.org/
- 下载LTS版本安装包
- 运行安装程序,按照提示完成安装
- 验证安装:
node -v npm -v
macOS系统
方式1:使用Homebrew(推荐)
brew install node方式2:官网下载
访问Node.js官网下载macOS安装包
Linux系统
# Ubuntu/Debian
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt-get install -y nodejs
# CentOS/RHEL
curl -fsSL https://rpm.nodesource.com/setup_lts.x | sudo bash -
sudo yum install -y nodejs下载和安装
- 访问DCloud官网:https://www.dcloud.io/hbuilderx.html
- 下载HBuilderX(推荐下载App开发版)
- 解压到指定目录(Windows需要解压,macOS直接拖拽到Applications)
- 启动HBuilderX
安装插件
- 打开HBuilderX
- 工具 → 插件安装
- 安装以下插件:
- uni-app编译
- scss/sass编译
- 代码提示
- Git插件(可选)
2.4 安装微信开发者工具
下载和安装
- 访问微信公众平台:https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html
- 下载对应操作系统的版本
- 安装并启动
- 使用微信扫码登录
配置小程序AppID
- 注册微信小程序账号:https://mp.weixin.qq.com/
- 获取AppID
- 在HBuilderX中配置AppID(manifest.json中)
配置Android开发环境(可选)
- 下载Android Studio:https://developer.android.com/studio
- 安装Android SDK
- 配置环境变量
配置iOS开发环境(可选,仅macOS)
- 从App Store安装Xcode
- 安装Xcode Command Line Tools
- 配置开发者证书
3. 创建第一个UniApp项目
步骤1:新建项目
- 打开HBuilderX
- 文件 → 新建 → 项目
- 选择"uni-app"
- 选择模板(推荐"默认模板")
- 填写项目名称和路径
- 点击"创建"
步骤2:项目配置
创建完成后,项目结构如下:
my-uniapp/
├── pages/ # 页面目录
│ ├── index/
│ │ └── index.vue # 首页
│ └── login/
│ └── login.vue # 登录页
├── static/ # 静态资源目录
├── App.vue # 应用入口文件
├── main.js # 入口js文件
├── manifest.json # 应用配置文件
├── pages.json # 页面路由配置
└── uni.scss # 全局样式文件3.2 使用CLI创建项目
安装uni-app CLI
npm install -g @dcloudio/uvm
uvm创建项目
# 使用vue-cli创建
vue create -p dcloudio/uni-preset-vue my-project
# 或使用HBuilderX CLI
hx create my-project在HBuilderX中运行
- 右键项目 → 运行 → 运行到浏览器
- 选择运行平台:
- 运行到浏览器 → Chrome
- 运行到小程序模拟器 → 微信开发者工具
- 运行到手机或模拟器 → Android/iOS
使用命令行运行
# 进入项目目录
cd my-uniapp
# 安装依赖
npm install
# 运行到H5
npm run dev:h5
# 运行到微信小程序
npm run dev:mp-weixin
# 运行到App
npm run dev:app-plus3.4 第一个Hello World
打开 pages/index/index.vue,修改代码如下:
<template>
<view class="container">
<text class="title">Hello UniApp!</text>
<button @click="handleClick">点击我</button>
</view>
</template>
<script>
export default {
data() {
return {
message: '欢迎使用UniApp'
}
},
methods: {
handleClick() {
uni.showToast({
title: 'Hello UniApp!',
icon: 'success'
})
}
}
}
</script>
<style>
.container {
padding: 20px;
text-align: center;
}
.title {
font-size: 24px;
color: #333;
margin-bottom: 20px;
}
</style>运行项目,即可看到Hello World页面。
4. 项目结构详解
4.1 目录结构
my-uniapp/
├── pages/ # 页面目录
│ ├── index/
│ │ └── index.vue # 首页
│ └── detail/
│ └── detail.vue # 详情页
├── components/ # 组件目录
│ └── custom-button/
│ └── custom-button.vue
├── static/ # 静态资源目录
│ ├── images/
│ └── icons/
├── store/ # 状态管理(Vuex)
│ └── index.js
├── utils/ # 工具函数
│ └── request.js
├── api/ # API接口
│ └── user.js
├── App.vue # 应用入口文件
├── main.js # 入口js文件
├── manifest.json # 应用配置文件
├── pages.json # 页面路由配置
├── uni.scss # 全局样式变量
└── package.json # 项目依赖配置4.2 核心文件说明
App.vue - 应用入口
<script>
export default {
onLaunch: function() {
console.log('App Launch')
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
}
}
</script>
<style>
/* 全局样式 */
page {
background-color: #f5f5f5;
}
</style>main.js - 入口文件
import App from './App'
import Vue from 'vue'
import store from './store'
Vue.config.productionTip = false
App.mpType = 'app'
const app = new Vue({
store,
...App
})
app.$mount()pages.json - 页面配置
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页"
}
},
{
"path": "pages/detail/detail",
"style": {
"navigationBarTitleText": "详情页"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "UniApp",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
},
"tabBar": {
"color": "#7A7E83",
"selectedColor": "#3cc51f",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"list": [
{
"pagePath": "pages/index/index",
"iconPath": "static/tab-home.png",
"selectedIconPath": "static/tab-home-current.png",
"text": "首页"
}
]
}
}manifest.json - 应用配置
{
"name": "my-uniapp",
"appid": "",
"description": "",
"versionName": "1.0.0",
"versionCode": "100",
"transformPx": false,
"app-plus": {
"usingComponents": true,
"nvueStyleCompiler": "uni-app",
"compilerVersion": 3,
"splashscreen": {
"alwaysShowBeforeRender": true,
"waiting": true,
"autoclose": true,
"delay": 0
}
},
"mp-weixin": {
"appid": "",
"setting": {
"urlCheck": false
},
"usingComponents": true
},
"h5": {
"router": {
"mode": "hash"
}
}
}5. 基础语法和规范
UniApp基于Vue.js,使用Vue的语法规范。
模板语法
<template>
<view>
<!-- 文本插值 -->
<text>{{ message }}</text>
<!-- 属性绑定 -->
<image :src="imageUrl"></image>
<!-- 条件渲染 -->
<view v-if="show">显示内容</view>
<view v-else>隐藏内容</view>
<!-- 列表渲染 -->
<view v-for="(item, index) in list" :key="index">
{{ item.name }}
</view>
<!-- 事件绑定 -->
<button @click="handleClick">点击</button>
</view>
</template>数据和方法
<script>
export default {
data() {
return {
message: 'Hello',
imageUrl: '/static/logo.png',
show: true,
list: [
{ name: 'Item 1' },
{ name: 'Item 2' }
]
}
},
methods: {
handleClick() {
this.message = 'Clicked!'
}
},
computed: {
reversedMessage() {
return this.message.split('').reverse().join('')
}
},
watch: {
message(newVal, oldVal) {
console.log('Message changed:', newVal)
}
}
}
</script>条件编译
<template>
<view>
<!-- #ifdef H5 -->
<view>H5平台特有内容</view>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<view>微信小程序特有内容</view>
<!-- #endif -->
<!-- #ifdef APP-PLUS -->
<view>App平台特有内容</view>
<!-- #endif -->
</view>
</template>
<script>
export default {
data() {
return {
// #ifdef H5
platform: 'H5',
// #endif
// #ifdef MP-WEIXIN
platform: 'WeChat',
// #endif
}
}
}
</script>平台判断
// 条件编译
// #ifdef H5
console.log('H5平台')
// #endif
// 运行时判断
if (uni.getSystemInfoSync().platform === 'android') {
console.log('Android平台')
}6. 常用组件
view - 视图容器
<template>
<view class="container">
<view class="box">内容区域</view>
</view>
</template>
<style>
.container {
padding: 20px;
}
.box {
background-color: #fff;
padding: 20px;
border-radius: 8px;
}
</style>text - 文本
<template>
<view>
<text>普通文本</text>
<text class="bold">加粗文本</text>
<text :selectable="true">可选择文本</text>
</view>
</template>
<style>
.bold {
font-weight: bold;
}
</style>image - 图片
<template>
<view>
<!-- 网络图片 -->
<image src="https://example.com/image.jpg" mode="aspectFit"></image>
<!-- 本地图片 -->
<image src="/static/logo.png" mode="widthFix"></image>
<!-- 懒加载 -->
<image :src="imageUrl" :lazy-load="true"></image>
</view>
</template>mode属性值:
scaleToFill:缩放填充aspectFit:保持纵横比缩放aspectFill:保持纵横比填充widthFix:宽度不变,高度自动变化heightFix:高度不变,宽度自动变化
button - 按钮
<template>
<view>
<!-- 默认按钮 -->
<button>默认按钮</button>
<!-- 主要按钮 -->
<button type="primary">主要按钮</button>
<!-- 警告按钮 -->
<button type="warn">警告按钮</button>
<!-- 禁用按钮 -->
<button :disabled="true">禁用按钮</button>
<!-- 加载中 -->
<button :loading="loading" @click="handleClick">加载中</button>
</view>
</template>
<script>
export default {
data() {
return {
loading: false
}
},
methods: {
handleClick() {
this.loading = true
setTimeout(() => {
this.loading = false
}, 2000)
}
}
}
</script>input - 输入框
<template>
<view>
<input
v-model="username"
placeholder="请输入用户名"
:maxlength="20"
@input="handleInput"
/>
<input
type="password"
v-model="password"
placeholder="请输入密码"
/>
<input
type="number"
v-model="age"
placeholder="请输入年龄"
/>
</view>
</template>
<script>
export default {
data() {
return {
username: '',
password: '',
age: ''
}
},
methods: {
handleInput(e) {
console.log('输入值:', e.detail.value)
}
}
}
</script>textarea - 多行输入
<template>
<view>
<textarea
v-model="content"
placeholder="请输入内容"
:maxlength="200"
:auto-height="true"
></textarea>
</view>
</template>picker - 选择器
<template>
<view>
<!-- 普通选择器 -->
<picker
:range="options"
@change="handlePickerChange"
>
<view>当前选择:{{ selectedOption }}</view>
</picker>
<!-- 日期选择器 -->
<picker
mode="date"
:value="date"
@change="handleDateChange"
>
<view>选择日期:{{ date }}</view>
</picker>
<!-- 时间选择器 -->
<picker
mode="time"
:value="time"
@change="handleTimeChange"
>
<view>选择时间:{{ time }}</view>
</picker>
</view>
</template>
<script>
export default {
data() {
return {
options: ['选项1', '选项2', '选项3'],
selectedOption: '选项1',
date: '2024-01-01',
time: '12:00'
}
},
methods: {
handlePickerChange(e) {
this.selectedOption = this.options[e.detail.value]
},
handleDateChange(e) {
this.date = e.detail.value
},
handleTimeChange(e) {
this.time = e.detail.value
}
}
}
</script>switch - 开关
<template>
<view>
<switch
:checked="checked"
@change="handleSwitchChange"
color="#007aff"
/>
</view>
</template>
<script>
export default {
data() {
return {
checked: false
}
},
methods: {
handleSwitchChange(e) {
this.checked = e.detail.value
}
}
}
</script>navigator - 页面链接
<template>
<view>
<!-- 普通跳转 -->
<navigator url="/pages/detail/detail">跳转到详情页</navigator>
<!-- 带参数 -->
<navigator url="/pages/detail/detail?id=123&name=test">
带参数跳转
</navigator>
<!-- 新窗口打开 -->
<navigator url="/pages/detail/detail" open-type="redirect">
重定向
</navigator>
</view>
</template>video - 视频
<template>
<view>
<video
src="https://example.com/video.mp4"
:controls="true"
:autoplay="false"
:poster="posterUrl"
@play="handlePlay"
@pause="handlePause"
></video>
</view>
</template>scroll-view - 滚动视图
<template>
<scroll-view
scroll-y
:scroll-top="scrollTop"
@scroll="handleScroll"
class="scroll-container"
>
<view v-for="item in list" :key="item.id">
{{ item.content }}
</view>
</scroll-view>
</template>swiper - 轮播图
<template>
<swiper
:indicator-dots="true"
:autoplay="true"
:interval="3000"
:duration="500"
class="swiper"
>
<swiper-item v-for="(item, index) in banners" :key="index">
<image :src="item.url" mode="aspectFill"></image>
</swiper-item>
</swiper>
</template>
<script>
export default {
data() {
return {
banners: [
{ url: '/static/banner1.jpg' },
{ url: '/static/banner2.jpg' },
{ url: '/static/banner3.jpg' }
]
}
}
}
</script>7. 页面路由和导航
在pages.json中配置页面路由:
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页",
"enablePullDownRefresh": true
}
},
{
"path": "pages/detail/detail",
"style": {
"navigationBarTitleText": "详情页"
}
}
]
}uni.navigateTo - 保留当前页面跳转
// 跳转到新页面
uni.navigateTo({
url: '/pages/detail/detail?id=123',
success: function(res) {
console.log('跳转成功')
},
fail: function(err) {
console.log('跳转失败', err)
}
})uni.redirectTo - 关闭当前页面跳转
uni.redirectTo({
url: '/pages/login/login'
})uni.switchTab - 切换到TabBar页面
uni.switchTab({
url: '/pages/index/index'
})uni.reLaunch - 关闭所有页面跳转
uni.reLaunch({
url: '/pages/index/index'
})uni.navigateBack - 返回上一页
uni.navigateBack({
delta: 1, // 返回的页面数
success: function() {
console.log('返回成功')
}
})传递参数
// 方式1:URL参数
uni.navigateTo({
url: '/pages/detail/detail?id=123&name=test'
})
// 方式2:通过EventChannel
uni.navigateTo({
url: '/pages/detail/detail',
events: {
acceptDataFromOpenedPage: function(data) {
console.log(data)
}
},
success: function(res) {
res.eventChannel.emit('acceptDataFromOpenedPage', {
data: 'test'
})
}
})接收参数
// pages/detail/detail.vue
export default {
onLoad(options) {
// URL参数
console.log('ID:', options.id)
console.log('Name:', options.name)
},
onReady() {
// 通过EventChannel接收
const eventChannel = this.getOpenerEventChannel()
eventChannel.on('acceptDataFromOpenedPage', (data) => {
console.log('接收数据:', data)
})
}
}{
"tabBar": {
"color": "#7A7E83",
"selectedColor": "#3cc51f",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"list": [
{
"pagePath": "pages/index/index",
"iconPath": "static/tab-home.png",
"selectedIconPath": "static/tab-home-current.png",
"text": "首页"
},
{
"pagePath": "pages/category/category",
"iconPath": "static/tab-category.png",
"selectedIconPath": "static/tab-category-current.png",
"text": "分类"
},
{
"pagePath": "pages/cart/cart",
"iconPath": "static/tab-cart.png",
"selectedIconPath": "static/tab-cart-current.png",
"text": "购物车"
},
{
"pagePath": "pages/user/user",
"iconPath": "static/tab-user.png",
"selectedIconPath": "static/tab-user-current.png",
"text": "我的"
}
]
}
}8. API使用
uni.request - HTTP请求
// 基础用法
uni.request({
url: 'https://api.example.com/user',
method: 'GET',
data: {
id: 123
},
header: {
'Authorization': 'Bearer token'
},
success: function(res) {
console.log('请求成功:', res.data)
},
fail: function(err) {
console.log('请求失败:', err)
}
})
// 使用async/await
async function fetchUser(id) {
try {
const res = await uni.request({
url: 'https://api.example.com/user',
method: 'GET',
data: { id }
})
return res.data
} catch (err) {
console.error('请求失败:', err)
throw err
}
}封装请求工具
// utils/request.js
const BASE_URL = 'https://api.example.com'
function request(options) {
return new Promise((resolve, reject) => {
uni.request({
url: BASE_URL + options.url,
method: options.method || 'GET',
data: options.data || {},
header: {
'Content-Type': 'application/json',
'Authorization': uni.getStorageSync('token') || '',
...options.header
},
success: (res) => {
if (res.statusCode === 200) {
resolve(res.data)
} else {
reject(res)
}
},
fail: (err) => {
reject(err)
}
})
})
}
export default {
get(url, data) {
return request({ url, method: 'GET', data })
},
post(url, data) {
return request({ url, method: 'POST', data })
}
}uni.setStorage - 存储数据
// 同步存储
uni.setStorageSync('key', 'value')
// 异步存储
uni.setStorage({
key: 'userInfo',
data: {
id: 123,
name: 'test'
},
success: function() {
console.log('存储成功')
}
})uni.getStorage - 获取数据
// 同步获取
const value = uni.getStorageSync('key')
// 异步获取
uni.getStorage({
key: 'userInfo',
success: function(res) {
console.log('获取成功:', res.data)
}
})uni.removeStorage - 删除数据
uni.removeStorageSync('key')
uni.removeStorage({
key: 'userInfo',
success: function() {
console.log('删除成功')
}
})uni.showToast - 提示框
uni.showToast({
title: '操作成功',
icon: 'success',
duration: 2000
})
uni.showToast({
title: '加载中...',
icon: 'loading',
duration: 2000
})uni.showModal - 模态对话框
uni.showModal({
title: '提示',
content: '确定要删除吗?',
success: function(res) {
if (res.confirm) {
console.log('用户点击确定')
} else if (res.cancel) {
console.log('用户点击取消')
}
}
})uni.showActionSheet - 操作菜单
uni.showActionSheet({
itemList: ['选项1', '选项2', '选项3'],
success: function(res) {
console.log('选择了第' + (res.tapIndex + 1) + '个选项')
}
})uni.getSystemInfo - 获取系统信息
uni.getSystemInfo({
success: function(res) {
console.log('手机品牌:', res.brand)
console.log('手机型号:', res.model)
console.log('屏幕宽度:', res.windowWidth)
console.log('屏幕高度:', res.windowHeight)
console.log('状态栏高度:', res.statusBarHeight)
}
})
// 同步获取
const systemInfo = uni.getSystemInfoSync()uni.getLocation - 获取位置
uni.getLocation({
type: 'gcj02',
success: function(res) {
console.log('经度:', res.longitude)
console.log('纬度:', res.latitude)
}
})扫码
uni.scanCode({
success: function(res) {
console.log('扫码结果:', res.result)
}
})选择图片
uni.chooseImage({
count: 1,
sourceType: ['album', 'camera'],
success: function(res) {
const tempFilePaths = res.tempFilePaths
console.log('选择的图片:', tempFilePaths)
}
})预览图片
uni.previewImage({
urls: ['https://example.com/image1.jpg', 'https://example.com/image2.jpg'],
current: 'https://example.com/image1.jpg'
})9. 样式和布局
UniApp支持以下尺寸单位:
- rpx:响应式像素,推荐使用
- px:物理像素
- upx:等同于rpx(已废弃)
rpx换算:
- 设计稿宽度750px时,1rpx = 1px
- 其他宽度按比例换算
<template>
<view class="container">
<view class="item">1</view>
<view class="item">2</view>
<view class="item">3</view>
</view>
</template>
<style>
.container {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.item {
flex: 1;
height: 100rpx;
background-color: #007aff;
margin: 10rpx;
}
</style>在App.vue中定义全局样式:
<style>
/* 全局样式 */
page {
background-color: #f5f5f5;
font-size: 28rpx;
color: #333;
}
/* 全局类 */
.container {
padding: 20rpx;
}
.text-center {
text-align: center;
}
</style>// uni.scss
$uni-color-primary: #007aff;
$uni-color-success: #4cd964;
$uni-color-warning: #f0ad4e;
$uni-color-error: #dd524d;
// 在组件中使用
<style lang="scss">
.custom-button {
background-color: $uni-color-primary;
color: #fff;
}
</style>10. 数据绑定和事件处理
单向绑定
<template>
<view>
<text>{{ message }}</text>
<image :src="imageUrl"></image>
</view>
</template>双向绑定
<template>
<view>
<input v-model="username" placeholder="请输入用户名" />
<text>当前输入:{{ username }}</text>
</view>
</template>
<script>
export default {
data() {
return {
username: ''
}
}
}
</script>事件绑定
<template>
<view>
<!-- 点击事件 -->
<button @click="handleClick">点击</button>
<!-- 带参数 -->
<button @click="handleClickWithParam('test')">带参数点击</button>
<!-- 事件对象 -->
<button @click="handleClickWithEvent">事件对象</button>
<!-- 阻止冒泡 -->
<view @click.stop="handleClick">阻止冒泡</view>
<!-- 阻止默认行为 -->
<form @submit.prevent="handleSubmit">表单</form>
</view>
</template>
<script>
export default {
methods: {
handleClick() {
console.log('点击了按钮')
},
handleClickWithParam(param) {
console.log('参数:', param)
},
handleClickWithEvent(event) {
console.log('事件对象:', event)
},
handleSubmit() {
console.log('提交表单')
}
}
}
</script>11. 生命周期
在App.vue中:
export default {
onLaunch: function() {
console.log('App Launch')
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
},
onError: function(err) {
console.log('App Error:', err)
}
}export default {
onLoad(options) {
// 页面加载时触发,接收页面参数
console.log('页面加载', options)
},
onShow() {
// 页面显示时触发
console.log('页面显示')
},
onReady() {
// 页面初次渲染完成时触发
console.log('页面渲染完成')
},
onHide() {
// 页面隐藏时触发
console.log('页面隐藏')
},
onUnload() {
// 页面卸载时触发
console.log('页面卸载')
},
onPullDownRefresh() {
// 下拉刷新时触发
console.log('下拉刷新')
setTimeout(() => {
uni.stopPullDownRefresh()
}, 1000)
},
onReachBottom() {
// 上拉加载时触发
console.log('上拉加载')
}
}12. 条件编译
<template>
<view>
<!-- #ifdef H5 -->
<view>H5平台</view>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<view>微信小程序</view>
<!-- #endif -->
<!-- #ifdef APP-PLUS -->
<view>App平台</view>
<!-- #endif -->
</view>
</template>
<script>
export default {
data() {
return {
// #ifdef H5
platform: 'H5',
// #endif
// #ifdef MP-WEIXIN
platform: 'WeChat',
// #endif
}
},
methods: {
// #ifdef APP-PLUS
nativeMethod() {
// App特有方法
},
// #endif
}
}
</script>
<style>
/* #ifdef H5 */
.h5-style {
color: red;
}
/* #endif */
</style>H5:H5平台MP-WEIXIN:微信小程序MP-ALIPAY:支付宝小程序MP-BAIDU:百度小程序MP-TOUTIAO:头条小程序MP-QQ:QQ小程序APP-PLUS:App平台APP-PLUS-NVUE:App nvue页面
13. 插件和扩展
安装
npm install @dcloudio/uni-ui使用
<template>
<view>
<uni-badge :text="badgeText" type="error"></uni-badge>
<uni-card title="标题" note="备注">
<text>内容</text>
</uni-card>
</view>
</template>
<script>
import uniBadge from '@dcloudio/uni-ui/lib/uni-badge/uni-badge.vue'
import uniCard from '@dcloudio/uni-ui/lib/uni-card/uni-card.vue'
export default {
components: {
uniBadge,
uniCard
},
data() {
return {
badgeText: '99+'
}
}
}
</script>从插件市场安装插件:
- 打开HBuilderX
- 工具 → 插件安装
- 搜索需要的插件
- 点击安装
14. 打包发布
- 在HBuilderX中:发行 → 网站-H5
- 配置域名等信息
- 点击发行
- 将生成的dist目录部署到服务器
14.2 打包小程序
微信小程序
- 配置manifest.json中的AppID
- 发行 → 小程序-微信
- 使用微信开发者工具打开生成的dist目录
- 在微信开发者工具中上传代码
- 在微信公众平台提交审核
14.3 打包App
Android
- 配置manifest.json
- 发行 → 原生App-云打包
- 选择Android平台
- 配置证书等信息
- 点击打包
- 下载APK文件
iOS
- 配置manifest.json
- 发行 → 原生App-云打包
- 选择iOS平台
- 上传证书和描述文件
- 点击打包
- 下载IPA文件
15. 实际开发案例
<template>
<view class="login-container">
<view class="logo">
<image src="/static/logo.png" mode="aspectFit"></image>
</view>
<view class="form">
<input
v-model="username"
placeholder="请输入用户名"
class="input"
/>
<input
v-model="password"
type="password"
placeholder="请输入密码"
class="input"
/>
<button
type="primary"
@click="handleLogin"
:loading="loading"
class="login-btn"
>
登录
</button>
</view>
</view>
</template>
<script>
import request from '@/utils/request'
export default {
data() {
return {
username: '',
password: '',
loading: false
}
},
methods: {
async handleLogin() {
if (!this.username || !this.password) {
uni.showToast({
title: '请输入用户名和密码',
icon: 'none'
})
return
}
this.loading = true
try {
const res = await request.post('/api/login', {
username: this.username,
password: this.password
})
uni.setStorageSync('token', res.token)
uni.setStorageSync('userInfo', res.userInfo)
uni.showToast({
title: '登录成功',
icon: 'success'
})
setTimeout(() => {
uni.switchTab({
url: '/pages/index/index'
})
}, 1500)
} catch (err) {
uni.showToast({
title: err.message || '登录失败',
icon: 'none'
})
} finally {
this.loading = false
}
}
}
}
</script>
<style>
.login-container {
padding: 100rpx 40rpx;
}
.logo {
text-align: center;
margin-bottom: 100rpx;
}
.logo image {
width: 200rpx;
height: 200rpx;
}
.form {
margin-top: 100rpx;
}
.input {
height: 88rpx;
background-color: #f5f5f5;
border-radius: 8rpx;
padding: 0 20rpx;
margin-bottom: 30rpx;
}
.login-btn {
margin-top: 50rpx;
height: 88rpx;
line-height: 88rpx;
}
</style><template>
<view class="product-list">
<view
v-for="item in productList"
:key="item.id"
class="product-item"
@click="goToDetail(item.id)"
>
<image :src="item.image" mode="aspectFill" class="product-image"></image>
<view class="product-info">
<text class="product-name">{{ item.name }}</text>
<view class="product-price">
<text class="price-symbol">¥</text>
<text class="price-value">{{ item.price }}</text>
</view>
</view>
</view>
</view>
</template>
<script>
import request from '@/utils/request'
export default {
data() {
return {
productList: [],
page: 1,
pageSize: 10,
hasMore: true
}
},
onLoad() {
this.loadProducts()
},
onReachBottom() {
if (this.hasMore) {
this.loadMore()
}
},
onPullDownRefresh() {
this.page = 1
this.hasMore = true
this.loadProducts()
},
methods: {
async loadProducts() {
try {
const res = await request.get('/api/products', {
page: this.page,
pageSize: this.pageSize
})
if (this.page === 1) {
this.productList = res.list
} else {
this.productList = [...this.productList, ...res.list]
}
this.hasMore = res.hasMore
uni.stopPullDownRefresh()
} catch (err) {
uni.showToast({
title: '加载失败',
icon: 'none'
})
}
},
loadMore() {
this.page++
this.loadProducts()
},
goToDetail(id) {
uni.navigateTo({
url: `/pages/detail/detail?id=${id}`
})
}
}
}
</script>
<style>
.product-list {
display: flex;
flex-wrap: wrap;
padding: 20rpx;
}
.product-item {
width: 345rpx;
background-color: #fff;
border-radius: 8rpx;
margin-bottom: 20rpx;
overflow: hidden;
}
.product-image {
width: 100%;
height: 345rpx;
}
.product-info {
padding: 20rpx;
}
.product-name {
font-size: 28rpx;
color: #333;
display: block;
margin-bottom: 10rpx;
}
.product-price {
display: flex;
align-items: baseline;
}
.price-symbol {
font-size: 24rpx;
color: #ff5000;
}
.price-value {
font-size: 32rpx;
color: #ff5000;
font-weight: bold;
}
</style>16. 常见问题和最佳实践
问题1:图片不显示
原因:路径错误或网络问题
解决:
- 检查图片路径是否正确
- 网络图片需要配置合法域名
- 使用相对路径或绝对路径
问题2:样式不生效
原因:作用域问题或单位问题
解决:
- 检查样式作用域
- 使用rpx单位
- 检查条件编译
问题3:页面跳转失败
原因:路径错误或页面未配置
解决:
- 检查pages.json配置
- 路径必须以/开头
- TabBar页面使用switchTab
代码规范
- 使用ESLint规范代码
- 统一命名规范
- 合理使用注释
性能优化
- 图片懒加载
- 列表虚拟滚动
- 合理使用缓存
代码复用
- 抽取公共组件
- 封装工具函数
- 使用混入(mixins)
错误处理
- 统一错误处理
- 友好的错误提示
- 日志记录
17. 总结与进阶
通过本教程,你已经掌握了:
- ✅ UniApp的基本概念和特点
- ✅ 环境搭建和项目创建
- ✅ 基础语法和组件使用
- ✅ 页面路由和导航
- ✅ API调用和数据存储
- ✅ 样式布局和事件处理
- ✅ 生命周期和条件编译
- ✅ 打包发布流程
状态管理
- Vuex的使用
- Pinia的使用
性能优化
- 代码分割
- 图片优化
- 网络优化
原生能力
- 调用原生插件
- 自定义原生组件
云开发
- uniCloud使用
- 云函数开发
- 官方文档:https://uniapp.dcloud.net.cn/
- 插件市场:https://ext.dcloud.net.cn/
- 社区论坛:https://ask.dcloud.net.cn/
- GitHub:https://github.com/dcloudio/uni-app
- 多练习:通过实际项目练习
- 看文档:经常查阅官方文档
- 学源码:学习优秀项目的源码
- 参与社区:参与社区讨论和贡献
结语
UniApp是一个强大的跨平台开发框架,通过本教程的学习,相信你已经掌握了UniApp的核心功能和使用方法。
记住:
- 多实践:理论结合实践,多写代码
- 看文档:官方文档是最好的学习资源
- 多思考:理解框架的设计思想
- 持续学习:关注框架的更新和发展
祝你学习愉快,开发顺利! 🚀
本教程由Java突击队学习社区编写,如有问题欢迎反馈。