mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 00:49:22 +08:00
feat: 使用全新的方式加载monaco
This commit is contained in:
@@ -37,7 +37,6 @@
|
||||
"@xterm/addon-web-links": "^0.12.0",
|
||||
"@xterm/addon-webgl": "^0.19.0",
|
||||
"@xterm/xterm": "^6.0.0",
|
||||
"ace-builds": "^1.43.5",
|
||||
"alova": "^3.3.4",
|
||||
"echarts": "^6.0.0",
|
||||
"install": "^0.13.0",
|
||||
@@ -45,6 +44,8 @@
|
||||
"luxon": "^3.7.2",
|
||||
"marked": "^17.0.0",
|
||||
"mitt": "^3.0.1",
|
||||
"monaco-editor": "^0.55.1",
|
||||
"monaco-editor-nginx": "^2.0.2",
|
||||
"node-forge": "^1.3.1",
|
||||
"pinia": "^3.0.3",
|
||||
"pinia-plugin-persistedstate": "^4.5.0",
|
||||
@@ -52,7 +53,6 @@
|
||||
"vue": "^3.5.22",
|
||||
"vue-echarts": "^8.0.1",
|
||||
"vue-router": "^4.6.3",
|
||||
"vue3-ace-editor": "^2.2.4",
|
||||
"vue3-gettext": "4.0.0-beta.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
111
web/pnpm-lock.yaml
generated
111
web/pnpm-lock.yaml
generated
@@ -44,9 +44,6 @@ importers:
|
||||
'@xterm/xterm':
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0
|
||||
ace-builds:
|
||||
specifier: ^1.43.5
|
||||
version: 1.43.5
|
||||
alova:
|
||||
specifier: ^3.3.4
|
||||
version: 3.4.1
|
||||
@@ -68,6 +65,12 @@ importers:
|
||||
mitt:
|
||||
specifier: ^3.0.1
|
||||
version: 3.0.1
|
||||
monaco-editor:
|
||||
specifier: ^0.55.1
|
||||
version: 0.55.1
|
||||
monaco-editor-nginx:
|
||||
specifier: ^2.0.2
|
||||
version: 2.0.2(@babel/runtime@7.28.4)(@nginx/reference-lib@1.1.25)(monaco-editor@0.55.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
node-forge:
|
||||
specifier: ^1.3.1
|
||||
version: 1.3.3
|
||||
@@ -89,9 +92,6 @@ importers:
|
||||
vue-router:
|
||||
specifier: ^4.6.3
|
||||
version: 4.6.4(vue@3.5.26(typescript@5.9.3))
|
||||
vue3-ace-editor:
|
||||
specifier: ^2.2.4
|
||||
version: 2.2.4(ace-builds@1.43.5)(vue@3.5.26(typescript@5.9.3))
|
||||
vue3-gettext:
|
||||
specifier: 4.0.0-beta.1
|
||||
version: 4.0.0-beta.1(@vue/compiler-sfc@3.5.26)(vue@3.5.26(typescript@5.9.3))
|
||||
@@ -345,6 +345,10 @@ packages:
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0-0
|
||||
|
||||
'@babel/runtime@7.28.4':
|
||||
resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/template@7.27.2':
|
||||
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -779,6 +783,9 @@ packages:
|
||||
'@marijn/find-cluster-break@1.0.2':
|
||||
resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==}
|
||||
|
||||
'@nginx/reference-lib@1.1.25':
|
||||
resolution: {integrity: sha512-y8g/3Z17VKCvnGB2KRtAvlcEWvRUaO0Ln5MIAdBsqX6qsfMIDznFt0lnFQMVZM51AQgQeY4TP+nWTOiKe6+wsQ==}
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -1039,6 +1046,9 @@ packages:
|
||||
'@types/node@24.10.4':
|
||||
resolution: {integrity: sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==}
|
||||
|
||||
'@types/trusted-types@2.0.7':
|
||||
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
|
||||
|
||||
'@types/web-bluetooth@0.0.21':
|
||||
resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
|
||||
|
||||
@@ -1350,9 +1360,6 @@ packages:
|
||||
'@xterm/xterm@6.0.0':
|
||||
resolution: {integrity: sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg==}
|
||||
|
||||
ace-builds@1.43.5:
|
||||
resolution: {integrity: sha512-iH5FLBKdB7SVn9GR37UgA/tpQS8OTWIxWAuq3Ofaw+Qbc69FfPXsXd9jeW7KRG2xKpKMqBDnu0tHBrCWY5QI7A==}
|
||||
|
||||
acorn-jsx@5.3.2:
|
||||
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
||||
peerDependencies:
|
||||
@@ -1716,6 +1723,9 @@ packages:
|
||||
resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
dompurify@3.2.7:
|
||||
resolution: {integrity: sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==}
|
||||
|
||||
domutils@2.8.0:
|
||||
resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==}
|
||||
|
||||
@@ -2414,6 +2424,11 @@ packages:
|
||||
resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
|
||||
hasBin: true
|
||||
|
||||
marked@14.0.0:
|
||||
resolution: {integrity: sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==}
|
||||
engines: {node: '>= 18'}
|
||||
hasBin: true
|
||||
|
||||
marked@17.0.1:
|
||||
resolution: {integrity: sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==}
|
||||
engines: {node: '>= 20'}
|
||||
@@ -2481,6 +2496,18 @@ packages:
|
||||
resolution: {integrity: sha512-eQsKcWzIaZzEZ07NuEyO4Nw65g0hdWAyurVol1IPl1gahRwY+svqzfgfey8U8dahLwG44d6/RwEzuK52rSa/JQ==}
|
||||
hasBin: true
|
||||
|
||||
monaco-editor-nginx@2.0.2:
|
||||
resolution: {integrity: sha512-F/qejb0w0hxIyxbu2JMCe+MhnOV7bxGbUynnyMXk7Cy25Na5fD99u5QqRr7WfRhIn0xcdhBEkJ/jikQsxVnEOA==}
|
||||
peerDependencies:
|
||||
'@babel/runtime': '>=7.10.0'
|
||||
'@nginx/reference-lib': '>=1.1.0'
|
||||
monaco-editor: '>=0.22.3'
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
|
||||
monaco-editor@0.55.1:
|
||||
resolution: {integrity: sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==}
|
||||
|
||||
mrmime@2.0.1:
|
||||
resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -2749,6 +2776,15 @@ packages:
|
||||
rate-limiter-flexible@5.0.5:
|
||||
resolution: {integrity: sha512-+/dSQfo+3FYwYygUs/V2BBdwGa9nFtakDwKt4l0bnvNB53TNT++QSFewwHX9qXrZJuMe9j+TUaU21lm5ARgqdQ==}
|
||||
|
||||
react-dom@19.2.3:
|
||||
resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==}
|
||||
peerDependencies:
|
||||
react: ^19.2.3
|
||||
|
||||
react@19.2.3:
|
||||
resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
read-package-json-fast@4.0.0:
|
||||
resolution: {integrity: sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==}
|
||||
engines: {node: ^18.17.0 || >=20.5.0}
|
||||
@@ -2780,9 +2816,6 @@ packages:
|
||||
remove@0.1.5:
|
||||
resolution: {integrity: sha512-AJMA9oWvJzdTjwIGwSQZsjGQiRx73YTmiOWmfCp1fpLa/D4n7jKcpoA+CZiVLJqKcEKUuh1Suq80c5wF+L/qVQ==}
|
||||
|
||||
resize-observer-polyfill@1.5.1:
|
||||
resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==}
|
||||
|
||||
resolve-from@4.0.0:
|
||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -2834,6 +2867,9 @@ packages:
|
||||
engines: {node: '>=14.0.0'}
|
||||
hasBin: true
|
||||
|
||||
scheduler@0.27.0:
|
||||
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
|
||||
|
||||
scule@1.3.0:
|
||||
resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==}
|
||||
|
||||
@@ -3306,12 +3342,6 @@ packages:
|
||||
peerDependencies:
|
||||
typescript: '>=5.0.0'
|
||||
|
||||
vue3-ace-editor@2.2.4:
|
||||
resolution: {integrity: sha512-FZkEyfpbH068BwjhMyNROxfEI8135Sc+x8ouxkMdCNkuj/Tuw83VP/gStFQqZHqljyX9/VfMTCdTqtOnJZGN8g==}
|
||||
peerDependencies:
|
||||
ace-builds: '*'
|
||||
vue: ^3
|
||||
|
||||
vue3-gettext@4.0.0-beta.1:
|
||||
resolution: {integrity: sha512-1A46SmubgTMyy7i5hj8ay50NFl6/vzwoIVZPuGCin/X3a/NVCAs99G0EbcnfJiR7NZNTJgUjvBzppufC7Kq+4A==}
|
||||
engines: {node: '>= 20.19.0'}
|
||||
@@ -3586,6 +3616,8 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@babel/runtime@7.28.4': {}
|
||||
|
||||
'@babel/template@7.27.2':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.27.1
|
||||
@@ -4169,6 +4201,8 @@ snapshots:
|
||||
|
||||
'@marijn/find-cluster-break@1.0.2': {}
|
||||
|
||||
'@nginx/reference-lib@1.1.25': {}
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
dependencies:
|
||||
'@nodelib/fs.stat': 2.0.5
|
||||
@@ -4358,6 +4392,9 @@ snapshots:
|
||||
dependencies:
|
||||
undici-types: 7.16.0
|
||||
|
||||
'@types/trusted-types@2.0.7':
|
||||
optional: true
|
||||
|
||||
'@types/web-bluetooth@0.0.21': {}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.50.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||
@@ -4836,8 +4873,6 @@ snapshots:
|
||||
|
||||
'@xterm/xterm@6.0.0': {}
|
||||
|
||||
ace-builds@1.43.5: {}
|
||||
|
||||
acorn-jsx@5.3.2(acorn@8.15.0):
|
||||
dependencies:
|
||||
acorn: 8.15.0
|
||||
@@ -5199,6 +5234,10 @@ snapshots:
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
|
||||
dompurify@3.2.7:
|
||||
optionalDependencies:
|
||||
'@types/trusted-types': 2.0.7
|
||||
|
||||
domutils@2.8.0:
|
||||
dependencies:
|
||||
dom-serializer: 1.4.1
|
||||
@@ -5952,6 +5991,8 @@ snapshots:
|
||||
punycode.js: 2.3.1
|
||||
uc.micro: 2.1.0
|
||||
|
||||
marked@14.0.0: {}
|
||||
|
||||
marked@17.0.1: {}
|
||||
|
||||
math-intrinsics@1.1.0: {}
|
||||
@@ -6029,6 +6070,19 @@ snapshots:
|
||||
dependencies:
|
||||
commander: 14.0.2
|
||||
|
||||
monaco-editor-nginx@2.0.2(@babel/runtime@7.28.4)(@nginx/reference-lib@1.1.25)(monaco-editor@0.55.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@nginx/reference-lib': 1.1.25
|
||||
monaco-editor: 0.55.1
|
||||
react: 19.2.3
|
||||
react-dom: 19.2.3(react@19.2.3)
|
||||
|
||||
monaco-editor@0.55.1:
|
||||
dependencies:
|
||||
dompurify: 3.2.7
|
||||
marked: 14.0.0
|
||||
|
||||
mrmime@2.0.1: {}
|
||||
|
||||
ms@2.0.0: {}
|
||||
@@ -6279,6 +6333,13 @@ snapshots:
|
||||
|
||||
rate-limiter-flexible@5.0.5: {}
|
||||
|
||||
react-dom@19.2.3(react@19.2.3):
|
||||
dependencies:
|
||||
react: 19.2.3
|
||||
scheduler: 0.27.0
|
||||
|
||||
react@19.2.3: {}
|
||||
|
||||
read-package-json-fast@4.0.0:
|
||||
dependencies:
|
||||
json-parse-even-better-errors: 4.0.0
|
||||
@@ -6318,8 +6379,6 @@ snapshots:
|
||||
dependencies:
|
||||
seq: 0.3.5
|
||||
|
||||
resize-observer-polyfill@1.5.1: {}
|
||||
|
||||
resolve-from@4.0.0: {}
|
||||
|
||||
resolve-pkg-maps@1.0.0: {}
|
||||
@@ -6397,6 +6456,8 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@parcel/watcher': 2.5.1
|
||||
|
||||
scheduler@0.27.0: {}
|
||||
|
||||
scule@1.3.0: {}
|
||||
|
||||
seemly@0.3.10: {}
|
||||
@@ -6953,12 +7014,6 @@ snapshots:
|
||||
'@vue/language-core': 3.2.1
|
||||
typescript: 5.9.3
|
||||
|
||||
vue3-ace-editor@2.2.4(ace-builds@1.43.5)(vue@3.5.26(typescript@5.9.3)):
|
||||
dependencies:
|
||||
ace-builds: 1.43.5
|
||||
resize-observer-polyfill: 1.5.1
|
||||
vue: 3.5.26(typescript@5.9.3)
|
||||
|
||||
vue3-gettext@4.0.0-beta.1(@vue/compiler-sfc@3.5.26)(vue@3.5.26(typescript@5.9.3)):
|
||||
dependencies:
|
||||
'@vue/compiler-sfc': 3.5.26
|
||||
|
||||
@@ -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 }
|
||||
36
web/types/monaco.d.ts
vendored
Normal file
36
web/types/monaco.d.ts
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
// Monaco Editor 本地化模块声明
|
||||
declare module 'monaco-editor/esm/nls.messages.cs.js'
|
||||
declare module 'monaco-editor/esm/nls.messages.de.js'
|
||||
declare module 'monaco-editor/esm/nls.messages.es.js'
|
||||
declare module 'monaco-editor/esm/nls.messages.fr.js'
|
||||
declare module 'monaco-editor/esm/nls.messages.it.js'
|
||||
declare module 'monaco-editor/esm/nls.messages.ja.js'
|
||||
declare module 'monaco-editor/esm/nls.messages.ko.js'
|
||||
declare module 'monaco-editor/esm/nls.messages.pl.js'
|
||||
declare module 'monaco-editor/esm/nls.messages.pt-br.js'
|
||||
declare module 'monaco-editor/esm/nls.messages.ru.js'
|
||||
declare module 'monaco-editor/esm/nls.messages.tr.js'
|
||||
declare module 'monaco-editor/esm/nls.messages.zh-cn.js'
|
||||
declare module 'monaco-editor/esm/nls.messages.zh-tw.js'
|
||||
|
||||
// Monaco Editor Worker 模块声明
|
||||
declare module 'monaco-editor/esm/vs/editor/editor.worker?worker' {
|
||||
const EditorWorker: new () => Worker
|
||||
export default EditorWorker
|
||||
}
|
||||
declare module 'monaco-editor/esm/vs/language/json/json.worker?worker' {
|
||||
const JsonWorker: new () => Worker
|
||||
export default JsonWorker
|
||||
}
|
||||
declare module 'monaco-editor/esm/vs/language/css/css.worker?worker' {
|
||||
const CssWorker: new () => Worker
|
||||
export default CssWorker
|
||||
}
|
||||
declare module 'monaco-editor/esm/vs/language/html/html.worker?worker' {
|
||||
const HtmlWorker: new () => Worker
|
||||
export default HtmlWorker
|
||||
}
|
||||
declare module 'monaco-editor/esm/vs/language/typescript/ts.worker?worker' {
|
||||
const TsWorker: new () => Worker
|
||||
export default TsWorker
|
||||
}
|
||||
Reference in New Issue
Block a user