| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- <script setup lang="ts">
- import type { editor } from 'monaco-editor'
- import { nextTick, onMounted, ref, watch } from 'vue'
- import * as monaco from 'monaco-editor'
- 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'
- }
- )
- // 动态切换主题
- 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
- /**
- * 设置文本
- * @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(() => {
- 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>
- <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>
|