Chapter 02

项目结构与配置

掌握 pages.json、manifest.json 的完整配置体系,学会条件编译处理多平台差异

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             # 应用基础信息 + 各平台专项配置
pages/ 目录规范

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"
    }
  }
}

关键字段详解

pages
页面路由注册数组。数组中第一个页面是应用启动页,这是一个重要的约定,调整页面顺序会影响首屏展示。每个页面的 style 对象控制该页面的导航栏样式、下拉刷新等。
subPackages(分包)
微信小程序的分包加载配置,将低频访问的页面放入子包,减小主包体积(微信小程序主包限制 2MB)。H5 和 App 端会自动忽略此配置。
tabBar
底部标签导航配置,最少 2 个、最多 5 个 tab。tabBar 页面必须在 pages 数组中,且不能是 subPackages 中的页面
globalStyle
全局页面样式,可被单个页面的 style 覆盖,遵循"局部优先"原则。
preloadRule
分包预加载规则,在进入指定页面时提前加载某个子包,改善用户体验。仅对小程序有效。

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
  }
}
AppID 的重要性

manifest.json 中的 appid 是 DCloud 平台的应用标识,用于云打包、uni-push 推送、uni-statistics 统计等服务的身份验证。不同应用必须使用不同的 appid,不能共用。在 HBuilderX 中新建项目时会自动分配。

2.4 条件编译详解

条件编译是 uni-app 解决多端差异的核心机制,通过特殊注释语法,让编译器在生成特定平台代码时包含或排除相应代码块。

支持的平台标识符

平台标识符说明
H5 / WebH5浏览器环境
App(全部)APPAndroid + iOS
App AndroidAPP-ANDROID仅 Android
App iOSAPP-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.jsonpaths 中也需要同步配置,否则 TS 会报找不到模块。

2.8 小结