feat(流程图): 输入/输出/语句/条件/循环五种框各一种颜色,不再输入输出同色、条件循环同色
Deploy / deploy (push) Canceled after 0s

输出改紫、循环改青,绿和红留给起止框、选中和标错。存下来的 Mermaid 里循环也标成
decision,画之前按「有箭头从后面绕回来」认出循环,历史提交的图也按新颜色画。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-28 12:33:00 +08:00
1 parent 179d5fa67d
commit 0916015405
2 files changed
+37 -12

No files matched your search

@@ -2,22 +2,28 @@ import { PLACEHOLDER, type LaidNode } from './layout'
/**
* 框的颜色和形状:画流程图的画布(index.vue)和只看的图(FlowchartView.vue)共用一份,
* 两边才长得一样
* 两边才长得一样。题面里的 Mermaid 图在 useMermaid.ts 另有一份,改颜色两边一起改。
*
* 输入 / 输出 / 处理 / 判断 / 循环各一种颜色;绿和红不给框用 —— 绿是起止框和选中,红是标错
*/
type Paint = { fill: string; stroke: string; text: string }
const LIGHT: Record<string, Paint> = {
terminal: { fill: '#eef8f2', stroke: '#18a058', text: '#0c6b3b' },
io: { fill: '#eef4fd', stroke: '#2f6bc4', text: '#1d4f9c' },
input: { fill: '#eef4fd', stroke: '#2f6bc4', text: '#1d4f9c' },
output: { fill: '#f4effc', stroke: '#8a55c9', text: '#5d2f99' },
process: { fill: '#ffffff', stroke: '#7d828a', text: '#1f2225' },
decision: { fill: '#fdf5e6', stroke: '#d08a1c', text: '#8a5200' },
if: { fill: '#fdf5e6', stroke: '#d08a1c', text: '#8a5200' },
while: { fill: '#e8f6f8', stroke: '#1590a6', text: '#0b5e6e' },
}
const DARK: Record<string, Paint> = {
terminal: { fill: 'rgba(24,160,88,0.16)', stroke: '#3fbf7f', text: '#8fe0b4' },
io: { fill: 'rgba(70,130,220,0.18)', stroke: '#5b8fdc', text: '#a9c6f2' },
input: { fill: 'rgba(70,130,220,0.18)', stroke: '#5b8fdc', text: '#a9c6f2' },
output: { fill: 'rgba(150,100,215,0.2)', stroke: '#a57ee0', text: '#d2bcf4' },
process: { fill: '#26272b', stroke: '#8a8f98', text: '#e6e7ea' },
decision: { fill: 'rgba(208,138,28,0.18)', stroke: '#e0a040', text: '#f2cd8c' },
if: { fill: 'rgba(208,138,28,0.18)', stroke: '#e0a040', text: '#f2cd8c' },
while: { fill: 'rgba(30,160,185,0.18)', stroke: '#3cb4c8', text: '#9edce6' },
}
export function useFlowPaint() {
@@ -25,9 +31,7 @@ export function useFlowPaint() {
function paint(kind: LaidNode['kind']) {
const paints = isDark.value ? DARK : LIGHT
if (kind === 'start' || kind === 'end') return paints.terminal!
if (kind === 'input' || kind === 'output') return paints.io!
if (kind === 'if' || kind === 'while') return paints.decision!
return paints.process!
return paints[kind] ?? paints.process!
}
const lineColor = computed(() => (isDark.value ? '#8a8f98' : '#7d828a'))
return { isDark, paint, lineColor }
+25 -4
View File
@@ -10,16 +10,16 @@ const semanticNodeClasses = ['startNode', 'endNode', 'startEnd', 'input', 'outpu
const displayStyleId = 'oj-mermaid-display-style'
/** 框的颜色:[class, 底色, 边框]。pele 把 `class n0 startNode` 原样挂在节点的 <g> 上 */
/** 框的颜色:[class, 底色, 边框],和画布的 FlowchartEditor/paint.ts 一致。pele 把 `class n0 startNode` 原样挂在节点的 <g> 上 */
const nodeColors: [string, string, string][] = [
['startNode', '#eef8f2', '#18a058'],
['startEnd', '#eef8f2', '#18a058'],
['endNode', '#eef8f2', '#18a058'],
['input', '#eef4fd', '#2f6bc4'],
['output', '#eef4fd', '#2f6bc4'],
['output', '#f4effc', '#8a55c9'],
['process', '#ffffff', '#7d828a'],
['decision', '#fdf5e6', '#d08a1c'],
['loop', '#fdf5e6', '#d08a1c'],
['loop', '#e8f6f8', '#1590a6'],
['oj-node-palette-0', '#dbeafe', '#2563eb'],
['oj-node-palette-1', '#ccfbf1', '#0d9488'],
['oj-node-palette-2', '#ede9fe', '#7c3aed'],
@@ -74,6 +74,27 @@ ${nodeColors
const svgNamespace = 'http://www.w3.org/2000/svg'
/**
* 契约的 flowToMermaid 把判断和循环都标成 decision(库里存的提交、参考图都是这样),
* 两者要分颜色,只能在画之前认出循环:有箭头从后面的框绕回来的 decision 就是循环
*/
function markLoops(code: string) {
const order = new Map<string, number>()
const backTargets = new Set<string>()
for (const line of code.split('\n')) {
const head = /^\s*(\w+)/.exec(line)?.[1]
if (head && head !== 'flowchart' && head !== 'graph' && head !== 'class' && !order.has(head)) order.set(head, order.size)
const edge = /^\s*(\w+)\b.*?-->(?:\|[^|]*\|)?\s*(\w+)/.exec(line)
if (!edge) continue
const [, from, to] = edge
if (!order.has(to!)) order.set(to!, order.size)
if (order.get(from!)! > order.get(to!)!) backTargets.add(to!)
}
return code.replace(/^(\s*class\s+)(\w+)(\s+)decision\s*$/gm, (line, pre: string, id: string, gap: string) =>
backTargets.has(id) ? `${pre}${id}${gap}loop` : line,
)
}
function applyFlowchartDisplayStyle(container: HTMLElement) {
container.classList.add('oj-mermaid-surface')
@@ -124,7 +145,7 @@ export function useMermaid() {
try {
// pele/lazy 按图的类型现拉对应的模块,题目里绝大多数是流程图,只拉那一块
const { renderAsync } = await import('pele/lazy')
const { svg } = await renderAsync(mermaidCode, {
const { svg } = await renderAsync(markLoops(mermaidCode), {
// 量字宽用页面真实的字体,否则框会比字窄或宽
fontFamily: getComputedStyle(container).fontFamily,
// 题目是老师写的、提交是学生写的,图里的链接和外链图片一律不画