feat: 常用操作界面支持Splitpane等

This commit is contained in:
meilin.huang
2023-12-07 23:57:11 +08:00
parent 172c729535
commit 59a7ff9ac7
13 changed files with 382 additions and 329 deletions

View File

@@ -29,6 +29,7 @@
"qrcode.vue": "^3.4.1", "qrcode.vue": "^3.4.1",
"screenfull": "^6.0.2", "screenfull": "^6.0.2",
"sortablejs": "^1.15.0", "sortablejs": "^1.15.0",
"splitpanes": "^3.1.5",
"sql-formatter": "^14.0.0", "sql-formatter": "^14.0.0",
"uuid": "^9.0.1", "uuid": "^9.0.1",
"vue": "^3.3.10", "vue": "^3.3.10",

View File

@@ -1,5 +1,5 @@
<template> <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> <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-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> <el-option v-for="mode in languageArr" :key="mode.value" :label="mode.label" :value="mode.value"> </el-option>

View File

@@ -13,6 +13,8 @@ import 'element-plus/theme-chalk/dark/css-vars.css';
import zhCn from 'element-plus/es/locale/lang/zh-cn'; import zhCn from 'element-plus/es/locale/lang/zh-cn';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import 'splitpanes/dist/splitpanes.css';
import '@/theme/index.scss'; import '@/theme/index.scss';
import '@/assets/font/font.css'; import '@/assets/font/font.css';
import '@/assets/iconfont/iconfont.js'; import '@/assets/iconfont/iconfont.js';

View File

@@ -373,6 +373,45 @@ body,
cursor: pointer; cursor: pointer;
transition: color 0.3s; transition: color 0.3s;
} }
.pointer-icon:hover { .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;
} }

View File

@@ -3,3 +3,4 @@ declare module 'sql-formatter';
declare module 'jsoneditor'; declare module 'jsoneditor';
declare module 'asciinema-player'; declare module 'asciinema-player';
declare module 'vue-grid-layout'; declare module 'vue-grid-layout';
declare module 'splitpanes';

View File

