1+ <script setup lang="ts">
2+ import { ref , onMounted , onUnmounted , watch , nextTick } from ' vue'
3+ import * as monaco from ' monaco-editor'
4+
5+ // 定义组件props
6+ interface Props {
7+ modelValue: string
8+ language? : string
9+ theme? : string
10+ options? : monaco .editor .IStandaloneEditorConstructionOptions
11+ debounceTime? : number
12+ }
13+
14+ const props = withDefaults (defineProps <Props >(), {
15+ language: ' html' ,
16+ theme: ' vs-dark' ,
17+ debounceTime: 300 ,
18+ options : () => ({
19+ automaticLayout: true ,
20+ minimap: { enabled: false },
21+ fontSize: 14 ,
22+ fontFamily: ' Monaco, Menlo, "Ubuntu Mono", monospace' ,
23+ lineNumbers: ' on' ,
24+ folding: true ,
25+ wordWrap: ' on' ,
26+ scrollBeyondLastLine: false ,
27+ renderWhitespace: ' boundary' ,
28+ tabSize: 2 ,
29+ insertSpaces: true ,
30+ })
31+ })
32+
33+ // 定义emits
34+ interface Emits {
35+ ' update:modelValue' : [value : string ]
36+ ' change' : [value : string , event : monaco .editor .IModelContentChangedEvent ]
37+ ' ready' : [editor : monaco .editor .IStandaloneCodeEditor ]
38+ ' render-request' : [content : string ]
39+ }
40+
41+ const emit = defineEmits <Emits >()
42+
43+ // 组件状态
44+ const editorContainer = ref <HTMLDivElement >()
45+ let editor: monaco .editor .IStandaloneCodeEditor | null = null
46+ let debounceTimer: number | null = null
47+
48+ // 防抖渲染函数
49+ const debounceRender = (content : string ) => {
50+ if (debounceTimer ) {
51+ clearTimeout (debounceTimer )
52+ }
53+ debounceTimer = window .setTimeout (() => {
54+ emit (' render-request' , content )
55+ }, props .debounceTime )
56+ }
57+
58+ // 初始化Monaco Editor
59+ const initEditor = async () => {
60+ if (! editorContainer .value ) return
61+
62+ // 配置Monaco Editor的worker (简化版本)
63+ self .MonacoEnvironment = {
64+ getWorkerUrl : function (moduleId , label ) {
65+ // 使用CDN版本以确保兼容性
66+ const BASE_URL = ' https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs'
67+
68+ if (label === ' json' ) {
69+ return ` ${BASE_URL }/language/json/json.worker.js `
70+ }
71+ if (label === ' css' || label === ' scss' || label === ' less' ) {
72+ return ` ${BASE_URL }/language/css/css.worker.js `
73+ }
74+ if (label === ' html' || label === ' handlebars' || label === ' razor' ) {
75+ return ` ${BASE_URL }/language/html/html.worker.js `
76+ }
77+ if (label === ' typescript' || label === ' javascript' ) {
78+ return ` ${BASE_URL }/language/typescript/ts.worker.js `
79+ }
80+ return ` ${BASE_URL }/editor/editor.worker.js `
81+ }
82+ }
83+
84+ // 创建editor实例
85+ editor = monaco .editor .create (editorContainer .value , {
86+ value: props .modelValue ,
87+ language: props .language ,
88+ theme: props .theme ,
89+ ... props .options ,
90+ })
91+
92+ // 监听内容变化
93+ editor .onDidChangeModelContent ((event ) => {
94+ const value = editor ! .getValue ()
95+ emit (' update:modelValue' , value )
96+ emit (' change' , value , event )
97+
98+ // 触发防抖渲染
99+ debounceRender (value )
100+ })
101+
102+ // 设置HTML语言的特殊配置
103+ if (props .language === ' html' ) {
104+ monaco .languages .html .htmlDefaults .setOptions ({
105+ format: {
106+ tabSize: 2 ,
107+ insertSpaces: true ,
108+ wrapLineLength: 80 ,
109+ unformatted: ' default' ,
110+ indentInnerHtml: false ,
111+ preserveNewLines: true ,
112+ maxPreserveNewLines: null ,
113+ indentHandlebars: false ,
114+ endWithNewline: false ,
115+ extraLiners: ' head, body, /html' ,
116+ wrapAttributes: ' auto'
117+ },
118+ suggest: {
119+ html5: true ,
120+ angular1: false ,
121+ ionic: false
122+ }
123+ })
124+ }
125+
126+ // 触发ready事件
127+ emit (' ready' , editor )
128+
129+ // 初始渲染
130+ if (props .modelValue ) {
131+ debounceRender (props .modelValue )
132+ }
133+ }
134+
135+ // 监听modelValue变化
136+ watch (() => props .modelValue , (newValue ) => {
137+ if (editor && editor .getValue () !== newValue ) {
138+ editor .setValue (newValue )
139+ }
140+ })
141+
142+ // 监听语言变化
143+ watch (() => props .language , (newLanguage ) => {
144+ if (editor ) {
145+ const model = editor .getModel ()
146+ if (model ) {
147+ monaco .editor .setModelLanguage (model , newLanguage )
148+ }
149+ }
150+ })
151+
152+ // 监听主题变化
153+ watch (() => props .theme , (newTheme ) => {
154+ if (editor ) {
155+ monaco .editor .setTheme (newTheme )
156+ }
157+ })
158+
159+ // 暴露方法给父组件
160+ const focus = () => {
161+ editor ?.focus ()
162+ }
163+
164+ const getEditor = () => {
165+ return editor
166+ }
167+
168+ const setValue = (value : string ) => {
169+ editor ?.setValue (value )
170+ }
171+
172+ const getValue = () => {
173+ return editor ?.getValue () || ' '
174+ }
175+
176+ const triggerRender = () => {
177+ const content = getValue ()
178+ emit (' render-request' , content )
179+ }
180+
181+ defineExpose ({
182+ focus ,
183+ getEditor ,
184+ setValue ,
185+ getValue ,
186+ triggerRender
187+ })
188+
189+ // 生命周期
190+ onMounted (async () => {
191+ await nextTick ()
192+ await initEditor ()
193+ })
194+
195+ onUnmounted (() => {
196+ if (debounceTimer ) {
197+ clearTimeout (debounceTimer )
198+ }
199+ if (editor ) {
200+ editor .dispose ()
201+ editor = null
202+ }
203+ })
204+ </script >
205+
206+ <template >
207+ <div ref =" editorContainer" class =" monaco-editor-container" />
208+ </template >
209+
210+ <style scoped>
211+ .monaco-editor-container {
212+ width : 100% ;
213+ height : 100% ;
214+ min-height : 200px ;
215+ }
216+
217+ /* 修复Monaco Editor在玻璃效果容器中的显示问题 */
218+ :deep(.monaco-editor ) {
219+ background : transparent !important ;
220+ }
221+
222+ :deep(.monaco-editor .margin ) {
223+ background : rgba (0 , 0 , 0 , 0.2 ) !important ;
224+ }
225+
226+ :deep(.monaco-editor .monaco-editor-background ) {
227+ background : rgba (0 , 0 , 0 , 0.3 ) !important ;
228+ }
229+
230+ :deep(.monaco-editor .current-line ) {
231+ background : rgba (255 , 255 , 255 , 0.05 ) !important ;
232+ }
233+
234+ :deep(.monaco-editor .line-numbers ) {
235+ color : var (--text-muted ) !important ;
236+ }
237+
238+ :deep(.monaco-editor .monaco-placeholder ) {
239+ color : var (--text-secondary ) !important ;
240+ }
241+ </style >
0 commit comments