当前位置:   article > 正文

前端vue项目中使用sql-formatter结合codemirror实现sql编辑器中的SQL代码格式化功能、自动匹配大小写功能、高亮功能

sql-formatter

前端 vue 项目中使用sql-formatter结合codemirror实现sql编辑器中的SQL代码格式化功能、自动匹配大小写功能、高亮功能

GitHub地址:https://github.com/mijing-web/sql-editor

前几天公司项目有个需求是要前端做个类似HUE的sql编辑器,需要实现sql语句的格式化功能、自动匹配大小写、代码高亮等功能(重任交给了我,但当时HUE我是第一次听说)。现部门是做大数据的,所以各种数据各种sql各种hive,对于我一个纯前端妹子来讲对什么后台sql语句之类的和前端没关系的我都没什么太大的兴趣~~

原本领导说是让我研究HUE源码,提取js,但是发现这个HUE是基于python开发的一款web管理器,python对于我来说…huhahaha…~ - ~…HUE源码拿来看过了,后来我放弃了。

后面网上找资料,认识到sql-formatter这个插件,但是网上找的各种资料几乎都是复制粘贴,并且这个单是实现了sql格式化功能,但我们功能还必须要实现自动匹配大小写和关键字高亮等功能。于是后面各种调研认识到了vue-codemirror插件,可以实现自动匹配大小写和高亮效果。嗯,于是认真研究了一把,把两者结合在一起。我想要的效果实现啦~~

1.安装npm安装sql-formatter、vue-codemirror 插件
npm install --save sql-formatter
npm install --save vue-codemirror 
  • 1
  • 2
2.使用方式

封装的子组件SqlEditor

<template>
    <div>
        <textarea 
        	ref="mycode" 
        	class="codesql" 
        	v-model="value" 
        >
        </textarea>
    </div>
</template>
<script>
    import "codemirror/theme/ambiance.css";
    import "codemirror/lib/codemirror.css";
    import "codemirror/addon/hint/show-hint.css";
    let CodeMirror = require("codemirror/lib/codemirror");
    require("codemirror/addon/edit/matchbrackets");
    require("codemirror/addon/selection/active-line");
    require("codemirror/mode/sql/sql");
    require("codemirror/addon/hint/show-hint");
    require("codemirror/addon/hint/sql-hint");
    export default {
   
        data() {
   
            return {
   
                editor: null
            }
        },
        props: {
   
            value: {
   
                type: String,
                default: ''
            },
            sqlStyle: {
   
                type: String,
                default: 'default'
            },
            readOnly: {
   
                type: [Boolean, String]
            }
        },
        watch: {
   
            newVal (newV, oldV) {
   
                if (this.editor) {
   
                    this.$emit('changeTextarea', this.editor.getValue())
                }
            }
        },
        computed: {
   
            newVal () {
   
                if (this.editor) {
   
                    return this.editor.getValue()
                }
            }
        },
        
        mounted(){
   
            let mime = 'text/x-mariadb'
            //let theme = 'ambiance'//设置主题,不设置的会使用默认主题
            this.editor = CodeMirror.fromTextArea(this.$refs.mycode, {
   
                value: this.value,
                mode: mime,//选择对应代码编辑器的语言,我这边选的是数据库,根据个人情况自行设置即可
                indentWithTabs: true,
                smartIndent: true,
                lineNumbers: true,
                matchBrackets: true,
                cursorHeight: 1,
                lineWrapping: true,
                readOnly: this.readOnly,
                //theme: theme,
                // autofocus: true,
                extraKeys: {
   'Ctrl': 'autocomplete'},//自定义快捷键
                hintOptions: {
   //自定义提示选项
                // 当匹配只有一项的时候是否自动补全
                    completeSingle: false,
                    // tables: {
   
                    //     users: ['name', 'score', 'birthDate'],
                    //     countries: ['name', 'population', 'size']
                    // }
                }
            })
            //代码自动提示功能,记住使用cursorActivity事件不要使用change事件,这是一个坑,那样页面直接会卡死
            this.editor.on('inputRead', () => {
   
                this.editor.showHint()
            })
        },
        methods: {
   
            setVal () {
   
                if (this.editor) {
   
                    if (this.value === '') {
   
                        this.editor.setValue('')
                    } else {
   
                        this.editor.setValue(this.value)
                    }
                    
                }
            }
        }
    }
