相信写OJ的小伙伴都会遇到一个问题,那就是在线编辑器的实现。到底要选哪个呢

其实开源的编辑器有挺多的比如Ace、CodeMirror、MonacoEditor、CodeFlask、EditArea。详细的可以看看这个Top 5: 用Javascript编写的最佳代码编辑器插件, Top 5: Best code editor plugins written in Javascript

我第一个用的是MonacoEditor,这东西对webpack最新版跟webpack会起冲突,当时搞了我一下午,我也不知道前一天晚上是怎么成功的。。。

接着我又翻到了一些较为详细的Ace使用文档,然后对比了一下发现与AcWing是一样的,hhh。这让我瞬间来了动力。记得当时是先用了基于vue二次开发的,但是文档都不是很清楚,所以后来就直接用官方的了。顺带提一下qdoj用的是CodeMirror。

话不多说我们先看看效果图吧(完全和AcWing一样,2333

图1
图2
图3
图4

我用的是Element-ui,其实这些实现起来不是特别难,但是对于我这个后端废物来说,还是花了我很多时间的,有一说一,前端还是挺麻烦的。

那接着就上代码吧

我就只给编辑器的代码好了,不然太多就显得一团糟,毕竟我还没把它们分出来,哈哈哈

// 首先html部分就这么一行,ref是vue选中标签需要给上的属性,看到的文档都说不要用id,说是会有问题,感兴趣的小伙伴也可以试试
<div ref="ace" class="ace"></div>

<script>
// 导入需要的包
import ace from 'ace-builds'
import 'ace-builds/webpack-resolver'; // 在 webpack 环境中使用必须要导入
//解决添加提示时控制台警告(提示必须引入的包)
import "ace-builds/src-noconflict/ext-language_tools"
import "ace-builds/src-noconflict/ext-emmet"
//语言提示
import 'ace-builds/src-noconflict/snippets/javascript'
import 'ace-builds/src-noconflict/snippets/c_cpp'
import 'ace-builds/src-noconflict/snippets/java'
import 'ace-builds/src-noconflict/snippets/golang'
import 'ace-builds/src-noconflict/snippets/python'
//输入类型
import 'ace-builds/src-noconflict/keybinding-emacs'
import 'ace-builds/src-noconflict/keybinding-vim'
import 'ace-builds/src-noconflict/keybinding-vscode'
//以下是在vue中使用,其余的自己根据文档更改
export default {
    name: 'CodeEditor',
    props: {
        value: {
        type: String,
        required: true
        }
    },
    data() {
        return {
            themeValue: 'ambiance',
            aceEditor: null,
            themePath: 'ace/theme/monokai', // 不导入 webpack-resolver,该模块路径会报错
            modePath: 'ace/mode/c_cpp', // 同上
            codeValue: this.value || ''
        }
    }
    mounted() {
    this.aceEditor = ace.edit(this.$refs.ace,{
      maxLines: 1000, // 最大行数,超过会自动出现滚动条
      minLines: 22, // 最小行数,还未到最大行数时,编辑器会自动伸缩大小
      fontSize: 14, // 编辑器内字体大小
      theme: this.themePath, // 默认设置的主题
      mode: this.modePath, // 默认设置的语言模式
      tabSize: 4, // 制表符设置为 4 个空格大小
      readOnly: false, //只读
      highlightActiveLine: true,
      value: this.codeValue
    });
    this.aceEditor.setOptions({
      enableSnippets: true,
      enableLiveAutocompletion: true,
      enableBasicAutocompletion: true
    });
    // 快捷键
    // this.aceEditor.commands.addCommand({
    //   name: 'myCommand',
    //   bindKey: {win: 'Ctrl-M',  mac: 'Command-M'},
    //   exec: function(editor) {
    //     //...
    //   },
    //   readOnly: true // false if this command should not apply in readOnly mode
    // });
  }
}
</script>
<style scoped lang="scss">
.ace {
  position: relative !important;
  border: 1px solid lightgray;
  margin: auto;
  height: auto;
  width: 100%;
}
</style>
引用组件时记得绑定value
<CodeEditor v-bind:value="''"></CodeEditor>

想了一下还是给全部代码吧,因为我没有拆分,所以全部代码就是我截图的整个页面

<template>
  <div class="Ace">
    <div class="code_tool_bar">
      <span style="position: relative;left: 10px; top: 16px; font-size: 18px;">写代码啦</span>
      <el-button icon="el-icon-s-tools" @click="dialogTableVisible = true" style="float: right;margin: 10px;"></el-button>
      <el-button icon="el-icon-refresh" @click="refresh_code" style="float: right; margin: 10px;"></el-button>
      <el-select v-model="selectLanguageValue" @change="changSelectValue1" filterable style="float: right;margin: 10px;">
        <el-option
          v-for="item in languagesOptions"
          :key="item.label"
          :label="item.label"
          :value="item.value">
        </el-option>
      </el-select>
      <el-dialog :visible.sync="dialogTableVisible"
                 :append-to-body="true"
                 top="40px"
                 width="39.194%"
                 :destroy-on-close="true"
                 :show-close="false"
                 custom-class="code-editor-config-dialog">
        <el-card style="margin: -59px -20px 0 -20px;"
                 shadow="never">
          <div slot="header" class="clearfix">
            <span>代码编辑器设置</span>
            <el-button style="float: right; padding: 3px 0" type="text" @click="dialogTableVisible = false">x</el-button>
          </div>
          <div class="row">
            <el-row>
              <el-col :span="16">
                <div class="code-editor-option-title">主题</div>
                <div class="code-editor-option-description">对白色界面感到厌倦了吗?可以尝试其他的背景和代码高亮风格。</div>
              </el-col>
              <el-col :span="8">
                <el-select v-model="selectThemeValue"
                           @change="changSelectValue"
                           filterable>
                  <el-option
                    v-for="item in themesOptions"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value">
                  </el-option>
                </el-select>
              </el-col>
            </el-row>
            <hr>
          </div>
          <div class="row">
            <el-row>
              <el-col :span="16">
                <div class="code-editor-option-title">编辑类型</div>
                <div class="code-editor-option-description">更喜欢Vim或者Emacs的输入方式吗?我们也为你提供了这些选项。</div>
              </el-col>
              <el-col :span="8">
                <el-select v-model="selectEditorValue"
                           @change="setEditorMode"
                           filterable>
                  <el-option
                    v-for="item in editorOption"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value">
                  </el-option>
                </el-select>
              </el-col>
            </el-row>
            <hr>
          </div>
          <div class="row">
            <el-row>
              <el-col :span="16">
                <div class="code-editor-option-title">缩进长度</div>
                <div class="code-editor-option-description">选择代码缩进的长度。默认是4个空格。</div>
              </el-col>
              <el-col :span="8">
                <el-select v-model="selectTabValue"
                           @change="setTabSize"
                           filterable>
                  <el-option
                    v-for="item in tabOption"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value">
                  </el-option>
                </el-select>
              </el-col>
            </el-row>
            <hr>
          </div>
          <div class="row">
            <el-row>
              <el-col :span="16">
                <div class="code-editor-option-title">主题</div>
                <div class="code-editor-option-description">对白色界面感到厌倦了吗?可以尝试其他的背景和代码高亮风格。</div>
              </el-col>
              <el-col :span="8">
                <el-select v-model="selectThemeValue"
                           @change="changSelectValue"
                           filterable>
                  <el-option
                    v-for="item in themesOptions"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value">
                  </el-option>
                </el-select>
              </el-col>
            </el-row>
          </div>
        </el-card>
        <el-button @click="dialogTableVisible = false" style="margin: 20px 480px -12px 480px">确 定</el-button>
      </el-dialog>
    </div>
    <div ref="ace" class="ace"></div>
    <el-button class="submitBtn" round type="success" ><i class="fa fa-cloud-upload" aria-hidden="true"></i>提交代码</el-button>
    <el-button class="debuggerBtn" round @click="debuggerCode" ><i class="fa fa-play-circle-o" aria-hidden="true"></i>调试代码</el-button>
  </div>
</template>

<script>
import ace from 'ace-builds'
import 'ace-builds/webpack-resolver'; // 在 webpack 环境中使用必须要导入
//解决添加提示时控制台警告(提示必须引入的包)
import "ace-builds/src-noconflict/ext-language_tools"
import "ace-builds/src-noconflict/ext-emmet"
//语言提示
import 'ace-builds/src-noconflict/snippets/javascript'
import 'ace-builds/src-noconflict/snippets/c_cpp'
import 'ace-builds/src-noconflict/snippets/java'
import 'ace-builds/src-noconflict/snippets/golang'
import 'ace-builds/src-noconflict/snippets/python'
//输入类型
import 'ace-builds/src-noconflict/keybinding-emacs'
import 'ace-builds/src-noconflict/keybinding-vim'
import 'ace-builds/src-noconflict/keybinding-vscode'

export default {
  name: 'CodeEditor',
  props: {
    value: {
      type: String,
      required: true
    }
  },
  methods: {
    changSelectValue(value) {
      this.aceEditor.setTheme(`ace/theme/${value}`);
    },
    refresh_code() {
      this.aceEditor.session.setValue('');
    },
    changSelectValue1(value) {
      this.aceEditor.session.setMode(`ace/mode/${value}`);
    },
    debuggerCode() {
      let value = this.aceEditor.session.getValue();
      console.log(value);
    },
    setTabSize(size) {
      this.aceEditor.session.setTabSize(size);
    },
    setEditorMode(value) {
      this.aceEditor.setKeyboardHandler(`ace/keyboard/${value}`);
    }
  },
  data() {
    return {
      dialogTableVisible: false,
      themeValue: 'ambiance',
      aceEditor: null,
      themePath: 'ace/theme/monokai', // 不导入 webpack-resolver,该模块路径会报错
      modePath: 'ace/mode/c_cpp', // 同上
      codeValue: this.value || '',
      editorOption:[{
        value: 'vscode',
        label: 'Standard'
      },{
        value: 'vim',
        label: 'Vim'
      },{
        value: 'emacs',
        label: 'Emacs'
      }],
      selectEditorValue: 'Standard',
      tabOption:[{
        value: '2',
        label: '2个空格'
      },{
        value: '4',
        label: '4个空格'
      },{
        value: '6',
        label: '6个空格'
      }],
      selectTabValue: '4个空格',
      themesOptions: [{
        value: 'crimson_editor',
        label: 'CrimsonEditor'
      },{
        value: 'monokai',
        label: 'Monokai'
      },{
        value: 'terminal',
        label: 'Terminal'
      },{
        value: 'xcode',
        label: 'Xcode'
      }],
      selectThemeValue: 'Monokai',
      languagesOptions: [{
        value: 'c_cpp',
        label: 'C++'
      },{
        value: 'c_cpp',
        label: 'C'
      },{
        value: 'java',
        label: 'Java'
      },{
        value: 'golang',
        label: 'Golang'
      },{
        value: 'python',
        label: 'Python'
      },{
        value: 'javascript',
        label: 'Javascript'
      }],
      selectLanguageValue: 'C++'
    };
  },
  mounted() {
    this.aceEditor = ace.edit(this.$refs.ace,{
      maxLines: 1000, // 最大行数,超过会自动出现滚动条
      minLines: 22, // 最小行数,还未到最大行数时,编辑器会自动伸缩大小
      fontSize: 14, // 编辑器内字体大小
      theme: this.themePath, // 默认设置的主题
      mode: this.modePath, // 默认设置的语言模式
      tabSize: 4, // 制表符设置为 4 个空格大小
      readOnly: false, //只读
      highlightActiveLine: true,
      value: this.codeValue
    });
    this.aceEditor.setOptions({
      enableSnippets: true,
      enableLiveAutocompletion: true,
      enableBasicAutocompletion: true
    });
    // 快捷键
    // this.aceEditor.commands.addCommand({
    //   name: 'myCommand',
    //   bindKey: {win: 'Ctrl-M',  mac: 'Command-M'},
    //   exec: function(editor) {
    //     //...
    //   },
    //   readOnly: true // false if this command should not apply in readOnly mode
    // });
  },
  watch: {
    value(newVal) {
      console.log(newVal);
      this.aceEditor.setValue(newVal);
    }
  }
};
</script>

<style scoped lang="scss">
.code_tool_bar {
  height: 60px;
  width: 100%;
  background: #f8f9fa;
  border: 1px solid #c2c7d0;
  margin-bottom: 0;
}

.code-editor-option-title {
  font-size: 17px;
  margin-bottom: 10px;
}

.code-editor-option-description {
  font-size: 13px;
  color: grey;
}

hr {
  background: none !important;
  height: 1px !important;
  border: 0 !important;
  border-top: 1px solid #eee !important;
  margin-top: 20px;
  margin-bottom: 20px;
}

.ace {
  position: relative !important;
  border: 1px solid lightgray;
  margin: auto;
  height: auto;
  width: 100%;
}
.debuggerBtn {
  float: right;
  margin: 13px 20px 0 0;
}
.submitBtn {
  float: right;
  margin: 13px 0 0 0;
}
</style>

好了,在线代码编辑器的分享就告一段落了,因为是网页上的东西,安全是一个很重要的考虑因素,比如csrf亦或者是xss,在提交的时候前后端都应该去过滤一下代码。有空的话再介绍一下Ace实现的MarkDown编辑器吧(也是一样的哦~)

Q.E.D.


都懂一点,不是很懂的小捞仔