/** * 右上角的轻提示(成就弹窗式):从右滑入一行字,几秒后上移淡出。 * * 模块级单例,谁都能发;渲染在外壳的 ToastHost 里。适合「动作已完成/失败」 * 这类看一眼就行的反馈;需要用户处理的内容(表单校验、带重试的错误) * 仍然留在页面里,不进这里。 */ import { ref } from 'vue' import { ApiError } from './api' export type ToastKind = 'ok' | 'error' | 'info' export interface Toast { id: number kind: ToastKind message: string hint: string } export const toasts = ref([]) let nextId = 1 export function toast(message: string, opts?: { kind?: ToastKind; hint?: string }): void { const item: Toast = { id: nextId++, kind: opts?.kind ?? 'ok', message, hint: opts?.hint ?? '', } toasts.value = [...toasts.value, item] // 错误多留一会儿,看清 hint 里的下一步 const ttl = item.kind === 'error' ? 8000 : 4000 setTimeout(() => dismiss(item.id), ttl) } export function dismiss(id: number): void { toasts.value = toasts.value.filter((t) => t.id !== id) } /** 接口报错的统一出口:message 说发生了什么,hint 说下一步。 */ export function toastError(error: unknown): void { if (error instanceof ApiError) toast(error.message, { kind: 'error', hint: error.hint }) else toast(String(error), { kind: 'error' }) }