显示流程图
Some checks failed
Deploy / deploy (push) Has been cancelled

This commit is contained in:
2025-10-12 12:59:38 +08:00
parent d7316d4681
commit d3694a8d28
12 changed files with 2659 additions and 90 deletions

View File

@@ -0,0 +1,161 @@
<script setup lang="ts">
import { nanoid } from "nanoid"
import { copyToClipboard } from "utils/functions"
// 动态导入 mermaid
let mermaid: any = null
const modelValue = defineModel<string>({ default: "" })
const mermaidContainer = useTemplateRef<HTMLElement>("mermaidContainer")
const codeEditor = useTemplateRef<HTMLTextAreaElement>("codeEditor")
// 渲染状态
const renderSuccess = ref(false)
// 定义事件
const emit = defineEmits<{
renderSuccess: []
}>()
// 动态加载 Mermaid
const loadMermaid = async () => {
if (!mermaid) {
const mermaidModule = await import("mermaid")
mermaid = mermaidModule.default
mermaid.initialize({
startOnLoad: false,
securityLevel: "loose",
theme: "default",
flowchart: {
useMaxWidth: true,
htmlLabels: true,
curve: "basis",
},
sequence: {
useMaxWidth: true,
},
gantt: {
useMaxWidth: true,
},
})
}
return mermaid
}
// 初始化
onMounted(async () => {
await loadMermaid()
nextTick(() => {
renderMermaid()
})
})
// 监听代码变化
watch(modelValue, () => {
renderMermaid()
})
// 渲染Mermaid图表
const renderMermaid = async () => {
if (!mermaidContainer.value) {
renderSuccess.value = false
return
}
// 总是先清空容器
mermaidContainer.value.innerHTML = ""
// 如果没有内容,直接返回
if (!modelValue.value.trim()) {
renderSuccess.value = false
return
}
try {
// 确保 mermaid 已加载
const mermaidInstance = await loadMermaid()
const id = `mermaid-${nanoid()}`
const { svg } = await mermaidInstance.render(id, modelValue.value)
mermaidContainer.value.innerHTML = svg
// 渲染成功
renderSuccess.value = true
emit("renderSuccess")
} catch (error: any) {
const errorMessage = error?.message || "请检查代码语法"
renderSuccess.value = false
mermaidContainer.value.innerHTML = `
<div style="color: #ff4d4f; padding: 20px; text-align: center; border: 1px dashed #ff4d4f; border-radius: 4px;">
<p>❌ Mermaid语法错误</p>
<p style="font-size: 12px; color: #666;">${errorMessage}</p>
</div>
`
}
}
// 清空代码
const clearCode = () => {
modelValue.value = ""
}
// 复制代码
const copyCode = async () => {
copyToClipboard(modelValue.value)
}
// 组件卸载时清空容器
onBeforeUnmount(() => {
if (mermaidContainer.value) {
mermaidContainer.value.innerHTML = ""
}
})
</script>
<template>
<n-flex>
<n-flex vertical>
<n-flex align="center">
<span>Mermaid 代码</span>
<n-flex align="center">
<n-button text @click="copyCode" size="small" type="primary"
>复制</n-button
>
<n-button text @click="clearCode" type="error" size="small"
>清空</n-button
>
</n-flex>
</n-flex>
<n-input
class="code-editor"
ref="codeEditor"
v-model:value="modelValue"
type="textarea"
:autosize="{ minRows: 10, maxRows: 20 }"
/>
</n-flex>
<n-flex vertical>
<n-flex align="center" justify="space-between">
<span>图表预览</span>
<n-tag v-if="modelValue && renderSuccess" type="success" size="small">
渲染成功
</n-tag>
</n-flex>
<div ref="mermaidContainer" class="mermaid-container"></div>
</n-flex>
</n-flex>
</template>
<style scoped>
.code-editor {
flex: 1;
width: 400px;
}
.mermaid-container {
width: 400px;
min-height: 400px;
border: 1px solid #d9d9d9;
border-radius: 3px;
padding: 16px;
}
</style>

View File

@@ -120,7 +120,7 @@ watch(signupModalOpen, (v) => {
clearable
type="password"
name="password"
id="signup-password"
id="signup-password"
autocomplete="new-password"
/>
</n-form-item>