效果展示
前置
npm i codemirror@5.65.5 //锁定版本
组件
<!--
* @Descripttion: 代码编辑器
* @version: 1.0
* @Author: bing
* @Date: 2024年3月1日
* @LastEditors:
* @LastEditTime:
-->
<template>
<div class="bing-code-editor" :style="{'height':_height}">
<textarea ref="textarea" v-model="contentValue"></textarea>
</div>
</template>
<script>
import { markRaw } from "vue"
//框架
import CodeMirror from 'codemirror'
import 'codemirror/lib/codemirror.css'
//主题
import 'codemirror/theme/idea.css'
import 'codemirror/theme/darcula.css'
//功能
import 'codemirror/addon/selection/active-line'
//语言
import 'codemirror/mode/javascript/javascript'
import 'codemirror/mode/sql/sql'
import 'codemirror/addon/hint/show-hint.js'
import 'codemirror/addon/hint/show-hint.css'
import 'codemirror/addon/hint/sql-hint.js'
export default {
props: {
modelValue: {
type: String,
default: ""
},
mode: {
type: String,
default: "javascript"
},
height: {
type: [String,Number],
default: 300,
},
options: {
type: Object,
default: () => {}
},
theme: {
type: String,
default: "idea"
},
readOnly: {
type: Boolean,
default: false
},
},
data() {
return {
contentValue: this.formatStrInJson(this.modelValue),
coder: null,
keywords: [
['吃了', "keyword"],
['兵兵', "property"],
['徐', "number"],
['牛肉', "comment"]
],
opt: {
theme: this.theme, //主题
styleActiveLine: true, //高亮当前行
lineNumbers: true, //行号
lineWrapping: true, //自动换行
tabSize: 4, //Tab缩进
indentUnit: 4, //缩进单位
indentWithTabs : true, //自动缩进
mode : this.mode, //语言
readOnly: this.readOnly, //只读
lint: true, // 格式化
foldGutter: true, // 启用折叠效果
hintOptions: { // 代码提示
completeSingle: false, // 当匹配只有一项的时候是否自动补全
hint: this.bingShowHint // 自定义提示
},
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'], // 配置折叠参数
...this.options
},
isBlank: false, // 是否为空格
start: null // 光标开始位置
}
},
computed: {
_height() {
return Number(this.height)?Number(this.height)+'px':this.height
},
},
watch: {
modelValue(val) {
this.contentValue = val
if (val !== this.coder.getValue()) {
const format = this.formatStrInJson(val)
this.coder.setValue(format)
}
}
},
mounted() {
this.init()
//获取挂载的所有modes
//console.log(CodeMirror.modes)
},
methods: {
init(){
const _this = this
// 颜色变换[高亮自定义关键词]
CodeMirror.defineMode('javascript', function() {
return {
token: (stream, state) => {
const cmCustomCheckStreamFn = (streamWrapper) => {
for (let i = 0; i < _this.keywords.length; i++) {
if (streamWrapper.match(_this.keywords[i][0])) { return _this.keywords[i][1] }
}
return ''
}
const ret = cmCustomCheckStreamFn(stream)
if(ret.length > 0) return ret
stream.next()
return null
}
}
})
this.coder = markRaw(CodeMirror.fromTextArea(this.$refs.textarea, this.opt))
this.coder.on('change', (coder, data) => {
// console.log(this.inputFilter(data.text))
this.contentValue = coder.getValue()
this.$emit('update:modelValue', this.contentValue)
})
// 输入或者粘贴时触发
this.coder.on('inputRead', (coder) => {
const cursor = coder.getDoc().getCursor()
const token = coder.getTokenAt(cursor)
console.log(token.string)
if((token.string).trim() == '') {
this.start = token.start + 1
}
if(this.start === null) {
this.start = token.start
}
const currentData = this.inputFilter(token.string)
if(currentData) {
coder.showHint()
}
})
},
// 输入过滤
inputFilter(data) {
var reg = /^[\u4e00-\u9fa5]+$/;
if(!reg.test(data)) {
return false
}else {
return data
}
},
// 自定义弹窗内容
bingShowHint(cmInstance) {
let cursor = cmInstance.getCursor()
let token = cmInstance.getTokenAt(cursor)
return {
list: [
{
text: '吃了',
displayText: '吃了',
displayInfo: '吃了',
},
{
text: '徐',
displayText: '徐',
displayInfo: '徐',
},
{
text: '兵兵',
displayText: '兵兵',
displayInfo: '兵兵',
}
],
from: {
// ch: token.start, line: cursor.line
ch: this.start, line: cursor.line
},
to: {
ch: token.end, line: cursor.line
}
}
},
formatStrInJson(strValue) {
// return JSON.stringify(JSON.parse(strValue), null, 4)
return strValue
}
}
}
</script>
<style scoped>
.bing-code-editor {font-size: 14px;border: 1px solid #ddd;line-height: 150%;}
.bing-code-editor:deep(.CodeMirror) {height: 100%;}
</style>
组件使用
<script setup>
import scCodeEditor from '@/components/bingEditor/index.vue'
import { ref } from 'vue'
const demoValue = ref('')
</script>
<template>
<div class="editor">
<sc-code-editor v-model="demoValue" mode="javascript" height="100vh" theme="darcula"></sc-code-editor>
</div>
</template>
<style scoped>
.editor {
width: 100%;
height: 100%;
}
</style>