diff --git a/report-ui/package.json b/report-ui/package.json index 0651496d..25549a78 100644 --- a/report-ui/package.json +++ b/report-ui/package.json @@ -24,6 +24,7 @@ "js-cookie": "2.2.0", "miment": "^0.0.9", "moment": "^2.29.1", + "monaco-editor": "^0.20.0", "normalize.css": "7.0.0", "nprogress": "0.2.0", "sortablejs": "^1.10.2", @@ -61,6 +62,7 @@ "html-webpack-plugin": "4.0.0-alpha", "js-md5": "^0.7.3", "mini-css-extract-plugin": "0.4.1", + "monaco-editor-webpack-plugin": "^4.1.1", "node-notifier": "5.2.1", "node-sass": "^4.7.2", "optimize-css-assets-webpack-plugin": "5.0.0", diff --git a/report-ui/src/views/report/resultset/components/EditDataSet.vue b/report-ui/src/views/report/resultset/components/EditDataSet.vue index 44d3abec..ea3f81d6 100644 --- a/report-ui/src/views/report/resultset/components/EditDataSet.vue +++ b/report-ui/src/views/report/resultset/components/EditDataSet.vue @@ -62,11 +62,11 @@ > <el-form-item label="查询SQL或请求体"> <div class="codemirror"> - <codemirror + <!-- <monaco-editor v-model.trim="formData.dynSentence" - :options="optionsSql" - style="height: 190px" - /> + language="sql" + style="height: 500px" + /> --> </div> </el-form-item> </el-col> @@ -92,7 +92,7 @@ align="center" label="序号" type="index" - width="80" + min-width="80" /> <el-table-column label="参数名" align="center"> <template slot-scope="scope"> @@ -349,10 +349,10 @@ > <div class="codemirror"> <!-- //自定义高级规则? --> - <codemirror + <monaco-editor v-model.trim="validationRules" - :options="optionsJavascript" - style="height: 210px;overflow: hidden;" + language="javascript" + style="height: 500px" /> </div> <span slot="footer" class="dialog-footer"> @@ -382,10 +382,10 @@ import "codemirror/mode/javascript/javascript.js"; import "codemirror/lib/codemirror.css"; // 核心样式 import "codemirror/theme/cobalt.css"; // 引入主题后还需要在 options 中指定主题才会生效 import vueJsonEditor from "vue-json-editor"; - +import MonacoEditor from "./MonacoEditor.vue"; export default { name: "Support", - components: { Dictionary, codemirror, vueJsonEditor }, + components: { Dictionary, codemirror, vueJsonEditor, MonacoEditor }, props: { visib: { required: true, diff --git a/report-ui/src/views/report/resultset/components/MonacoEditor.vue b/report-ui/src/views/report/resultset/components/MonacoEditor.vue new file mode 100644 index 00000000..8f5325b1 --- /dev/null +++ b/report-ui/src/views/report/resultset/components/MonacoEditor.vue @@ -0,0 +1,132 @@ +<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> diff --git a/report-ui/src/views/report/resultset/components/util/javascript-completion.js b/report-ui/src/views/report/resultset/components/util/javascript-completion.js new file mode 100644 index 00000000..3999cc29 --- /dev/null +++ b/report-ui/src/views/report/resultset/components/util/javascript-completion.js @@ -0,0 +1,38 @@ +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; \ No newline at end of file diff --git a/report-ui/src/views/report/resultset/components/util/log-language.js b/report-ui/src/views/report/resultset/components/util/log-language.js new file mode 100644 index 00000000..a3db9c6b --- /dev/null +++ b/report-ui/src/views/report/resultset/components/util/log-language.js @@ -0,0 +1,58 @@ +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; \ No newline at end of file diff --git a/report-ui/src/views/report/resultset/components/util/sql-completion.js b/report-ui/src/views/report/resultset/components/util/sql-completion.js new file mode 100644 index 00000000..537057be --- /dev/null +++ b/report-ui/src/views/report/resultset/components/util/sql-completion.js @@ -0,0 +1,82 @@ +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", + "FORM", + "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; \ No newline at end of file diff --git a/report-ui/webpack.config.js b/report-ui/webpack.config.js index ae796712..d731edd7 100644 --- a/report-ui/webpack.config.js +++ b/report-ui/webpack.config.js @@ -1,5 +1,6 @@ 'use strict' const path = require('path') +const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); module.exports = { resolve: { @@ -8,5 +9,8 @@ module.exports = { '@': path.resolve('src'), 'vue$': 'vue/dist/vue.esm.js' } - } + }, + plugins: [ + new MonacoWebpackPlugin() + ] }