Skip to content

全局配置

在uni-app开发中,全局配置是指应用级别的设置,包括应用的基本信息、页面路由、窗口样式、权限申请等。本文将详细介绍uni-app中的全局配置方法和常用配置项。

pages.json

pages.json是uni-app项目的全局配置文件,用来对uni-app进行全局配置,决定页面文件的路径、窗口样式、原生的导航栏、底部的原生tabbar等。

基本结构

一个基本的pages.json文件结构如下:

json
{
  "pages": [{
    "path": "pages/index/index",
    "style": {
      "navigationBarTitleText": "首页"
    }
  }, {
    "path": "pages/detail/detail",
    "style": {
      "navigationBarTitleText": "详情页"
    }
  }],
  "globalStyle": {
    "navigationBarTextStyle": "white",
    "navigationBarTitleText": "uni-app",
    "navigationBarBackgroundColor": "#007AFF",
    "backgroundColor": "#F8F8F8"
  },
  "tabBar": {
    "color": "#7A7E83",
    "selectedColor": "#007AFF",
    "borderStyle": "black",
    "backgroundColor": "#ffffff",
    "list": [{
      "pagePath": "pages/index/index",
      "iconPath": "static/tab-home.png",
      "selectedIconPath": "static/tab-home-active.png",
      "text": "首页"
    }, {
      "pagePath": "pages/mine/mine",
      "iconPath": "static/tab-mine.png",
      "selectedIconPath": "static/tab-mine-active.png",
      "text": "我的"
    }]
  }
}

主要配置项

1. pages

pages数组用于配置应用的页面路由,每一项是一个对象,对应一个页面。

json
"pages": [{
  "path": "pages/index/index", // 页面路径
  "style": {                   // 页面窗口表现
    "navigationBarTitleText": "首页", // 导航栏标题
    "enablePullDownRefresh": true    // 是否开启下拉刷新
  }
}]

提示

pages数组中第一项表示应用的默认启动页。

2. globalStyle

globalStyle用于设置应用的全局样式,会作用于每一个页面。如果某个页面有特定的样式需求,可以在页面的style中单独配置,会覆盖全局样式。

json
"globalStyle": {
  "navigationBarTextStyle": "white",        // 导航栏标题颜色,仅支持 black/white
  "navigationBarTitleText": "uni-app",      // 导航栏标题文字
  "navigationBarBackgroundColor": "#007AFF", // 导航栏背景色
  "backgroundColor": "#F8F8F8",             // 窗口背景色
  "backgroundTextStyle": "light",           // 下拉刷新背景字体、loading 图的样式,仅支持 dark/light
  "enablePullDownRefresh": false,           // 是否开启下拉刷新
  "usingComponents": {}                     // 引用小程序组件
}

3. tabBar

tabBar用于配置应用的底部选项卡,最少2个、最多5个tab。

json
"tabBar": {
  "color": "#7A7E83",            // tab 上的文字默认颜色
  "selectedColor": "#007AFF",    // tab 上的文字选中时的颜色
  "borderStyle": "black",        // tabbar 上边框的颜色,可选值 black/white
  "backgroundColor": "#ffffff",  // tab 的背景色
  "list": [{                     // tab 的列表,最少2个、最多5个
    "pagePath": "pages/index/index", // 页面路径
    "iconPath": "static/tab-home.png", // 图片路径,建议尺寸为81px * 81px
    "selectedIconPath": "static/tab-home-active.png", // 选中时的图片路径
    "text": "首页"               // tab 上按钮文字
  }],
  "position": "bottom"           // 可选值 bottom、top
}

4. condition

condition用于启用特定页面的场景值,方便开发调试。

json
"condition": {
  "current": 0,                  // 当前激活的模式(list 的索引项)
  "list": [{
    "name": "详情页",            // 模式名称
    "path": "pages/detail/detail", // 启动页面
    "query": "id=1"              // 启动参数
  }]
}

5. subPackages

subPackages用于分包加载,优化小程序的启动速度。

json
"subPackages": [{
  "root": "pagesA",              // 分包根目录
  "pages": [{                    // 分包页面路径
    "path": "list/list",
    "style": {
      "navigationBarTitleText": "列表"
    }
  }]
}]

6. preloadRule

