mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-12-10 01:40:25 +08:00
refactor: 标签树展示调整
This commit is contained in:
@@ -45,7 +45,7 @@ const props = defineProps({
|
||||
},
|
||||
items: {
|
||||
type: Array,
|
||||
default: [],
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
>
|
||||
<template #default="{ node, data }">
|
||||
<span>
|
||||
<span v-if="data.type == TagTreeNode.TagPath">
|
||||
<span v-if="data.type.value == TagTreeNode.TagPath">
|
||||
<tag-info :tag-path="data.label" />
|
||||
</span>
|
||||
|
||||
@@ -54,7 +54,11 @@ const props = defineProps({
|
||||
},
|
||||
load: {
|
||||
type: Function,
|
||||
required: true,
|
||||
required: false,
|
||||
},
|
||||
loadTags: {
|
||||
type: Function,
|
||||
required: false,
|
||||
},
|
||||
loadContextmenuItems: {
|
||||
type: Function,
|
||||
@@ -117,7 +121,13 @@ const loadNode = async (node: any, resolve: any) => {
|
||||
}
|
||||
let nodes = [];
|
||||
try {
|
||||
if (node.level == 0 && props.loadTags) {
|
||||
nodes = await props.loadTags(node);
|
||||
} else if (props.load) {
|
||||
nodes = await props.load(node);
|
||||
} else {
|
||||
nodes = await node.data.loadChildren();
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
}
|
||||
@@ -126,18 +136,23 @@ const loadNode = async (node: any, resolve: any) => {
|
||||
|
||||
const treeNodeClick = (data: any) => {
|
||||
emit('nodeClick', data);
|
||||
if (data.type.nodeClickFunc) {
|
||||
data.type.nodeClickFunc(data);
|
||||
}
|
||||
// 关闭可能存在的右击菜单
|
||||
contextmenuRef.value.closeContextmenu();
|
||||
};
|
||||
|
||||
// 树节点右击事件
|
||||
const nodeContextmenu = (event: any, data: any) => {
|
||||
if (!props.loadContextmenuItems) {
|
||||
return;
|
||||
}
|
||||
// 加载当前节点是否需要显示右击菜单
|
||||
const items = props.loadContextmenuItems(data);
|
||||
let items = data.type.contextMenuItems;
|
||||
if (!items || items.length == 0) {
|
||||
if (props.loadContextmenuItems) {
|
||||
items = props.loadContextmenuItems(data);
|
||||
}
|
||||
}
|
||||
if (!items) {
|
||||
return;
|
||||
}
|
||||
state.contextmenuItems = items;
|
||||
|
||||
@@ -12,18 +12,24 @@ export class TagTreeNode {
|
||||
/**
|
||||
* 树节点类型
|
||||
*/
|
||||
type: any;
|
||||
type: NodeType;
|
||||
|
||||
/**
|
||||
* 是否为叶子节点
|
||||
*/
|
||||
isLeaf: boolean = false;
|
||||
|
||||
/**
|
||||
* 额外需要传递的参数
|
||||
*/
|
||||
params: any;
|
||||
|
||||
static TagPath = -1;
|
||||
|
||||
constructor(key: any, label: string, type?: any) {
|
||||
constructor(key: any, label: string, type?: NodeType) {
|
||||
this.key = key;
|
||||
this.label = label;
|
||||
this.type = type || TagTreeNode.TagPath;
|
||||
this.type = type || new NodeType(TagTreeNode.TagPath);
|
||||
}
|
||||
|
||||
withIsLeaf(isLeaf: boolean) {
|
||||
@@ -35,4 +41,68 @@ export class TagTreeNode {
|
||||
this.params = params;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载子节点,使用节点类型的loadNodesFunc去加载子节点
|
||||
* @returns 子节点信息
|
||||
*/
|
||||
async loadChildren() {
|
||||
if (this.isLeaf) {
|
||||
return [];
|
||||
}
|
||||
if (this.type && this.type.loadNodesFunc) {
|
||||
return await this.type.loadNodesFunc(this);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 节点类型,用于加载子节点及点击事件等
|
||||
*/
|
||||
export class NodeType {
|
||||
/**
|
||||
* 节点类型值
|
||||
*/
|
||||
value: number;
|
||||
|
||||
contextMenuItems: [];
|
||||
|
||||
loadNodesFunc: (parentNode: TagTreeNode) => Promise<TagTreeNode[]>;
|
||||
|
||||
nodeClickFunc: (node: TagTreeNode) => void;
|
||||
|
||||
constructor(value: number) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 赋值加载子节点回调函数
|
||||
* @param func 加载子节点回调函数
|
||||
* @returns this
|
||||
*/
|
||||
withLoadNodesFunc(func: (parentNode: TagTreeNode) => Promise<TagTreeNode[]>) {
|
||||
this.loadNodesFunc = func;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 赋值节点点击事件回调函数
|
||||
* @param func 节点点击事件回调函数
|
||||
* @returns this
|
||||
*/
|
||||
withNodeClickFunc(func: (node: TagTreeNode) => void) {
|
||||
this.nodeClickFunc = func;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 赋值右击菜单按钮选项
|
||||
* @param contextMenuItems 右击菜单按钮选项
|
||||
* @returns this
|
||||
*/
|
||||
withContextMenuItems(contextMenuItems: []) {
|
||||
this.contextMenuItems = contextMenuItems;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,16 +31,9 @@
|
||||
|
||||
<el-row type="flex">
|
||||
<el-col :span="4">
|
||||
<tag-tree
|
||||
ref="tagTreeRef"
|
||||
@node-click="nodeClick"
|
||||
:load="loadNode"
|
||||
:load-contextmenu-items="getContextmenuItems"
|
||||
@current-contextmenu-click="onCurrentContextmenuClick"
|
||||
:height="state.tagTreeHeight"
|
||||
>
|
||||
<tag-tree ref="tagTreeRef" :loadTags="loadTags" @current-contextmenu-click="onCurrentContextmenuClick" :height="state.tagTreeHeight">
|
||||
<template #prefix="{ data }">
|
||||
<span v-if="data.type == NodeType.DbInst">
|
||||
<span v-if="data.type.value == SqlExecNodeType.DbInst">
|
||||
<el-popover :show-after="500" placement="right-start" title="数据库实例信息" trigger="hover" :width="210">
|
||||
<template #reference>
|
||||
<SvgIcon v-if="data.params.type === 'mysql'" name="iconfont icon-op-mysql" :size="18" />
|
||||
@@ -60,13 +53,13 @@
|
||||
</el-popover>
|
||||
</span>
|
||||
|
||||
<SvgIcon v-if="data.type == NodeType.Db" name="Coin" color="#67c23a" />
|
||||
<SvgIcon v-if="data.type.value == SqlExecNodeType.Db" name="Coin" color="#67c23a" />
|
||||
|
||||
<SvgIcon name="Calendar" v-if="data.type == NodeType.TableMenu" color="#409eff" />
|
||||
<SvgIcon name="Calendar" v-if="data.type.value == SqlExecNodeType.TableMenu" color="#409eff" />
|
||||
|
||||
<el-tooltip
|
||||
:show-after="500"
|
||||
v-if="data.type == NodeType.Table"
|
||||
v-if="data.type.value == SqlExecNodeType.Table"
|
||||
effect="customized"
|
||||
:content="data.params.tableComment"
|
||||
placement="top-end"
|
||||
@@ -74,11 +67,14 @@
|
||||
<SvgIcon name="Calendar" color="#409eff" />
|
||||
</el-tooltip>
|
||||
|
||||
<SvgIcon name="Files" v-if="data.type == NodeType.SqlMenu || data.type == NodeType.Sql" color="#f56c6c" />
|
||||
<SvgIcon name="Files" v-if="data.type.value == SqlExecNodeType.SqlMenu || data.type.value == SqlExecNodeType.Sql" color="#f56c6c" />
|
||||
</template>
|
||||
|
||||
<template #suffix="{ data }">
|
||||
<span class="db-table-size" v-if="data.type == NodeType.Table">{{ ` ${data.params.size}` }}</span>
|
||||
<span class="db-table-size" v-if="data.type.value == SqlExecNodeType.Table && data.params.size">{{ ` ${data.params.size}` }}</span>
|
||||
<span class="db-table-size" v-if="data.type.value == SqlExecNodeType.TableMenu && data.params.dbTableSize">{{
|
||||
` ${data.params.dbTableSize}`
|
||||
}}</span>
|
||||
</template>
|
||||
</tag-tree>
|
||||
</el-col>
|
||||
@@ -119,7 +115,7 @@ import { defineAsyncComponent, onMounted, reactive, ref, toRefs, onBeforeUnmount
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { formatByteSize } from '@/common/utils/format';
|
||||
import { DbInst, TabInfo, TabType, registerDbCompletionItemProvider } from './db';
|
||||
import { TagTreeNode } from '../component/tag';
|
||||
import { TagTreeNode, NodeType } from '../component/tag';
|
||||
import TagTree from '../component/TagTree.vue';
|
||||
import { dbApi } from './api';
|
||||
import { dispposeCompletionItemProvider } from '../../../components/monaco/completionItemProvider';
|
||||
@@ -129,7 +125,7 @@ const TableData = defineAsyncComponent(() => import('./component/tab/TableData.v
|
||||
/**
|
||||
* 树节点类型
|
||||
*/
|
||||
class NodeType {
|
||||
class SqlExecNodeType {
|
||||
static DbInst = 1;
|
||||
static Db = 2;
|
||||
static TableMenu = 3;
|
||||
@@ -137,10 +133,115 @@ class NodeType {
|
||||
static Table = 5;
|
||||
static Sql = 6;
|
||||
}
|
||||
|
||||
class ContextmenuClickId {
|
||||
static ReloadTable = 0;
|
||||
}
|
||||
|
||||
// node节点点击时,触发改变db事件
|
||||
const changeDb = (nodeData: TagTreeNode) => {
|
||||
const params = nodeData.params;
|
||||
changeSchema({ id: params.id, name: params.name, type: params.type, tagPath: params.tagPath, databases: params.database }, params.db);
|
||||
};
|
||||
|
||||
// tagpath 节点类型
|
||||
const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
||||
const dbInfos = instMap.get(parentNode.key);
|
||||
if (!dbInfos) {
|
||||
return [];
|
||||
}
|
||||
return dbInfos?.map((x: any) => {
|
||||
return new TagTreeNode(`${parentNode.key}.${x.id}`, x.name, NodeTypeDbInst).withParams(x);
|
||||
});
|
||||
});
|
||||
|
||||
// 数据库实例节点类型
|
||||
const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst)
|
||||
.withLoadNodesFunc((parentNode: TagTreeNode) => {
|
||||
const params = parentNode.params;
|
||||
const dbs = params.database.split(' ')?.sort();
|
||||
return dbs.map((x: any) => {
|
||||
return new TagTreeNode(`${parentNode.key}.${x}`, x, NodeTypeDb).withParams({
|
||||
tagPath: params.tagPath,
|
||||
id: params.id,
|
||||
name: params.name,
|
||||
type: params.type,
|
||||
dbs: dbs,
|
||||
db: x,
|
||||
});
|
||||
});
|
||||
})
|
||||
.withNodeClickFunc(changeDb);
|
||||
|
||||
// 数据库节点
|
||||
const NodeTypeDb = new NodeType(SqlExecNodeType.Db)
|
||||
.withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
||||
const params = parentNode.params;
|
||||
return [
|
||||
new TagTreeNode(`${params.id}.${params.db}.table-menu`, '表', NodeTypeTableMenu).withParams(params),
|
||||
new TagTreeNode(getSqlMenuNodeKey(params.id, params.db), 'SQL', NodeTypeSqlMenu).withParams(params),
|
||||
];
|
||||
})
|
||||
.withNodeClickFunc(changeDb);
|
||||
|
||||
// 数据库表菜单节点
|
||||
const NodeTypeTableMenu = new NodeType(SqlExecNodeType.TableMenu)
|
||||
.withContextMenuItems([{ contextMenuClickId: ContextmenuClickId.ReloadTable, txt: '刷新', icon: 'RefreshRight' }] as any)
|
||||
.withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
||||
const params = parentNode.params;
|
||||
const { id, db } = params;
|
||||
// 获取当前库的所有表信息
|
||||
let tables = await DbInst.getInst(id).loadTables(db, state.reloadStatus);
|
||||
state.reloadStatus = false;
|
||||
let dbTableSize = 0;
|
||||
const tablesNode = tables.map((x: any) => {
|
||||
dbTableSize += x.dataLength + x.indexLength;
|
||||
return new TagTreeNode(`${id}.${db}.${x.tableName}`, x.tableName, NodeTypeTable).withIsLeaf(true).withParams({
|
||||
id,
|
||||
db,
|
||||
tableName: x.tableName,
|
||||
tableComment: x.tableComment,
|
||||
size: formatByteSize(x.dataLength + x.indexLength, 1),
|
||||
});
|
||||
});
|
||||
// 设置父节点参数的表大小
|
||||
parentNode.params.dbTableSize = formatByteSize(dbTableSize);
|
||||
return tablesNode;
|
||||
})
|
||||
.withNodeClickFunc(changeDb);
|
||||
|
||||
// 数据库sql模板菜单节点
|
||||
const NodeTypeSqlMenu = new NodeType(SqlExecNodeType.SqlMenu)
|
||||
.withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
||||
const params = parentNode.params;
|
||||
const id = params.id;
|
||||
const db = params.db;
|
||||
const dbs = params.dbs;
|
||||
// 加载用户保存的sql脚本
|
||||
const sqls = await dbApi.getSqlNames.request({ id: id, db: db });
|
||||
return sqls.map((x: any) => {
|
||||
return new TagTreeNode(`${id}.${db}.${x.name}`, x.name, NodeTypeSql).withIsLeaf(true).withParams({
|
||||
id,
|
||||
db,
|
||||
dbs,
|
||||
sqlName: x.name,
|
||||
});
|
||||
});
|
||||
})
|
||||
.withNodeClickFunc(changeDb);
|
||||
|
||||
// 表节点类型
|
||||
const NodeTypeTable = new NodeType(SqlExecNodeType.Table).withNodeClickFunc((nodeData: TagTreeNode) => {
|
||||
const params = nodeData.params;
|
||||
loadTableData({ id: params.id, nodeKey: nodeData.key }, params.db, params.tableName);
|
||||
});
|
||||
|
||||
// sql模板节点类型
|
||||
const NodeTypeSql = new NodeType(SqlExecNodeType.Sql).withNodeClickFunc((nodeData: TagTreeNode) => {
|
||||
const params = nodeData.params;
|
||||
addQueryTab({ id: params.id, nodeKey: nodeData.key, dbs: params.dbs }, params.db, params.sqlName);
|
||||
});
|
||||
|
||||
const tagTreeRef: any = ref(null);
|
||||
|
||||
const tabs: Map<string, TabInfo> = new Map();
|
||||
@@ -200,97 +301,16 @@ const getInsts = async () => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 加载树节点
|
||||
* @param {Object} node
|
||||
* @param {Object} resolve
|
||||
* 加载标签树节点
|
||||
*/
|
||||
const loadNode = async (node: any) => {
|
||||
// 一级为tagPath
|
||||
if (node.level === 0) {
|
||||
const loadTags = async () => {
|
||||
await getInsts();
|
||||
const tagPaths = instMap.keys();
|
||||
const tagNodes = [];
|
||||
for (let tagPath of tagPaths) {
|
||||
tagNodes.push(new TagTreeNode(tagPath, tagPath));
|
||||
tagNodes.push(new TagTreeNode(tagPath, tagPath, NodeTypeTagPath));
|
||||
}
|
||||
return tagNodes;
|
||||
}
|
||||
|
||||
const data = node.data;
|
||||
const nodeType = data.type;
|
||||
const params = data.params;
|
||||
|
||||
// 点击tagPath -> 加载数据库实例信息列表
|
||||
if (nodeType === TagTreeNode.TagPath) {
|
||||
const dbInfos = instMap.get(data.key);
|
||||
return dbInfos?.map((x: any) => {
|
||||
return new TagTreeNode(`${data.key}.${x.id}`, x.name, NodeType.DbInst).withParams(x);
|
||||
});
|
||||
}
|
||||
|
||||
// 点击数据库实例 -> 加载库列表
|
||||
if (nodeType === NodeType.DbInst) {
|
||||
const dbs = params.database.split(' ')?.sort();
|
||||
return dbs.map((x: any) => {
|
||||
return new TagTreeNode(`${data.key}.${x}`, x, NodeType.Db).withParams({
|
||||
tagPath: params.tagPath,
|
||||
id: params.id,
|
||||
name: params.name,
|
||||
type: params.type,
|
||||
dbs: dbs,
|
||||
db: x,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 点击数据库 -> 加载 表&Sql 菜单
|
||||
if (nodeType === NodeType.Db) {
|
||||
return [
|
||||
new TagTreeNode(`${params.id}.${params.db}.table-menu`, '表', NodeType.TableMenu).withParams(params),
|
||||
new TagTreeNode(getSqlMenuNodeKey(params.id, params.db), 'SQL', NodeType.SqlMenu).withParams(params),
|
||||
];
|
||||
}
|
||||
|
||||
// 点击表菜单 -> 加载表列表
|
||||
if (nodeType === NodeType.TableMenu) {
|
||||
return await getTables(params);
|
||||
}
|
||||
|
||||
if (nodeType === NodeType.SqlMenu) {
|
||||
return await loadSqls(params.id, params.db, params.dbs);
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
const nodeClick = async (data: any) => {
|
||||
const params = data.params;
|
||||
const nodeKey = data.key;
|
||||
const dataType = data.type;
|
||||
// 点击数据库,修改当前数据库信息
|
||||
if (dataType === NodeType.Db || dataType === NodeType.SqlMenu || dataType === NodeType.TableMenu || dataType === NodeType.DbInst) {
|
||||
changeSchema({ id: params.id, name: params.name, type: params.type, tagPath: params.tagPath, databases: params.database }, params.db);
|
||||
return;
|
||||
}
|
||||
|
||||
// 点击表加载表数据tab
|
||||
if (dataType === NodeType.Table) {
|
||||
await loadTableData({ id: params.id, nodeKey: nodeKey }, params.db, params.tableName);
|
||||
return;
|
||||
}
|
||||
|
||||
// 点击表加载表数据tab
|
||||
if (dataType === NodeType.Sql) {
|
||||
await addQueryTab({ id: params.id, nodeKey: nodeKey, dbs: params.dbs }, params.db, params.sqlName);
|
||||
}
|
||||
};
|
||||
|
||||
const getContextmenuItems = (data: any) => {
|
||||
const dataType = data.type;
|
||||
if (dataType === NodeType.TableMenu) {
|
||||
return [{ contextMenuClickId: ContextmenuClickId.ReloadTable, txt: '刷新', icon: 'RefreshRight' }];
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
// 当前右击菜单点击事件
|
||||
@@ -301,39 +321,6 @@ const onCurrentContextmenuClick = (clickData: any) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getTables = async (params: any) => {
|
||||
const { id, db } = params;
|
||||
let tables = await DbInst.getInst(id).loadTables(db, state.reloadStatus);
|
||||
state.reloadStatus = false;
|
||||
return tables.map((x: any) => {
|
||||
return new TagTreeNode(`${id}.${db}.${x.tableName}`, x.tableName, NodeType.Table).withIsLeaf(true).withParams({
|
||||
id,
|
||||
db,
|
||||
tableName: x.tableName,
|
||||
tableComment: x.tableComment,
|
||||
size: formatByteSize(x.dataLength + x.indexLength, 1),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 加载用户保存的sql脚本
|
||||
*
|
||||
* @param inst
|
||||
* @param schema
|
||||
*/
|
||||
const loadSqls = async (id: any, db: string, dbs: any) => {
|
||||
const sqls = await dbApi.getSqlNames.request({ id: id, db: db });
|
||||
return sqls.map((x: any) => {
|
||||
return new TagTreeNode(`${id}.${db}.${x.name}`, x.name, NodeType.Sql).withIsLeaf(true).withParams({
|
||||
id,
|
||||
db,
|
||||
dbs,
|
||||
sqlName: x.name,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 选择数据库
|
||||
const changeSchema = (inst: any, schema: string) => {
|
||||
state.nowDbInst = DbInst.getOrNewInst(inst);
|
||||
@@ -372,6 +359,7 @@ const addQueryTab = async (inst: any, db: string, sqlName: string = '') => {
|
||||
ElMessage.warning('请选择数据库实例及对应的schema');
|
||||
return;
|
||||
}
|
||||
changeSchema(inst, db);
|
||||
|
||||
const dbId = inst.id;
|
||||
let label;
|
||||
@@ -470,31 +458,9 @@ const reloadTables = (nodeKey: string) => {
|
||||
|
||||
<style lang="scss">
|
||||
.db-sql-exec {
|
||||
.sql-file-exec {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.db-table-size {
|
||||
color: #c4c9c4;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.sqlEditor {
|
||||
font-size: 8pt;
|
||||
font-weight: 600;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.editor-move-resize {
|
||||
cursor: n-resize;
|
||||
height: 3px;
|
||||
text-align: center;
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
#data-exec {
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
<div>
|
||||
<el-row>
|
||||
<el-col :span="4">
|
||||
<tag-tree @node-click="nodeClick" :load="loadNode">
|
||||
<tag-tree :loadTags="loadTags">
|
||||
<template #prefix="{ data }">
|
||||
<span v-if="data.type == NodeType.Mongo">
|
||||
<span v-if="data.type.value == MongoNodeType.Mongo">
|
||||
<el-popover :show-after="500" placement="right-start" title="mongo实例信息" trigger="hover" :width="210">
|
||||
<template #reference>
|
||||
<SvgIcon name="iconfont icon-op-mongo" :size="18" />
|
||||
@@ -18,14 +18,18 @@
|
||||
</el-popover>
|
||||
</span>
|
||||
|
||||
<SvgIcon v-if="data.type == NodeType.Dbs" name="Coin" color="#67c23a" />
|
||||
<SvgIcon v-if="data.type.value == MongoNodeType.Dbs" name="Coin" color="#67c23a" />
|
||||
|
||||
<SvgIcon v-if="data.type == NodeType.Coll || data.type == NodeType.CollMenu" name="Document" class="color-primary" />
|
||||
<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 == NodeType.Dbs">
|
||||
{{ data.params.dbName }}
|
||||
<span v-if="data.type.value == MongoNodeType.Dbs">
|
||||
{{ data.params.database }}
|
||||
<span style="color: #8492a6; font-size: 13px"> [{{ formatByteSize(data.params.size) }}] </span>
|
||||
</span>
|
||||
|
||||
@@ -161,7 +165,7 @@ import { computed, defineAsyncComponent, reactive, ref, toRefs } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import { isTrue, notBlank } from '@/common/assert';
|
||||
import { TagTreeNode } from '../component/tag';
|
||||
import { TagTreeNode, NodeType } from '../component/tag';
|
||||
import TagTree from '../component/TagTree.vue';
|
||||
import { formatByteSize } from '@/common/utils/format';
|
||||
|
||||
@@ -175,13 +179,63 @@ const perms = {
|
||||
/**
|
||||
* 树节点类型
|
||||
*/
|
||||
class NodeType {
|
||||
class MongoNodeType {
|
||||
static Mongo = 1;
|
||||
static Dbs = 2;
|
||||
static CollMenu = 3;
|
||||
static Coll = 4;
|
||||
}
|
||||
|
||||
// tagpath 节点类型
|
||||
const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
||||
// 点击标签 -> 显示mongo信息列表
|
||||
const mongoInfos = instMap.get(parentNode.key);
|
||||
if (!mongoInfos) {
|
||||
return [];
|
||||
}
|
||||
return mongoInfos?.map((x: any) => {
|
||||
return new TagTreeNode(`${parentNode.key}.${x.id}`, x.name, NodeTypeMongo).withParams(x);
|
||||
});
|
||||
});
|
||||
|
||||
const NodeTypeMongo = new NodeType(MongoNodeType.Mongo).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
||||
const inst = parentNode.params;
|
||||
// 点击mongo -> 加载mongo数据库列表
|
||||
const res = await mongoApi.databases.request({ id: inst.id });
|
||||
return res.Databases.map((x: any) => {
|
||||
const database = x.Name;
|
||||
return new TagTreeNode(`${inst.id}.${database}`, database, NodeTypeDbs).withParams({
|
||||
id: inst.id,
|
||||
database,
|
||||
size: x.SizeOnDisk,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const NodeTypeDbs = new NodeType(MongoNodeType.Dbs).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
||||
const params = parentNode.params;
|
||||
// 点击数据库列表 -> 加载数据库下拥有的菜单列表
|
||||
return [new TagTreeNode(`${params.id}.${params.database}.mongo-coll`, '集合', NodeTypeCollMenu).withParams(params)];
|
||||
});
|
||||
|
||||
const NodeTypeCollMenu = new NodeType(MongoNodeType.CollMenu).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
||||
const { id, database } = parentNode.params;
|
||||
// 点击数据库集合节点 -> 加载集合列表
|
||||
const colls = await mongoApi.collections.request({ id, database });
|
||||
return colls.map((x: any) => {
|
||||
return new TagTreeNode(`${id}.${database}.${x}`, x, NodeTypeColl).withIsLeaf(true).withParams({
|
||||
id,
|
||||
database,
|
||||
collection: x,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const NodeTypeColl = new NodeType(MongoNodeType.Coll).withNodeClickFunc((nodeData: TagTreeNode) => {
|
||||
const { id, database, collection } = nodeData.params;
|
||||
changeCollection(id, database, collection);
|
||||
});
|
||||
|
||||
const findParamInputRef: any = ref(null);
|
||||
const state = reactive({
|
||||
tags: [],
|
||||
@@ -237,89 +291,16 @@ const getInsts = async () => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 加载文件树节点
|
||||
* @param {Object} node
|
||||
* @param {Object} resolve
|
||||
* 加载标签树树节点
|
||||
*/
|
||||
const loadNode = async (node: any) => {
|
||||
// 一级为tagPath
|
||||
if (node.level === 0) {
|
||||
const loadTags = async () => {
|
||||
await getInsts();
|
||||
const tagPaths = instMap.keys();
|
||||
const tagNodes = [];
|
||||
for (let tagPath of tagPaths) {
|
||||
tagNodes.push(new TagTreeNode(tagPath, tagPath));
|
||||
tagNodes.push(new TagTreeNode(tagPath, tagPath, NodeTypeTagPath));
|
||||
}
|
||||
return tagNodes;
|
||||
}
|
||||
|
||||
const data = node.data;
|
||||
const params = data.params;
|
||||
const nodeType = data.type;
|
||||
|
||||
// 点击标签 -> 显示mongo信息列表
|
||||
if (nodeType === TagTreeNode.TagPath) {
|
||||
const mongoInfos = instMap.get(data.key);
|
||||
return mongoInfos?.map((x: any) => {
|
||||
return new TagTreeNode(`${data.key}.${x.id}`, x.name, NodeType.Mongo).withParams(x);
|
||||
});
|
||||
}
|
||||
|
||||
// 点击mongo -> 加载mongo数据库列表
|
||||
if (nodeType === NodeType.Mongo) {
|
||||
return await getDatabases(params);
|
||||
}
|
||||
|
||||
// 点击数据库列表 -> 加载数据库下拥有的菜单列表
|
||||
if (nodeType === NodeType.Dbs) {
|
||||
return [new TagTreeNode(`${params.id}.${params.dbName}.mongo-coll`, '集合', NodeType.CollMenu).withParams(params)];
|
||||
}
|
||||
|
||||
// 点击数据库集合节点 -> 加载集合列表
|
||||
if (nodeType === NodeType.CollMenu) {
|
||||
return await getCollections(params.id, params.dbName);
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取实例的所有库信息
|
||||
* @param inst 实例信息
|
||||
*/
|
||||
const getDatabases = async (inst: any) => {
|
||||
const res = await mongoApi.databases.request({ id: inst.id });
|
||||
return res.Databases.map((x: any) => {
|
||||
const dbName = x.Name;
|
||||
return new TagTreeNode(`${inst.id}.${dbName}`, dbName, NodeType.Dbs).withParams({
|
||||
id: inst.id,
|
||||
dbName,
|
||||
size: x.SizeOnDisk,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取集合列表信息
|
||||
* @param inst
|
||||
*/
|
||||
const getCollections = async (id: any, database: string) => {
|
||||
const colls = await mongoApi.collections.request({ id, database });
|
||||
return colls.map((x: any) => {
|
||||
return new TagTreeNode(`${id}.${database}.${x}`, x, NodeType.Coll).withIsLeaf(true).withParams({
|
||||
id,
|
||||
database,
|
||||
collection: x,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const nodeClick = async (data: any) => {
|
||||
// 点击集合
|
||||
if (data.type === NodeType.Coll) {
|
||||
const { id, database, collection } = data.params;
|
||||
await changeCollection(id, database, collection);
|
||||
}
|
||||
};
|
||||
|
||||
const changeCollection = async (id: any, schema: string, collection: string) => {
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
<el-col :span="4">
|
||||
<el-row type="flex" justify="space-between">
|
||||
<el-col :span="24" class="flex-auto">
|
||||
<tag-tree @node-click="nodeClick" :load="loadNode">
|
||||
<tag-tree :loadTags="loadTags">
|
||||
<template #prefix="{ data }">
|
||||
<span v-if="data.type == NodeType.Redis">
|
||||
<span v-if="data.type.value == RedisNodeType.Redis">
|
||||
<el-popover :show-after="500" placement="right-start" title="redis实例信息" trigger="hover" :width="210">
|
||||
<template #reference>
|
||||
<SvgIcon name="iconfont icon-op-redis" :size="18" />
|
||||
@@ -22,7 +22,7 @@
|
||||
</el-popover>
|
||||
</span>
|
||||
|
||||
<SvgIcon v-if="data.type == NodeType.Db" name="Coin" color="#67c23a" />
|
||||
<SvgIcon v-if="data.type.value == RedisNodeType.Db" name="Coin" color="#67c23a" />
|
||||
</template>
|
||||
</tag-tree>
|
||||
</el-col>
|
||||
@@ -184,7 +184,7 @@ import { redisApi } from './api';
|
||||
import { ref, defineAsyncComponent, toRefs, reactive, onMounted, nextTick } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { isTrue, notBlank, notNull } from '@/common/assert';
|
||||
import { TagTreeNode } from '../component/tag';
|
||||
import { TagTreeNode, NodeType } from '../component/tag';
|
||||
import TagTree from '../component/TagTree.vue';
|
||||
import { keysToTree, sortByTreeNodes, keysToList } from './utils';
|
||||
|
||||
@@ -193,11 +193,61 @@ const KeyDetail = defineAsyncComponent(() => import('./KeyDetail.vue'));
|
||||
/**
|
||||
* 树节点类型
|
||||
*/
|
||||
class NodeType {
|
||||
class RedisNodeType {
|
||||
static Redis = 1;
|
||||
static Db = 2;
|
||||
}
|
||||
|
||||
// tagpath 节点类型
|
||||
const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
||||
const redisInfos = instMap.get(parentNode.key);
|
||||
if (!redisInfos) {
|
||||
return [];
|
||||
}
|
||||
return redisInfos.map((x: any) => {
|
||||
return new TagTreeNode(`${parentNode.key}.${x.id}`, x.name, NodeTypeRedis).withParams(x);
|
||||
});
|
||||
});
|
||||
|
||||
// redis实例节点类型
|
||||
const NodeTypeRedis = new NodeType(RedisNodeType.Redis).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
||||
const redisInfo = parentNode.params;
|
||||
let dbs: TagTreeNode[] = redisInfo.db.split(',').map((x: string) => {
|
||||
return new TagTreeNode(x, `db${x}`, NodeTypeDb).withIsLeaf(true).withParams({
|
||||
id: redisInfo.id,
|
||||
db: x,
|
||||
name: `db${x}`,
|
||||
keys: 0,
|
||||
});
|
||||
});
|
||||
|
||||
if (redisInfo.mode == 'cluster') {
|
||||
return dbs;
|
||||
}
|
||||
|
||||
const res = await redisApi.redisInfo.request({ id: redisInfo.id, host: redisInfo.host, section: 'Keyspace' });
|
||||
for (let db in res.Keyspace) {
|
||||
for (let d of dbs) {
|
||||
if (db == d.params.name) {
|
||||
d.params.keys = res.Keyspace[db]?.split(',')[0]?.split('=')[1] || 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 替换label
|
||||
dbs.forEach((e: any) => {
|
||||
e.label = `${e.params.name} [${e.params.keys}]`;
|
||||
});
|
||||
return dbs;
|
||||
});
|
||||
|
||||
// 库节点类型
|
||||
const NodeTypeDb = new NodeType(RedisNodeType.Db).withNodeClickFunc((nodeData: TagTreeNode) => {
|
||||
resetScanParam();
|
||||
state.scanParam.id = nodeData.params.id;
|
||||
state.scanParam.db = nodeData.params.db;
|
||||
scan();
|
||||
});
|
||||
|
||||
const treeProps = {
|
||||
label: 'name',
|
||||
children: 'children',
|
||||
@@ -270,80 +320,16 @@ const getInsts = async () => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 加载文件树节点
|
||||
* @param {Object} node
|
||||
* @param {Object} resolve
|
||||
* 加载标签树节点
|
||||
*/
|
||||
const loadNode = async (node: any) => {
|
||||
// 一级为tagPath
|
||||
if (node.level === 0) {
|
||||
const loadTags = async () => {
|
||||
await getInsts();
|
||||
const tagPaths = instMap.keys();
|
||||
const tagNodes = [];
|
||||
for (let tagPath of tagPaths) {
|
||||
tagNodes.push(new TagTreeNode(tagPath, tagPath));
|
||||
tagNodes.push(new TagTreeNode(tagPath, tagPath, NodeTypeTagPath));
|
||||
}
|
||||
return tagNodes;
|
||||
}
|
||||
|
||||
const data = node.data;
|
||||
// 点击tagPath -> 加载数据库信息列表
|
||||
if (data.type === TagTreeNode.TagPath) {
|
||||
const redisInfos = instMap.get(data.key);
|
||||
return redisInfos?.map((x: any) => {
|
||||
return new TagTreeNode(`${data.key}.${x.id}`, x.name, NodeType.Redis).withParams(x);
|
||||
});
|
||||
}
|
||||
|
||||
// 点击redis实例 -> 加载库列表
|
||||
if (data.type === NodeType.Redis) {
|
||||
return await getDbs(data.params);
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
const nodeClick = (data: any) => {
|
||||
// 点击库事件
|
||||
if (data.type === NodeType.Db) {
|
||||
resetScanParam();
|
||||
state.scanParam.id = data.params.id;
|
||||
state.scanParam.db = data.params.db;
|
||||
scan();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取所有库信息
|
||||
* @param redisInfo redis信息
|
||||
*/
|
||||
const getDbs = async (redisInfo: any) => {
|
||||
let dbs: TagTreeNode[] = redisInfo.db.split(',').map((x: string) => {
|
||||
return new TagTreeNode(x, `db${x}`, NodeType.Db).withIsLeaf(true).withParams({
|
||||
id: redisInfo.id,
|
||||
db: x,
|
||||
name: `db${x}`,
|
||||
keys: 0,
|
||||
});
|
||||
});
|
||||
|
||||
if (redisInfo.mode == 'cluster') {
|
||||
return dbs;
|
||||
}
|
||||
|
||||
const res = await redisApi.redisInfo.request({ id: redisInfo.id, host: redisInfo.host, section: 'Keyspace' });
|
||||
for (let db in res.Keyspace) {
|
||||
for (let d of dbs) {
|
||||
if (db == d.params.name) {
|
||||
d.params.keys = res.Keyspace[db]?.split(',')[0]?.split('=')[1] || 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 替换label
|
||||
dbs.forEach((e: any) => {
|
||||
e.label = `${e.params.name} [${e.params.keys}]`;
|
||||
});
|
||||
return dbs;
|
||||
};
|
||||
|
||||
const scan = async (appendKey = false) => {
|
||||
|
||||
Reference in New Issue
Block a user