UniApp 全局 Toast 方案,从 Layout 到 Pinia
提示本文发布于 3 个月前,其中信息可能已经时过境迁...
上篇写了用 h 函数封装全局 Toast 的思路,但那是在 H5 环境下的做法。在 UniApp 里,因为跨端限制,不能用 document.createElement 创建容器。所以需要另一套方案。
问题的根源
UniApp 的组件化架构决定了:你不能像 Vue 3 那样在 app.mount() 之后全局挂载组件。uni.showToast() 功能有限,样式也不能自定义。Wot UI 的 Toast 需要在 setup 里调用 useToast,无法在路由拦截和请求拦截中使用。
说白了,核心痛点就一个:怎么在非组件上下文里也能弹 Toast。
方案架构
我的做法分三步:
1. Layout 插件统一插入组件
通过 @uni-helper/vite-plugin-uni-layouts 插件实现统一布局,把所有全局组件一次性插入:
vue
<template>
<wd-config-provider>
<slot />
<wd-toast />
<wd-message-box />
<global-toast />
<global-loading />
</wd-config-provider>
</template>这样所有页面都共享这些组件,做到了"一次插入,全局可用"。
2. GlobalToast 组件监听状态
vue
<script setup>
const { toastOptions, currentPage } = storeToRefs(useGlobalToast())
watch(() => toastOptions.value, (newVal) => {
if (newVal?.show) {
toast.show(toastOptions.value)
} else {
toast.close()
}
})
</script>3. Pinia 驱动状态
typescript
export const useGlobalToast = defineStore('global-toast', {
actions: {
show(option) {
this.toastOptions = { show: true, ...option }
},
success(msg) {
this.show({ iconName: 'success', msg, duration: 1500 })
},
error(msg) {
this.show({ iconName: 'error', msg })
},
}
})效果
现在可以在任何地方调用:
typescript
// 请求拦截器里
const globalToast = useGlobalToast()
globalToast.error('网络请求失败')
// 路由守卫里
globalToast.warning('请先登录')
// 组件里
globalToast.success('操作成功!')这套方案的思路是"组件负责渲染,Pinia 负责状态,Layout 负责挂载"——各司其职,互不干扰。后来基于同样的架构,还扩展出了全局 Loading 和全局弹窗,复用性很好。
