Skip to content

全域配置

uni-app 提供了豐富的全域配置選項,讓開發者能夠統一管理應用程式的設定、樣式、狀態等。本文將詳細介紹 uni-app 中的全域配置方法和常用配置項。

配置檔案

pages.json

pages.json 是 uni-app 的頁面路由配置檔案,用於配置頁面路由、視窗樣式、原生導航列、底部 tab 等。

基本結構

json
{
  "pages": [
    {
      "path": "pages/index/index",
      "style": {
        "navigationBarTitleText": "首頁"
      }
    }
  ],
  "globalStyle": {
    "navigationBarTextStyle": "black",
    "navigationBarTitleText": "uni-app",
    "navigationBarBackgroundColor": "#F8F8F8",
    "backgroundColor": "#F8F8F8"
  },
  "tabBar": {
    "color": "#7A7E83",
    "selectedColor": "#3cc51f",
    "borderStyle": "black",
    "backgroundColor": "#ffffff",
    "list": [
      {
        "pagePath": "pages/index/index",
        "iconPath": "static/tab-home.png",
        "selectedIconPath": "static/tab-home-current.png",
        "text": "首頁"
      }
    ]
  }
}

主要配置項

pages:頁面路由配置

json
{
  "pages": [
    {
      "path": "pages/index/index",
      "style": {
        "navigationBarTitleText": "首頁",
        "enablePullDownRefresh": true,
        "onReachBottomDistance": 50
      }
    },
    {
      "path": "pages/user/user",
      "style": {
        "navigationBarTitleText": "個人中心",
        "navigationStyle": "custom"
      }
    }
  ]
}

globalStyle:全域視窗樣式

json
{
  "globalStyle": {
    "navigationBarTextStyle": "white",
    "navigationBarTitleText": "我的應用",
    "navigationBarBackgroundColor": "#007AFF",
    "backgroundColor": "#F5F5F5",
    "backgroundTextStyle": "dark",
    "enablePullDownRefresh": false,
    "onReachBottomDistance": 50
  }
}

tabBar:底部標籤列配置

json
{
  "tabBar": {
    "color": "#7A7E83",
    "selectedColor": "#007AFF",
    "borderStyle": "black",
    "backgroundColor": "#ffffff",
    "position": "bottom",
    "list": [
      {
        "pagePath": "pages/index/index",
        "iconPath": "static/tab-home.png",
        "selectedIconPath": "static/tab-home-current.png",
        "text": "首頁"
      },
      {
        "pagePath": "pages/category/category",
        "iconPath": "static/tab-category.png",
        "selectedIconPath": "static/tab-category-current.png",
        "text": "分類"
      },
      {
        "pagePath": "pages/cart/cart",
        "iconPath": "static/tab-cart.png",
        "selectedIconPath": "static/tab-cart-current.png",
        "text": "購物車"
      },
      {
        "pagePath": "pages/user/user",
        "iconPath": "static/tab-user.png",
        "selectedIconPath": "static/tab-user-current.png",
        "text": "我的"
      }
    ]
  }
}

condition:啟動模式配置

json
{
  "condition": {
    "current": 0,
    "list": [
      {
        "name": "商品詳情",
        "path": "pages/product/detail",
        "query": "id=123"
      },
      {
        "name": "個人中心",
        "path": "pages/user/user"
      }
    ]
  }
}

subPackages:分包配置

json
{
  "subPackages": [
    {
      "root": "packageA",
      "pages": [
        {
          "path": "pages/cat/cat",
          "style": {
            "navigationBarTitleText": "貓咪"
          }
        }
      ]
    },
    {
      "root": "packageB",
      "pages": [
        {
          "path": "pages/dog/dog",
          "style": {
            "navigationBarTitleText": "狗狗"
          }
        }
      ]
    }
  ]
}

preloadRule:分包預載入規則

json
{
  "preloadRule": {
    "pages/index/index": {
      "network": "all",
      "packages": ["packageA"]
    },
    "pages/category/category": {
      "network": "wifi",
      "packages": ["packageB"]
    }
  }
}

