Vue3中使用CodeMirror出现setValue后点击报错

光标错乱的问题借鉴:Vue3中使用CodeMirror出现setValue后点击报错 - 简书

版本及其安装

"sql-formatter": "^4.0.2"

"codemirror": "^5.65.9"

npm install --save sql-formatter@4.0.2
npm install codemirror@5.65.2

setup函数中,我们通过refreactive函数创建响应式数据,其特点是,每次修改数据都会更新UI界面,这样的问题是非常消耗性能
所以,如果我们有些时候,有些操作可以不需要每次修改数据都去更新UI界面,那么我们可以通过vue3提供的toRaw方法来获取该数据被Proxy包装前的原始数据,然后通过对原始数据进行修改,进而可以修改对应的代理对象内部数据。这是通过原始数据修改改变的,并不会触发 UI界面更新

有的情况下,我们希望某个数据在以后的操作中永远都不会被追踪,vue3提供了一个方法:markRaw

markRaw标记后,当后面将sqlEditor变成proxy对象时,发现调用change方法改变数据时,界面并不会再跟新了

<script>
// 核心样式
  import 'codemirror/lib/codemirror.css'

  // 引入主题后还需要在 options 中指定主题才会生效
  import 'codemirror/addon/display/fullscreen.css' // 全屏显示编辑器
  import 'codemirror/addon/display/fullscreen.js' // 全屏显示编辑器

  import 'codemirror/theme/cobalt.css'
  import 'codemirror/theme/eclipse.css'
  import 'codemirror/theme/ayu-dark.css'
  import 'codemirror/theme/idea.css'
  import 'codemirror/theme/solarized.css'
  import 'codemirror/addon/hint/show-hint.css'
  import 'codemirror/addon/hint/show-hint.js'
  import 'codemirror/addon/hint/sql-hint.js'
  import 'codemirror/addon/selection/active-line.js'
  import 'codemirror/addon/edit/matchbrackets.js'
  import 'codemirror/mode/sql/sql'
  import CodeMirror from 'codemirror'
  import { format } from 'sql-formatter'

  sqlEditor.value = markRaw(CodeMirror.fromTextArea(proxy.$refs.codeMirror, {
      mode: "text/x-sparksql", // spark sql模式
      lineNumbers: options.lineNumbers,  // 显示行号
      styleActiveLine: options.styleActiveLine, // 激活当前行
      theme: options.theme, // 主题
      lineWrapping: options.lineWrapping, // 自动换行
      matchBrackets: true, // 括号匹配
      autoCloseBrackets: true,
      line: true,
      extraKeys: { // 触发提示按键
        [options.extraKeys]: "autocomplete"
      },
      hintOptions: { // 自定义提示选项
        completeSingle: false, // 当匹配只有一项的时候是否自动补全
        tables: {} // 代码提示
      },
    }))
    // setValue('SELECT')
    editorEvents()
</script>

 sqledit.vue

<template>
  <div class="sqleditor">
    <div v-show="runLoading" class="run-loadings">
      <span class="el-icon-loading"></span>
      正在执行
    </div>
    <div class="header-tools">
      <ul class="tools-list">
        <li class="tools-item" @click="onRunCode">
          <i class="el-icon-video-play"></i>
          运行
        </li>
        <li class="tools-item" @click="onStopRun">
          <i class="el-icon-video-pause"></i>
          停止
        </li>
        <li class="tools-item" @click="onCommitOrder">
          <i class="el-icon-s-promotion"></i>
          提交
        </li>
        <li class="tools-item" @click="onFormatSQL">
          <i class="el-icon-document"></i>
          格式化
        </li>
      </ul>
    </div>
    <textarea ref="codeMirror" class="sqleditor-top"></textarea>
    <!-- <div class="sqleditor-bottom">
      <div class="sqleditor-bottom-tablesresult" :style="{height: `${tablesHeight}px`}">
        <el-tabs type="border-card" style="height: 99%">
          <el-tab-pane label="信息">
            <span v-if="runType === 0" class="el-icon-info" style="color: #909399;"></span>
            <span v-if="runType === 1" class="el-icon-success" style="color: #67C23A;"></span>
            <span v-if="runType === 2" class="el-icon-warning" style="color: #E6A23C;"></span>
            <span v-if="runType === 3" class="el-icon-error" style="color: #F56C6C;"></span>
            {{ runResult }}
            <template v-if="runType === 3">
              <div class="error-message"><b>message:</b>{{ errMsg.sqlMessage }}</div>
              <div class="error-message"><b>errno:</b>{{ errMsg.errno }}</div>
              <div class="error-message"><b>sql:</b>"{{ errMsg.sql }}"</div>
              <div class="error-message"><b>code:</b>{{ errMsg.code }}</div>
              <div class="error-message"><b>sqlState:</b>{{ errMsg.sqlState }}</div>
            </template>
          </el-tab-pane>
          <el-tab-pane label="结果">
            <el-table
              :data="tableData"
              border
              :style="{width: '100%', height: `${tablesHeight - 80}px`}">
              <el-table-column
                v-for="item in columns"
                :key="item.value"
                :prop="item.value"
                :label="item.label"
              />
            </el-table>
          </el-tab-pane>
        </el-tabs>
      </div>
    </div> -->
  </div>
</template>
<script setup>
  // 核心样式
  import 'codemirror/lib/codemirror.css'

  // 引入主题后还需要在 options 中指定主题才会生效
  import 'codemirror/addon/display/fullscreen.css' // 全屏显示编辑器
  import 'codemirror/addon/display/fullscreen.js' // 全屏显示编辑器

  import 'codemirror/theme/cobalt.css'
  import 'codemirror/theme/eclipse.css'
  import 'codemirror/theme/ayu-dark.css'
  import 'codemirror/theme/idea.css'
  import 'codemirror/theme/solarized.css'
  import 'codemirror/addon/hint/show-hint.css'
  import 'codemirror/addon/hint/show-hint.js'
  import 'codemirror/addon/hint/sql-hint.js'
  import 'codemirror/addon/selection/active-line.js'
  import 'codemirror/addon/edit/matchbrackets.js'
  import 'codemirror/mode/sql/sql'
  import CodeMirror from 'codemirror'
  import { format } from 'sql-formatter'

  const props = defineProps({
    // value: {
    //   type: String,
    //   default: ''
    // },
    runLoading: {
      type: Boolean,
      default: false
    },
    czshow: {
      type: Object,
      default: ''
    }
  })
  watch(() => props.czshow, val => {
    if(val.value && val.type == '双击'){
      console.log(val,'setValue')
      setValue(val.value)
    }
  },{ deep: true, immediate: true })
  const emit = defineEmits()
  const { proxy } = getCurrentInstance();
  const sqlEditor = ref(null)
  const codes = ref('')
  const options = reactive({
    styleActiveLine: true,
    lineNumbers: true,
    lineWrapping: false,
    extraKeys: 'Ctrl',
    theme: 'ayu-dark',
  })
  onMounted(() => {
    init()
    setSize()
  })
  function init() {
    sqlEditor.value = markRaw(CodeMirror.fromTextArea(proxy.$refs.codeMirror, {
      mode: "text/x-sparksql", // spark sql模式
      lineNumbers: options.lineNumbers,  // 显示行号
      styleActiveLine: options.styleActiveLine, // 激活当前行
      theme: options.theme, // 主题
      lineWrapping: options.lineWrapping, // 自动换行
      matchBrackets: true, // 括号匹配
      autoCloseBrackets: true,
      line: true,
      extraKeys: { // 触发提示按键
        [options.extraKeys]: "autocomplete"
      },
      hintOptions: { // 自定义提示选项
        completeSingle: false, // 当匹配只有一项的时候是否自动补全
        tables: {} // 代码提示
      },
    }))
    // setValue('SELECT')
    editorEvents()
  }
  function setValue(sql) {
    sqlEditor.value.setValue(sql)
  }
  function setSize(width = 'auto', height = '300px') {
    sqlEditor.value.setSize(width,height)
  }
  function setHintOptions(tables) {
    sqlEditor.value.options.hintOptions.tables = tables
  }
  function editorEvents() {
    // 设置代码提示
    sqlEditor.value.on('keyup', (cm, event) => {
      if (event.keyCode >= 65 && event.keyCode <= 90){
        cm.showHint();
      }
      //所有的字母和'$','{','.'在键按下之后都将触发自动完成  
      if (!cm.state.completionActive && ((event.keyCode >= 65 && event.keyCode <= 90 ) 
      || event.keyCode == 52 || event.keyCode == 219 || event.keyCode == 190)) {  
        CodeMirror.commands.autocomplete(cm, null, {completeSingle: false});  
      }
    })

    // 代码输入的双向绑定
    sqlEditor.value.on('change', (editor) => {
      // 这里要用多一个载体去获取值,不然会重复赋值卡顿
      console.log(editor,'editor')
      codes.value = editor.getValue()
      if (emit) {
        emit('input1', codes.value)
      }
    })
  }
  function onRunCode() {
    // 运行代码
    emit('run', sqlEditor.value.getValue())
  }
  function onStopRun() {
    // 停止执行代码
    emit('stop')
  }
  function onCommitOrder() {
    // 提交命令
    emit('commit', sqlEditor.value.getValue())
  }
  function onFormatSQL() {
    // 格式化代码
    if (sqlEditor.value.getValue().trim() === '') {
      // this.$message.warning('请先编辑 SQL 命令!')
      return
    }
    const sqlCode = sqlEditor.value.getValue()
    console.log(sqlCode,'sqlCode')
    sqlEditor.value.setValue(format(sqlCode))
  }
</script>
<style lang="scss">
.sqleditor {
  font-size: 18px;
  width: 100%;
  height: 100%;
  position: relative;
  &-top{
    width: 100%;
    height: 89%;
  }
  &-bottom{
    width: 100%;
    height: 40%;
    &-tablesresult {
      width: 100%;
      height: 100%;
      box-sizing: border-box;
      padding: 5px 0 0 1px;
      .el-tabs {
        height: 100%;
        .el-tab-pane {
          font-size: 12px;
          height: 100%;          
          .error-message {
            margin: 8px 0px;
            box-sizing: border-box;
            padding-left: 14px;
          }
        }
        .el-table {
          // border: 1px solid red;
          // height: 100px;
          height: 84%;
          overflow-y: scroll;
          font-size: 12px;
        }
        .el-table::-webkit-scrollbar {
          width: 5px;
        }
        .el-table::-webkit-scrollbar-thumb {
          border-radius: 10px;
          background: rgba(0, 0, 0, 0.2);
        }
        .el-tabs__item{
          font-size: 12px;
        }
      }
      .el-tabs__content{
        border: none;
        border-radius: 0px;
        // margin: 1rem;
        // height: 82%;
      }
    }
  }
  .header-tools {
      height: 11%;
      user-select: none;

      .tools-list {
        list-style: none;
        padding: 0  0 0 20px;
        margin: 0;
        display: flex;
        align-items: center;
        height: 100%;
        font-size: 12px;
        color: #666;
  
        .tools-item {
          margin: 0px 10px;
          cursor: pointer;
  
          &:hover {
            color: #999;
          }
  
          .el-icon-video-play {
            color: #4BC451;
          }
          .el-icon-video-pause {
            color: #F35353;
          }
          .el-icon-document-checked,.el-icon-s-promotion,.el-icon-document {
            color: #4381E6;
          }
        }
      }
  }
  // 加载状态
  .run-loadings {
    position: absolute;
    bottom: 0;
    width: 100%;
    height: calc(100% - 40px);
    z-index: 999;
    background: rgba($color: #000000, $alpha: .3);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    color: #462CF4;
    font-size: 12px;

    .el-icon-loading {
      font-size: 22px;
      margin-bottom: 10px;
      color: #462CF4;
    }
  }
}
</style>

主页面 

<template>
  <div class="editprocess">
    <div class="editprocess-contain">
      <el-card style="height: 98%;">
        <div style="display: flex;height: 100%;">
          <div class="mapContainer">
            <SqlEditor class="mapContainer-righttop"
              ref="codemirrorEditor"
              :run-loading="runLoading"
              :czshow="czshow"
              @input1="onEditorInput"
              @run="onSQLRun"
              @stop="onSQLStop"
              @commit="onSQLCommit">
            </SqlEditor>
            
            <div class="mapContainer-rightbottom">
              <div class="tablesresult">
                <el-tabs type="border-card" style="height: 99%">
                  <el-tab-pane label="信息">
                    <span v-if="runType === 0" class="el-icon-info" style="color: #909399;"></span>
                    <span v-if="runType === 1" class="el-icon-success" style="color: #67C23A;"></span>
                    <span v-if="runType === 2" class="el-icon-warning" style="color: #E6A23C;"></span>
                    <span v-if="runType === 3" class="el-icon-error" style="color: #F56C6C;"></span>
                    {{ runResult }}
                    <template v-if="runType === 3">
                      <div class="error-message"><b>message:</b>{{ errMsg.sqlMessage }}</div>
                      <div class="error-message"><b>errno:</b>{{ errMsg.errno }}</div>
                      <div class="error-message"><b>sql:</b>"{{ errMsg.sql }}"</div>
                      <div class="error-message"><b>code:</b>{{ errMsg.code }}</div>
                      <div class="error-message"><b>sqlState:</b>{{ errMsg.sqlState }}</div>
                    </template>
                  </el-tab-pane>
                  <el-tab-pane label="结果">
                    <el-table
                      :data="tableData.arr"
                      border>
                      <el-table-column
                        v-for="item in columns"
                        :key="item.value"
                        :prop="item.value"
                        :label="item.label"
                      />
                    </el-table>
                  </el-tab-pane>
                </el-tabs>
              </div>
            </div>
          </div>
        </div>
      </el-card>
    </div>
    <pointDialog :pointDialogVisual="pointDialogVisual"></pointDialog>
  </div>
</template>
<script setup>
  import Product from './SQLproduct'
  import SqlEditor from './SqlEditor'
  import pointDialog from './pointDialog.vue'
  import { useRouter } from "vue-router"
  const content = ref('')
  const { proxy } = getCurrentInstance();
  
  // sql
  const tablesHeight = ref('150')
  const code = ref('')
  const czshow = reactive({
    type: '',
    value: '',
  })
  const runLoading = ref(false)
  const runResult = ref('暂无数据')
  const runType = ref(0)
  const tableData = reactive({
    arr:[]
  })
  const columns = reactive({
    arr:[]
  })
  const errMsg = reactive({
    sqlMessage: '',
    errno: '',
    sql: '',
    code: '',
    sqlState: ''
  })
  function onEditorInput(codes) {
    // 编辑器输入事件
    console.log(codes,'1111111')
    code.value = codes
    czshow.type = ''
  }
  function onSQLStop() {
    // SQL编辑器停止运行事件
  }
  function onSQLCommit() {
    // SQL编辑器提交事件
  }
  function onSQLRun() {
    // SQL编辑器运行事件
    executeSql()
  }
  function executeSql() {
    // 执行SQL语句
    console.log(code.value)
    if (code.value.trim() === '') {
      // this.$message.warning('请先编辑 SQL 命令!')
      return
    }
    runLoading.value = true
    tableData.arr = []
    columns.arr = []
    runResult.value = '执行中...'
    runType.value = 0
    // errMsg = {}
    // const res = await this.sendSQL({ sql: code.value })
    // const { type, result } = res
    // if (code.value.startsWith('SELECT')) {
    //   if (type === '1') {
    //     runType.value = 1 
    //     runResult.value = `成功查询 ${result.length} 条数据`
    //     if (result.length > 0) {
    //       tableData.arr = [...result]
    //       const obj = { ...tableData.arr[0] }
    //       for(let key in obj) {
    //         columns.arr.push({
    //           label: key,
    //           value: key
    //         })
    //       }
    //     }
    //   } else if (type === '2') {
    //     runType.value = 3
    //     runResult.value = '执行失败!'
    //     errMsg = {...result}
    //   }
    // } else {
    //   if (type === '1') {
    //     runType.value = 1
    //     runResult.value = `执行成功!${result.affectedRows}行数据受影响`
    //     queryTables()
    //   } else if (type === '2') {
    //     runType.value = 3
    //     runResult.value = '执行失败!'
    //     errMsg = {...result}
    //   }
    // }
    runLoading.value = false
  }
  // product传值
  function getdblclickData(val){
    debugger
    if(code.value){
      code.value = code.value +' '+ val
    }else{
      code.value = val
    }
    czshow.value = code.value
    czshow.type = '双击'
    console.log(code.value,'code传值')
  }
</script>
<style lang="scss">
  .editprocess{
    width: 100%;
    height: 95%;
    &-top{
      display: flex;
      justify-content: space-between;
      .title{
        font-size: 1.8rem;
        margin-bottom: 2rem;
        font-family: PingFang SC;
        color: rgba(63, 63, 63, 0.6);
      }
    }
    &-contain{
      width: 100%;
      height: 100%;
      &-left{
        width: 20%;
        height: 100%;
        &-toptitle{
          text-align: center;
          font-size: 20px;
          font-family: PingFang SC;
          font-weight: bold;
          color: #3F3F3F;
        }
        &-product{
          overflow: auto;
          width: 100%;
          height: 96%;
        }
      }
      .mapContainer{
        width: 80%;
        height: 100%;
        padding: 10px;
        &-righttop{
          width: 100%;
          height: 50%;
        }
        &-rightbottom{
          width: 100%;
          height: 50%;
          .tablesresult {
            width: 100%;
            height: 100%;
            box-sizing: border-box;
            padding: 5px 0 0 1px;
            .el-tabs {
              height: 100%;
              .el-tab-pane {
                font-size: 12px;
                height: 100%;          
                .error-message {
                  margin: 8px 0px;
                  box-sizing: border-box;
                  padding-left: 14px;
                }
              }
              .el-table {
                // border: 1px solid red;
                // height: 100px;
                height: 84%;
                overflow-y: scroll;
                font-size: 12px;
              }
              .el-table::-webkit-scrollbar {
                width: 5px;
              }
              .el-table::-webkit-scrollbar-thumb {
                border-radius: 10px;
                background: rgba(0, 0, 0, 0.2);
              }
              .el-tabs__item{
                font-size: 12px;
              }
            }
            .el-tabs__content{
              border: none;
              border-radius: 0px;
              // margin: 1rem;
              // height: 82%;
            }
          }
        }
      }
    }
  }
</style>

结果

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue 3使用CodeMirror需要以下步骤和配置: 1. 首先,你需要通过命令行安装vue-codemirror插件。可以使用以下命令进行安装: ``` npm install vue-codemirror --save ``` 2. 然后,在你的Vue组件,引入CodeMirror和相关样式的依赖。你可以在组件的`<script>`标签添加以下代码: ```javascript import VueCodeMirror from 'vue-codemirror' import 'codemirror/lib/codemirror.css' import 'codemirror/theme/your-theme.css' // 替换"your-theme"为你想要的主题名称 import 'codemirror/mode/javascript/javascript' // 如果你想使用JavaScript语法高亮,需要引入对应的语言模式文件 ``` 3. 接下来,将`VueCodeMirror`作为组件的局部组件或全局组件进行注册。你可以在组件的`<script>`标签添加以下代码: ```javascript export default { components: { VueCodeMirror }, // ... } ``` 4. 最后,在你的Vue模板使用`vue-codemirror`组件。你可以在模板添加以下代码: ```html <vue-codemirror :options="yourOptions" v-model="yourModel"></vue-codemirror> ``` 这里的`yourOptions`是CodeMirror的配置选项,你可以根据需求进行相应的配置。`yourModel`是你绑定的数据模型,它将保存CodeMirror编辑器的内容。 需要注意的是,如果你在运行官方示例时遇到了`@codemirror/lang-javascript`或`@codemirror/theme-one-dark`的报错,可能是由于相关依赖包没有安装或版本不匹配导致的。你可以尝试更新这些依赖包的版本,或者检查是否正确安装了相关依赖。 希望这个解答对你有帮助!<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [前端插件库之vue3使用vue-codemirror插件](https://blog.csdn.net/weixin_46372074/article/details/124994320)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值