preloadRule用于配置分包预下载规则。

json
"preloadRule": {
  "pages/index/index": {
    "network": "all",            // 在指定网络下预下载,可选值为:all(不限网络)、wifi(仅wifi下预下载)
    "packages": ["pagesA"]       // 进入页面后预下载分包的 root 或 name
  }
}

7. easycom

easycom用于配置组件自动引入规则,不需要在页面中手动引入组件。

json
"easycom": {
  "autoscan": true,              // 是否开启自动扫描
  "custom": {                    // 自定义扫描规则
    "^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue"
  }
}

manifest.json

manifest.json是应用的配置文件,用于指定应用的名称、图标、权限等信息。

基本结构

json
{
  "name": "应用名称",
  "appid": "",
  "description": "应用描述",
  "versionName": "1.0.0",
  "versionCode": "100",
  "transformPx": false,
  "app-plus": {
    "usingComponents": true,
    "nvueCompiler": "uni-app",
    "splashscreen": {
      "alwaysShowBeforeRender": true,
      "waiting": true,
      "autoclose": true,
      "delay": 0
    },
    "modules": {},
    "distribute": {
      "android": {
        "permissions": [
          "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>"
        ]
      },
      "ios": {},
      "sdkConfigs": {}
    }
  },
  "quickapp": {},
  "mp-weixin": {
    "appid": "wx开头的微信小程序appid",
    "setting": {
      "urlCheck": false
    },
    "usingComponents": true
  },
  "mp-alipay": {
    "usingComponents": true
  },
  "mp-baidu": {
    "usingComponents": true
  },
  "mp-toutiao": {
    "usingComponents": true
  },
  "h5": {
    "router": {
      "base": "/"
    }
  }
}

主要配置项

1. 基本信息

json
{
  "name": "应用名称",
  "appid": "",
  "description": "应用描述",
  "versionName": "1.0.0",
  "versionCode": "100"
}

2. app-plus(App 特有配置)

json
"app-plus": {
  "usingComponents": true,
  "nvueCompiler": "uni-app",
  "splashscreen": {
    "alwaysShowBeforeRender": true,
    "waiting": true,
    "autoclose": true,
    "delay": 0
  },
  "modules": {
    "Payment": {},
    "Push": {},
    "Share": {},
    "OAuth": {}
  },
  "distribute": {
    "android": {
      "permissions": [
        "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>"
      ],
      "minSdkVersion": 21,
      "targetSdkVersion": 28,
      "abiFilters": ["armeabi-v7a", "arm64-v8a"]
    },
    "ios": {
      "dSYMs": false,
      "idfa": false
    },
    "sdkConfigs": {
      "ad": {},
      "maps": {},
      "oauth": {},
      "payment": {},
      "push": {},
      "share": {}
    },
    "icons": {
      "android": {
        "hdpi": "unpackage/res/icons/72x72.png",
        "xhdpi": "unpackage/res/icons/96x96.png",
        "xxhdpi": "unpackage/res/icons/144x144.png",
        "xxxhdpi": "unpackage/res/icons/192x192.png"
      },
      "ios": {
        "appstore": "unpackage/res/icons/1024x1024.png",
        "ipad": {
          "app": "unpackage/res/icons/76x76.png",
          "app@2x": "unpackage/res/icons/152x152.png",
          "notification": "unpackage/res/icons/20x20.png",
          "notification@2x": "unpackage/res/icons/40x40.png",
          "proapp@2x": "unpackage/res/icons/167x167.png",
          "settings": "unpackage/res/icons/29x29.png",
          "settings@2x": "unpackage/res/icons/58x58.png",
          "spotlight": "unpackage/res/icons/40x40.png",
          "spotlight@2x": "unpackage/res/icons/80x80.png"
        },
        "iphone": {
          "app@2x": "unpackage/res/icons/120x120.png",
          "app@3x": "unpackage/res/icons/180x180.png",
          "notification@2x": "unpackage/res/icons/40x40.png",
          "notification@3x": "unpackage/res/icons/60x60.png",
          "settings@2x": "unpackage/res/icons/58x58.png",
          "settings@3x": "unpackage/res/icons/87x87.png",
          "spotlight@2x": "unpackage/res/icons/80x80.png",
          "spotlight@3x": "unpackage/res/icons/120x120.png"
        }
      }
    }
  }
}

