refactor: 标签树展示调整

This commit is contained in:
meilin.huang
2023-11-03 17:09:20 +08:00
parent 37026f3269
commit 0ce82b41ba
6 changed files with 356 additions and 338 deletions

View File

@@ -45,7 +45,7 @@ const props = defineProps({
}, },
items: { items: {
type: Array, type: Array,
default: [], default: () => [],
}, },
}); });

View File

@@ -21,7 +21,7 @@
> >
<template #default="{ node, data }"> <template #default="{ node, data }">
<span> <span>
<span v-if="data.type == TagTreeNode.TagPath"> <span v-if="data.type.value == TagTreeNode.TagPath">
<tag-info :tag-path="data.label" /> <tag-info :tag-path="data.label" />
</span> </span>
@@ -54,7 +54,11 @@ const props = defineProps({
}, },
load: { load: {
type: Function, type: Function,
required: true, required: false,
},
loadTags: {
type: Function,
required: false,
}, },
loadContextmenuItems: { loadContextmenuItems: {
type: Function, type: Function,
@@ -117,7 +121,13 @@ const loadNode = async (node: any, resolve: any) => {
} }
let nodes = []; let nodes = [];
try { try {
nodes = await props.load(node); 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) { } catch (e: any) {
console.error(e); console.error(e);
} }
@@ -126,18 +136,23 @@ const loadNode = async (node: any, resolve: any) => {
const treeNodeClick = (data: any) => { const treeNodeClick = (data: any) => {
emit('nodeClick', data); emit('nodeClick', data);
if (data.type.nodeClickFunc) {
data.type.nodeClickFunc(data);
}
// 关闭可能存在的右击菜单 // 关闭可能存在的右击菜单
contextmenuRef.value.closeContextmenu(); contextmenuRef.value.closeContextmenu();
}; };
// 树节点右击事件 // 树节点右击事件
const nodeContextmenu = (event: any, data: any) => { 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 (!items || items.length == 0) {
if (props.loadContextmenuItems) {
items = props.loadContextmenuItems(data);
}
}
if (!items) {
return; return;
} }
state.contextmenuItems = items; state.contextmenuItems = items;

View File

@@ -12,18 +12,24 @@ export class TagTreeNode {
/** /**
* 树节点类型 * 树节点类型
*/ */
type: any; type: NodeType;
/**
* 是否为叶子节点
*/
isLeaf: boolean = false; isLeaf: boolean = false;
/**
* 额外需要传递的参数
*/
params: any; params: any;
static TagPath = -1; static TagPath = -1;
constructor(key: any, label: string, type?: any) { constructor(key: any, label: string, type?: NodeType) {
this.key = key; this.key = key;
this.label = label; this.label = label;
this.type = type || TagTreeNode.TagPath; this.type = type || new NodeType(TagTreeNode.TagPath);
} }
withIsLeaf(isLeaf: boolean) { withIsLeaf(isLeaf: boolean) {
@@ -35,4 +41,68 @@ export class TagTreeNode {
this.params = params; this.params = params;
return this; 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;
}
} }

View File

@@ -31,16 +31,9 @@
<el-row type="flex"> <el-row type="flex">
<el-col :span="4"> <el-col :span="4">
<tag-tree <tag-tree ref="tagTreeRef" :loadTags="loadTags" @current-contextmenu-click="onCurrentContextmenuClick" :height="state.tagTreeHeight">
ref="tagTreeRef"
@node-click="nodeClick"
:load="loadNode"
:load-contextmenu-items="getContextmenuItems"
@current-contextmenu-click="onCurrentContextmenuClick"
:height="state.tagTreeHeight"
>
<template #prefix="{ data }"> <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"> <el-popover :show-after="500" placement="right-start" title="数据库实例信息" trigger="hover" :width="210">
<template #reference> <template #reference>
<SvgIcon v-if="data.params.type === 'mysql'" name="iconfont icon-op-mysql" :size="18" /> <SvgIcon v-if="data.params.type === 'mysql'" name="iconfont icon-op-mysql" :size="18" />
@@ -60,13 +53,13 @@
</el-popover> </el-popover>
</span> </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 <el-tooltip
:show-after="500" :show-after="500"
v-if="data.type == NodeType.Table" v-if="data.type.value == SqlExecNodeType.Table"
effect="customized" effect="customized"
:content="data.params.tableComment" :content="data.params.tableComment"
placement="top-end" placement="top-end"
@@ -74,11 +67,14 @@
<SvgIcon name="Calendar" color="#409eff" /> <SvgIcon name="Calendar" color="#409eff" />
</el-tooltip> </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>
<template #suffix="{ data }"> <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> </template>
</tag-tree> </tag-tree>
</el-col> </el-col>
@@ -119,7 +115,7 @@ import { defineAsyncComponent, onMounted, reactive, ref, toRefs, onBeforeUnmount
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import { formatByteSize } from '@/common/utils/format'; import { formatByteSize } from '@/common/utils/format';
import { DbInst, TabInfo, TabType, registerDbCompletionItemProvider } from './db'; 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 TagTree from '../component/TagTree.vue';
import { dbApi } from './api'; import { dbApi } from './api';
import { dispposeCompletionItemProvider } from '../../../components/monaco/completionItemProvider'; 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 DbInst = 1;
static Db = 2; static Db = 2;
static TableMenu = 3; static TableMenu = 3;
@@ -137,10 +133,115 @@ class NodeType {
static Table = 5; static Table = 5;
static Sql = 6; static Sql = 6;
} }
class ContextmenuClickId { class ContextmenuClickId {
static ReloadTable = 0; 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 tagTreeRef: any = ref(null);
const tabs: Map<string, TabInfo> = new Map(); 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) => { const loadTags = async () => {
// 一级为tagPath await getInsts();
if (node.level === 0) { const tagPaths = instMap.keys();
await getInsts(); const tagNodes = [];
const tagPaths = instMap.keys(); for (let tagPath of tagPaths) {
const tagNodes = []; tagNodes.push(new TagTreeNode(tagPath, tagPath, NodeTypeTagPath));
for (let tagPath of tagPaths) {
tagNodes.push(new TagTreeNode(tagPath, tagPath));
}
return tagNodes;
} }
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) => { const changeSchema = (inst: any, schema: string) => {
state.nowDbInst = DbInst.getOrNewInst(inst); state.nowDbInst = DbInst.getOrNewInst(inst);
@@ -372,6 +359,7 @@ const addQueryTab = async (inst: any, db: string, sqlName: string = '') => {
ElMessage.warning('请选择数据库实例及对应的schema'); ElMessage.warning('请选择数据库实例及对应的schema');
return; return;
} }
changeSchema(inst, db);
const dbId = inst.id; const dbId = inst.id;
let label; let label;
@@ -470,31 +458,9 @@ const reloadTables = (nodeKey: string) => {
<style lang="scss"> <style lang="scss">
.db-sql-exec { .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 { .db-table-size {
color: #c4c9c4; color: #c4c9c4;
font-size: 10px; font-size: 9px;
}
.sqlEditor {
font-size: 8pt;
font-weight: 600;
border: 1px solid #ccc;
}
.editor-move-resize {
cursor: n-resize;
height: 3px;
text-align: center;
} }
#data-exec { #data-exec {

View File

@@ -2,9 +2,9 @@
<div> <div>
<el-row> <el-row>
<el-col :span="4"> <el-col :span="4">
<tag-tree @node-click="nodeClick" :load="loadNode"> <tag-tree :loadTags="loadTags">
<template #prefix="{ data }"> <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"> <el-popover :show-after="500" placement="right-start" title="mongo实例信息" trigger="hover" :width="210">
<template #reference> <template #reference>
<SvgIcon name="iconfont icon-op-mongo" :size="18" /> <SvgIcon name="iconfont icon-op-mongo" :size="18" />
@@ -18,14 +18,18 @@
</el-popover> </el-popover>
</span> </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>
<template #label="{ data }"> <template #label="{ data }">
<span v-if="data.type == NodeType.Dbs"> <span v-if="data.type.value == MongoNodeType.Dbs">
{{ data.params.dbName }} {{ data.params.database }}
<span style="color: #8492a6; font-size: 13px"> [{{ formatByteSize(data.params.size) }}] </span> <span style="color: #8492a6; font-size: 13px"> [{{ formatByteSize(data.params.size) }}] </span>
</span> </span>
@@ -161,7 +165,7 @@ import { computed, defineAsyncComponent, reactive, ref, toRefs } from 'vue';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import { isTrue, notBlank } from '@/common/assert'; import { isTrue, notBlank } from '@/common/assert';
import { TagTreeNode } from '../component/tag'; import { TagTreeNode, NodeType } from '../component/tag';
import TagTree from '../component/TagTree.vue'; import TagTree from '../component/TagTree.vue';
import { formatByteSize } from '@/common/utils/format'; import { formatByteSize } from '@/common/utils/format';
@@ -175,13 +179,63 @@ const perms = {
/** /**
* 树节点类型 * 树节点类型
*/ */
class NodeType { class MongoNodeType {
static Mongo = 1; static Mongo = 1;
static Dbs = 2; static Dbs = 2;
static CollMenu = 3; static CollMenu = 3;
static Coll = 4; 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 findParamInputRef: any = ref(null);
const state = reactive({ const state = reactive({
tags: [], tags: [],
@@ -237,89 +291,16 @@ const getInsts = async () => {
}; };
/** /**
* 加载文件树节点 * 加载标签树树节点
* @param {Object} node
* @param {Object} resolve
*/ */
const loadNode = async (node: any) => { const loadTags = async () => {
// 一级为tagPath await getInsts();
if (node.level === 0) { const tagPaths = instMap.keys();
await getInsts(); const tagNodes = [];
const tagPaths = instMap.keys(); for (let tagPath of tagPaths) {
const tagNodes = []; tagNodes.push(new TagTreeNode(tagPath, tagPath, NodeTypeTagPath));
for (let tagPath of tagPaths) {
tagNodes.push(new TagTreeNode(tagPath, tagPath));
}
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);
} }
return tagNodes;
}; };
const changeCollection = async (id: any, schema: string, collection: string) => { const changeCollection = async (id: any, schema: string, collection: string) => {

View File

@@ -4,9 +4,9 @@
<el-col :span="4"> <el-col :span="4">
<el-row type="flex" justify="space-between"> <el-row type="flex" justify="space-between">
<el-col :span="24" class="flex-auto"> <el-col :span="24" class="flex-auto">
<tag-tree @node-click="nodeClick" :load="loadNode"> <tag-tree :loadTags="loadTags">
<template #prefix="{ data }"> <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"> <el-popover :show-after="500" placement="right-start" title="redis实例信息" trigger="hover" :width="210">
<template #reference> <template #reference>
<SvgIcon name="iconfont icon-op-redis" :size="18" /> <SvgIcon name="iconfont icon-op-redis" :size="18" />
@@ -22,7 +22,7 @@
</el-popover> </el-popover>
</span> </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> </template>
</tag-tree> </tag-tree>
</el-col> </el-col>
@@ -184,7 +184,7 @@ import { redisApi } from './api';
import { ref, defineAsyncComponent, toRefs, reactive, onMounted, nextTick } from 'vue'; import { ref, defineAsyncComponent, toRefs, reactive, onMounted, nextTick } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus'; import { ElMessage, ElMessageBox } from 'element-plus';
import { isTrue, notBlank, notNull } from '@/common/assert'; 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 TagTree from '../component/TagTree.vue';
import { keysToTree, sortByTreeNodes, keysToList } from './utils'; import { keysToTree, sortByTreeNodes, keysToList } from './utils';
@@ -193,11 +193,61 @@ const KeyDetail = defineAsyncComponent(() => import('./KeyDetail.vue'));
/** /**
* 树节点类型 * 树节点类型
*/ */
class NodeType { class RedisNodeType {
static Redis = 1; static Redis = 1;
static Db = 2; 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 = { const treeProps = {
label: 'name', label: 'name',
children: 'children', children: 'children',
@@ -270,80 +320,16 @@ const getInsts = async () => {
}; };
/** /**
* 加载文件树节点 * 加载标签树节点
* @param {Object} node
* @param {Object} resolve
*/ */
const loadNode = async (node: any) => { const loadTags = async () => {
// 一级为tagPath await getInsts();
if (node.level === 0) { const tagPaths = instMap.keys();
await getInsts(); const tagNodes = [];
const tagPaths = instMap.keys(); for (let tagPath of tagPaths) {
const tagNodes = []; tagNodes.push(new TagTreeNode(tagPath, tagPath, NodeTypeTagPath));
for (let tagPath of tagPaths) {
tagNodes.push(new TagTreeNode(tagPath, tagPath));
}
return tagNodes;
} }
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) => { const scan = async (appendKey = false) => {