基于Vue2.X/Vue3.X对Monaco Editor在线代码编辑器进行封装与使用

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 这篇文章介绍了如何在Vue 2.X和Vue 3.X项目中封装和使用Monaco Editor在线代码编辑器,包括安装所需依赖、创建封装组件、在父组件中调用以及处理Vue 3中可能遇到的问题。

最近有个需求是显示日志模块的信息,用了好多在线代码编辑器,如各式各样的 markdown 啥的,都不太好使......

最后发现微软的 Monaco Editor 在线代码编辑器,这个插件就是牛!对此进行基于Vue2.X/Vue3.X的封装和使用。

// 查看 xxx 版本
格式:npm view xxx versions --json
举例:npm view monaco-editor versions --json

// monaco-editor 插件,必须
npm install monaco-editor --save-dev

// monaco-editor-webpack-plugin 插件,非必须
npm install monaco-editor-webpack-plugin --save-dev

// monaco-editor-nls 插件,非必须
npm install monaco-editor-nls --save-dev

// monaco-editor-esm-webpack-plugin 插件,非必须
npm install monaco-editor-esm-webpack-plugin --save-dev

// 引入 font-awesome 图标库,非必须
npm install font-awesome --save

父组件:index.vue

<template>
  <div class="monaco" style="padding: 100px;">
    <div class="monaco-left">
      <p align="center">
        <el-button type="primary" @click="handleMEContentChangeClick">OK</el-button>
      </p>
    </div>

    <div class="monaco-right">
      <MonacoEditor
        class="monaco-editor"
        ref="monacoEditorRef"
        :editorParams="editorParams">
      </MonacoEditor>
    </div>
  </div>
</template>

<script>
import MonacoEditor from './components/monacoEditor'

export default {
   
   
  components: {
   
   
    MonacoEditor
  },
  data: () => ({
   
   
    // editorParams 必填非空的代码编辑器参数
    editorParams: {
   
   
      id: 'monaco_ed_1',// 编辑器DOM节点ID
      title: '',// 编辑器标题
      content: '',// 编辑器内容
      height: '100%',// 编辑器高度
      readOnly: false,// 编辑器是否禁用
      isScrollToBottom: true // 是否滚动到底部
    }
  }),
  methods: {
   
   
    /**
     * 改变编辑器内容
     */
    handleMEContentChangeClick() {
   
   
      this.editorParams.content = 
        "\n" +
        "  .   ____          _            __ _ _\n" +
        " /\\\\ / ___'_ __ _ _(_)_ __  __ _ \\ \\ \\ \\\n" +
        "( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\\n" +
        " \\\\/  ___)| |_)| | | | | || (_| |  ) ) ) )\n" +
        "  '  |____| .__|_| |_|_| |_\\__, | / / / /\n" +
        " =========|_|==============|___/=/_/_/_/\n" +
        " :: Spring Boot ::        (v2.2.5.RELEASE)\n" +
        "\n" +
        new Date() +
        "\n" +
        Math.random()
    }
  }
}
</script>

<style lang="less" scoped>
  .monaco {
   
   
    width: calc(100% - 200px);
    height: calc(100% - 200px);
    position: relative;
    display: flex;

    .monaco-left {
   
   
      width: 600px;
      height: 100%;
      background-color: #f8f8f8;
    }

    .monaco-right {
   
   
      width: calc(100% - 600px);
      height: 100%;
      background-color: #ff9b9b;

      .monaco-editor {
   
   
        height: 100%;
        overflow: hidden;
      }
    }
  }
</style>

子组件:monacoEditor.vue

<template>
  <div class="m-e" id="m-e-id">
    <div class="m-e-main">
      <div class="m-e-main_toolbar" :style="isThemeLightOrBlack ? 'background-color: #fff; box-shadow: 0px 2px 5px #ddd;' : 'background-color: #1e1e1e; box-shadow: 0px 2px 5px #111;'">
        <div class="m-e-main_toolbar_left">
          <span>日志 - {
   
   {
   
    title }}</span>
        </div>

        <div class="m-e-main_toolbar_right" :style="isThemeLightOrBlack ? 'color: #000' : 'color: #fff'">
          <a title="查找" @click="findByKeyword"><i class="fa fa-search"/></a>
          <a title="回到顶部" @click="scrollToTop"><i class="fa fa-chevron-circle-up"/></a>
          <a title="回到底部" @click="scrollToBottom"><i class="fa fa-chevron-circle-down"/></a>
          <a title="是否截断换行" @click="setEditorWordWrap"><i class="fa fa-bars"/></a>
          <a title="切换白天或暗夜模式" @click="setEditorTheme"><i class="fa fa-adjust"/></a>
          <a :title="fullScreen ? '退出全屏' : '全屏显示'" @click="handleFullScreenClick"><i :class="fullScreen ? 'fa fa-compress' : 'fa fa-arrows-alt'"/></a>
          <a title="下载日志" @click="handleDownloadLogClick"><i class="fa fa-download" style="position: relative; top: 2px"/></a>
        </div>
      </div>
      <div :id="id" class="m-e-main_container" :style="'height: ' + height"></div>
    </div>
  </div>
