Files
web-frontend/docs/.vitepress/theme/components/MermaidDiagram.vue
2026-05-01 14:41:02 +07:00

95 lines
1.7 KiB
Vue

<script setup lang="ts">
import mermaid from 'mermaid';
import { computed, onMounted, ref, watch } from 'vue';
const props = defineProps<{
chart: string;
}>();
const svg = ref('');
const error = ref('');
let initialized = false;
let diagramId = 0;
function initMermaid() {
if (initialized) {
return;
}
mermaid.initialize({
startOnLoad: false,
theme: 'neutral',
securityLevel: 'strict',
fontFamily: 'Inter, Arial, sans-serif',
flowchart: {
useMaxWidth: true,
htmlLabels: true,
},
});
initialized = true;
}
const source = computed(() => props.chart.trim());
async function renderDiagram() {
if (!import.meta.client) {
return;
}
try {
initMermaid();
error.value = '';
diagramId += 1;
const { svg: result } = await mermaid.render(`mermaid-diagram-${diagramId}`, source.value);
svg.value = result;
} catch (cause) {
svg.value = '';
error.value = cause instanceof Error ? cause.message : 'Failed to render Mermaid diagram.';
}
}
onMounted(() => {
void renderDiagram();
});
watch(source, () => {
void renderDiagram();
});
</script>
<template>
<div class="mermaid-diagram">
<div v-if="error" class="mermaid-diagram__error">{{ error }}</div>
<div v-else class="mermaid-diagram__canvas" v-html="svg" />
</div>
</template>
<style scoped>
.mermaid-diagram {
margin: 1.25rem 0 1.75rem;
border: 1px solid #d9dee7;
border-radius: 20px;
background: #fff;
overflow: hidden;
}
.mermaid-diagram__canvas {
padding: 1rem;
}
.mermaid-diagram__canvas :deep(svg) {
display: block;
width: 100%;
height: auto;
}
.mermaid-diagram__error {
padding: 1rem 1.25rem;
color: #b42318;
background: #fef3f2;
font-size: 0.95rem;
}
</style>