</script>
<style>
.CodeMirror {
   
  color: black;
  direction: ltr;
  line-height: 22px;
}
// 这句为了解决匹配框显示有问题而加
.CodeMirror-hints{
   
  z-index: 9999 !important;
}
</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135

父组件调用

<template>
	<div>
		<SqlEditor ref="sqleditor"
		   :value="basicInfoForm.sqlMain"
		   @changeTextarea="changeTextarea($event)"
		/>
		<el-button type="primary" size="small" class="sql-btn" @click="formaterSql (basicInfoForm.sqlMain)">格式化sql</el-button>
	</div>
</template>
<script>
    import sqlFormatter from 'sql-formatter'
    import SqlEditor from '@/components/SqlEditor'
	export default {
   
        components: {
   
            SqlEditor
        },
        data() {
   
	        return{
   
	        	basicInfoForm:{
   
	        		sqlMain: ''
				}
			}
		},
        methods:{
   
        	changeTextarea (val){
   
        	   this.$set(this.basicInfoForm, 'sqlMain', val)
        	},
        	formaterSql (val) {
   
                let dom = this.$refs.sqleditor
                dom.editor.setValue(sqlFormatter.format(dom.editor.getValue()))
           },
        },
     }
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

最后再附上效果图~
在这里插入图片描述
支持格式:sql、pl/sql、n1ql、db2、

功能是完成了 ,但是sql-formatter插件格式化出来的效果和我们想要的格式还是有差距,一些复杂的sql校验和HUE并不能完全匹配,甚至后台校验大部分都不能通过。想想也只能改源码了,找到sql-formatter的源码,进行了一些源码修改,最终搞定。

网上看到过很多只实现了格式化功能或者只实现了高亮、大小写匹配功能,没有三者同时实现的,代码和思路不太符合我的需要,并且几乎每个人的版本都是复制粘贴的一模一样的内容,今天我附上自己纯手敲齐全代码,欢迎参考点赞~

GitHub地址:https://github.com/mijing-web/sql-editor

如果你觉得本文对你有帮助,欢迎转载和点赞,转载麻烦请注明出处,谢谢~~~~ ^ _ ^ ~~~

后记:
经一些踩坑网友反馈,现增加两条内容:一是部分反馈的报错问题,二是修改后的源码问题做个总结;
(一)报错问题:
1.vue.esm.js?efeb:628 [Vue warn]: Error in v-on handler: “TypeError: Cannot read properties of undefined (reading ‘format’)”
这个问题我之前使用时没用遇到,所以有人反馈时开始以为大家有没用引入的东西,后来也有技术大佬在评论区给了解决方案:(1)降低版本,降至3.0或者2.0;(2)更改sqlFormatter名字。

我本地从新尝试了下sqlFormatter下载的是最新版本,也出现了和大家相同的报错,我是降低了sqlFormatter的版本,方法(2)更改名字我没实验成功(着实尴尬)。
在这里插入图片描述
2.有人反馈说光标一直处于中间位置,这个在我今天实验时也出现了这个问题,但是后来发现是我的项目根样式控制了编辑器的样式,也就是#app。所以出现这个问题还是需要检查大家自己的样式问题。本身它的封装是靠左显示的。

(二)由于源码包很大,原本没有附上,但是有很多小伙伴都私聊了,并且大多都是比较着急使用,有时间个人工作忙或者星期天时间不能及时回复,多少会耽误大家的时间,所以还是贴上修改后的源码。(源码修改的只是部分语法解析,具体情况还是要根据大家项目需求而改。当下的源码是满足了我当时的项目需求的sql格式校验,如果大家有其他的校验方式可以自行找源码修改下~)

已修改后的源码如下:

(function webpackUniversalModuleDefinition(root, factory) {
   
	if(typeof exports === 'object' && typeof module === 'object')
		module.exports = factory();
	else if(typeof define === 'function' && define.amd)
		define([], factory);
	else if(typeof exports === 'object')
		exports["sqlFormatter"] = factory();
	else
		root["sqlFormatter"] = factory();
})(this, function() {
   
return /******/ (function(modules) {
    // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {
   };

/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
   

/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId])
/******/ 			return installedModules[moduleId].exports;

/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
   
/******/ 			exports: {
   },
/******/ 			id: moduleId,
/******/ 			loaded: false
/******/ 		};

/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

