uni-app 详细讲解:从入门、三端适配到 H5 / 小程序 / App 发布上线
适合对象:有 HTML、CSS、JavaScript、Vue 基础,想用一套代码开发 H5、小程序、App 的前端开发者。
推荐技术栈:
uni-app + Vue3 + TypeScript + Vite + Pinia + SCSS + uni-ui/uView Plus更新时间:2026 年
目录
- [uni-app 是什么](#uni-app 是什么)
- [uni-app 适合做什么项目](#uni-app 适合做什么项目)
- [uni-app 技术栈推荐](#uni-app 技术栈推荐)
- 开发环境准备
- [创建 uni-app 项目](#创建 uni-app 项目)
- 项目目录结构详细讲解
- [核心配置文件 pages.json 详细讲解](#核心配置文件 pages.json 详细讲解)
- [核心配置文件 manifest.json 详细讲解](#核心配置文件 manifest.json 详细讲解)
- [package.json、vite.config.ts 和环境变量](#package.json、vite.config.ts 和环境变量)
- 页面、组件和基础语法
- 路由跳转和登录拦截
- 网络请求封装
- 本地缓存封装
- [Pinia 状态管理](#Pinia 状态管理)
- 生命周期详细讲解
- [样式、rpx、flex 和安全区适配](#样式、rpx、flex 和安全区适配)
- 条件编译详细讲解
- [一套代码如何适配 H5、小程序、App 三端](#一套代码如何适配 H5、小程序、App 三端)
- [H5 发布流程和配置](#H5 发布流程和配置)
- 微信小程序发布流程和配置
- [App 发布流程和配置](#App 发布流程和配置)
- 三端发布命令和产物目录
- 项目实战:登录、首页、列表、详情、个人中心
- 常见问题和解决方案
- 学习路线
- 资源文档链接大全
- 上线前检查清单
1. uni-app 是什么?
uni-app 是 DCloud 推出的跨端应用开发框架。它的核心目标是:
使用 Vue 语法开发,一套代码发布到多个平台。
常见可发布平台包括:
- H5 / Web
- Android App
- iOS App
- 微信小程序
- 支付宝小程序
- 抖音小程序
- 百度小程序
- QQ 小程序
- 快手小程序
- 钉钉小程序
- 飞书小程序
- 小红书小程序
- 鸿蒙相关平台,具体能力以官方当前文档为准
简单理解:
txt
Vue 语法
+ uni-app 内置组件
+ uni-app API
+ pages.json 路由配置
+ manifest.json 平台配置
+ 条件编译
= 一套代码多端发布
2. uni-app 适合做什么项目?
2.1 非常适合
| 项目类型 | 适合程度 | 说明 |
|---|---|---|
| 企业官网 H5 | 高 | 可以快速适配移动端 |
| 微信小程序 | 高 | uni-app 对小程序生态支持成熟 |
| 多端小程序 | 高 | 同一业务可以发布到微信、支付宝、抖音等 |
| 商城系统 | 高 | 商品、购物车、订单、个人中心都很适合 |
| 预约系统 | 高 | 医疗预约、课程预约、门店预约 |
| 社区资讯类 App | 高 | 列表、详情、评论、分享场景常见 |
| 企业内部移动端 | 高 | 审批、报表、移动办公 |
| 活动页 / 营销页 | 中高 | H5 和小程序都可以做 |
| 轻量 App | 中高 | 不依赖过多复杂原生能力时很适合 |
2.2 不太适合
| 项目类型 | 原因 |
|---|---|
| 大型 3D 游戏 | 图形渲染和性能要求高 |
| 重度音视频剪辑 App | 强依赖原生能力和高性能处理 |
| 极复杂原生交互 App | 需要大量原生插件和平台差异处理 |
| 高频实时交易类系统 | 对稳定性、性能、网络链路要求极高 |
| 复杂地图导航类 App | 原生地图 SDK 能力差异较多 |
3. uni-app 技术栈推荐
3.1 现代推荐栈
txt
uni-app
Vue3
TypeScript
Vite
Pinia
SCSS
uni-ui / uView Plus
HBuilderX / VS Code
微信开发者工具
3.2 每个技术的作用
| 技术 | 作用 |
|---|---|
| uni-app | 跨端框架核心 |
| Vue3 | 页面、组件、响应式开发 |
| TypeScript | 类型约束,减少运行时报错 |
| Vite | 构建工具,开发启动快 |
| Pinia | 全局状态管理 |
| SCSS | 样式变量、嵌套、mixin |
| uni-ui | DCloud 官方 UI 组件库 |
| uView Plus | 常用第三方移动端 UI 组件库 |
| pages.json | 页面路由、导航栏、tabBar 配置 |
| manifest.json | 应用名称、图标、权限、平台发行配置 |
| HBuilderX | uni-app 官方推荐开发工具,打包 App 更方便 |
| 微信开发者工具 | 微信小程序调试和上传必备 |
4. 开发环境准备
4.1 必装工具
| 工具 | 用途 | 下载地址 |
|---|---|---|
| HBuilderX | 创建、运行、发行 uni-app 项目 | https://www.dcloud.io/hbuilderx.html |
| Node.js | CLI 项目、npm 包管理 | https://nodejs.org/ |
| 微信开发者工具 | 微信小程序预览、上传、审核前测试 | https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html |
| VS Code | 可选代码编辑器 | https://code.visualstudio.com/ |
| Git | 代码版本管理 | https://git-scm.com/ |
4.2 推荐插件
| 插件 | 作用 |
|---|---|
| Vue - Official | Vue 语法支持 |
| TypeScript Vue Plugin | Vue TS 辅助 |
| ESLint | 代码规范 |
| Prettier | 代码格式化 |
| uni-app snippets | uni-app 代码片段 |
| SCSS IntelliSense | SCSS 辅助 |
5. 创建 uni-app 项目
uni-app 项目主要有两种创建方式:
- HBuilderX 可视化创建
- CLI 命令行创建
5.1 HBuilderX 创建项目
适合新手和 App 打包场景。
步骤:
txt
打开 HBuilderX
→ 文件
→ 新建
→ 项目
→ 选择 uni-app
→ 输入项目名称
→ 选择 Vue3 / Vue2 模板
→ 创建
优点:
- 创建简单
- 内置 uni-app 编译器
- App 真机运行、云打包方便
- pages.json、manifest.json 有可视化配置
- 对新手友好
缺点:
- 工程化自由度略低
- 团队协作时需要统一 HBuilderX 版本
- 对熟悉 VS Code + CLI 的开发者不够灵活
5.2 CLI 创建项目
适合前端工程化开发者。
bash
npx degit dcloudio/uni-preset-vue#vite-ts my-uniapp-project
cd my-uniapp-project
npm install
npm run dev:h5
常用命令:
bash
# 运行到 H5
npm run dev:h5
# 打包 H5
npm run build:h5
# 运行到微信小程序
npm run dev:mp-weixin
# 打包微信小程序
npm run build:mp-weixin
# 运行到 App
npm run dev:app
# 打包 App 资源
npm run build:app
5.3 HBuilderX 项目和 CLI 项目区别
| 对比项 | HBuilderX 项目 | CLI 项目 |
|---|---|---|
| 创建方式 | 图形化 | 命令行 |
| 编译器位置 | HBuilderX 内置 | 项目 node_modules |
| 代码目录 | 根目录 | 通常在 src 目录 |
| 打包产物 | unpackage 目录 | dist 目录 |
| App 云打包 | 更方便 | 仍建议拖入 HBuilderX |
| 团队工程化 | 一般 | 更强 |
| 适合人群 | 新手、App 开发 | 熟悉前端工程化开发者 |
6. 项目目录结构详细讲解
推荐企业级目录:
txt
src
├─ api # 接口请求模块
│ ├─ user.ts
│ ├─ home.ts
│ ├─ order.ts
│ └─ upload.ts
├─ components # 公共组件
│ ├─ BaseNavbar
│ │ └─ BaseNavbar.vue
│ └─ GoodsCard
│ └─ GoodsCard.vue
├─ hooks # 组合式函数
│ ├─ useLogin.ts
│ ├─ usePaging.ts
│ └─ useSystemInfo.ts
├─ pages # 页面目录
│ ├─ index
│ │ └─ index.vue
│ ├─ login
│ │ └─ login.vue
│ ├─ goods
│ │ └─ detail.vue
│ └─ user
│ └─ user.vue
├─ pages-sub # 分包页面
│ ├─ order
│ │ └─ list.vue
│ └─ settings
│ └─ index.vue
├─ platform # 平台差异封装
│ ├─ login.ts
│ ├─ pay.ts
│ ├─ share.ts
│ └─ permission.ts
├─ static # 静态资源
│ ├─ logo.png
│ └─ tabbar
├─ stores # Pinia 状态管理
│ ├─ user.ts
│ └─ app.ts
├─ styles # 全局样式
│ ├─ variables.scss
│ ├─ mixins.scss
│ └─ common.scss
├─ types # TS 类型声明
│ ├─ user.d.ts
│ └─ api.d.ts
├─ utils # 工具函数
│ ├─ request.ts
│ ├─ router.ts
│ ├─ storage.ts
│ ├─ env.ts
│ └─ validate.ts
├─ App.vue # 应用入口
├─ main.ts # Vue 初始化入口
├─ pages.json # 页面路由和窗口配置
├─ manifest.json # 应用和平台配置
├─ uni.scss # 全局 SCSS 变量
└─ vite.config.ts # Vite 配置
6.1 目录职责说明
| 目录/文件 | 职责 |
|---|---|
pages |
主包页面,首页、登录、个人中心等 |
pages-sub |
分包页面,用于小程序减小主包体积 |
components |
可复用组件 |
api |
所有接口函数 |
utils/request.ts |
请求封装、token、错误处理 |
utils/router.ts |
路由跳转封装、登录拦截 |
utils/storage.ts |
缓存封装 |
stores |
用户信息、token、全局配置 |
platform |
微信、H5、App 特有能力封装 |
styles |
全局样式变量和 mixin |
static |
图片、字体、tabBar 图标 |
pages.json |
页面注册、导航栏、tabBar、分包 |
manifest.json |
应用名称、图标、权限、AppID、发行配置 |
7. 核心配置文件 pages.json 详细讲解
pages.json 是 uni-app 最重要的配置文件之一,用来配置:
- 页面路径
- 页面标题
- 导航栏样式
- 全局窗口样式
- tabBar
- 分包
- easycom 组件自动导入
- H5 大屏窗口
- 页面启动模式
7.1 基础 pages.json 示例
json
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页"
}
},
{
"path": "pages/category/index",
"style": {
"navigationBarTitleText": "分类"
}
},
{
"path": "pages/user/index",
"style": {
"navigationBarTitleText": "我的"
}
},
{
"path": "pages/login/index",
"style": {
"navigationBarTitleText": "登录"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app 项目",
"navigationBarBackgroundColor": "#ffffff",
"backgroundColor": "#f5f5f5",
"enablePullDownRefresh": false,
"onReachBottomDistance": 80
}
}
7.2 pages 配置
pages 用于注册页面。
json
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页"
}
}
注意:
txt
1. pages 数组第一个页面默认是启动首页。
2. path 不要以 / 开头。
3. 每个页面必须在 pages.json 中注册后才能跳转。
4. tabBar 页面也必须在 pages 中注册。
7.3 globalStyle 全局窗口配置
json
{
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "商城系统",
"navigationBarBackgroundColor": "#ffffff",
"backgroundColor": "#f7f8fa",
"enablePullDownRefresh": false,
"onReachBottomDistance": 80
}
}
常用属性:
| 属性 | 说明 |
|---|---|
navigationBarTextStyle |
导航栏文字颜色,只支持 black 或 white |
navigationBarTitleText |
默认导航栏标题 |
navigationBarBackgroundColor |
导航栏背景色 |
backgroundColor |
页面背景色 |
enablePullDownRefresh |
是否开启下拉刷新 |
onReachBottomDistance |
页面触底距离 |
7.4 单页面 style 配置
json
{
"path": "pages/goods/detail",
"style": {
"navigationBarTitleText": "商品详情",
"enablePullDownRefresh": true,
"navigationBarBackgroundColor": "#ffffff",
"backgroundColor": "#f5f5f5"
}
}
单页面配置会覆盖全局配置。
7.5 tabBar 配置
json
{
"tabBar": {
"color": "#666666",
"selectedColor": "#1677ff",
"backgroundColor": "#ffffff",
"borderStyle": "black",
"list": [
{
"pagePath": "pages/index/index",
"iconPath": "static/tabbar/home.png",
"selectedIconPath": "static/tabbar/home-active.png",
"text": "首页"
},
{
"pagePath": "pages/category/index",
"iconPath": "static/tabbar/category.png",
"selectedIconPath": "static/tabbar/category-active.png",
"text": "分类"
},
{
"pagePath": "pages/user/index",
"iconPath": "static/tabbar/user.png",
"selectedIconPath": "static/tabbar/user-active.png",
"text": "我的"
}
]
}
}
注意:
txt
1. tabBar 页面跳转必须使用 uni.switchTab。
2. 非 tabBar 页面跳转通常使用 uni.navigateTo。
3. tabBar 图标建议放 static 目录。
4. 小程序 tabBar 图标大小和格式要注意,建议 PNG。
7.6 分包 subPackages
分包主要用于小程序,解决主包体积限制问题。
json
{
"subPackages": [
{
"root": "pages-sub/order",
"pages": [
{
"path": "list",
"style": {
"navigationBarTitleText": "订单列表"
}
},
{
"path": "detail",
"style": {
"navigationBarTitleText": "订单详情"
}
}
]
},
{
"root": "pages-sub/settings",
"pages": [
{
"path": "index",
"style": {
"navigationBarTitleText": "设置"
}
}
]
}
]
}
跳转分包页面:
ts
uni.navigateTo({
url: '/pages-sub/order/detail?id=100'
})
分包建议:
txt
主包:登录、首页、tabBar 页面、公共组件、核心工具
分包:订单、设置、活动、详情、非首屏功能
7.7 easycom 自动导入组件
json
{
"easycom": {
"autoscan": true,
"custom": {
"^base-(.*)": "@/components/base-$1/base-$1.vue",
"^u-(.*)": "uview-plus/components/u-$1/u-$1.vue"
}
}
}
符合以下目录结构的组件可以自动导入:
txt
components/GoodsCard/GoodsCard.vue
components/base-button/base-button.vue
uni_modules/uni-card/components/uni-card/uni-card.vue
页面中可以直接使用:
vue
<template>
<GoodsCard />
<base-button>提交</base-button>
<uni-card title="标题">内容</uni-card>
</template>
8. 核心配置文件 manifest.json 详细讲解
manifest.json 是应用级配置文件,主要配置:
- 应用名称
- AppID
- 版本号
- 图标
- 启动图
- 权限
- H5 配置
- 小程序配置
- App 配置
- SDK 配置
- 网络超时时间
- 原生模块
8.1 基础字段
json
{
"name": "uniapp-shop",
"appid": "__UNI__XXXXXXX",
"description": "uni-app 商城项目",
"versionName": "1.0.0",
"versionCode": 100,
"locale": "zh-Hans"
}
| 字段 | 说明 |
|---|---|
name |
应用名称 |
appid |
DCloud 应用标识 |
description |
应用描述 |
versionName |
版本名称,例如 1.0.0 |
versionCode |
版本号,例如 100 |
locale |
默认语言 |
版本建议:
txt
versionName:给用户看的版本,如 1.0.0、1.1.0
versionCode:给应用市场识别升级用,每次发布必须递增,如 100、101、102
8.2 networkTimeout 网络超时配置
json
{
"networkTimeout": {
"request": 60000,
"connectSocket": 60000,
"uploadFile": 60000,
"downloadFile": 60000
}
}
建议:
| 场景 | 建议超时 |
|---|---|
| 普通请求 | 10000 - 30000 ms |
| 上传图片 | 60000 ms |
| 上传视频 | 120000 ms |
| 弱网场景 | 适当放宽并做重试 |
8.3 H5 配置
json
{
"h5": {
"title": "uni-app 商城",
"router": {
"mode": "hash",
"base": "/"
},
"devServer": {
"port": 5173,
"proxy": {
"/api": {
"target": "https://api.example.com",
"changeOrigin": true,
"secure": false
}
}
}
}
}
常用说明:
| 配置 | 作用 |
|---|---|
title |
浏览器标题 |
router.mode |
路由模式,常用 hash 或 history |
router.base |
部署基础路径 |
devServer.port |
本地开发端口 |
devServer.proxy |
本地开发代理,解决调试跨域 |
H5 路由建议:
txt
部署简单:使用 hash 模式
追求 URL 美观:使用 history 模式,但服务器必须配置 fallback 到 index.html
8.4 微信小程序配置
json
{
"mp-weixin": {
"appid": "wx1234567890abcdef",
"setting": {
"urlCheck": true,
"es6": true,
"postcss": true,
"minified": true
},
"usingComponents": true,
"permission": {
"scope.userLocation": {
"desc": "用于获取附近门店和配送地址"
}
}
}
}
常用说明:
| 配置 | 作用 |
|---|---|
appid |
微信小程序 AppID |
setting.urlCheck |
是否校验合法域名 |
setting.es6 |
是否启用 ES6 转换 |
setting.minified |
是否压缩代码 |
permission |
小程序权限说明 |
注意:
txt
1. 小程序上线必须使用正式 AppID。
2. 请求接口必须配置合法域名。
3. 接口建议使用 HTTPS。
4. 用到定位、相册、摄像头等能力时,要配置权限说明。
5. 涉及用户隐私信息时,需要在小程序后台配置用户隐私保护指引。
8.5 App 配置 app-plus
json
{
"app-plus": {
"usingComponents": true,
"nvueStyleCompiler": "uni-app",
"compilerVersion": 3,
"splashscreen": {
"alwaysShowBeforeRender": true,
"waiting": true,
"autoclose": true,
"delay": 0
},
"modules": {
"Camera": {},
"Gallery": {},
"Geolocation": {},
"OAuth": {},
"Payment": {},
"Share": {}
},
"distribute": {
"android": {
"packagename": "com.example.uniappshop",
"permissions": [
"<uses-permission android:name=\"android.permission.INTERNET\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>"
]
},
"ios": {
"appid": "com.example.uniappshop"
},
"sdkConfigs": {}
}
}
}
常用说明:
| 配置 | 作用 |
|---|---|
splashscreen |
App 启动图相关 |
modules |
App 原生模块,如相机、定位、支付、分享 |
distribute.android.packagename |
Android 包名 |
distribute.android.permissions |
Android 权限 |
distribute.ios.appid |
iOS Bundle ID |
sdkConfigs |
第三方 SDK 配置 |
8.6 App 图标和启动图
发布 App 前必须配置:
txt
1. App 图标
2. 启动图
3. 应用名称
4. 包名 / Bundle ID
5. 版本名称 versionName
6. 版本号 versionCode
7. 权限说明
8. 签名证书
建议:
txt
图标:1024x1024 PNG,避免透明背景
启动图:按平台要求准备多尺寸
应用名称:不要频繁变更
包名:一旦上架,后续不要更换
版本号:每次发版必须递增
9. package.json、vite.config.ts 和环境变量
9.1 package.json 常用 scripts
json
{
"scripts": {
"dev:h5": "uni -p h5",
"build:h5": "uni build -p h5",
"dev:mp-weixin": "uni -p mp-weixin",
"build:mp-weixin": "uni build -p mp-weixin",
"dev:app": "uni -p app",
"build:app": "uni build -p app"
}
}
9.2 环境变量
建议建立:
txt
.env.development
.env.production
.env.test
.env.development:
env
VITE_APP_ENV=development
VITE_API_BASE_URL=https://dev-api.example.com
.env.production:
env
VITE_APP_ENV=production
VITE_API_BASE_URL=https://api.example.com
读取环境变量:
ts
export const ENV = {
appEnv: import.meta.env.VITE_APP_ENV,
apiBaseUrl: import.meta.env.VITE_API_BASE_URL
}
9.3 vite.config.ts 示例
ts
import { defineConfig } from 'vite'
import uni from '@dcloudio/vite-plugin-uni'
import path from 'path'
export default defineConfig({
plugins: [uni()],
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
}
})
10. 页面、组件和基础语法
10.1 页面基础写法
vue
<template>
<view class="page">
<text class="title">首页</text>
<button @click="goDetail">进入详情页</button>
</view>
</template>
<script setup lang="ts">
const goDetail = () => {
uni.navigateTo({
url: '/pages/goods/detail?id=100'
})
}
</script>
<style scoped lang="scss">
.page {
min-height: 100vh;
padding: 32rpx;
background: #f5f5f5;
}
.title {
font-size: 36rpx;
font-weight: 600;
}
</style>
10.2 常用标签对比
| HTML | uni-app 推荐组件 | 说明 |
|---|---|---|
div |
view |
容器 |
span |
text |
文本 |
img |
image |
图片 |
a |
navigator |
页面跳转 |
input |
input |
输入框 |
button |
button |
按钮 |
textarea |
textarea |
多行输入 |
ul/li |
view |
列表通常用 view 实现 |
10.3 组件示例
components/GoodsCard/GoodsCard.vue
vue
<template>
<view class="goods-card" @click="handleClick">
<image class="goods-image" :src="goods.image" mode="aspectFill" />
<view class="goods-info">
<text class="goods-title">{{ goods.title }}</text>
<text class="goods-price">¥{{ goods.price }}</text>
</view>
</view>
</template>
<script setup lang="ts">
interface Goods {
id: number
title: string
image: string
price: number
}
const props = defineProps<{
goods: Goods
}>()
const emit = defineEmits<{
click: [goods: Goods]
}>()
const handleClick = () => {
emit('click', props.goods)
}
</script>
<style scoped lang="scss">
.goods-card {
display: flex;
padding: 24rpx;
border-radius: 20rpx;
background: #fff;
}
.goods-image {
width: 160rpx;
height: 160rpx;
border-radius: 12rpx;
}
.goods-info {
flex: 1;
margin-left: 24rpx;
}
.goods-title {
display: block;
font-size: 30rpx;
color: #222;
}
.goods-price {
display: block;
margin-top: 20rpx;
font-size: 32rpx;
color: #f53f3f;
font-weight: 600;
}
</style>
页面使用:
vue
<template>
<GoodsCard :goods="item" @click="goDetail" />
</template>
11. 路由跳转和登录拦截
uni-app 常用路由 API:
| API | 作用 | 使用场景 |
|---|---|---|
uni.navigateTo |
保留当前页,跳转非 tabBar 页面 | 列表到详情 |
uni.redirectTo |
关闭当前页,跳转新页面 | 登录页跳转 |
uni.reLaunch |
关闭所有页面,打开指定页面 | 退出登录 |
uni.switchTab |
跳转 tabBar 页面 | 首页、分类、我的 |
uni.navigateBack |
返回上一页 | 详情返回列表 |
11.1 封装 router.ts
ts
// utils/router.ts
const whiteList = [
'/pages/login/index',
'/pages/register/index',
'/pages/index/index'
]
export function getToken() {
return uni.getStorageSync('token')
}
export function isWhitePage(url: string) {
return whiteList.some(item => url.startsWith(item))
}
export function navigateTo(url: string) {
if (!getToken() && !isWhitePage(url)) {
uni.redirectTo({
url: '/pages/login/index'
})
return
}
uni.navigateTo({ url })
}
export function redirectTo(url: string) {
uni.redirectTo({ url })
}
export function switchTab(url: string) {
if (!getToken() && !isWhitePage(url)) {
uni.redirectTo({
url: '/pages/login/index'
})
return
}
uni.switchTab({ url })
}
export function reLaunch(url: string) {
uni.reLaunch({ url })
}
11.2 页面使用
ts
import { navigateTo } from '@/utils/router'
navigateTo('/pages/goods/detail?id=100')
11.3 为什么不完全照搬 Vue Router?
uni-app 不是传统 Web SPA,它要兼容小程序和 App,因此没有完全等同于 Vue Router 的全局路由守卫。实际项目中更推荐:
txt
1. 统一封装跳转方法
2. 所有页面跳转都通过封装方法
3. 请求层统一处理 401
4. 页面 onLoad 中做兜底验证
5. 特殊平台差异用条件编译处理
12. 网络请求封装
12.1 基础请求
ts
uni.request({
url: 'https://api.example.com/user',
method: 'GET',
success(res) {
console.log(res.data)
},
fail(err) {
console.log(err)
}
})
12.2 企业级请求封装
utils/request.ts
ts
import { ENV } from '@/utils/env'
type Method = 'GET' | 'POST' | 'PUT' | 'DELETE'
interface RequestOptions {
url: string
method?: Method
data?: Record<string, any>
header?: Record<string, string>
showLoading?: boolean
}
interface ApiResponse<T = any> {
code: number
message: string
data: T
}
const BASE_URL = ENV.apiBaseUrl
export function request<T = any>(options: RequestOptions): Promise<T> {
const token = uni.getStorageSync('token')
if (options.showLoading !== false) {
uni.showLoading({
title: '加载中',
mask: true
})
}
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: token ? `Bearer ${token}` : '',
...options.header
},
success(res) {
const result = res.data as ApiResponse<T>
if (res.statusCode === 200 && result.code === 0) {
resolve(result.data)
return
}
if (res.statusCode === 401 || result.code === 401) {
uni.removeStorageSync('token')
uni.redirectTo({
url: '/pages/login/index'
})
reject(result)
return
}
uni.showToast({
title: result.message || '请求失败',
icon: 'none'
})
reject(result)
},
fail(err) {
uni.showToast({
title: '网络异常,请稍后重试',
icon: 'none'
})
reject(err)
},
complete() {
if (options.showLoading !== false) {
uni.hideLoading()
}
}
})
})
}
12.3 接口模块
api/user.ts
ts
import { request } from '@/utils/request'
export interface LoginParams {
username: string
password: string
}
export interface LoginResult {
token: string
userInfo: {
id: number
nickname: string
avatar: string
}
}
export function loginApi(data: LoginParams) {
return request<LoginResult>({
url: '/auth/login',
method: 'POST',
data
})
}
export function getUserInfoApi() {
return request({
url: '/user/info',
method: 'GET'
})
}
12.4 H5、小程序、App 请求差异
| 平台 | 请求注意点 |
|---|---|
| H5 | 有浏览器跨域问题,本地可用 devServer.proxy,线上要后端配置 CORS 或同域代理 |
| 微信小程序 | 必须配置 request 合法域名,正式环境通常必须 HTTPS |
| App | 一般没有浏览器跨域限制,但要注意证书、HTTPS、弱网、超时 |
| 多端通用 | token、错误码、loading、重试、刷新 token 要统一封装 |
13. 本地缓存封装
13.1 基础用法
ts
uni.setStorageSync('token', 'abc')
const token = uni.getStorageSync('token')
uni.removeStorageSync('token')
uni.clearStorageSync()
13.2 封装 storage.ts
ts
// utils/storage.ts
export const storage = {
set<T = any>(key: string, value: T) {
uni.setStorageSync(key, value)
},
get<T = any>(key: string): T | null {
const value = uni.getStorageSync(key)
return value || null
},
remove(key: string) {
uni.removeStorageSync(key)
},
clear() {
uni.clearStorageSync()
}
}
13.3 常见缓存内容
| 内容 | key 示例 |
|---|---|
| token | token |
| 用户信息 | userInfo |
| 搜索历史 | searchHistory |
| 主题配置 | theme |
| 首次启动标记 | hasOpened |
| 定位城市 | locationCity |
14. Pinia 状态管理
14.1 安装
bash
npm install pinia
14.2 注册 Pinia
main.ts
ts
import { createSSRApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
export function createApp() {
const app = createSSRApp(App)
const pinia = createPinia()
app.use(pinia)
return {
app
}
}
14.3 用户 Store
stores/user.ts
ts
import { defineStore } from 'pinia'
import { loginApi, LoginParams } from '@/api/user'
interface UserInfo {
id: number
nickname: string
avatar: string
}
export const useUserStore = defineStore('user', {
state: () => ({
token: uni.getStorageSync('token') || '',
userInfo: uni.getStorageSync('userInfo') as UserInfo | null
}),
getters: {
isLogin: state => !!state.token
},
actions: {
async login(params: LoginParams) {
const res = await loginApi(params)
this.token = res.token
this.userInfo = res.userInfo
uni.setStorageSync('token', res.token)
uni.setStorageSync('userInfo', res.userInfo)
},
logout() {
this.token = ''
this.userInfo = null
uni.removeStorageSync('token')
uni.removeStorageSync('userInfo')
uni.reLaunch({
url: '/pages/login/index'
})
}
}
})
15. 生命周期详细讲解
uni-app 生命周期主要分为:
txt
应用生命周期
页面生命周期
组件生命周期
15.1 应用生命周期
写在 App.vue:
vue
<script lang="ts">
export default {
onLaunch() {
console.log('App 启动')
},
onShow() {
console.log('App 显示')
},
onHide() {
console.log('App 隐藏')
},
onError(error: string) {
console.log('App 错误', error)
}
}
</script>
| 生命周期 | 说明 | 常用场景 |
|---|---|---|
onLaunch |
应用初始化 | 读取缓存、初始化 SDK |
onShow |
应用进入前台 | 刷新状态、检查登录 |
onHide |
应用进入后台 | 暂停任务、保存状态 |
onError |
应用报错 | 错误上报 |
15.2 页面生命周期
vue
<script setup lang="ts">
import {
onLoad,
onShow,
onReady,
onHide,
onUnload,
onPullDownRefresh,
onReachBottom
} from '@dcloudio/uni-app'
onLoad((options) => {
console.log('页面加载参数', options)
})
onShow(() => {
console.log('页面显示')
})
onReady(() => {
console.log('页面初次渲染完成')
})
onHide(() => {
console.log('页面隐藏')
})
onUnload(() => {
console.log('页面卸载')
})
onPullDownRefresh(() => {
console.log('下拉刷新')
uni.stopPullDownRefresh()
})
onReachBottom(() => {
console.log('触底加载更多')
})
</script>
| 生命周期 | 说明 | 常用场景 |
|---|---|---|
onLoad |
页面加载 | 接收参数、请求详情 |
onShow |
页面显示 | 每次进入刷新 |
onReady |
首次渲染完成 | 获取节点信息 |
onHide |
页面隐藏 | 暂停视频、保存草稿 |
onUnload |
页面卸载 | 清理定时器 |
onPullDownRefresh |
下拉刷新 | 刷新列表 |
onReachBottom |
触底 | 分页加载 |
onShareAppMessage |
小程序分享 | 自定义分享 |
16. 样式、rpx、flex 和安全区适配
16.1 rpx 是什么?
rpx 是响应式单位。uni-app 以 750rpx 作为屏幕宽度基准。
txt
屏幕宽度 = 750rpx
设计稿 750px 时:1px ≈ 1rpx
设计稿 375px 时:1px ≈ 2rpx
示例:
scss
.card {
width: 702rpx;
margin: 24rpx;
padding: 32rpx;
border-radius: 20rpx;
background: #fff;
}
16.2 单位使用建议
| 场景 | 推荐单位 |
|---|---|
| 宽度 | rpx、% |
| 高度 | rpx、px |
| 间距 | rpx |
| 字体 | rpx 或 px |
| 边框 | 1px |
| H5 响应式 | vw、vh、%、媒体查询 |
| App 原生导航相关 | px |
16.3 flex 布局建议
scss
.row {
display: flex;
align-items: center;
justify-content: space-between;
}
.column {
display: flex;
flex-direction: column;
}
.flex-1 {
flex: 1;
min-width: 0;
}
16.4 安全区适配
iPhone 底部安全区适配:
scss
.safe-bottom {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
底部固定按钮:
vue
<template>
<view class="bottom-bar safe-bottom">
<button class="submit-btn">提交订单</button>
</view>
</template>
<style scoped lang="scss">
.bottom-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
padding: 20rpx 24rpx;
background: #fff;
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.06);
}
.submit-btn {
height: 88rpx;
line-height: 88rpx;
border-radius: 44rpx;
background: #1677ff;
color: #fff;
}
</style>
16.5 自定义导航栏适配
获取状态栏高度:
ts
const systemInfo = uni.getSystemInfoSync()
const statusBarHeight = systemInfo.statusBarHeight || 0
自定义导航栏示例:
vue
<template>
<view class="navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<text class="title">首页</text>
</view>
</view>
</template>
<script setup lang="ts">
const systemInfo = uni.getSystemInfoSync()
const statusBarHeight = systemInfo.statusBarHeight || 0
</script>
<style scoped lang="scss">
.navbar {
background: #fff;
}
.navbar-content {
height: 44px;
display: flex;
align-items: center;
justify-content: center;
}
.title {
font-size: 32rpx;
font-weight: 600;
}
</style>
17. 条件编译详细讲解
条件编译用于处理平台差异,是 uni-app 实现一套代码多端适配的关键能力。
17.1 常用平台标识
| 平台 | 条件编译标识 |
|---|---|
| H5 | H5 |
| App | APP-PLUS |
| 微信小程序 | MP-WEIXIN |
| 支付宝小程序 | MP-ALIPAY |
| 抖音小程序 | MP-TOUTIAO |
| 百度小程序 | MP-BAIDU |
| QQ 小程序 | MP-QQ |
| 所有小程序 | MP |
17.2 JS / TS 中使用
ts
// #ifdef H5
console.log('只在 H5 执行')
// #endif
// #ifdef MP-WEIXIN
console.log('只在微信小程序执行')
// #endif
// #ifdef APP-PLUS
console.log('只在 App 执行')
// #endif
17.3 template 中使用
vue
<template>
<view>
<!-- #ifdef H5 -->
<view>H5 专属内容</view>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<view>微信小程序专属内容</view>
<!-- #endif -->
<!-- #ifdef APP-PLUS -->
<view>App 专属内容</view>
<!-- #endif -->
</view>
</template>
17.4 CSS 中使用
scss
.page {
min-height: 100vh;
}
/* #ifdef H5 */
.page {
padding-top: 20px;
}
/* #endif */
/* #ifdef MP-WEIXIN */
.page {
padding-top: 20rpx;
}
/* #endif */
/* #ifdef APP-PLUS */
.page {
padding-top: 0;
}
/* #endif */
17.5 多条件判断
ts
// #ifdef H5 || MP-WEIXIN
console.log('H5 或微信小程序执行')
// #endif
// #ifndef H5
console.log('非 H5 平台执行')
// #endif
17.6 条件编译使用原则
txt
1. 能用统一 API 就不要条件编译。
2. 条件编译只处理真正的平台差异。
3. 平台差异代码集中放到 platform 目录。
4. 不要在业务页面到处散落大量 #ifdef。
5. 对支付、登录、分享、权限等能力做统一封装。
18. 一套代码如何适配 H5、小程序、App 三端
这是 uni-app 项目最重要的工程能力。
18.1 核心思想
txt
公共业务逻辑统一
公共页面结构统一
公共组件统一
公共 API 封装统一
公共状态管理统一
平台差异通过:
1. 条件编译
2. 适配层封装
3. manifest.json 平台配置
4. pages.json 多端路由配置
5. 样式单位和安全区适配
6. 平台专属模块隔离
18.2 三端差异总览
| 差异点 | H5 | 小程序 | App |
|---|---|---|---|
| 运行环境 | 浏览器 | 小程序容器 | 原生 App 容器 |
| 请求限制 | 跨域 CORS | 合法域名限制 | 通常无浏览器跨域 |
| 路由 | 浏览器路由 | 小程序页面栈 | App 页面栈 |
| 支付 | H5 支付 | 微信/支付宝小程序支付 | App 支付 SDK |
| 分享 | Web Share / 链接分享 | 小程序分享 | 系统分享 / SDK 分享 |
| 登录 | 账号密码、短信、OAuth | 微信登录、手机号授权 | 手机号、OAuth、第三方登录 |
| 定位 | 浏览器定位 | 小程序定位权限 | 系统定位权限 |
| 文件上传 | input/file 或 uni.chooseImage | 小程序文件能力 | App 原生文件能力 |
| 审核 | 不需要平台审核 | 小程序审核 | 应用市场审核 |
| 更新 | 部署服务器即可 | 提交审核发布 | 发版或热更新 |
18.3 统一组件写法
正确做法:
vue
<template>
<view class="page">
<text>标题</text>
<image src="/static/logo.png" mode="aspectFit" />
</view>
</template>
不推荐:
vue
<template>
<div>
<span>标题</span>
<img src="/static/logo.png" />
</div>
</template>
虽然部分 HTML 标签在某些端可以被转换,但多端项目建议统一使用 uni-app 组件:
txt
view
text
image
button
input
textarea
scroll-view
swiper
navigator
18.4 统一请求层
不要在页面中直接写:
ts
uni.request(...)
而是统一走:
ts
request({
url: '/goods/list',
method: 'GET'
})
优势:
txt
1. H5、小程序、App 共用同一套接口调用方式。
2. token 自动携带。
3. 401 自动跳登录。
4. 错误提示统一。
5. loading 统一。
6. 后续切换 baseURL 更方便。
18.5 统一路由层
不要到处直接写:
ts
uni.navigateTo(...)
建议统一走:
ts
navigateTo('/pages/goods/detail?id=1')
好处:
txt
1. 可以统一做登录拦截。
2. 可以区分 tabBar 和非 tabBar。
3. 可以统一处理页面不存在。
4. 可以记录埋点。
18.6 统一平台能力适配层
推荐目录:
txt
platform
├─ login.ts
├─ pay.ts
├─ share.ts
├─ location.ts
└─ permission.ts
18.6.1 登录适配
platform/login.ts
ts
export async function platformLogin() {
// #ifdef MP-WEIXIN
return new Promise((resolve, reject) => {
uni.login({
provider: 'weixin',
success: resolve,
fail: reject
})
})
// #endif
// #ifdef H5
return Promise.resolve({
type: 'h5',
message: 'H5 使用账号密码或短信登录'
})
// #endif
// #ifdef APP-PLUS
return Promise.resolve({
type: 'app',
message: 'App 可接入手机号、微信、苹果等登录方式'
})
// #endif
}
18.6.2 支付适配
platform/pay.ts
ts
interface PayParams {
provider?: string
orderInfo: any
}
export function pay(params: PayParams) {
// #ifdef MP-WEIXIN
return new Promise((resolve, reject) => {
uni.requestPayment({
provider: 'wxpay',
...params.orderInfo,
success: resolve,
fail: reject
})
})
// #endif
// #ifdef H5
window.location.href = params.orderInfo.payUrl
return Promise.resolve()
// #endif
// #ifdef APP-PLUS
return new Promise((resolve, reject) => {
uni.requestPayment({
provider: params.provider || 'wxpay',
orderInfo: params.orderInfo,
success: resolve,
fail: reject
})
})
// #endif
}
18.6.3 分享适配
platform/share.ts
ts
interface ShareOptions {
title: string
path?: string
imageUrl?: string
url?: string
}
export function share(options: ShareOptions) {
// #ifdef H5
if (navigator.share) {
return navigator.share({
title: options.title,
url: options.url
})
}
uni.setClipboardData({
data: options.url || '',
success() {
uni.showToast({
title: '链接已复制',
icon: 'none'
})
}
})
// #endif
// #ifdef APP-PLUS
uni.share({
provider: 'weixin',
scene: 'WXSceneSession',
type: 0,
href: options.url,
title: options.title,
imageUrl: options.imageUrl
})
// #endif
}
小程序分享通常写在页面生命周期:
ts
import { onShareAppMessage } from '@dcloudio/uni-app'
onShareAppMessage(() => {
return {
title: '商品详情',
path: '/pages/goods/detail?id=100',
imageUrl: '/static/share.png'
}
})
18.7 统一样式规范
推荐:
scss
/* styles/common.scss */
.page {
min-height: 100vh;
background: #f5f5f5;
}
.container {
padding: 24rpx;
}
.card {
border-radius: 20rpx;
background: #fff;
}
.text-ellipsis {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.safe-bottom {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
18.8 环境变量适配三端
ts
// utils/env.ts
const env = import.meta.env.VITE_APP_ENV
const baseURLMap: Record<string, string> = {
development: 'https://dev-api.example.com',
test: 'https://test-api.example.com',
production: 'https://api.example.com'
}
export const ENV = {
env,
apiBaseUrl: baseURLMap[env] || baseURLMap.production
}
18.9 图片资源适配
建议:
txt
1. 本地固定资源放 static。
2. 后台图片使用完整 HTTPS 地址。
3. 小程序远程图片域名要在后台配置。
4. App 图片要注意 http/https 和证书问题。
5. H5 图片要注意跨域、CDN、缓存。
正确:
vue
<image src="/static/logo.png" mode="aspectFit" />
<image :src="goods.imageUrl" mode="aspectFill" />
18.10 一套代码适配三端的推荐架构
txt
页面层 pages
只写业务展示和用户交互
组件层 components
只写通用 UI,不直接依赖平台能力
业务接口层 api
统一管理接口
基础工具层 utils
request、router、storage、validate
状态管理 stores
管理 token、userInfo、cart、settings
平台能力层 platform
登录、支付、分享、定位、权限
条件编译
只放在 platform 层或少量平台差异 UI
19. H5 发布流程和配置
H5 发布本质是把 uni-app 项目打包成 Web 静态资源,然后部署到服务器。
19.1 H5 发布流程总览
txt
1. 配置 manifest.json 的 h5
2. 配置生产接口地址
3. 执行 H5 打包
4. 得到 H5 静态文件
5. 上传到服务器
6. 配置 Nginx / Apache
7. 配置 HTTPS
8. 测试页面、接口、刷新、路由、静态资源
9. 正式上线
19.2 manifest.json H5 配置
json
{
"h5": {
"title": "uni-app 商城",
"router": {
"mode": "hash",
"base": "/"
},
"optimization": {
"treeShaking": {
"enable": true
}
},
"devServer": {
"port": 5173,
"https": false,
"proxy": {
"/api": {
"target": "https://api.example.com",
"changeOrigin": true,
"secure": false
}
}
}
}
}
19.3 hash 和 history 如何选择?
| 路由模式 | URL 示例 | 优点 | 缺点 |
|---|---|---|---|
| hash | https://example.com/#/pages/index/index |
部署简单,刷新不 404 | URL 不够美观 |
| history | https://example.com/pages/index/index |
URL 美观 | 服务器要配置 fallback |
新手建议:
txt
先用 hash,等项目稳定后再考虑 history。
19.4 H5 打包命令
HBuilderX:
txt
发行
→ 网站 - H5 手机版
→ 生成 H5 资源
CLI:
bash
npm run build:h5
常见产物目录:
txt
HBuilderX 项目:unpackage/dist/build/h5
CLI 项目:dist/build/h5
19.5 Nginx 部署 hash 模式
nginx
server {
listen 80;
server_name example.com;
root /www/wwwroot/uni-h5;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass https://api.example.com/;
proxy_set_header Host api.example.com;
proxy_set_header X-Real-IP $remote_addr;
}
}
19.6 Nginx 部署 history 模式
nginx
server {
listen 80;
server_name example.com;
root /www/wwwroot/uni-h5;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
如果刷新 404,基本就是没有配置:
nginx
try_files $uri $uri/ /index.html;
19.7 H5 常见上线问题
| 问题 | 原因 | 解决 |
|---|---|---|
| 页面刷新 404 | history 没配置 fallback | Nginx 配置 try_files |
| 接口跨域 | 后端未配置 CORS | 后端允许跨域或 Nginx 反向代理 |
| 静态资源 404 | base 路径错误 | 检查 router.base 和部署目录 |
| 白屏 | JS 资源加载失败 | 看浏览器 Network |
| 图片不显示 | 路径错误或跨域 | 使用绝对地址或 CDN |
| 首屏慢 | 资源太大 | gzip、CDN、图片压缩、懒加载 |
19.8 H5 上线检查
txt
1. 是否使用生产接口。
2. 是否开启 HTTPS。
3. 是否处理跨域。
4. 页面刷新是否正常。
5. 静态资源是否加载成功。
6. 手机浏览器是否正常。
7. 微信内置浏览器是否正常。
8. 分享标题、描述、图片是否正确。
9. 埋点、统计是否正常。
10. 404、500 页面是否处理。
20. 微信小程序发布流程和配置
20.1 微信小程序发布流程总览
txt
1. 注册微信小程序账号
2. 获取小程序 AppID
3. 在 manifest.json 配置 mp-weixin.appid
4. 配置服务器域名
5. 配置隐私协议和权限说明
6. HBuilderX 或 CLI 打包微信小程序
7. 用微信开发者工具导入打包目录
8. 真机预览测试
9. 上传代码
10. 微信公众平台提交审核
11. 审核通过后发布
20.2 注册和准备
需要准备:
txt
1. 微信公众平台账号
2. 小程序 AppID
3. 小程序名称
4. 小程序头像
5. 服务类目
6. 服务器域名
7. 隐私保护指引
8. 业务资质材料,例如电商、医疗、教育等场景
微信公众平台:
微信小程序官方文档:
https://developers.weixin.qq.com/miniprogram/dev/framework/
20.3 manifest.json 微信小程序配置
json
{
"mp-weixin": {
"appid": "wx1234567890abcdef",
"setting": {
"urlCheck": true,
"es6": true,
"postcss": true,
"minified": true,
"newFeature": true
},
"usingComponents": true,
"permission": {
"scope.userLocation": {
"desc": "用于为你推荐附近门店和计算配送距离"
}
}
}
}
20.4 服务器域名配置
在微信公众平台配置:
txt
微信公众平台
→ 开发
→ 开发管理
→ 开发设置
→ 服务器域名
常见域名类型:
| 类型 | 用途 |
|---|---|
| request 合法域名 | uni.request 请求接口 |
| socket 合法域名 | WebSocket |
| uploadFile 合法域名 | 上传文件 |
| downloadFile 合法域名 | 下载文件 |
| udp 合法域名 | UDP 通信 |
注意:
txt
1. 正式环境域名必须 HTTPS。
2. 不能直接用 IP。
3. 域名需要备案和可访问。
4. 本地开发可以在开发者工具里勾选"不校验合法域名",但上线不行。
5. 接口域名、图片域名、上传域名要分别检查。
20.5 小程序权限配置
如果用到定位:
json
{
"mp-weixin": {
"permission": {
"scope.userLocation": {
"desc": "用于获取当前位置,展示附近门店"
}
}
}
}
常见权限:
| 权限 | 场景 |
|---|---|
scope.userLocation |
定位 |
| 摄像头 | 扫码、拍照 |
| 相册 | 上传图片 |
| 录音 | 语音输入 |
| 通知 | 订阅消息 |
20.6 小程序分包优化
当项目变大时,建议使用分包:
json
{
"subPackages": [
{
"root": "pages-sub/order",
"pages": [
{
"path": "list",
"style": {
"navigationBarTitleText": "订单列表"
}
}
]
}
]
}
优化建议:
txt
1. 首页和 tabBar 放主包。
2. 订单、活动、设置、详情放分包。
3. 大图片不要放进主包。
4. 静态资源尽量使用 CDN。
5. 组件库按需引入。
20.7 打包微信小程序
HBuilderX:
txt
发行
→ 小程序 - 微信
→ 输入小程序名称和 AppID
→ 发行
CLI:
bash
npm run build:mp-weixin
产物目录:
txt
HBuilderX 项目:unpackage/dist/build/mp-weixin
CLI 项目:dist/build/mp-weixin
20.8 微信开发者工具导入
txt
打开微信开发者工具
→ 导入项目
→ 选择 mp-weixin 打包产物目录
→ 填写 AppID
→ 编译运行
→ 真机预览
→ 上传代码
20.9 上传和审核
txt
微信开发者工具
→ 上传
→ 填写版本号和项目备注
→ 上传成功
微信公众平台
→ 版本管理
→ 开发版本
→ 提交审核
→ 填写审核信息
→ 等待审核
→ 审核通过
→ 发布
20.10 小程序审核常见被拒原因
| 被拒原因 | 解决方案 |
|---|---|
| 服务类目不匹配 | 修改服务类目或调整业务描述 |
| 隐私协议未配置 | 在后台补充隐私保护指引 |
| 诱导分享/诱导关注 | 删除相关文案和逻辑 |
| 登录强制授权 | 非必要场景不要强制登录 |
| 页面无法访问 | 检查接口域名、测试账号 |
| 内容违规 | 修改图片、文案、商品 |
| 支付能力未开通 | 先申请微信支付 |
| 资质不足 | 补充营业执照、行业资质 |
| 体验路径不清楚 | 提供测试账号和操作说明 |
20.11 小程序上线检查
txt
1. 使用正式 AppID。
2. 接口域名已配置。
3. 图片、上传、下载域名已配置。
4. 隐私协议已配置。
5. 权限说明已配置。
6. 支付能力已开通。
7. 分包体积符合要求。
8. 真机预览正常。
9. 提供审核测试账号。
10. 页面无空白、无测试数据、无调试按钮。
21. App 发布流程和配置
uni-app 发布 App 分为:
txt
Android App
iOS App
常见打包方式:
txt
1. 云打包:HBuilderX 提交到 DCloud 云端打包,适合大多数项目。
2. 离线打包:使用 Android Studio / Xcode 原生工程打包,适合深度原生定制。
21.1 App 发布流程总览
txt
1. 配置 manifest.json
2. 配置应用名称、图标、启动图
3. 配置包名 / Bundle ID
4. 配置版本号
5. 配置权限和 SDK
6. 准备签名证书
7. 运行真机测试
8. 云打包生成 APK / AAB / IPA
9. Android 提交应用市场
10. iOS 提交 App Store Connect
11. 等待审核
12. 审核通过后上架
21.2 App manifest.json 基础配置
json
{
"name": "uniapp-shop",
"appid": "__UNI__XXXXXXX",
"description": "uni-app 商城项目",
"versionName": "1.0.0",
"versionCode": 100,
"app-plus": {
"usingComponents": true,
"nvueStyleCompiler": "uni-app",
"compilerVersion": 3,
"splashscreen": {
"alwaysShowBeforeRender": true,
"waiting": true,
"autoclose": true,
"delay": 0
},
"modules": {
"Camera": {},
"Gallery": {},
"Geolocation": {},
"OAuth": {},
"Payment": {},
"Share": {}
}
}
}
21.3 Android 配置
Android 需要重点配置:
txt
1. 应用包名 packageName
2. 应用签名证书 keystore
3. 版本名称 versionName
4. 版本号 versionCode
5. 权限
6. 应用图标
7. 启动图
8. 打包格式 APK / AAB
21.3.1 Android 包名
推荐规则:
txt
com.公司名.项目名
示例:
txt
com.example.uniappshop
com.zengzhanwen.shop
注意:
txt
1. 包名是应用唯一标识。
2. 上架后不要更换包名。
3. 更新版本必须使用相同包名。
4. 更新版本必须使用相同签名。
21.3.2 Android 证书
如果是正式商业项目,建议自己生成并保管证书。
生成 keystore 示例:
bash
keytool -genkeypair \
-alias uniappshop \
-keyalg RSA \
-keysize 2048 \
-validity 36500 \
-keystore uniappshop.keystore
需要保存:
txt
证书文件:uniappshop.keystore
证书别名:uniappshop
证书库密码
证书密码
重要:
txt
证书千万不要丢。
证书丢失后,很多应用市场无法正常更新旧版本。
21.3.3 Android 权限配置
常见权限:
xml
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
权限建议:
txt
1. 不需要的权限不要申请。
2. 用到相机、定位、存储时要有明确说明。
3. 国内应用市场对隐私权限审核较严格。
4. 首次打开不要一次性申请大量权限。
21.3.4 APK 和 AAB
| 格式 | 说明 | 适用 |
|---|---|---|
| APK | Android 安装包 | 国内应用市场、测试包 |
| AAB | Android App Bundle | Google Play |
建议:
txt
国内市场:通常准备 APK。
Google Play:通常准备 AAB。
21.4 iOS 配置
iOS 需要重点配置:
txt
1. Apple Developer Program 账号
2. Bundle ID
3. iOS 证书
4. 描述文件 Provisioning Profile
5. App Store Connect 应用记录
6. 隐私清单和权限说明
7. IPA 包
21.4.1 Bundle ID
格式示例:
txt
com.example.uniappshop
注意:
txt
1. Bundle ID 和 Android 包名类似,是 iOS 应用唯一标识。
2. 上架后不要随意更换。
3. 证书、描述文件、App Store Connect 需要保持一致。
21.4.2 iOS 权限说明
常见权限说明:
txt
相机:用于拍摄头像、上传商品图片
相册:用于选择图片上传
定位:用于展示附近门店和配送地址
麦克风:用于录音或视频拍摄
通知:用于订单状态提醒
iOS 权限文案要写清楚,否则容易审核被拒。
21.5 App 云打包流程
HBuilderX:
txt
发行
→ 原生 App - 云打包
→ 选择 Android / iOS
→ 填写包名 / Bundle ID
→ 选择证书
→ 选择打包格式
→ 提交云打包
→ 下载 APK / AAB / IPA
Android 云打包需要:
txt
1. Android 包名
2. 证书类型
3. 证书文件
4. 证书密码
5. 证书别名
6. 打包格式 APK / AAB
iOS 云打包需要:
txt
1. Bundle ID
2. iOS 发布证书
3. Provisioning Profile
4. Apple 相关配置
21.6 App 真机调试
建议流程:
txt
1. 开发阶段使用运行到手机或自定义基座。
2. 测试相机、定位、上传、支付、分享、推送。
3. 弱网测试。
4. Android 多品牌手机测试。
5. iOS 多机型测试。
6. 测试安装、覆盖安装、卸载重装。
21.7 Android 上架流程
不同应用市场要求不同,但大致流程相似:
txt
1. 注册开发者账号
2. 实名认证 / 企业认证
3. 创建应用
4. 填写应用名称、简介、分类、截图
5. 上传 APK / AAB
6. 填写隐私政策
7. 填写权限说明
8. 提交审核
9. 审核通过后上架
常见国内应用市场:
| 市场 | 地址 |
|---|---|
| 华为应用市场 | https://developer.huawei.com/consumer/cn/ |
| 小米开放平台 | https://dev.mi.com/ |
| OPPO 开放平台 | https://open.oppomobile.com/ |
| vivo 开放平台 | https://dev.vivo.com.cn/ |
| 腾讯应用宝 | https://open.qq.com/ |
| Google Play Console | https://play.google.com/console |
21.8 iOS 上架流程
txt
1. 加入 Apple Developer Program
2. 创建 Bundle ID
3. 创建证书和描述文件
4. 在 App Store Connect 创建 App
5. 上传 IPA
6. 填写应用信息、截图、隐私信息
7. 选择构建版本
8. 提交审核
9. 审核通过后发布
相关地址:
| 平台 | 地址 |
|---|---|
| Apple Developer | https://developer.apple.com/ |
| App Store Connect | https://appstoreconnect.apple.com/ |
| App Store Connect 文档 | https://developer.apple.com/help/app-store-connect/ |
21.9 App 审核常见问题
| 问题 | 解决 |
|---|---|
| 权限申请过多 | 删除无用权限,补充用途说明 |
| 隐私政策不完整 | 增加隐私政策 URL 和权限说明 |
| 测试账号无法登录 | 提供可用账号和说明 |
| 支付不合规 | 按平台要求接入支付 |
| App 崩溃 | 真机多机型测试,修复异常 |
| 内容违规 | 修改文案、图片和业务 |
| 版本号错误 | versionCode 递增 |
| 签名错误 | 使用同一证书重新打包 |
22. 三端发布命令和产物目录
22.1 HBuilderX 项目
| 目标平台 | 操作 | 产物目录 |
|---|---|---|
| H5 | 发行 → 网站 - H5 手机版 | unpackage/dist/build/h5 |
| 微信小程序 | 发行 → 小程序 - 微信 | unpackage/dist/build/mp-weixin |
| App | 发行 → 原生 App - 云打包 | APK / AAB / IPA |
22.2 CLI 项目
| 目标平台 | 命令 | 产物目录 |
|---|---|---|
| H5 | npm run build:h5 |
dist/build/h5 |
| 微信小程序 | npm run build:mp-weixin |
dist/build/mp-weixin |
| App 资源 | npm run build:app |
dist/build/app 或相关 app 目录,具体以当前模板为准 |
22.3 发布前环境切换
建议:
txt
开发环境:dev-api.example.com
测试环境:test-api.example.com
生产环境:api.example.com
不要把测试接口发布到正式环境。
23. 项目实战:登录、首页、列表、详情、个人中心
23.1 页面规划
txt
pages/index/index 首页
pages/category/index 分类
pages/user/index 我的
pages/login/index 登录
pages/goods/detail 商品详情
pages-sub/order/list 订单列表
pages-sub/order/detail 订单详情
23.2 pages.json
json
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页"
}
},
{
"path": "pages/category/index",
"style": {
"navigationBarTitleText": "分类"
}
},
{
"path": "pages/user/index",
"style": {
"navigationBarTitleText": "我的"
}
},
{
"path": "pages/login/index",
"style": {
"navigationBarTitleText": "登录"
}
},
{
"path": "pages/goods/detail",
"style": {
"navigationBarTitleText": "商品详情"
}
}
],
"subPackages": [
{
"root": "pages-sub/order",
"pages": [
{
"path": "list",
"style": {
"navigationBarTitleText": "订单列表"
}
},
{
"path": "detail",
"style": {
"navigationBarTitleText": "订单详情"
}
}
]
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "商城",
"navigationBarBackgroundColor": "#ffffff",
"backgroundColor": "#f5f5f5"
},
"tabBar": {
"color": "#666666",
"selectedColor": "#1677ff",
"backgroundColor": "#ffffff",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "static/tabbar/home.png",
"selectedIconPath": "static/tabbar/home-active.png"
},
{
"pagePath": "pages/category/index",
"text": "分类",
"iconPath": "static/tabbar/category.png",
"selectedIconPath": "static/tabbar/category-active.png"
},
{
"pagePath": "pages/user/index",
"text": "我的",
"iconPath": "static/tabbar/user.png",
"selectedIconPath": "static/tabbar/user-active.png"
}
]
}
}
23.3 登录页面
vue
<template>
<view class="login-page">
<view class="logo">商城</view>
<input
v-model="form.username"
class="input"
placeholder="请输入账号"
/>
<input
v-model="form.password"
class="input"
password
placeholder="请输入密码"
/>
<button class="login-btn" @click="handleLogin">
登录
</button>
</view>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
const form = reactive({
username: '',
password: ''
})
const handleLogin = async () => {
if (!form.username || !form.password) {
uni.showToast({
title: '请输入账号和密码',
icon: 'none'
})
return
}
await userStore.login(form)
uni.switchTab({
url: '/pages/index/index'
})
}
</script>
<style scoped lang="scss">
.login-page {
min-height: 100vh;
padding: 120rpx 48rpx;
background: #fff;
}
.logo {
margin-bottom: 80rpx;
text-align: center;
font-size: 48rpx;
font-weight: 700;
}
.input {
height: 88rpx;
margin-bottom: 28rpx;
padding: 0 28rpx;
border-radius: 12rpx;
background: #f5f5f5;
}
.login-btn {
margin-top: 40rpx;
height: 88rpx;
line-height: 88rpx;
border-radius: 44rpx;
background: #1677ff;
color: #fff;
}
</style>
23.4 分页列表 Hook
hooks/usePaging.ts
ts
import { ref } from 'vue'
interface PagingOptions<T> {
request: (params: { page: number; pageSize: number }) => Promise<{
list: T[]
total?: number
}>
pageSize?: number
}
export function usePaging<T>(options: PagingOptions<T>) {
const page = ref(1)
const pageSize = ref(options.pageSize || 10)
const list = ref<T[]>([])
const loading = ref(false)
const finished = ref(false)
const loadData = async (reset = false) => {
if (loading.value) return
if (finished.value && !reset) return
loading.value = true
if (reset) {
page.value = 1
list.value = []
finished.value = false
}
try {
const res = await options.request({
page: page.value,
pageSize: pageSize.value
})
const rows = res.list || []
list.value.push(...rows)
if (rows.length < pageSize.value) {
finished.value = true
} else {
page.value++
}
} finally {
loading.value = false
}
}
return {
page,
pageSize,
list,
loading,
finished,
loadData
}
}
23.5 首页列表
vue
<template>
<view class="page">
<swiper class="banner" indicator-dots autoplay circular>
<swiper-item v-for="item in banners" :key="item.id">
<image class="banner-img" :src="item.image" mode="aspectFill" />
</swiper-item>
</swiper>
<view class="goods-list">
<GoodsCard
v-for="item in list"
:key="item.id"
:goods="item"
@click="goDetail"
/>
</view>
<view class="loading-text">
{{ finished ? '没有更多了' : '加载中...' }}
</view>
</view>
</template>
<script setup lang="ts">
import { onLoad, onReachBottom, onPullDownRefresh } from '@dcloudio/uni-app'
import { usePaging } from '@/hooks/usePaging'
import { getGoodsListApi } from '@/api/goods'
import { navigateTo } from '@/utils/router'
const banners = [
{ id: 1, image: '/static/banner/banner1.png' },
{ id: 2, image: '/static/banner/banner2.png' }
]
const { list, finished, loadData } = usePaging({
request: getGoodsListApi,
pageSize: 10
})
const goDetail = (goods: any) => {
navigateTo(`/pages/goods/detail?id=${goods.id}`)
}
onLoad(() => {
loadData()
})
onReachBottom(() => {
loadData()
})
onPullDownRefresh(async () => {
await loadData(true)
uni.stopPullDownRefresh()
})
</script>
<style scoped lang="scss">
.page {
min-height: 100vh;
background: #f5f5f5;
}
.banner {
width: 100%;
height: 320rpx;
}
.banner-img {
width: 100%;
height: 320rpx;
}
.goods-list {
padding: 24rpx;
display: flex;
flex-direction: column;
gap: 24rpx;
}
.loading-text {
padding: 24rpx;
text-align: center;
font-size: 26rpx;
color: #999;
}
</style>
24. 常见问题和解决方案
24.1 小程序请求失败
原因:
txt
1. 没有配置 request 合法域名。
2. 接口不是 HTTPS。
3. 使用了 IP 或 localhost。
4. 证书不合法。
5. 开发者工具开启了校验域名。
解决:
txt
1. 到微信公众平台配置服务器域名。
2. 使用正式 HTTPS 域名。
3. 开发阶段可临时关闭合法域名校验。
4. 真机测试前必须配置正确。
24.2 H5 跨域
原因:
txt
浏览器同源策略限制。
解决:
txt
1. 后端配置 CORS。
2. Nginx 反向代理。
3. 本地开发用 devServer.proxy。
4. 生产环境不要依赖本地代理。
24.3 tabBar 页面跳转失败
错误:
ts
uni.navigateTo({
url: '/pages/index/index'
})
正确:
ts
uni.switchTab({
url: '/pages/index/index'
})
24.4 页面刷新 404
原因:
txt
H5 history 模式没有配置服务端 fallback。
解决:
nginx
location / {
try_files $uri $uri/ /index.html;
}
24.5 App 权限不生效
解决:
txt
1. 检查 manifest.json 权限。
2. 检查 Android / iOS 原生权限说明。
3. 重新打包自定义基座或正式包。
4. 真机测试,不要只看浏览器。
24.6 图片不显示
可能原因:
txt
1. 本地路径错误。
2. 图片没放 static。
3. 远程图片域名未配置。
4. 图片链接 http 被拦截。
5. 图片太大加载慢。
解决:
txt
1. 本地图片使用 /static/logo.png。
2. 远程图片使用 HTTPS。
3. 小程序后台配置图片域名。
4. 图片上 CDN 并压缩。
24.7 scroll-view 性能差
解决:
txt
1. 普通页面长列表优先用页面滚动 + onReachBottom。
2. 超长列表使用虚拟列表。
3. App 高性能列表考虑 nvue / list-view。
4. 图片懒加载。
5. 减少复杂阴影和圆角。
24.8 条件编译太乱
解决:
txt
1. 条件编译不要散落在大量页面中。
2. 平台差异放 platform 目录。
3. 支付、分享、登录、权限统一封装。
4. 页面只调用统一方法。
25. 学习路线
第一阶段:Vue 基础
txt
1. Vue3 模板语法
2. ref、reactive
3. computed、watch
4. props、emit
5. 组件封装
6. 生命周期
7. TypeScript 基础
第二阶段:uni-app 基础
txt
1. 创建项目
2. 页面写法
3. view、text、image、button 等组件
4. pages.json
5. manifest.json
6. 路由跳转
7. 生命周期
第三阶段:项目能力
txt
1. 请求封装
2. 登录注册
3. token 管理
4. Pinia 状态管理
5. 表单校验
6. 图片上传
7. 分页列表
8. 下拉刷新
9. 上拉加载
第四阶段:三端适配
txt
1. H5 跨域和部署
2. 小程序合法域名和审核
3. App 权限和打包
4. 条件编译
5. 支付、分享、登录适配
6. 样式和安全区适配
第五阶段:上线发布
txt
1. H5 部署到服务器
2. 微信小程序上传审核发布
3. Android APK/AAB 打包
4. iOS IPA 打包
5. 应用市场审核
6. 版本更新
7. 崩溃监控和错误上报
26. 资源文档链接大全
26.1 uni-app 官方资源
26.2 UI 组件库
| 资源 | 链接 | 说明 |
|---|---|---|
| uni-ui | https://uniapp.dcloud.net.cn/component/uniui/uni-ui.html | DCloud 官方 UI |
| uView Plus | https://uiadmin.net/uview-plus/ | Vue3 版 uView |
| ThorUI | https://thorui.cn/ | 移动端 UI 组件 |
| Wot Design Uni | https://wot-design-uni.cn/ | uni-app Vue3 组件库 |
26.3 小程序官方文档
| 平台 | 链接 | 说明 |
|---|---|---|
| 微信小程序文档 | https://developers.weixin.qq.com/miniprogram/dev/framework/ | 微信小程序开发 |
| 微信开发者工具 | https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html | 调试、预览、上传 |
| 微信公众平台 | https://mp.weixin.qq.com/ | 小程序后台 |
| 支付宝小程序文档 | https://opendocs.alipay.com/mini/developer | 支付宝小程序 |
| 抖音小程序文档 | https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/introduction/overview | 抖音小程序 |
| 百度智能小程序 | https://smartprogram.baidu.com/docs/introduction/enter_application/ | 百度小程序 |
| QQ 小程序 | https://q.qq.com/wiki/ | QQ 小程序 |
26.4 App 发布相关
| 资源 | 链接 | 说明 |
|---|---|---|
| Apple Developer | https://developer.apple.com/ | 苹果开发者 |
| App Store Connect | https://appstoreconnect.apple.com/ | iOS App 上架后台 |
| App Store Connect 帮助 | https://developer.apple.com/help/app-store-connect/ | 官方上架文档 |
| Google Play Console | https://play.google.com/console | Google Play 上架后台 |
| Android Developers | https://developer.android.com/ | Android 官方文档 |
| 华为开发者联盟 | https://developer.huawei.com/consumer/cn/ | 华为应用市场 |
| 小米开放平台 | https://dev.mi.com/ | 小米应用商店 |
| OPPO 开放平台 | https://open.oppomobile.com/ | OPPO 应用市场 |
| vivo 开放平台 | https://dev.vivo.com.cn/ | vivo 应用市场 |
| 腾讯开放平台 | https://open.qq.com/ | 应用宝 |
26.5 前端基础资源
| 资源 | 链接 | 说明 |
|---|---|---|
| Vue 官方文档 | https://cn.vuejs.org/ | Vue3 学习 |
| TypeScript 文档 | https://www.typescriptlang.org/zh/ | TS 学习 |
| Pinia 文档 | https://pinia.vuejs.org/zh/ | 状态管理 |
| Vite 文档 | https://cn.vitejs.dev/ | 构建工具 |
| MDN Web Docs | https://developer.mozilla.org/zh-CN/ | Web 基础 |
| Can I Use | https://caniuse.com/ | CSS/JS 兼容性 |
27. 上线前检查清单
27.1 通用检查
txt
□ 是否切换到生产环境接口
□ token 失效是否能自动跳登录
□ 请求失败是否有提示
□ loading 是否正常关闭
□ 页面是否有空白
□ 图片是否都能加载
□ 表单校验是否完整
□ 版本号是否递增
□ 是否删除 console 和测试入口
□ 是否处理弱网和超时
□ 是否有隐私政策
□ 是否测试 Android 和 iOS 真机
27.2 H5 检查
txt
□ 域名是否配置 HTTPS
□ 路由刷新是否正常
□ 跨域是否解决
□ 静态资源是否加载成功
□ gzip 是否开启
□ 微信浏览器是否正常
□ 移动端适配是否正常
27.3 微信小程序检查
txt
□ AppID 是否正确
□ request 合法域名是否配置
□ uploadFile/downloadFile 域名是否配置
□ 隐私协议是否配置
□ 权限说明是否配置
□ 分包体积是否合规
□ 真机预览是否正常
□ 审核账号是否可用
□ 支付、分享、订阅消息是否正常
27.4 App 检查
txt
□ 应用图标是否正确
□ 启动图是否正确
□ 包名 / Bundle ID 是否正确
□ versionCode 是否递增
□ Android 签名是否正确
□ iOS 证书和描述文件是否正确
□ 权限申请是否合理
□ 隐私政策是否完整
□ 崩溃日志是否接入
□ 应用市场资料是否完整
总结
uni-app 的核心不是简单地"写一次,到处运行",而是:
txt
统一业务代码
统一组件规范
统一请求封装
统一状态管理
统一路由封装
统一样式规范
通过条件编译和平台适配层处理差异
分别完成 H5、小程序、App 的配置和发布
你真正要掌握的重点是:
txt
1. Vue3 + TypeScript 基础
2. pages.json 页面和路由配置
3. manifest.json 应用和平台配置
4. uni-app 内置组件和 API
5. request、router、storage 封装
6. Pinia 状态管理
7. rpx、flex、安全区适配
8. 条件编译
9. H5、小程序、App 三端差异
10. H5 部署、小程序审核、App 打包上架
建议最终做一个完整项目练习:
txt
登录注册
首页轮播
商品分类
商品列表
商品详情
购物车
订单提交
个人中心
设置页面
H5 发布
微信小程序发布
Android App 打包
iOS App 打包
完整做完一遍,你就基本具备 uni-app 商业项目开发和上线能力。