easycom:元件自動引入

json
{
  "easycom": {
    "autoscan": true,
    "custom": {
      "^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue",
      "^my-(.*)": "@/components/my-$1/my-$1.vue"
    }
  }
}

manifest.json

manifest.json 是應用程式配置檔案,用於配置應用程式名稱、圖示、權限等。

基本結構

json
{
  "name": "我的應用",
  "appid": "__UNI__XXXXXX",
  "description": "應用程式描述",
  "versionName": "1.0.0",
  "versionCode": "100",
  "transformPx": false,
  "app-plus": {
    "usingComponents": true,
    "nvueStyleCompiler": "uni-app",
    "compilerVersion": 3,
    "splashscreen": {
      "alwaysShowBeforeRender": true,
      "waiting": true,
      "autoclose": true,
      "delay": 0
    },
    "modules": {},
    "distribute": {
      "android": {
        "permissions": [
          "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\" />",
          "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\" />",
          "<uses-permission android:name=\"android.permission.VIBRATE\" />",
          "<uses-permission android:name=\"android.permission.READ_LOGS\" />",
          "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\" />",
          "<uses-feature android:name=\"android.hardware.camera.autofocus\" />",
          "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />",
          "<uses-permission android:name=\"android.permission.CAMERA\" />",
          "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\" />",
          "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\" />",
          "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\" />",
          "<uses-permission android:name=\"android.permission.WAKE_LOCK\" />",
          "<uses-permission android:name=\"android.permission.FLASHLIGHT\" />",
          "<uses-feature android:name=\"android.hardware.camera\" />",
          "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\" />"
        ]
      },
      "ios": {},
      "sdkConfigs": {}
    }
  },
  "quickapp": {},
  "mp-weixin": {
    "appid": "",
    "setting": {
      "urlCheck": false
    },
    "usingComponents": true
  },
  "mp-alipay": {
    "usingComponents": true
  },
  "mp-baidu": {
    "usingComponents": true
  },
  "mp-toutiao": {
    "usingComponents": true
  },
  "h5": {
    "title": "我的應用",
    "template": "index.html"
  }
}

平台配置

app-plus:App 平台配置

json
{
  "app-plus": {
    "splashscreen": {
      "alwaysShowBeforeRender": true,
      "waiting": true,
      "autoclose": true,
      "delay": 0
    },
    "modules": {
      "OAuth": {},
      "Payment": {},
      "Push": {},
      "Share": {},
      "Speech": {},
      "VideoPlayer": {}
    },
    "distribute": {
      "android": {
        "permissions": [
          "<uses-permission android:name=\"android.permission.CAMERA\" />",
          "<uses-permission android:name=\"android.permission.RECORD_AUDIO\" />"
        ],
        "abiFilters": ["armeabi-v7a", "arm64-v8a", "x86"],
        "targetSdkVersion": 28,
        "minSdkVersion": 21
      },
      "ios": {
        "dSYMs": false,
        "privacyDescription": {
          "NSCameraUsageDescription": "此應用程式需要使用相機功能",
          "NSMicrophoneUsageDescription": "此應用程式需要使用麥克風功能",
          "NSLocationWhenInUseUsageDescription": "此應用程式需要使用定位功能"
        }
      },
      "sdkConfigs": {
        "oauth": {
          "weixin": {
            "appid": "wx1234567890",
            "appsecret": "your_app_secret"
          }
        },
        "payment": {
          "weixin": {
            "appid": "wx1234567890"
          },
          "alipay": {}
        }
      }
    }
  }
}

mp-weixin:微信小程式配置

