在日常开发中对于一些数据计算场景可能会遇到标签计算的需求,下面关于如何使用CodeMirror实现标签计算编辑功能。
1,结果图
2,主体代码逻辑
大家只需要复制粘贴主要codeMirror使用逻辑即可
<template>
<el-dialog
ref="dialogRef"
:model-value="visible"
width="800px"
:close-on-press-escape="false"
destroy-on-close
append-to-body
@close="
() => {
$emit('update:visible', false);
}
"
>
<template #title>
<span> 编辑表达式 </span>
</template>
<!-- 左侧无用dom元素已删除 -->
<div class="content__right">
<div class="symbol-group">
<div v-for="item in symbolList" :key="item.code" class="symbol-item" @click="handleSymbolClick(item)">
{{ item.name }}
</div>
</div>
<!-- 代码编辑器 -->
<textarea
ref="codeEditorContainerRef"
v-model="currentCodeInEditor"
class="editor"
/>
</div>
</div>
<template #footer>
<div class="dialog-button-box">
<el-button
size="small"
@click="
() => {
$emit('update:visible', false);
}
"
>
取消
</el-button>
<el-button size="small" type="primary" @click="handleOk">
确定
</el-button>
</div>
</template>
</el-dialog>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs, watch, ref, onMounted, nextTick } from 'vue';
import { InfoFilled, Search } from '@element-plus/icons';
import { TreeNodeData } from 'element-plus/es/el-tree/src/tree.type.d';
// 引入代码编辑器核心配置包
import * as CodeMirror from 'codemirror';
import 'codemirror/lib/codemirror.css';
// 引入代码编辑器主题样式
import 'codemirror/theme/base16-light.css';
import 'codemirror/theme/ambiance.css';
import 'codemirror/addon/hint/show-hint.css';
import 'codemirror/theme/monokai.css';
import 'codemirror/theme/material.css';
import 'codemirror/theme/dracula.css';
import 'codemirror/addon/fold/foldgutter.css';
import 'codemirror/mode/javascript/javascript';
// 引入代码编辑器常用语言包
require('codemirror/addon/edit/matchbrackets');
require('codemirror/addon/selection/active-line');
require('codemirror/mode/sql/sql');
require('codemirror/addon/hint/show-hint');
require('codemirror/addon/hint/sql-hint');
require('codemirror/keymap/sublime');
// 引入代码折叠文件
require('codemirror/addon/fold/foldcode');
require('codemirror/addon/fold/foldgutter');
require('codemirror/addon/fold/brace-fold');
require('codemirror/addon/fold/xml-fold');
require('codemirror/addon/fold/indent-fold');
require('codemirror/addon/fold/markdown-fold');
require('codemirror/addon/fold/comment-fold');
export default defineComponent({
name: 'RemarksDialog',
components: {
},
props: {
visible: {
type: Boolean,
default: false,
},
data: {
type: String,
default: '',
},
},
emits: ['update:visible', 'save'],
setup(props, { emit }) {
const dialogRef = ref();
const state = reactive({
// 符号列表
symbolList: [
{
id: 'plus',
code: 'plus',
name: '+',
type: 'operator',
},
{
id: 'minus',
code: 'minus',
name: '-',
type: 'operator',
},
{
id: 'multiply',
code: 'multiply',
name: '*',
type: 'operator',
},
{
id: 'exception',
code: 'exception',
name: '/',
type: 'operator',
},
{
id: 'leftBrackets',
code: 'leftBrackets',
name: '(',
type: 'operator',
},
{
id: 'rightBrackets',
code: 'rightBrackets',
name: ')',
type: 'operator',
},
] as SymbolItem[],
});
// 代码编辑器容器实例
const codeEditorContainerRef = ref<HTMLElement | null>();
// 代码编辑器
let editor : TreeNodeData | null;
// 编辑器当前所用编程语言
const currentLanguage = ref('javascript');
// 编辑器当前主题
const currentTheme = ref('base16-light');
// 编辑器当前展示的代码
const currentCodeInEditor = ref();
// 获取表达式的元素集合
const getCalcResult = (): SymbolItem[] => {
const temp: any[] = editor?.getValue()
.split('$');
// 清除最后一个空格元素
temp.pop();
// 循环生成最后的集合
return temp
.map((item: string) => state.calculationIdMap[item])
.filter((item) => !!item);
};
/**
* @description: 创建标签
* @return {*}
*/
const makeLabel = (mark: any) => {
const spanDom = document.createElement('span');
const label = mark.variable;
spanDom.title = label;
spanDom.innerText = label;
spanDom.classList.add('textarea-tag');
spanDom.dataset.variable = mark.variable;
editor?.markText(mark.start, mark.end, {
replacedWith: spanDom, // 将特定位置的文本替换成给定的节点元素,必须是行元素,不能是块元素
atomic: true, // 原子化,会把节点元素当成一个整体,光标不会进入其中
});
};
/**
* @description: 插入标签
* @return {*}
*/
const insertLabel = (content: any, isCalcDim: boolean) => {
if (!content) return;
const cursor = editor?.getCursor();
editor?.replaceSelection(`${content.code}$`);
makeLabel({
start: cursor,
end: editor?.getCursor(), // 获取自定义标签插入之后的光标对象
variable: content.name,
});
editor?.setCursor(editor?.getCursor());
editor?.focus();
};
/**
* 初始化代码编辑器
*/
const initEditor = () => {
nextTick(() => {
if (codeEditorContainerRef.value) {
editor = CodeMirror.fromTextArea(codeEditorContainerRef.value, {
// 编辑器语言的模式
mode: currentLanguage.value,
// 编辑器主题风格
theme: currentTheme.value,
// 缩进的时候,是否把前面的 N*tab 大小的空间,转化为 N个tab 字符
indentWithTabs: true,
// 是否使用 mode 提供的上下文的缩进
smartIndent: true,
// 编辑器左侧是否显示行号
lineNumbers: true,
// 括号匹配
matchBrackets: true,
// 初始化时是否自动获得焦点
autofocus: true,
// 代码自动换行
lineWrapping: true,
// 代码块折叠
foldGutter: true,
// 只读
readOnly: false,
// 代码块折叠样式
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
// 自定义快捷键
extraKeys: { Ctrl: 'autocomplete' },
// 自定义提示选项
hintOptions: {
tables: {
users: ['name', 'score', 'birthDate'],
countries: ['name', 'population', 'size'],
},
},
});
// 公式回显
if (props.data) {
const dataArray = JSON.parse(props.data);
dataArray.forEach((item: any) => {
insertLabel(item, false);
state.calculationIdMap[item.code] = item;
});
// 重新计算时间维度和可用维度
setTimeout(() => {
// 重新计算时间维度和其他可用维度
resetCurrentDateDimension();
resetCurrentOtherDimension();
}, 500);
}
}
});
};
/**
* @description: 符号点击触发方法
* @param {SymbolItem} data
* @return {*}
*/
const handleSymbolClick = (data: SymbolItem) => {
insertLabel(data, false);
state.calculationIdMap[data.code] = data;
};
watch(
() => props.visible,
async (newVal) => {
if (newVal) {
initEditor();
await getIndicatorList();
} else {
state.currentOtherDimension = [];
state.currentDateDimension = {} as any;
}
},
);
return {
...toRefs(state),
dialogRef,
Search,
codeEditorContainerRef,
currentCodeInEditor,
handleSymbolClick,
};
},
});
</script>
<style lang="scss" scoped>
</style>
大家只需要关注codeMirror插件的引入以及相关api 的使用即可。
未解决的问题:在点击生成标签只有,不能通过鼠标点击直接移动光标位置,只能通过方向键移动光标位置。