|
@@ -1,76 +1,192 @@
|
|
|
-<!--
|
|
|
|
|
- * @Author: liuJie
|
|
|
|
|
- * @Date: 2026-01-28 02:04:56
|
|
|
|
|
- * @LastEditors: liuJie
|
|
|
|
|
- * @LastEditTime: 2026-01-28 16:55:30
|
|
|
|
|
- * @Describe: 代码编辑器
|
|
|
|
|
--->
|
|
|
|
|
-<template>
|
|
|
|
|
- <div class="space-y-4">
|
|
|
|
|
- <!-- <div class="flex items-center justify-between text-gray-800">
|
|
|
|
|
- <span class="w-[60px] text-sm">语法</span>
|
|
|
|
|
- <ElSelect v-model="selectedLanguageVal" @change="changeLanguage">
|
|
|
|
|
- <el-option v-for="li in language" :key="li" :value="li">{{li}}</el-option>
|
|
|
|
|
- </ElSelect>
|
|
|
|
|
- </div> -->
|
|
|
|
|
- <div ref="editorContainer" style="height: 240px;"></div>
|
|
|
|
|
- </div>
|
|
|
|
|
-</template>
|
|
|
|
|
-
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
|
|
+import type { editor } from 'monaco-editor'
|
|
|
|
|
|
|
|
-import { ref, onMounted, onUnmounted } from 'vue'
|
|
|
|
|
|
|
+import { nextTick, onMounted, ref, watch } from 'vue'
|
|
|
import * as monaco from 'monaco-editor'
|
|
import * as monaco from 'monaco-editor'
|
|
|
-// const language = ref(['javascript','python']);
|
|
|
|
|
-// let selectedLanguageVal = ref('javascript')
|
|
|
|
|
|
|
+import { IconButton } from '@repo/ui'
|
|
|
|
|
+
|
|
|
|
|
+const props = withDefaults(
|
|
|
|
|
+ defineProps<{
|
|
|
|
|
+ allowFullscreen?: boolean
|
|
|
|
|
+ autoToggleTheme?: boolean
|
|
|
|
|
+ bordered?: boolean
|
|
|
|
|
+ config?: editor.IStandaloneEditorConstructionOptions
|
|
|
|
|
+ language?: string
|
|
|
|
|
+ lineNumbers?: 'off' | 'on'
|
|
|
|
|
+ modelValue?: any
|
|
|
|
|
+ readOnly?: boolean
|
|
|
|
|
+ theme?: 'hc-black' | 'vs-dark' | 'vs-light'
|
|
|
|
|
+ valueFormat?: string
|
|
|
|
|
+ height?: string
|
|
|
|
|
+ appendTo?: string | HTMLElement
|
|
|
|
|
+ }>(),
|
|
|
|
|
+ {
|
|
|
|
|
+ allowFullscreen: true,
|
|
|
|
|
+ config: () => ({
|
|
|
|
|
+ minimap: {
|
|
|
|
|
+ enabled: false
|
|
|
|
|
+ },
|
|
|
|
|
+ selectOnLineNumbers: true
|
|
|
|
|
+ }),
|
|
|
|
|
+ autoToggleTheme: true,
|
|
|
|
|
+ language: 'json',
|
|
|
|
|
+ lineNumbers: 'on',
|
|
|
|
|
+ readOnly: false,
|
|
|
|
|
+ theme: 'vs-light',
|
|
|
|
|
+ valueFormat: 'string',
|
|
|
|
|
+ height: '120px'
|
|
|
|
|
+ }
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+// todo 动态切换主题
|
|
|
|
|
+const theme = ref('dark')
|
|
|
|
|
+
|
|
|
|
|
+const emit = defineEmits(['update:modelValue', 'update:language'])
|
|
|
|
|
+
|
|
|
|
|
+const isFullScreen = ref(false)
|
|
|
|
|
+
|
|
|
|
|
+const fullScreenStyle = `position: fixed;
|
|
|
|
|
+ top: 0;
|
|
|
|
|
+ left: 0;
|
|
|
|
|
+ right: 0;
|
|
|
|
|
+ bottom: 0;
|
|
|
|
|
+ z-index: 2999;`
|
|
|
|
|
+
|
|
|
|
|
+const editContainer = ref<HTMLElement | null>(null)
|
|
|
|
|
+
|
|
|
|
|
+let monacoEditor: monaco.editor.IStandaloneCodeEditor | null = null
|
|
|
|
|
|
|
|
-const editorContainer = ref<HTMLElement | null>(null)
|
|
|
|
|
-let editor = ref(null) as any;
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * 设置文本
|
|
|
|
|
+ * @param text
|
|
|
|
|
+ */
|
|
|
|
|
+function setValue(text: string) {
|
|
|
|
|
+ monacoEditor?.setValue(text || '')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 光标处插入文本
|
|
|
|
|
+ * @param text
|
|
|
|
|
+ */
|
|
|
|
|
+function insertText(text: string) {
|
|
|
|
|
+ // 获取光标位置
|
|
|
|
|
+ const position = monacoEditor?.getPosition()
|
|
|
|
|
+ // 未获取到光标位置信息
|
|
|
|
|
+ if (!position) {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ // 插入
|
|
|
|
|
+ monacoEditor?.executeEdits('', [
|
|
|
|
|
+ {
|
|
|
|
|
+ range: new monaco.Range(
|
|
|
|
|
+ position.lineNumber,
|
|
|
|
|
+ position.column,
|
|
|
|
|
+ position.lineNumber,
|
|
|
|
|
+ position.column
|
|
|
|
|
+ ),
|
|
|
|
|
+ text
|
|
|
|
|
+ }
|
|
|
|
|
+ ])
|
|
|
|
|
+ // 设置新的光标位置
|
|
|
|
|
+ monacoEditor?.setPosition({
|
|
|
|
|
+ ...position,
|
|
|
|
|
+ column: position.column + text.length
|
|
|
|
|
+ })
|
|
|
|
|
+ // 重新聚焦
|
|
|
|
|
+ monacoEditor?.focus()
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
onMounted(() => {
|
|
|
- try {
|
|
|
|
|
- if (!editorContainer.value) {
|
|
|
|
|
- console.log('编辑器挂载节点没获取到')
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
- editor.value = monaco.editor.create(editorContainer.value, {
|
|
|
|
|
- value: [
|
|
|
|
|
- 'function x() {',
|
|
|
|
|
- ' console.log("Hello world!");',
|
|
|
|
|
- '}'
|
|
|
|
|
- ].join('\n'),
|
|
|
|
|
- language: 'javascript',
|
|
|
|
|
- theme: 'vs-dark',
|
|
|
|
|
- automaticLayout: true,
|
|
|
|
|
- fixedOverflowWidgets: true,
|
|
|
|
|
- });
|
|
|
|
|
- // 防止冒泡
|
|
|
|
|
- editor.value.onKeyDown((e: Event) => {
|
|
|
|
|
- e.stopPropagation();
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- } catch (err) {
|
|
|
|
|
- console.error("Monaco 创建失败:", err);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-});
|
|
|
|
|
-
|
|
|
|
|
-onUnmounted(() => {
|
|
|
|
|
- if (editor.value) {
|
|
|
|
|
- editor.value.dispose();
|
|
|
|
|
- }
|
|
|
|
|
-});
|
|
|
|
|
-
|
|
|
|
|
-// // 动态设置模型语言
|
|
|
|
|
-// const changeLanguage = ()=>{
|
|
|
|
|
-// console.log('changeLanguage', selectedLanguageVal.value,editor.value );
|
|
|
|
|
-// if(editor.value){
|
|
|
|
|
-// const model = editor.value.getModel()
|
|
|
|
|
-// if (model) {
|
|
|
|
|
-// monaco.editor.setModelLanguage(model, selectedLanguageVal.value)
|
|
|
|
|
-// }
|
|
|
|
|
-// }
|
|
|
|
|
-// }
|
|
|
|
|
|
|
+ monacoEditor = monaco.editor.create(editContainer.value as HTMLElement, {
|
|
|
|
|
+ value: getValue(),
|
|
|
|
|
+ ...props.config,
|
|
|
|
|
+ automaticLayout: true,
|
|
|
|
|
+ language: props.language,
|
|
|
|
|
+ lineNumbers: props.lineNumbers,
|
|
|
|
|
+ readOnly: props.readOnly,
|
|
|
|
|
+ scrollBeyondLastLine: false,
|
|
|
|
|
+ theme: props.theme
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ function handleToggleTheme() {
|
|
|
|
|
+ if (theme.value === 'dark') {
|
|
|
|
|
+ monaco.editor.setTheme('vs-dark')
|
|
|
|
|
+ } else {
|
|
|
|
|
+ monaco.editor.setTheme('vs-light')
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 自动切换主题
|
|
|
|
|
+ if (props.autoToggleTheme) {
|
|
|
|
|
+ watch(
|
|
|
|
|
+ () => theme,
|
|
|
|
|
+ () => {
|
|
|
|
|
+ nextTick(() => handleToggleTheme())
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ immediate: true
|
|
|
|
|
+ }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 获取值
|
|
|
|
|
+ function getValue() {
|
|
|
|
|
+ // valueFormat 为json 格式,需要转换处理
|
|
|
|
|
+ if (props.valueFormat === 'json' && props.modelValue) {
|
|
|
|
|
+ return JSON.stringify(props.modelValue, null, 2)
|
|
|
|
|
+ }
|
|
|
|
|
+ return props.modelValue ?? ''
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 监听值变化
|
|
|
|
|
+ monacoEditor.onDidChangeModelContent(() => {
|
|
|
|
|
+ const currenValue = monacoEditor?.getValue()
|
|
|
|
|
|
|
|
|
|
+ // valueFormat 为json 格式,需要转换处理
|
|
|
|
|
+ if (props.valueFormat === 'json' && currenValue) {
|
|
|
|
|
+ emit('update:modelValue', JSON.parse(currenValue))
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
|
|
+ emit('update:modelValue', currenValue ?? '')
|
|
|
|
|
+ })
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+defineExpose({
|
|
|
|
|
+ insertText,
|
|
|
|
|
+ setValue
|
|
|
|
|
+})
|
|
|
</script>
|
|
</script>
|
|
|
|
|
+<template>
|
|
|
|
|
+ <Teleport :disabled="!appendTo" :to="appendTo">
|
|
|
|
|
+ <div
|
|
|
|
|
+ ref="editContainer"
|
|
|
|
|
+ :class="{ bordered: props.bordered }"
|
|
|
|
|
+ :style="isFullScreen ? fullScreenStyle : { height: props.height }"
|
|
|
|
|
+ class="code-editor w-full h-full relative"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="z-999 text-$epic-text-helper absolute right-4 top-2 cursor-pointer text-xl"
|
|
|
|
|
+ @click="isFullScreen = !isFullScreen"
|
|
|
|
|
+ v-if="props.allowFullscreen"
|
|
|
|
|
+ >
|
|
|
|
|
+ <IconButton v-if="!isFullScreen" icon="lucide:fullscreen" link />
|
|
|
|
|
+ <IconButton v-else icon="material-symbols-light:fullscreen-exit-rounded" link />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </Teleport>
|
|
|
|
|
+</template>
|
|
|
|
|
+<style lang="less" scoped>
|
|
|
|
|
+.code-editor {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ min-height: 150px;
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.monaco-editor) {
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &.bordered {
|
|
|
|
|
+ border: 1px solid var(--epic-border-color);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|