vue3简使用codemirror

3,584 阅读2分钟

最近新开了一个vue3的项目,一个充满坑的旅程,下面就简单记载一下,vue3中codemirror的简单实用,以及所遇到的一些问题。

目前项目的依赖库版本如下:

    "axios": "^0.21.1",
    "element-plus": "1.0.2-beta.46",
    "js-cookie": "^2.2.1",
    "mitt": "^3.0.0",
    "vue": "3.0.11",
    "vue-router": "4.0.4",
    "vuex": "4.0.0",
    "@vitejs/plugin-vue": "^1.2.2",
    "@vitejs/plugin-vue-jsx": "^1.1.4",
    "@vue/compiler-sfc": "3.0.11",
    "dayjs": "^1.10.4",
    "eslint": "^7.26.0",
    "eslint-plugin-prettier": "^3.4.0",
    "sass": "^1.32.13",
    "vite": "^2.3.3"

vite, vue, element-plus是频繁发版,直接升级会遇到很多问题,所以没有用最新的版本,等基本稳定后再更新吧。。。如果codemirror有支持vue3的版本了,也希望大佬们可以不吝赐教。

引入codemirror依赖

npm官方库有一个vue-codemirror,之前vue2.X版本都是用的这个库做集成(npm i vue-codemirror),但vue3引入该依赖使用后会报错。所以项目中,我是用的codemirror github.com/codemirror/…的代码,放进本地项目目录。目录如下图:

image.png

由于项目中仅需要支持sql,所以我只引入了sql,如果有其它语言的需求,也是直接copy放进mode目录里。

codemirror简单使用

废话少说,直接上代码。

封装code组件

<template>
  <div>
    <el-input
      :id="`code-editor-${currentTimeStamp}`"
      type="textarea"
      :rows="row"
      placeholder="请输入SQL"
      v-model="currentCode"
    >
    </el-input>
  </div>
</template>

<script setup>
import { defineProps, onMounted, ref, useContext, watch, defineEmit } from 'vue'

// 引入相关依赖
import '@/cm5/lib/codemirror.js'
import '@/cm5/lib/codemirror.css'
import '@/cm5/mode/sql/sql.js'

let codeEditor = null
const props = defineProps({
  code: {
    type: String,
    default: ''
  },
  row: {
    type: Number,
    default: 6
  },
  readOnly: {
    type: Boolean,
    default: false
  },
  needWatch: {
    type: Boolean,
    default: true
  }, // 此处是为了解决多个codemirror来回显示,值错乱问题;设置成false,不会自动更新值,需要父组件手动赋值
  lineNumbers: {
    type: Boolean,
    default: true
  }
})
const emit = defineEmit(['onBlur'])
const currentCode = ref('')

// 确保codemirror唯一
const currentTimeStamp = new Date().getTime()

watch(props, (newProps) => {
  if (codeEditor && props.needWatch) {
    if (newProps.code !== currentCode.value) {
      currentCode.value = newProps.code
      codeEditor.setValue(newProps.code)
    }
  }
})

onMounted(() => {
  codeEditor = CodeMirror.fromTextArea(
    document.getElementById(`code-editor-${currentTimeStamp}`),
    {
      lineNumbers: props.lineNumbers,
      mode: 'text/x-sql',
      lineWrapping: true,
      readOnly: props.readOnly,
      autoRefresh: true
    }
  )
  
  // 失去焦点监听
  codeEditor.on('blur', (instance, event) => {
    emit('onBlur')
  })

  if (props.needWatch) {
    currentCode.value = props.code
    codeEditor.setValue(props.code || '')
  }
})

// 获取codemirror值
const getValue = () => {
  if (codeEditor) {
    return codeEditor.getValue()
  }
  return ''
}

// 给codemirror赋值
const setValue = (newValue) => {
  if (codeEditor) {
    currentCode.value = newValue
    codeEditor.setValue(newValue)
  }
}

// 获取codemirror选中
const getSelection = () => {
  if (codeEditor) {
    return codeEditor.getSelection()
  }
  return null
}

// 手动刷新codemirror
const refresh = () => {
  if (codeEditor) {
    codeEditor.refresh()
  }
}

const { expose } = useContext()
expose({
  setValue,
  getValue,
  getSelection,
  refresh
})
</script>

父组件使用

<code-editor
  :code="sql"
  ref="codeEditorRef"
>
</code-editor>

// 手动赋值
<code-editor
  ref="codeEditorRef"
  :need-watch="false"
>
</code-editor>

codemirror样式问题,整体左移

image.png

解决办法:官方说只需要添加autoRefresh: true属性,就可以,但我这边设置了没用。 后来经过不断查找发现,重新设置值之后,手动调用codeEditor.refresh(),就完美解决了。

image.png

多个tab切换,父组件手动赋值,没有正确显示内容

如果是在watch里监听到值的变化,再去赋值,很有可能codemirror还没有渲染出来,但是已经watch到值的变化,需要等dom渲染完毕之后再去赋值。

watch(props, async (newProps) => {
  if (newProps.sql !== props.sql) {
    await nextTick() // 为了解决切换会话,prettyCodeRef还未渲染,但是值已经watch到,所以等渲染完毕再手动赋值
    if (prettyCodeRef.value) {
      prettyCodeRef.value.setValue(newProps.sql || '')
      prettyCodeRef.value.refresh() // 为了解决codemirror重新渲染之后,样式错乱(整体左移)
    }
  }
})