mirror of
https://github.com/acepanel/panel.git
synced 2026-02-07 09:27:15 +08:00
feat: 使用全新的方式加载monaco
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import '@/utils/ace'
|
||||
import { VAceEditor } from 'vue3-ace-editor'
|
||||
import { useThemeStore } from '@/store'
|
||||
import { getMonaco } from '@/utils/monaco'
|
||||
import type * as Monaco from 'monaco-editor'
|
||||
|
||||
const value = defineModel<string>('value', { type: String, required: true })
|
||||
const props = defineProps({
|
||||
@@ -19,17 +20,105 @@ const props = defineProps({
|
||||
required: false
|
||||
}
|
||||
})
|
||||
|
||||
const containerRef = ref<HTMLDivElement>()
|
||||
const editorRef = shallowRef<Monaco.editor.IStandaloneCodeEditor>()
|
||||
const monacoRef = shallowRef<typeof Monaco>()
|
||||
const loading = ref(true)
|
||||
|
||||
const themeStore = useThemeStore()
|
||||
|
||||
async function initEditor() {
|
||||
if (!containerRef.value) return
|
||||
|
||||
const monaco = await getMonaco(themeStore.locale)
|
||||
monacoRef.value = monaco
|
||||
|
||||
editorRef.value = monaco.editor.create(containerRef.value, {
|
||||
value: value.value,
|
||||
language: props.lang,
|
||||
theme: 'vs-dark',
|
||||
readOnly: props.readOnly,
|
||||
automaticLayout: true,
|
||||
smoothScrolling: true,
|
||||
formatOnPaste: true,
|
||||
formatOnType: true
|
||||
})
|
||||
|
||||
editorRef.value.onDidChangeModelContent(() => {
|
||||
const newValue = editorRef.value?.getValue() ?? ''
|
||||
if (newValue !== value.value) {
|
||||
value.value = newValue
|
||||
}
|
||||
})
|
||||
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
watch(value, (newValue) => {
|
||||
if (editorRef.value && editorRef.value.getValue() !== newValue) {
|
||||
editorRef.value.setValue(newValue)
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.lang,
|
||||
(newLang) => {
|
||||
if (editorRef.value && monacoRef.value) {
|
||||
const model = editorRef.value.getModel()
|
||||
if (model) {
|
||||
monacoRef.value.editor.setModelLanguage(model, newLang)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.readOnly,
|
||||
(newReadOnly) => {
|
||||
if (editorRef.value) {
|
||||
editorRef.value.updateOptions({ readOnly: newReadOnly })
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
initEditor()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
editorRef.value?.dispose()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-ace-editor
|
||||
v-model:value="value"
|
||||
:lang="props.lang"
|
||||
:options="{ useWorker: true }"
|
||||
:readonly="props.readOnly"
|
||||
theme="monokai"
|
||||
:style="{ height: props.height }"
|
||||
/>
|
||||
<div class="common-editor" :style="{ height: props.height }">
|
||||
<div v-if="loading" class="editor-loading">
|
||||
<n-spin size="medium" />
|
||||
</div>
|
||||
<div ref="containerRef" class="editor-container" :style="{ height: props.height }" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
<style scoped lang="scss">
|
||||
.common-editor {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.editor-loading {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.editor-container {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import file from '@/api/panel/file'
|
||||
import { useThemeStore } from '@/store'
|
||||
import { decodeBase64 } from '@/utils'
|
||||
import '@/utils/ace'
|
||||
import { languageByPath } from '@/utils/file'
|
||||
import { VAceEditor } from 'vue3-ace-editor'
|
||||
import { getMonaco } from '@/utils/monaco'
|
||||
import type * as Monaco from 'monaco-editor'
|
||||
import { onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
|
||||
const { $gettext } = useGettext()
|
||||
@@ -18,9 +20,59 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
const themeStore = useThemeStore()
|
||||
const containerRef = ref<HTMLDivElement>()
|
||||
const editorRef = shallowRef<Monaco.editor.IStandaloneCodeEditor>()
|
||||
const monacoRef = shallowRef<typeof Monaco>()
|
||||
const loading = ref(true)
|
||||
|
||||
const disabled = ref(false) // 在出现错误的情况下禁用保存
|
||||
const content = ref('')
|
||||
|
||||
async function initEditor() {
|
||||
if (!containerRef.value) return
|
||||
|
||||
const monaco = await getMonaco(themeStore.locale)
|
||||
monacoRef.value = monaco
|
||||
|
||||
editorRef.value = monaco.editor.create(containerRef.value, {
|
||||
value: content.value,
|
||||
language: languageByPath(props.path),
|
||||
theme:
|
||||
(languageByPath(props.path) == 'nginx' ? 'nginx-theme' : 'vs') +
|
||||
(themeStore.darkMode ? '-dark' : ''),
|
||||
readOnly: props.readOnly,
|
||||
automaticLayout: true,
|
||||
smoothScrolling: true,
|
||||
formatOnPaste: true,
|
||||
formatOnType: true
|
||||
})
|
||||
|
||||
editorRef.value.onDidChangeModelContent(() => {
|
||||
const newValue = editorRef.value?.getValue() ?? ''
|
||||
if (newValue !== content.value) {
|
||||
content.value = newValue
|
||||
}
|
||||
})
|
||||
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
watch(content, (newValue) => {
|
||||
if (editorRef.value && editorRef.value.getValue() !== newValue) {
|
||||
editorRef.value.setValue(newValue)
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.readOnly,
|
||||
(newReadOnly) => {
|
||||
if (editorRef.value) {
|
||||
editorRef.value.updateOptions({ readOnly: newReadOnly })
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const get = () => {
|
||||
useRequest(file.content(encodeURIComponent(props.path)))
|
||||
.onSuccess(({ data }) => {
|
||||
@@ -44,6 +96,11 @@ const save = () => {
|
||||
|
||||
onMounted(() => {
|
||||
get()
|
||||
initEditor()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
editorRef.value?.dispose()
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
@@ -53,13 +110,33 @@ defineExpose({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-ace-editor
|
||||
v-model:value="content"
|
||||
:lang="languageByPath(props.path)"
|
||||
:options="{ useWorker: true }"
|
||||
theme="monokai"
|
||||
style="height: 60vh"
|
||||
/>
|
||||
<div class="file-editor" style="height: 60vh">
|
||||
<div v-if="loading" class="editor-loading">
|
||||
<n-spin size="medium" />
|
||||
</div>
|
||||
<div ref="containerRef" class="editor-container" style="height: 60vh" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
<style scoped lang="scss">
|
||||
.file-editor {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.editor-loading {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.editor-container {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -160,6 +160,9 @@ const languageByPath = (path: string) => {
|
||||
return 'html'
|
||||
case 'ini':
|
||||
case 'conf':
|
||||
if (path.toLowerCase().includes('nginx')) {
|
||||
return 'nginx'
|
||||
}
|
||||
return 'ini'
|
||||
case 'java':
|
||||
return 'java'
|
||||
|
||||
115
web/src/utils/monaco/index.ts
Normal file
115
web/src/utils/monaco/index.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import type * as Monaco from 'monaco-editor'
|
||||
|
||||
let monacoInstance: typeof Monaco | null = null
|
||||
let isInitialized = false
|
||||
let initPromise: Promise<typeof Monaco> | null = null
|
||||
|
||||
async function loadMonacoLocale(locale: string) {
|
||||
switch (locale) {
|
||||
case 'cs':
|
||||
await import('monaco-editor/esm/nls.messages.cs.js')
|
||||
break
|
||||
case 'de':
|
||||
await import('monaco-editor/esm/nls.messages.de.js')
|
||||
break
|
||||
case 'es':
|
||||
await import('monaco-editor/esm/nls.messages.es.js')
|
||||
break
|
||||
case 'fr':
|
||||
await import('monaco-editor/esm/nls.messages.fr.js')
|
||||
break
|
||||
case 'it':
|
||||
await import('monaco-editor/esm/nls.messages.it.js')
|
||||
break
|
||||
case 'ja':
|
||||
await import('monaco-editor/esm/nls.messages.ja.js')
|
||||
break
|
||||
case 'ko':
|
||||
await import('monaco-editor/esm/nls.messages.ko.js')
|
||||
break
|
||||
case 'pl':
|
||||
await import('monaco-editor/esm/nls.messages.pl.js')
|
||||
break
|
||||
case 'pt_BR':
|
||||
await import('monaco-editor/esm/nls.messages.pt-br.js')
|
||||
break
|
||||
case 'ru':
|
||||
await import('monaco-editor/esm/nls.messages.ru.js')
|
||||
break
|
||||
case 'tr':
|
||||
await import('monaco-editor/esm/nls.messages.tr.js')
|
||||
break
|
||||
case 'zh_CN':
|
||||
await import('monaco-editor/esm/nls.messages.zh-cn.js')
|
||||
break
|
||||
case 'zh_TW':
|
||||
await import('monaco-editor/esm/nls.messages.zh-tw.js')
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
async function setupMonacoWorkers() {
|
||||
if (self.MonacoEnvironment) return
|
||||
|
||||
const [editorWorker, jsonWorker, cssWorker, htmlWorker, tsWorker] = await Promise.all([
|
||||
import('monaco-editor/esm/vs/editor/editor.worker?worker'),
|
||||
import('monaco-editor/esm/vs/language/json/json.worker?worker'),
|
||||
import('monaco-editor/esm/vs/language/css/css.worker?worker'),
|
||||
import('monaco-editor/esm/vs/language/html/html.worker?worker'),
|
||||
import('monaco-editor/esm/vs/language/typescript/ts.worker?worker')
|
||||
])
|
||||
|
||||
self.MonacoEnvironment = {
|
||||
getWorker(_: any, label: string) {
|
||||
if (label === 'json') {
|
||||
return new jsonWorker.default()
|
||||
}
|
||||
if (label === 'css' || label === 'scss' || label === 'less') {
|
||||
return new cssWorker.default()
|
||||
}
|
||||
if (label === 'html' || label === 'handlebars' || label === 'razor') {
|
||||
return new htmlWorker.default()
|
||||
}
|
||||
if (label === 'typescript' || label === 'javascript') {
|
||||
return new tsWorker.default()
|
||||
}
|
||||
return new editorWorker.default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Monaco 实例
|
||||
* @param locale 可选的语言设置
|
||||
* @returns Monaco 实例
|
||||
*/
|
||||
export async function getMonaco(locale?: string): Promise<typeof Monaco> {
|
||||
if (isInitialized && monacoInstance) {
|
||||
return monacoInstance
|
||||
}
|
||||
|
||||
if (initPromise) {
|
||||
return initPromise
|
||||
}
|
||||
|
||||
initPromise = (async () => {
|
||||
if (locale) {
|
||||
await loadMonacoLocale(locale)
|
||||
}
|
||||
|
||||
await setupMonacoWorkers()
|
||||
|
||||
const monaco = await import('monaco-editor')
|
||||
await import('monaco-editor-nginx')
|
||||
monacoInstance = monaco
|
||||
isInitialized = true
|
||||
|
||||
return monaco
|
||||
})()
|
||||
|
||||
return initPromise
|
||||
}
|
||||
|
||||
export type { Monaco }
|
||||
Reference in New Issue
Block a user