feat: 初步移除codemirror

This commit is contained in:
meilin.huang
2022-11-05 15:13:40 +08:00
parent 881009321b
commit 2b1e687ed4
12 changed files with 427 additions and 559 deletions

View File

@@ -10,7 +10,6 @@
"@element-plus/icons-vue": "^2.0.9",
"asciinema-player": "^3.0.1",
"axios": "^1.1.2",
"codemirror": "^5.65.5",
"countup.js": "^2.0.7",
"cropperjs": "^1.5.11",
"echarts": "^5.3.3",

View File

@@ -4,7 +4,7 @@ declare module '*.vue' {
const component: DefineComponent<{}, {}, any>;
export default component;
}
declare module 'codemirror';
declare module 'sql-formatter';
declare module 'jsoneditor';
declare module 'asciinema-player';
declare module 'asciinema-player';
declare module 'monaco-editor';

View File

@@ -1,360 +0,0 @@
<template>
<div class="in-coder-panel">
<textarea ref="textarea"></textarea>
<el-select v-if="canChangeMode" class="code-mode-select" v-model="mode" @change="changeMode">
<el-option v-for="mode in modes" :key="mode.value" :label="mode.label" :value="mode.value"> </el-option>
</el-select>
</div>
</template>
<script lang="ts">
import { ref, nextTick, toRefs, reactive, watch, onMounted, defineComponent } from 'vue';
// 引入全局实例
import _CodeMirror from 'codemirror';
// 核心样式
import 'codemirror/lib/codemirror.css';
// 引入主题后还需要在 options 中指定主题才会生效
import 'codemirror/theme/cobalt.css';
import 'codemirror/addon/selection/active-line.js';
// 匹配括号
import 'codemirror/addon/edit/matchbrackets.js';
import 'codemirror/addon/selection/active-line';
import 'codemirror/addon/comment/comment';
// 需要引入具体的语法高亮库才会有对应的语法高亮效果
// codemirror 官方其实支持通过 /addon/mode/loadmode.js 和 /mode/meta.js 来实现动态加载对应语法高亮库
// 但 vue 貌似没有无法在实例初始化后再动态加载对应 JS ,所以此处才把对应的 JS 提前引入
import 'codemirror/mode/yaml/yaml.js';
import 'codemirror/mode/dockerfile/dockerfile.js';
import 'codemirror/mode/nginx/nginx.js';
import 'codemirror/mode/javascript/javascript.js';
import 'codemirror/mode/css/css.js';
import 'codemirror/mode/xml/xml.js';
import 'codemirror/mode/markdown/markdown.js';
import 'codemirror/mode/python/python.js';
import 'codemirror/mode/shell/shell.js';
import 'codemirror/mode/sql/sql.js';
import 'codemirror/mode/vue/vue.js';
import 'codemirror/mode/textile/textile.js';
import 'codemirror/addon/hint/show-hint.css';
import 'codemirror/addon/hint/show-hint.js';
import { ElOption, ElSelect } from 'element-plus';
// 尝试获取全局实例
const CodeMirror = (window as any).CodeMirror || _CodeMirror;
export default defineComponent({
name: 'CodeMirror',
components: {
ElOption,
ElSelect,
},
props: {
modelValue: {
type: String,
},
language: {
type: String,
default: null,
},
height: {
type: String,
default: '500px',
},
width: {
type: String,
default: 'auto',
},
canChangeMode: {
type: Boolean,
default: false,
},
options: {
type: Object,
default: null,
},
},
setup(props: any, { emit }) {
let { modelValue, language } = toRefs(props);
const textarea: any = ref(null);
// 编辑器实例
let coder = null as any;
const state = reactive({
coder: null as any,
content: '',
// 默认的语法类型
mode: 'x-sh',
// 默认配置
options: {
// 缩进格式
tabSize: 2,
// 主题,对应主题库 JS 需要提前引入
theme: 'cobalt',
// 显示行号
lineNumbers: true,
line: true,
indentWithTabs: true,
smartIndent: true,
matchBrackets: true,
autofocus: true,
styleSelectedText: true,
styleActiveLine: true, // 高亮选中行
foldGutter: true, // 块槽
// extraKeys: { Tab: 'autocomplete' }, // 自定义快捷键
hintOptions: {
// 当匹配只有一项的时候是否自动补全
completeSingle: false,
},
},
// 支持切换的语法高亮类型,对应 JS 已经提前引入
// 使用的是 MIME-TYPE ,不过作为前缀的 text/ 在后面指定时写死了
modes: [
{
value: 'x-sh',
label: 'Shell',
},
{
value: 'x-yaml',
label: 'Yaml',
},
{
value: 'x-dockerfile',
label: 'Dockerfile',
},
{
value: 'x-nginx-conf',
label: 'Nginx',
},
{
value: 'html',
label: 'XML/HTML',
},
{
value: 'x-python',
label: 'Python',
},
{
value: 'x-sql',
label: 'SQL',
},
{
value: 'css',
label: 'CSS',
},
{
value: 'javascript',
label: 'Javascript',
},
{
value: 'x-java',
label: 'Java',
},
{
value: 'x-vue',
label: 'Vue',
},
{
value: 'markdown',
label: 'Markdown',
},
{
value: 'text/x-textile',
label: 'text',
},
],
});
onMounted(() => {
init();
});
watch(
() => props.modelValue,
(newValue) => {
handerCodeChange(newValue);
}
);
// watch(
// () => props.options,
// (newValue, oldValue) => {
// for (const key in newValue) {
// coder.setOption(key, newValue[key]);
// }
// }
// );
const init = () => {
if (props.options) {
state.options = props.options;
}
// 初始化编辑器实例,传入需要被实例化的文本域对象和默认配置
coder = CodeMirror.fromTextArea(textarea.value, state.options);
coder.setValue(modelValue.value || state.content);
// 支持双向绑定
coder.on('change', (coder: any) => {
state.content = coder.getDoc().getValue();
emit('update:modelValue', state.content);
});
coder.on('inputRead', (instance: any, changeObj: any) => {
if (/^[a-zA-Z]/.test(changeObj.text[0])) {
instance.showHint();
}
});
coder.setSize(props.width, props.height);
// editor.setSize('width','height');
// 修改编辑器的语法配置
setMode(language.value);
[
'scroll',
'changes',
'beforeChange',
'cursorActivity',
'keyHandled',
'inputRead',
'electricInput',
'beforeSelectionChange',
'viewportChange',
'swapDoc',
'gutterClick',
'gutterContextMenu',
'focus',
'blur',
'refresh',
'optionChange',
'scrollCursorIntoView',
'update',
].forEach((event) => {
// 循环事件,并兼容 run-time 事件命名
coder.on(event, (...args: any) => {
// console.log('当有事件触发了', event, args);
emit(event, ...args);
const lowerCaseEvent = event.replace(/([A-Z])/g, '-$1').toLowerCase();
if (lowerCaseEvent !== event) {
emit(lowerCaseEvent, ...args);
}
});
});
state.coder = coder;
// 不加无法显示内容,需点击后才可显示
refresh();
};
const refresh = () => {
nextTick(() => {
coder.refresh();
});
};
// 设置模式
const setMode = (val: string) => {
if (val) {
// 获取具体的语法类型对象
let modeObj = getLanguage(val);
// 判断父容器传入的语法是否被支持
if (modeObj) {
state.mode = modeObj.value;
}
}
// 修改编辑器的语法配置
coder.setOption('mode', `text/${state.mode}`);
};
// 获取当前语法类型
const getLanguage = (language: string) => {
// 在支持的语法类型列表中寻找传入的语法类型
return state.modes.find((mode: any) => {
// 所有的值都忽略大小写,方便比较
let currentLanguage = language.toLowerCase();
let currentLabel = mode.label.toLowerCase();
let currentValue = mode.value.toLowerCase();
// 由于真实值可能不规范,例如 java 的真实值是 x-java ,所以讲 value 和 label 同时和传入语法进行比较
return currentLabel === currentLanguage || currentValue === currentLanguage;
});
};
// 更改模式
const changeMode = (val: string) => {
setMode(val);
// 获取修改后的语法
let label = (getLanguage(val) as any).label.toLowerCase();
// 允许父容器通过以下函数监听当前的语法值
emit('language-change', label);
};
const handerCodeChange = (newVal: string) => {
const cm_value = coder.getValue();
if (newVal !== cm_value) {
const scrollInfo = coder.getScrollInfo();
coder.setValue(newVal);
state.content = newVal;
coder.scrollTo(scrollInfo.left, scrollInfo.top);
refresh();
}
};
return {
...toRefs(state),
textarea,
changeMode,
refresh,
};
},
});
</script>
<style lang="scss">
.in-coder-panel {
flex-grow: 1;
display: flex;
position: relative;
.CodeMirror {
flex-grow: 1;
z-index: 1;
.CodeMirror-code {
line-height: 19px;
}
font-family: 'JetBrainsMono';
}
.code-mode-select {
position: absolute;
z-index: 2;
right: 10px;
top: 10px;
max-width: 130px;
}
}
.CodeMirror-hints {
position: absolute;
z-index: 10000;
overflow: hidden;
list-style: none;
margin: 0;
padding: 2px;
-webkit-box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2);
-moz-box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2);
box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2);
border-radius: 3px;
border: 1px solid silver;
background: white;
font-size: 90%;
font-family: 'JetBrainsMono';
max-height: 20em;
overflow-y: auto;
}
</style>

View File

@@ -1,20 +0,0 @@
import _CodeMirror from 'codemirror'
import codemirror from './codemirror.vue'
const CodeMirror = window.CodeMirror || _CodeMirror
const install = (Vue, config) => {
if (config) {
if (config.options) {
codemirror.props.globalOptions.default = () => config.options
}
if (config.events) {
codemirror.props.globalEvents.default = () => config.events
}
}
Vue.component(codemirror.name, codemirror)
}
const VueCodemirror = { CodeMirror, codemirror, install }
export default VueCodemirror
export { CodeMirror, codemirror, install }

View File

@@ -0,0 +1,209 @@
<template>
<div class="monaco-editor">
<div ref="monacoTextarea" :style="{ height: height }"></div>
<el-select v-if="canChangeMode" class="code-mode-select" v-model="languageMode" @change="changeLanguage">
<el-option v-for="mode in languages" :key="mode.value" :label="mode.label" :value="mode.value"> </el-option>
</el-select>
</div>
</template>
<script lang="ts" setup>
import { ref, toRefs, reactive, onMounted } from 'vue';
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker.js?worker';
import * as monaco from 'monaco-editor';
import { editor } from 'monaco-editor';
// 主题仓库 https://github.com/brijeshb42/monaco-themes
// 主题例子 https://editor.bitwiser.in/
// import Monokai from 'monaco-themes/themes/Monokai.json'
// import Active4D from 'monaco-themes/themes/Active4D.json'
// import ahe from 'monaco-themes/themes/All Hallows Eve.json'
// import bop from 'monaco-themes/themes/Birds of Paradise.json'
// import krTheme from 'monaco-themes/themes/krTheme.json'
// import Dracula from 'monaco-themes/themes/Dracula.json'
import SolarizedLight from 'monaco-themes/themes/Solarized-light.json'
import { ElOption, ElSelect } from 'element-plus';
const props = defineProps({
modelValue: {
type: String,
},
language: {
type: String,
default: null,
},
height: {
type: String,
default: '500px',
},
width: {
type: String,
default: 'auto',
},
canChangeMode: {
type: Boolean,
default: false,
},
options: {
type: Object,
default: null,
},
})
//定义事件
const emit = defineEmits(['update:modelValue'])
const languages = [
{
value: 'shell',
label: 'Shell',
},
{
value: 'yaml',
label: 'Yaml',
},
{
value: 'dockerfile',
label: 'Dockerfile',
},
{
value: 'x-nginx-conf',
label: 'Nginx',
},
{
value: 'html',
label: 'XML/HTML',
},
{
value: 'python',
label: 'Python',
},
{
value: 'sql',
label: 'SQL',
},
{
value: 'css',
label: 'CSS',
},
{
value: 'javascript',
label: 'Javascript',
},
{
value: 'json',
label: 'Json',
},
{
value: 'java',
label: 'Java',
},
{
value: 'markdown',
label: 'Markdown',
},
{
value: 'text',
label: 'text',
},
];
const state = reactive({
languageMode: 'shell',
})
const {
languageMode,
} = toRefs(state)
onMounted(() => {
state.languageMode = props.language
initMonacoEditor();
setEditorValue(props.modelValue)
});
const monacoTextarea: any = ref(null);
let monacoEditor: editor.IStandaloneCodeEditor = null;
self.MonacoEnvironment = {
getWorker() {
return new EditorWorker();
}
};
const initMonacoEditor = () => {
console.log('初始化monaco编辑器')
// options参数参考 https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html#language
// 初始化一些主题
monaco.editor.defineTheme('SolarizedLight', SolarizedLight);
// monaco.editor.defineTheme('Monokai', Monokai);
// monaco.editor.defineTheme('Active4D', Active4D);
// monaco.editor.defineTheme('ahe', ahe);
// monaco.editor.defineTheme('bop', bop);
// monaco.editor.defineTheme('krTheme', krTheme);
// monaco.editor.defineTheme('Dracula', Dracula);
// monaco.editor.defineTheme('TextmateMac', TextmateMac);
monacoEditor = monaco.editor.create(monacoTextarea.value, {
language: state.languageMode,
theme: 'SolarizedLight',
automaticLayout: true, //自适应宽高布局
foldingStrategy: 'indentation',//代码可分小段折叠
roundedSelection: false, // 禁用选择文本背景的圆角
matchBrackets: 'near',
linkedEditing: true,
cursorBlinking: 'smooth',// 光标闪烁样式
mouseWheelZoom: true, // 在按住Ctrl键的同时使用鼠标滚轮时在编辑器中缩放字体
overviewRulerBorder: false, // 不要滚动条的边框
tabSize: 2, // tab 缩进长度
fontFamily: 'JetBrainsMono', // 字体 暂时不要设置,否则光标容易错位
fontWeight: 'bold',
// fontSize: 12,
// letterSpacing: 1, 字符间距
// quickSuggestions:false, // 禁用代码提示
minimap: {
enabled: false, // 不要小地图
},
});
// 监听内容改变,双向绑定
monacoEditor.onDidChangeModelContent(() => {
emit('update:modelValue', monacoEditor.getModel().getValue());
})
// 动态设置主题
// monaco.editor.setTheme('hc-black');
};
const changeLanguage = (value: any) => {
console.log('change lan');
// 获取当前的文档模型
let oldModel = monacoEditor.getModel()
// 创建一个新的文档模型
let newModel = monaco.editor.createModel(oldModel.getValue(), value)
// 设置成新的
monacoEditor.setModel(newModel)
// 销毁旧的模型
if (oldModel) {
oldModel.dispose()
}
}
const setEditorValue = (value: any) => {
monacoEditor.getModel().setValue(value)
}
</script>
<style lang="scss">
.monaco-editor {
.code-mode-select {
position: absolute;
z-index: 2;
right: 10px;
top: 10px;
max-width: 130px;
}
}
</style>

View File

@@ -87,10 +87,11 @@
</div>
<div style="float: right" class="fl">
<el-select v-model="monacoOptions.theme" placeholder="切换编辑器主题" @change="changeEditorTheme"
filterable allow-create default-first-option size="small" class="mr10">
<el-option v-for="item in monacoOptions.defaultThemes as string" :key="item" :label="item"
:value="item">
<el-select v-model="monacoOptions.theme" placeholder="切换编辑器主题"
@change="changeEditorTheme" filterable allow-create default-first-option
size="small" class="mr10">
<el-option v-for="item in monacoOptions.defaultThemes as string" :key="item"
:label="item" :value="item">
{{ item }}
</el-option>
</el-select>
@@ -111,10 +112,12 @@
</div>
<div class="mt5 sqlEditor">
<div ref="monacoTextarea" :style="{height: monacoOptions.height}"></div>
<div ref="monacoTextarea" :style="{ height: monacoOptions.height }"></div>
</div>
<div class="editor-move-resize" @mousedown="onDragSetHeight">
<el-icon><Minus /></el-icon>
<el-icon>
<Minus />
</el-icon>
</div>
<div class="mt5">
<el-row>
@@ -129,8 +132,8 @@
</el-row>
<el-table @cell-dblclick="cellClick" @selection-change="onDataSelectionChange"
:data="queryTab.execRes.data" v-loading="queryTab.loading" element-loading-text="查询中..."
size="small" :max-height="monacoOptions.tableMaxHeight" empty-text="tips: select *开头的单表查询或点击表名默认查询的数据,可双击数据在线修改"
stripe border class="mt5">
size="small" :max-height="monacoOptions.tableMaxHeight"
empty-text="tips: select *开头的单表查询或点击表名默认查询的数据,可双击数据在线修改" stripe border class="mt5">
<el-table-column v-if="queryTab.execRes.tableColumn.length > 0 && queryTab.nowTableName"
type="selection" width="35" />
<el-table-column min-width="100" :width="flexColumnWidth(item, queryTab.execRes.data)"
@@ -139,7 +142,7 @@
</el-table-column>
</el-table>
</div>
</div>
</el-tab-pane>
@@ -254,44 +257,44 @@
import { onMounted, toRefs, reactive, ref, watch } from 'vue';
import { dbApi } from './api';
import {format as sqlFormatter} from 'sql-formatter';
import {isTrue, notBlank, notEmpty} from '@/common/assert';
import {ElMessage, ElMessageBox} from 'element-plus';
import { format as sqlFormatter } from 'sql-formatter';
import { isTrue, notBlank, notEmpty } from '@/common/assert';
import { ElMessage, ElMessageBox } from 'element-plus';
import config from '@/common/config';
import {getSession} from '@/common/utils/storage';
import { getSession } from '@/common/utils/storage';
import SqlExecBox from './component/SqlExecBox';
import {dateStrFormat} from '@/common/utils/date.ts';
import {useStore} from '@/store/index.ts';
import {tagApi} from '../tag/api.ts';
import { dateStrFormat } from '@/common/utils/date.ts';
import { useStore } from '@/store/index.ts';
import { tagApi } from '../tag/api.ts';
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker.js?worker';
import {language as sqlLanguage} from 'monaco-editor/esm/vs/basic-languages/mysql/mysql.js';
import { language as sqlLanguage } from 'monaco-editor/esm/vs/basic-languages/mysql/mysql.js';
import * as monaco from 'monaco-editor';
import {editor, languages, Position} from 'monaco-editor';
import { editor, languages, Position } from 'monaco-editor';
// 主题仓库 https://github.com/brijeshb42/monaco-themes
// 主题例子 https://editor.bitwiser.in/
import Monokai from 'monaco-themes/themes/Monokai.json'
import Active4D from 'monaco-themes/themes/Active4D.json'
import ahe from 'monaco-themes/themes/All Hallows Eve.json'
import bop from 'monaco-themes/themes/Birds of Paradise.json'
import krTheme from 'monaco-themes/themes/krTheme.json'
import Dracula from 'monaco-themes/themes/Dracula.json'
import TextmateMac from 'monaco-themes/themes/Textmate (Mac Classic).json'
// import Monokai from 'monaco-themes/themes/Monokai.json'
// import Active4D from 'monaco-themes/themes/Active4D.json'
// import ahe from 'monaco-themes/themes/All Hallows Eve.json'
// import bop from 'monaco-themes/themes/Birds of Paradise.json'
// import krTheme from 'monaco-themes/themes/krTheme.json'
// import Dracula from 'monaco-themes/themes/Dracula.json'
import SolarizedLight from 'monaco-themes/themes/Solarized-light.json'
// import TextmateMac from 'monaco-themes/themes/Textmate (Mac Classic).json'
import { Minus } from '@element-plus/icons-vue';
const store = useStore();
const monacoTextarea: any = ref(null);
const token = getSession('token');
const tableMap = new Map();
const defalutLimit = 20
export type TableMeta = {
type TableMeta = {
// 表名
tableName:string,
tableName: string,
// 表注释
tableComment:string
tableComment: string
}
const state = reactive({
token: token,
@@ -317,16 +320,16 @@ const state = reactive({
// 点击执行按钮执行结果信息
execRes: {
data: [],
tableColumn: []
tableColumn: []
},
loading: false,
nowTableName: '', //当前表格数据操作的数据库表名,用于双击编辑表内容使用
selectionDatas: []
selectionDatas: []
},
params: {
pageNum: 1,
pageSize: 100,
tagPath: null
tagPath: null
},
conditionDialog: {
title: '',
@@ -335,19 +338,19 @@ const state = reactive({
dataTab: null,
visible: false,
condition: '=',
value: null
value: null
},
genSqlDialog: {
visible: false,
sql: '',
},
monacoOptions: {
editor: {} as editor.IStandaloneCodeEditor,
editor: {} as editor.IStandaloneCodeEditor,
height: '',
tableMaxHeight:250,
dbTables:{},
theme:'',
defaultThemes:[ 'vs' ,'vs-dark', 'hc-black', 'hc-light', 'Monokai', 'Active4D', 'ahe', 'bop', 'krTheme', 'Dracula', 'TextmateMac'],
tableMaxHeight: 250,
dbTables: {},
theme: 'SolarizedLight',
defaultThemes: ['SolarizedLight', 'vs', 'vs-dark'],
}
});
const {
@@ -382,23 +385,32 @@ self.MonacoEnvironment = {
const initMonacoEditor = () => {
console.log('初始化编辑器')
// options参数参考 https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html#language
let defVal = `-- monaco editor`;
// 初始化一些主题
monaco.editor.defineTheme('SolarizedLight', SolarizedLight);
// monaco.editor.defineTheme('Monokai', Monokai);
// monaco.editor.defineTheme('Active4D', Active4D);
// monaco.editor.defineTheme('ahe', ahe);
// monaco.editor.defineTheme('bop', bop);
// monaco.editor.defineTheme('krTheme', krTheme);
// monaco.editor.defineTheme('Dracula', Dracula);
// monaco.editor.defineTheme('TextmateMac', TextmateMac);
monacoEditor = monaco.editor.create(monacoTextarea.value, {
value: defVal,
language: 'sql',
theme: 'vs',
theme: state.monacoOptions.theme,
automaticLayout: true, //自适应宽高布局
foldingStrategy: 'indentation',//代码可分小段折叠
roundedSelection: false, // 禁用选择文本背景的圆角
matchBrackets: 'near',
linkedEditing:true,
linkedEditing: true,
cursorBlinking: 'smooth',// 光标闪烁样式
mouseWheelZoom: true, // 在按住Ctrl键的同时使用鼠标滚轮时在编辑器中缩放字体
overviewRulerBorder: false, // 不要滚动条的边框
tabSize: 2, // tab 缩进长度
// fontFamily:'consolas', // 字体 暂时不要设置,否则光标容易错位
fontFamily: 'JetBrainsMono', // 字体 暂时不要设置,否则光标容易错位
fontWeight: 'bold',
// letterSpacing: 1, 字符间距
// quickSuggestions:false, // 禁用代码提示
minimap: {
@@ -406,41 +418,32 @@ const initMonacoEditor = () => {
},
});
// 初始化一些主题
monaco.editor.defineTheme('Monokai', Monokai);
monaco.editor.defineTheme('Active4D', Active4D);
monaco.editor.defineTheme('ahe', ahe);
monaco.editor.defineTheme('bop', bop);
monaco.editor.defineTheme('krTheme', krTheme);
monaco.editor.defineTheme('Dracula', Dracula);
monaco.editor.defineTheme('TextmateMac', TextmateMac);
// 动态设置主题
// monaco.editor.setTheme('hc-black');
// 参考 https://microsoft.github.io/monaco-editor/playground.html#extending-language-services-completion-provider-example
monaco.languages.registerCompletionItemProvider('sql', {
triggerCharacters:['.'],
triggerCharacters: ['.'],
provideCompletionItems: async (model: editor.ITextModel, position: Position): Promise<languages.CompletionList | null | undefined> => {
let word = model.getWordUntilPosition(position);
const {lineNumber, column} = position
const {startColumn, endColumn} = word
const { lineNumber, column } = position
const { startColumn, endColumn } = word
// 当前行文本
let lineContent = model.getLineContent(lineNumber);
// 注释行不需要代码提示
if(lineContent.startsWith('--')){
return {suggestions: []}
if (lineContent.startsWith('--')) {
return { suggestions: [] }
}
let range = {
startLineNumber: lineNumber,
endLineNumber: lineNumber,
startColumn,
endColumn,
};
// 光标前文本
const textBeforePointer = model.getValueInRange({
startLineNumber: lineNumber,
@@ -466,9 +469,9 @@ const initMonacoEditor = () => {
const tokens = textBeforePointer.trim().split(/\s+/)
const lastToken = tokens[tokens.length - 1].toLowerCase()
console.log("光标前文本=>" + textBeforePointerMulti)
// console.log("光标前文本=>" + textBeforePointerMulti)
console.log("最后输入的=>" + lastToken)
// console.log("最后输入的=>" + lastToken)
if (lastToken.endsWith('.')) {
// 如果是.触发代码提示,则进行【 库.表名联想 】 或 【 表别名.表字段联想 】
let str = lastToken.substring(0, lastToken.lastIndexOf('.'))
@@ -476,52 +479,50 @@ const initMonacoEditor = () => {
if (state.databaseList.indexOf(str) > -1) {
let tables = await loadTableMetadata(str)
let suggestions: languages.CompletionItem[] = []
for(let item of tables){
const {tableName, tableComment} = item
for (let item of tables) {
const { tableName, tableComment } = item
suggestions.push({
label: tableName + ( tableComment?' - ' + tableComment :'' ),
label: tableName + (tableComment ? ' - ' + tableComment : ''),
kind: monaco.languages.CompletionItemKind.File,
insertText: tableName,
range
});
});
}
return { suggestions }
}
let sql = textBeforePointerMulti.split(';')[textBeforePointerMulti.split(';').length - 1] + textAfterPointerMulti.split(';')[0];
// 表别名.表字段联想
let tableInfo = getTableByAlias(sql,state.db, str)
if(tableInfo.tableName){
let tableInfo = getTableByAlias(sql, state.db, str)
if (tableInfo.tableName) {
let table = tableInfo.tableName
let db = tableInfo.dbName
// 取出表名并提示
let dbs = state.monacoOptions.dbTables[db]
let tables = dbs ? (dbs[table] || []) : [];
if((!tables || tables.length === 0) && db){
let columns = dbs ? (dbs[table] || []) : [];
if ((!columns || columns.length === 0) && db) {
state.monacoOptions.dbTables[db] = await loadHintTables(db)
dbs = state.monacoOptions.dbTables[db]
tables = dbs ? (dbs[table] || []) : [];
columns = dbs ? (dbs[table] || []) : [];
}
tables.sort()
let suggestions: languages.CompletionItem[] = []
tables.forEach((a:string)=>{
columns.forEach((a: string, index: any) => {
// 字段数据格式 字段名 字段注释, 如: create_time [datetime][创建时间]
let fieldName = a.substring(0,a.indexOf(" "))
let comment = a.replace(eval(`/${fieldName}\\s+/`), '')
let detail = fieldName + ( comment?' - ' + comment :'' )
const nameAndComment = a.split(" ")
const fieldName = nameAndComment[0]
suggestions.push({
label: detail,
label: a, // [datetime][创建时间]
kind: monaco.languages.CompletionItemKind.Property,
detail: detail,
insertText: fieldName,
range
detail: '', // 不显示detail, 否则选中时备注等会被遮挡
insertText: fieldName + ' ', // create_time
range,
sortText: 100 + index + '' // 使用表字段声明顺序排序
});
})
return { suggestions }
//
}
return { suggestions:[] }
}
return { suggestions: [] }
}
// 库名联想
@@ -570,28 +571,27 @@ const initMonacoEditor = () => {
kind: monaco.languages.CompletionItemKind.Folder,
insertText: a,
range
});
});
})
// 表名联想
state.tableMetadata.forEach((tableMeta: TableMeta) => {
const {tableName, tableComment} = tableMeta
const { tableName, tableComment } = tableMeta
suggestions.push({
label: tableName + ' - ' + tableComment,
kind: monaco.languages.CompletionItemKind.File,
detail: tableComment,
insertText: tableName,
insertText: tableName + ' ',
range
});
});
})
// 默认提示
return {
suggestions: suggestions
};
};
},
});
};
/**
@@ -600,8 +600,8 @@ const initMonacoEditor = () => {
* @param db 默认数据库
* @param alias 别名
*/
const getTableByAlias = (sql: string, db: string, alias: string):{dbName: string, tableName: string} => {
const getTableByAlias = (sql: string, db: string, alias: string): { dbName: string, tableName: string } => {
// 表别名:表名
let result = {};
let defName = '';
@@ -617,10 +617,10 @@ where l.name='kevin' and exsits(select 1 from pharmacywestpas pw where p.outvisi
unit all
select * from invisit v where`.match(/(join|from)\s+(\w*-?\w*\.?\w+)\s*(as)?\s*(\w*)/gi)
*/
let match = sql.match(/(join|from)\s+(\w*-?\w*\.?\w+)\s*(as)?\s*(\w*)/gi)
if(match && match.length>0){
match.forEach(a=>{
if (match && match.length > 0) {
match.forEach(a => {
// 去掉前缀,取出
let t = a.substring(5, a.length)
.replaceAll(/\s+as\s+/g, ' ')
@@ -628,11 +628,11 @@ select * from invisit v where`.match(/(join|from)\s+(\w*-?\w*\.?\w+)\s*(as)?\s*(
let withDb = t[0].split('.');
// 表名是 db名.表名
let tName = withDb.length > 1 ? withDb[1] : withDb[0]
let dbName = withDb.length > 1 ? withDb[0] :(db||'')
if(t.length == 2){
let dbName = withDb.length > 1 ? withDb[0] : (db || '')
if (t.length == 2) {
// 表别名:表名
result[t[1]]= {tableName: tName, dbName}
}else{
result[t[1]] = { tableName: tName, dbName }
} else {
// 只有表名无别名 取第一个无别名的表为默认表
!defName && (defResult = { tableName: tName, dbName: db })
}
@@ -652,18 +652,18 @@ onMounted(() => {
});
/**
* 设置codemirror高度和数据表高度
* 设置editor高度和数据表高度
*/
const setHeight = () => {
// 默认300px
state.monacoOptions.height = window.innerHeight - 550 + 'px'
state.dataTabsTableHeight = window.innerHeight - 274 ;
state.dataTabsTableHeight = window.innerHeight - 274;
};
/**
* 拖拽改变sql编辑区和查询结果区高度
*/
const onDragSetHeight = (e: any) => {
const onDragSetHeight = () => {
document.onmousemove = (e) => {
e.preventDefault();
//得到鼠标拖动的宽高距离:取绝对值
@@ -948,16 +948,16 @@ const getColumnTip = (tableName: string, columnName: string) => {
const getSql = () => {
let res = '' as string | undefined;
// 编辑器还没初始化
if(!monacoEditor.getModel){
if (!monacoEditor.getModel) {
return res;
}
// 选择选中的sql
let selection = monacoEditor.getSelection()
if (selection){
if (selection) {
res = monacoEditor.getModel()?.getValueInRange(selection)
}
// 整个编辑器的sql
if(!res){
if (!res) {
return monacoEditor.getModel()?.getValue()
}
return res
@@ -982,22 +982,22 @@ const changeDb = async (db: string) => {
return;
}
clearDb();
// 加载数据库下所有表
state.tableMetadata = await loadTableMetadata(db)
// 加载数据库下所有表字段信息
state.monacoOptions.dbTables[db] = await loadHintTables(db)
getSqlNames();
};
const loadTableMetadata = async (db: string) =>{
return await dbApi.tableMetadata.request({id: state.dbId, db})
const loadTableMetadata = async (db: string) => {
return await dbApi.tableMetadata.request({ id: state.dbId, db })
}
const loadHintTables = async (db: string) =>{
return await dbApi.hintTables.request({id: state.dbId, db,})
const loadHintTables = async (db: string) => {
return await dbApi.hintTables.request({ id: state.dbId, db, })
}
// 选择表事件
@@ -1195,6 +1195,7 @@ const onTableSortChange = async (sort: any) => {
const changeSqlTemplate = () => {
getUserSql();
};
const changeEditorTheme = () => {
monaco.editor.setTheme(state.monacoOptions.theme);
};
@@ -1217,10 +1218,6 @@ const setSqlEditorValue = (value: string) => {
monacoEditor.getModel()?.setValue(value);
};
const getCodermirrorValue = () => {
return codemirror.getValue();
};
/**
* 获取用户保存的sql模板名称
*/
@@ -1370,7 +1367,7 @@ const cellClick = (row: any, column: any, cell: any) => {
return;
}
// 转为字符串比较,可能存在数字等
let text = (row[property] || row[property]==0 ? row[property] : '') + '';
let text = (row[property] || row[property] == 0 ? row[property] : '') + '';
let div = cell.children[0];
if (div) {
let input = document.createElement('input');
@@ -1466,13 +1463,49 @@ const addRow = async () => {
* 格式化sql
*/
const formatSql = () => {
let selectSql = getSql();
if(selectSql){
monacoEditor.getModel()?.setValue(sqlFormatter(selectSql))
let selection = monacoEditor.getSelection()
let sql = monacoEditor.getModel()?.getValueInRange(selection)
// 有选中sql则格式化并替换选中sql, 否则格式化编辑器所有内容
if (sql) {
replaceSelection(sqlFormatter(sql), selection)
return;
}
monacoEditor.getModel()?.setValue(sqlFormatter(monacoEditor.getValue()));
};
/**
* 替换选中的内容
*/
const replaceSelection = (str: string, selection: any) => {
if (!selection) {
monacoEditor.getModel().setValue(str);
return;
}
const { startLineNumber, endLineNumber, startColumn, endColumn } = selection
const model = monacoEditor.getModel();
const textBeforeSelection = model.getValueInRange({
startLineNumber: 1,
startColumn: 0,
endLineNumber: startLineNumber,
endColumn: startColumn,
})
const textAfterSelection = model.getValueInRange({
startLineNumber: endLineNumber,
startColumn: endColumn,
endLineNumber: model.getLineCount(),
endColumn: model.getLineMaxColumn(model.getLineCount()),
})
monacoEditor.setValue(textBeforeSelection + str + textAfterSelection)
monacoEditor.focus()
monacoEditor.setPosition({
lineNumber: startLineNumber,
column: 0,
})
}
const search = async () => {
const res = await dbApi.dbs.request(state.params);
state.dbs = res.list;
@@ -1515,17 +1548,6 @@ watch(store.state.sqlExecInfo, async (newValue) => {
font-size: 8pt;
font-weight: 600;
border: 1px solid #ccc;
.CodeMirror {
flex-grow: 1;
z-index: 1;
.CodeMirror-code {
line-height: 19px;
}
font-family: 'JetBrainsMono';
}
}
.editor-move-resize {

View File

@@ -1,8 +1,7 @@
<template>
<div>
<el-dialog title="待执行SQL" v-model="dialogVisible" :show-close="false" width="600px" @close="cancel">
<codemirror height="350px" class="codesql" ref="cmEditor" language="sql" v-model="sqlValue"
:options="cmOptions" />
<el-dialog :destroy-on-close="true" title="待执行SQL" v-model="dialogVisible" :show-close="false" width="600px" @close="cancel">
<monaco-editor height="300px" class="codesql" language="sql" v-model="sqlValue" />
<el-input ref="remarkInputRef" v-model="remark" placeholder="请输入执行备注" class="mt5" />
<template #footer>
<span class="dialog-footer">
@@ -19,11 +18,7 @@ import { toRefs, ref, nextTick, reactive } from 'vue';
import { dbApi } from '../api';
import { ElDialog, ElButton, ElInput, ElMessage, InputInstance } from 'element-plus';
// import base style
import 'codemirror/lib/codemirror.css';
// 引入主题后还需要在 options 中指定主题才会生效
import 'codemirror/theme/base16-light.css';
import 'codemirror/addon/selection/active-line';
import { codemirror } from '@/components/codemirror';
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
import { format as sqlFormatter } from 'sql-formatter';
import { SqlExecProps } from './SqlExecBox';
@@ -43,19 +38,6 @@ const props = defineProps({
},
})
const cmOptions = {
tabSize: 4,
mode: 'text/x-sql',
lineNumbers: true,
line: true,
indentWithTabs: true,
smartIndent: true,
matchBrackets: true,
theme: 'base16-light',
autofocus: true,
extraKeys: { Tab: 'autocomplete' }, // 自定义快捷键
}
const remarkInputRef = ref<InputInstance>();
const state = reactive({
dialogVisible: false,

View File

@@ -148,11 +148,10 @@
</template>
</el-dialog>
<el-dialog :destroy-on-close="true" :title="fileContent.dialogTitle" v-model="fileContent.contentVisible"
:close-on-click-modal="false" top="5vh" width="70%">
<el-dialog :destroy-on-close="true" :title="fileContent.dialogTitle" v-model="fileContent.contentVisible" :close-on-click-modal="false"
top="5vh" width="70%">
<div>
<codemirror :can-change-mode="true" ref="cmEditor" v-model="fileContent.content"
:language="fileContent.type" />
<monaco-editor :can-change-mode="true" v-model="fileContent.content" :language="fileContent.type" />
</div>
<template #footer>
@@ -170,7 +169,7 @@ import { ref, toRefs, reactive, watch } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { machineApi } from './api';
import { codemirror } from '@/components/codemirror';
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
import { getSession } from '@/common/utils/storage';
import enums from './enums';
import config from '@/common/config';
@@ -343,14 +342,17 @@ const getFileType = (path: string) => {
if (path.endsWith('.sh')) {
return 'shell';
}
if (path.endsWith('js') || path.endsWith('json')) {
if (path.endsWith('js')) {
return 'javascript';
}
if (path.endsWith('json')) {
return 'json';
}
if (path.endsWith('Dockerfile')) {
return 'dockerfile';
}
if (path.endsWith('nginx.conf')) {
return 'nginx';
return 'shell';
}
if (path.endsWith('sql')) {
return 'sql';

View File

@@ -45,9 +45,7 @@
</el-row>
</el-form-item>
<el-form-item prop="script" label="内容" id="content">
<codemirror ref="cmEditor" v-model="form.script" language="shell" width="700px" />
</el-form-item>
<monaco-editor v-model="form.script" language="shell" height="300px" />
</el-form>
<template #footer>
@@ -67,8 +65,7 @@ import { ElMessage } from 'element-plus';
import { machineApi } from './api';
import enums from './enums';
import { notEmpty } from '@/common/assert';
import { codemirror } from '@/components/codemirror';
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
const props = defineProps({
visible: {
@@ -176,9 +173,5 @@ const cancel = () => {
};
</script>
<style lang="scss">
#content {
.CodeMirror {
height: 300px !important;
}
}
</style>

View File

@@ -54,7 +54,7 @@
</template>
<script lang="ts" setup>
import { ref, toRefs, reactive, watch, defineComponent } from 'vue';
import { ref, toRefs, reactive, watch } from 'vue';
import { configApi } from '../api';
const props = defineProps({

View File

@@ -96,6 +96,11 @@
resolved "https://registry.npmmirror.com/@sphinxxxx/color-conversion/-/color-conversion-2.2.2.tgz"
integrity sha512-XExJS3cLqgrmNBIP3bBw6+1oQ1ksGjFh0+oClDKFYpCCqx/hlqwWO5KO/S63fzUo67SxI9dMrF0y5T/Ey7h8Zw==
"@types/antlr4@4.7.0":
version "4.7.0"
resolved "https://registry.npmmirror.com/@types/antlr4/-/antlr4-4.7.0.tgz#e6300119bddff6e23f5bbd299f5cf3c722a6249b"
integrity sha512-WdyHH4PHxBQkeWoRTbuC/dvf0QErJpJE4UpESQSRmKoMER15DCLFHAHQjkwevMKQie0kqawS/eTY563GGMbz/g==
"@types/json-schema@^7.0.7":
version "7.0.9"
resolved "https://registry.npmmirror.com/@types/json-schema/download/@types/json-schema-7.0.9.tgz?cache=0&sync_timestamp=1637266073261&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40types%2Fjson-schema%2Fdownload%2F%40types%2Fjson-schema-7.0.9.tgz"
@@ -435,6 +440,11 @@ ansi-styles@^4.1.0:
dependencies:
color-convert "^2.0.1"
antlr4@4.7.2:
version "4.7.2"
resolved "https://registry.npmmirror.com/antlr4/-/antlr4-4.7.2.tgz#9d0b5987bb63660de658055ee9149141b4d9b462"
integrity sha512-vZA1xYufXLe3LX+ja9rIVxjRmILb1x3k7KYZHltRbfJtXjJ1DlFIqt+CbPYmghx0EuzY9DajiDw+MdyEt1qAsQ==
anymatch@~3.1.2:
version "3.1.2"
resolved "https://registry.nlark.com/anymatch/download/anymatch-3.1.2.tgz"
@@ -542,11 +552,6 @@ clipboard@^2.0.6:
select "^1.1.2"
tiny-emitter "^2.0.0"
codemirror@^5.65.5:
version "5.65.5"
resolved "https://registry.npmmirror.com/codemirror/-/codemirror-5.65.5.tgz"
integrity sha512-HNyhvGLnYz5c+kIsB9QKVitiZUevha3ovbIYaQiGzKo7ECSL/elWD9RXt3JgNr0NdnyqE9/Rc/7uLfkJQL638w==
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.nlark.com/color-convert/download/color-convert-2.0.1.tgz"
@@ -646,6 +651,14 @@ dotenv@^10.0.0:
resolved "https://registry.nlark.com/dotenv/download/dotenv-10.0.0.tgz"
integrity sha1-PUInuPuV+BCWzdK2ZlP7LHCFuoE=
dt-sql-parser@^4.0.0-beta.2.2:
version "4.0.0-beta.2.2"
resolved "https://registry.npmmirror.com/dt-sql-parser/-/dt-sql-parser-4.0.0-beta.2.2.tgz#84fbed385afc19ca6464cd266889e3f52e73083a"
integrity sha512-LLAE659zgizdokkDniHFPk0PsLPV3cXFOQPW+QT+3W1/TQJ2h8yzKCBBufXmKAHMpAr+KjTRTa71VJRzWJx8Zg==
dependencies:
"@types/antlr4" "4.7.0"
antlr4 "4.7.2"
echarts@^5.3.3:
version "5.3.3"
resolved "https://registry.npmmirror.com/echarts/-/echarts-5.3.3.tgz"
@@ -982,6 +995,11 @@ fast-levenshtein@^2.0.6:
resolved "https://registry.nlark.com/fast-levenshtein/download/fast-levenshtein-2.0.6.tgz"
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
fast-plist@^0.1.2:
version "0.1.2"
resolved "https://registry.npmmirror.com/fast-plist/-/fast-plist-0.1.2.tgz#a45aff345196006d406ca6cdcd05f69051ef35b8"
integrity sha512-2HxzrqJhmMoxVzARjYFvkzkL2dCBB8sogU5sD8gqcZWv5UCivK9/cXM9KIPDRwU+eD3mbRDN/GhW8bO/4dtMfg==
fastq@^1.6.0:
version "1.13.0"
resolved "https://registry.nlark.com/fastq/download/fastq-1.13.0.tgz"
@@ -1342,6 +1360,25 @@ mobius1-selectr@^2.4.13:
resolved "https://registry.npmmirror.com/mobius1-selectr/-/mobius1-selectr-2.4.13.tgz"
integrity sha512-Mk9qDrvU44UUL0EBhbAA1phfQZ7aMZPjwtL7wkpiBzGh8dETGqfsh50mWoX9EkjDlkONlErWXArHCKfoxVg0Bw==
monaco-editor@^0.34.1:
version "0.34.1"
resolved "https://registry.npmmirror.com/monaco-editor/-/monaco-editor-0.34.1.tgz#1b75c4ad6bc4c1f9da656d740d98e0b850a22f87"
integrity sha512-FKc80TyiMaruhJKKPz5SpJPIjL+dflGvz4CpuThaPMc94AyN7SeC9HQ8hrvaxX7EyHdJcUY5i4D0gNyJj1vSZQ==
monaco-sql-languages@^0.9.5:
version "0.9.5"
resolved "https://registry.npmmirror.com/monaco-sql-languages/-/monaco-sql-languages-0.9.5.tgz#075ffe947e66f0dc7a53f92cb99c2d4649632a34"
integrity sha512-IBIKQVIoW1Q90pJ/0Qi0sWMgbvho5ug17wx64hVid/XCr+L7ngJaTdaRnveOMPwg9qj+PQqOt1Ga0q0AwG85wA==
dependencies:
dt-sql-parser "^4.0.0-beta.2.2"
monaco-themes@^0.4.2:
version "0.4.2"
resolved "https://registry.npmmirror.com/monaco-themes/-/monaco-themes-0.4.2.tgz#6939339cb2f0bfb743f6e454de4136971834f16a"
integrity sha512-T3kp6SC5MPJvwYGXZENCd0UOIKVgUVV5SjsiXLBhgEZBnScY+6gEbwNRK1oYmfwbf+dGVqF1bSLN5YcrFu3HmA==
dependencies:
fast-plist "^0.1.2"
ms@2.1.2:
version "2.1.2"
resolved "https://registry.npmmirror.com/ms/download/ms-2.1.2.tgz"

View File

@@ -3,6 +3,7 @@ package api
import (
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/global"
"mayfly-go/pkg/ws"
"github.com/gin-gonic/gin"
@@ -17,8 +18,11 @@ func (s *System) ConnectWs(g *gin.Context) {
wsConn, err := ws.Upgrader.Upgrade(g.Writer, g.Request, nil)
defer func() {
if err := recover(); err != nil {
wsConn.WriteMessage(websocket.TextMessage, []byte(err.(error).Error()))
wsConn.Close()
global.Log.Error(err.(error).Error())
if wsConn != nil {
wsConn.WriteMessage(websocket.TextMessage, []byte(err.(error).Error()))
wsConn.Close()
}
}
}()