<template> <div> <div class="editorRef min-h-[320px] h-[320px] border-1" ref="editorRef"></div> </div> </template> <script setup lang="ts"> import { ref, shallowRef, reactive, watchEffect, nextTick, onMounted } from 'vue'; import { basicSetup } from 'codemirror'; import { EditorState, EditorSelection } from '@codemirror/state'; import { EditorView, keymap, ViewPlugin } from '@codemirror/view'; import { autocompletion } from '@codemirror/autocomplete'; import { indentWithTab } from '@codemirror/commands'; import { githubLight } from '@uiw/codemirror-theme-github'; import { python } from '@codemirror/lang-python'; import { java } from '@codemirror/lang-java'; import { javascript } from '@codemirror/lang-javascript'; type Props = { doc: string; }; const props = defineProps<Props>(); const editorRef = ref<any>(null); const editorView = shallowRef<any>(null); const editorState = shallowRef<any>(null); const chinesePhrases = { // @codemirror/view 'Control character': '控制字符', // @codemirror/commands 'Selection deleted': '选择删除', // @codemirror/language 'Folded lines': '折叠行', 'Unfolded lines': '折叠行', to: '向前', 'folded code': '折叠代码', unfold: '展开代码', 'Fold line': '', 'Unfold line': '展开行', // @codemirror/search 'Go to line': '跳转到行', go: '确定', Find: '查找', Replace: '替换', next: '下一个', previous: '上一个', all: '所有', 'match case': '区分大小写', regexp: '正则表达式', 'by word': '全字匹配', replace: '替换', 'replace all': '全部替换', close: '关闭', 'current match': '当前匹配', 'replaced $ matches': '$ Treffer ersetzt', 'replaced match on line $': 'Treffer on Zeile $ ersetzt', 'on line': 'auf Zeile', // @codemirror/autocomplete Completions: '完成', // @codemirror/lint Diagnostics: '诊断', 'No diagnostics': '不诊断', }; const initState = doc => { let state = { doc: mapDoc(doc) || '', extensions: [ basicSetup, githubLight, keymap.of([indentWithTab]), python(), autocompletion(), EditorState.phrases.of(chinesePhrases), ], smartIndent: true, }; editorState.value = EditorState.create(state); }; const initEditor = () => { editorView.value = new EditorView({ state: editorState.value, parent: editorRef.value, }); const { state, dispatch } = editorView.value; const { selection } = state; if (selection && !selection.empty) { // 清空选择区域 dispatch({ selection: { anchor: selection.main.head } }); } }; const insertValue = value => { const { state, dispatch } = editorView.value; const { selection } = state; // 如果存在选择区域,则替换选区内容 if (selection && !selection.main.empty) { const { from, to } = selection.main; dispatch({ changes: { from, to, insert: value }, selection: { anchor: from + value.length }, }); return; } // 如果不存在选择区域,则在末尾 const docLength = state.doc.length; dispatch({ changes: { from: docLength, insert: value }, selection: { anchor: docLength + value.length }, }); }; const getValue = () => { return editorView.value.state.doc.toString(); }; const mapDoc = doc => { const lines = []; const maxLength = 2000; for (let i = 0; i < doc.length; i += maxLength) { lines.push(doc.substr(i, maxLength)); } return lines.join('\n'); }; onMounted(() => { initState(props.doc); initEditor(); }); defineExpose({ getValue, insertValue, }); </script> <style lang="scss" scoped> :deep(.cm-focused) { outline: none !important; } :deep(.cm-editor) { height: 100% !important; overflow: auto; font-size: 15px; .cm-gutters { background-color: #fff !important; border-right: 0px !important; } } </style>
以上是组件 复制到项目中安装依赖
下边是使用的方法
<template>
<el-drawer
modal-class="d_drawer"
:model-value="drawer"
v-if="drawer"
size="600px"
@close="handleClose"
direction="rtl"
>
<template #header>
<h4 class="text-[#000] font-bold">{{ title }}</h4>
</template>
<template #default>
<div class="">
<div class="flex items-center justify-between mx-[16px] mt-[16px]">
<div class="flex items-center text-[14px] text-[#606266] my_button">
<el-popover placement="bottom" :offset="5" trigger="hover">
<template #reference>
<el-button v-noBtnFocus>过滤器</el-button>
</template>
<div v-for="(dataIetm, index) in filtersOptions" :key="index">
<div
:class="index == 0 ? '' : 'mt-[8px]'"
class="select-none cursor-pointer hover:opacity-[0.8] overflow-hidden text-ellipsis whitespace-nowrap"
@click="handleInsertValue(dataIetm.value)"
:title="dataIetm.label"
>
{{ dataIetm.label }}
</div>
</div>
</el-popover>
<el-popover placement="bottom" :offset="5" trigger="hover">
<template #reference>
<el-button v-noBtnFocus>数学</el-button>
</template>
<div v-for="(dataIetm, index) in mathOptions" :key="index">
<div
:class="index == 0 ? '' : 'mt-[8px]'"
class="select-none cursor-pointer hover:opacity-[0.8] overflow-hidden text-ellipsis whitespace-nowrap"
@click="handleInsertValue(dataIetm.value)"
:title="dataIetm.label"
>
{{ dataIetm.label }}
</div>
</div>
</el-popover>
<el-popover placement="bottom" :offset="5" trigger="hover">
<template #reference>
<el-button v-noBtnFocus>Python</el-button>
</template>
<div v-for="(dataIetm, index) in pythonOptions" :key="index">
<div
:class="index == 0 ? '' : 'mt-[8px]'"
class="select-none cursor-pointer hover:opacity-[0.8] overflow-hidden text-ellipsis whitespace-nowrap"
@click="handleInsertValue(dataIetm.value)"
:title="dataIetm.label"
>
{{ dataIetm.label }}
</div>
</div>
</el-popover>
</div>
<div>
<el-popover placement="bottom" :offset="5" trigger="hover">
<template #reference>
<el-button :icon="Plus" link v-noBtnFocus> 添加数据 </el-button>
</template>
<div v-for="(dataIetm, index) in addDataOptions" :key="index">
<div
:class="index == 0 ? '' : 'mt-[8px]'"
class="select-none cursor-pointer hover:opacity-[0.8] overflow-hidden text-ellipsis whitespace-nowrap"
@click="handleInsertValue(dataIetm.value)"
:title="dataIetm.label"
>
{{ dataIetm.label }}
</div>
</div>
</el-popover>
</div>
</div>
<div class="flex-1 mt-[8px] mx-[16px]">
<CodemirrorVue ref="codemirrorVueRef" :doc="doc" />
</div>
<div class="mt-[8px] mx-[16px]">
<el-button :loading="buttonLoading" v-noBtnFocus @click="handleGetCodemirror">
<div class="flex items-center">
<div
class="i-ic-round-slow-motion-video w-[16px] h-[16px] color-[#2854e3] mr-[4px]"
></div>
<div>运行</div>
</div>
</el-button>
</div>
<div class="mt-[16px] border-b-[1px] border-t-[1px] border-[#f8f8f8]">
<div class="font-bold text-[14px] m-[16px] text-[#000]">运行结果</div>
<div v-loading="buttonLoading" class="m-[16px] bg-[#fafbfc] min-h-[181px]">
<JsonVue ref="jsonVueRef" />
</div>
</div>
</div>
</template>
<template #footer>
<div style="flex: auto">
<el-button v-noBtnFocus @click="handleClose">取消</el-button>
<el-button :loading="drawerLoading" v-noBtnFocus type="primary" @click="confirmClick()">
确定
</el-button>
</div>
</template>
</el-drawer>
</template>
<script lang="ts" setup>
import { ref, inject } from 'vue';
import type { Ref } from 'vue';
import { Plus } from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus';
import type { FormInstance, FormRules } from 'element-plus';
import CodemirrorVue from './components/CodemirrorVue/index.vue';
import TopoToolDrawer from '../TopoToolDrawer/index.vue';
import JsonVue from './components/JsonVue/index.vue';
import { useOptions } from './hooks/useOptions';
import { postFlowableInstanceCheckPythonApi } from '@/api/arrangement/index';
const { filtersOptions, mathOptions, pythonOptions } = useOptions();
const jsonVueRef = ref<InstanceType<typeof JsonVue> | null>(null);
const topoToolDrawerRef = inject<Ref<InstanceType<typeof TopoToolDrawer>>>('topoToolDrawerRef');
const codemirrorVueRef = ref<InstanceType<typeof CodemirrorVue> | null>(null);
const drawer = ref(false);
const title = ref('');
const drawerLoading = ref(false);
const doc = ref('');
const actionId = ref('');
const addDataOptions = ref<any[]>([]);
const buttonLoading = ref(false);
const confirmClick = async () => {
doc.value = codemirrorVueRef.value?.getValue();
topoToolDrawerRef.value.setSettingParameterValue(actionId.value, doc.value);
handleClose();
};
const handleClose = () => {
drawer.value = false;
title.value = '';
doc.value = '';
actionId.value = '';
jsonVueRef?.value.setJsonData('');
};
const handleGetCodemirror = async () => {
try {
buttonLoading.value = true;
let req = codemirrorVueRef.value?.getValue();
if (!req) return ElMessage.warning('请输入代码');
if (req.length > 20000) return ElMessage.warning('代码长度不能超过20000');
let res = await postFlowableInstanceCheckPythonApi({ script: req });
jsonVueRef?.value.setJsonData(res.data.msg);
if (!res.data.msg) return ElMessage.warning('运行结果为空');
} catch (error) {
console.log('🚀 🤷 ~ file: index.vue:164 ~ handleGetCodemirror ~ error 👉', error);
} finally {
buttonLoading.value = false;
}
};
const handleInsertValue = val => {
codemirrorVueRef.value?.insertValue(val);
};
defineExpose({
drawer,
title,
doc,
actionId,
addDataOptions,
});
</script>
<style lang="scss" scoped>
.my_button {
.el-button:focus,
.el-button:hover {
color: #909399;
border-color: #f0f0f0;
background-color: #fff;
}
}
</style>