/******/ 		// Flag the module as loaded
/******/ 		module.loaded = true;

/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}


/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;

/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;

/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";

/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
   

	"use strict";

	exports.__esModule = true;

	var _Db2Formatter = __webpack_require__(24);

	var _Db2Formatter2 = _interopRequireDefault(_Db2Formatter);

	var _N1qlFormatter = __webpack_require__(25);

	var _N1qlFormatter2 = _interopRequireDefault(_N1qlFormatter);

	var _PlSqlFormatter = __webpack_require__(26);

	var _PlSqlFormatter2 = _interopRequireDefault(_PlSqlFormatter);

	var _StandardSqlFormatter = __webpack_require__(27);

	var _StandardSqlFormatter2 = _interopRequireDefault(_StandardSqlFormatter);

	function _interopRequireDefault(obj) {
    return obj && obj.__esModule ? obj : {
    "default": obj }; }

	exports["default"] = {
   
	    /**
	     * Format whitespaces in a query to make it easier to read.
	     *
	     * @param {String} query
	     * @param {Object} cfg
	     *  @param {String} cfg.language Query language, default is Standard SQL
	     *  @param {String} cfg.indent Characters used for indentation, default is "  " (2 spaces)
	     *  @param {Object} cfg.params Collection of params for placeholder replacement
	     * @return {String}
	     */
	    format: function format(query, cfg) {
   
	        cfg = cfg || {
   };

	        switch (cfg.language) {
   
	            case "db2":
	                return new _Db2Formatter2["default"](cfg).format(query);
	            case "n1ql":
	                return new _N1qlFormatter2["default"](cfg).format(query);
	            case "pl/sql":
	                return new _PlSqlFormatter2["default"](cfg).format(query);
	            case "sql":
	            case undefined:
	                return new _StandardSqlFormatter2["default"](cfg).format(query);
	            default:
	                throw Error("Unsupported SQL dialect: " + cfg.language);
	        }
	    }
	};
	module.exports = exports["default"];

/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
   

	var freeGlobal = __webpack_require__(12);

	/** Detect free variable `self`. */
	var freeSelf = typeof self == 'object' && self && self.Object === Object && self;

	/** Used as a reference to the global object. */
	var root = freeGlobal || freeSelf || Function('return this')();

	module.exports = root;


