核心优化: - 新增统一导出避免多实例问题 - 语言加载器从动态改为静态导入 - 使用 Compartment 实现主题/语言动态切换 依赖清理: - 移除废弃的 @codemirror/highlight - 移除不再使用的 @codemirror/legacy-modes 组件优化: - CodeEditor 添加内容更新防抖 - 改进亮色主题样式 - 移除不必要的编辑器重建逻辑 构建配置: - 简化 Vite manualChunks 配置 - 优化依赖预加载列表 文档清理: - 删除过期的代码审查文档 - 更新版本号 0.3.0 → 0.3.2
220 lines
5.5 KiB
Vue
220 lines
5.5 KiB
Vue
<template>
|
||
<div ref="editorContainer" class="codemirror-editor"></div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted, watch, onBeforeUnmount, computed, nextTick } from 'vue'
|
||
import {
|
||
EditorView, lineNumbers, highlightActiveLineGutter, keymap,
|
||
EditorState, Compartment,
|
||
defaultKeymap, history,
|
||
bracketMatching, defaultHighlightStyle, syntaxHighlighting,
|
||
oneDark
|
||
} from '@/utils/codemirrorExports'
|
||
import { useThemeStore } from '@/stores/theme'
|
||
import { loadLanguageExtension, getLanguageFromExtension } from '@/utils/codeMirrorLoader'
|
||
|
||
// ==================== 主题定义 ====================
|
||
|
||
// 亮色主题的基础样式
|
||
const lightTheme = EditorView.theme({
|
||
'&': { backgroundColor: '#ffffff' },
|
||
'.cm-gutters': { backgroundColor: '#f7f7f7', color: '#999', border: 'none' },
|
||
'.cm-activeLineGutter': { backgroundColor: '#e8e8e8', color: '#333' },
|
||
'.cm-line': { caretColor: '#000' },
|
||
'.cm-selection': { backgroundColor: '#d9d9d9' },
|
||
'.cm-cursor': { borderLeftColor: '#000' }
|
||
})
|
||
|
||
// ==================== Props & Emits ====================
|
||
|
||
const props = defineProps({
|
||
modelValue: { type: String, required: true },
|
||
fileExtension: { type: String, default: '' }
|
||
})
|
||
|
||
const emit = defineEmits(['update:modelValue'])
|
||
|
||
// ==================== 状态管理 ====================
|
||
|
||
const themeStore = useThemeStore()
|
||
const editorContainer = ref(null)
|
||
let view = null
|
||
|
||
// 使用 Compartment 实现动态切换,避免重建编辑器
|
||
const themeCompartment = new Compartment()
|
||
const languageCompartment = new Compartment()
|
||
|
||
// ==================== 防抖处理 ====================
|
||
|
||
let emitTimeout = null
|
||
const debouncedEmit = (value) => {
|
||
if (emitTimeout) {
|
||
clearTimeout(emitTimeout)
|
||
}
|
||
emitTimeout = setTimeout(() => {
|
||
emit('update:modelValue', value)
|
||
}, 150)
|
||
}
|
||
|
||
// 获取当前主题扩展
|
||
const getThemeExtension = () => {
|
||
if (themeStore.isDark) {
|
||
return [oneDark]
|
||
} else {
|
||
// 亮色主题:使用默认语法高亮样式
|
||
return [
|
||
EditorView.theme({
|
||
'&': { backgroundColor: '#ffffff' },
|
||
'.cm-gutters': { backgroundColor: '#f7f7f7', color: '#999', border: 'none' },
|
||
'.cm-activeLineGutter': { backgroundColor: '#e8e8e8', color: '#333' },
|
||
'.cm-line': { caretColor: '#000' },
|
||
'.cm-selection': { backgroundColor: '#d9d9d9' },
|
||
'.cm-cursor': { borderLeftColor: '#000' }
|
||
}),
|
||
syntaxHighlighting(defaultHighlightStyle)
|
||
]
|
||
}
|
||
}
|
||
|
||
// ==================== 扩展配置 ====================
|
||
|
||
const createExtensions = () => {
|
||
const extensions = [
|
||
lineNumbers(),
|
||
highlightActiveLineGutter(),
|
||
history(),
|
||
keymap.of(defaultKeymap),
|
||
bracketMatching(),
|
||
|
||
// 内容更新监听(带防抖)
|
||
EditorView.updateListener.of((update) => {
|
||
if (update.docChanged) {
|
||
debouncedEmit(update.state.doc.toString())
|
||
}
|
||
}),
|
||
|
||
// 基础样式
|
||
EditorView.theme({
|
||
'&': { height: '100%', fontSize: '13px' },
|
||
'.cm-scroller': { overflow: 'auto', fontFamily: 'Consolas, Monaco, Courier New, monospace' },
|
||
'.cm-content': { padding: '8px', minHeight: '100%' },
|
||
'.cm-line': { padding: '0 0' },
|
||
'&.cm-focused': { outline: 'none' }
|
||
}),
|
||
|
||
// 使用 Compartment 支持动态切换主题
|
||
themeCompartment.of(getThemeExtension()),
|
||
|
||
// 使用 Compartment 支持动态切换语言
|
||
languageCompartment.of([])
|
||
]
|
||
|
||
return extensions
|
||
}
|
||
|
||
// ==================== 语言管理 ====================
|
||
|
||
const initLanguage = async () => {
|
||
const language = getLanguageFromExtension(props.fileExtension)
|
||
if (language === 'text') return
|
||
|
||
try {
|
||
const langExtension = await loadLanguageExtension(language)
|
||
if (langExtension && view) {
|
||
view.dispatch({
|
||
effects: languageCompartment.reconfigure(langExtension)
|
||
})
|
||
}
|
||
} catch (error) {
|
||
console.warn(`[CodeEditor] 加载语言包失败: ${language}`, error)
|
||
}
|
||
}
|
||
|
||
// ==================== 编辑器创建 ====================
|
||
|
||
const createEditor = (docContent = '') => {
|
||
if (!editorContainer.value) return
|
||
|
||
const state = EditorState.create({
|
||
doc: docContent,
|
||
extensions: createExtensions()
|
||
})
|
||
|
||
view = new EditorView({ state, parent: editorContainer.value })
|
||
|
||
// 初始化语言
|
||
initLanguage()
|
||
}
|
||
|
||
// ==================== 生命周期 ====================
|
||
|
||
onMounted(() => {
|
||
createEditor(props.modelValue || '')
|
||
|
||
// 确保主题正确应用(在下一 tick)
|
||
nextTick(() => {
|
||
if (view) {
|
||
view.dispatch({
|
||
effects: themeCompartment.reconfigure(getThemeExtension())
|
||
})
|
||
}
|
||
})
|
||
})
|
||
|
||
onBeforeUnmount(() => {
|
||
if (emitTimeout) {
|
||
clearTimeout(emitTimeout)
|
||
}
|
||
view?.destroy()
|
||
view = null
|
||
})
|
||
|
||
// ==================== 监听器 ====================
|
||
|
||
// 监听外部内容变化
|
||
watch(() => props.modelValue, (newValue) => {
|
||
if (view && newValue !== view.state.doc.toString()) {
|
||
view.dispatch({
|
||
changes: { from: 0, to: view.state.doc.length, insert: newValue || '' }
|
||
})
|
||
}
|
||
})
|
||
|
||
// 监听主题变化(使用 Compartment 重建,不丢失状态)
|
||
watch(() => themeStore.isDark, () => {
|
||
if (view) {
|
||
view.dispatch({
|
||
effects: themeCompartment.reconfigure(getThemeExtension())
|
||
})
|
||
}
|
||
})
|
||
|
||
// 监听文件扩展名变化(重新加载语言)
|
||
watch(() => props.fileExtension, () => {
|
||
initLanguage()
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.codemirror-editor {
|
||
height: 100%;
|
||
width: 100%;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.codemirror-editor :deep(.cm-editor) {
|
||
height: 100%;
|
||
width: 100%;
|
||
}
|
||
|
||
.codemirror-editor :deep(.cm-scroller) {
|
||
overflow: auto;
|
||
height: 100%;
|
||
}
|
||
|
||
.codemirror-editor :deep(.cm-content) {
|
||
height: 100%;
|
||
}
|
||
</style>
|