vue2 monoca-editor使用SQL:自动补全、自定义颜色、格式化代码、标记错误

一、安装

yarn add monaco-editor

yarn add vite-plugin-monaco-editor -D

二、配置vite.config.js

import { defineConfig } from 'vite'
import monacoEditorPlugin from "vite-plugin-monaco-editor"

export default defineConfig({
  plugins: [
    monacoEditorPlugin()
})

三、使用

1.导入

// 全局导入
import * as monaco from 'monaco-editor'

// 局部导入需要的功能和依赖
import * as monaco from 'monaco-editor/esm/vs/editor/edcore.main'
import 'monaco-editor/esm/vs/basic-languages/sql/sql.contribution'

2.封装组件

基本框架

<template>
    <div id="code"></div>
</template>

<script setup>
  import * as monaco from 'monaco-editor/esm/vs/editor/edcore.main'
  import 'monaco-editor/esm/vs/basic-languages/sql/sql.contribution'
  // import { language } from 'monaco-editor/esm/vs/basic-languages/sql/sql'
  // language.keywords插件自带关键词的不全,网上找了一份自己维护
  import sqlKeywords from './sqlKeywords.js'
  const props = defineProps({
    database: {
      type: String,
      default: ''
    }
  })
  const editor = ref(null)
  
  const initEditor = () => {
    // 初始化编辑器,确保dom已经渲染
    editor.value = monaco.editor.create(document.getElementById('code'), {
      //初始化配置
      value: props.value,
      theme: 'vs-dark',
      autoIndex: true,
      language: 'sql', // 语言类型
      tabCompletion: 'on',
      cursorSmoothCaretAnimation: true,
      formatOnPaste: true,
      mouseWheelZoom: true,
      folding: true, //代码折叠
      autoClosingBrackets: 'always',
      autoClosingOvertype: 'always',
      autoClosingQuotes: 'always',
      automaticLayout: 'always'
    })
  }
  
  // 父组件获取值
  const handleValue = () => {
    return toRaw(editor.value).getValue()
  }
  // 父组件设置值
  const setValue = (content) => {
    toRaw(editor.value).setValue(content)
  }
  
  onMounted(() => {
    initEditor()
  })
  defineExpose({
    handleValue,
    setValue
  })
</script>

自定义提示

const suggestion = ref(null)

  // 数据库对应的表名
  const hintData = reactive({
    a: ['group', 'area'],
    b: ['user', 'client']
  })
  // 表对应的字段名
  const tableData = reactive({
    user: ['age', 'gender'],
    group: ['id', 'name']
  })
  // 关键字提示
  const getSQLSuggest = () => {
    return sqlKeywords.map((key) => ({
      label: key,
      kind: monaco.languages.CompletionItemKind.Keyword,
      insertText: key,
      detail: 'keyword'
    }))
  }
  // 表名提示
  const getTableSuggest = (dbName) => {
    const tableNames = hintData[dbName]
    if (!tableNames) {
      return []
    }
    return tableNames.map((name) => ({
      label: name,
      kind: monaco.languages.CompletionItemKind.Constant,
      insertText: name,
      detail: dbName
    }))
  }
  // 字段名提示
  const getParamSuggest = (tableName) => {
    const params = tableData[tableName]
    if (!params) {
      return []
    }
    return params.map((name) => ({
      label: name,
      kind: monaco.languages.CompletionItemKind.Constant,
      insertText: name,
      detail: 'param'
    }))
  }
  // 数据库名提示
  const getDBSuggest = () => {
    return Object.keys(hintData).map((key) => ({
      label: key,
      kind: monaco.languages.CompletionItemKind.Enum,
      insertText: key,
      detail: 'database'
    }))
  }

const initEditor = () => {

 suggestion.value = monaco.languages.registerCompletionItemProvider('sql', {
        // 触发条件,也可以不写,不写的话只要输入满足label就会提示
        // 只能配置单字符
        triggerCharacters: ['.', ' '],
        provideCompletionItems: (model, position) => {
            let suggestions = []
            const { lineNumber, column } = position
            const textBeforePointer = model.getValueInRange({
                startLineNumber: lineNumber,
                startColumn: 0,
                endLineNumber: lineNumber,
                endColumn: column,
            })
            const tokens = textBeforePointer.toLocaleLowerCase().trim().split(/\s+/)
            const lastToken = tokens[tokens.length - 1] // 获取最后一段非空字符串
            const word = model.getWordUntilPosition(position)
            const range = {
              startLineNumber: position.lineNumber,
              endLineNumber: position.lineNumber,
              startColumn: word.startColumn,
              endColumn: word.endColumn
            }
            if (lastToken.endsWith('.')) {
            // 提示该数据库下的表名
                const tokenNoDot = lastToken.slice(0, lastToken.length - 1)
                if (Object.keys(hintData).includes(tokenNoDot)) {
                    suggestions = [...getTableSuggest(tokenNoDot)]
                }
            } else if (lastToken === '.') {
                suggestions = []
            } else if (textBeforePointer.endsWith(' ')) {
              if (textBeforePointer.endsWith('select * from ')) {
                // select * from 提示指定数据库的表名
                suggestions = getTableSuggest(props.database)
              } else if (lastToken === 'where') {
                // select * from tableName where 提示指定表的字段名
                const lastToken2 = tokens[tokens.length - 2]
                const lastToken3 = tokens[tokens.length - 3]
                const lastToken4 = tokens[tokens.length - 4]
                const lastToken5 = tokens[tokens.length - 5]
                if (lastToken5 + lastToken4 + lastToken3 === 'select*from') {
                  suggestions = [...getParamSuggest(lastToken2)]
                } else {
                  suggestions = []
                }
              }else {
                suggestions = []
              }
            } else {
                // 提示数据库名和关键词
                suggestions = [...getDBSuggest(), ...getSQLSuggest()]
            }
            return {
                suggestions
            }
        }
    })
}

	
	

 自定义文本颜色,自带也有

const color = ref(null)
const initEditor = () => {

 let reg = '/'
 sqlKeywords.forEach((keyword) => {
    reg += `${keyword}|`
 })
 reg += '/'
 color.value = monaco.languages.setMonarchTokensProvider('sql', {
    ignoreCase: true,
    tokenizer: {
      root: [
        [
            reg,
            { token: 'keyword' },
        ], //蓝色
        [
            /[+]|[-]|[*]|[/]|[%]|[>]|[<]|[=]|[!]|[:]|[&&]|[||]/,
            { token: 'string' },
        ], //红色
        [/'.*?'|".*?"/, { token: 'string.escape' }], //橙色
        [/#--.*?\--#/, { token: 'comment' }], //绿色
        [/null/, { token: 'regexp' }], //粉色
        [/[{]|[}]/, { token: 'type' }], //青色
        [/[\u4e00-\u9fa5]/, { token: 'predefined' }],//亮粉色
        [/''/, { token: 'invalid' }],//红色
        [/[\u4e00-\u9fa5]/, { token: 'number.binary' }],//浅绿
        [/(?!.*[a-zA-Z])[0-9]/, { token: 'number.hex' }], //浅绿
        [/[(]|[)]/, { token: 'number.octal' }], //浅绿
        [/[\u4e00-\u9fa5]/, { token: 'number.float' }],//浅绿
      ],
    },
 })

}

 格式化代码&标记错误

yarn add  sql-formatter -D

import { format } from 'sql-formatter'

const initEditor = () => {

 // 改写插件自带格式化功能
 formatProvider.value = monaco.languages.registerDocumentFormattingEditProvider('sql', {
	provideDocumentFormattingEdits(model) {
		return [{
			text: formatSql(1),
			range: model.getFullModelRange()
		}]
	}
 })
 // 格式化代码
 const formatSql = (needValue) => {
	clearMistake()
	try {
	    setValue(format(toRaw(editor.value).getValue()))
	} catch (e) {
		const {message} = e
		const list = message.split(' ')
		const line = list.indexOf('line')
		const column = list.indexOf('column')
		markMistake({
	        startLineNumber: Number(list[line + 1]),
			endLineNumber: Number(list[line + 1]),
			startColumn: Number(list[column + 1]),
			endColumn: Number(list[column + 1])
		}, 'Error', message)
	}
	if (needValue) {
		 return toRaw(editor.value).getValue()
	}
 }
 // 标记错误信息
 const markMistake = (range, type, message) => {
	const {startLineNumber, endLineNumber, startColumn, endColumn} = range
	monaco.editor.setModelMarkers(
		  toRaw(editor.value).getModel(),
		  'eslint',
		  [{
			startLineNumber,
			endLineNumber,
			startColumn,
			endColumn,
			severity: monaco.MarkerSeverity[type], // type可以是Error,Warning,Info
			message
		  }]
	)
 }
 // 清除错误信息
 const clearMistake = () => {
	monaco.editor.setModelMarkers(
	    toRaw(editor.value).getModel(),
		'eslint',
		[]
	)
 }
}

 监听值变化

const initEditor = () => {
    toRaw(editor.value).onDidChangeModelContent(() => {
        console.log('value', toRaw(editor.value).getValue())
    })
}

销毁编辑器及其配置,防止自定义提示数据重复

onBeforeUnmount(() => {
    if (editor.value) {
      clearMistake()
      toRaw(editor.value).dispose()
      toRaw(color.value).dispose()
      toRaw(suggestion.value).dispose()
      toRaw(formatProvider.value).dispose()
    }
})

Logo

前往低代码交流专区

更多推荐