大家好,我是不如摸鱼去,wot-ui的主要维护者,欢迎来到我的 uni-app 分享专栏。
在 uni-app 开发中,我们经常遇到需要在任何地方(如网络请求拦截器、>路由守卫等)显示 Toast 提示的需求。然而,uni-app 的组件化架构使>得全局 Toast 的实现变得复杂。本文将介绍一套完整的解决方案,让你轻松>实现真正的全局 Toast。
本文实现的全局 Toast、Loading、MessageBox 等组件均已在现代化 uni-app 模板,基于vitesse-uni-app的深度整合 Wot UI 组件库的快速起手项目 wot-demo 中提供,模板地址在文章末尾,作者是其主要维护者。
问题分析
传统方案的局限性
在 uni-app 中,常见的 Toast 实现方式有以下几种:
- uni.showToast() - 功能有限,样式单一,无法自定义
- 传统Toast - 只能在当前组件使用,无法跨组件调用
- wot ui 方案 - 基于provide/inject实现,需要在 setup 顶层调用useToast,无法在路由拦截和请求拦截中使用
核心难点
- uni-app 无法像 Vue 3 那样全局挂载组件
- 组件实例无法在非 Vue 上下文中访问
- 需要在网络请求和路由拦截中使用 Toast
解决方案架构
我们的解决方案包含三个核心部分:
- wd-toast 组件 - 基于
provide/inject
的函数式调用 - Layout 插件 - 实现一次插入,全局可用
- useGlobalToast - 基于 Pinia 的状态管理
实现详解
1. wd-toast 组件实现
wot ui 是一个当下流行的 uni-app vue3 UI 库,作者也是其重要维护者之一。
首先,我们使用 wot-ui 的 Toast 组件,它基于 provide/inject
实现函数式调用:
vue
<!-- 在组件中使用 -->
<script setup>
const toast = useToast('myToast')
// 显示 Toast
toast.show({
msg: '这是一个提示',
duration: 2000
})
</script>
<template>
<wd-toast selector="myToast" />
</template>
优点: 函数式调用,使用简单 缺点: useToast 必须在 setup 顶层调用,toast.show 仅能在vue组件中使用
2. Layout 插件 - 一次插入,全局可用
由于 uni-app 无法全局插入组件,我们通过 @uni-helper/vite-plugin-uni-layouts
插件实现统一布局管理:
vue
<!-- src/layouts/default.vue -->
<template>
<wd-config-provider :theme-vars="themeVars" :theme="theme">
<slot />
<!-- 全局组件一次性插入 -->
<wd-notify />
<wd-message-box />
<wd-toast />
<global-loading />
<global-toast />
<global-message />
</wd-config-provider>
</template>
这样,所有页面都会包含这些全局组件,实现了"一次插入,全局可用"的效果。
3. GlobalToast 组件实现
vue
<!-- src/components/GlobalToast.vue -->
<script lang="ts" setup>
const { toastOptions, currentPage } = storeToRefs(useGlobalToast())
const { close: closeGlobalToast } = useGlobalToast()
const toast = useToast('globalToast')
const currentPath = getCurrentPath()
// 支付宝小程序兼容性处理
// #ifdef MP-ALIPAY
const hackAlipayVisible = ref(false)
nextTick(() => {
hackAlipayVisible.value = true
})
// #endif
// 监听全局状态变化
watch(() => toastOptions.value, (newVal) => {
if (newVal && newVal.show) {
// 只在当前页面显示 Toast
if (currentPage.value === currentPath) {
toast.show(toastOptions.value)
}
}
else {
toast.close()
}
})
</script>
<template>
<!-- 支付宝小程序特殊处理 -->
<!-- #ifdef MP-ALIPAY -->
<wd-toast v-if="hackAlipayVisible" selector="globalToast" :closed="closeGlobalToast" />
<!-- #endif -->
<!-- #ifndef MP-ALIPAY -->
<wd-toast selector="globalToast" :closed="closeGlobalToast" />
<!-- #endif -->
</template>
关键特性:
- 通过
currentPage
确保 Toast 只在正确的页面显示 - 支持支付宝小程序的兼容性处理
- 使用
virtualHost
和styleIsolation
优化结构和样式
4. useGlobalToast - Pinia 状态管理
typescript
// src/composables/useGlobalToast.ts
import { defineStore } from 'pinia'
import type { ToastOptions } from 'wot-design-uni/components/wd-toast/types'
interface GlobalToast {
toastOptions: ToastOptions
currentPage: string
}
const defaultOptions: ToastOptions = {
duration: 2000,
show: false,
}
export const useGlobalToast = defineStore('global-toast', {
state: (): GlobalToast => ({
toastOptions: defaultOptions,
currentPage: '',
}),
actions: {
// 显示 Toast
show(option: ToastOptions | string) {
this.currentPage = getCurrentPath()
const options = CommonUtil.deepMerge(
defaultOptions,
typeof option === 'string' ? { msg: option } : option
) as ToastOptions
this.toastOptions = CommonUtil.deepMerge(options, {
show: true,
position: options.position || 'middle',
}) as ToastOptions
},
// 成功提示
success(option: ToastOptions | string) {
this.show(CommonUtil.deepMerge({
iconName: 'success',
duration: 1500,
}, typeof option === 'string' ? { msg: option } : option) as ToastOptions)
},
// 错误提示
error(option: ToastOptions | string) {
this.show(CommonUtil.deepMerge({
iconName: 'error',
direction: 'vertical',
}, typeof option === 'string' ? { msg: option } : option) as ToastOptions)
},
// 信息提示
info(option: ToastOptions | string) {
this.show(CommonUtil.deepMerge({
iconName: 'info',
}, typeof option === 'string' ? { msg: option } : option) as ToastOptions)
},
// 警告提示
warning(option: ToastOptions | string) {
this.show(CommonUtil.deepMerge({
iconName: 'warning',
}, typeof option === 'string' ? { msg: option } : option) as ToastOptions)
},
// 关闭 Toast
close() {
this.toastOptions = defaultOptions
this.currentPage = ''
},
},
})
使用方式
1. 在组件中使用
vue
<script setup>
const globalToast = useGlobalToast()
function handleClick() {
globalToast.success('操作成功!')
globalToast.error('操作失败!')
globalToast.info('提示信息')
globalToast.warning('警告信息')
}
</script>
2. 在网络请求中使用
typescript
// 请求拦截器
uni.addInterceptor('request', {
fail(err) {
const globalToast = useGlobalToast()
globalToast.error('网络请求失败')
}
})
// 或在 API 封装中
export async function apiRequest(url: string, data: any) {
try {
const result = await uni.request({ url, data })
return result
}
catch (error) {
const globalToast = useGlobalToast()
globalToast.error('请求失败,请重试')
throw error
}
}
3. 在路由拦截中使用
typescript
// 路由守卫
function routeGuard(to: string) {
const globalToast = useGlobalToast()
if (!isLogin()) {
globalToast.warning('请先登录')
uni.navigateTo({ url: '/pages/login/index' })
return false
}
return true
}
扩展功能
基于同样的架构,我们还实现了:
1. GlobalLoading - 全局加载提示
typescript
export const useGlobalLoading = defineStore('global-loading', {
actions: {
loading(option: ToastOptions | string) {
this.currentPage = getCurrentPath()
this.loadingOptions = CommonUtil.deepMerge({
iconName: 'loading',
duration: 0,
cover: true,
position: 'middle',
show: true,
}, typeof option === 'string' ? { msg: option } : option)
}
}
})
2. GlobalMessage - 全局弹窗
typescript
export const useGlobalMessage = defineStore('global-message', {
actions: {
alert(option: GlobalMessageOptions | string) {
const messageOptions = CommonUtil.deepMerge(
{ type: 'alert' },
CommonUtil.isString(option) ? { title: option } : option
)
messageOptions.showCancelButton = false
this.show(messageOptions)
}
}
})
总结
通过这套方案,我们成功解决了 uni-app 全局 Toast 的难题:
- wd-toast 组件 提供了优秀的基础能力
- @uni-helper/vite-plugin-uni-layouts 插件 实现了统一布局和组件的全局插入
- Pinia 状态管理 让我们能在任何地方调用 Toast
这个方案具有以下优势:
- 真正的全局调用,可在任何地方使用
- 完整的类型支持
- 多端兼容性
- 页面隔离机制
如果你也在为 uni-app 的全局 Toast 而烦恼,不妨试试这个方案!
相关资源
- wot-ui: github.com/Moonofweish... - 优秀的 uni-app UI 组件库
- Pinia: pinia.vuejs.org/ - Vue 3 状态管理库
- uni-app: uniapp.dcloud.net.cn/ - 跨平台应用开发框架
- @uni-helper/vite-plugin-uni-layouts: github.com/uni-helper/... - uni-app 布局插件
- wot-demo: github.com/Moonofweish... - 一个现代化的 uni-app vue3 模板
如有问题或者更好的方案,欢迎评论区,交流讨论👇!欢迎点赞、关注、转发。