json
{
  "mp-weixin": {
    "appid": "wx1234567890",
    "setting": {
      "urlCheck": false,
      "es6": true,
      "enhance": true,
      "postcss": true,
      "preloadBackgroundData": false,
      "minified": true,
      "newFeature": false,
      "coverView": true,
      "nodeModules": false,
      "autoAudits": false,
      "showShadowRootInWxmlPanel": true,
      "scopeDataCheck": false,
      "uglifyFileName": false,
      "checkInvalidKey": true,
      "checkSiteMap": true,
      "uploadWithSourceMap": true,
      "compileHotReLoad": false,
      "useMultiFrameRuntime": true,
      "useApiHook": true,
      "babelSetting": {
        "ignore": [],
        "disablePlugins": [],
        "outputPath": ""
      },
      "useIsolateContext": true,
      "useCompilerModule": true,
      "userConfirmedUseCompilerModuleSwitch": false
    },
    "usingComponents": true,
    "permission": {
      "scope.userLocation": {
        "desc": "您的位置資訊將用於小程式位置介面的效果展示"
      }
    }
  }
}

h5:H5 平台配置

json
{
  "h5": {
    "title": "我的應用",
    "template": "index.html",
    "router": {
      "mode": "hash",
      "base": "./"
    },
    "async": {
      "loading": "AsyncLoading",
      "error": "AsyncError",
      "delay": 200,
      "timeout": 60000
    },
    "devServer": {
      "https": false,
      "disableHostCheck": true,
      "proxy": {
        "/api": {
          "target": "http://localhost:3000",
          "changeOrigin": true,
          "secure": false
        }
      }
    },
    "optimization": {
      "treeShaking": {
        "enable": true
      }
    },
    "uniStatistics": {
      "enable": false
    }
  }
}

主要配置項

基本資訊

  • name:應用程式名稱
  • appid:應用程式 ID
  • description:應用程式描述
  • versionName:版本名稱
  • versionCode:版本號

app-plus 配置

  • splashscreen:啟動畫面配置
  • modules:原生模組配置
  • distribute:發布配置,包括 Android 和 iOS 的權限、SDK 配置等

小程式平台配置

  • mp-weixin:微信小程式配置,包括 appid、設定、元件等
  • mp-alipay:支付寶小程式配置
  • mp-baidu:百度小程式配置
  • mp-toutiao:字節跳動小程式配置

H5 配置

  • title:頁面標題
  • router:路由模式配置
  • devServer:開發伺服器代理設定
  • optimization:最佳化配置
  • template:HTML 範本

全域資料儲存

uni.setStorage 和 uni.getStorage

uni-app 提供了統一的儲存 API,可以在執行時儲存和獲取全域資料。

同步儲存

javascript
// 儲存資料
uni.setStorageSync('key', 'value')
uni.setStorageSync('userInfo', {
  name: '張三',
  age: 25,
  avatar: 'https://example.com/avatar.jpg'
})

// 獲取資料
const value = uni.getStorageSync('key')
const userInfo = uni.getStorageSync('userInfo')

// 移除資料
uni.removeStorageSync('key')

// 清空所有資料
uni.clearStorageSync()

非同步儲存

javascript
// 儲存資料
uni.setStorage({
  key: 'userInfo',
  data: {
    name: '李四',
    age: 30
  },
  success: function() {
    console.log('儲存成功')
  },
  fail: function() {
    console.log('儲存失敗')
  }
})

// 獲取資料
uni.getStorage({
  key: 'userInfo',
  success: function(res) {
    console.log('獲取成功:', res.data)
  },
  fail: function() {
    console.log('獲取失敗')
  }
})

// 移除資料
uni.removeStorage({
  key: 'userInfo',
  success: function() {
    console.log('移除成功')
  }
})

// 清空所有資料
uni.clearStorage({
  success: function() {
    console.log('清空成功')
  }
})

儲存資訊獲取

javascript
// 獲取儲存資訊
uni.getStorageInfo({
  success: function(res) {
    console.log('當前儲存的 keys:', res.keys)
    console.log('當前佔用的空間大小:', res.currentSize)
    console.log('限制的空間大小:', res.limitSize)
  }
})

// 同步獲取儲存資訊
const storageInfo = uni.getStorageInfoSync()
console.log(storageInfo)

全域應用程式實例

getApp()

getApp() 函數用於獲取當前應用程式實例,可以透過它存取全域資料和方法。

javascript
// App.vue
<script>
export default {
  globalData: {
    userInfo: null,
    token: '',
    theme: 'light'
  },
  onLaunch: function() {
    console.log('App Launch')
    
    // 初始化全域資料
    this.initGlobalData()
  },
  onShow: function() {
    console.log('App Show')
  },
  onHide: function() {
    console.log('App Hide')
  },
  methods: {
    initGlobalData() {
      // 從本地儲存載入資料
      const userInfo = uni.getStorageSync('userInfo')
      const token = uni.getStorageSync('token')
      const theme = uni.getStorageSync('theme') || 'light'
      
      this.globalData.userInfo = userInfo
      this.globalData.token = token
      this.globalData.theme = theme
    },
    setUserInfo(userInfo) {
      this.globalData.userInfo = userInfo
      uni.setStorageSync('userInfo', userInfo)
    },
    setToken(token) {
      this.globalData.token = token
      uni.setStorageSync('token', token)
    },
    setTheme(theme) {
      this.globalData.theme = theme
      uni.setStorageSync('theme', theme)
      
      // 更新主題
      this.updateTheme(theme)
    },
    updateTheme(theme) {
      // 更新 CSS 變數
      const root = document.documentElement
      if (theme === 'dark') {
        root.classList.add('dark')
      } else {
        root.classList.remove('dark')
      }
    }
  }
}
</script>

在頁面中使用:

javascript
// 頁面中使用全域資料
export default {
  data() {
    return {
      userInfo: null
    }
  },
  onLoad() {
    // 獲取應用程式實例
    const app = getApp()
    
    // 存取全域資料
    this.userInfo = app.globalData.userInfo
    
    // 呼叫全域方法
    app.setTheme('dark')
  },
  methods: {
    updateUserInfo() {
      const app = getApp()
      const newUserInfo = {
        name: '王五',
        age: 28
      }
      
      // 更新全域資料
      app.setUserInfo(newUserInfo)
      this.userInfo = newUserInfo
    }
  }
}

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 && prevPage.refreshData()
}

// 向上一個頁面傳遞資料
if (prevPage) {
  prevPage.$vm.selectedData = this.selectedData
}

實際應用範例:

javascript
// 商品列表頁面
export default {
  data() {
    return {
      productList: []
    }
  },
  methods: {
    // 重新整理資料的方法
    refreshData() {
      this.loadProductList()
    },
    loadProductList() {
      // 載入商品列表
      uni.request({
        url: '/api/products',
        success: (res) => {
          this.productList = res.data
        }
      })
    },
    goToDetail(product) {
      uni.navigateTo({
        url: `/pages/product/detail?id=${product.id}`
      })
    }
  }
}

// 商品詳情頁面
export default {
  methods: {
    addToCart() {
      // 加入購物車邏輯
      uni.request({
        url: '/api/cart/add',
        method: 'POST',
        data: {
          productId: this.productId,
          quantity: this.quantity
        },
        success: () => {
          uni.showToast({
            title: '加入購物車成功',
            icon: 'success'
          })
          
          // 通知上一個頁面重新整理資料
          const pages = getCurrentPages()
          const prevPage = pages[pages.length - 2]
          if (prevPage && prevPage.route === 'pages/product/list') {
            prevPage.$vm.refreshData()
          }
          
          // 返回上一頁
          uni.navigateBack()
        }
      })
    }
  }
}

Vuex 狀態管理

對於複雜的狀態管理需求,可以使用 Vuex。

安裝和配置

bash
npm install vuex@3

建立 store:

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

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    theme: 'light',
    language: 'zh-CN'
  },
  getters: {
    isDarkTheme: state => state.theme === 'dark',
    isEnglish: state => state.language === 'en'
  },
  mutations: {
    SET_THEME(state, theme) {
      state.theme = theme
    },
    SET_LANGUAGE(state, language) {
      state.language = language
    }
  },
  actions: {
    setTheme({ commit }, theme) {
      commit('SET_THEME', theme)
      uni.setStorageSync('theme', theme)
    },
    setLanguage({ commit }, language) {
      commit('SET_LANGUAGE', language)
      uni.setStorageSync('language', language)
    }
  },
  modules: {
    user,
    cart
  }
})

