mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-03 16:00:25 +08:00
360 lines
11 KiB
Vue
360 lines
11 KiB
Vue
<template>
|
|
<div class="in-coder-panel">
|
|
<textarea ref="textarea"></textarea>
|
|
<el-select v-if="canChangeMode" class="code-mode-select" v-model="mode" @change="changeMode">
|
|
<el-option v-for="mode in modes" :key="mode.value" :label="mode.label" :value="mode.value"> </el-option>
|
|
</el-select>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { ref, nextTick, toRefs, reactive, watch, onMounted, defineComponent } from 'vue';
|
|
// 引入全局实例
|
|
import _CodeMirror from 'codemirror';
|
|
|
|
// 核心样式
|
|
import 'codemirror/lib/codemirror.css';
|
|
// 引入主题后还需要在 options 中指定主题才会生效
|
|
import 'codemirror/theme/cobalt.css';
|
|
import 'codemirror/addon/selection/active-line.js';
|
|
// 匹配括号
|
|
import 'codemirror/addon/edit/matchbrackets.js';
|
|
import 'codemirror/addon/selection/active-line';
|
|
import 'codemirror/addon/comment/comment';
|
|
|
|
// 需要引入具体的语法高亮库才会有对应的语法高亮效果
|
|
// codemirror 官方其实支持通过 /addon/mode/loadmode.js 和 /mode/meta.js 来实现动态加载对应语法高亮库
|
|
// 但 vue 貌似没有无法在实例初始化后再动态加载对应 JS ,所以此处才把对应的 JS 提前引入
|
|
import 'codemirror/mode/yaml/yaml.js';
|
|
import 'codemirror/mode/dockerfile/dockerfile.js';
|
|
import 'codemirror/mode/nginx/nginx.js';
|
|
import 'codemirror/mode/javascript/javascript.js';
|
|
import 'codemirror/mode/css/css.js';
|
|
import 'codemirror/mode/xml/xml.js';
|
|
import 'codemirror/mode/markdown/markdown.js';
|
|
import 'codemirror/mode/python/python.js';
|
|
import 'codemirror/mode/shell/shell.js';
|
|
import 'codemirror/mode/sql/sql.js';
|
|
import 'codemirror/mode/vue/vue.js';
|
|
import 'codemirror/mode/textile/textile.js';
|
|
import 'codemirror/addon/hint/show-hint.css';
|
|
import 'codemirror/addon/hint/show-hint.js';
|
|
|
|
import { ElOption, ElSelect } from 'element-plus';
|
|
|
|
// 尝试获取全局实例
|
|
const CodeMirror = (window as any).CodeMirror || _CodeMirror;
|
|
|
|
export default defineComponent({
|
|
name: 'CodeMirror',
|
|
components: {
|
|
ElOption,
|
|
ElSelect,
|
|
},
|
|
props: {
|
|
modelValue: {
|
|
type: String,
|
|
},
|
|
language: {
|
|
type: String,
|
|
default: null,
|
|
},
|
|
height: {
|
|
type: String,
|
|
default: '500px',
|
|
},
|
|
width: {
|
|
type: String,
|
|
default: 'auto',
|
|
},
|
|
canChangeMode: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
options: {
|
|
type: Object,
|
|
default: null,
|
|
},
|
|
},
|
|
setup(props: any, { emit }) {
|
|
let { modelValue, language } = toRefs(props);
|
|
const textarea: any = ref(null);
|
|
// 编辑器实例
|
|
let coder = null as any;
|
|
|
|
const state = reactive({
|
|
coder: null as any,
|
|
content: '',
|
|
// 默认的语法类型
|
|
mode: 'x-sh',
|
|
// 默认配置
|
|
options: {
|
|
// 缩进格式
|
|
tabSize: 2,
|
|
// 主题,对应主题库 JS 需要提前引入
|
|
theme: 'cobalt',
|
|
// 显示行号
|
|
lineNumbers: true,
|
|
line: true,
|
|
indentWithTabs: true,
|
|
smartIndent: true,
|
|
matchBrackets: true,
|
|
autofocus: true,
|
|
styleSelectedText: true,
|
|
styleActiveLine: true, // 高亮选中行
|
|
foldGutter: true, // 块槽
|
|
// extraKeys: { Tab: 'autocomplete' }, // 自定义快捷键
|
|
hintOptions: {
|
|
// 当匹配只有一项的时候是否自动补全
|
|
completeSingle: false,
|
|
},
|
|
},
|
|
// 支持切换的语法高亮类型,对应 JS 已经提前引入
|
|
// 使用的是 MIME-TYPE ,不过作为前缀的 text/ 在后面指定时写死了
|
|
modes: [
|
|
{
|
|
value: 'x-sh',
|
|
label: 'Shell',
|
|
},
|
|
{
|
|
value: 'x-yaml',
|
|
label: 'Yaml',
|
|
},
|
|
{
|
|
value: 'x-dockerfile',
|
|
label: 'Dockerfile',
|
|
},
|
|
{
|
|
value: 'x-nginx-conf',
|
|
label: 'Nginx',
|
|
},
|
|
{
|
|
value: 'html',
|
|
label: 'XML/HTML',
|
|
},
|
|
{
|
|
value: 'x-python',
|
|
label: 'Python',
|
|
},
|
|
{
|
|
value: 'x-sql',
|
|
label: 'SQL',
|
|
},
|
|
{
|
|
value: 'css',
|
|
label: 'CSS',
|
|
},
|
|
{
|
|
value: 'javascript',
|
|
label: 'Javascript',
|
|
},
|
|
{
|
|
value: 'x-java',
|
|
label: 'Java',
|
|
},
|
|
{
|
|
value: 'x-vue',
|
|
label: 'Vue',
|
|
},
|
|
{
|
|
value: 'markdown',
|
|
label: 'Markdown',
|
|
},
|
|
{
|
|
value: 'text/x-textile',
|
|
label: 'text',
|
|
},
|
|
],
|
|
});
|
|
|
|
onMounted(() => {
|
|
init();
|
|
});
|
|
|
|
watch(
|
|
() => props.modelValue,
|
|
(newValue) => {
|
|
handerCodeChange(newValue);
|
|
}
|
|
);
|
|
|
|
// watch(
|
|
// () => props.options,
|
|
// (newValue, oldValue) => {
|
|
// for (const key in newValue) {
|
|
// coder.setOption(key, newValue[key]);
|
|
// }
|
|
// }
|
|
// );
|
|
|
|
const init = () => {
|
|
if (props.options) {
|
|
state.options = props.options;
|
|
}
|
|
// 初始化编辑器实例,传入需要被实例化的文本域对象和默认配置
|
|
coder = CodeMirror.fromTextArea(textarea.value, state.options);
|
|
coder.setValue(modelValue.value || state.content);
|
|
|
|
// 支持双向绑定
|
|
coder.on('change', (coder: any) => {
|
|
state.content = coder.getDoc().getValue();
|
|
emit('update:modelValue', state.content);
|
|
});
|
|
|
|
coder.on('inputRead', (instance: any, changeObj: any) => {
|
|
if (/^[a-zA-Z]/.test(changeObj.text[0])) {
|
|
instance.showHint();
|
|
}
|
|
});
|
|
|
|
coder.setSize(props.width, props.height);
|
|
// editor.setSize('width','height');
|
|
|
|
// 修改编辑器的语法配置
|
|
setMode(language.value);
|
|
|
|
[
|
|
'scroll',
|
|
'changes',
|
|
'beforeChange',
|
|
'cursorActivity',
|
|
'keyHandled',
|
|
'inputRead',
|
|
'electricInput',
|
|
'beforeSelectionChange',
|
|
'viewportChange',
|
|
'swapDoc',
|
|
'gutterClick',
|
|
'gutterContextMenu',
|
|
'focus',
|
|
'blur',
|
|
'refresh',
|
|
'optionChange',
|
|
'scrollCursorIntoView',
|
|
'update',
|
|
].forEach((event) => {
|
|
// 循环事件,并兼容 run-time 事件命名
|
|
coder.on(event, (...args: any) => {
|
|
// console.log('当有事件触发了', event, args);
|
|
emit(event, ...args);
|
|
const lowerCaseEvent = event.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
if (lowerCaseEvent !== event) {
|
|
emit(lowerCaseEvent, ...args);
|
|
}
|
|
});
|
|
});
|
|
|
|
state.coder = coder;
|
|
// 不加无法显示内容,需点击后才可显示
|
|
refresh();
|
|
};
|
|
|
|
const refresh = () => {
|
|
nextTick(() => {
|
|
coder.refresh();
|
|
});
|
|
};
|
|
|
|
// 设置模式
|
|
const setMode = (val: string) => {
|
|
if (val) {
|
|
// 获取具体的语法类型对象
|
|
let modeObj = getLanguage(val);
|
|
// 判断父容器传入的语法是否被支持
|
|
if (modeObj) {
|
|
state.mode = modeObj.value;
|
|
}
|
|
}
|
|
// 修改编辑器的语法配置
|
|
coder.setOption('mode', `text/${state.mode}`);
|
|
};
|
|
|
|
// 获取当前语法类型
|
|
const getLanguage = (language: string) => {
|
|
// 在支持的语法类型列表中寻找传入的语法类型
|
|
return state.modes.find((mode: any) => {
|
|
// 所有的值都忽略大小写,方便比较
|
|
let currentLanguage = language.toLowerCase();
|
|
let currentLabel = mode.label.toLowerCase();
|
|
let currentValue = mode.value.toLowerCase();
|
|
|
|
// 由于真实值可能不规范,例如 java 的真实值是 x-java ,所以讲 value 和 label 同时和传入语法进行比较
|
|
return currentLabel === currentLanguage || currentValue === currentLanguage;
|
|
});
|
|
};
|
|
|
|
// 更改模式
|
|
const changeMode = (val: string) => {
|
|
setMode(val);
|
|
// 获取修改后的语法
|
|
let label = (getLanguage(val) as any).label.toLowerCase();
|
|
|
|
// 允许父容器通过以下函数监听当前的语法值
|
|
emit('language-change', label);
|
|
};
|
|
|
|
const handerCodeChange = (newVal: string) => {
|
|
const cm_value = coder.getValue();
|
|
if (newVal !== cm_value) {
|
|
const scrollInfo = coder.getScrollInfo();
|
|
coder.setValue(newVal);
|
|
state.content = newVal;
|
|
coder.scrollTo(scrollInfo.left, scrollInfo.top);
|
|
refresh();
|
|
}
|
|
};
|
|
|
|
return {
|
|
...toRefs(state),
|
|
textarea,
|
|
changeMode,
|
|
refresh,
|
|
};
|
|
},
|
|
});
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
.in-coder-panel {
|
|
flex-grow: 1;
|
|
display: flex;
|
|
position: relative;
|
|
.CodeMirror {
|
|
flex-grow: 1;
|
|
z-index: 1;
|
|
.CodeMirror-code {
|
|
line-height: 19px;
|
|
}
|
|
font-family: 'JetBrainsMono';
|
|
}
|
|
.code-mode-select {
|
|
position: absolute;
|
|
z-index: 2;
|
|
right: 10px;
|
|
top: 10px;
|
|
max-width: 130px;
|
|
}
|
|
}
|
|
.CodeMirror-hints {
|
|
position: absolute;
|
|
z-index: 10000;
|
|
overflow: hidden;
|
|
list-style: none;
|
|
|
|
margin: 0;
|
|
padding: 2px;
|
|
|
|
-webkit-box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2);
|
|
-moz-box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2);
|
|
box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2);
|
|
border-radius: 3px;
|
|
border: 1px solid silver;
|
|
|
|
background: white;
|
|
font-size: 90%;
|
|
font-family: 'JetBrainsMono';
|
|
|
|
max-height: 20em;
|
|
overflow-y: auto;
|
|
}
|
|
</style> |