mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-02 15:30:25 +08:00
feat: 常用操作界面支持Splitpane等
This commit is contained in:
@@ -29,6 +29,7 @@
|
||||
"qrcode.vue": "^3.4.1",
|
||||
"screenfull": "^6.0.2",
|
||||
"sortablejs": "^1.15.0",
|
||||
"splitpanes": "^3.1.5",
|
||||
"sql-formatter": "^14.0.0",
|
||||
"uuid": "^9.0.1",
|
||||
"vue": "^3.3.10",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="monaco-editor" style="border: 1px solid var(--el-border-color-light, #ebeef5)">
|
||||
<div class="monaco-editor" style="border: 1px solid var(--el-border-color-light, #ebeef5); height: 100%">
|
||||
<div class="monaco-editor-content" 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 languageArr" :key="mode.value" :label="mode.label" :value="mode.value"> </el-option>
|
||||
|
||||
@@ -13,6 +13,8 @@ import 'element-plus/theme-chalk/dark/css-vars.css';
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import 'splitpanes/dist/splitpanes.css';
|
||||
|
||||
import '@/theme/index.scss';
|
||||
import '@/assets/font/font.css';
|
||||
import '@/assets/iconfont/iconfont.js';
|
||||
|
||||
@@ -8,21 +8,21 @@
|
||||
}
|
||||
|
||||
:root {
|
||||
--color-white: #ffffff;
|
||||
--bg-main-color: #f8f8f8;
|
||||
--bg-color: #f5f5ff;
|
||||
--bg-menuBarActiveColor: #0000000a; // 菜单栏激活时的背景色
|
||||
--border-color-light: #f1f2f3;
|
||||
--el-color-primary-lighter: #ecf5ff;
|
||||
--color-success-lighter: #f0f9eb;
|
||||
--color-warning-lighter: #fdf6ec;
|
||||
--color-danger-lighter: #fef0f0;
|
||||
--color-dark-hover: #0000001a;
|
||||
--color-menu-hover: rgba(0, 0, 0, 0.2);
|
||||
--color-user-hover: rgba(0, 0, 0, 0.04);
|
||||
--color-seting-main: #e9eef3;
|
||||
--color-seting-aside: #d3dce6;
|
||||
--color-seting-header: #b3c0d1;
|
||||
--color-white: #ffffff;
|
||||
--bg-main-color: #f8f8f8;
|
||||
--bg-color: #f5f5ff;
|
||||
--bg-menuBarActiveColor: #0000000a; // 菜单栏激活时的背景色
|
||||
--border-color-light: #f1f2f3;
|
||||
--el-color-primary-lighter: #ecf5ff;
|
||||
--color-success-lighter: #f0f9eb;
|
||||
--color-warning-lighter: #fdf6ec;
|
||||
--color-danger-lighter: #fef0f0;
|
||||
--color-dark-hover: #0000001a;
|
||||
--color-menu-hover: rgba(0, 0, 0, 0.2);
|
||||
--color-user-hover: rgba(0, 0, 0, 0.04);
|
||||
--color-seting-main: #e9eef3;
|
||||
--color-seting-aside: #d3dce6;
|
||||
--color-seting-header: #b3c0d1;
|
||||
|
||||
--tagsview3-active-background-color: var(--el-color-primary-light-9);
|
||||
}
|
||||
@@ -373,6 +373,45 @@ body,
|
||||
cursor: pointer;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.pointer-icon:hover {
|
||||
color: var(--el-color-primary); /* 鼠标移动到图标时的颜色 */
|
||||
color: var(--el-color-primary);
|
||||
/* 鼠标移动到图标时的颜色 */
|
||||
}
|
||||
|
||||
|
||||
/** splitpanes **/
|
||||
.splitpanes.default-theme .splitpanes {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.splitpanes.default-theme .splitpanes__pane {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.default-theme.splitpanes--vertical>.splitpanes__splitter {
|
||||
border-left: 1px solid var(--bg-main-color);
|
||||
}
|
||||
|
||||
.default-theme.splitpanes--horizontal>.splitpanes__splitter {
|
||||
border-top: 1px solid var(--bg-main-color);
|
||||
}
|
||||
// 竖线样式
|
||||
.splitpanes.default-theme .splitpanes__splitter::before,
|
||||
.splitpanes.default-theme .splitpanes__splitter::after {
|
||||
background-color: var(--el-color-info-light-5);
|
||||
}
|
||||
|
||||
.splitpanes.default-theme .splitpanes__splitter:hover::before,
|
||||
.splitpanes.default-theme .splitpanes__splitter:hover::after {
|
||||
background-color: var(--el-color-success);
|
||||
}
|
||||
|
||||
.splitpanes.default-theme .splitpanes__splitter {
|
||||
min-width: 6px;
|
||||
background: var(--el-color-info-light-8) !important;
|
||||
}
|
||||
|
||||
.splitpanes.default-theme .splitpanes__splitter:hover {
|
||||
background: var(--el-color-success-light-8) !important;
|
||||
}
|
||||
1
mayfly_go_web/src/types/shim.d.ts
vendored
1
mayfly_go_web/src/types/shim.d.ts
vendored
@@ -3,3 +3,4 @@ declare module 'sql-formatter';
|
||||
declare module 'jsoneditor';
|
||||
declare module 'asciinema-player';
|
||||
declare module 'vue-grid-layout';
|
||||
declare module 'splitpanes';
|
||||
|
||||
@@ -98,7 +98,7 @@ onMounted(async () => {
|
||||
});
|
||||
|
||||
const setHeight = () => {
|
||||
state.height = vh.value - 148 + 'px';
|
||||
state.height = vh.value - 150 + 'px';
|
||||
};
|
||||
|
||||
onUnmounted(() => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="db-sql-exec">
|
||||
<el-row>
|
||||
<el-col :span="5">
|
||||
<Splitpanes class="default-theme">
|
||||
<Pane size="20" max-size="30">
|
||||
<tag-tree :resource-type="TagResourceTypeEnum.Db.value" :tag-path-node-type="NodeTypeTagPath" ref="tagTreeRef">
|
||||
<template #prefix="{ data }">
|
||||
<span v-if="data.type.value == SqlExecNodeType.DbInst">
|
||||
@@ -44,9 +44,9 @@
|
||||
}}</span>
|
||||
</template>
|
||||
</tag-tree>
|
||||
</el-col>
|
||||
</Pane>
|
||||
|
||||
<el-col :span="19">
|
||||
<Pane>
|
||||
<el-row>
|
||||
<el-col :span="24" v-if="state.db">
|
||||
<el-descriptions :column="4" size="small" border class="ml5">
|
||||
@@ -90,8 +90,9 @@
|
||||
@tab-change="onTabChange"
|
||||
style="width: 100%"
|
||||
v-model="state.activeName"
|
||||
class="h100"
|
||||
>
|
||||
<el-tab-pane closable v-for="dt in state.tabs.values()" :label="dt.label" :name="dt.key" :key="dt.key">
|
||||
<el-tab-pane class="h100" closable v-for="dt in state.tabs.values()" :label="dt.label" :name="dt.key" :key="dt.key">
|
||||
<template #label>
|
||||
<el-popover :show-after="1000" placement="bottom-start" trigger="hover" :width="250">
|
||||
<template #reference> {{ dt.label }} </template>
|
||||
@@ -129,7 +130,6 @@
|
||||
:db-name="dt.db"
|
||||
:sql-name="dt.params.sqlName"
|
||||
@save-sql-success="reloadSqls"
|
||||
:editor-height="state.editorHeight"
|
||||
>
|
||||
</db-sql-editor>
|
||||
|
||||
@@ -143,8 +143,8 @@
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -162,6 +162,7 @@ import { ContextmenuItem } from '@/components/contextmenu';
|
||||
import { getDbDialect } from './dialect/index';
|
||||
import { sleep } from '@/common/utils/loading';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { Splitpanes, Pane } from 'splitpanes';
|
||||
|
||||
const DbSqlEditor = defineAsyncComponent(() => import('./component/sqleditor/DbSqlEditor.vue'));
|
||||
const DbTableDataOp = defineAsyncComponent(() => import('./component/table/DbTableDataOp.vue'));
|
||||
@@ -377,8 +378,7 @@ const state = reactive({
|
||||
activeName: '',
|
||||
reloadStatus: false,
|
||||
tabs,
|
||||
dataTabsTableHeight: 600,
|
||||
editorHeight: '600',
|
||||
dataTabsTableHeight: '600px',
|
||||
tablesOpHeight: '600',
|
||||
});
|
||||
|
||||
@@ -398,8 +398,7 @@ onBeforeUnmount(() => {
|
||||
* 设置editor高度和数据表高度
|
||||
*/
|
||||
const setHeight = () => {
|
||||
state.editorHeight = window.innerHeight - 525 + 'px';
|
||||
state.dataTabsTableHeight = window.innerHeight - 255;
|
||||
state.dataTabsTableHeight = window.innerHeight - 255 + 'px';
|
||||
state.tablesOpHeight = window.innerHeight - 220 + 'px';
|
||||
};
|
||||
|
||||
|
||||
@@ -39,86 +39,96 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MonacoEditor ref="monacoEditorRef" class="mt5" v-model="state.sql" language="sql" :height="state.editorHeight" :id="'MonacoTextarea-' + getKey()" />
|
||||
<Splitpanes
|
||||
@pane-maximize="resizeTableHeight([{ size: 0 }])"
|
||||
@resize="resizeTableHeight"
|
||||
horizontal
|
||||
class="default-theme"
|
||||
style="height: calc(100vh - 225px)"
|
||||
>
|
||||
<Pane :size="state.editorSize" max-size="80">
|
||||
<MonacoEditor ref="monacoEditorRef" class="mt5" v-model="state.sql" language="sql" height="100%" :id="'MonacoTextarea-' + getKey()" />
|
||||
</Pane>
|
||||
|
||||
<div class="editor-move-resize" @mousedown="onDragSetHeight">
|
||||
<el-icon>
|
||||
<Minus />
|
||||
</el-icon>
|
||||
</div>
|
||||
<Pane :size="100 - state.editorSize">
|
||||
<div class="mt5 sql-exec-res h100">
|
||||
<el-tabs class="h100 w100" v-if="state.execResTabs.length > 0" @tab-remove="onRemoveTab" v-model="state.activeTab">
|
||||
<el-tab-pane class="h100" closable v-for="dt in state.execResTabs" :label="dt.id" :name="dt.id" :key="dt.id">
|
||||
<template #label>
|
||||
<el-popover :show-after="1000" placement="top-start" title="执行信息" trigger="hover" :width="300">
|
||||
<template #reference>
|
||||
<div>
|
||||
<span>
|
||||
<span v-if="dt.loading">
|
||||
<SvgIcon class="mb2 is-loading" name="Loading" color="var(--el-color-primary)" />
|
||||
</span>
|
||||
<span v-else>
|
||||
<SvgIcon class="mb2" v-if="!dt.errorMsg" name="CircleCheck" color="var(--el-color-success)" />
|
||||
<SvgIcon class="mb2" v-if="dt.errorMsg" name="CircleClose" color="var(--el-color-error)" />
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<div class="mt5 sql-exec-res">
|
||||
<el-tabs v-if="state.execResTabs.length > 0" @tab-remove="onRemoveTab" style="width: 100%" v-model="state.activeTab">
|
||||
<el-tab-pane closable v-for="dt in state.execResTabs" :label="dt.id" :name="dt.id" :key="dt.id">
|
||||
<template #label>
|
||||
<el-popover :show-after="1000" placement="top-start" title="执行信息" trigger="hover" :width="300">
|
||||
<template #reference>
|
||||
<div>
|
||||
<span>
|
||||
<span v-if="dt.loading">
|
||||
<SvgIcon class="mb2 is-loading" name="Loading" color="var(--el-color-primary)" />
|
||||
</span>
|
||||
<span v-else>
|
||||
<SvgIcon class="mb2" v-if="!dt.errorMsg" name="CircleCheck" color="var(--el-color-success)" />
|
||||
<SvgIcon class="mb2" v-if="dt.errorMsg" name="CircleClose" color="var(--el-color-error)" />
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span> 结果{{ dt.id }} </span>
|
||||
</div>
|
||||
</template>
|
||||
<template #default>
|
||||
<el-descriptions v-if="dt.sql" :column="1" size="small">
|
||||
<el-descriptions-item>
|
||||
<div style="width: 280px">
|
||||
<el-text size="small" truncated :title="dt.sql"> {{ dt.sql }} </el-text>
|
||||
<span> 结果{{ dt.id }} </span>
|
||||
</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="耗时 :"> {{ dt.execTime }}ms </el-descriptions-item>
|
||||
<el-descriptions-item label="结果集 :">
|
||||
{{ dt.data?.length }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</template>
|
||||
<template #default>
|
||||
<el-descriptions v-if="dt.sql" :column="1" size="small">
|
||||
<el-descriptions-item>
|
||||
<div style="width: 280px">
|
||||
<el-text size="small" truncated :title="dt.sql"> {{ dt.sql }} </el-text>
|
||||
</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="耗时 :"> {{ dt.execTime }}ms </el-descriptions-item>
|
||||
<el-descriptions-item label="结果集 :">
|
||||
{{ dt.data?.length }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</template>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-popover>
|
||||
</template>
|
||||
|
||||
<el-row>
|
||||
<span v-if="dt.hasUpdatedFileds" class="mt5">
|
||||
<span>
|
||||
<el-link type="success" :underline="false" @click="submitUpdateFields(dt)"><span style="font-size: 12px">提交</span></el-link>
|
||||
</span>
|
||||
<span>
|
||||
<el-divider direction="vertical" border-style="dashed" />
|
||||
<el-link type="warning" :underline="false" @click="cancelUpdateFields(dt)"><span style="font-size: 12px">取消</span></el-link>
|
||||
</span>
|
||||
</span>
|
||||
</el-row>
|
||||
<db-table-data
|
||||
v-if="!dt.errorMsg"
|
||||
:ref="(el) => (dt.dbTableRef = el)"
|
||||
:db-id="dbId"
|
||||
:db="dbName"
|
||||
:data="dt.data"
|
||||
:table="dt.table"
|
||||
:columns="dt.tableColumn"
|
||||
:loading="dt.loading"
|
||||
:loading-key="dt.loadingKey"
|
||||
:height="tableDataHeight"
|
||||
empty-text="tips: select *开头的单表查询或点击表名默认查询的数据,可双击数据在线修改"
|
||||
@change-updated-field="changeUpdatedField($event, dt)"
|
||||
@data-delete="onDeleteData($event, dt)"
|
||||
></db-table-data>
|
||||
<el-row>
|
||||
<span v-if="dt.hasUpdatedFileds" class="mt5">
|
||||
<span>
|
||||
<el-link type="success" :underline="false" @click="submitUpdateFields(dt)"
|
||||
><span style="font-size: 12px">提交</span></el-link
|
||||
>
|
||||
</span>
|
||||
<span>
|
||||
<el-divider direction="vertical" border-style="dashed" />
|
||||
<el-link type="warning" :underline="false" @click="cancelUpdateFields(dt)"
|
||||
><span style="font-size: 12px">取消</span></el-link
|
||||
>
|
||||
</span>
|
||||
</span>
|
||||
</el-row>
|
||||
<db-table-data
|
||||
v-if="!dt.errorMsg"
|
||||
:ref="(el) => (dt.dbTableRef = el)"
|
||||
:db-id="dbId"
|
||||
:db="dbName"
|
||||
:data="dt.data"
|
||||
:table="dt.table"
|
||||
:columns="dt.tableColumn"
|
||||
:loading="dt.loading"
|
||||
:loading-key="dt.loadingKey"
|
||||
:height="tableDataHeight"
|
||||
empty-text="tips: select *开头的单表查询或点击表名默认查询的数据,可双击数据在线修改"
|
||||
@change-updated-field="changeUpdatedField($event, dt)"
|
||||
@data-delete="onDeleteData($event, dt)"
|
||||
></db-table-data>
|
||||
|
||||
<el-result v-else icon="error" title="执行失败" :sub-title="dt.errorMsg"> </el-result>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
<el-result v-else icon="error" title="执行失败" :sub-title="dt.errorMsg"> </el-result>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, nextTick, watch, onMounted, reactive, toRefs, ref } from 'vue';
|
||||
import { h, nextTick, onMounted, reactive, toRefs, ref } from 'vue';
|
||||
import { getToken } from '@/common/utils/storage';
|
||||
import { notBlank } from '@/common/assert';
|
||||
import { format as sqlFormatter } from 'sql-formatter';
|
||||
@@ -141,6 +151,7 @@ import syssocket from '@/common/syssocket';
|
||||
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||
import { getDbDialect } from '../../dialect';
|
||||
import { randomUuid } from '@/common/utils/string';
|
||||
import { Splitpanes, Pane } from 'splitpanes';
|
||||
|
||||
const emits = defineEmits(['saveSqlSuccess']);
|
||||
|
||||
@@ -157,10 +168,6 @@ const props = defineProps({
|
||||
sqlName: {
|
||||
type: String,
|
||||
},
|
||||
editorHeight: {
|
||||
type: String,
|
||||
default: '600',
|
||||
},
|
||||
});
|
||||
|
||||
class ExecResTab {
|
||||
@@ -206,31 +213,30 @@ const monacoEditorRef: any = ref(null);
|
||||
let monacoEditor: editor.IStandaloneCodeEditor;
|
||||
|
||||
const state = reactive({
|
||||
editorSize: 50, // editor高度比例
|
||||
token,
|
||||
sql: '', // 当前编辑器的sql内容s
|
||||
sqlName: '' as any, // sql模板名称
|
||||
execResTabs: [] as ExecResTab[],
|
||||
activeTab: 1,
|
||||
editorHeight: '500',
|
||||
tableDataHeight: 255 as any,
|
||||
tableDataHeight: '250px',
|
||||
});
|
||||
|
||||
const { tableDataHeight } = toRefs(state);
|
||||
|
||||
watch(
|
||||
() => props.editorHeight,
|
||||
(newValue: any) => {
|
||||
state.editorHeight = newValue;
|
||||
}
|
||||
);
|
||||
|
||||
const getNowDbInst = () => {
|
||||
return DbInst.getInst(props.dbId);
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
console.log('in query mounted');
|
||||
state.editorHeight = props.editorHeight;
|
||||
|
||||
// 第一个pane为sql editor
|
||||
resizeTableHeight([{ size: state.editorSize }]);
|
||||
window.onresize = () => {
|
||||
resizeTableHeight([{ size: state.editorSize }]);
|
||||
};
|
||||
|
||||
// 默认新建一个结果集tab
|
||||
state.execResTabs.push(new ExecResTab(1));
|
||||
@@ -265,19 +271,11 @@ const onRemoveTab = (targetId: number) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 拖拽改变sql编辑区和查询结果区高度
|
||||
*/
|
||||
const onDragSetHeight = () => {
|
||||
document.onmousemove = (e) => {
|
||||
e.preventDefault();
|
||||
//得到鼠标拖动的宽高距离:取绝对值
|
||||
state.editorHeight = `${document.getElementById('MonacoTextarea-' + getKey())!.clientHeight + e.movementY}px`;
|
||||
state.tableDataHeight -= e.movementY;
|
||||
};
|
||||
document.onmouseup = () => {
|
||||
document.onmousemove = null;
|
||||
};
|
||||
const resizeTableHeight = (e: any) => {
|
||||
const vh = window.innerHeight;
|
||||
state.editorSize = e[0].size;
|
||||
const editorHeight = (vh - 225) * (state.editorSize / 100);
|
||||
state.tableDataHeight = vh - 225 - 40 - editorHeight + 'px';
|
||||
};
|
||||
|
||||
const getKey = () => {
|
||||
@@ -335,6 +333,10 @@ const onRunSql = async (newTab = false) => {
|
||||
// 不是新建tab执行,则在当前激活的tab上执行sql
|
||||
i = state.execResTabs.findIndex((x) => x.id == state.activeTab);
|
||||
execRes = state.execResTabs[i];
|
||||
if (execRes.loading) {
|
||||
ElMessage.error('当前结果集tab正在执行, 请使用新标签执行');
|
||||
return;
|
||||
}
|
||||
id = execRes.id;
|
||||
}
|
||||
|
||||
@@ -445,7 +447,7 @@ const formatSql = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const formatDialect = getDbDialect(getNowDbInst().type).getFormatDialect();
|
||||
const formatDialect = getDbDialect(getNowDbInst().type).getInfo().formatSqlDialect;
|
||||
|
||||
let sql = monacoEditor.getModel()?.getValueInRange(selection);
|
||||
// 有选中sql则格式化并替换选中sql, 否则格式化编辑器所有内容
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="db-table-data mt5" :style="{ height: `${tableHeight}px` }">
|
||||
<div class="db-table-data mt5" :style="{ height: tableHeight }">
|
||||
<el-auto-resizer>
|
||||
<template #default="{ height, width }">
|
||||
<el-table-v2
|
||||
@@ -93,7 +93,8 @@
|
||||
<template v-if="state.loading" #overlay>
|
||||
<div class="el-loading-mask" style="display: flex; flex-direction: column; align-items: center; justify-content: center">
|
||||
<div>
|
||||
<SvgIcon class="is-loading" name="loading" color="var(--el-color-primary)" :size="42" />
|
||||
<SvgIcon class="is-loading" name="loading" color="var(--el-color-primary)" :size="28" />
|
||||
<el-text class="ml5" tag="b">执行时间 - {{ state.execTime.toFixed(1) }}s</el-text>
|
||||
</div>
|
||||
<div v-if="loadingKey" class="mt10">
|
||||
<el-button @click="cancelLoading" type="info" size="small" plain>取 消</el-button>
|
||||
@@ -103,7 +104,7 @@
|
||||
|
||||
<template #empty>
|
||||
<div style="text-align: center">
|
||||
<el-empty :style="{ height: `${tableHeight}px` }" :description="state.emptyText" :image-size="100" />
|
||||
<el-empty class="h100" :description="state.emptyText" :image-size="100" />
|
||||
</div>
|
||||
</template>
|
||||
</el-table-v2>
|
||||
@@ -124,7 +125,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, watch, reactive, toRefs } from 'vue';
|
||||
import { ref, onMounted, watch, reactive, toRefs, onBeforeUnmount } from 'vue';
|
||||
import { ElInput } from 'element-plus';
|
||||
import { copyToClipboard } from '@/common/utils/string';
|
||||
import { DbInst } from '@/views/ops/db/db';
|
||||
@@ -137,9 +138,6 @@ import { dbApi } from '../../api';
|
||||
const emits = defineEmits(['dataDelete', 'sortChange', 'deleteData', 'selectionChange', 'changeUpdatedField']);
|
||||
|
||||
const props = defineProps({
|
||||
loadingKey: {
|
||||
type: String,
|
||||
},
|
||||
dbId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
@@ -166,6 +164,9 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
loadingKey: {
|
||||
type: String,
|
||||
},
|
||||
emptyText: {
|
||||
type: String,
|
||||
default: '暂无数据',
|
||||
@@ -175,8 +176,8 @@ const props = defineProps({
|
||||
default: false,
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 600,
|
||||
type: String,
|
||||
default: '600px',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -283,6 +284,9 @@ const selectionRowsMap: Map<number, any> = new Map();
|
||||
// 更新单元格 key-> rowIndex value -> 更新行
|
||||
const cellUpdateMap: Map<number, UpdatedRow> = new Map();
|
||||
|
||||
// 数据加载时间计时器
|
||||
let execTimeInterval: any = null;
|
||||
|
||||
const state = reactive({
|
||||
dbId: 0, // 当前选中操作的数据库实例
|
||||
dbType: '',
|
||||
@@ -291,9 +295,10 @@ const state = reactive({
|
||||
datas: [],
|
||||
columns: [] as any,
|
||||
loading: false,
|
||||
tableHeight: 600,
|
||||
tableHeight: '600px',
|
||||
emptyText: '',
|
||||
|
||||
execTime: 0,
|
||||
contextmenu: {
|
||||
dropdown: {
|
||||
x: 0,
|
||||
@@ -365,6 +370,11 @@ watch(
|
||||
() => props.loading,
|
||||
(newValue: any) => {
|
||||
state.loading = newValue;
|
||||
if (newValue) {
|
||||
startLoading();
|
||||
} else {
|
||||
endLoading();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -379,13 +389,15 @@ onMounted(async () => {
|
||||
state.db = props.db;
|
||||
state.table = props.table;
|
||||
setTableData(props.data);
|
||||
|
||||
if (state.loading) {
|
||||
startLoading();
|
||||
}
|
||||
});
|
||||
|
||||
const cancelLoading = async () => {
|
||||
if (props.loadingKey) {
|
||||
await dbApi.sqlExecCancel.request({ id: state.dbId, execId: props.loadingKey });
|
||||
}
|
||||
};
|
||||
onBeforeUnmount(() => {
|
||||
endLoading();
|
||||
});
|
||||
|
||||
const setTableData = (datas: any) => {
|
||||
tableRef.value.scrollTo({ scrollLeft: 0, scrollTop: 0 });
|
||||
@@ -416,6 +428,27 @@ const setTableColumns = (columns: any) => {
|
||||
}
|
||||
};
|
||||
|
||||
const startLoading = () => {
|
||||
if (execTimeInterval) {
|
||||
endLoading();
|
||||
}
|
||||
execTimeInterval = setInterval(() => {
|
||||
state.execTime += 0.1; // 每秒递增执行时间
|
||||
}, 100);
|
||||
};
|
||||
|
||||
const endLoading = () => {
|
||||
state.execTime = 0;
|
||||
clearInterval(execTimeInterval);
|
||||
};
|
||||
|
||||
const cancelLoading = async () => {
|
||||
if (props.loadingKey) {
|
||||
await dbApi.sqlExecCancel.request({ id: state.dbId, execId: props.loadingKey });
|
||||
endLoading();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 当前单元格是否允许编辑
|
||||
* @param rowIndex ri
|
||||
|
||||
@@ -218,8 +218,8 @@ const props = defineProps({
|
||||
required: true,
|
||||
},
|
||||
tableHeight: {
|
||||
type: [Number],
|
||||
default: 600,
|
||||
type: [String],
|
||||
default: '600px',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -268,7 +268,7 @@ const state = reactive({
|
||||
placeholder: '',
|
||||
visible: false,
|
||||
},
|
||||
tableHeight: 600,
|
||||
tableHeight: '600px',
|
||||
hasUpdatedFileds: false,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,124 +1,128 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-row>
|
||||
<el-col :span="5">
|
||||
<tag-tree :resource-type="TagResourceTypeEnum.Mongo.value" :tag-path-node-type="NodeTypeTagPath">
|
||||
<template #prefix="{ data }">
|
||||
<span v-if="data.type.value == MongoNodeType.Mongo">
|
||||
<el-popover :show-after="500" placement="right-start" title="mongo实例信息" trigger="hover" :width="250">
|
||||
<template #reference>
|
||||
<SvgIcon name="iconfont icon-op-mongo" :size="18" />
|
||||
</template>
|
||||
<template #default>
|
||||
<el-descriptions :column="1" size="small">
|
||||
<el-descriptions-item label="名称">
|
||||
{{ data.params.name }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="链接">
|
||||
{{ data.params.uri }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</template>
|
||||
</el-popover>
|
||||
</span>
|
||||
<Splitpanes class="default-theme">
|
||||
<Pane size="20" max-size="30">
|
||||
<tag-tree :resource-type="TagResourceTypeEnum.Mongo.value" :tag-path-node-type="NodeTypeTagPath">
|
||||
<template #prefix="{ data }">
|
||||
<span v-if="data.type.value == MongoNodeType.Mongo">
|
||||
<el-popover :show-after="500" placement="right-start" title="mongo实例信息" trigger="hover" :width="250">
|
||||
<template #reference>
|
||||
<SvgIcon name="iconfont icon-op-mongo" :size="18" />
|
||||
</template>
|
||||
<template #default>
|
||||
<el-descriptions :column="1" size="small">
|
||||
<el-descriptions-item label="名称">
|
||||
{{ data.params.name }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="链接">
|
||||
{{ data.params.uri }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</template>
|
||||
</el-popover>
|
||||
</span>
|
||||
|
||||
<SvgIcon v-if="data.type.value == MongoNodeType.Dbs" name="Coin" color="#67c23a" />
|
||||
<SvgIcon v-if="data.type.value == MongoNodeType.Dbs" name="Coin" color="#67c23a" />
|
||||
|
||||
<SvgIcon
|
||||
v-if="data.type.value == MongoNodeType.Coll || data.type.value == MongoNodeType.CollMenu"
|
||||
name="Document"
|
||||
class="color-primary"
|
||||
/>
|
||||
</template>
|
||||
<SvgIcon
|
||||
v-if="data.type.value == MongoNodeType.Coll || data.type.value == MongoNodeType.CollMenu"
|
||||
name="Document"
|
||||
class="color-primary"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #label="{ data }">
|
||||
<span v-if="data.type.value == MongoNodeType.Dbs">
|
||||
{{ data.params.database }}
|
||||
<span style="color: #8492a6; font-size: 13px"> [{{ formatByteSize(data.params.size) }}] </span>
|
||||
</span>
|
||||
<template #label="{ data }">
|
||||
<span v-if="data.type.value == MongoNodeType.Dbs">
|
||||
{{ data.params.database }}
|
||||
<span style="color: #8492a6; font-size: 13px"> [{{ formatByteSize(data.params.size) }}] </span>
|
||||
</span>
|
||||
|
||||
<span v-else>{{ data.label }}</span>
|
||||
</template>
|
||||
</tag-tree>
|
||||
</el-col>
|
||||
<span v-else>{{ data.label }}</span>
|
||||
</template>
|
||||
</tag-tree>
|
||||
</Pane>
|
||||
|
||||
<el-col :span="19">
|
||||
<div id="mongo-tab" class="ml5" style="border: 1px solid var(--el-border-color-light, #ebeef5); margin-top: 1px">
|
||||
<el-row v-if="nowColl">
|
||||
<el-descriptions :column="10" size="small" border>
|
||||
<!-- <el-descriptions-item label-align="right" label="tag">xxx</el-descriptions-item> -->
|
||||
<Pane>
|
||||
<div id="mongo-tab" class="ml5" style="border: 1px solid var(--el-border-color-light, #ebeef5); margin-top: 1px">
|
||||
<el-row v-if="nowColl">
|
||||
<el-descriptions :column="10" size="small" border>
|
||||
<!-- <el-descriptions-item label-align="right" label="tag">xxx</el-descriptions-item> -->
|
||||
|
||||
<el-descriptions-item label="ns" label-align="right">
|
||||
{{ nowColl.stats?.ns }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="count" label-align="right">
|
||||
{{ nowColl.stats?.count }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="avgObjSize" label-align="right">
|
||||
{{ formatByteSize(nowColl.stats?.avgObjSize) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="size" label-align="right">
|
||||
{{ formatByteSize(nowColl.stats?.size) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="totalSize" label-align="right">
|
||||
{{ formatByteSize(nowColl.stats?.totalSize) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="storageSize" label-align="right">
|
||||
{{ formatByteSize(nowColl.stats?.storageSize) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="freeStorageSize" label-align="right">
|
||||
{{ formatByteSize(nowColl.stats?.freeStorageSize) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-row>
|
||||
<el-descriptions-item label="ns" label-align="right">
|
||||
{{ nowColl.stats?.ns }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="count" label-align="right">
|
||||
{{ nowColl.stats?.count }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="avgObjSize" label-align="right">
|
||||
{{ formatByteSize(nowColl.stats?.avgObjSize) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="size" label-align="right">
|
||||
{{ formatByteSize(nowColl.stats?.size) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="totalSize" label-align="right">
|
||||
{{ formatByteSize(nowColl.stats?.totalSize) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="storageSize" label-align="right">
|
||||
{{ formatByteSize(nowColl.stats?.storageSize) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="freeStorageSize" label-align="right">
|
||||
{{ formatByteSize(nowColl.stats?.freeStorageSize) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-row>
|
||||
|
||||
<el-row type="flex">
|
||||
<el-tabs @tab-remove="removeDataTab" style="width: 100%; margin-left: 5px" v-model="state.activeName">
|
||||
<el-tab-pane closable v-for="dt in state.dataTabs" :key="dt.key" :label="dt.label" :name="dt.key">
|
||||
<el-row>
|
||||
<el-col :span="2">
|
||||
<div class="mt5">
|
||||
<el-link @click="findCommand(state.activeName)" icon="refresh" :underline="false" class=""> </el-link>
|
||||
<el-divider direction="vertical" border-style="dashed" />
|
||||
<el-link v-auth="perms.saveData" @click="onEditDoc(null)" type="primary" icon="plus" :underline="false"> </el-link>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="22">
|
||||
<el-input
|
||||
ref="findParamInputRef"
|
||||
v-model="dt.findParamStr"
|
||||
placeholder="点击输入相应查询条件"
|
||||
@focus="showFindDialog(dt.key)"
|
||||
>
|
||||
<template #prepend>查询参数</template>
|
||||
</el-input>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :style="`height: ${dataHeight}; overflow: auto;`">
|
||||
<el-col :span="6" v-for="item in dt.datas" :key="item">
|
||||
<el-card :body-style="{ padding: '0px', position: 'relative' }">
|
||||
<el-input type="textarea" v-model="item.value" :rows="10" />
|
||||
<div style="padding: 3px; float: right" class="mr5 mongo-doc-btns">
|
||||
<div>
|
||||
<el-link @click="onEditDoc(item)" :underline="false" type="success" icon="MagicStick"></el-link>
|
||||
|
||||
<el-divider direction="vertical" border-style="dashed" />
|
||||
|
||||
<el-popconfirm @confirm="onDeleteDoc(item.value)" title="确定删除该文档?" width="160">
|
||||
<template #reference>
|
||||
<el-link v-auth="perms.delData" :underline="false" type="danger" icon="DocumentDelete"> </el-link>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</div>
|
||||
<el-row type="flex">
|
||||
<el-tabs @tab-remove="removeDataTab" style="width: 100%; margin-left: 5px" v-model="state.activeName">
|
||||
<el-tab-pane closable v-for="dt in state.dataTabs" :key="dt.key" :label="dt.label" :name="dt.key">
|
||||
<el-row>
|
||||
<el-col :span="2">
|
||||
<div class="mt5">
|
||||
<el-link @click="findCommand(state.activeName)" icon="refresh" :underline="false" class=""> </el-link>
|
||||
<el-divider direction="vertical" border-style="dashed" />
|
||||
<el-link v-auth="perms.saveData" @click="onEditDoc(null)" type="primary" icon="plus" :underline="false">
|
||||
</el-link>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-col>
|
||||
<el-col :span="22">
|
||||
<el-input
|
||||
ref="findParamInputRef"
|
||||
v-model="dt.findParamStr"
|
||||
placeholder="点击输入相应查询条件"
|
||||
@focus="showFindDialog(dt.key)"
|
||||
>
|
||||
<template #prepend>查询参数</template>
|
||||
</el-input>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :style="`height: ${dataHeight}; overflow: auto;`">
|
||||
<el-col :span="6" v-for="item in dt.datas" :key="item">
|
||||
<el-card :body-style="{ padding: '0px', position: 'relative' }">
|
||||
<el-input type="textarea" v-model="item.value" :rows="10" />
|
||||
<div style="padding: 3px; float: right" class="mr5 mongo-doc-btns">
|
||||
<div>
|
||||
<el-link @click="onEditDoc(item)" :underline="false" type="success" icon="MagicStick"></el-link>
|
||||
|
||||
<el-divider direction="vertical" border-style="dashed" />
|
||||
|
||||
<el-popconfirm @confirm="onDeleteDoc(item.value)" title="确定删除该文档?" width="160">
|
||||
<template #reference>
|
||||
<el-link v-auth="perms.delData" :underline="false" type="danger" icon="DocumentDelete">
|
||||
</el-link>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-row>
|
||||
</div>
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
</el-row>
|
||||
|
||||
<el-dialog width="600px" title="find参数" v-model="findDialog.visible">
|
||||
@@ -174,6 +178,7 @@ import TagTree from '../component/TagTree.vue';
|
||||
import { formatByteSize } from '@/common/utils/format';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { sleep } from '@/common/utils/loading';
|
||||
import { Splitpanes, Pane } from 'splitpanes';
|
||||
|
||||
const MonacoEditor = defineAsyncComponent(() => import('@/components/monaco/MonacoEditor.vue'));
|
||||
|
||||
|
||||
@@ -1,43 +1,39 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-row>
|
||||
<el-col :span="5">
|
||||
<el-row type="flex" justify="space-between">
|
||||
<el-col :span="24" class="flex-auto">
|
||||
<tag-tree :resource-type="TagResourceTypeEnum.Redis.value" :tag-path-node-type="NodeTypeTagPath">
|
||||
<template #prefix="{ data }">
|
||||
<span v-if="data.type.value == RedisNodeType.Redis">
|
||||
<el-popover :show-after="500" placement="right-start" title="redis实例信息" trigger="hover" :width="250">
|
||||
<template #reference>
|
||||
<SvgIcon name="iconfont icon-op-redis" :size="18" />
|
||||
</template>
|
||||
<template #default>
|
||||
<el-descriptions :column="1" size="small">
|
||||
<el-descriptions-item label="名称">
|
||||
{{ data.params.name }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="模式">
|
||||
{{ data.params.mode }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="host">
|
||||
{{ data.params.host }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" label-align="right">
|
||||
{{ data.params.remark }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</template>
|
||||
</el-popover>
|
||||
</span>
|
||||
<Splitpanes class="default-theme">
|
||||
<Pane size="20" max-size="30">
|
||||
<tag-tree :resource-type="TagResourceTypeEnum.Redis.value" :tag-path-node-type="NodeTypeTagPath">
|
||||
<template #prefix="{ data }">
|
||||
<span v-if="data.type.value == RedisNodeType.Redis">
|
||||
<el-popover :show-after="500" placement="right-start" title="redis实例信息" trigger="hover" :width="250">
|
||||
<template #reference>
|
||||
<SvgIcon name="iconfont icon-op-redis" :size="18" />
|
||||
</template>
|
||||
<template #default>
|
||||
<el-descriptions :column="1" size="small">
|
||||
<el-descriptions-item label="名称">
|
||||
{{ data.params.name }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="模式">
|
||||
{{ data.params.mode }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="host">
|
||||
{{ data.params.host }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" label-align="right">
|
||||
{{ data.params.remark }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</template>
|
||||
</el-popover>
|
||||
</span>
|
||||
|
||||
<SvgIcon v-if="data.type.value == RedisNodeType.Db" name="Coin" color="#67c23a" />
|
||||
</template>
|
||||
</tag-tree>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-col>
|
||||
<SvgIcon v-if="data.type.value == RedisNodeType.Db" name="Coin" color="#67c23a" />
|
||||
</template>
|
||||
</tag-tree>
|
||||
</Pane>
|
||||
|
||||
<el-col v-loading="state.loadingKeyTree" :span="7">
|
||||
<Pane min-size="20" size="30">
|
||||
<div class="key-list-vtree">
|
||||
<el-row>
|
||||
<el-col :span="2">
|
||||
@@ -98,7 +94,6 @@
|
||||
height: state.keyTreeHeight,
|
||||
overflow: 'auto',
|
||||
border: '1px solid var(--el-border-color-light, #ebeef5)',
|
||||
marginLeft: '5px',
|
||||
}"
|
||||
ref="keyTreeRef"
|
||||
:highlight-current="true"
|
||||
@@ -127,21 +122,20 @@
|
||||
</template>
|
||||
</el-tree>
|
||||
|
||||
<!-- right context menu -->
|
||||
<contextmenu :dropdown="state.contextmenu.dropdown" :items="state.contextmenu.items" ref="contextmenuRef" />
|
||||
</div>
|
||||
</el-col>
|
||||
</Pane>
|
||||
|
||||
<el-col :span="12" style="border-left: 1px solid var(--el-card-border-color)">
|
||||
<div class="ml5">
|
||||
<el-tabs @tab-remove="removeDataTab" style="width: 100%" v-model="state.activeName">
|
||||
<Pane min-size="40">
|
||||
<div class="">
|
||||
<el-tabs @tab-remove="removeDataTab" v-model="state.activeName">
|
||||
<el-tab-pane closable v-for="dt in state.dataTabs" :key="dt.key" :label="dt.label" :name="dt.key">
|
||||
<key-detail :redisId="scanParam.id" :db="scanParam.db" :key-info="dt.keyInfo" @change-key="searchKey()" @del-key="delKey" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
|
||||
<div style="text-align: center; margin-top: 10px"></div>
|
||||
|
||||
@@ -183,6 +177,7 @@ import { keysToTree, sortByTreeNodes, keysToList } from './utils';
|
||||
import { Contextmenu, ContextmenuItem } from '@/components/contextmenu';
|
||||
import { sleep } from '../../../common/utils/loading';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { Splitpanes, Pane } from 'splitpanes';
|
||||
|
||||
const KeyDetail = defineAsyncComponent(() => import('./KeyDetail.vue'));
|
||||
|
||||
@@ -327,35 +322,6 @@ const setHeight = () => {
|
||||
state.keyTreeHeight = window.innerHeight - 174 + 'px';
|
||||
};
|
||||
|
||||
// /**
|
||||
// * instmap; tagPaht -> redis info[]
|
||||
// */
|
||||
// const instMap: Map<string, any[]> = new Map();
|
||||
|
||||
// const getInsts = async () => {
|
||||
// const res = await redisApi.redisList.request({ pageNum: 1, pageSize: 1000 });
|
||||
// if (!res.total) return;
|
||||
// for (const redisInfo of res.list) {
|
||||
// const tagPath = redisInfo.tagPath;
|
||||
// let redisInsts = instMap.get(tagPath) || [];
|
||||
// redisInsts.push(redisInfo);
|
||||
// instMap.set(tagPath, redisInsts);
|
||||
// }
|
||||
// };
|
||||
|
||||
// /**
|
||||
// * 加载标签树节点
|
||||
// */
|
||||
// const loadTags = async () => {
|
||||
// await getInsts();
|
||||
// const tagPaths = instMap.keys();
|
||||
// const tagNodes = [];
|
||||
// for (let tagPath of tagPaths) {
|
||||
// tagNodes.push(new TagTreeNode(tagPath, tagPath, NodeTypeTagPath));
|
||||
// }
|
||||
// return tagNodes;
|
||||
// };
|
||||
|
||||
const scan = async (appendKey = false) => {
|
||||
isTrue(state.scanParam.id != null, '请先选择redis');
|
||||
notBlank(state.scanParam.db, '请先选择库');
|
||||
@@ -612,7 +578,7 @@ const delKey = (key: string) => {
|
||||
|
||||
<style lang="scss">
|
||||
.key-list-vtree {
|
||||
height: calc(100vh - 250px);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.key-list-vtree .folder-label {
|
||||
|
||||
@@ -1765,6 +1765,11 @@ sortablejs@^1.15.0:
|
||||
resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz"
|
||||
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
|
||||
|
||||
splitpanes@^3.1.5:
|
||||
version "3.1.5"
|
||||
resolved "https://registry.npmmirror.com/splitpanes/-/splitpanes-3.1.5.tgz#de81da25681c252d131747a9cb48a17156e2b210"
|
||||
integrity sha512-r3Mq2ITFQ5a2VXLOy4/Sb2Ptp7OfEO8YIbhVJqJXoFc9hc5nTXXkCvtVDjIGbvC0vdE7tse+xTM9BMjsszP6bw==
|
||||
|
||||
sql-formatter@^14.0.0:
|
||||
version "14.0.0"
|
||||
resolved "https://registry.npmmirror.com/sql-formatter/-/sql-formatter-14.0.0.tgz#07a1714c49d7d280ff2f6f09c64eebfba82b7053"
|
||||
|
||||
Reference in New Issue
Block a user