99 lines
3.0 KiB
TypeScript
99 lines
3.0 KiB
TypeScript
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<boolean>): 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<boolean>,
|
||
copied: Ref<boolean>,
|
||
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),
|
||
}
|
||
}
|