全局配置
在uni-app开发中,全局配置是指应用级别的设置,包括应用的基本信息、页面路由、窗口样式、权限申请等。本文将详细介绍uni-app中的全局配置方法和常用配置项。
pages.json
pages.json
是uni-app项目的全局配置文件,用来对uni-app进行全局配置,决定页面文件的路径、窗口样式、原生的导航栏、底部的原生tabbar等。
基本结构
一个基本的pages.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
数组用于配置应用的页面路由,每一项是一个对象,对应一个页面。
"pages": [{
"path": "pages/index/index", // 页面路径
"style": { // 页面窗口表现
"navigationBarTitleText": "首页", // 导航栏标题
"enablePullDownRefresh": true // 是否开启下拉刷新
}
}]
提示
pages
数组中第一项表示应用的默认启动页。
2. globalStyle
globalStyle
用于设置应用的全局样式,会作用于每一个页面。如果某个页面有特定的样式需求,可以在页面的style
中单独配置,会覆盖全局样式。
"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。
"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
用于启用特定页面的场景值,方便开发调试。
"condition": {
"current": 0, // 当前激活的模式(list 的索引项)
"list": [{
"name": "详情页", // 模式名称
"path": "pages/detail/detail", // 启动页面
"query": "id=1" // 启动参数
}]
}
5. subPackages
subPackages
用于分包加载,优化小程序的启动速度。
"subPackages": [{
"root": "pagesA", // 分包根目录
"pages": [{ // 分包页面路径
"path": "list/list",
"style": {
"navigationBarTitleText": "列表"
}
}]
}]
6. preloadRule
preloadRule
用于配置分包预下载规则。
"preloadRule": {
"pages/index/index": {
"network": "all", // 在指定网络下预下载,可选值为:all(不限网络)、wifi(仅wifi下预下载)
"packages": ["pagesA"] // 进入页面后预下载分包的 root 或 name
}
}
7. easycom
easycom
用于配置组件自动引入规则,不需要在页面中手动引入组件。
"easycom": {
"autoscan": true, // 是否开启自动扫描
"custom": { // 自定义扫描规则
"^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue"
}
}
manifest.json
manifest.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. 基本信息
{
"name": "应用名称",
"appid": "",
"description": "应用描述",
"versionName": "1.0.0",
"versionCode": "100"
}
2. app-plus(App 特有配置)
"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. 各平台小程序配置
"mp-weixin": {
"appid": "wx开头的微信小程序appid",
"setting": {
"urlCheck": false,
"es6": true,
"postcss": true,
"minified": true
},
"usingComponents": true,
"permission": {
"scope.userLocation": {
"desc": "你的位置信息将用于小程序位置接口的效果展示"
}
}
}
4. H5 配置
"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还提供了运行时的全局数据存储方法,可以在应用的不同页面之间共享数据。
存储数据
// 同步存储
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)
}
})
获取数据
// 同步获取
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)
}
})
删除数据
// 同步删除
try {
uni.removeStorageSync('key')
} catch (e) {
console.error(e)
}
// 异步删除
uni.removeStorage({
key: 'key',
success: function() {
console.log('删除成功')
}
})
清空所有数据
// 同步清空
try {
uni.clearStorageSync()
} catch (e) {
console.error(e)
}
// 异步清空
uni.clearStorage({
success: function() {
console.log('清空成功')
}
})
getApp() 与 getCurrentPages()
uni-app提供了全局的应用实例和页面实例获取方法。
getApp()
getApp()
用于获取当前应用实例,可以用来获取和设置全局数据。
const app = getApp()
// 设置全局数据
app.globalData = {
userInfo: null
}
// 获取全局数据
console.log(app.globalData.userInfo)
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()
}
Vuex 状态管理
对于复杂应用,推荐使用Vuex进行状态管理。
安装Vuex
npm install vuex --save
创建Store
// 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中注册
// 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()
在组件中使用
<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 获取系统信息
在开发过程中,经常需要获取设备的系统信息,如屏幕尺寸、设备型号等。
// 同步获取
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)
}
})
系统信息包含以下属性:
{
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>
标签定义全局样式。
<!-- 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变量实现主题切换功能。
<!-- 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>
在页面中使用:
<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应用更加结构化、易于维护,并提高开发效率。