markdown控件增加支持渲染mermaid图表

This commit is contained in:
jingrow 2026-05-05 18:17:42 +08:00
parent f96a710611
commit 5855b84989

View File

@ -9,11 +9,15 @@ const props = defineProps<{ df: any; record: Record<string, any>; canEdit: boole
const labelLayout = computed(() => props.df.label_layout || 'vertical')
const editorHost = ref<HTMLDivElement | null>(null)
const previewHost = ref<HTMLDivElement | null>(null)
let monaco: any = null
let editor: any = null
const expanded = ref(false)
const mode = ref<'preview' | 'edit'>('preview')
// mermaid ID
let mermaidCounter = 0
const mdPreview = computed(() => {
try {
return marked.parse(String(props.record[props.df.fieldname] || '')) as string
@ -22,6 +26,49 @@ const mdPreview = computed(() => {
}
})
// Mermaid marked.parse
async function renderMermaidBlocks() {
if (!previewHost.value) return
const { default: mermaid } = await import('mermaid')
mermaid.initialize({ startOnLoad: false, theme: 'default' })
// ```mermaid
const codeBlocks = previewHost.value.querySelectorAll('pre code.language-mermaid')
for (const block of Array.from(codeBlocks)) {
const pre = block.parentElement
if (!pre || pre.dataset.mermaidRendered) continue
const code = block.textContent || ''
const id = `md-mermaid-${++mermaidCounter}`
try {
const { svg } = await mermaid.render(id, code)
pre.outerHTML = `<div class="mermaid-diagram" data-mermaid-rendered="true">${svg}</div>`
} catch (err) {
console.error('[MarkdownEditor] Mermaid render error:', err)
pre.dataset.mermaidRendered = 'true'
}
}
// Mermaid ``` flowchart/graph/...
// <p> Mermaid
const paragraphs = previewHost.value.querySelectorAll('p')
for (const p of Array.from(paragraphs)) {
if (p.dataset.mermaidRendered || p.dataset.mermaidChecked) continue
p.dataset.mermaidChecked = 'true'
const text = p.textContent || ''
const trimmed = text.trim()
if (/^(flowchart|graph|sequenceDiagram|classDiagram|stateDiagram|erDiagram|gantt|pie|requirementDiagram|gitGraph|journey)\s/i.test(trimmed)) {
const id = `md-mermaid-${++mermaidCounter}`
try {
const { svg } = await mermaid.render(id, trimmed)
p.outerHTML = `<div class="mermaid-diagram" data-mermaid-rendered="true">${svg}</div>`
} catch (err) {
console.error('[MarkdownEditor] Mermaid render error:', err)
p.dataset.mermaidRendered = 'true'
}
}
}
}
async function mountEditor() {
if (!editorHost.value) return
if (!monaco) monaco = await loader.init()
@ -91,6 +138,13 @@ watch(mode, async (m) => {
}
})
// Mermaid
watch([mode, mdPreview], async () => {
if (mode.value !== 'preview') return
await nextTick()
await renderMermaidBlocks()
})
onBeforeUnmount(disposeEditor)
</script>
@ -118,7 +172,7 @@ onBeforeUnmount(disposeEditor)
>{{ props.ctx?.t ? props.ctx.t('Edit') : 'Edit' }}</n-button>
</div>
<div v-if="mode==='preview'" class="md-editor__preview" v-html="mdPreview"></div>
<div v-if="mode==='preview'" ref="previewHost" class="md-editor__preview" v-html="mdPreview"></div>
<div v-else ref="editorHost" class="md-editor__monaco" :class="{ collapsed: !expanded }"></div>
<div v-if="mode==='edit'" class="md-editor__actions">
@ -158,6 +212,23 @@ onBeforeUnmount(disposeEditor)
/* 无溢出隐藏滚动条 */
.no-scroll-y :deep(.monaco-scrollable-element > .scrollbar.vertical) { display: none !important; }
.no-scroll-x :deep(.monaco-scrollable-element > .scrollbar.horizontal) { display: none !important; }
/* Mermaid 图表样式 */
.md-editor__preview :deep(.mermaid-diagram) {
display: flex;
justify-content: center;
align-items: center;
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 16px;
margin: 12px 0;
overflow-x: auto;
}
.md-editor__preview :deep(.mermaid-diagram svg) {
max-width: 100%;
height: auto;
}
</style>