export default store

使用者模組:

javascript
// store/modules/user.js
const state = {
  userInfo: null,
  token: '',
  isLogin: false
}

const getters = {
  userName: state => state.userInfo ? state.userInfo.name : '',
  userAvatar: state => state.userInfo ? state.userInfo.avatar : ''
}

const mutations = {
  SET_USER_INFO(state, userInfo) {
    state.userInfo = userInfo
    state.isLogin = !!userInfo
  },
  SET_TOKEN(state, token) {
    state.token = token
  },
  LOGOUT(state) {
    state.userInfo = null
    state.token = ''
    state.isLogin = false
  }
}

const actions = {
  login({ commit }, { username, password }) {
    return new Promise((resolve, reject) => {
      uni.request({
        url: '/api/login',
        method: 'POST',
        data: { username, password },
        success: (res) => {
          if (res.data.code === 0) {
            const { userInfo, token } = res.data.data
            commit('SET_USER_INFO', userInfo)
            commit('SET_TOKEN', token)
            
            // 儲存到本地
            uni.setStorageSync('userInfo', userInfo)
            uni.setStorageSync('token', token)
            
            resolve(res.data)
          } else {
            reject(res.data)
          }
        },
        fail: reject
      })
    })
  },
  logout({ commit }) {
    commit('LOGOUT')
    uni.removeStorageSync('userInfo')
    uni.removeStorageSync('token')
  }
}

export default {
  namespaced: true,
  state,
  getters,
  mutations,
  actions
}

購物車模組:

javascript
// store/modules/cart.js
const state = {
  items: [],
  total: 0
}

const getters = {
  cartCount: state => state.items.reduce((count, item) => count + item.quantity, 0),
  cartTotal: state => state.items.reduce((total, item) => total + item.price * item.quantity, 0)
}

const mutations = {
  ADD_TO_CART(state, product) {
    const existingItem = state.items.find(item => item.id === product.id)
    if (existingItem) {
      existingItem.quantity += product.quantity || 1
    } else {
      state.items.push({
        ...product,
        quantity: product.quantity || 1
      })
    }
  },
  REMOVE_FROM_CART(state, productId) {
    const index = state.items.findIndex(item => item.id === productId)
    if (index > -1) {
      state.items.splice(index, 1)
    }
  },
  UPDATE_QUANTITY(state, { productId, quantity }) {
    const item = state.items.find(item => item.id === productId)
    if (item) {
      item.quantity = quantity
    }
  },
  CLEAR_CART(state) {
    state.items = []
  }
}

const actions = {
  addToCart({ commit }, product) {
    commit('ADD_TO_CART', product)
    uni.showToast({
      title: '加入購物車成功',
      icon: 'success'
    })
  },
  removeFromCart({ commit }, productId) {
    commit('REMOVE_FROM_CART', productId)
  },
  updateQuantity({ commit }, payload) {
    commit('UPDATE_QUANTITY', payload)
  },
  clearCart({ commit }) {
    commit('CLEAR_CART')
  }
}

export default {
  namespaced: true,
  state,
  getters,
  mutations,
  actions
}

在 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({
  ...App,
  store
})
app.$mount()

在元件中使用:

javascript
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'

export default {
  computed: {
    ...mapState(['theme', 'language']),
    ...mapState('user', ['userInfo', 'isLogin']),
    ...mapGetters('user', ['userName', 'userAvatar']),
    ...mapGetters('cart', ['cartCount', 'cartTotal'])
  },
  methods: {
    ...mapMutations(['SET_THEME']),
    ...mapActions('user', ['login', 'logout']),
    ...mapActions('cart', ['addToCart', 'removeFromCart']),
    
    async handleLogin() {
      try {
        await this.login({
          username: this.username,
          password: this.password
        })
        uni.showToast({
          title: '登入成功',
          icon: 'success'
        })
      } catch (error) {
        uni.showToast({
          title: error.message || '登入失敗',
          icon: 'none'
        })
      }
    }
  }
}

系統資訊獲取

uni.getSystemInfo

獲取系統資訊,包括裝置型號、作業系統版本、螢幕尺寸等。

