Js代码编辑器

在有些项目中有时需要让用户输入一些代码,使用户能自主处理一些数据逻辑,但如果让用户直接注入代码,会有很高的风险(恶意代码).
因此我们可以将用户输入的代码以字符串的形式传给后端,让后端根据所传的代码字符串处理数据.
为了使用户能有一个较好的代码输入体验,我们可以使用monaco-editor

npm install --save monaco-editor

使用的版本

  "monaco-editor": "^0.20.0",

组件MonacoEditor.vue文件

<template>
  <div ref="editor" class="main"></div>
</template>

<script>
  import * as monaco from 'monaco-editor'
  import createSqlCompleter from './util/sql-completion'
  import createJavascriptCompleter from './util/javascript-completion'
  import registerLanguage from './util/log-language'
  const global = {}

  const getHints = model => {
    let id = model.id.substring(6)
    return (global[id] && global[id].hints) || []
  }
  monaco.languages.registerCompletionItemProvider(
    'sql',
    createSqlCompleter(getHints)
  )
  monaco.languages.registerCompletionItemProvider(
    'javascript',
    createJavascriptCompleter(getHints)
  )
  registerLanguage(monaco)
  /**
   * monaco options
   * https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandaloneeditorconstructionoptions.html
   */
  export default {
    props: {
      options: {
        type: Object,
        default () {
          return {}
        }
      },
      value: {
        type: String,
        required: false
      },
      language: {
        type: String
      },
      hints: {
        type: Array,
        default () {
          return []
        }
      }
    },
    name: 'MonacoEditor',
    data () {
      return {
        editorInstance: null,
        defaultOptions: {
          theme: 'vs-dark',
          fontSize: 14
        }
      }
    },
    watch: {
      value () {
        if (this.value !== this.editorInstance.getValue()) {
          this.editorInstance.setValue(this.value)
        }
      }
    },
    mounted () {
      this.initEditor()
      global[this.editorInstance._id] = this
      window.addEventListener('resize', this.layout)
    },
    destroyed () {
      this.editorInstance.dispose()
      global[this.editorInstance._id] = null
      window.removeEventListener('resize', this.layout)
    },
    methods: {
      layout () {
        this.editorInstance.layout()
      },
      undo () {
        this.editorInstance.trigger('anyString', 'undo')
        this.onValueChange()
      },
      redo () {
        this.editorInstance.trigger('anyString', 'redo')
        this.onValueChange()
      },
      getOptions () {
        let props = { value: this.value }
        this.language !== undefined && (props.language = this.language)
        let options = Object.assign({}, this.defaultOptions, this.options, props)
        return options
      },
      onValueChange () {
        this.$emit('input', this.editorInstance.getValue())
        this.$emit('change', this.editorInstance.getValue())
      },
      initEditor () {
        this.MonacoEnvironment = {
          getWorkerUrl: function () {
            return './editor.worker.bundle.js'
          }
        }
        this.editorInstance = monaco.editor.create(
          this.$refs.editor,
          this.getOptions()
        )
        this.editorInstance.onContextMenu(e => {
          this.$emit('contextmenu', e)
        })
        this.editorInstance.onDidChangeModelContent(() => {
          this.onValueChange()
        })
        this.editorInstance.addCommand(
          monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_S,
          () => {
            this.$emit('save', this.editorInstance.getValue())
          }
        )
      }
    }
  }
</script>
<style scoped>
.main /deep/ .view-lines * {
  font-family: Consolas, "Courier New", monospace !important;
}
</style>

Util/javascript-completion.js

/* eslint-disable */
 import * as monaco from 'monaco-editor'
// js 有内置提示
function createCompleter (getExtraHints) {
  const createSuggestions = function (model, textUntilPosition) {
    let text = model.getValue()
    textUntilPosition = textUntilPosition.replace(/[\*\[\]@\$\(\)]/g, '').replace(/(\s+|\.)/g, ' ')
    let arr = textUntilPosition.split(/[\s;]/)
    let activeStr = arr[arr.length - 1]
    let len = activeStr.length
    let rexp = new RegExp('([^\\w]|^)' + activeStr + '\\w*', 'gim')
    let match = text.match(rexp)
    let mergeHints = Array.from(new Set([...getExtraHints(model)]))
      .sort()
      .filter(ele => {
        let rexp = new RegExp(ele.substr(0, len), 'gim')
        return (match && match.length === 1 && ele === activeStr) ||
                    ele.length === 1 ? false : activeStr.match(rexp)
      })
    return mergeHints.map(ele => ({
      label: ele,
      kind: monaco.languages.CompletionItemKind.Text,
      documentation: ele,
      insertText: ele
    }))
  }
  return {
    provideCompletionItems (model, position) {
      let textUntilPosition = model.getValueInRange({
        startLineNumber: position.lineNumber,
        startColumn: 1,
        endLineNumber: position.lineNumber,
        endColumn: position.column
      })
      return { suggestions: createSuggestions(model, textUntilPosition) }
    }
  }
}
export default createCompleter