3. 各平台小程序配置

json
"mp-weixin": {
  "appid": "wx开头的微信小程序appid",
  "setting": {
    "urlCheck": false,
    "es6": true,
    "postcss": true,
    "minified": true
  },
  "usingComponents": true,
  "permission": {
    "scope.userLocation": {
      "desc": "你的位置信息将用于小程序位置接口的效果展示"
    }
  }
}

4. H5 配置

json
"h5": {
  "title": "应用名称",
  "router": {
    "mode": "history",
    "base": "/"
  },
  "devServer": {
    "https": false,
    "port": 8080,
    "disableHostCheck": true,
    "proxy": {
      "/api": {
        "target": "http://localhost:3000",
        "changeOrigin": true,
        "secure": false,
        "pathRewrite": {
          "^/api": ""
        }
      }
    }
  },
  "optimization": {
    "treeShaking": {
      "enable": true
    }
  },
  "template": "index.html"
}

uni.setStorage 与 getStorage

除了配置文件外,uni-app还提供了运行时的全局数据存储方法,可以在应用的不同页面之间共享数据。

存储数据

javascript
// 同步存储
try {
  uni.setStorageSync('key', 'value')
} catch (e) {
  console.error(e)
}

// 异步存储
uni.setStorage({
  key: 'key',
  data: 'value',
  success: function() {
    console.log('存储成功')
  },
  fail: function(err) {
    console.error('存储失败', err)
  }
})

获取数据

javascript
// 同步获取
try {
  const value = uni.getStorageSync('key')
  if (value) {
    console.log(value)
  }
} catch (e) {
  console.error(e)
}

// 异步获取
uni.getStorage({
  key: 'key',
  success: function(res) {
    console.log(res.data)
  },
  fail: function(err) {
    console.error('获取失败', err)
  }
})

删除数据

javascript
// 同步删除
try {
  uni.removeStorageSync('key')
} catch (e) {
  console.error(e)
}

// 异步删除
uni.removeStorage({
  key: 'key',
  success: function() {
    console.log('删除成功')
  }
})

清空所有数据

javascript
// 同步清空
try {
  uni.clearStorageSync()
} catch (e) {
  console.error(e)
}

// 异步清空
uni.clearStorage({
  success: function() {
    console.log('清空成功')
  }
})

getApp() 与 getCurrentPages()

uni-app提供了全局的应用实例和页面实例获取方法。

getApp()

getApp()用于获取当前应用实例,可以用来获取和设置全局数据。

javascript
const app = getApp()

// 设置全局数据
app.globalData = {
  userInfo: null
}

// 获取全局数据
console.log(app.globalData.userInfo)

getCurrentPages()

getCurrentPages()用于获取当前页面栈的实例,数组中第一个元素为首页,最后一个元素为当前页面。

javascript
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1]
const prevPage = pages[pages.length - 2]

// 获取当前页面的路径
console.log(currentPage.route)

// 获取当前页面的参数
console.log(currentPage.options)

// 调用上一个页面的方法
if (prevPage) {
  prevPage.refreshData()
}

Vuex 状态管理

对于复杂应用,推荐使用Vuex进行状态管理。

安装Vuex

bash
npm install vuex --save

创建Store

javascript
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 0,
    userInfo: null
  },
  mutations: {
    increment(state) {
      state.count++
    },
    setUserInfo(state, userInfo) {
      state.userInfo = userInfo
    }
  },
  actions: {
    login({ commit }, userInfo) {
      return new Promise((resolve, reject) => {
        // 模拟登录请求
        setTimeout(() => {
          commit('setUserInfo', userInfo)
          resolve()
        }, 1000)
      })
    }
  },
  getters: {
    isLoggedIn: state => !!state.userInfo
  }
})

在main.js中注册

javascript
// main.js
import Vue from 'vue'
import App from './App'
import store from './store'

Vue.config.productionTip = false

const app = new Vue({
  store,
  ...App
})
app.$mount()

在组件中使用

html
<template>
  <view class="container">
    <text>Count: {{ count }}</text>
    <button @tap="increment">+1</button>
    
    <view v-if="isLoggedIn">
      <text>欢迎, {{ userInfo.name }}</text>
      <button @tap="logout">退出登录</button>
    </view>
    <view v-else>
      <button @tap="login">登录</button>
    </view>
  </view>