javascript
// 非同步獲取
uni.getSystemInfo({
  success: function(res) {
    console.log('裝置品牌:', res.brand)
    console.log('裝置型號:', res.model)
    console.log('裝置畫素比:', res.pixelRatio)
    console.log('螢幕寬度:', res.screenWidth)
    console.log('螢幕高度:', res.screenHeight)
    console.log('可使用視窗寬度:', res.windowWidth)
    console.log('可使用視窗高度:', res.windowHeight)
    console.log('狀態列高度:', res.statusBarHeight)
    console.log('導航列高度:', res.navigationBarHeight)
    console.log('底部安全區域高度:', res.safeAreaInsets.bottom)
    console.log('作業系統:', res.platform)
    console.log('作業系統版本:', res.system)
    console.log('微信版本號:', res.version)
    console.log('基礎庫版本:', res.SDKVersion)
  }
})

// 同步獲取
const systemInfo = uni.getSystemInfoSync()
console.log(systemInfo)

實際應用

javascript
// utils/system.js
export function getSystemInfo() {
  return uni.getSystemInfoSync()
}

export function isIOS() {
  const systemInfo = getSystemInfo()
  return systemInfo.platform === 'ios'
}

export function isAndroid() {
  const systemInfo = getSystemInfo()
  return systemInfo.platform === 'android'
}

export function isH5() {
  // #ifdef H5
  return true
  // #endif
  // #ifndef H5
  return false
  // #endif
}

export function isMiniProgram() {
  // #ifdef MP
  return true
  // #endif
  // #ifndef MP
  return false
  // #endif
}

export function getStatusBarHeight() {
  const systemInfo = getSystemInfo()
  return systemInfo.statusBarHeight || 0
}

export function getNavigationBarHeight() {
  const systemInfo = getSystemInfo()
  // iOS 導航列高度為 44,Android 為 48
  return isIOS() ? 44 : 48
}

export function getSafeAreaBottom() {
  const systemInfo = getSystemInfo()
  return systemInfo.safeAreaInsets ? systemInfo.safeAreaInsets.bottom : 0
}

// 計算自訂導航列總高度
export function getCustomNavHeight() {
  return getStatusBarHeight() + getNavigationBarHeight()
}

在元件中使用:

javascript
import { getSystemInfo, getCustomNavHeight, getSafeAreaBottom } from '@/utils/system'

export default {
  data() {
    return {
      systemInfo: {},
      customNavHeight: 0,
      safeAreaBottom: 0
    }
  },
  onLoad() {
    this.systemInfo = getSystemInfo()
    this.customNavHeight = getCustomNavHeight()
    this.safeAreaBottom = getSafeAreaBottom()
  }
}

全域樣式與主題

全域樣式定義

App.vue 中定義全域樣式:

vue
<!-- App.vue -->
<style>
/* 全域樣式 */
page {
  background-color: #f5f5f5;
  font-size: 28rpx;
  line-height: 1.6;
}

/* CSS 變數定義 */
:root {
  /* 主色調 */
  --primary-color: #007AFF;
  --secondary-color: #5856D6;
  --success-color: #34C759;
  --warning-color: #FF9500;
  --danger-color: #FF3B30;
  
  /* 文字顏色 */
  --text-color: #333333;
  --text-color-secondary: #666666;
  --text-color-placeholder: #999999;
  
  /* 背景顏色 */
  --bg-color: #ffffff;
  --bg-color-secondary: #f8f8f8;
  --bg-color-tertiary: #f0f0f0;
  
  /* 邊框顏色 */
  --border-color: #e0e0e0;
  --border-color-light: #f0f0f0;
  
  /* 陰影 */
  --shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
  --shadow-light: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
  
  /* 圓角 */
  --border-radius: 8rpx;
  --border-radius-large: 16rpx;
  
  /* 間距 */
  --spacing-xs: 8rpx;
  --spacing-sm: 16rpx;
  --spacing-md: 24rpx;
  --spacing-lg: 32rpx;
  --spacing-xl: 48rpx;
}