/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {
   

	var Symbol = __webpack_require__(9),
	    getRawTag = __webpack_require__(48),
	    objectToString = __webpack_require__(57);

	/** `Object#toString` result references. */
	var nullTag = '[object Null]',
	    undefinedTag = '[object Undefined]';

	/** Built-in value references. */
	var symToStringTag = Symbol ? Symbol.toStringTag : undefined;

	/**
	 * The base implementation of `getTag` without fallbacks for buggy environments.
	 *
	 * @private
	 * @param {*} value The value to query.
	 * @returns {string} Returns the `toStringTag`.
	 */
	function baseGetTag(value) {
   
	  if (value == null) {
   
	    return value === undefined ? undefinedTag : nullTag;
	  }
	  return (symToStringTag && symToStringTag in Object(value))
	    ? getRawTag(value)
	    : objectToString(value);
	}

	module.exports = baseGetTag;


/***/ }),
/* 3 */
/***/ (function(module, exports, __webpack_require__) {
   

	var baseIsNative = __webpack_require__(39),
	    getValue = __webpack_require__(50);

	/**
	 * Gets the native function at `key` of `object`.
	 *
	 * @private
	 * @param {Object} object The object to query.
	 * @param {string} key The key of the method to get.
	 * @returns {*} Returns the function if it's native, else `undefined`.
	 */
	function getNative(object, key) {
   
	  var value = getValue(object, key);
	  return baseIsNative(value) ? value : undefined;
	}

	module.exports = getNative;


/***/ }),
/* 4 */
/***/ (function(module, exports, __webpack_require__) {
   

	"use strict";

	exports.__esModule = true;

	var _trimEnd = __webpack_require__(74);

	var _trimEnd2 = _interopRequireDefault(_trimEnd);

	var _tokenTypes = __webpack_require__(8);

	var _tokenTypes2 = _interopRequireDefault(_tokenTypes);

	var _Indentation = __webpack_require__(21);

	var _Indentation2 = _interopRequireDefault(_Indentation);

	var _InlineBlock = __webpack_require__(22);

	var _InlineBlock2 = _interopRequireDefault(_InlineBlock);

	var _Params = __webpack_require__(23);

	var _Params2 = _interopRequireDefault(_Params);

	function _interopRequireDefault(obj) {
    return obj && obj.__esModule ? obj : {
    "default": obj }; }

	function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function"); } }

	var Formatter = function () {
   
	    /**
	     * @param {Object} cfg
	     *   @param {Object} cfg.indent
	     *   @param {Object} cfg.params
	     * @param {Tokenizer} tokenizer
	     */
	    function Formatter(cfg, tokenizer) {
   
	        _classCallCheck(this, Formatter);

	        this.cfg = cfg || {
   };
	        this.indentation = new _Indentation2["default"](this.cfg.indent);
	        this.inlineBlock = new _InlineBlock2["default"]();
	        this.params = new _Params2["default"](this.cfg.params);
	        this.tokenizer = tokenizer;
	        this.previousReservedWord = {
   };
	        this.tokens = [];
	        this.index = 0;
	    }

	    /**
	     * Formats whitespaces in a SQL string to make it easier to read.
	     *
	     * @param {String} query The SQL query string
	     * @return {String} formatted query
	     */


	    Formatter.prototype.format = function format(query) {
   
	        this.tokens = this.tokenizer.tokenize(query);
	        var formattedQuery = this.getFormattedQueryFromTokens();
	        return formattedQuery.trim();
	    };

	    Formatter.prototype.getFormattedQueryFromTokens = function getFormattedQueryFromTokens() {
   
	        var _this = this;
	        var formattedQuery = "";
	        this.tokens.forEach(function (token, index) {
   
	            _this.index = index;
	            if (token.type === _tokenTypes2["default"].WHITESPACE) {
   
	                // ignore (we do our own whitespace formatting)
	            } else if (token.type === _tokenTypes2["default"].LINE_COMMENT) {
   
	                formattedQuery = _this.formatLineComment(token, formattedQuery);
	            } else if (token.type === _tokenTypes2["default"].BLOCK_COMMENT) {
   
	                formattedQuery = _this.formatBlockComment(token, formattedQuery);
	            } else if (token.type === _tokenTypes2["default"].RESERVED_TOPLEVEL) {
   
	                formattedQuery = _this.formatToplevelReservedWord(token, formattedQuery);
	                _this.previousReservedWord = token;
	            } else if (token.type === _tokenTypes2["default"].RESERVED_NEWLINE) {
   
	                formattedQuery = _this.formatNewlineReservedWord(token, formattedQuery);
	                _this.previousReservedWord = token;
	            }else if (token.type === _tokenTypes2["default"].RESERVED) {
   
	                formattedQuery = _this.formatWithSpaces(token, formattedQuery);
	                _this.previousReservedWord = token;
	            } else if (token.type === _tokenTypes2["default"].OPEN_PAREN) {
   
	                formattedQuery = _this.formatOpeningParentheses(token, formattedQuery);
	            } else if (token.type === _tokenTypes2["default"].CLOSE_PAREN) {
   
	                formattedQuery = _this.formatClosingParentheses(token, formattedQuery);
	            } else if (token.type === _tokenTypes2["default"].PLACEHOLDER) {
   
	                formattedQuery = _this.formatPlaceholder(token, formattedQuery);
	            }   else if (token.value === '$') {
   
	                formattedQuery = _this.formatNewWithSpaces(token, formattedQuery);
	            } else if (token.value === ",") {
   
	                formattedQuery = _this.formatComma(token, formattedQuery);
	            } else if (token.value === ":") {
   
	                formattedQuery = _this.formatWithSpaceAfter(token, formattedQuery);
	            } else if (token.value === ".") {
   
	                formattedQuery = _this.formatWithoutSpaces(token, formattedQuery);
	            } else if (token.value === ";") {
   
	                formattedQuery = _this.formatQuerySeparator(token, formattedQuery);
	            } else {
   
	                formattedQuery = _this.formatWithSpaces(token, formattedQuery);
	            }
	        });
	        return formattedQuery;
	    };

	    Formatter.prototype.formatLineComment = function formatLineComment(token, query) {
   
	        return this.addNewline(query + token.value);
	    };

	    Formatter.prototype.formatBlockComment = function formatBlockComment(token, query) {
   
	        return this.addNewline(this.addNewline(query) + this.indentComment(token.value));
	    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号