From da75f50798417b48b201ce0c1a59db16ef0ba0f3 Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Wed, 6 May 2026 21:36:20 -0600 Subject: [PATCH] styling mermaid --- src/index.css | 20 ++ .../composables/useMermaidConverter.ts | 22 +- src/shared/composables/useMermaid.ts | 235 +++++++++++++++++- 3 files changed, 269 insertions(+), 8 deletions(-) diff --git a/src/index.css b/src/index.css index 6e9ffed..6b09e2f 100644 --- a/src/index.css +++ b/src/index.css @@ -10,6 +10,26 @@ body { --md-theme-color: var(--n-text-color) !important; } +.oj-mermaid-surface { + box-sizing: border-box; + padding: 18px; + overflow: auto; + border: 1px solid rgba(148, 163, 184, 0.24); + border-radius: 8px; + background: + linear-gradient(rgba(148, 163, 184, 0.08) 1px, transparent 1px), + linear-gradient(90deg, rgba(148, 163, 184, 0.08) 1px, transparent 1px), + linear-gradient(135deg, #ffffff 0%, #f8fafc 52%, #eef6ff 100%); + background-size: + 24px 24px, + 24px 24px, + auto; +} + +.oj-mermaid-surface > svg { + max-width: 100%; +} + ::view-transition-old(root), ::view-transition-new(root) { animation: none; diff --git a/src/oj/problem/composables/useMermaidConverter.ts b/src/oj/problem/composables/useMermaidConverter.ts index b5641c5..7070400 100644 --- a/src/oj/problem/composables/useMermaidConverter.ts +++ b/src/oj/problem/composables/useMermaidConverter.ts @@ -72,15 +72,19 @@ export function useMermaidConverter() { // 添加样式定义来区分不同类型的节点 mermaid += "\n" mermaid += - " classDef startEnd fill:#e1f5fe,stroke:#01579b,stroke-width:2px\n" + " classDef startNode fill:#dcfce7,stroke:#16a34a,stroke-width:2.5px,color:#0f172a\n" mermaid += - " classDef input fill:#e3f2fd,stroke:#1976d2,stroke-width:2px\n" + " classDef endNode fill:#fee2e2,stroke:#dc2626,stroke-width:2.5px,color:#0f172a\n" mermaid += - " classDef output fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px\n" + " classDef input fill:#dbeafe,stroke:#2563eb,stroke-width:2.5px,color:#0f172a\n" mermaid += - " classDef process fill:#e8f5e8,stroke:#1b5e20,stroke-width:2px\n" + " classDef output fill:#ede9fe,stroke:#7c3aed,stroke-width:2.5px,color:#0f172a\n" mermaid += - " classDef decision fill:#fff3e0,stroke:#e65100,stroke-width:2px\n" + " classDef process fill:#f0f9ff,stroke:#0284c7,stroke-width:2.5px,color:#0f172a\n" + mermaid += + " classDef decision fill:#fef3c7,stroke:#d97706,stroke-width:2.5px,color:#0f172a\n" + mermaid += + " classDef loop fill:#fae8ff,stroke:#c026d3,stroke-width:2.5px,color:#0f172a\n" mermaid += "\n" // 为节点应用样式 @@ -90,8 +94,10 @@ export function useMermaidConverter() { switch (originalType) { case "start": + mermaid += ` class ${nodeId} startNode\n` + break case "end": - mermaid += ` class ${nodeId} startEnd\n` + mermaid += ` class ${nodeId} endNode\n` break case "input": mermaid += ` class ${nodeId} input\n` @@ -100,9 +106,11 @@ export function useMermaidConverter() { mermaid += ` class ${nodeId} output\n` break case "decision": - case "loop": mermaid += ` class ${nodeId} decision\n` break + case "loop": + mermaid += ` class ${nodeId} loop\n` + break default: mermaid += ` class ${nodeId} process\n` } diff --git a/src/shared/composables/useMermaid.ts b/src/shared/composables/useMermaid.ts index 69d9db7..b5a2d45 100644 --- a/src/shared/composables/useMermaid.ts +++ b/src/shared/composables/useMermaid.ts @@ -1,5 +1,236 @@ import { getRandomId } from "utils/functions" +const mermaidThemeVariables = { + primaryColor: "#e0f2fe", + primaryTextColor: "#0f172a", + primaryBorderColor: "#0284c7", + lineColor: "#64748b", + secondaryColor: "#f5f3ff", + tertiaryColor: "#ecfdf5", + background: "#ffffff", + mainBkg: "#f8fafc", + secondBkg: "#eef2ff", + tertiaryBkg: "#f0fdfa", + nodeBorder: "#2563eb", + clusterBkg: "#f8fafc", + clusterBorder: "#cbd5e1", + edgeLabelBackground: "#ffffff", + fontFamily: + 'Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif', +} + +const semanticNodeClasses = [ + "startNode", + "endNode", + "startEnd", + "input", + "output", + "process", + "decision", + "loop", +] + +const displayStyleId = "oj-mermaid-display-style" + +const mermaidDisplayStyle = ` +.oj-mermaid-flowchart { + max-width: 100%; + height: auto; +} + +.oj-mermaid-flowchart g.node rect, +.oj-mermaid-flowchart g.node polygon, +.oj-mermaid-flowchart g.node ellipse, +.oj-mermaid-flowchart g.node circle, +.oj-mermaid-flowchart g.node path { + stroke-width: 2.5px !important; + filter: drop-shadow(0 6px 12px rgba(15, 23, 42, 0.12)); +} + +.oj-mermaid-flowchart g.node.startNode rect, +.oj-mermaid-flowchart g.node.startNode polygon, +.oj-mermaid-flowchart g.node.startNode ellipse, +.oj-mermaid-flowchart g.node.startNode circle, +.oj-mermaid-flowchart g.node.startNode path, +.oj-mermaid-flowchart g.node.startEnd rect, +.oj-mermaid-flowchart g.node.startEnd polygon, +.oj-mermaid-flowchart g.node.startEnd ellipse, +.oj-mermaid-flowchart g.node.startEnd circle, +.oj-mermaid-flowchart g.node.startEnd path { + fill: #dcfce7 !important; + stroke: #16a34a !important; +} + +.oj-mermaid-flowchart g.node.endNode rect, +.oj-mermaid-flowchart g.node.endNode polygon, +.oj-mermaid-flowchart g.node.endNode ellipse, +.oj-mermaid-flowchart g.node.endNode circle, +.oj-mermaid-flowchart g.node.endNode path { + fill: #fee2e2 !important; + stroke: #dc2626 !important; +} + +.oj-mermaid-flowchart g.node.input rect, +.oj-mermaid-flowchart g.node.input polygon, +.oj-mermaid-flowchart g.node.input ellipse, +.oj-mermaid-flowchart g.node.input circle, +.oj-mermaid-flowchart g.node.input path { + fill: #dbeafe !important; + stroke: #2563eb !important; +} + +.oj-mermaid-flowchart g.node.output rect, +.oj-mermaid-flowchart g.node.output polygon, +.oj-mermaid-flowchart g.node.output ellipse, +.oj-mermaid-flowchart g.node.output circle, +.oj-mermaid-flowchart g.node.output path { + fill: #ede9fe !important; + stroke: #7c3aed !important; +} + +.oj-mermaid-flowchart g.node.process rect, +.oj-mermaid-flowchart g.node.process polygon, +.oj-mermaid-flowchart g.node.process ellipse, +.oj-mermaid-flowchart g.node.process circle, +.oj-mermaid-flowchart g.node.process path { + fill: #f0f9ff !important; + stroke: #0284c7 !important; +} + +.oj-mermaid-flowchart g.node.decision rect, +.oj-mermaid-flowchart g.node.decision polygon, +.oj-mermaid-flowchart g.node.decision ellipse, +.oj-mermaid-flowchart g.node.decision circle, +.oj-mermaid-flowchart g.node.decision path { + fill: #fef3c7 !important; + stroke: #d97706 !important; +} + +.oj-mermaid-flowchart g.node.loop rect, +.oj-mermaid-flowchart g.node.loop polygon, +.oj-mermaid-flowchart g.node.loop ellipse, +.oj-mermaid-flowchart g.node.loop circle, +.oj-mermaid-flowchart g.node.loop path { + fill: #fae8ff !important; + stroke: #c026d3 !important; +} + +.oj-mermaid-flowchart g.node.oj-node-palette-0 rect, +.oj-mermaid-flowchart g.node.oj-node-palette-0 polygon, +.oj-mermaid-flowchart g.node.oj-node-palette-0 ellipse, +.oj-mermaid-flowchart g.node.oj-node-palette-0 circle, +.oj-mermaid-flowchart g.node.oj-node-palette-0 path { + fill: #dbeafe !important; + stroke: #2563eb !important; +} + +.oj-mermaid-flowchart g.node.oj-node-palette-1 rect, +.oj-mermaid-flowchart g.node.oj-node-palette-1 polygon, +.oj-mermaid-flowchart g.node.oj-node-palette-1 ellipse, +.oj-mermaid-flowchart g.node.oj-node-palette-1 circle, +.oj-mermaid-flowchart g.node.oj-node-palette-1 path { + fill: #ccfbf1 !important; + stroke: #0d9488 !important; +} + +.oj-mermaid-flowchart g.node.oj-node-palette-2 rect, +.oj-mermaid-flowchart g.node.oj-node-palette-2 polygon, +.oj-mermaid-flowchart g.node.oj-node-palette-2 ellipse, +.oj-mermaid-flowchart g.node.oj-node-palette-2 circle, +.oj-mermaid-flowchart g.node.oj-node-palette-2 path { + fill: #ede9fe !important; + stroke: #7c3aed !important; +} + +.oj-mermaid-flowchart g.node.oj-node-palette-3 rect, +.oj-mermaid-flowchart g.node.oj-node-palette-3 polygon, +.oj-mermaid-flowchart g.node.oj-node-palette-3 ellipse, +.oj-mermaid-flowchart g.node.oj-node-palette-3 circle, +.oj-mermaid-flowchart g.node.oj-node-palette-3 path { + fill: #ffe4e6 !important; + stroke: #e11d48 !important; +} + +.oj-mermaid-flowchart g.node.oj-node-palette-4 rect, +.oj-mermaid-flowchart g.node.oj-node-palette-4 polygon, +.oj-mermaid-flowchart g.node.oj-node-palette-4 ellipse, +.oj-mermaid-flowchart g.node.oj-node-palette-4 circle, +.oj-mermaid-flowchart g.node.oj-node-palette-4 path { + fill: #fef3c7 !important; + stroke: #d97706 !important; +} + +.oj-mermaid-flowchart g.node.oj-node-palette-5 rect, +.oj-mermaid-flowchart g.node.oj-node-palette-5 polygon, +.oj-mermaid-flowchart g.node.oj-node-palette-5 ellipse, +.oj-mermaid-flowchart g.node.oj-node-palette-5 circle, +.oj-mermaid-flowchart g.node.oj-node-palette-5 path { + fill: #dcfce7 !important; + stroke: #16a34a !important; +} + +.oj-mermaid-flowchart g.node .label, +.oj-mermaid-flowchart g.node .nodeLabel, +.oj-mermaid-flowchart g.node .nodeLabel p, +.oj-mermaid-flowchart g.node .label span { + color: #0f172a !important; + fill: #0f172a !important; + font-weight: 650 !important; +} + +.oj-mermaid-flowchart .edgePaths path.path, +.oj-mermaid-flowchart .flowchart-link { + stroke: #64748b !important; + stroke-width: 2.4px !important; +} + +.oj-mermaid-flowchart marker path, +.oj-mermaid-flowchart .marker { + fill: #64748b !important; + stroke: #64748b !important; +} + +.oj-mermaid-flowchart .edgeLabel rect, +.oj-mermaid-flowchart .edgeLabel .labelBkg { + fill: rgba(255, 255, 255, 0.94) !important; + stroke: #cbd5e1 !important; +} + +.oj-mermaid-flowchart .edgeLabel, +.oj-mermaid-flowchart .edgeLabel span, +.oj-mermaid-flowchart .edgeLabel p { + color: #334155 !important; + font-weight: 600 !important; +} +` + +const svgNamespace = "http://www.w3.org/2000/svg" + +function applyFlowchartDisplayStyle(container: HTMLElement) { + container.classList.add("oj-mermaid-surface") + + const svg = container.querySelector("svg") + if (!svg) return + + svg.classList.add("oj-mermaid-flowchart") + + const nodes = Array.from(svg.querySelectorAll("g.node")) + nodes.forEach((node, index) => { + const hasSemanticClass = semanticNodeClasses.some((className) => + node.classList.contains(className), + ) + if (!hasSemanticClass) { + node.classList.add(`oj-node-palette-${index % 6}`) + } + }) + + svg.querySelector(`#${displayStyleId}`)?.remove() + const style = document.createElementNS(svgNamespace, "style") + style.setAttribute("id", displayStyleId) + style.textContent = mermaidDisplayStyle + svg.insertBefore(style, svg.firstChild) +} + export function useMermaid() { // 渲染状态 const renderError = ref(null) @@ -15,7 +246,8 @@ export function useMermaid() { mermaid.initialize({ startOnLoad: false, securityLevel: "strict", - theme: "default", + theme: "base", + themeVariables: mermaidThemeVariables, }) } return mermaid @@ -37,6 +269,7 @@ export function useMermaid() { const id = `mermaid-${getRandomId()}` const { svg } = await mermaid.render(id, mermaidCode) container.innerHTML = svg + applyFlowchartDisplayStyle(container) } } catch (error) { renderError.value =