import { useMessage } from 'naive-ui' import { h, ref, type Ref, type VNodeChild } from 'vue' type ToastKind = 'error' | 'warning' const AUTO_CLOSE_MS = 8000 /** 超过此长度的消息自动折叠成「标题 + 展开详情」 */ const LONG_TEXT = 64 /** 长错误提炼标题:优先 hint 分隔符(request.ts 的「hint|error」),次选首个冒号操作前缀,兜底截断 */ function summarize(text: string): string { const bar = text.indexOf('|') if (bar > 0 && bar <= 40) return text.slice(0, bar) const colon = text.search(/:|: /) if (colon > 0 && colon <= 40) return text.slice(0, colon) return [...text].slice(0, 40).join('') + '…' } function renderDetail(kind: ToastKind, detail: string, copied: Ref): VNodeChild { return h('div', { class: 'toast-code' }, [ h('div', { class: 'toast-code-head' }, [ h('span', { class: `toast-tag toast-tag-${kind}` }, kind === 'error' ? 'ERROR' : 'WARN'), h( 'button', { class: 'toast-copy', onClick: () => { void navigator.clipboard.writeText(detail) copied.value = true }, }, copied.value ? '已复制' : '⎘ 复制', ), ]), h('pre', { class: 'toast-code-pre' }, detail), ]) } function renderToast( kind: ToastKind, text: string, detail: string, expanded: Ref, copied: Ref, toggle: () => void, ): VNodeChild { const rows: VNodeChild[] = [ h('div', { class: 'toast-line' }, [ h('span', text), h('a', { class: 'toast-toggle', onClick: toggle }, expanded.value ? '收起 ▴' : '展开详情 ▾'), ]), ] if (expanded.value) rows.push(renderDetail(kind, detail, copied)) return h('div', { class: 'toast-body' }, rows) } /** * 带可展开详情的消息封装:error/warning 传 detail 时,toast 内嵌 * 「展开详情」代码块(带复制);展开期间暂停 8s 自动关闭,收起后重新计时。 */ export function useToast() { const message = useMessage() function show(kind: ToastKind, text: string, detail?: string) { // 未显式给 detail 的超长消息(如 OCI 透传错误)自动折叠,避免整段撑爆 toast if (!detail && text.length > LONG_TEXT) { detail = text text = summarize(text) } if (!detail) { message[kind](text) return } const expanded = ref(false) const copied = ref(false) let timer = 0 const inst = message[kind]( () => renderToast(kind, text, detail, expanded, copied, toggle), { duration: 0, closable: true }, ) const arm = () => { timer = window.setTimeout(() => inst.destroy(), AUTO_CLOSE_MS) } function toggle() { expanded.value = !expanded.value window.clearTimeout(timer) if (!expanded.value) arm() } arm() } return { success: (text: string) => message.success(text), info: (text: string) => message.info(text), error: (text: string, detail?: string) => show('error', text, detail), warning: (text: string, detail?: string) => show('warning', text, detail), } }