Commit 84526182 by Liu

fix:饼图展示效果

parent ce98b1a5
...@@ -62,66 +62,79 @@ async function getMermaid() { ...@@ -62,66 +62,79 @@ async function getMermaid() {
return mermaidInitPromise return mermaidInitPromise
} }
function MermaidBlock({ chart }: { chart: string }) { const mermaidSvgCache = new Map<string, string>()
const mermaidHeightCache = new Map<string, number>()
function MermaidBlock({ chart, cacheKey }: { chart: string, cacheKey: string }) {
const reactId = useId() const reactId = useId()
const renderId = useMemo(() => `mermaid-${reactId.replace(/:/g, '-')}`, [reactId]) const renderId = useMemo(() => `mermaid-${reactId.replace(/:/g, '-')}`, [reactId])
const [svg, setSvg] = useState<string>('') const [svg, setSvg] = useState<string>(() => mermaidSvgCache.get(cacheKey) ?? '')
const [hidden, setHidden] = useState<boolean>(false) const [minHeight, setMinHeight] = useState<number>(() => mermaidHeightCache.get(cacheKey) ?? 0)
const svgContainerRef = useRef<HTMLDivElement | null>(null) const svgContainerRef = useRef<HTMLDivElement | null>(null)
const lastGoodSvgRef = useRef<string>('')
useEffect(() => { useEffect(() => {
let cancelled = false const cached = mermaidSvgCache.get(cacheKey)
const code = normalizeMermaid(chart) if (cached) {
if (!code) { lastGoodSvgRef.current = cached
// During streaming, chart text may be temporarily empty. setSvg(cached)
// Keep the previous SVG (if any) to avoid flicker, and only hide when nothing to show.
if (!svg) {
setSvg('')
setHidden(true)
}
return () => {
cancelled = true
}
} }
const cachedH = mermaidHeightCache.get(cacheKey)
if (cachedH)
setMinHeight(cachedH)
}, [cacheKey])
;(async () => { useEffect(() => {
try { let cancelled = false
const mermaid = await getMermaid() const code = normalizeMermaid(chart)
mermaid.initialize({ startOnLoad: false, suppressErrorRendering: true }) // During streaming, chart text is often incomplete. To avoid page flicker:
try { // - debounce render work (avoid per-token rerenders)
await mermaid.parse(code) // - only update SVG on successful parse+render
} // - keep the last successful SVG visible when parse fails temporarily
catch { const t = window.setTimeout(() => {
if (!cancelled) { ;(async () => {
if (!code) {
if (!cancelled && !lastGoodSvgRef.current)
setSvg('') setSvg('')
setHidden(true)
}
return return
} }
try {
const mermaid = await getMermaid()
try {
await mermaid.parse(code)
}
catch {
// Keep previous SVG if any.
if (!cancelled && !lastGoodSvgRef.current)
setSvg('')
return
}
const res = await mermaid.render(renderId, code)
if (cancelled)
return
const res = await mermaid.render(renderId, code)
if (!cancelled) {
const outSvg = String(res?.svg || '') const outSvg = String(res?.svg || '')
if (/Syntax error in text/i.test(outSvg)) { if (!outSvg || /Syntax error in text/i.test(outSvg)) {
setSvg('') if (!lastGoodSvgRef.current)
setHidden(true) setSvg('')
} return
else {
setSvg(outSvg)
setHidden(false)
} }
lastGoodSvgRef.current = outSvg
mermaidSvgCache.set(cacheKey, outSvg)
setSvg(outSvg)
} }
} catch {
catch { if (!cancelled && !lastGoodSvgRef.current)
if (!cancelled) { setSvg('')
setSvg('')
setHidden(true)
} }
} })()
})() }, 150)
return () => { return () => {
cancelled = true cancelled = true
window.clearTimeout(t)
} }
}, [chart, renderId]) }, [chart, renderId])
...@@ -130,13 +143,23 @@ function MermaidBlock({ chart }: { chart: string }) { ...@@ -130,13 +143,23 @@ function MermaidBlock({ chart }: { chart: string }) {
if (!el) if (!el)
return return
el.innerHTML = svg el.innerHTML = svg
if (!svg)
return
// Measure height after DOM update to reduce layout shift.
const raf = window.requestAnimationFrame(() => {
const h = Math.ceil(el.getBoundingClientRect().height)
if (h > 0) {
mermaidHeightCache.set(cacheKey, h)
setMinHeight(prev => (h > prev ? h : prev))
}
})
return () => {
window.cancelAnimationFrame(raf)
}
}, [svg]) }, [svg])
if (hidden || !svg)
return null
return ( return (
<div className="my-[8px] overflow-x-auto"> <div className="my-[8px] overflow-x-auto" style={{ minHeight: minHeight || undefined }}>
<div ref={svgContainerRef} /> <div ref={svgContainerRef} />
</div> </div>
) )
...@@ -178,7 +201,10 @@ export const MarkdownDetail: React.FC<MarkdownDetailProps> = memo(({ children, d ...@@ -178,7 +201,10 @@ export const MarkdownDetail: React.FC<MarkdownDetailProps> = memo(({ children, d
|| (isPie && !disablePie) || (isPie && !disablePie)
) { ) {
const chart = toText((first.props as any)?.children) const chart = toText((first.props as any)?.children)
return <MermaidBlock chart={chart} /> const node: any = (props as any)?.node
const startOffset = node?.position?.start?.offset
const key = `${lang || 'mermaid'}:${String(startOffset ?? 'na')}`
return <MermaidBlock chart={chart} cacheKey={key} />
} }
} }
return <pre {...props}>{children}</pre> return <pre {...props}>{children}</pre>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment