!20 feat: 新增monaco编辑器

Merge pull request !20 from zongyangleo/dev_upstream
This commit is contained in:
Coder慌
2022-11-04 06:10:35 +00:00
committed by Gitee
7 changed files with 432 additions and 116 deletions

View File

@@ -19,6 +19,9 @@
"jsoneditor": "^9.9.2", "jsoneditor": "^9.9.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mitt": "^3.0.0", "mitt": "^3.0.0",
"monaco-editor": "^0.34.1",
"monaco-sql-languages": "^0.9.5",
"monaco-themes": "^0.4.2",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"screenfull": "^6.0.2", "screenfull": "^6.0.2",
"sortablejs": "^1.13.0", "sortablejs": "^1.13.0",

View File

@@ -2,4 +2,23 @@ window.globalConfig = {
// 默认为空以访问根目录为api请求地址。若前后端分离部署可单独配置该后端api请求地址 // 默认为空以访问根目录为api请求地址。若前后端分离部署可单独配置该后端api请求地址
"BaseApiUrl": "", "BaseApiUrl": "",
"BaseWsUrl": "" "BaseWsUrl": ""
} }
// index.html添加百秒级时间戳防止被浏览器缓存
!function(){
let t = "t=" + new Date().getTime().toString().substring(0, 8)
let search = location.search;
let m = search && search.match(/t=\d*/g)
if (m[0]){
if( m[0] !== t){
location.search = search.replace(m[0], t)
}
} else {
if(search.indexOf('?') > -1){
location.search = search + '&'+t
}else{
location.search = t
}
}
}()

View File

@@ -353,8 +353,6 @@ const state = reactive({
tableCreateDialog: { tableCreateDialog: {
title: '创建表', title: '创建表',
visible: false, visible: false,
columns: [],
indexs: [],
activeName: '1', activeName: '1',
type: '', type: '',
enableEditTypes:['mysql'], // 支持"编辑表"的数据库类型 enableEditTypes:['mysql'], // 支持"编辑表"的数据库类型
@@ -693,8 +691,8 @@ const openEditTable = async (row: any) => {
state.tableCreateDialog.visible = true state.tableCreateDialog.visible = true
state.tableCreateDialog.activeName = '1' state.tableCreateDialog.activeName = '1'
if(row === false){ if (row === false) {
state.tableCreateDialog.data = {edit: false, row: {}, indexs: [], columns: [] } state.tableCreateDialog.data = { edit: false, row: {}, indexs: [], columns: [] }
state.tableCreateDialog.title = '创建表' state.tableCreateDialog.title = '创建表'
} }

View File

@@ -87,6 +87,13 @@
</div> </div>
<div style="float: right" class="fl"> <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">
{{ item }}
</el-option>
</el-select>
<el-select v-model="sqlName" placeholder="选择or输入SQL模板名" @change="changeSqlTemplate" <el-select v-model="sqlName" placeholder="选择or输入SQL模板名" @change="changeSqlTemplate"
filterable allow-create default-first-option size="small" class="mr10"> filterable allow-create default-first-option size="small" class="mr10">
<el-option v-for="item in sqlNames as any" :key="item" :label="item.database" <el-option v-for="item in sqlNames as any" :key="item" :label="item.database"
@@ -104,9 +111,11 @@
</div> </div>
<div class="mt5 sqlEditor"> <div class="mt5 sqlEditor">
<textarea ref="codeTextarea"></textarea> <div ref="monacoTextarea" :style="{height: monacoOptions.height}"></div>
</div>
<div class="editor-move-resize" @mousedown="onDragSetHeight">
<el-icon><Minus /></el-icon>
</div> </div>
<div class="mt5"> <div class="mt5">
<el-row> <el-row>
<el-link v-if="queryTab.nowTableName" @click="onDeleteData" class="ml5" type="danger" <el-link v-if="queryTab.nowTableName" @click="onDeleteData" class="ml5" type="danger"
@@ -120,7 +129,7 @@
</el-row> </el-row>
<el-table @cell-dblclick="cellClick" @selection-change="onDataSelectionChange" <el-table @cell-dblclick="cellClick" @selection-change="onDataSelectionChange"
:data="queryTab.execRes.data" v-loading="queryTab.loading" element-loading-text="查询中..." :data="queryTab.execRes.data" v-loading="queryTab.loading" element-loading-text="查询中..."
size="small" max-height="250" empty-text="tips: select *开头的单表查询或点击表名默认查询的数据,可双击数据在线修改" size="small" :max-height="monacoOptions.tableMaxHeight" empty-text="tips: select *开头的单表查询或点击表名默认查询的数据,可双击数据在线修改"
stripe border class="mt5"> stripe border class="mt5">
<el-table-column v-if="queryTab.execRes.tableColumn.length > 0 && queryTab.nowTableName" <el-table-column v-if="queryTab.execRes.tableColumn.length > 0 && queryTab.nowTableName"
type="selection" width="35" /> type="selection" width="35" />
@@ -130,6 +139,7 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
</div> </div>
</div> </div>
</el-tab-pane> </el-tab-pane>
@@ -244,65 +254,56 @@
import { onMounted, toRefs, reactive, ref, watch } from 'vue'; import { onMounted, toRefs, reactive, ref, watch } from 'vue';
import { dbApi } from './api'; import { dbApi } from './api';
import 'codemirror/addon/hint/show-hint.css'; import {format as sqlFormatter} from 'sql-formatter';
// import base style import {isTrue, notBlank, notEmpty} from '@/common/assert';
import 'codemirror/lib/codemirror.css'; import {ElMessage, ElMessageBox} from 'element-plus';
// 引入主题后还需要在 options 中指定主题才会生效
import 'codemirror/theme/base16-light.css';
import 'codemirror/addon/selection/active-line';
import _CodeMirror from 'codemirror';
import 'codemirror/addon/hint/show-hint.js';
import 'codemirror/addon/hint/sql-hint.js';
import { format as sqlFormatter } from 'sql-formatter';
import { notBlank, notEmpty, isTrue } from '@/common/assert';
import { ElMessage, ElMessageBox } from 'element-plus';
import config from '@/common/config'; import config from '@/common/config';
import { getSession } from '@/common/utils/storage'; import {getSession} from '@/common/utils/storage';
import SqlExecBox from './component/SqlExecBox'; import SqlExecBox from './component/SqlExecBox';
import { dateStrFormat } from '@/common/utils/date.ts'; import {dateStrFormat} from '@/common/utils/date.ts';
import { useStore } from '@/store/index.ts'; import {useStore} from '@/store/index.ts';
import { tagApi } from '../tag/api.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 * as monaco 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 { Minus } from '@element-plus/icons-vue';
const store = useStore(); const store = useStore();
const codeTextarea: any = ref(null); const monacoTextarea: any = ref(null);
const token = getSession('token'); const token = getSession('token');
let codemirror = null as any;
const tableMap = new Map(); const tableMap = new Map();
const defalutLimit = 20 const defalutLimit = 20
const cmOptions = { export type TableMeta = {
tabSize: 4, // 表名
mode: 'text/x-sql', tableName:string,
lineNumbers: true, // 表注释
line: true, tableComment:string
indentWithTabs: true,
smartIndent: true,
matchBrackets: true,
theme: 'base16-light',
autofocus: true,
extraKeys: { Tab: 'autocomplete' }, // 自定义快捷键
hintOptions: {
completeSingle: false,
// 自定义提示选项
tables: {},
},
// more CodeMirror options...
} }
const state = reactive({ const state = reactive({
token: token, token: token,
tags: [], tags: [],
dbs: [] as any, // 数据库实例列表 dbs: [] as any, // 数据库实例列表
databaseList: [], // 数据库实例拥有的数据库列表1数据库实例 -> 多数据库 databaseList: [] as string[], // 数据库实例拥有的数据库列表1数据库实例 -> 多数据库
db: '', // 当前操作的数据库 db: '', // 当前操作的数据库
dbType: '', dbType: '',
tables: [] as any, tables: [] as any,
dbId: null, // 当前选中操作的数据库实例 dbId: null, // 当前选中操作的数据库实例
tableName: '', tableName: '',
tableMetadata: [], tableMetadata: [] as TableMeta[],
sqlName: '', // 当前sql模板名 sqlName: '', // 当前sql模板名
sqlNames: [], // 所有sql模板名 sqlNames: [], // 所有sql模板名
activeName: 'Query', activeName: 'Query',
@@ -316,16 +317,16 @@ const state = reactive({
// 点击执行按钮执行结果信息 // 点击执行按钮执行结果信息
execRes: { execRes: {
data: [], data: [],
tableColumn: [], tableColumn: []
}, },
loading: false, loading: false,
nowTableName: '', //当前表格数据操作的数据库表名,用于双击编辑表内容使用 nowTableName: '', //当前表格数据操作的数据库表名,用于双击编辑表内容使用
selectionDatas: [], selectionDatas: []
}, },
params: { params: {
pageNum: 1, pageNum: 1,
pageSize: 100, pageSize: 100,
tagPath: null, tagPath: null
}, },
conditionDialog: { conditionDialog: {
title: '', title: '',
@@ -334,14 +335,21 @@ const state = reactive({
dataTab: null, dataTab: null,
visible: false, visible: false,
condition: '=', condition: '=',
value: null, value: null
}, },
genSqlDialog: { genSqlDialog: {
visible: false, visible: false,
sql: '', sql: '',
}, },
monacoOptions: {
editor: {} as editor.IStandaloneCodeEditor,
height: '',
tableMaxHeight:250,
dbTables:{},
theme:'',
defaultThemes:[ 'vs' ,'vs-dark', 'hc-black', 'hc-light', 'Monokai', 'Active4D', 'ahe', 'bop', 'krTheme', 'Dracula', 'TextmateMac'],
}
}); });
const { const {
tags, tags,
dbs, dbs,
@@ -360,27 +368,282 @@ const {
params, params,
conditionDialog, conditionDialog,
genSqlDialog, genSqlDialog,
monacoOptions
} = toRefs(state) } = toRefs(state)
const initCodemirror = () => { let monacoEditor = {} as editor.IStandaloneCodeEditor;
// 初始化编辑器实例,传入需要被实例化的文本域对象和默认配置
codemirror = _CodeMirror.fromTextArea(codeTextarea.value, cmOptions);
codemirror.on('inputRead', (instance: any, changeObj: any) => {
if (/^[a-zA-Z]/.test(changeObj.text[0])) {
instance.showHint();
}
});
codemirror.on('beforeChange', (instance: any, changeObj: any) => { self.MonacoEnvironment = {
var text = changeObj.text[0]; getWorker() {
// 将sql提示去除 return new EditorWorker();
changeObj.text[0] = text.split(' ')[0]; }
});
}; };
const initMonacoEditor = () => {
console.log('初始化编辑器')
// options参数参考 https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html#language
let defVal = `-- monaco editor`;
monacoEditor = monaco.editor.create(monacoTextarea.value, {
value: defVal,
language: 'sql',
theme: 'vs',
automaticLayout: true, //自适应宽高布局
foldingStrategy: 'indentation',//代码可分小段折叠
roundedSelection: false, // 禁用选择文本背景的圆角
matchBrackets: 'near',
linkedEditing:true,
cursorBlinking: 'smooth',// 光标闪烁样式
mouseWheelZoom: true, // 在按住Ctrl键的同时使用鼠标滚轮时在编辑器中缩放字体
overviewRulerBorder: false, // 不要滚动条的边框
tabSize: 2, // tab 缩进长度
// fontFamily:'consolas', // 字体 暂时不要设置,否则光标容易错位
// letterSpacing: 1, 字符间距
// quickSuggestions:false, // 禁用代码提示
minimap: {
enabled: false, // 不要小地图
},
});
// 初始化一些主题
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:['.'],
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
// 当前行文本
let lineContent = model.getLineContent(lineNumber);
// 注释行不需要代码提示
if(lineContent.startsWith('--')){
return {suggestions: []}
}
let range = {
startLineNumber: lineNumber,
endLineNumber: lineNumber,
startColumn,
endColumn,
};
// 光标前文本
const textBeforePointer = model.getValueInRange({
startLineNumber: lineNumber,
startColumn: 0,
endLineNumber: lineNumber,
endColumn: column
})
const textBeforePointerMulti = model.getValueInRange({
startLineNumber: 1,
startColumn: 0,
endLineNumber: lineNumber,
endColumn: column
})
// 光标后文本
const textAfterPointerMulti = model.getValueInRange({
startLineNumber: lineNumber,
startColumn: column,
endLineNumber: model.getLineCount(),
endColumn: model.getLineMaxColumn(model.getLineCount())
})
// // const nextTokens = textAfterPointer.trim().split(/\s+/)
// // const nextToken = nextTokens[0].toLowerCase()
const tokens = textBeforePointer.trim().split(/\s+/)
const lastToken = tokens[tokens.length - 1].toLowerCase()
console.log("光标前文本=>" + textBeforePointerMulti)
console.log("最后输入的=>" + lastToken)
if (lastToken.endsWith('.')) {
// 如果是.触发代码提示,则进行【 库.表名联想 】 或 【 表别名.表字段联想 】
let str = lastToken.substring(0, lastToken.lastIndexOf('.'))
// 库.表名联想
if (state.databaseList.indexOf(str) > -1) {
let tables = await loadTableMetadata(str)
let suggestions: languages.CompletionItem[] = []
for(let item of tables){
const {tableName, tableComment} = item
suggestions.push({
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 table = tableInfo.tableName
let db = tableInfo.dbName
// 取出表名并提示
let dbs = state.monacoOptions.dbTables[db]
let tables = dbs ? (dbs[table] || []) : [];
if((!tables || tables.length === 0) && db){
state.monacoOptions.dbTables[db] = await loadHintTables(db)
dbs = state.monacoOptions.dbTables[db]
tables = dbs ? (dbs[table] || []) : [];
}
tables.sort()
let suggestions: languages.CompletionItem[] = []
tables.forEach((a:string)=>{
// 字段数据格式 字段名 字段注释, 如: create_time [datetime][创建时间]
let fieldName = a.substring(0,a.indexOf(" "))
let comment = a.replace(eval(`/${fieldName}\\s+/`), '')
let detail = fieldName + ( comment?' - ' + comment :'' )
suggestions.push({
label: detail,
kind: monaco.languages.CompletionItemKind.Property,
detail: detail,
insertText: fieldName,
range
});
})
return { suggestions }
//
}
return { suggestions:[] }
}
// 库名联想
let suggestions: languages.CompletionItem[] = []
// mysql关键字
sqlLanguage.keywords.forEach((item: any) => {
suggestions.push({
label: item,
kind: monaco.languages.CompletionItemKind.Keyword,
insertText: item,
range
});
})
// 操作符
sqlLanguage.operators.forEach((item: any) => {
suggestions.push({
label: item,
kind: monaco.languages.CompletionItemKind.Operator,
insertText: item,
range
});
})
// 内置函数
sqlLanguage.builtinFunctions.forEach((item: any) => {
suggestions.push({
label: item,
kind: monaco.languages.CompletionItemKind.Function,
insertText: item,
range
});
})
// 内置变量
sqlLanguage.builtinVariables.forEach((item: string) => {
suggestions.push({
label: item,
kind: monaco.languages.CompletionItemKind.Variable,
insertText: item,
range
});
})
// 库名提示
state.databaseList.forEach(a => {
suggestions.push({
label: a + ' - schema',
kind: monaco.languages.CompletionItemKind.Folder,
insertText: a,
range
});
})
// 表名联想
state.tableMetadata.forEach((tableMeta: TableMeta) => {
const {tableName, tableComment} = tableMeta
suggestions.push({
label: tableName + ' - ' + tableComment,
kind: monaco.languages.CompletionItemKind.File,
detail: tableComment,
insertText: tableName,
range
});
})
// 默认提示
return {
suggestions: suggestions
};
},
});
};
/**
* 根据别名获取sql里的表名
* @param sql sql
* @param db 默认数据库
* @param alias 别名
*/
const getTableByAlias = (sql: string, db: string, alias: string):{dbName: string, tableName: string} => {
// 表别名:表名
let result = {};
let defName = '';
let defResult = {};
// 正则匹配取出表名和表别名
// 测试sql
/*
`select * from database.Outvisit l
left join patient p on l.patid=p.patientid
join patstatic c on l.patid=c.patid inner join patphone ph on l.patid=ph.patid
where l.name='kevin' and exsits(select 1 from pharmacywestpas pw where p.outvisitid=l.outvisitid)
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=>{
// 去掉前缀,取出
let t = a.substring(5, a.length)
.replaceAll(/\s+as\s+/g, ' ')
.split(/\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){
// 表别名:表名
result[t[1]]= {tableName: tName, dbName}
}else{
// 只有表名无别名 取第一个无别名的表为默认表
!defName && (defResult = { tableName: tName, dbName: db })
}
})
}
return result[alias] || defResult
}
onMounted(() => { onMounted(() => {
initCodemirror();
setHeight(); setHeight();
initMonacoEditor();
// 监听浏览器窗口大小变化,更新对应组件高度 // 监听浏览器窗口大小变化,更新对应组件高度
window.onresize = () => window.onresize = () =>
(() => { (() => {
@@ -393,10 +656,25 @@ onMounted(() => {
*/ */
const setHeight = () => { const setHeight = () => {
// 默认300px // 默认300px
codemirror.setSize('auto', `${window.innerHeight - 538}px`); state.monacoOptions.height = window.innerHeight - 550 + 'px'
state.dataTabsTableHeight = window.innerHeight - 274; state.dataTabsTableHeight = window.innerHeight - 274 ;
}; };
/**
* 拖拽改变sql编辑区和查询结果区高度
*/
const onDragSetHeight = (e: any) => {
document.onmousemove = (e) => {
e.preventDefault();
//得到鼠标拖动的宽高距离:取绝对值
state.monacoOptions.height = `${monacoTextarea.value.offsetHeight + e.movementY}px`
state.monacoOptions.tableMaxHeight -= e.movementY
}
document.onmouseup = () => {
document.onmousemove = null;
}
}
/** /**
* 标签更改后的回调事件 * 标签更改后的回调事件
*/ */
@@ -425,9 +703,8 @@ const getTags = async () => {
const onRunSql = async () => { const onRunSql = async () => {
notBlank(state.dbId, '请先选择数据库'); notBlank(state.dbId, '请先选择数据库');
// 没有选中的文本,则为全部文本 // 没有选中的文本,则为全部文本
let sql = getSql(); let sql = getSql() as string;
isTrue(sql && sql.trim(), '请选中需要执行的sql'); notBlank(sql && sql.trim(), '请选中需要执行的sql');
// 去除字符串前的空格、换行等 // 去除字符串前的空格、换行等
sql = sql.replace(/(^\s*)/g, ''); sql = sql.replace(/(^\s*)/g, '');
let execRemark = ''; let execRemark = '';
@@ -669,12 +946,21 @@ const getColumnTip = (tableName: string, columnName: string) => {
* 获取sql如果有鼠标选中则返回选中内容否则返回输入框内所有内容 * 获取sql如果有鼠标选中则返回选中内容否则返回输入框内所有内容
*/ */
const getSql = () => { const getSql = () => {
// 没有选中的文本,则为全部文本 let res = '' as string | undefined;
let selectSql = codemirror.getSelection(); // 编辑器还没初始化
if (!selectSql) { if(!monacoEditor.getModel){
selectSql = getCodermirrorValue(); return res;
} }
return selectSql; // 选择选中的sql
let selection = monacoEditor.getSelection()
if (selection){
res = monacoEditor.getModel()?.getValueInRange(selection)
}
// 整个编辑器的sql
if(!res){
return monacoEditor.getModel()?.getValue()
}
return res
}; };
/** /**
@@ -691,26 +977,29 @@ const changeDbInstance = (dbId: any) => {
/** /**
* 更改数据库事件 * 更改数据库事件
*/ */
const changeDb = (db: string) => { const changeDb = async (db: string) => {
if (!db) { if (!db) {
return; return;
} }
clearDb(); clearDb();
dbApi.tableMetadata.request({ id: state.dbId, db }).then((res) => {
state.tableMetadata = res; // 加载数据库下所有表
}); state.tableMetadata = await loadTableMetadata(db)
// 加载数据库下所有表字段信息
state.monacoOptions.dbTables[db] = await loadHintTables(db)
dbApi.hintTables
.request({
id: state.dbId,
db,
})
.then((res) => {
cmOptions.hintOptions.tables = res;
});
getSqlNames(); getSqlNames();
}; };
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 changeTable = async (tableName: string, execSelectSql: boolean = true) => { const changeTable = async (tableName: string, execSelectSql: boolean = true) => {
if (tableName == '') { if (tableName == '') {
@@ -906,6 +1195,9 @@ const onTableSortChange = async (sort: any) => {
const changeSqlTemplate = () => { const changeSqlTemplate = () => {
getUserSql(); getUserSql();
}; };
const changeEditorTheme = () => {
monaco.editor.setTheme(state.monacoOptions.theme);
};
/** /**
* 获取用户保存的sql模板内容 * 获取用户保存的sql模板内容
@@ -914,15 +1206,15 @@ const getUserSql = () => {
notBlank(state.dbId, '请先选择数据库'); notBlank(state.dbId, '请先选择数据库');
dbApi.getSql.request({ id: state.dbId, type: 1, name: state.sqlName, db: state.db }).then((res) => { dbApi.getSql.request({ id: state.dbId, type: 1, name: state.sqlName, db: state.db }).then((res) => {
if (res) { if (res) {
setCodermirrorValue(res.sql); setSqlEditorValue(res.sql);
} else { } else {
setCodermirrorValue(''); setSqlEditorValue('');
} }
}); });
}; };
const setCodermirrorValue = (value: string) => { const setSqlEditorValue = (value: string) => {
codemirror.setValue(value); monacoEditor.getModel()?.setValue(value);
}; };
const getCodermirrorValue = () => { const getCodermirrorValue = () => {
@@ -952,8 +1244,8 @@ const getSqlNames = () => {
}; };
const saveSql = async () => { const saveSql = async () => {
const sql = codemirror.getValue(); const sql = monacoEditor.getModel()?.getValue();
notEmpty(sql, 'sql内容不能为空'); notBlank(sql, 'sql内容不能为空');
notBlank(state.dbId, '请先选择数据库实例'); notBlank(state.dbId, '请先选择数据库实例');
await dbApi.saveSql.request({ id: state.dbId, db: state.db, sql: sql, type: 1, name: state.sqlName }); await dbApi.saveSql.request({ id: state.dbId, db: state.db, sql: sql, type: 1, name: state.sqlName });
ElMessage.success('保存成功'); ElMessage.success('保存成功');
@@ -990,13 +1282,12 @@ const clearDb = () => {
state.nowTableName = ''; state.nowTableName = '';
state.tableMetadata = []; state.tableMetadata = [];
state.dataTabs = {}; state.dataTabs = {};
setCodermirrorValue(''); setSqlEditorValue('');
state.sqlNames = []; state.sqlNames = [];
state.sqlName = ''; state.sqlName = '';
state.activeName = state.queryTab.name; state.activeName = state.queryTab.name;
state.queryTab.execRes.data = []; state.queryTab.execRes.data = [];
state.queryTab.execRes.tableColumn = []; state.queryTab.execRes.tableColumn = [];
cmOptions.hintOptions.tables = [];
tableMap.clear(); tableMap.clear();
}; };
@@ -1079,19 +1370,21 @@ const cellClick = (row: any, column: any, cell: any) => {
return; return;
} }
// 转为字符串比较,可能存在数字等 // 转为字符串比较,可能存在数字等
let text = (row[property] ? row[property] : '') + ''; let text = (row[property] || row[property]==0 ? row[property] : '') + '';
let div = cell.children[0]; let div = cell.children[0];
if (div) { if (div) {
let input = document.createElement('input'); let input = document.createElement('input');
input.setAttribute('value', text); input.setAttribute('value', text);
// 将表格width也赋值于输入框避免输入框长度超过表格长度 // 将表格width也赋值于输入框避免输入框长度超过表格长度
input.setAttribute('style', 'height:30px;' + div.getAttribute('style')); input.setAttribute('style', 'height:23px;text-align:center;border:none;' + div.getAttribute('style'));
cell.replaceChildren(input); cell.replaceChildren(input);
input.focus(); input.focus();
input.addEventListener('blur', async () => { input.addEventListener('blur', async () => {
row[property] = input.value; row[property] = input.value;
cell.replaceChildren(div); cell.replaceChildren(div);
if (input.value !== text) { if (input.value !== text) {
// 设置修改了的字段 背景色
// div.setAttribute('style', (div.getAttribute('style')||'')+';background-color:var(--el-color-success)')
const primaryKey = await getColumn(state.nowTableName); const primaryKey = await getColumn(state.nowTableName);
const primaryKeyColumnName = primaryKey.columnName; const primaryKeyColumnName = primaryKey.columnName;
// 更新字段列信息 // 更新字段列信息
@@ -1099,7 +1392,10 @@ const cellClick = (row: any, column: any, cell: any) => {
const sql = `UPDATE ${state.nowTableName} SET ${column.rawColumnKey} = ${wrapColumnValue(updateColumn, input.value)} const sql = `UPDATE ${state.nowTableName} SET ${column.rawColumnKey} = ${wrapColumnValue(updateColumn, input.value)}
WHERE ${primaryKeyColumnName} = ${wrapColumnValue(primaryKey, row[primaryKeyColumnName])}`; WHERE ${primaryKeyColumnName} = ${wrapColumnValue(primaryKey, row[primaryKeyColumnName])}`;
promptExeSql(sql, () => { promptExeSql(sql, () => {
// 还原值
row[property] = text; row[property] = text;
// 还原背景色
// div.setAttribute('style', (div.getAttribute('style')||'')+';background-color:inherit')
}); });
} }
}); });
@@ -1170,9 +1466,11 @@ const addRow = async () => {
* 格式化sql * 格式化sql
*/ */
const formatSql = () => { const formatSql = () => {
let selectSql = codemirror.getSelection(); let selectSql = getSql();
isTrue(selectSql, '请选中需要格式化的sql'); if(selectSql){
codemirror.replaceSelection(sqlFormatter(selectSql)); monacoEditor.getModel()?.setValue(sqlFormatter(selectSql))
}
}; };
const search = async () => { const search = async () => {
@@ -1183,7 +1481,7 @@ const search = async () => {
// 加载选中的db // 加载选中的db
const setSelects = async (sqlExecInfo: any) => { const setSelects = async (sqlExecInfo: any) => {
// 保存sql // 保存sql
let sql = codemirror?.getValue(); let sql = getSql();
if (sql && sql.length > 0 && state.dbId) { if (sql && sql.length > 0 && state.dbId) {
await saveSql(); await saveSql();
} }
@@ -1197,7 +1495,7 @@ const setSelects = async (sqlExecInfo: any) => {
state.dbId = dbId; state.dbId = dbId;
state.db = db; state.db = db;
// 加载schema下所有表 // 加载schema下所有表
changeDb(db); await changeDb(db);
}; };
// 判断如果有数据则加载下拉选项 // 判断如果有数据则加载下拉选项
@@ -1230,6 +1528,12 @@ watch(store.state.sqlExecInfo, async (newValue) => {
} }
} }
.editor-move-resize {
cursor: n-resize;
height: 3px;
text-align: center;
}
.el-tabs__header { .el-tabs__header {
padding: 0 10px; padding: 0 10px;
background-color: #fff; background-color: #fff;

View File

@@ -41,4 +41,4 @@ const SqlExecBox = (props: SqlExecProps): void => {
} }
} }
export default SqlExecBox; export default SqlExecBox;

View File

@@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<el-dialog title="待执行SQL" v-model="dialogVisible" :show-close="false" width="600px"> <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" <codemirror height="350px" class="codesql" ref="cmEditor" language="sql" v-model="sqlValue"
:options="cmOptions" /> :options="cmOptions" />
<el-input ref="remarkInputRef" v-model="remark" placeholder="请输入执行备注" class="mt5" /> <el-input ref="remarkInputRef" v-model="remark" placeholder="请输入执行备注" class="mt5" />
@@ -70,7 +70,7 @@ const {
dialogVisible, dialogVisible,
sqlValue, sqlValue,
remark, remark,
btnLoading, btnLoading
} = toRefs(state) } = toRefs(state)
state.sqlValue = props.sql as any; state.sqlValue = props.sql as any;
@@ -148,7 +148,6 @@ const open = (props: SqlExecProps) => {
}; };
defineExpose({ open }) defineExpose({ open })
</script> </script>
<style lang="scss"> <style lang="scss">
.codesql { .codesql {

View File

@@ -37,14 +37,7 @@ const viteConfig: UserConfig = {
outDir: 'dist', outDir: 'dist',
minify: 'esbuild', minify: 'esbuild',
sourcemap: false, sourcemap: false,
chunkSizeWarningLimit: 1500, chunkSizeWarningLimit: 1500
rollupOptions: {
output: {
entryFileNames: `assets/[name].${new Date().getTime()}.js`,
chunkFileNames: `assets/[name].${new Date().getTime()}.js`,
assetFileNames: `assets/[name].${new Date().getTime()}.[ext]`,
},
},
}, },
define: { define: {
__VUE_I18N_LEGACY_API__: JSON.stringify(false), __VUE_I18N_LEGACY_API__: JSON.stringify(false),