/** * 轻量 toast 提示 composable — 消除 AiChat / Settings / Projects / TaskDetail 4 处重复。 * * 原 4 处各自 reactive/ref + _toastTimer + showToast + onUnmounted 清理, * 且 Projects 用 4000ms 其余 3000ms(体验不一致 bug)。 * 本 composable 统一默认 3000ms,支持调用方按需传 durationMs。 * * 用法: * const { toast, showToast } = useToast() * showToast('保存成功') * showToast('导入失败', 'error', 4000) * // template:
{{ toast.msg }}
*/ import { reactive } from 'vue' import { useTimerOwnership } from './useTimerOwnership' // P0-2: 加 'success'(保存成功用绿色 toast,此前成功/中性都用 info 致反馈不明确) export type ToastType = 'info' | 'error' | 'warning' | 'success' export interface ToastState { visible: boolean msg: string type: ToastType } export function useToast(defaultDurationMs = 3000) { const toast = reactive({ visible: false, msg: '', type: 'info', }) // G5.4:原模块级 `let _timer` 单例被所有 useToast() 实例共享 —— 组件 B showToast/onUnmounted // 会清掉组件 A 的 timer,致 A 的 toast 永不隐藏(跨实例 bug)。 // 已下沉为「每实例独立 timer」:useTimerOwnership 每实例注册表 + onUnmounted 只清自己。 // 不再保留模块级兜底 —— 模块级共享正是串扰根源,每实例自清理即正确语义。 const { setOwnTimeout, clearOwn } = useTimerOwnership() let _timer: ReturnType | null = null function showToast(msg: string, type: ToastType = 'info', durationMs?: number) { toast.msg = msg toast.type = type toast.visible = true if (_timer) clearOwn(_timer) _timer = setOwnTimeout(() => { _timer = null toast.visible = false }, durationMs ?? defaultDurationMs) } function hideToast() { if (_timer) { clearOwn(_timer) _timer = null } toast.visible = false } return { toast, showToast, hideToast } }