</template>

<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'

export default {
  computed: {
    ...mapState(['count', 'userInfo']),
    ...mapGetters(['isLoggedIn'])
  },
  methods: {
    ...mapMutations(['increment']),
    ...mapActions(['login']),
    logout() {
      this.$store.commit('setUserInfo', null)
    },
    handleLogin() {
      const userInfo = {
        id: 1,
        name: '张三',
        avatar: '/static/avatar.png'
      }
      this.login(userInfo)
    }
  }
}
</script>

uni.getSystemInfo 获取系统信息

在开发过程中,经常需要获取设备的系统信息,如屏幕尺寸、设备型号等。

javascript
// 同步获取
try {
  const systemInfo = uni.getSystemInfoSync()
  console.log(systemInfo)
} catch (e) {
  console.error(e)
}

// 异步获取
uni.getSystemInfo({
  success: function(res) {
    console.log(res)
  },
  fail: function(err) {
    console.error(err)
  }
})

系统信息包含以下属性:

javascript
{
  brand: 'iPhone',              // 手机品牌
  model: 'iPhone X',            // 手机型号
  pixelRatio: 3,                // 设备像素比
  screenWidth: 375,             // 屏幕宽度
  screenHeight: 812,            // 屏幕高度
  windowWidth: 375,             // 可使用窗口宽度
  windowHeight: 724,            // 可使用窗口高度
  statusBarHeight: 44,          // 状态栏高度
  language: 'zh-CN',            // 应用设置的语言
  system: 'iOS 11.0',           // 操作系统版本
  version: '1.0.0',             // 应用版本号
  platform: 'ios',              // 客户端平台
  fontSizeSetting: 16,          // 用户字体大小设置
  SDKVersion: '2.0.4',          // 客户端基础库版本
  safeArea: {                   // 安全区域
    left: 0,
    right: 375,
    top: 44,
    bottom: 778,
    width: 375,
    height: 734
  }
}

全局样式与主题

全局样式文件

在uni-app中,可以通过App.vue中的<style>标签定义全局样式。

html
<!-- App.vue -->
<style>
/* 全局样式 */
page {
  background-color: #f8f8f8;
  font-size: 16px;
  font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, Segoe UI, Arial, Roboto, 'PingFang SC', 'miui', 'Hiragino Sans GB', 'Microsoft Yahei', sans-serif;
}

.container {
  padding: 20rpx;
}

.btn {
  padding: 20rpx 30rpx;
  background-color: #007AFF;
  color: #ffffff;
  border-radius: 8rpx;
  font-size: 28rpx;
  text-align: center;
}

.btn-danger {
  background-color: #dd524d;
}

.text-primary {
  color: #007AFF;
}

.text-danger {
  color: #dd524d;
}
</style>

主题切换

可以通过动态切换CSS变量实现主题切换功能。

html
<!-- App.vue -->
<style>
/* 定义CSS变量 */
page {
  /* 浅色主题 */
  --bg-color: #f8f8f8;
  --text-color: #333333;
  --border-color: #e5e5e5;
  --primary-color: #007AFF;
  --secondary-color: #6c757d;
  --success-color: #28a745;
  --danger-color: #dc3545;
  --warning-color: #ffc107;
  --info-color: #17a2b8;
}

/* 深色主题 */
page.dark {
  --bg-color: #121212;
  --text-color: #e5e5e5;
  --border-color: #333333;
  --primary-color: #409eff;
  --secondary-color: #909399;
  --success-color: #67c23a;
  --danger-color: #f56c6c;
  --warning-color: #e6a23c;
  --info-color: #909399;
}

/* 使用CSS变量 */
.container {
  background-color: var(--bg-color);
  color: var(--text-color);
}

.btn-primary {
  background-color: var(--primary-color);
  color: #ffffff;
}

.border {
  border: 1px solid var(--border-color);
}
</style>