</template>

<script>
// 引用 font-awesome 资源
import 'font-awesome/css/font-awesome.min.css';

// 先汉化 monaco
import {
   
    setLocaleData } from "monaco-editor-nls"
import zh_CN from "monaco-editor-nls/locale/zh-hans"
setLocaleData(zh_CN)

// 再加载 monaco ,才能汉化成功
import * as me from 'monaco-editor'

export default {
   
   
  props:[
    'editorParams'
  ],
  data: () => ({
   
   
    editor: null,// 编辑器对象
    id: null,// 编辑器DOM节点ID
    title: '',// 编辑器标题
    content: '',// 编辑器内容
    height: 'auto',// 编辑器高度
    readOnly: true,// 编辑器是否禁用
    isScrollToBottom: false, // 是否滚动到底部

    // 其他配置项...
    fullScreen: false,// 是否全屏状态
    wordWrap: false,// 当单行文本太长时截断换行,true 为换行,false 为不换行
    isThemeLightOrBlack: false,// 明亮或暗夜模式,true 为白天模式,false 为暗夜模式

  }),
  mounted() {
   
   
    /**
     * 监听全屏显示状态
     */
    let that = this;
    window.onresize = function() {
   
   
      if (!document.fullscreenElement) {
   
   
        that.fullScreen = false;
      } else {
   
   
        that.fullScreen = true;
      } 
    }
  },
  watch: {
   
   
    /**
     * 深度监听富文本参数
     */
    "editorParams": {
   
   
        handler: function(newVal, oldVal) {
   
   
          // console.log('newVal =>', newVal, ' | oldVal =>', oldVal);
          if (oldVal == null && newVal != null) {
   
   
            // 首次变化
            this.id = newVal.id;
            this.title = newVal.title;
            this.content = newVal.content;
            this.height = 'calc(' + newVal.height + ' - 42px)';
            this.readOnly = newVal.readOnly;
            this.isScrollToBottom = newVal.isScrollToBottom;
            this.initEditor(this.id, this.content, this.readOnly);
            if (this.isScrollToBottom) {
   
   
              this.scrollToBottom(); // 滚动到底部
            }
          } else if (newVal != null && newVal != null) {
   
   
            // 二次变化
            this.title = newVal.title;
            this.content = newVal.content;
            this.isScrollToBottom = newVal.isScrollToBottom;
            if (this.isScrollToBottom) {
   
   
              this.editor.setValue(this.content);
              this.scrollToBottom(); // 滚动到底部
            } else {
   
   
              this.editor.setValue(this.content);
            }
            // this.setEditorContent(this.content);
          }
        },
        immediate: true,
        deep: true
    },
  },
  methods: {
   
   
    /**
     * 实例化在线代码编辑器
     * 
     * 文档地址:https://microsoft.github.io/monaco-editor/api/index.html
     */
    async initEditor(id, content, readOnly) {
   
   
      // 异步获取节点,确保 Dom 节点已经渲染完成,不可删
      let dom = await document.getElementById(this.id);
      const monaco = require("monaco-editor/esm/vs/editor/editor.api");
      this.editor = monaco.editor.create(document.getElementById(id), {
   
   
          value: content,// 编辑器内容
          language: 'python',// 选择支持语言
          automaticLayout: true,// 是否自动布局
          theme: 'vs-dark',// 官方自带三种主题:vs、hc-black、vs-dark
          readOnly: readOnly,// 设置是否只读
          wordWrap: this.wordWrap ? 'on' : 'off',// 设置启用截断功能
          scrollBeyondLastLine: false,// 滚动完最后一行后再滚动一屏幕

          // 滚动条
          // scrollbar: {
   
   
          //   verticalScrollbarSize: 15,
          //   horizontalScrollbarSize: 15
          // },

          // 是否开启小地图
          minimap: {
   
   
            enabled: true
          },
      });

      // 设置编辑器滚动到最底部
      // this.scrollToBottom();
    },

    /**
     * 设置编辑器的内容且滚动到最底部
     */
    setEditorContent(val) {
   
   
      this.editor.setValue(val);
      this.scrollToBottom();
    },

    /**
     * 获取编辑器的内容
     */
    getEditorContent() {
   
   
      this.editor.getValue();
    },

    /**
     * 打开编辑器查找功能
     */
    findByKeyword() {
   
   
      try {
   
   
        // 先聚焦编辑器
        this.editor.focus();

        // 从模型中获取要查找的字符串范围 new Range(startLineNumber, startColumn, endLineNumber, endColumn)
        this.editor.setSelection(new me.Range(1, 9999, 1, 10000));

        // 触发查找操作
        // this.editor.getAction('actions.find').run();// 查找方式一
        this.editor.trigger('', 'actions.find');// 查找方式二
      } catch(error) {
   
   
        console.log(error);
      }
    },

    /**
     * 设置编辑器从只读变成可写
     */
    setEditorRW() {
   
   
      this.editor.updateOptions({
   
   readOnly: false});
    },

    /**
     * 设置编辑器开关截断功能
     */
    setEditorWordWrap() {
   
   
      this.wordWrap = this.wordWrap ? false : true;
      if (this.wordWrap) {
   
   
        this.editor.updateOptions({
   
   wordWrap: 'on'});
      } else {
   
   
        this.editor.updateOptions({
   
   wordWrap: 'off'});
      }
    },

    /**
     * 设置编辑器明亮或暗夜模式
     */
    setEditorTheme() {
   
   
      this.isThemeLightOrBlack = this.isThemeLightOrBlack ? false : true;
      if (this.isThemeLightOrBlack) {
   
   
        this.editor.updateOptions({
   
   theme: 'vs'});
      } else {
   
   
        this.editor.updateOptions({
   
   theme: 'vs-dark'});
      }
    },

    /**
     * 设置编辑器滚动到最顶部
     */
    scrollToTop() {
   
   
      this.editor.setScrollPosition({
   
   scrollTop: 0});
    },

    /**
     * 设置编辑器滚动到最底部
     */
    scrollToBottom() {
   
   
      // this.editor.revealLineInCenter(99999);
      this.editor.revealLine(this.editor.getModel().getLineCount());
    },

    /**
     * 全屏显示句柄
     */
    handleFullScreenClick () {
   
   
            const element = document.getElementById('m-e-id');
      if (!document.fullscreenElement) {
   
   
        element.requestFullscreen();
      } else {
   
   
        document.exitFullscreen();
      } 
    },

    /**
     * 下载日志句柄
     */
    handleDownloadLogClick() {
   
   
      this.exportFile(this.title, this.content);
    },

    /**
     * 下载日志
     */
    exportFile(name, data) {
   
   
      let url = window.URL || window.webkitURL || window;
      let blob = new Blob([data]);
      let event = document.createEvent("MouseEvents");
      event.initMouseEvent("click", true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
      let link = document.createElementNS("http://www.w3.org/1999/xhtml", "a");
      link.href = url.createObjectURL(blob);
      link.download = name;
      link.dispatchEvent(event);
    }
  },
  /**
   * 销毁在线代码编辑器
   */
  beforeDestroy() {
   
   
    if (this.monacoEditor) {
   
   
      this.monacoEditor.dispose()
    }
  },
}
</script>

<style lang="less" scoped>
  .m-e {
   
   
    width: 100%;
    height: 100%;

    .m-e-main {
   
   
      width: 100%;
      height: 100%;

      .m-e-main_toolbar {
   
   
        width: 100%;
        height: 40px;
        box-shadow: 0px 2px 5px #000;
        display: flex;
        position: relative;
        z-index: 99;

        .m-e-main_toolbar_left {
   
   
          flex: 1;
          overflow: hidden;

          span {
   
   
            display: block;
            font-size: 15px;
            padding-left: 10px;
            line-height: 26px;
            line-height: 40px;
            white-space:nowrap;/* 不换行 */
            overflow:hidden;/* 内容超出宽度时隐藏超出部分的内容 */
            text-overflow:ellipsis;/* 当对象内文本溢出时显示省略标记(...) ;需与overflow:hidden;一起使用。*/
            // user-select: none;
          }
        }

        .m-e-main_toolbar_right {
   
   
          margin-right: 15px;

          a {
   
   
            width: 16px;
            height: 16px;
            line-height: 16px;
            transition: ease all 0.3s ;
            text-align: center;
            display: inline-block;
            padding: 5px;
            cursor: pointer;
            border-radius: 2px;
            margin: 7px 0 7px 5px;

            i {
   
   
              font-size: 15px;
            }

            &:hover {
   
   
              background-color: rgba(255, 255, 255, 0.1);
            }
          }
        }
      }
    }
  }
</style>

最终效果:

以上代码在Vue3.X项目运行是有点问题的,页面会卡死,因为Vue3不再暴露子组件实例的属性和方法,但是却可以用toRaw方法获取到原始数据,也就是可以获取到子组件的实例,自然也就可以获取到实例的属性和方法。

例如获取编辑器内容方法,this.editor改为toRaw(this.editor)即可,还有其他方法也要改一下哦。

// 引入获取原始数据组件
import {
   
    toRaw } from 'vue'

/**
 * 获取编辑器的内容
 */
getEditorContent() {
   
   
  toRaw(this.editor).getValue();
},

好了,本次分享就到这里。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
11月前
|
JSON 小程序 前端开发
|
6月前
|
JSON 前端开发 数据可视化
AMIS【部署 01】amis前端低代码框架可视化编辑器amis-editor本地部署流程
AMIS【部署 01】amis前端低代码框架可视化编辑器amis-editor本地部署流程
987 0
|
2月前
|
JavaScript 前端开发 API
vue3 v-md-editor markdown编辑器(VMdEditor)和预览组件(VMdPreview )的使用
本文介绍了如何在Vue 3项目中使用v-md-editor组件库来创建markdown编辑器和预览组件。文章提供了安装步骤、如何在main.js中进行全局配置、以及如何在页面中使用VMdEditor和VMdPreview组件的示例代码。此外,还提供了一个完整示例的链接,包括编辑器和预览组件的使用效果和代码。
vue3 v-md-editor markdown编辑器(VMdEditor)和预览组件(VMdPreview )的使用
|
3月前
|
JavaScript
基于Vue2.X对WangEditor 5富文本编辑器进行封装与使用,支持单个或多个图片点击、粘贴、拖拽上传,Vue3.X项目也可直接使用
这篇文章介绍了如何在Vue 2.X项目中封装和使用WangEditor 5富文本编辑器,支持图片的点击、粘贴和拖拽上传,同时提到封装的组件也适用于Vue 3.X项目,并提供了详细的使用示例和后端配置。
230 1
基于Vue2.X对WangEditor 5富文本编辑器进行封装与使用,支持单个或多个图片点击、粘贴、拖拽上传,Vue3.X项目也可直接使用
|
3月前
|
存储 JavaScript 前端开发
Vue中通过集成Quill富文本编辑器实现公告的发布。Vue项目中vue-quill-editor的安装与使用【实战开发应用】
文章展示了在Vue项目中通过集成Quill富文本编辑器实现公告功能的完整开发过程,包括前端的公告发布、修改、删除操作以及后端的数据存储和处理逻辑。
Vue中通过集成Quill富文本编辑器实现公告的发布。Vue项目中vue-quill-editor的安装与使用【实战开发应用】
|
2月前
一款非常棒的十六进制编辑器 —— 010 Editor
一款非常棒的十六进制编辑器 —— 010 Editor
|
4月前
|
小程序
【微信小程序-原生开发】富文本编辑器 editor 的使用教程
【微信小程序-原生开发】富文本编辑器 editor 的使用教程
600 0
【微信小程序-原生开发】富文本编辑器 editor 的使用教程
|
6月前
|
前端开发 JavaScript 搜索推荐
react-app框架——使用monaco editor实现online编辑html代码编辑器
react-app框架——使用monaco editor实现online编辑html代码编辑器
288 3
|
6月前
|
JavaScript 前端开发
在 Vue 2 中安装和使用 mavon-editor富文本编辑器
在 Vue 2 中安装和使用 mavon-editor富文本编辑器
445 0
|
6月前
|
存储 前端开发 JavaScript
医院电子病历编辑器,EMRE(EMR Editor)源码
医院电子病历编辑器,EMRE(EMR Editor)源码
143 0