全域配置
uni-app 提供了豐富的全域配置選項,讓開發者能夠統一管理應用程式的設定、樣式、狀態等。本文將詳細介紹 uni-app 中的全域配置方法和常用配置項。
配置檔案
pages.json
pages.json 是 uni-app 的頁面路由配置檔案,用於配置頁面路由、視窗樣式、原生導航列、底部 tab 等。
基本結構
{
"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:頁面路由配置
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首頁",
"enablePullDownRefresh": true,
"onReachBottomDistance": 50
}
},
{
"path": "pages/user/user",
"style": {
"navigationBarTitleText": "個人中心",
"navigationStyle": "custom"
}
}
]
}globalStyle:全域視窗樣式
{
"globalStyle": {
"navigationBarTextStyle": "white",
"navigationBarTitleText": "我的應用",
"navigationBarBackgroundColor": "#007AFF",
"backgroundColor": "#F5F5F5",
"backgroundTextStyle": "dark",
"enablePullDownRefresh": false,
"onReachBottomDistance": 50
}
}tabBar:底部標籤列配置
{
"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:啟動模式配置
{
"condition": {
"current": 0,
"list": [
{
"name": "商品詳情",
"path": "pages/product/detail",
"query": "id=123"
},
{
"name": "個人中心",
"path": "pages/user/user"
}
]
}
}subPackages:分包配置
{
"subPackages": [
{
"root": "packageA",
"pages": [
{
"path": "pages/cat/cat",
"style": {
"navigationBarTitleText": "貓咪"
}
}
]
},
{
"root": "packageB",
"pages": [
{
"path": "pages/dog/dog",
"style": {
"navigationBarTitleText": "狗狗"
}
}
]
}
]
}preloadRule:分包預載入規則
{
"preloadRule": {
"pages/index/index": {
"network": "all",
"packages": ["packageA"]
},
"pages/category/category": {
"network": "wifi",
"packages": ["packageB"]
}
}
}easycom:元件自動引入
{
"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 是應用程式配置檔案,用於配置應用程式名稱、圖示、權限等。
基本結構
{
"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 平台配置
{
"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:微信小程式配置
{
"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 平台配置
{
"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:應用程式 IDdescription:應用程式描述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,可以在執行時儲存和獲取全域資料。
同步儲存
// 儲存資料
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()非同步儲存
// 儲存資料
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('清空成功')
}
})儲存資訊獲取
// 獲取儲存資訊
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() 函數用於獲取當前應用程式實例,可以透過它存取全域資料和方法。
// 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>在頁面中使用:
// 頁面中使用全域資料
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() 函數用於獲取當前頁面堆疊的實例,可以用於頁面間的通訊。
// 獲取當前頁面堆疊
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
}實際應用範例:
// 商品列表頁面
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。
安裝和配置
npm install vuex@3建立 store:
// 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使用者模組:
// 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
}購物車模組:
// 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 中註冊:
// 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()在元件中使用:
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
獲取系統資訊,包括裝置型號、作業系統版本、螢幕尺寸等。
// 非同步獲取
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)實際應用
// 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()
}在元件中使用:
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 中定義全域樣式:
<!-- 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 中實現主題切換:
<!-- 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>在元件中使用主題:
<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)
對於需要在多個元件中複用的邏輯,可以使用全域混入。
// 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中註冊全域混入:
// 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()在元件中使用:
<template>
<view>
<text>{{ globalData }}</text>
<button @tap="testGlobalMethod">測試全域方法</button>
</view>
</template>
<script>
export default {
methods: {
testGlobalMethod() {
this.globalMethod()
this.showToast('使用全域方法顯示提示')
}
}
}
</script>全域過濾器
對於需要在多個元件中複用的資料格式化邏輯,可以使用全域過濾器。
// 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中註冊全域過濾器:
// 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()在元件中使用:
<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的複用邏輯,可以使用全域指令。
// 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中註冊全域指令:
// 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()在元件中使用:
<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中新增全域錯誤處理器。
// 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()全域事件匯流排
對於跨元件通訊,可以使用全域事件匯流排。
// main.js
import Vue from 'vue'
import App from './App'
// 建立全域事件匯流排
Vue.prototype.$bus = new Vue()
const app = new Vue({
...App
})
app.$mount()在元件中使用:
// 元件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請求進行統一處理的場景,可以使用攔截器。
// 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
})
}在元件中使用:
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)
}
}
}
}全域導航守衛
對於需要在頁面跳轉時進行權限控制的場景,可以使用導航守衛。
// 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中初始化導航守衛:
// main.js
import Vue from 'vue'
import App from './App'
import { setupNavigationGuard } from '@/utils/router'
// 設定導航守衛
setupNavigationGuard()
const app = new Vue({
...App
})
app.$mount()全域配置最佳實踐
- 模組化配置:將配置按功能拆分為多個模組,便於維護。
// 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'
}
}- 環境配置:為不同環境提供不同的配置。
// 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
}- 統一管理API:將API介面集中管理。
// 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
}- 常數管理:將常數集中管理,避免硬編碼。
// 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'
}- 工具函數:將通用工具函數集中管理。
// 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中的全域配置方法和常用配置項,包括:
- 配置檔案:
pages.json和manifest.json的配置項和用法 - 資料儲存:
uni.setStorage和uni.getStorage的使用方法 - 應用程式實例:
getApp()和getCurrentPages()的使用方法 - 狀態管理:Vuex的配置和使用
- 系統資訊:
uni.getSystemInfo的使用方法 - 全域樣式與主題:全域樣式定義和主題切換
- 全域混入:複用元件邏輯
- 全域過濾器:資料格式化
- 全域指令:DOM操作複用
- 全域錯誤處理:捕獲應用程式中的錯誤
- 全域事件匯流排:跨元件通訊
- 全域API攔截器:統一處理API請求
- 全域導航守衛:控制頁面跳轉
- 全域配置最佳實踐:模組化配置、環境配置、API管理等
透過合理使用這些全域配置方法,可以使uni-app應用程式更加結構化、易於維護,並提高開發效率。