<script>
export default {
  onLaunch: function() {
    // 从本地存储获取主题设置
    try {
      const theme = uni.getStorageSync('theme')
      if (theme === 'dark') {
        this.setDarkTheme()
      } else {
        this.setLightTheme()
      }
    } catch (e) {
      console.error(e)
    }
  },
  methods: {
    setDarkTheme() {
      // 添加深色主题类
      document.documentElement.classList.add('dark')
      // 存储主题设置
      uni.setStorageSync('theme', 'dark')
    },
    setLightTheme() {
      // 移除深色主题类
      document.documentElement.classList.remove('dark')
      // 存储主题设置
      uni.setStorageSync('theme', 'light')
    },
    toggleTheme() {
      // 切换主题
      if (document.documentElement.classList.contains('dark')) {
        this.setLightTheme()
      } else {
        this.setDarkTheme()
      }
    }
  }
}
</script>

在页面中使用:

html
<template>
  <view class="container">
    <view class="card">
      <text class="title">主题切换示例</text>
      <view class="content">
        <text>当前主题: {{ isDarkTheme ? '深色' : '浅色' }}</text>
      </view>
      <button @tap="toggleTheme" class="btn-primary">切换主题</button>
    </view>
  </view>
</template>

<script>
export default {
  computed: {
    isDarkTheme() {
      return document.documentElement.classList.contains('dark')
    }
  },
  methods: {
    toggleTheme() {
      const app = getApp()
      app.toggleTheme()
    }
  }
}
</script>

<style>
.card {
  background-color: var(--bg-color);
  color: var(--text-color);
  border: 1px solid var(--border-color);
  border-radius: 8rpx;
  padding: 20rpx;
  margin-bottom: 20rpx;
}

.title {
  font-size: 32rpx;
  font-weight: bold;
  margin-bottom: 10rpx;
}

.content {
  margin-bottom: 20rpx;
}

.btn-primary {
  background-color: var(--primary-color);
  color: #ffffff;
  padding: 10rpx 20rpx;
  border-radius: 4rpx;
}
</style>

全局混入(Mixin)

对于需要在多个组件中复用的逻辑,可以使用全局混入。

javascript
// mixins/global.js
export default {
  data() {
    return {
      globalData: 'This is global data'
    }
  },
  onLoad() {
    console.log('Global mixin onLoad')
  },
  methods: {
    globalMethod() {
      console.log('This is a global method')
    },
    showToast(title, icon = 'none') {
      uni.showToast({
        title,
        icon
      })
    }
  }
}

main.js中注册全局混入:

javascript
// main.js
import Vue from 'vue'
import App from './App'
import globalMixin from './mixins/global'

Vue.mixin(globalMixin)

const app = new Vue({
  ...App
})
app.$mount()

在组件中使用:

html
<template>
  <view>
    <text>{{ globalData }}</text>
    <button @tap="testGlobalMethod">测试全局方法</button>
  </view>
</template>

<script>
export default {
  methods: {
    testGlobalMethod() {
      this.globalMethod()
      this.showToast('使用全局方法显示提示')
    }
  }
}
</script>

全局过滤器

对于需要在多个组件中复用的数据格式化逻辑,可以使用全局过滤器。

javascript
// filters/index.js
export function formatDate(date, fmt = 'yyyy-MM-dd') {
  if (!date) return ''
  if (typeof date === 'string') {
    date = new Date(date.replace(/-/g, '/'))
  }
  if (typeof date === 'number') {
    date = new Date(date)
  }
  
  const o = {
    'M+': date.getMonth() + 1,
    'd+': date.getDate(),
    'h+': date.getHours(),
    'm+': date.getMinutes(),
    's+': date.getSeconds(),
    'q+': Math.floor((date.getMonth() + 3) / 3),
    'S': date.getMilliseconds()
  }
  
  if (/(y+)/.test(fmt)) {
    fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
  }
  
  for (let k in o) {
    if (new RegExp('(' + k + ')').test(fmt)) {
      fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)))
    }
  }
  
  return fmt
}

export function formatPrice(price) {
  if (!price && price !== 0) return ''
  return '¥' + parseFloat(price).toFixed(2)
}

export function formatFileSize(size) {
  if (!size) return '0 B'
  const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
  let index = 0
  while (size >= 1024 && index < units.length - 1) {
    size /= 1024
    index++
  }
  return size.toFixed(2) + ' ' + units[index]
}

main.js中注册全局过滤器:

javascript
// main.js
import Vue from 'vue'
import App from './App'
import * as filters from './filters'

