掌握一套代码发布到iOS、Android、Web及多种小程序的终极解决方案
什么是UniApp?
UniApp是一个使用Vue.js开发所有前端应用的框架,开发者编写一套代码,可发布到iOS、Android、Web(响应式)、以及各种小程序(微信/支付宝/百度/头条/QQ/快手/钉钉/淘宝)、快应用等多个平台。
UniApp由DCloud公司开发,基于Vue.js语法规范,使用HBuilderX作为官方IDE,提供了丰富的组件和API,大大提高了跨端开发的效率。它解决了多端开发中重复编写代码的痛点,让开发者可以专注于业务逻辑的实现。
UniApp的主要优势
- 跨平台能力 – 一套代码可编译到14个平台
- 性能接近原生 – 使用weex原生渲染引擎,提升用户体验
- 丰富的生态系统 – 插件市场提供数千款插件
- 学习成本低 – 基于Vue.js语法,前端开发者快速上手
- 开发效率高 – HBuilderX提供强大的开发调试功能
开发环境搭建
1. 安装HBuilderX
HBuilderX是UniApp官方推荐的IDE,提供了丰富的开发功能和调试工具:
- 访问HBuilderX官网下载对应版本
- 安装并启动HBuilderX
- 安装必要的插件(如vue语法提示、uniapp语法提示等)
2. 创建第一个UniApp项目
在HBuilderX中创建新项目:
- 点击菜单”文件” -> “新建” -> “项目”
- 选择”uni-app”项目类型
- 选择默认模板或官方示例模板
- 输入项目名称和存储路径
- 点击创建完成项目初始化
3. 安装开发依赖(可选)
如需使用npm管理依赖:
# 在项目根目录执行
npm init -y
npm install @dcloudio/uni-ui # 安装官方UI组件库
4. 运行项目
在HBuilderX中:
- 选择运行到浏览器
- 选择运行到手机或模拟器
- 选择运行到小程序开发者工具
UniApp基础概念
项目结构
一个典型的UniApp项目包含以下目录结构:
uni-app-project/
├── pages/ // 页面目录
│ └── index/
│ ├── index.vue // 页面组件
│ └── index.json // 页面配置
├── static/ // 静态资源目录
├── components/ // 自定义组件目录
├── uni_modules/ // uni模块目录
├── App.vue // 应用入口组件
├── main.js // 应用入口js
├── manifest.json // 应用配置文件
└── pages.json // 页面配置文件
页面生命周期
UniApp页面支持Vue组件生命周期,同时增加了应用级生命周期:
export default {
// 页面生命周期
onLoad(options) {
// 页面加载时触发
},
onShow() {
// 页面显示时触发
},
onReady() {
// 页面初次渲染完成时触发
},
onHide() {
// 页面隐藏时触发
},
onUnload() {
// 页面卸载时触发
},
// Vue生命周期
created() {
// Vue实例创建后调用
},
mounted() {
// Vue实例挂载后调用
}
}
路由系统
UniApp使用小程序风格的路由系统:
// 保留当前页面,跳转到应用内的某个页面
uni.navigateTo({
url: '/pages/product/detail?id=1'
})
// 关闭当前页面,跳转到应用内的某个页面
uni.redirectTo({
url: '/pages/index/index'
})
// 跳转到tabBar页面,并关闭其他所有非tabBar页面
uni.switchTab({
url: '/pages/category/category'
})
// 关闭所有页面,打开到应用内的某个页面
uni.reLaunch({
url: '/pages/login/login'
})
UniApp核心组件
UniApp提供了一系列跨端兼容的组件,这些组件在不同平台上会有相应的原生组件实现:
视图容器组件
<!-- view组件 - 视图容器 -->
<view class="container">
<text>这是一个文本内容</text>
</view>
<!-- scroll-view组件 - 可滚动视图区域 -->
<scroll-view scroll-y="true" style="height: 300px;">
<view v-for="item in list" :key="item.id">
{{ item.name }}
</view>
</scroll-view>
<!-- swiper组件 - 滑块视图容器 -->
<swiper :indicator-dots="true" :autoplay="true" :interval="3000">
<swiper-item v-for="(item, index) in banners" :key="index">
<image :src="item.image" mode="aspectFill"></image>
</swiper-item>
</swiper>
基础内容组件
<!-- text组件 - 文本组件 -->
<text selectable="true" space="ensp">这段文本可以选择复制</text>
<!-- rich-text组件 - 富文本组件 -->
<rich-text :nodes="htmlContent"></rich-text>
<!-- progress组件 - 进度条组件 -->
<progress :percent="70" show-info stroke-width="5"></progress>
表单组件
<!-- input组件 - 输入框 -->
<input v-model="username" placeholder="请输入用户名" />
<!-- checkbox组件 - 多项选择器 -->
<checkbox-group @change="checkboxChange">
<label v-for="item in items" :key="item.value">
<checkbox :value="item.value" :checked="item.checked" />
{{ item.name }}
</label>
</checkbox-group>
<!-- picker组件 - 选择器 -->
<picker mode="date" :value="date" @change="bindDateChange">
<view class="picker">
当前选择: {{date}}
</view>
</picker>
UniApp常用API
UniApp提供了丰富的API,用于调用设备功能和平台特性:
网络请求
// 发起GET请求
uni.request({
url: 'https://api.example.com/data',
method: 'GET',
data: {
page: 1,
limit: 10
},
success: (res) => {
console.log('请求成功:', res.data);
},
fail: (err) => {
console.error('请求失败:', err);
}
});
// 上传文件
uni.uploadFile({
url: 'https://api.example.com/upload',
filePath: tempFilePaths[0],
name: 'file',
success: (uploadRes) => {
console.log('上传成功:', uploadRes.data);
}
});
数据缓存
// 异步存储数据
uni.setStorage({
key: 'userInfo',
data: userData,
success: () => {
console.log('存储成功');
}
});
// 同步存储数据
uni.setStorageSync('token', 'abcdef123456');
// 获取数据
const token = uni.getStorageSync('token');
// 移除数据
uni.removeStorage({
key: 'userInfo'
});
设备相关API
// 获取系统信息
uni.getSystemInfo({
success: (res) => {
console.log('手机型号:', res.model);
console.log('系统版本:', res.system);
}
});
// 拨打电话
uni.makePhoneCall({
phoneNumber: '10086'
});
// 获取地理位置
uni.getLocation({
type: 'wgs84',
success: (res) => {
console.log('当前位置:', res.latitude, res.longitude);
}
});
实战项目:多端商品展示应用
下面我们创建一个简单的商品展示应用,演示UniApp的开发流程:
项目结构
shop-demo/
├── pages/
│ ├── index/
│ │ ├── index.vue // 首页
│ │ └── index.json // 首页配置
│ ├── category/
│ │ ├── category.vue // 分类页
│ │ └── category.json
│ ├── cart/
│ │ ├── cart.vue // 购物车页
│ │ └── cart.json
│ ├── product/
│ │ ├── list.vue // 商品列表页
│ │ ├── detail.vue // 商品详情页
│ │ └── detail.json
│ └── user/
│ ├── user.vue // 用户中心页
│ └── user.json
├── components/
│ ├── product-card.vue // 商品卡片组件
│ └── tab-bar.vue // 自定义标签栏
├── static/
│ ├── images/
│ └── icons/
├── store/
│ └── index.js // Vuex状态管理
├── App.vue
├── main.js
├── manifest.json
└── pages.json
首页实现 (index.vue)
<template>
<view class="page-container">
<!-- 搜索栏 -->
<view class="search-bar">
<uni-search-bar placeholder="搜索商品" @confirm="onSearch"></uni-search-bar>
</view>
<!-- 轮播图 -->
<swiper class="swiper" :indicator-dots="true" :autoplay="true" :interval="3000">
<swiper-item v-for="(banner, index) in banners" :key="index">
<image :src="banner.image" mode="aspectFill" class="swiper-image" @click="onBannerClick(banner)"></image>
</swiper-item>
</swiper>
<!-- 分类导航 -->
<view class="category-nav">
<view v-for="category in categories" :key="category.id" class="nav-item" @click="onCategoryClick(category)">
<image :src="category.icon" mode="aspectFit" class="nav-icon"></image>
<text class="nav-text">{{ category.name }}</text>
</view>
</view>
<!-- 商品列表 -->
<view class="section-title">热门推荐</view>
<view class="product-list">
<product-card
v-for="product in products"
:key="product.id"
:product="product"
@click="onProductClick(product)">
</product-card>
</view>
</view>
</template>
<script>
import { mapState, mapActions } from 'vuex';
import ProductCard from '@/components/product-card.vue';
export default {
components: {
ProductCard
},
data() {
return {
banners: [],
categories: [],
products: []
};
},
computed: {
...mapState(['userInfo'])
},
onLoad() {
this.loadHomeData();
},
methods: {
...mapActions(['addToCart']),
async loadHomeData() {
// 模拟API请求
try {
const [bannerRes, categoryRes, productRes] = await Promise.all([
uni.request({ url: '/api/banners' }),
uni.request({ url: '/api/categories' }),
uni.request({ url: '/api/products/hot' })
]);
this.banners = bannerRes.data;
this.categories = categoryRes.data;
this.products = productRes.data;
} catch (error) {
uni.showToast({
title: '数据加载失败',
icon: 'none'
});
}
},
onSearch(keyword) {
uni.navigateTo({
url: `/pages/product/list?keyword=${keyword}`
});
},
onBannerClick(banner) {
if (banner.link) {
uni.navigateTo({
url: banner.link
});
}
},
onCategoryClick(category) {
uni.navigateTo({
url: `/pages/product/list?categoryId=${category.id}`
});
},
onProductClick(product) {
uni.navigateTo({
url: `/pages/product/detail?id=${product.id}`
});
}
}
};
</script>
商品卡片组件 (product-card.vue)
<template>
<view class="product-card" @click="$emit('click', product)">
<image :src="product.image" mode="aspectFill" class="product-image"></image>
<view class="product-info">
<text class="product-name">{{ product.name }}</text>
<text class="product-desc">{{ product.description }}</text>
<view class="product-bottom">
<text class="product-price">¥{{ product.price }}</text>
<view class="action-buttons">
<uni-icons
type="plus"
size="20"
color="#ff6700"
@click.stop="onAddToCart">
</uni-icons>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
props: {
product: {
type: Object,
required: true
}
},
methods: {
onAddToCart() {
this.$emit('add-to-cart', this.product);
uni.showToast({
title: '已加入购物车',
icon: 'success'
});
}
}
};
</script>
多端发布指南
发布到微信小程序
- 在微信公众平台注册小程序账号并获取AppID
- 在HBuilderX中打开manifest.json文件
- 配置微信小程序AppID
- 点击”发行” -> “小程序-微信”
- 等待编译完成,自动打开微信开发者工具
- 在微信开发者工具中上传代码并提交审核
发布到App
- 配置manifest.json中的App设置
- 点击”发行” -> “原生App-云打包”
- 选择需要打包的平台(iOS/Android)
- 配置证书(Android使用自有证书,iOS需提供证书文件)
- 等待云端打包完成,下载安装包
- 提交到各应用市场(App Store、各大安卓市场)
发布到H5
- 配置manifest.json中的H5设置
- 点击”发行” -> “网站-H5手机版”
- 设置网站标题和域名
- 等待编译完成,将生成的目录部署到服务器
- 配置服务器路由 history 模式(如需)
条件编译
UniApp提供了条件编译语法,处理不同平台的差异:
// #ifdef H5
console.log('仅在H5平台编译此代码');
// #endif
// #ifdef MP-WEIXIN
console.log('仅在微信小程序平台编译此代码');
// #endif
// #ifdef APP-PLUS
console.log('仅在App平台编译此代码');
// #endif
<!-- 条件编译示例 -->
<view>
<!-- 在H5和微信小程序中显示 -->
<button>通用按钮</button>
<!-- #ifdef H5 -->
<a href="/about" rel="external nofollow" >关于我们</a>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<button open-type="contact">联系我们</button>
<!-- #endif -->
</view>