@@ -98,7 +98,7 @@ onMounted(async () => {
}); });
const setHeight = () => { const setHeight = () => {
state.height = vh.value - 148 + 'px'; state.height = vh.value - 150 + 'px';
}; };
onUnmounted(() => { onUnmounted(() => {

View File

@@ -1,7 +1,7 @@
<template> <template>
<div class="db-sql-exec"> <div class="db-sql-exec">
<el-row> <Splitpanes class="default-theme">
<el-col :span="5"> <Pane size="20" max-size="30">
<tag-tree :resource-type="TagResourceTypeEnum.Db.value" :tag-path-node-type="NodeTypeTagPath" ref="tagTreeRef"> <tag-tree :resource-type="TagResourceTypeEnum.Db.value" :tag-path-node-type="NodeTypeTagPath" ref="tagTreeRef">
<template #prefix="{ data }"> <template #prefix="{ data }">
<span v-if="data.type.value == SqlExecNodeType.DbInst"> <span v-if="data.type.value == SqlExecNodeType.DbInst">
@@ -44,9 +44,9 @@
}}</span> }}</span>
</template> </template>
</tag-tree> </tag-tree>
</el-col> </Pane>
<el-col :span="19"> <Pane>
<el-row> <el-row>
<el-col :span="24" v-if="state.db"> <el-col :span="24" v-if="state.db">
<el-descriptions :column="4" size="small" border class="ml5"> <el-descriptions :column="4" size="small" border class="ml5">
@@ -90,8 +90,9 @@
@tab-change="onTabChange" @tab-change="onTabChange"
style="width: 100%" style="width: 100%"
v-model="state.activeName" 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> <template #label>
<el-popover :show-after="1000" placement="bottom-start" trigger="hover" :width="250"> <el-popover :show-after="1000" placement="bottom-start" trigger="hover" :width="250">
<template #reference> {{ dt.label }} </template> <template #reference> {{ dt.label }} </template>
@@ -129,7 +130,6 @@
:db-name="dt.db" :db-name="dt.db"
:sql-name="dt.params.sqlName" :sql-name="dt.params.sqlName"
@save-sql-success="reloadSqls" @save-sql-success="reloadSqls"
:editor-height="state.editorHeight"
> >
</db-sql-editor> </db-sql-editor>
@@ -143,8 +143,8 @@
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</div> </div>
</el-col> </Pane>
</el-row> </Splitpanes>
</div> </div>
</template> </template>
@@ -162,6 +162,7 @@ import { ContextmenuItem } from '@/components/contextmenu';
import { getDbDialect } from './dialect/index'; import { getDbDialect } from './dialect/index';
import { sleep } from '@/common/utils/loading'; import { sleep } from '@/common/utils/loading';
import { TagResourceTypeEnum } from '@/common/commonEnum'; import { TagResourceTypeEnum } from '@/common/commonEnum';
import { Splitpanes, Pane } from 'splitpanes';
const DbSqlEditor = defineAsyncComponent(() => import('./component/sqleditor/DbSqlEditor.vue')); const DbSqlEditor = defineAsyncComponent(() => import('./component/sqleditor/DbSqlEditor.vue'));
const DbTableDataOp = defineAsyncComponent(() => import('./component/table/DbTableDataOp.vue')); const DbTableDataOp = defineAsyncComponent(() => import('./component/table/DbTableDataOp.vue'));
@@ -377,8 +378,7 @@ const state = reactive({
activeName: '', activeName: '',
reloadStatus: false, reloadStatus: false,
tabs, tabs,
dataTabsTableHeight: 600, dataTabsTableHeight: '600px',
editorHeight: '600',
tablesOpHeight: '600', tablesOpHeight: '600',
}); });
@@ -398,8 +398,7 @@ onBeforeUnmount(() => {
* 设置editor高度和数据表高度 * 设置editor高度和数据表高度
*/ */
const setHeight = () => { const setHeight = () => {
state.editorHeight = window.innerHeight - 525 + 'px'; state.dataTabsTableHeight = window.innerHeight - 255 + 'px';
state.dataTabsTableHeight = window.innerHeight - 255;
state.tablesOpHeight = window.innerHeight - 220 + 'px'; state.tablesOpHeight = window.innerHeight - 220 + 'px';
}; };

View File

@@ -39,17 +39,21 @@
</div> </div>
</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"> <Pane :size="100 - state.editorSize">
<el-icon> <div class="mt5 sql-exec-res h100">
<Minus /> <el-tabs class="h100 w100" v-if="state.execResTabs.length > 0" @tab-remove="onRemoveTab" v-model="state.activeTab">
</el-icon> <el-tab-pane class="h100" closable v-for="dt in state.execResTabs" :label="dt.id" :name="dt.id" :key="dt.id">
</div>
<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> <template #label>
<el-popover :show-after="1000" placement="top-start" title="执行信息" trigger="hover" :width="300"> <el-popover :show-after="1000" placement="top-start" title="执行信息" trigger="hover" :width="300">
<template #reference> <template #reference>
@@ -86,11 +90,15 @@
<el-row> <el-row>
<span v-if="dt.hasUpdatedFileds" class="mt5"> <span v-if="dt.hasUpdatedFileds" class="mt5">
<span> <span>
<el-link type="success" :underline="false" @click="submitUpdateFields(dt)"><span style="font-size: 12px">提交</span></el-link> <el-link type="success" :underline="false" @click="submitUpdateFields(dt)"
><span style="font-size: 12px">提交</span></el-link
>
</span> </span>
<span> <span>
<el-divider direction="vertical" border-style="dashed" /> <el-divider direction="vertical" border-style="dashed" />
<el-link type="warning" :underline="false" @click="cancelUpdateFields(dt)"><span style="font-size: 12px">取消</span></el-link> <el-link type="warning" :underline="false" @click="cancelUpdateFields(dt)"
><span style="font-size: 12px">取消</span></el-link
>
</span> </span>
</span> </span>
</el-row> </el-row>
@@ -114,11 +122,13 @@
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</div> </div>
</Pane>
</Splitpanes>
</div> </div>
</template> </template>
<script lang="ts" setup> <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 { getToken } from '@/common/utils/storage';
import { notBlank } from '@/common/assert'; import { notBlank } from '@/common/assert';
import { format as sqlFormatter } from 'sql-formatter'; import { format as sqlFormatter } from 'sql-formatter';
@@ -141,6 +151,7 @@ import syssocket from '@/common/syssocket';
import SvgIcon from '@/components/svgIcon/index.vue'; import SvgIcon from '@/components/svgIcon/index.vue';
import { getDbDialect } from '../../dialect'; import { getDbDialect } from '../../dialect';
import { randomUuid } from '@/common/utils/string'; import { randomUuid } from '@/common/utils/string';
import { Splitpanes, Pane } from 'splitpanes';
const emits = defineEmits(['saveSqlSuccess']); const emits = defineEmits(['saveSqlSuccess']);
@@ -157,10 +168,6 @@ const props = defineProps({
sqlName: { sqlName: {
type: String, type: String,
}, },
editorHeight: {
type: String,
default: '600',
},
}); });
class ExecResTab { class ExecResTab {
@@ -206,31 +213,30 @@ const monacoEditorRef: any = ref(null);
let monacoEditor: editor.IStandaloneCodeEditor; let monacoEditor: editor.IStandaloneCodeEditor;
const state = reactive({ const state = reactive({
editorSize: 50, // editor高度比例
token, token,
sql: '', // 当前编辑器的sql内容s sql: '', // 当前编辑器的sql内容s
sqlName: '' as any, // sql模板名称 sqlName: '' as any, // sql模板名称
execResTabs: [] as ExecResTab[], execResTabs: [] as ExecResTab[],
activeTab: 1, activeTab: 1,
editorHeight: '500', editorHeight: '500',
tableDataHeight: 255 as any, tableDataHeight: '250px',
}); });
const { tableDataHeight } = toRefs(state); const { tableDataHeight } = toRefs(state);
watch(
() => props.editorHeight,
(newValue: any) => {
state.editorHeight = newValue;
}
);
const getNowDbInst = () => { const getNowDbInst = () => {
return DbInst.getInst(props.dbId); return DbInst.getInst(props.dbId);
}; };
onMounted(async () => { onMounted(async () => {
console.log('in query mounted'); console.log('in query mounted');
state.editorHeight = props.editorHeight;
// 第一个pane为sql editor
resizeTableHeight([{ size: state.editorSize }]);
window.onresize = () => {
resizeTableHeight([{ size: state.editorSize }]);
};
// 默认新建一个结果集tab // 默认新建一个结果集tab
state.execResTabs.push(new ExecResTab(1)); state.execResTabs.push(new ExecResTab(1));
@@ -265,19 +271,11 @@ const onRemoveTab = (targetId: number) => {
} }
}; };
/** const resizeTableHeight = (e: any) => {
* 拖拽改变sql编辑区和查询结果区高度 const vh = window.innerHeight;
*/ state.editorSize = e[0].size;
const onDragSetHeight = () => { const editorHeight = (vh - 225) * (state.editorSize / 100);
document.onmousemove = (e) => { state.tableDataHeight = vh - 225 - 40 - editorHeight + 'px';
e.preventDefault();
//得到鼠标拖动的宽高距离:取绝对值
state.editorHeight = `${document.getElementById('MonacoTextarea-' + getKey())!.clientHeight + e.movementY}px`;
state.tableDataHeight -= e.movementY;
};
document.onmouseup = () => {
document.onmousemove = null;
};
}; };
const getKey = () => { const getKey = () => {
@@ -335,6 +333,10 @@ const onRunSql = async (newTab = false) => {
// 不是新建tab执行则在当前激活的tab上执行sql // 不是新建tab执行则在当前激活的tab上执行sql
i = state.execResTabs.findIndex((x) => x.id == state.activeTab); i = state.execResTabs.findIndex((x) => x.id == state.activeTab);
execRes = state.execResTabs[i]; execRes = state.execResTabs[i];
if (execRes.loading) {
ElMessage.error('当前结果集tab正在执行, 请使用新标签执行');
return;
}
id = execRes.id; id = execRes.id;
} }
@@ -445,7 +447,7 @@ const formatSql = () => {
return; return;
} }
const formatDialect = getDbDialect(getNowDbInst().type).getFormatDialect(); const formatDialect = getDbDialect(getNowDbInst().type).getInfo().formatSqlDialect;
let sql = monacoEditor.getModel()?.getValueInRange(selection); let sql = monacoEditor.getModel()?.getValueInRange(selection);
// 有选中sql则格式化并替换选中sql, 否则格式化编辑器所有内容 // 有选中sql则格式化并替换选中sql, 否则格式化编辑器所有内容

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="db-table-data mt5" :style="{ height: `${tableHeight}px` }"> <div class="db-table-data mt5" :style="{ height: tableHeight }">
<el-auto-resizer> <el-auto-resizer>
<template #default="{ height, width }"> <template #default="{ height, width }">
<el-table-v2 <el-table-v2
@@ -93,7 +93,8 @@
<template v-if="state.loading" #overlay> <template v-if="state.loading" #overlay>
<div class="el-loading-mask" style="display: flex; flex-direction: column; align-items: center; justify-content: center"> <div class="el-loading-mask" style="display: flex; flex-direction: column; align-items: center; justify-content: center">
<div> <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>
<div v-if="loadingKey" class="mt10"> <div v-if="loadingKey" class="mt10">
<el-button @click="cancelLoading" type="info" size="small" plain> </el-button> <el-button @click="cancelLoading" type="info" size="small" plain> </el-button>
@@ -103,7 +104,7 @@
<template #empty> <template #empty>
<div style="text-align: center"> <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> </div>
</template> </template>
</el-table-v2> </el-table-v2>
@@ -124,7 +125,7 @@
</template> </template>
<script lang="ts" setup> <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 { ElInput } from 'element-plus';
import { copyToClipboard } from '@/common/utils/string'; import { copyToClipboard } from '@/common/utils/string';
import { DbInst } from '@/views/ops/db/db'; import { DbInst } from '@/views/ops/db/db';
@@ -137,9 +138,6 @@ import { dbApi } from '../../api';
const emits = defineEmits(['dataDelete', 'sortChange', 'deleteData', 'selectionChange', 'changeUpdatedField']); const emits = defineEmits(['dataDelete', 'sortChange', 'deleteData', 'selectionChange', 'changeUpdatedField']);
const props = defineProps({ const props = defineProps({
loadingKey: {
type: String,
},
dbId: { dbId: {
type: Number, type: Number,
required: true, required: true,
@@ -166,6 +164,9 @@ const props = defineProps({
type: Boolean, type: Boolean,
default: false, default: false,
}, },
loadingKey: {
type: String,
},
emptyText: { emptyText: {
type: String, type: String,
default: '暂无数据', default: '暂无数据',
@@ -175,8 +176,8 @@ const props = defineProps({
default: false, default: false,
}, },
height: { height: {
type: Number, type: String,
default: 600, default: '600px',
}, },
}); });
@@ -283,6 +284,9 @@ const selectionRowsMap: Map<number, any> = new Map();
// 更新单元格 key-> rowIndex value -> 更新行 // 更新单元格 key-> rowIndex value -> 更新行
const cellUpdateMap: Map<number, UpdatedRow> = new Map(); const cellUpdateMap: Map<number, UpdatedRow> = new Map();
// 数据加载时间计时器
let execTimeInterval: any = null;
const state = reactive({ const state = reactive({
dbId: 0, // 当前选中操作的数据库实例 dbId: 0, // 当前选中操作的数据库实例
dbType: '', dbType: '',
@@ -291,9 +295,10 @@ const state = reactive({
datas: [], datas: [],
columns: [] as any, columns: [] as any,
loading: false, loading: false,
tableHeight: 600, tableHeight: '600px',
emptyText: '', emptyText: '',
execTime: 0,
contextmenu: { contextmenu: {
dropdown: { dropdown: {
x: 0, x: 0,
@@ -365,6 +370,11 @@ watch(
() => props.loading, () => props.loading,
(newValue: any) => { (newValue: any) => {
state.loading = newValue; state.loading = newValue;
if (newValue) {
startLoading();
} else {
endLoading();
}
} }
); );
@@ -379,13 +389,15 @@ onMounted(async () => {
state.db = props.db; state.db = props.db;
state.table = props.table; state.table = props.table;
setTableData(props.data); setTableData(props.data);
if (state.loading) {
startLoading();
}
}); });
const cancelLoading = async () => { onBeforeUnmount(() => {
if (props.loadingKey) { endLoading();
await dbApi.sqlExecCancel.request({ id: state.dbId, execId: props.loadingKey }); });
}
};
const setTableData = (datas: any) => { const setTableData = (datas: any) => {
tableRef.value.scrollTo({ scrollLeft: 0, scrollTop: 0 }); 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 * @param rowIndex ri

View File

@@ -218,8 +218,8 @@ const props = defineProps({
required: true, required: true,
}, },
tableHeight: { tableHeight: {
type: [Number], type: [String],
default: 600, default: '600px',
}, },
}); });
@@ -268,7 +268,7 @@ const state = reactive({
placeholder: '', placeholder: '',
visible: false, visible: false,
}, },
tableHeight: 600, tableHeight: '600px',
hasUpdatedFileds: false, hasUpdatedFileds: false,
}); });

View File

@@ -1,7 +1,8 @@
<template> <template>
<div> <div>
<el-row> <el-row>
<el-col :span="5"> <Splitpanes class="default-theme">
<Pane size="20" max-size="30">
<tag-tree :resource-type="TagResourceTypeEnum.Mongo.value" :tag-path-node-type="NodeTypeTagPath"> <tag-tree :resource-type="TagResourceTypeEnum.Mongo.value" :tag-path-node-type="NodeTypeTagPath">
<template #prefix="{ data }"> <template #prefix="{ data }">
<span v-if="data.type.value == MongoNodeType.Mongo"> <span v-if="data.type.value == MongoNodeType.Mongo">
@@ -40,9 +41,9 @@
<span v-else>{{ data.label }}</span> <span v-else>{{ data.label }}</span>
</template> </template>
</tag-tree> </tag-tree>
</el-col> </Pane>
<el-col :span="19"> <Pane>
<div id="mongo-tab" class="ml5" style="border: 1px solid var(--el-border-color-light, #ebeef5); margin-top: 1px"> <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-row v-if="nowColl">
<el-descriptions :column="10" size="small" border> <el-descriptions :column="10" size="small" border>
@@ -80,7 +81,8 @@
<div class="mt5"> <div class="mt5">
<el-link @click="findCommand(state.activeName)" icon="refresh" :underline="false" class=""> </el-link> <el-link @click="findCommand(state.activeName)" icon="refresh" :underline="false" class=""> </el-link>
<el-divider direction="vertical" border-style="dashed" /> <el-divider direction="vertical" border-style="dashed" />
<el-link v-auth="perms.saveData" @click="onEditDoc(null)" type="primary" icon="plus" :underline="false"> </el-link> <el-link v-auth="perms.saveData" @click="onEditDoc(null)" type="primary" icon="plus" :underline="false">
</el-link>
</div> </div>
</el-col> </el-col>
<el-col :span="22"> <el-col :span="22">
@@ -106,7 +108,8 @@
<el-popconfirm @confirm="onDeleteDoc(item.value)" title="确定删除该文档?" width="160"> <el-popconfirm @confirm="onDeleteDoc(item.value)" title="确定删除该文档?" width="160">
<template #reference> <template #reference>
<el-link v-auth="perms.delData" :underline="false" type="danger" icon="DocumentDelete"> </el-link> <el-link v-auth="perms.delData" :underline="false" type="danger" icon="DocumentDelete">
</el-link>
</template> </template>
</el-popconfirm> </el-popconfirm>
</div> </div>
@@ -118,7 +121,8 @@
</el-tabs> </el-tabs>
</el-row> </el-row>
</div> </div>
</el-col> </Pane>
</Splitpanes>
</el-row> </el-row>
<el-dialog width="600px" title="find参数" v-model="findDialog.visible"> <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 { formatByteSize } from '@/common/utils/format';
import { TagResourceTypeEnum } from '@/common/commonEnum'; import { TagResourceTypeEnum } from '@/common/commonEnum';
import { sleep } from '@/common/utils/loading'; import { sleep } from '@/common/utils/loading';
import { Splitpanes, Pane } from 'splitpanes';
const MonacoEditor = defineAsyncComponent(() => import('@/components/monaco/MonacoEditor.vue')); const MonacoEditor = defineAsyncComponent(() => import('@/components/monaco/MonacoEditor.vue'));

View File

@@ -1,9 +1,7 @@
<template> <template>
<div> <div>
<el-row> <Splitpanes class="default-theme">
<el-col :span="5"> <Pane size="20" max-size="30">
<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"> <tag-tree :resource-type="TagResourceTypeEnum.Redis.value" :tag-path-node-type="NodeTypeTagPath">
<template #prefix="{ data }"> <template #prefix="{ data }">
<span v-if="data.type.value == RedisNodeType.Redis"> <span v-if="data.type.value == RedisNodeType.Redis">
@@ -33,11 +31,9 @@
<SvgIcon v-if="data.type.value == RedisNodeType.Db" name="Coin" color="#67c23a" /> <SvgIcon v-if="data.type.value == RedisNodeType.Db" name="Coin" color="#67c23a" />
</template> </template>
</tag-tree> </tag-tree>
</el-col> </Pane>
</el-row>
</el-col>
<el-col v-loading="state.loadingKeyTree" :span="7"> <Pane min-size="20" size="30">
<div class="key-list-vtree"> <div class="key-list-vtree">
<el-row> <el-row>
<el-col :span="2"> <el-col :span="2">
@@ -98,7 +94,6 @@
height: state.keyTreeHeight, height: state.keyTreeHeight,
overflow: 'auto', overflow: 'auto',
border: '1px solid var(--el-border-color-light, #ebeef5)', border: '1px solid var(--el-border-color-light, #ebeef5)',
marginLeft: '5px',
}" }"
ref="keyTreeRef" ref="keyTreeRef"
:highlight-current="true" :highlight-current="true"
@@ -127,21 +122,20 @@
</template> </template>
</el-tree> </el-tree>
<!-- right context menu -->
<contextmenu :dropdown="state.contextmenu.dropdown" :items="state.contextmenu.items" ref="contextmenuRef" /> <contextmenu :dropdown="state.contextmenu.dropdown" :items="state.contextmenu.items" ref="contextmenuRef" />
</div> </div>
</el-col> </Pane>
<el-col :span="12" style="border-left: 1px solid var(--el-card-border-color)"> <Pane min-size="40">
<div class="ml5"> <div class="">
<el-tabs @tab-remove="removeDataTab" style="width: 100%" v-model="state.activeName"> <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"> <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" /> <key-detail :redisId="scanParam.id" :db="scanParam.db" :key-info="dt.keyInfo" @change-key="searchKey()" @del-key="delKey" />
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</div> </div>
</el-col> </Pane>
</el-row> </Splitpanes>
<div style="text-align: center; margin-top: 10px"></div> <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 { Contextmenu, ContextmenuItem } from '@/components/contextmenu';
import { sleep } from '../../../common/utils/loading'; import { sleep } from '../../../common/utils/loading';
import { TagResourceTypeEnum } from '@/common/commonEnum'; import { TagResourceTypeEnum } from '@/common/commonEnum';
import { Splitpanes, Pane } from 'splitpanes';
const KeyDetail = defineAsyncComponent(() => import('./KeyDetail.vue')); const KeyDetail = defineAsyncComponent(() => import('./KeyDetail.vue'));
@@ -327,35 +322,6 @@ const setHeight = () => {
state.keyTreeHeight = window.innerHeight - 174 + 'px'; 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) => { const scan = async (appendKey = false) => {
isTrue(state.scanParam.id != null, '请先选择redis'); isTrue(state.scanParam.id != null, '请先选择redis');
notBlank(state.scanParam.db, '请先选择库'); notBlank(state.scanParam.db, '请先选择库');
@@ -612,7 +578,7 @@ const delKey = (key: string) => {
<style lang="scss"> <style lang="scss">
.key-list-vtree { .key-list-vtree {
height: calc(100vh - 250px); height: 100%;
} }
.key-list-vtree .folder-label { .key-list-vtree .folder-label {

View File

@@ -1765,6 +1765,11 @@ sortablejs@^1.15.0:
resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz" resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== 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: sql-formatter@^14.0.0:
version "14.0.0" version "14.0.0"
resolved "https://registry.npmmirror.com/sql-formatter/-/sql-formatter-14.0.0.tgz#07a1714c49d7d280ff2f6f09c64eebfba82b7053" resolved "https://registry.npmmirror.com/sql-formatter/-/sql-formatter-14.0.0.tgz#07a1714c49d7d280ff2f6f09c64eebfba82b7053"