/* 深色主題 */
.dark {
  --text-color: #ffffff;
  --text-color-secondary: #cccccc;
  --text-color-placeholder: #999999;
  
  --bg-color: #1c1c1e;
  --bg-color-secondary: #2c2c2e;
  --bg-color-tertiary: #3a3a3c;
  
  --border-color: #38383a;
  --border-color-light: #48484a;
}

/* 通用類別 */
.container {
  padding: var(--spacing-md);
}

.card {
  background-color: var(--bg-color);
  border-radius: var(--border-radius);
  box-shadow: var(--shadow-light);
  padding: var(--spacing-md);
  margin-bottom: var(--spacing-md);
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--spacing-sm) var(--spacing-md);
  border-radius: var(--border-radius);
  font-size: 28rpx;
  text-align: center;
  transition: all 0.3s ease;
}

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

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

.btn-outline {
  background-color: transparent;
  border: 2rpx solid var(--primary-color);
  color: var(--primary-color);
}

.text-primary {
  color: var(--primary-color);
}

.text-secondary {
  color: var(--text-color-secondary);
}

.text-placeholder {
  color: var(--text-color-placeholder);
}

.text-center {
  text-align: center;
}

.text-right {
  text-align: right;
}

.flex {
  display: flex;
}

.flex-column {
  flex-direction: column;
}

.flex-center {
  align-items: center;
  justify-content: center;
}

.flex-between {
  justify-content: space-between;
}

.flex-around {
  justify-content: space-around;
}

.flex-1 {
  flex: 1;
}

.mt-xs { margin-top: var(--spacing-xs); }
.mt-sm { margin-top: var(--spacing-sm); }
.mt-md { margin-top: var(--spacing-md); }
.mt-lg { margin-top: var(--spacing-lg); }
.mt-xl { margin-top: var(--spacing-xl); }

.mb-xs { margin-bottom: var(--spacing-xs); }
.mb-sm { margin-bottom: var(--spacing-sm); }
.mb-md { margin-bottom: var(--spacing-md); }
.mb-lg { margin-bottom: var(--spacing-lg); }
.mb-xl { margin-bottom: var(--spacing-xl); }

.pt-xs { padding-top: var(--spacing-xs); }
.pt-sm { padding-top: var(--spacing-sm); }
.pt-md { padding-top: var(--spacing-md); }
.pt-lg { padding-top: var(--spacing-lg); }
.pt-xl { padding-top: var(--spacing-xl); }

.pb-xs { padding-bottom: var(--spacing-xs); }
.pb-sm { padding-bottom: var(--spacing-sm); }
.pb-md { padding-bottom: var(--spacing-md); }
.pb-lg { padding-bottom: var(--spacing-lg); }
.pb-xl { padding-bottom: var(--spacing-xl); }
</style>

主題切換

App.vue 中實現主題切換:

vue
<!-- App.vue -->
<script>
export default {
  globalData: {
    theme: 'light'
  },
  onLaunch: function() {
    // 初始化主題
    this.initTheme()
  },
  methods: {
    initTheme() {
      // 從本地儲存獲取主題設定
      const savedTheme = uni.getStorageSync('theme') || 'light'
      this.setTheme(savedTheme)
    },
    setTheme(theme) {
      this.globalData.theme = theme
      uni.setStorageSync('theme', theme)
      
      // 更新 CSS 類別
      // #ifdef H5
      const root = document.documentElement
      if (theme === 'dark') {
        root.classList.add('dark')
      } else {
        root.classList.remove('dark')
      }
      // #endif
      
      // 發送主題變更事件
      uni.$emit('themeChange', theme)
    },
    toggleTheme() {
      const newTheme = this.globalData.theme === 'light' ? 'dark' : 'light'
      this.setTheme(newTheme)
    }
  }
}
</script>

在元件中使用主題:

vue
<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()

在元件中使用:

vue
<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()

在元件中使用:

vue
<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()

在元件中使用:

vue
<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應用程式更加結構化、易於維護,並提高開發效率。

一次開發,多端部署 - 讓跨平台開發更簡單