feat: CodeMirror6代码编辑器替代textarea,暗色主题+语法高亮

This commit is contained in:
mediabot-pt
2026-07-01 13:38:56 +08:00
parent 621e436eb0
commit e05d337567
3 changed files with 78 additions and 4 deletions

View File

@@ -0,0 +1,67 @@
<template>
<div ref="editorEl" style="height:100%;overflow:hidden"></div>
</template>
<script setup>
import { ref, onMounted, watch, onUnmounted } from 'vue'
import { EditorView, keymap, lineNumbers, highlightActiveLineGutter, highlightSpecialChars, drawSelection, rectangularSelection, crosshairCursor, highlightActiveLine } from '@codemirror/view'
import { EditorState } from '@codemirror/state'
import { foldGutter, indentOnInput, syntaxHighlighting, defaultHighlightStyle, bracketMatching, foldKeymap } from '@codemirror/language'
import { javascript } from '@codemirror/lang-javascript'
import { oneDark } from '@codemirror/theme-one-dark'
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands'
import { closeBrackets, closeBracketsKeymap, autocompletion, completionKeymap } from '@codemirror/autocomplete'
import { searchKeymap, highlightSelectionMatches } from '@codemirror/search'
import { lintKeymap } from '@codemirror/lint'
const props = defineProps({ modelValue: { type: String, default: '' } })
const emit = defineEmits(['update:modelValue'])
const editorEl = ref(null)
let view = null
onMounted(() => {
view = new EditorView({
doc: props.modelValue,
extensions: [
lineNumbers(),
highlightActiveLineGutter(),
highlightSpecialChars(),
history(),
foldGutter(),
drawSelection(),
EditorState.allowMultipleSelections.of(true),
indentOnInput(),
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
bracketMatching(),
closeBrackets(),
autocompletion(),
rectangularSelection(),
crosshairCursor(),
highlightActiveLine(),
highlightSelectionMatches(),
keymap.of([
...closeBracketsKeymap, ...defaultKeymap, ...searchKeymap,
...historyKeymap, ...foldKeymap, ...completionKeymap, ...lintKeymap
]),
javascript(), // 用 JS 语法高亮近似 Groovy
oneDark,
EditorView.updateListener.of(update => {
if (update.docChanged) {
emit('update:modelValue', update.state.doc.toString())
}
}),
EditorView.theme({ '&': { height: '100%' }, '.cm-scroller': { overflow: 'auto' } })
],
parent: editorEl.value
})
})
watch(() => props.modelValue, val => {
if (view && val !== view.state.doc.toString()) {
view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: val } })
}
})
onUnmounted(() => { view?.destroy() })
</script>