vue 使用 vue-codemirror

vue 使用 vue-codemirror 的编辑器组件


依赖:

npm install jsonlint

npm install jshint

npm install script-loader

npm install vue-codemirror

npm install element-ui

编辑器组件封装代码


<template>
  <div class="code-mirror-div">
    <div class="tool-bar" ref="toolBar" v-if="showTool">
      <slot name="tool_before"/>
        <el-select v-model="cmOptions.theme" placeholder="请选择" size="mini" style="width:150px">
          <el-option v-for="item in cmThemeOptions" :key="item" :label="item" :value="item"></el-option>
        </el-select>
      <slot name="tool_after"/>
    </div>
    <div :style="{ height:codeMirrorHeight }">
      <codemirror
        ref="myCm"
        :value="editorValue"
        :options="{...cmOptions,...userOption}"
        @input="$emit('input',$event)"
        @changes="onCmCodeChanges"
        @blur="onCmBlur"
        @keydown.native="onKeyDown"
        @mousedown.native="onMouseDown"
        @paste.native="OnPaste"
      ></codemirror>
    </div>
  </div>
</template>

<script>
import { codemirror } from "vue-codemirror";
import "./codemirror.js"

export default {
  name: "CodeEditor",
  components: {
    codemirror,
  },
  model: {
    prop: 'code',
    event: 'input'
  },
  data() {
    return {
      codeMirrorHeight:'100%',
      editorValue: this.code,
      userOption:{},
      cmThemeOptions: [
        "default",
        "3024-day",
        "3024-night",
        "abcdef",
        "ambiance",
        "ayu-dark",
        "ayu-mirage",
        "base16-dark",
        "base16-light",
        "bespin",
        "blackboard",
        "cobalt",
        "colorforth",
        "darcula",
        "dracula",
        "duotone-dark",
        "duotone-light",
        "eclipse",
        "elegant",
        "erlang-dark",
        "gruvbox-dark",
        "hopscotch",
        "icecoder",
        "idea",
        "isotope",
        "lesser-dark",
        "liquibyte",
        "lucario",
        "material",
        "material-darker",
        "material-palenight",
        "material-ocean",
        "mbo",
        "mdn-like",
        "midnight",
        "monokai",
        "moxer",
        "neat",
        "neo",
        "night",
        "nord",
        "oceanic-next",
        "panda-syntax",
        "paraiso-dark",
        "paraiso-light",
        "pastel-on-dark",
        "railscasts",
        "rubyblue",
        "seti",
        "shadowfox",
        "solarized dark",
        "solarized light",
        "the-matrix",
        "tomorrow-night-bright",
        "tomorrow-night-eighties",
        "ttcn",
        "twilight",
        "vibrant-ink",
        "xq-dark",
        "xq-light",
        "yeti",
        "yonce",
        "zenburn"
      ],
      cmEditorModeOptions: [
          "default",
          "json",
          "sql",
          "javascript",
          "xml",
          "html",
          "yaml",
          "markdown",
          "python"
      ],
      cmOptions: {
        theme: 'idea', 
        mode: 'application/json', 
        lineWrapping: true, 
        lineNumbers: true, 
        autofocus: true, 
        smartIndent: false, 
        autocorrect: true, 
        spellcheck: true, 
        hintOptions: this.cmHintOptions||{},
        extraKeys: {
          "Alt-Q": "autocomplete", 
          "Ctrl-Alt-L": () => {
            try {
              if (
                this.cmOptions.mode == "application/json" &&
                this.editorValue
              ) {
                this.editorValue = this.formatStrInJson(
                  this.editorValue
                );
              }
            } catch (e) {
              this.$message.error(
                "格式化代码出错:" + e.toString()
              );
            }
          }                    
        },
        lint: true,
        gutters: [
          "CodeMirror-lint-markers", 
          "CodeMirror-linenumbers", 
          "CodeMirror-foldgutter" 
        ],
        foldGutter: true,
        autoCloseBrackets: true, 
        autoCloseTags: true,
        matchTags: { bothTags: true },
        matchBrackets: true, 
        styleActiveLine: true, 
        autoRefresh: true, 
        highlightSelectionMatches: {
            minChars: 2,
            style: "matchhighlight", 
            showToken: true 
        },
        styleSelectedText: true,
        enableAutoFormatJson: true,
        defaultJsonIndentation: 2 
      },
      enableAutoFormatJson: true,
      defaultJsonIndentation: 2 
    }
  },
  props: {
    cmHintOptions: {
      type: Object,
      default() {
        return {}
      }
    },
    code: {
      type: String,
      default: ''
    },
    options: {
      type: Object,
      default() {
        return {}
      }
    },
    showTool: {
      type: Boolean,
      default: false
    }
  },
  computed: {
    myCodemirror () {
      return this.$refs.mirror.codemirror
    },
    inCode: {
      get() {
        return this.code
      },
      set() {}
    },
  },
  watch: {
    'cmOptions.theme': function(newValue, oldValue) {
      try {
        let theme =
        this.cmOptions.theme == "default" ? "blackboard" : newValue;
        require("codemirror/theme/" + theme + ".css");
        this.cmOptions.theme = theme;
        this.resetLint();
      } catch (e) {
        this.$message.error("切换编辑器主题出错:" + e.toString());
      }
    },
    options: {
      handler(n,o) {
        if(Object.keys(n).length) {
          this.userOption = JSON.parse(JSON.stringify(n))
          if(this.userOption.mode) this.userOption.mode = this.onEditorModeChange(this.userOption.mode)
        }
      },
      deep: true,
      immediate: true
    },
    code(n,o) {
      if(n!=this.editorValue) {
        try {
          this.editorValue = this.formatStrInJson(n)
        } catch (error) {
          this.editorValue = n
          // 啥也不做
        }
      }
    }
  },
  mounted() {
    this.codeMirrorHeight = this.showTool?`calc( 100% - ${this.$refs.toolBar.offsetHeight+10}px )`:'100%'
  },
  created() {
    try {
      if (!this.editorValue) {
        this.cmOptions.lint = false;
        return;
      }
      if (this.cmOptions.mode == "application/json") {
        if (!this.enableAutoFormatJson) {
            return;
        }
        this.editorValue = this.formatStrInJson(this.editorValue);
        
      }
    } catch (e) {
      // console.log("初始化codemirror出错:" + e);
      // this.$message.error("初始化codemirror出错:" + e);
    }
  },
  methods:{
    onEditorModeChange(value) {
      switch (value) {
        case "json":
            return "application/json";
        case "sql":
            return "sql";
        case "javascript":
            return "javascript";
        case "xml":
            return "xml";
        case "css":
            return "css";
        case "html":
            return "htmlmixed";
        case "yaml":
            return "yaml";
        case "markdown":
            return "markdown";
        case "python":
            return "python";
        default:
        return "application/json";
      }
    },
    resetLint() {
      if (!this.$refs.myCm.codemirror.getValue()) {
        this.$nextTick(() => {
            this.$refs.myCm.codemirror.setOption("lint", false);
        });
        return;
      }
      this.$refs.myCm.codemirror.setOption("lint", false);
      this.$nextTick(() => {
        this.$refs.myCm.codemirror.setOption("lint", true);
      });
    },
    resetFoldGutter() {
      this.$refs.myCm.codemirror.setOption("foldGutter", false);
      this.$nextTick(() => {
          this.$refs.myCm.codemirror.setOption("foldGutter", true);
      });
    },
    // 获取值
    getValue() {
        try {
          return this.$refs.myCm.codemirror.getValue();
        } catch (e) {
          let errorInfo = e.toString();
          this.$message.error("获取编辑框内容失败:" + errorInfo);
          return errorInfo;
        }
    },
    // 修改值
    setValue(value) {
      try {
        if (typeof value != typeof "") {
            this.$message.error(
                "修改编辑框内容失败:编辑宽内容只能为字符串"
            );
            return;
        }
        if (this.cmOptions.mode == "application/json") {
            this.editorValue = this.formatStrInJson(value);
        } else {
            this.editorValue = value;
        }
      } catch (e) {
        this.$message.error("修改编辑框内容失败:" + e.toString());
      }
    },
    // 黏贴事件处理函数
    OnPaste(event) {
      if (this.cmOptions.mode == "application/json") {
        try {
            this.editorValue = this.formatStrInJson(this.editorValue);
        } catch (e) {
            // 啥都不做
        }
      }
    },
    // 失去焦点时处理函数
    onCmBlur(cm, event) {
      this.$emit('onCmBlur',cm.getValue())
      try {
        let editorValue = cm.getValue();
        if (this.cmOptions.mode == "application/json" && editorValue) {
          if (!this.enableAutoFormatJson) {
              return;
          }
          this.editorValue = this.formatStrInJson(editorValue);
        }
      } catch (e) {
        // 啥也不做
      }
    },
    // 按下键盘事件处理函数
    onKeyDown(event) {
      const keyCode = event.keyCode || event.which || event.charCode;
      const keyCombination =
          event.ctrlKey || event.altKey || event.metaKey;
      //满足条件触发代码提示
      if (!keyCombination && keyCode > 64 && keyCode < 123) {
        this.$refs.myCm.codemirror.showHint({ completeSingle: false });
      }
    },
    // 按下鼠标时事件处理函数
    onMouseDown(event) {
      //取消代码提示
      this.$refs.myCm.codemirror.closeHint();
    },
    onCmCodeChanges(cm, changes) {
      this.editorValue = cm.getValue();
      this.resetLint();
    },
    // 格式化字符串为json格式字符串
    formatStrInJson(strValue) {
      this.$emit('checkJson',strValue)
      return JSON.stringify(
        // JSON.parse(strValue.replace(/\s/g, '')),
        JSON.parse(strValue),
        null,
        this.defaultJsonIndentation
      );
    },
  },
}
</script>
<style lang="scss">
.CodeMirror-hints {
  z-index: 3330 !important;
}
.code-mirror-div {
  height: 100%;
 .tool-bar {
   margin: 10px 0;
 }
}
.CodeMirror {
  height: 100%;
  border: 1px solid #ccc;
  // border-bottom: 0px;
}
.CodeMirror-selected {
  background-color: blue !important;
}

.CodeMirror-selectedtext {
  color: white !important;
}
.cm-matchhighlight {
  background-color: #fef6f6;
}
.CodeMirror-scroll {
  margin: 0;
  padding: 0;
}
.vue-codemirror {
  height: 100%;
}
</style>

codemirror.js

import Vue from 'vue'
import { codemirror } from "vue-codemirror"
import 'codemirror/lib/codemirror.css'

Vue.use(codemirror);
import "codemirror/theme/blackboard.css";
import 'codemirror/theme/eclipse.css'
import "codemirror/mode/javascript/javascript.js"; 
import "codemirror/mode/xml/xml.js";
import "codemirror/mode/htmlmixed/htmlmixed.js"; 
import "codemirror/mode/css/css.js";
import "codemirror/mode/yaml/yaml.js";
import "codemirror/mode/sql/sql.js";
import "codemirror/mode/python/python.js";
import "codemirror/mode/markdown/markdown.js";
import "codemirror/addon/hint/show-hint.css";
import "codemirror/addon/hint/show-hint.js"; 
import "codemirror/addon/hint/javascript-hint.js";
import "codemirror/addon/hint/xml-hint.js";
import "codemirror/addon/hint/css-hint.js"; 
import "codemirror/addon/hint/html-hint.js";
import "codemirror/addon/hint/sql-hint.js";
import "codemirror/addon/hint/anyword-hint.js"; 
import "codemirror/addon/lint/lint.css";
import "codemirror/addon/lint/lint.js"; 
import "codemirror/addon/lint/json-lint"; 
require("script-loader!jsonlint");
// import "codemirror/addon/lint/css-lint.js";  
import "codemirror/addon/lint/javascript-lint.js"; 
import "codemirror/addon/fold/foldcode.js"; 
import "codemirror/addon/fold/foldgutter.js"; 
import "codemirror/addon/fold/foldgutter.css";
import "codemirror/addon/fold/brace-fold.js"; 
import "codemirror/addon/fold/xml-fold.js";
import "codemirror/addon/fold/comment-fold.js"; 
import "codemirror/addon/fold/markdown-fold.js"; 
import "codemirror/addon/fold/indent-fold.js"; 
import "codemirror/addon/edit/closebrackets.js"; 
import "codemirror/addon/edit/closetag.js";
import "codemirror/addon/edit/matchtags.js"; 
import "codemirror/addon/edit/matchbrackets.js";
import "codemirror/addon/selection/active-line.js"; 
import "codemirror/addon/search/jump-to-line.js"; 
import "codemirror/addon/dialog/dialog.js";
import "codemirror/addon/dialog/dialog.css";
import "codemirror/addon/search/searchcursor.js"; 
import "codemirror/addon/search/search.js"; 
import "codemirror/addon/display/autorefresh.js"; 
import "codemirror/addon/selection/mark-selection.js"; 
import "codemirror/addon/search/match-highlighter.js"; 

组件简单使用

checkJson: 用于json的格式化

<code-editor v-model="code" showTool @checkJson="checkJson" :options="{ mode:'json' }">
  <template slot="tool_after">
     <search-item label="模板名称">
       <el-input size='mini' v-model="newTemObj.templateName"></el-input>
     </search-item>
     <search-item label="模板描述">
       <el-input size='mini' v-model="newTemObj.templateRemark"></el-input>
     </search-item>
  </template>
</code-editor>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容