2.1 项目结构全解析
一个标准的 uni-app(Vue 3 + Vite 模板)项目有以下文件结构。熟悉每个文件的职责,是高效开发的基础:
src/
├── pages/ # 页面目录(必须与 pages.json 对应)
│ ├── index/
│ │ └── index.vue
│ ├── detail/
│ │ └── detail.vue
│ └── user/
│ └── user.vue
├── components/ # 公共组件
│ └── MyButton.vue
├── composables/ # Composition API 复用逻辑
│ └── useRequest.ts
├── stores/ # Pinia 状态仓库
│ └── user.ts
├── utils/ # 工具函数
│ └── request.ts
├── static/ # 静态资源(打包时直接复制,不编译)
│ ├── images/
│ └── fonts/
├── uni_modules/ # 插件市场安装的插件
├── App.vue # 应用根组件
├── main.ts # Vue 实例创建入口
├── pages.json # 路由 + 导航栏 + tabBar 配置
└── manifest.json # 应用基础信息 + 各平台专项配置
uni-app 要求每个页面单独放在一个子目录中,目录名即为路由路径的一部分。例如 pages/detail/detail.vue 的路由路径为 /pages/detail/detail。这种强制规范有助于保持项目结构清晰。
2.2 pages.json 深度解析
pages.json 是 uni-app 的路由注册中心和全局 UI 配置文件,相当于传统 Web 应用中 Vue Router 配置文件与全局样式的结合体。
完整配置示例
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页",
"navigationBarBackgroundColor": "#0ea5e9",
"navigationBarTextStyle": "white",
"enablePullDownRefresh": true
}
},
{
"path": "pages/detail/detail",
"style": {
"navigationBarTitleText": "详情",
// 微信小程序特有配置
"mp-weixin": {
"navigationStyle": "custom" // 自定义导航栏
}
}
}
],
"subPackages": [
{
"root": "packageA",
"pages": [
{
"path": "pages/order/order",
"style": { "navigationBarTitleText": "订单" }
}
]
}
],
"tabBar": {
"color": "#78716c",
"selectedColor": "#0ea5e9",
"backgroundColor": "#ffffff",
"borderStyle": "black",
"list": [
{
"pagePath": "pages/index/index",
"iconPath": "static/tabbar/home.png",
"selectedIconPath": "static/tabbar/home-active.png",
"text": "首页"
},
{
"pagePath": "pages/user/user",
"iconPath": "static/tabbar/user.png",
"selectedIconPath": "static/tabbar/user-active.png",
"text": "我的"
}
]
},
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#ffffff",
"backgroundColor": "#f5f5f0"
},
"preloadRule": {
"pages/index/index": {
"packages": ["packageA"],
"network": "all"
}
}
}
关键字段详解
2.3 manifest.json 详解
manifest.json 是应用的"身份证",包含应用基础信息和各平台的专项配置。
{
"name": "我的应用",
"appid": "__UNI__XXXXXXX", // DCloud AppID,唯一标识
"description": "应用描述",
"versionCode": "100", // 版本号(整数,用于升级判断)
"versionName": "1.0.0", // 版本名(展示用)
// Vue3 编译器配置
"vue": "3",
// H5 平台配置
"h5": {
"title": "我的应用",
"router": {
"mode": "history", // hash 或 history 模式
"base": "/"
},
"devServer": {
"port": 5173,
"https": false
}
},
// App 端配置
"app": {
"distribute": {
"android": {
"permissions": [
"<uses-permission android:name=\"android.permission.INTERNET\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>"
],
"minSdkVersion": 21,
"targetSdkVersion": 34
},
"ios": {
"privacyDescription": {
"NSCameraUsageDescription": "用于拍照功能",
"NSLocationWhenInUseUsageDescription": "用于获取位置信息"
}
}
}
},
// 微信小程序配置
"mp-weixin": {
"appid": "wx1234567890abcdef", // 微信小程序 AppID
"setting": {
"urlCheck": false // 关闭合法域名校验(仅开发阶段)
},
"usingComponents": true
}
}
manifest.json 中的 appid 是 DCloud 平台的应用标识,用于云打包、uni-push 推送、uni-statistics 统计等服务的身份验证。不同应用必须使用不同的 appid,不能共用。在 HBuilderX 中新建项目时会自动分配。
2.4 条件编译详解
条件编译是 uni-app 解决多端差异的核心机制,通过特殊注释语法,让编译器在生成特定平台代码时包含或排除相应代码块。
支持的平台标识符
| 平台 | 标识符 | 说明 |
|---|---|---|
| H5 / Web | H5 | 浏览器环境 |
| App(全部) | APP | Android + iOS |
| App Android | APP-ANDROID | 仅 Android |
| App iOS | APP-IOS | 仅 iOS |
| 微信小程序 | MP-WEIXIN | 微信小程序 |
| 支付宝小程序 | MP-ALIPAY | 支付宝小程序 |
| 所有小程序 | MP | 所有小程序平台 |
在 JS / TS 中使用条件编译
// 在 .vue 文件的 <script> 或 .ts 文件中
let shareText = ''
// #ifdef MP-WEIXIN
// 微信小程序:使用微信分享 API
shareText = '微信分享'
wx.showShareMenu({ withShareTicket: true })
// #endif
// #ifdef APP
// App 端:使用原生分享
shareText = '原生分享'
plus.share.sendWithSystem({ content: '分享内容', href: 'https://example.com' })
// #endif
// #ifndef APP || MP-WEIXIN
// 既不是 App 也不是微信小程序(如 H5 或其他小程序)
shareText = '复制链接分享'
// #endif
在 HTML 模板中使用条件编译
<template>
<view>
<!-- #ifdef MP-WEIXIN -->
<!-- 仅微信小程序展示的组件 -->
<open-data type="userNickName"/>
<!-- #endif -->
<!-- #ifdef H5 -->
<a href="/download">下载 App</a>
<!-- #endif -->
<!-- 所有平台都有的内容 -->
<text>通用内容</text>
</view>
</template>
在 CSS 中使用条件编译
<style>
/* #ifdef H5 */
.container {
max-width: 1200px;
margin: 0 auto;
}
/* #endif */
/* #ifdef APP */
.container {
/* App 端全屏布局 */
flex: 1;
}
/* #endif */
</style>
2.5 Vue 3 Composition API 在 uni-app 中的使用
uni-app 从 2.5.5 版本起全面支持 Vue 3,推荐使用 <script setup> 语法(Composition API 的糖衣语法)。这也是本教程统一采用的写法。
标准页面结构
<template>
<view class="container">
<text class="title">{{ title }}</text>
<view v-for="item in list" :key="item.id">
<text>{{ item.name }}</text>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
// 响应式数据
const title = ref('页面标题')
const list = ref<{ id: number; name: string }[]>([])
// uni-app 页面生命周期(携带路由参数)
onLoad((query) => {
console.log('路由参数:', query)
const id = query?.id as string
fetchData(id)
})
// Vue 组件生命周期
onMounted(() => {
console.log('Vue 组件挂载完成')
})
async function fetchData(id: string) {
const res = await uni.request({
url: `https://api.example.com/items/${id}`
})
list.value = res.data as typeof list.value
}
</script>
<style scoped>
.container { padding: 16px; }
.title { font-size: 18px; font-weight: bold; }
</style>
使用 defineOptions 设置页面选项
在 <script setup> 模式下,可以用 defineOptions 宏设置组件选项:
<script setup lang="ts">
// 设置组件名,用于 keep-alive 缓存
defineOptions({
name: 'DetailPage'
})
</script>
2.6 TypeScript 配置
uni-app 项目推荐开启 TypeScript,以获得更好的类型检查和智能提示。项目根目录的 tsconfig.json:
{
"extends": "@vue/tsconfig/tsconfig.json",
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"jsx": "preserve",
"lib": ["ESNext", "DOM"],
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"types": [
"@dcloudio/types" // uni-app API 类型定义
]
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
}
2.7 vite.config.ts 配置
import { defineConfig } from 'vite'
import uni from '@dcloudio/vite-plugin-uni'
import { resolve } from 'path'
export default defineConfig({
plugins: [uni()],
resolve: {
alias: {
'@': resolve(__dirname, 'src')
}
},
server: {
port: 5173,
proxy: {
'/api': {
target: 'https://api.example.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
})
配置 @ 别名后,可以用 import { useUserStore } from '@/stores/user' 代替相对路径,避免 ../../../ 的烦恼。注意在 tsconfig.json 的 paths 中也需要同步配置,否则 TS 会报找不到模块。
2.8 小结
pages.json集中管理所有路由注册、导航栏、tabBar、分包配置manifest.json管理应用 ID、版本信息和各平台专项配置(权限等)- 条件编译(
#ifdef/#endif)用于在同一份代码中处理多平台差异 - 推荐使用
<script setup lang="ts">+ Composition API 编写页面逻辑 - uni-app 页面生命周期(onLoad 等)和 Vue 生命周期(onMounted 等)并存,各有其用途