哪家公司因为做网站失败了,动漫设计公司,织梦做的的网站首页显示空白,做汽车商城网站原文链接#xff1a;Vue3Monaco Editor封装及SQL编辑器实现 Ping通途说
0. 前言 最近收到需求#xff0c;老板想要在前端自定义SQL语句然后查询。安全性我强调了几次#xff0c;仍然拗不过老板#xff0c;那就干吧...只能在语句检查和权限上注意一下#xff0c;例如…原文链接Vue3Monaco Editor封装及SQL编辑器实现 Ping通途说0. 前言最近收到需求老板想要在前端自定义SQL语句然后查询。安全性我强调了几次仍然拗不过老板那就干吧...只能在语句检查和权限上注意一下例如严格的语句检查和创建一个仅有单表查权限的数据库用户执行语句。项目其他地方都使用了Monaco Editor所以在这里直接复用之前封装的组件文章也会贴出封装的代码有需要可以自取。若你没有听过这个编辑器那应该听过VSCode吧Monaco就是VSCode的核心编辑器组件。目前编辑器内置全套配件语法检查、格式化等的语言有TypeScript、JavaScript、HTML和JSON其他语言就要自己去配置。Monaco开放的APIMonaco Editor1. 基础功能实现有些教程官方也这么教会教直接用import * as monaco from monaco-editor;来将整个依赖库导入这样的后果就是打包出来的Monaco整整占用4MB左图而我们按需导入后体积骤降2.5MB右图因此如果你的项目和服务器带宽对加载速度有要求的可以使用以下方式导入Monacoimport * as monaco from monaco-editor/esm/vs/editor/editor.api;这样做的代价就是除了最基础的编辑框所有的右键菜单、语言高亮提示、语法提示等所有扩展功能都需要自己手动导入。直接来看最基础的Monaco Editor组件封装template div refcontainer :classw-full min-h-[450px] :style{ height: height }/div /template script setup import monaco-editor/esm/vs/editor/contrib/contextmenu/browser/contextmenu.js; // 右键显示菜单 import monaco-editor/esm/vs/editor/contrib/folding/browser/folding.js; // 折叠 import monaco-editor/esm/vs/editor/contrib/format/browser/formatActions.js; // 格式化代码 import monaco-editor/esm/vs/editor/contrib/suggest/browser/suggestController.js; // 代码联想提示 import monaco-editor/esm/vs/editor/contrib/tokenization/browser/tokenization.js; // 代码联想提示 import * as monaco from monaco-editor/esm/vs/editor/editor.api; import EditorWorker from monaco-editor/esm/vs/editor/editor.worker?worker; import JSONWorker from monaco-editor/esm/vs/language/json/json.worker?worker; import monaco-editor/esm/vs/language/json/monaco.contribution; // JSON代码高亮提示 import { nextTick, onMounted, ref, toRaw, watch } from vue; // 容器对象 const container ref(null) // 编辑器对象 const editor ref(null) // input 事件 const emit defineEmits([update:value]) const props defineProps({ value: { type: String, default: , }, language: { type: String, default: json, }, theme: { type: String, default: vs, }, readOnly: { type: Boolean, default: false, }, height: { type: String, default: 450px, }, wordWrap: { type: String, default: on, }, }) self.MonacoEnvironment { getWorker(workId, label) { if (label json) { return new JSONWorker() } return new EditorWorker() }, getWorkerUrl(moduleId, label) { if (label json) { return ./json.worker.bundle.js } return ./editor.worker.bundle.js }, } function updateValue(value) { // 更新值 提供给父组件使用 if (value) { const model monaco.editor.getModels()[0] model.setValue(value.toString()) } } defineExpose({ updateValue, }) onMounted(() { // 确保容器已经渲染完成后再创建编辑器 nextTick(() { editor.value monaco.editor.create(container.value, { value: props.value, language: props.language, scrollBeyondLastLine: false, theme: props.theme, wordWrap: props.wordWrap, automaticLayout: true, minimap: { enabled: false, }, readOnly: props.readOnly, }) editor.value.onDidChangeModelContent(() { const value toRaw(editor.value).getValue() emit(update:value, value) }) }) }) // 监听value prop变化更新编辑器内容 watch( () props.value, (newValue) { if (editor.value) { const model toRaw(editor.value).getModel() if (model newValue ! toRaw(editor.value).getValue()) { model.setValue(newValue || ) } } }, ) watch( () props.readOnly, (newValue) { toRaw(editor.value).updateOptions({ readOnly: newValue, }) }, ) /script封装的组件实现了以下功能支持JSON语言模式可通过language参数指定支持的编程语言JSON语法高亮、代码自动折叠、右键上下文菜单、JSON代码格式化、JSON智能代码提示和自动补全支持传入配置value编辑器内容值支持双向绑定language编程语言类型默认JSON需要预先配置好对应语言的参数才能使用语言高亮和智能提示theme编辑器主题默认vs暗黑模式就是vs-darkreadonly动态调整是否只读模式height编辑框容器高度wordWrap是否自动换行数据同步在编辑框输入和内部修改的内容由v-model进行双向绑定要使用这个组件先假设当前组件名称为“MonacoEditor.vue”然后就能在需要使用中的组件导入然后绑定值并传入参数。需要注意的是传入值必须为字符串传回值也是字符串这就意味着当前页面需要正常使用JSON就需要使用computed实时计算template div classmx-auto w-full h-[450px] MonacoEditor languagejson :valueformatRawText v-model:valueeditText :themetheme dark ? vs-dark : vs :readOnlyreadOnly / /div /template script setup import MonacoEditor from /components/editor/MonacoEditor.vue import { computed, ref, watch } from vue const rawText ref({}) const editText ref({}) const readOnly ref(false) const theme ref(dark) const formatRawText computed(() { try { if (typeof rawText object) { return JSON.stringify(JSON.parse(rawText), null, 2) } return config } catch { return {} } }) watch(editText.value,(){ if(editText.value editText.value ! {}){ try { const parsedText JSON.parse(editText.value) editText.value parsedText } catch(e){ console.log(解析发生错误,e) } } })另外如果你仔细观察组件的导入支持JSON功能的库有import JSONWorker from monaco-editor/esm/vs/language/json/json.worker?worker; import monaco-editor/esm/vs/language/json/monaco.contribution; // JSON代码高亮提示文章一开始也提到官方提供了4种语言的深度支持如果你需要使用他们可以模仿JSON引用库的方式来引用其他三项的依赖2. MySQL语言支持template div refcontainer :classw-full min-h-[450px] :style{ height: height }/div /template script setup import monaco-editor/esm/vs/basic-languages/mysql/mysql.contribution.js; import monaco-editor/esm/vs/basic-languages/mysql/mysql.js; import { language as mysqlLanguage } from monaco-editor/esm/vs/basic-languages/mysql/mysql.js; import monaco-editor/esm/vs/editor/contrib/contextmenu/browser/contextmenu.js; // 右键显示菜单 import monaco-editor/esm/vs/editor/contrib/folding/browser/folding.js; // 折叠 import monaco-editor/esm/vs/editor/contrib/format/browser/formatActions.js; // 格式化代码 import monaco-editor/esm/vs/editor/contrib/suggest/browser/suggestController.js; // 代码联想提示 import monaco-editor/esm/vs/editor/contrib/tokenization/browser/tokenization.js; // 代码联想提示 import * as monaco from monaco-editor/esm/vs/editor/editor.api; import EditorWorker from monaco-editor/esm/vs/editor/editor.worker?worker; import JSONWorker from monaco-editor/esm/vs/language/json/json.worker?worker; import monaco-editor/esm/vs/language/json/monaco.contribution; // JSON代码高亮提示 import { nextTick, onMounted, ref, toRaw, watch } from vue; // 容器对象 const container ref(null) // 编辑器对象 const editor ref(null) // input 事件 const emit defineEmits([update:value]) const props defineProps({ value: { type: String, default: , }, language: { type: String, default: json, }, theme: { type: String, default: vs, }, readOnly: { type: Boolean, default: false, }, height: { type: String, default: 450px, }, wordWrap: { type: String, default: on, }, }) self.MonacoEnvironment { getWorker(workId, label) { if (label json) { return new JSONWorker() } if (label mysql) { return new MySQLWorker() } return new EditorWorker() }, getWorkerUrl(moduleId, label) { if (label json) { return ./json.worker.bundle.js } if (label mysql) { return ./mysql.worker.bundle.js } return ./editor.worker.bundle.js }, } function updateValue(value) { // 更新值 提供给父组件使用 if (value) { const model monaco.editor.getModels()[0] model.setValue(value.toString()) } } defineExpose({ updateValue, }) onMounted(() { // 确保容器已经渲染完成后再创建编辑器 nextTick(() { editor.value monaco.editor.create(container.value, { value: props.value, language: props.language, scrollBeyondLastLine: false, theme: props.theme, wordWrap: props.wordWrap, automaticLayout: true, minimap: { enabled: false, }, readOnly: props.readOnly, }) monaco.languages.register({ id: mysql }) if (props.language mysql) { //注册片段 const keywords mysqlLanguage.keywords.map(item ({ label: item, kind: monaco.languages.CompletionItemKind.Keyword, insertText: item, detail: MySQL Keyword })) monaco.languages.registerCompletionItemProvider(mysql, { // 设置触发自动补全的字符 triggerCharacters: [ , ., (, ], provideCompletionItems: (model, position) { // 获取当前行的所有文本 const word model.getWordUntilPosition(position); const range { startLineNumber: position.lineNumber, endLineNumber: position.lineNumber, startColumn: word.startColumn, endColumn: word.endColumn }; // 返回所有关键词和自定义片段 return { suggestions: [{ label: queryUser, kind: monaco.languages.CompletionItemKind.Snippet, insertText: SELECT ${1:user_id},${2:open_id},${3:age},${4:params},${5:created_at} FROM user, insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, documentation: 查询用户信息, range: range }, ...keywords.map(keyword ({ ...keyword, range: range }))] } }, }) } editor.value.onDidChangeModelContent(() { const value toRaw(editor.value).getValue() emit(update:value, value) }) }) }) // ... exist // 监听value prop变化更新编辑器内容 watch( () props.value, (newValue) { if (editor.value) { const model toRaw(editor.value).getModel() if (model newValue ! toRaw(editor.value).getValue()) { model.setValue(newValue || ) } } }, ) watch( () props.readOnly, (newValue) { toRaw(editor.value).updateOptions({ readOnly: newValue, }) }, ) /script通过代码高亮可以发现我们导入了mysql的语法高亮支持然后在111行注册语言。在113行中遍历官方提供关键字列表并加入到编辑器中因为直接导入是没有用的打字没有提示。第120行我们定义了在什么时候触发关键词提示目前空格括号反引号等这些在MySQL语句一般接的就是关键词了。在这里你也可以将从服务器获取的表名也导入进去。然后136行我们自定义了一个片段用于快捷输入我们常用的SQL片段设置完成后我们可以通过输入queryUser来快捷使用SQL片段。片段中使用了${1:user_id}这种代表插槽用户可按Tab键快速切换到下一个插槽中填数据如果不填就使用插槽内部默认的字段非常的方便