// 注册全局过滤器
Object.keys(filters).forEach(key => {
  Vue.filter(key, filters[key])
})

const app = new Vue({
  ...App
})
app.$mount()

在组件中使用:

html
<template>
  <view>
    <view>日期:{{ date | formatDate }}</view>
    <view>自定义格式:{{ date | formatDate('yyyy年MM月dd日') }}</view>
    <view>价格:{{ price | formatPrice }}</view>
    <view>文件大小:{{ fileSize | formatFileSize }}</view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      date: new Date(),
      price: 99.8,
      fileSize: 1024 * 1024 * 3.5 // 3.5MB
    }
  }
}
</script>

全局指令

对于需要直接操作DOM的复用逻辑,可以使用全局指令。

javascript
// directives/index.js
export const focus = {
  inserted: function(el) {
    // 聚焦元素
    el.focus()
  }
}

export const lazyload = {
  bind(el, binding) {
    const observer = new IntersectionObserver(entries => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          el.src = binding.value
          observer.unobserve(el)
        }
      })
    })
    observer.observe(el)
  }
}

export const longpress = {
  bind: function(el, binding) {
    let pressTimer = null
    
    // 开始计时
    const start = e => {
      if (e.type === 'click') return
      
      if (pressTimer === null) {
        pressTimer = setTimeout(() => {
          // 执行函数
          binding.value(e)
        }, 1000)
      }
    }
    
    // 取消计时
    const cancel = () => {
      if (pressTimer !== null) {
        clearTimeout(pressTimer)
        pressTimer = null
      }
    }
    
    // 添加事件监听器
    el.addEventListener('touchstart', start)
    el.addEventListener('touchend', cancel)
    el.addEventListener('touchcancel', cancel)
  }
}

main.js中注册全局指令:

javascript
// main.js
import Vue from 'vue'
import App from './App'
import * as directives from './directives'

// 注册全局指令
Object.keys(directives).forEach(key => {
  Vue.directive(key, directives[key])
})

const app = new Vue({
  ...App
})
app.$mount()

在组件中使用:

html
<template>
  <view>
    <!-- 自动聚焦 -->
    <input v-focus placeholder="自动聚焦" />
    
    <!-- 图片懒加载 -->
    <image v-lazyload="imageUrl" mode="aspectFill" />
    
    <!-- 长按指令 -->
    <view v-longpress="handleLongPress" class="long-press-area">
      长按此区域
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      imageUrl: 'https://example.com/image.jpg'
    }
  },
  methods: {
    handleLongPress() {
      uni.showToast({
        title: '长按成功',
        icon: 'success'
      })
    }
  }
}
</script>

全局错误处理

为了捕获应用中的全局错误,可以在main.js中添加全局错误处理器。

javascript
// main.js
import Vue from 'vue'
import App from './App'

// 全局错误处理
Vue.config.errorHandler = function(err, vm, info) {
  console.error('Vue错误:', err)
  console.error('错误信息:', info)
  
  // 可以将错误上报到服务器
  // reportErrorToServer(err, info)
}

// 捕获Promise错误
window.addEventListener('unhandledrejection', event => {
  console.error('未处理的Promise错误:', event.reason)
  
  // 可以将错误上报到服务器
  // reportErrorToServer(event.reason)
  
  // 阻止默认处理
  event.preventDefault()
})

const app = new Vue({
  ...App
})
app.$mount()

全局事件总线

对于跨组件通信,可以使用全局事件总线。

javascript
// main.js
import Vue from 'vue'
import App from './App'

// 创建全局事件总线
Vue.prototype.$bus = new Vue()

const app = new Vue({
  ...App
})
app.$mount()

在组件中使用:

javascript
// 组件A:发送事件
this.$bus.$emit('custom-event', { data: 'Hello from Component A' })

// 组件B:监听事件
export default {
  created() {
    this.$bus.$on('custom-event', this.handleCustomEvent)
  },
  beforeDestroy() {
    // 组件销毁前移除事件监听
    this.$bus.$off('custom-event', this.handleCustomEvent)
  },
  methods: {
    handleCustomEvent(data) {
      console.log('接收到自定义事件:', data)
    }
  }
}

全局API拦截器

对于需要对API请求进行统一处理的场景,可以使用拦截器。

