Skip to content

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 和全局弹窗,复用性很好。