Util/log-language.js

function registerLanguage (monaco) {
  monaco.languages.register({
    id: 'log'
  })
  monaco.languages.setMonarchTokensProvider('log', {
    tokenizer: {
      root: [
        [/(^[=a-zA-Z].*|\d\s.*)/, 'log-normal'],
        [/\sERROR\s.*/, 'log-error'],
        [/\sWARN\s.*/, 'log-warn'],
        [/\sINFO\s.*/, 'log-info'],
        [
          /^([0-9]{4}||[0-9]{2})-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}(.[0-9]{3})?/,
          'log-date'
        ],
        [
          /^[0-9]{2}\/[0-9]{2}\/[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}(.[0-9]{3})?/,
          'log-date'
        ],
        [/(^\*\*Waiting queue:.*)/, 'log-info'],
        [/(^\*\*result tips:.*)/, 'log-info']
      ]
    }
  })
  monaco.editor.defineTheme('log', {
    base: 'vs',
    inherit: true,
    rules: [{
      token: 'log-info',
      foreground: '4b71ca'
    },
    {
      token: 'log-error',
      foreground: 'ff0000',
      fontStyle: 'bold'
    },
    {
      token: 'log-warn',
      foreground: 'FFA500'
    },
    {
      token: 'log-date',
      foreground: '008800'
    },
    {
      token: 'log-normal',
      foreground: '808080'
    }
    ],
    colors: {
      'editor.lineHighlightBackground': '#ffffff',
      'editorGutter.background': '#f7f7f7'
    }
  })
}

export default registerLanguage

Util/sql-completion.js

/* eslint-disable */
 import * as monaco from 'monaco-editor'
const hints = [
  'SELECT',
  'INSERT',
  'DELETE',
  'UPDATE',
  'CREATE TABLE',
  'DROP TABLE',
  'ALTER TABLE',
  'CREATE VIEW',
  'DROP VIEW',
  'CREATE INDEX',
  'DROP INDEX',
  'CREATE PROCEDURE',
  'DROP PROCEDURE',
  'CREATE TRIGGER',
  'DROP TRIGGER',
  'CREATE SCHEMA',
  'DROP SCHEMA',
  'CREATE DOMAIN',
  'ALTER DOMAIN',
  'DROP DOMAIN',
  'GRANT',
  'DENY',
  'REVOKE',
  'COMMIT',
  'ROLLBACK',
  'SET TRANSACTION',
  'DECLARE',
  'EXPLAN',
  'OPEN',
  'FETCH',
  'CLOSE',
  'PREPARE',
  'EXECUTE',
  'DESCRIBE',
  'FROM',
  'ORDER BY']
function createCompleter (getExtraHints) {
  const createSuggestions = function (model, textUntilPosition) {
    let text = model.getValue()
    textUntilPosition = textUntilPosition.replace(/[\*\[\]@\$\(\)]/g, '').replace(/(\s+|\.)/g, ' ')
    let arr = textUntilPosition.split(/[\s;]/)
    let activeStr = arr[arr.length - 1]
    let len = activeStr.length
    let rexp = new RegExp('([^\\w]|^)' + activeStr + '\\w*', 'gim')
    let match = text.match(rexp)
    let textHints = !match ? []
      : match.map(ele => {
        let rexp = new RegExp(activeStr, 'gim')
        let search = ele.search(rexp)
        return ele.substr(search)
      })
    let mergeHints = Array.from(new Set([...hints, ...textHints, ...getExtraHints(model)]))
      .sort()
      .filter(ele => {
        let rexp = new RegExp(ele.substr(0, len), 'gim')
        return (match && match.length === 1 && ele === activeStr) ||
                    ele.length === 1 ? false : activeStr.match(rexp)
      })
    return mergeHints.map(ele => ({
      label: ele,
      kind: hints.indexOf(ele) > -1
        ? monaco.languages.CompletionItemKind.Keyword
        : monaco.languages.CompletionItemKind.Text,
      documentation: ele,
      insertText: ele
    }))
  }
  return {
    provideCompletionItems (model, position) {
      let textUntilPosition = model.getValueInRange({
        startLineNumber: position.lineNumber,
        startColumn: 1,
        endLineNumber: position.lineNumber,
        endColumn: position.column
      })
      return { suggestions: createSuggestions(model, textUntilPosition) }
    }
  }
}
export default createCompleter

在使用的页面

 <monaco-editor
          v-model.trim="transformScript"
          language="javascript"
          style="height: 380px"
        />
  import MonacoEditor from '@/module/components/MonacoEditor/MonacoEditor'
 transformScript: '', // 执行代码文本
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容