javascript
// utils/request.js
import { baseURL } from '@/config'

// 请求拦截器
uni.addInterceptor('request', {
  invoke(args) {
    // 请求前处理
    console.log('请求拦截器:', args)
    
    // 添加baseURL
    if (!args.url.startsWith('http')) {
      args.url = baseURL + args.url
    }
    
    // 添加token
    const token = uni.getStorageSync('token')
    if (token) {
      args.header = {
        ...args.header,
        'Authorization': `Bearer ${token}`
      }
    }
    
    // 添加时间戳防止缓存
    if (args.method === 'GET') {
      args.url += (args.url.includes('?') ? '&' : '?') + `_t=${Date.now()}`
    }
  },
  success(args) {
    // 请求成功后处理
    console.log('请求成功:', args)
    
    // 处理业务状态码
    if (args.data.code !== 0) {
      uni.showToast({
        title: args.data.message || '请求失败',
        icon: 'none'
      })
      
      // 处理特定错误码
      if (args.data.code === 401) {
        // token过期,跳转到登录页
        uni.navigateTo({
          url: '/pages/login/login'
        })
      }
      
      // 抛出业务错误
      const error = new Error(args.data.message)
      error.code = args.data.code
      throw error
    }
    
    // 返回业务数据
    return args.data.data
  },
  fail(err) {
    // 请求失败处理
    console.error('请求失败:', err)
    
    uni.showToast({
      title: '网络异常,请稍后再试',
      icon: 'none'
    })
    
    return Promise.reject(err)
  },
  complete(res) {
    // 请求完成处理
    console.log('请求完成:', res)
  }
})

// 封装请求方法
export function request(options) {
  return new Promise((resolve, reject) => {
    uni.request({
      ...options,
      success: res => {
        resolve(res.data)
      },
      fail: err => {
        reject(err)
      }
    })
  })
}

// GET请求
export function get(url, data = {}, options = {}) {
  return request({
    url,
    data,
    method: 'GET',
    ...options
  })
}

// POST请求
export function post(url, data = {}, options = {}) {
  return request({
    url,
    data,
    method: 'POST',
    ...options
  })
}

在组件中使用:

javascript
import { get, post } from '@/utils/request'

export default {
  methods: {
    async fetchData() {
      try {
        const data = await get('/api/data')
        this.list = data
      } catch (err) {
        console.error(err)
      }
    },
    async submitForm() {
      try {
        const result = await post('/api/submit', {
          name: this.name,
          age: this.age
        })
        uni.showToast({
          title: '提交成功',
          icon: 'success'
        })
      } catch (err) {
        console.error(err)
      }
    }
  }
}

全局导航守卫

对于需要在页面跳转时进行权限控制的场景,可以使用导航守卫。

javascript
// utils/router.js
import { getToken } from '@/utils/auth'

// 需要登录的页面
const loginPages = [
  '/pages/user/profile',
  '/pages/order/list',
  '/pages/cart/cart'
]

// 全局导航守卫
export function setupNavigationGuard() {
  // 页面跳转前
  function beforeEach(options) {
    const url = options.url.split('?')[0]
    
    // 检查是否需要登录
    if (loginPages.some(page => url.includes(page))) {
      const token = getToken()
      if (!token) {
        // 未登录,跳转到登录页
        uni.navigateTo({
          url: '/pages/login/login'
        })
        return false // 阻止原始跳转
      }
    }
    
    return true // 允许跳转
  }
  
  // 拦截跳转方法
  const navigateAPIs = [
    'navigateTo',
    'redirectTo',
    'reLaunch',
    'switchTab'
  ]
  
  navigateAPIs.forEach(api => {
    uni.addInterceptor(api, {
      invoke(options) {
        return beforeEach(options)
      },
      fail(err) {
        console.error(`${api} 失败:`, err)
      }
    })
  })
}

main.js中初始化导航守卫:

javascript
// main.js
import Vue from 'vue'
import App from './App'
import { setupNavigationGuard } from '@/utils/router'

// 设置导航守卫
setupNavigationGuard()

const app = new Vue({
  ...App
})
app.$mount()

全局配置最佳实践

  1. 模块化配置:将配置按功能拆分为多个模块,便于维护。
javascript
// config/index.js
import development from './env.development'
import production from './env.production'

// 根据环境选择配置
const env = process.env.NODE_ENV
const config = env === 'development' ? development : production

export default {
  // 基础配置
  appName: 'uni-app示例',
  appVersion: '1.0.0',
  
  // 环境配置
  ...config,
  
  // 通用配置
  navBar: {
    backgroundColor: '#007AFF',
    titleColor: '#ffffff',
    backIconColor: '#ffffff'
  },
  tabBar: {
    color: '#7A7E83',
    selectedColor: '#007AFF',
    backgroundColor: '#ffffff'
  },
  
  // 缓存键
  storageKeys: {
    token: 'APP_TOKEN',
    userInfo: 'USER_INFO',
    theme: 'APP_THEME',
    language: 'APP_LANGUAGE'
  }
}
  1. 环境配置:为不同环境提供不同的配置。
javascript
// config/env.development.js
export default {
  baseURL: 'http://localhost:3000/api',
  debug: true,
  mockData: true
}

// config/env.production.js
export default {
  baseURL: 'https://api.example.com',
  debug: false,
  mockData: false
}
  1. 统一管理API:将API接口集中管理。
javascript
// api/user.js
import { get, post } from '@/utils/request'

export function login(data) {
  return post('/user/login', data)
}

export function getUserInfo() {
  return get('/user/info')
}

export function updateUserInfo(data) {
  return post('/user/update', data)
}

// api/product.js
import { get, post } from '@/utils/request'

export function getProductList(params) {
  return get('/product/list', params)
}

export function getProductDetail(id) {
  return get(`/product/detail/${id}`)
}

// api/index.js
import * as user from './user'
import * as product from './product'

export default {
  user,
  product
}
  1. 常量管理:将常量集中管理,避免硬编码。
javascript
// constants/index.js
export const USER_ROLES = {
  ADMIN: 'admin',
  USER: 'user',
  GUEST: 'guest'
}

export const ORDER_STATUS = {
  PENDING: 0,
  PAID: 1,
  SHIPPED: 2,
  COMPLETED: 3,
  CANCELLED: 4
}

export const PAYMENT_METHODS = {
  WECHAT: 'wechat',
  ALIPAY: 'alipay',
  CREDIT_CARD: 'credit_card'
}
  1. 工具函数:将通用工具函数集中管理。
javascript
// utils/index.js
export function debounce(func, wait = 300) {
  let timeout
  return function(...args) {
    clearTimeout(timeout)
    timeout = setTimeout(() => {
      func.apply(this, args)
    }, wait)
  }
}

export function throttle(func, wait = 300) {
  let timeout = null
  let previous = 0
  
  return function(...args) {
    const now = Date.now()
    const remaining = wait - (now - previous)
    
    if (remaining <= 0) {
      if (timeout) {
        clearTimeout(timeout)
        timeout = null
      }
      previous = now
      func.apply(this, args)
    } else if (!timeout) {
      timeout = setTimeout(() => {
        previous = Date.now()
        timeout = null
        func.apply(this, args)
      }, remaining)
    }
  }
}

export function formatNumber(num) {
  return num.toString().replace(/(\d)(?=(?:\d{3})+$)/g, '$1,')
}

总结

本文详细介绍了uni-app中的全局配置方法和常用配置项,包括:

  1. 配置文件pages.jsonmanifest.json的配置项和用法
  2. 数据存储uni.setStorageuni.getStorage的使用方法
  3. 应用实例getApp()getCurrentPages()的使用方法
  4. 状态管理:Vuex的配置和使用
  5. 系统信息uni.getSystemInfo的使用方法
  6. 全局样式与主题:全局样式定义和主题切换
  7. 全局混入:复用组件逻辑
  8. 全局过滤器:数据格式化
  9. 全局指令:DOM操作复用
  10. 全局错误处理:捕获应用中的错误
  11. 全局事件总线:跨组件通信
  12. 全局API拦截器:统一处理API请求
  13. 全局导航守卫:控制页面跳转
  14. 全局配置最佳实践:模块化配置、环境配置、API管理等

通过合理使用这些全局配置方法,可以使uni-app应用更加结构化、易于维护,并提高开发效率。

一次开发,多端部署 - 让跨平台开发更简单