feat: 代码小调整

This commit is contained in:
meilin.huang
2023-02-07 16:54:44 +08:00
parent fa0cb73ec9
commit 00fee24a85
8 changed files with 528 additions and 592 deletions

View File

@@ -0,0 +1,106 @@
<template>
<div class="instances-box layout-aside">
<el-row type="flex" justify="space-between">
<el-col :span="24"
:style="{ maxHeight: instanceMenuMaxHeight, height: instanceMenuMaxHeight, overflow: 'auto' }"
class="el-scrollbar flex-auto">
<el-menu background-color="transparent" :collapse-transition="false">
<!-- 第一级tag -->
<el-sub-menu v-for="tag of tags" :index="tag.tagPath" :key="tag.tagPath"
@click.stop="clickTag(tag.tagPath)">
<template #title>
<el-icon>
<FolderOpened v-if="opend[tag.tagPath]" color="#e6a23c" />
<Folder v-else />
</el-icon>
<span>{{ tag.tagPath }}</span>
</template>
<slot :tag="tag" name="submenu"></slot>
</el-sub-menu>
</el-menu>
</el-col>
</el-row>
</div>
</template>
<script lang="ts" setup>
import { reactive, toRefs } from 'vue';
const props = defineProps({
instanceMenuMaxHeight: {
type: [Number, String],
},
tags: {
type: Object, required: true
},
})
const state = reactive({
instanceMenuMaxHeight: props.instanceMenuMaxHeight,
tags: props.tags,
opend: {},
})
const {
opend,
} = toRefs(state)
const clickTag = (tagPath: string) => {
if (state.opend[tagPath] === undefined) {
state.opend[tagPath] = true;
return;
}
const opend = state.opend[tagPath]
state.opend[tagPath] = !opend
}
</script>
<style lang="scss">
.instances-box {
.el-menu {
width: 100%;
}
.el-sub-menu {
.checked {
.checked-schema {
color: var(--el-color-primary);
}
}
}
.el-sub-menu__title {
padding-left: 0 !important;
height: 30px !important;
line-height: 30px !important;
}
.el-menu--vertical:not(.el-menu--collapse):not(.el-menu--popup-container) .el-sub-menu__title {
padding-right: 10px;
}
.el-menu-item {
padding-left: 0 !important;
height: 20px !important;
line-height: 20px !important;
}
.el-icon {
margin: 0;
}
.el-sub-menu__icon-arrow {
top: inherit;
right: 10px;
}
}
.instances-pop-form {
.el-form-item {
margin-bottom: unset;
}
}
</style>

View File

@@ -219,7 +219,8 @@
layout="prev, pager, next, total, jumper" v-model:current-page="dt.pageNum" layout="prev, pager, next, total, jumper" v-model:current-page="dt.pageNum"
:page-size="defalutLimit"></el-pagination> :page-size="defalutLimit"></el-pagination>
</el-row> </el-row>
<div style=" font-size: 12px; padding: 0 10px; color: #606266"><span>{{ dt.sql }}</span></div> <div style=" font-size: 12px; padding: 0 10px; color: #606266"><span>{{ dt.sql }}</span>
</div>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</el-container> </el-container>
@@ -258,7 +259,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, nextTick, onMounted, reactive, watch } from 'vue'; import { computed, nextTick, onMounted, reactive } from 'vue';
import { dbApi } from './api'; import { dbApi } from './api';
import { format as sqlFormatter } from 'sql-formatter'; import { format as sqlFormatter } from 'sql-formatter';
@@ -1754,14 +1755,14 @@ const loadSchemaTables = async (inst: any, schema: string, fn: Function) => {
} else { } else {
tables.forEach((a: any) => a.show = true) tables.forEach((a: any) => a.show = true)
} }
fn(state.instances.tables[id+schema]) fn(state.instances.tables[id + schema])
} }
// 选择数据库实例 // 选择数据库实例
const changeInstance = (inst: any, fn?: Function) => { const changeInstance = (inst: any, fn?: Function) => {
state.dbId = inst.id state.dbId = inst.id
state.dbType = inst.type state.dbType = inst.type
fn && fn() fn && fn()
} }
// 选择数据库 // 选择数据库
const changeSchema = (inst: any, schema: string) => { const changeSchema = (inst: any, schema: string) => {

View File

@@ -1,127 +1,114 @@
<template> <template>
<div class="instances-box layout-aside"> <tag-menu :instanceMenuMaxHeight="instanceMenuMaxHeight" :tags="instances.tags">
<el-row type="flex" justify="space-between"> <template #submenu="props">
<el-col :span="24" :style="{maxHeight: instanceMenuMaxHeight,height: instanceMenuMaxHeight, overflow:'auto'}" class="el-scrollbar flex-auto"> <!-- 第二级数据库实例 -->
<el-menu background-color="transparent" ref="menuRef"> <el-sub-menu v-for="inst in instances.tree[props.tag.tagId]" :index="'instance-' + inst.id"
<!-- 第一级tag --> :key="'instance-' + inst.id" @click.stop="changeInstance(inst, () => { })">
<el-sub-menu v-for="tag of instances.tags" :index="tag.tagPath" :key="tag.tagPath"> <template #title>
<template #title> <el-popover placement="right-start" title="数据库实例信息" trigger="hover" :width="210">
<el-icon> <template #reference>
<FolderOpened color="#e6a23c" /> <span>&nbsp;&nbsp;<el-icon>
</el-icon> <MostlyCloudy color="#409eff" />
<span>{{ tag.tagPath }}</span> </el-icon>{{ inst.name }}</span>
</template> </template>
<!-- 第二级数据库实例 --> <template #default>
<el-sub-menu v-for="inst in instances.tree[tag.tagId]" :index="'instance-' + inst.id" <el-form class="instances-pop-form" label-width="55px" :size="'small'">
:key="'instance-' + inst.id" @click="changeInstance(inst, ()=>{})"> <el-form-item label="类型:">{{ inst.type }}</el-form-item>
<el-form-item label="链接:">{{ inst.host }}:{{ inst.port }}</el-form-item>
<el-form-item label="用户:">{{ inst.username }}</el-form-item>
<el-form-item v-if="inst.remark" label="备注:">{{
inst.remark
}}</el-form-item>
</el-form>
</template>
</el-popover>
</template>
<!-- 第三级数据库 -->
<el-sub-menu v-for="schema in instances.dbs[inst.id]" :index="inst.id + schema" :key="inst.id + schema"
:class="state.nowSchema === (inst.id + schema) && 'checked'"
@click.stop="changeSchema(inst, schema)">
<template #title>
&nbsp;&nbsp;&nbsp;&nbsp;<el-icon>
<Coin color="#67c23a" />
</el-icon>
<span class="checked-schema">{{ schema }}</span>
</template>
<!-- 第四级 01 -->
<el-sub-menu :index="inst.id + schema + '-table'">
<template #title>
<div style="width: 100%" @click="loadTableNames(inst, schema, () => { })">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<el-icon>
<Calendar color="#409eff" />
</el-icon>
<span></span>
<el-icon v-show="state.loading[inst.id + schema]" class="is-loading">
<Loading />
</el-icon>
</div>
</template>
<el-menu-item :index="inst.id + schema + '-tableSearch'"
:key="inst.id + schema + '-tableSearch'">
<template #title> <template #title>
<el-popover placement="right-start" title="数据库实例信息" trigger="hover" :width="210"> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<template #reference> <el-input size="small" placeholder="过滤表" clearable
<span>&nbsp;&nbsp;<el-icon> @change="filterTableName(inst.id, schema)"
<MostlyCloudy color="#409eff" /> @keyup="(e: any) => filterTableName(inst.id, schema, e)"
</el-icon>{{ inst.name }}</span> v-model="state.filterParam[inst.id + schema]" />
</template>
<template #default>
<el-form class="instances-pop-form" label-width="55px" :size="'small'">
<el-form-item label="类型:">{{ inst.type }}</el-form-item>
<el-form-item label="链接:">{{ inst.host }}:{{ inst.port }}</el-form-item>
<el-form-item label="用户:">{{ inst.username }}</el-form-item>
<el-form-item v-if="inst.remark" label="备注:">{{
inst.remark
}}</el-form-item>
</el-form>
</template>
</el-popover>
</template> </template>
<!-- 第三级数据库 --> </el-menu-item>
<el-sub-menu v-for="schema in instances.dbs[inst.id]" :index="inst.id + schema"
:key="inst.id + schema" :class="state.nowSchema === (inst.id + schema) && 'checked'" <template v-for="tb in instances.tables[inst.id + schema]">
@click="changeSchema(inst, schema)"> <el-menu-item :index="inst.id + schema + tb.tableName"
:key="inst.id + schema + tb.tableName" v-if="tb.show"
@click="loadTableData(inst, schema, tb.tableName)">
<template #title> <template #title>
&nbsp;&nbsp;&nbsp;&nbsp;<el-icon> <div style="width: 100%">
<Coin color="#67c23a" /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<el-icon>
</el-icon> <Calendar color="#409eff" />
<span class="checked-schema">{{ schema }}</span>
</template>
<!-- 第四级 01 -->
<el-sub-menu :index="inst.id + schema + '-table'">
<template #title>
<div style="width: 100%" @click="loadTableNames(inst, schema, ()=>{})">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<el-icon>
<Calendar color="#409eff"/>
</el-icon>
<span></span>
<el-icon v-show="state.loading[inst.id + schema]" class="is-loading">
<Loading />
</el-icon>
</div>
</template>
<el-menu-item :index="inst.id + schema + '-tableSearch'"
:key="inst.id + schema + '-tableSearch'">
<template #title>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<el-input size="small" placeholder="过滤表" clearable
@change="filterTableName(inst.id, schema)"
@keyup="(e: any) => filterTableName(inst.id, schema, e)"
v-model="state.filterParam[inst.id + schema]" />
</template>
</el-menu-item>
<template v-for="tb in instances.tables[inst.id + schema]">
<el-menu-item :index="inst.id + schema + tb.tableName"
:key="inst.id + schema + tb.tableName" v-if="tb.show"
@click="loadTableData(inst, schema, tb.tableName)">
<template #title>
<div style="width: 100%">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<el-icon>
<Calendar color="#409eff" />
</el-icon>
<el-tooltip v-if="tb.tableComment" effect="customized"
:content="tb.tableComment" placement="right">
{{ tb.tableName }}
</el-tooltip>
<span v-else>{{ tb.tableName }}</span>
</div>
</template>
</el-menu-item>
</template>
</el-sub-menu>
<!-- 第四级 02sql -->
<el-sub-menu :index="inst.id + schema + '-sql'">
<template #title>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<el-icon>
<List color="#f56c6c" />
</el-icon> </el-icon>
<span>sql</span> <el-tooltip v-if="tb.tableComment" effect="customized"
</template> :content="tb.tableComment" placement="right">
{{ tb.tableName }}
<template v-for="sql in instances.sqls[inst.id + schema]"> </el-tooltip>
<el-menu-item :index="inst.id + schema + sql.name" <span v-else>{{ tb.tableName }}</span>
:key="inst.id + schema + sql.name" v-if="sql.show" </div>
@click="loadSql(inst, schema, sql.name)"> </template>
<template #title> </el-menu-item>
<div style="width: 100%"> </template>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<el-icon>
<Calendar color="#409eff" />
</el-icon>
<span>{{ sql.name }}</span>
</div>
</template>
</el-menu-item>
</template>
</el-sub-menu>
</el-sub-menu>
</el-sub-menu>
</el-sub-menu> </el-sub-menu>
</el-menu> <!-- 第四级 02sql -->
</el-col> <el-sub-menu :index="inst.id + schema + '-sql'">
</el-row> <template #title>
</div> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<el-icon>
<List color="#f56c6c" />
</el-icon>
<span>sql</span>
</template>
<template v-for="sql in instances.sqls[inst.id + schema]">
<el-menu-item :index="inst.id + schema + sql.name" :key="inst.id + schema + sql.name"
v-if="sql.show" @click="loadSql(inst, schema, sql.name)">
<template #title>
<div style="width: 100%">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<el-icon>
<Calendar color="#409eff" />
</el-icon>
<span>{{ sql.name }}</span>
</div>
</template>
</el-menu-item>
</template>
</el-sub-menu>
</el-sub-menu>
</el-sub-menu>
</template>
</tag-menu>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {nextTick, onBeforeMount, onMounted, reactive, ref, Ref, watch} from 'vue'; import { nextTick, onBeforeMount, onMounted, reactive, ref, Ref, watch } from 'vue';
import {store} from '@/store'; import { store } from '@/store';
import TagMenu from '../../component/TagMenu.vue';
const props = defineProps({ const props = defineProps({
instanceMenuMaxHeight: { instanceMenuMaxHeight: {
@@ -158,8 +145,8 @@ const initLoadInstances = () => {
* @param inst 选中的实例对象 * @param inst 选中的实例对象
* @param fn 选中的实例对象后的回调函数 * @param fn 选中的实例对象后的回调函数
*/ */
const changeInstance = (inst : any, fn: Function) => { const changeInstance = (inst: any, fn: Function) => {
emits('changeInstance', inst, fn) emits('changeInstance', inst, fn)
} }
/** /**
* 改变选中的数据库schema * 改变选中的数据库schema
@@ -178,9 +165,9 @@ const changeSchema = (inst: any, schema: string) => {
*/ */
const loadTableNames = async (inst: any, schema: string, fn: Function) => { const loadTableNames = async (inst: any, schema: string, fn: Function) => {
state.loading[inst.id + schema] = true state.loading[inst.id + schema] = true
await emits('loadTableNames', inst, schema, (res: any[])=>{ await emits('loadTableNames', inst, schema, (res: any[]) => {
state.loading[inst.id + schema] = false state.loading[inst.id + schema] = false
fn && fn(res) fn && fn(res)
}) })
} }
/** /**
@@ -206,81 +193,41 @@ const filterTableName = (instId: number, schema: string, event?: any) => {
} }
const selectDb = async (val?: any) => { const selectDb = async (val?: any) => {
let info = val || store.state.sqlExecInfo.dbOptInfo; let info = val || store.state.sqlExecInfo.dbOptInfo;
if (info && info.dbId) { if (info && info.dbId) {
const {tagPath, dbId, db} = info const { tagPath, dbId, db } = info
menuRef.value.open(tagPath); menuRef.value.open(tagPath);
menuRef.value.open('instance-' + dbId); menuRef.value.open('instance-' + dbId);
await changeInstance({id: dbId}, () => { await changeInstance({ id: dbId }, () => {
// 加载数据库 // 加载数据库
nextTick(async () => { nextTick(async () => {
menuRef.value.open(dbId + db) menuRef.value.open(dbId + db)
state.nowSchema = (dbId+db) state.nowSchema = (dbId + db)
// 加载集合列表 // 加载集合列表
await nextTick(async () => { await nextTick(async () => {
await loadTableNames({id: dbId}, db, (res: any[]) => { await loadTableNames({ id: dbId }, db, (res: any[]) => {
// 展开集合列表 // 展开集合列表
menuRef.value.open(dbId + db + '-table') menuRef.value.open(dbId + db + '-table')
console.log(res) console.log(res)
}) })
})
})
}) })
}) }
})
}
} }
onMounted(()=>{ onMounted(() => {
selectDb(); selectDb();
}) })
watch(()=>store.state.sqlExecInfo.dbOptInfo, async newValue => { watch(() => store.state.sqlExecInfo.dbOptInfo, async newValue => {
await selectDb(newValue) await selectDb(newValue)
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
.instances-box {
.el-menu {
width: 100%;
}
.el-sub-menu {
.checked {
.checked-schema {
color: var(--el-color-primary);
}
}
}
.el-sub-menu__title {
padding-left: 0 !important;
height: 30px !important;
line-height: 30px !important;
}
.el-menu--vertical:not(.el-menu--collapse):not(.el-menu--popup-container) .el-sub-menu__title {
padding-right: 10px;
}
.el-menu-item {
padding-left: 0 !important;
height: 20px !important;
line-height: 20px !important;
}
.el-icon {
margin: 0;
}
.el-sub-menu__icon-arrow {
top: inherit;
right: 10px;
}
}
.instances-pop-form { .instances-pop-form {
.el-form-item { .el-form-item {
margin-bottom: unset; margin-bottom: unset;

View File

@@ -1,109 +1,88 @@
<template> <template>
<div class="instances-box layout-aside"> <tag-menu :instanceMenuMaxHeight="state.instanceMenuMaxHeight" :tags="instances.tags">
<el-row type="flex" justify="space-between"> <template #submenu="props">
<el-col :span="24" :style="{ <el-sub-menu v-for="inst in instances.tree[props.tag.tagId]" :index="'mongo-instance-' + inst.id"
maxHeight: state.instanceMenuMaxHeight, :key="'mongo-instance-' + inst.id" @click.stop="changeInstance(inst, () => { })">
height: state.instanceMenuMaxHeight, <template #title>
overflow: 'auto' <el-popover placement="right-start" title="mongo数据库实例信息" trigger="hover" :width="210">
}" class="el-scrollbar flex-auto"> <template #reference>
<span>&nbsp;&nbsp;<el-icon>
<el-menu background-color="transparent" ref="menuRef"> <MostlyCloudy color="#409eff" />
<!-- 第一级tag --> </el-icon>{{ inst.name }}</span>
<el-sub-menu v-for="tag of instances.tags" :index="tag.tagPath" :key="tag.tagPath">
<template #title>
<el-icon>
<FolderOpened color="#e6a23c" />
</el-icon>
<span>{{ tag.tagPath }}</span>
</template> </template>
<!-- 第二级数据库实例 --> <template #default>
<el-sub-menu v-for="inst in instances.tree[tag.tagId]" <el-form class="instances-pop-form" label-width="55px" :size="'small'">
:index="'mongo-instance-' + inst.id" <el-form-item label="名称:">{{ inst.name }}</el-form-item>
:key="'mongo-instance-' + inst.id" <el-form-item label="链接:">{{ inst.uri }}</el-form-item>
@click.prevent="changeInstance(inst, ()=>{})" </el-form>
> </template>
</el-popover>
</template>
<!-- 第三级数据库 -->
<el-sub-menu v-for="db in instances.dbs[inst.id]" :index="inst.id + db.Name" :key="inst.id + db.Name"
:class="state.nowSchema === (inst.id + db.Name) && 'checked'"
@click.stop="changeSchema(inst, db.Name)">
<template #title>
&nbsp;&nbsp;&nbsp;&nbsp;<el-icon>
<Coin color="#67c23a" />
</el-icon>
<span class="checked-schema">
{{ db.Name }}
<span style="color: #8492a6;font-size: 13px">[{{
formatByteSize(db.SizeOnDisk)
}}]</span>
</span>
</template>
<!-- 第四级 01 -->
<el-sub-menu :index="inst.id + db.Name + '-table'">
<template #title>
<div style="width: 100%" @click="loadTableNames(inst, db.Name, () => { })">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<el-icon>
<Calendar color="#409eff" />
</el-icon>
<span>集合</span>
<el-icon v-show="state.loading[inst.id + db.Name]" class="is-loading">
<Loading />
</el-icon>
</div>
</template>
<el-menu-item :index="inst.id + db.Name + '-tableSearch'"
:key="inst.id + db.Name + '-tableSearch'">
<template #title> <template #title>
<el-popover placement="right-start" title="mongo数据库实例信息" trigger="hover" :width="210"> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<template #reference> <el-input size="small" placeholder="过滤" clearable
<span>&nbsp;&nbsp;<el-icon> @change="filterTableName(inst.id, db.Name)"
<MostlyCloudy color="#409eff" /> @keyup="(e: any) => filterTableName(inst.id, db.Name, e)"
</el-icon>{{ inst.name }}</span> v-model="state.filterParam[inst.id + db.Name]" />
</template>
<template #default>
<el-form class="instances-pop-form" label-width="55px" :size="'small'">
<el-form-item label="名称:">{{ inst.name }}</el-form-item>
<el-form-item label="链接:">{{ inst.uri }}</el-form-item>
</el-form>
</template>
</el-popover>
</template> </template>
<!-- 第三级数据库 --> </el-menu-item>
<el-sub-menu v-for="db in instances.dbs[inst.id]" :index="inst.id + db.Name"
:key="inst.id + db.Name" :class="state.nowSchema === (inst.id + db.Name) && 'checked'"
@click.prevent="changeSchema(inst, db.Name)">
<template #title>
&nbsp;&nbsp;&nbsp;&nbsp;<el-icon>
<Coin color="#67c23a" />
</el-icon>
<span class="checked-schema">
{{ db.Name }}
<span style="color: #8492a6;font-size: 13px">[{{
formatByteSize(db.SizeOnDisk)
}}]</span>
</span>
</template>
<!-- 第四级 01 -->
<el-sub-menu :index="inst.id + db.Name + '-table'">
<template #title>
<div style="width: 100%" @click="loadTableNames(inst, db.Name, ()=>{})">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<el-icon>
<Calendar color="#409eff"/>
</el-icon>
<span>集合</span>
<el-icon v-show="state.loading[inst.id + db.Name]" class="is-loading">
<Loading />
</el-icon>
</div>
</template>
<el-menu-item :index="inst.id + db.Name + '-tableSearch'"
:key="inst.id + db.Name + '-tableSearch'">
<template #title>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<el-input size="small" placeholder="过滤" clearable
@change="filterTableName(inst.id, db.Name)"
@keyup="(e: any) => filterTableName(inst.id, db.Name, e)"
v-model="state.filterParam[inst.id + db.Name]" />
</template>
</el-menu-item>
<template v-for="tb in instances.tables[inst.id + db.Name]"> <template v-for="tb in instances.tables[inst.id + db.Name]">
<el-menu-item :index="inst.id + db.Name + tb.tableName" <el-menu-item :index="inst.id + db.Name + tb.tableName"
:key="inst.id + db.Name + tb.tableName" v-if="tb.show" :key="inst.id + db.Name + tb.tableName" v-if="tb.show"
@click="loadTableData(inst, db.Name, tb.tableName)"> @click="loadTableData(inst, db.Name, tb.tableName)">
<template #title> <template #title>
<div style="width: 100%"> <div style="width: 100%">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<el-icon> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<el-icon>
<Calendar color="#409eff" /> <Calendar color="#409eff" />
</el-icon> </el-icon>
<span :title="tb.tableComment || ''">{{ tb.tableName }}</span> <span :title="tb.tableComment || ''">{{ tb.tableName }}</span>
</div> </div>
</template> </template>
</el-menu-item> </el-menu-item>
</template> </template>
</el-sub-menu>
</el-sub-menu>
</el-sub-menu>
</el-sub-menu> </el-sub-menu>
</el-menu> </el-sub-menu>
</el-col> </el-sub-menu>
</el-row> </template>
</div> </tag-menu>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {nextTick, onBeforeMount, onMounted, reactive, ref, Ref, watch} from 'vue'; import { nextTick, onBeforeMount, onMounted, reactive, ref, Ref, watch } from 'vue';
import { formatByteSize } from '@/common/utils/format'; import { formatByteSize } from '@/common/utils/format';
import {store} from '@/store'; import { store } from '@/store';
import TagMenu from '../component/TagMenu.vue';
const props = defineProps({ const props = defineProps({
instances: { instances: {
@@ -119,7 +98,7 @@ onBeforeMount(async () => {
}) })
const setHeight = () => { const setHeight = () => {
state.instanceMenuMaxHeight = window.innerHeight - 140 + 'px'; state.instanceMenuMaxHeight = window.innerHeight - 115 + 'px';
} }
const menuRef = ref(null) as Ref const menuRef = ref(null) as Ref
@@ -144,8 +123,8 @@ const initLoadInstances = () => {
* @param inst 选中的实例对象 * @param inst 选中的实例对象
* @param fn 选中的实例对象后的回调事件 * @param fn 选中的实例对象后的回调事件
*/ */
const changeInstance = (inst : any, fn: Function) => { const changeInstance = (inst: any, fn: Function) => {
emits('changeInstance', inst, fn) emits('changeInstance', inst, fn)
} }
/** /**
* 改变选中的数据库schema * 改变选中的数据库schema
@@ -164,7 +143,7 @@ const changeSchema = (inst: any, schema: string) => {
*/ */
const loadTableNames = async (inst: any, schema: string, fn: Function) => { const loadTableNames = async (inst: any, schema: string, fn: Function) => {
state.loading[inst.id + schema] = true state.loading[inst.id + schema] = true
await emits('loadTableNames', inst, schema, (res: any)=>{ await emits('loadTableNames', inst, schema, (res: any) => {
state.loading[inst.id + schema] = false state.loading[inst.id + schema] = false
fn && fn(res) fn && fn(res)
}) })
@@ -192,80 +171,40 @@ const filterTableName = (instId: number, schema: string, event?: any) => {
} }
const selectDb = async (val?: any) => { const selectDb = async (val?: any) => {
let info = val || store.state.mongoDbOptInfo.dbOptInfo; let info = val || store.state.mongoDbOptInfo.dbOptInfo;
if (info && info.dbId) { if (info && info.dbId) {
const {tagPath, dbId, db} = info const { tagPath, dbId, db } = info
menuRef.value.open(tagPath); menuRef.value.open(tagPath);
menuRef.value.open('mongo-instance-' + dbId); menuRef.value.open('mongo-instance-' + dbId);
await changeInstance({id: dbId}, () => { await changeInstance({ id: dbId }, () => {
// 加载数据库 // 加载数据库
nextTick(async () => { nextTick(async () => {
menuRef.value.open(dbId + db) menuRef.value.open(dbId + db)
// 加载集合列表 // 加载集合列表
await nextTick(async () => { await nextTick(async () => {
await loadTableNames({id: dbId}, db, (res: any[]) => { await loadTableNames({ id: dbId }, db, (res: any[]) => {
// 展开集合列表 // 展开集合列表
menuRef.value.open(dbId + db + '-table') menuRef.value.open(dbId + db + '-table')
// 加载第一张集合数据 // 加载第一张集合数据
loadTableData({id: dbId}, db, res[0].tableName) loadTableData({ id: dbId }, db, res[0].tableName)
}) })
})
})
}) })
}) }
})
}
} }
onMounted(()=>{ onMounted(() => {
selectDb(); selectDb();
}) })
watch(()=>store.state.mongoDbOptInfo.dbOptInfo, async newValue => { watch(() => store.state.mongoDbOptInfo.dbOptInfo, async newValue => {
await selectDb(newValue) await selectDb(newValue)
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
.instances-box {
.el-menu {
width: 100%;
}
.el-sub-menu {
.checked {
.checked-schema {
color: var(--el-color-primary);
}
}
}
.el-sub-menu__title {
padding-left: 0 !important;
height: 30px !important;
line-height: 30px !important;
}
.el-menu--vertical:not(.el-menu--collapse):not(.el-menu--popup-container) .el-sub-menu__title {
padding-right: 10px;
}
.el-menu-item {
padding-left: 0 !important;
height: 20px !important;
line-height: 20px !important;
}
.el-icon {
margin: 0;
}
.el-sub-menu__icon-arrow {
top: inherit;
right: 10px;
}
}
.instances-pop-form { .instances-pop-form {
.el-form-item { .el-form-item {
margin-bottom: unset; margin-bottom: unset;

View File

@@ -1,72 +1,66 @@
<template> <template>
<div> <div>
<el-card> <el-row>
<el-row> <el-col :span="4">
<el-col :span="3"> <redis-instance-tree @init-load-instances="initLoadInstances" @change-instance="changeInstance"
<redis-instance-tree @change-schema="loadInitSchema" :instances="state.instances" />
@init-load-instances="initLoadInstances"
@change-instance="changeInstance"
@change-schema="loadInitSchema"
:instances="state.instances"
/>
</el-col> </el-col>
<el-col :span="19" style="border-left: 1px solid var(--el-card-border-color)"> <el-col :span="20" style="border-left: 1px solid var(--el-card-border-color);">
<el-col class="mt10"> <el-col class="mt10">
<el-form class="search-form" label-position="right" :inline="true" label-width="60px"> <el-form class="search-form" label-position="right" :inline="true" label-width="60px">
<el-form-item label="key" label-width="40px"> <el-form-item label="key" label-width="40px">
<el-input placeholder="match 支持*模糊key" style="width: 250px" v-model="scanParam.match" <el-input placeholder="match 支持*模糊key" style="width: 250px" v-model="scanParam.match"
@clear="clear()" clearable></el-input> @clear="clear()" clearable></el-input>
</el-form-item> </el-form-item>
<el-form-item label="count" label-width="40px"> <el-form-item label="count" label-width="40px">
<el-input placeholder="count" style="width: 70px" v-model.number="scanParam.count"> <el-input placeholder="count" style="width: 70px" v-model.number="scanParam.count">
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button @click="searchKey()" type="success" icon="search" plain></el-button> <el-button @click="searchKey()" type="success" icon="search" plain></el-button>
<el-button @click="scan()" icon="bottom" plain>scan</el-button> <el-button @click="scan()" icon="bottom" plain>scan</el-button>
<el-popover placement="right" :width="200" trigger="click"> <el-popover placement="right" :width="200" trigger="click">
<template #reference> <template #reference>
<el-button type="primary" icon="plus" plain></el-button> <el-button type="primary" icon="plus" plain></el-button>
</template> </template>
<el-tag @click="onAddData('string')" :color="getTypeColor('string')" <el-tag @click="onAddData('string')" :color="getTypeColor('string')"
style="cursor: pointer">string</el-tag> style="cursor: pointer">string</el-tag>
<el-tag @click="onAddData('hash')" :color="getTypeColor('hash')" class="ml5" <el-tag @click="onAddData('hash')" :color="getTypeColor('hash')" class="ml5"
style="cursor: pointer">hash</el-tag> style="cursor: pointer">hash</el-tag>
<el-tag @click="onAddData('set')" :color="getTypeColor('set')" class="ml5" <el-tag @click="onAddData('set')" :color="getTypeColor('set')" class="ml5"
style="cursor: pointer">set</el-tag> style="cursor: pointer">set</el-tag>
<!-- <el-tag @click="onAddData('list')" :color="getTypeColor('list')" class="ml5" style="cursor: pointer">list</el-tag> --> <!-- <el-tag @click="onAddData('list')" :color="getTypeColor('list')" class="ml5" style="cursor: pointer">list</el-tag> -->
</el-popover> </el-popover>
</el-form-item> </el-form-item>
<div style="float: right"> <div style="float: right">
<span>keys: {{ state.dbsize }}</span> <span>keys: {{ state.dbsize }}</span>
</div> </div>
</el-form> </el-form>
</el-col> </el-col>
<el-table v-loading="state.loading" :data="state.keys" stripe :highlight-current-row="true" style="cursor: pointer"> <el-table v-loading="state.loading" :data="state.keys" stripe :highlight-current-row="true"
<el-table-column show-overflow-tooltip prop="key" label="key"></el-table-column> style="cursor: pointer">
<el-table-column prop="type" label="type" width="80"> <el-table-column show-overflow-tooltip prop="key" label="key"></el-table-column>
<template #default="scope"> <el-table-column prop="type" label="type" width="80">
<el-tag :color="getTypeColor(scope.row.type)" size="small">{{ scope.row.type }}</el-tag> <template #default="scope">
</template> <el-tag :color="getTypeColor(scope.row.type)" size="small">{{ scope.row.type }}</el-tag>
</el-table-column> </template>
<el-table-column prop="ttl" label="ttl(过期时间)" width="140"> </el-table-column>
<template #default="scope"> <el-table-column prop="ttl" label="ttl(过期时间)" width="140">
{{ ttlConveter(scope.row.ttl) }} <template #default="scope">
</template> {{ ttlConveter(scope.row.ttl) }}
</el-table-column> </template>
<el-table-column label="操作"> </el-table-column>
<template #default="scope"> <el-table-column label="操作">
<el-button @click="getValue(scope.row)" type="success" icon="search" plain size="small">查看 <template #default="scope">
</el-button> <el-button @click="getValue(scope.row)" type="success" icon="search" plain size="small">查看
<el-button @click="del(scope.row.key)" type="danger" icon="delete" plain size="small">删除 </el-button>
</el-button> <el-button @click="del(scope.row.key)" type="danger" icon="delete" plain size="small">删除
</template> </el-button>
</el-table-column> </template>
</el-table> </el-table-column>
</el-table>
</el-col> </el-col>
</el-row> </el-row>
</el-card>
<div style="text-align: center; margin-top: 10px"></div> <div style="text-align: center; margin-top: 10px"></div>
@@ -142,7 +136,7 @@ const state = reactive({
}, },
keys: [], keys: [],
dbsize: 0, dbsize: 0,
instances:{tags:{}, tree:{}, dbs:{}, tables:{}} instances: { tags: {}, tree: {}, dbs: {}, tables: {} }
}); });
const { const {
@@ -257,16 +251,16 @@ const del = (key: string) => {
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning', type: 'warning',
}).then(() => { }).then(() => {
redisApi.delKey redisApi.delKey
.request({ .request({
key, key,
id: state.scanParam.id, id: state.scanParam.id,
db: state.scanParam.db, db: state.scanParam.db,
}) })
.then(() => { .then(() => {
ElMessage.success('删除成功!'); ElMessage.success('删除成功!');
searchKey(); searchKey();
}); });
}).catch(() => { }); }).catch(() => { });
}; };
@@ -319,43 +313,43 @@ const getTypeColor = (type: string) => {
}; };
const initLoadInstances = async ()=>{ const initLoadInstances = async () => {
const res = await redisApi.redisList.request({}); const res = await redisApi.redisList.request({});
if(!res.total) return if (!res.total) return
state.instances = {tags:{}, tree:{}, dbs:{}, tables:{}} ; // 初始化变量 state.instances = { tags: {}, tree: {}, dbs: {}, tables: {} }; // 初始化变量
for (const db of res.list) { for (const db of res.list) {
let arr = state.instances.tree[db.tagId] || [] let arr = state.instances.tree[db.tagId] || []
const {tagId, tagPath} = db const { tagId, tagPath } = db
// tags // tags
state.instances.tags[db.tagId]={tagId, tagPath} state.instances.tags[db.tagId] = { tagId, tagPath }
// 实例 // 实例
arr.push(db) arr.push(db)
state.instances.tree[db.tagId] = arr; state.instances.tree[db.tagId] = arr;
}
}
} }
const changeInstance = async (inst: any, fn: Function) => { const changeInstance = async (inst: any, fn: Function) => {
let dbs = state.instances.dbs[inst.id] || [] let dbs = inst.db.split(',').map((x: string) => {
if(dbs.length <=0 ){ return { name: `db${x}`, keys: 0 }
const res = await redisApi.redisInfo.request({ id: inst.id, host:inst.host }); })
for (let db in res.Keyspace){ const res = await redisApi.redisInfo.request({ id: inst.id, host: inst.host, section: "Keyspace" });
dbs.push({ for (let db in res.Keyspace) {
name: db, for (let d of dbs) {
keys: res.Keyspace[db]?.split(',')[0]?.split('=')[1] || 0 if (db == d.name) {
}) d.keys = res.Keyspace[db]?.split(',')[0]?.split('=')[1] || 0
}
}
} }
}
state.instances.dbs[inst.id] = dbs state.instances.dbs[inst.id] = dbs
fn && fn(dbs) fn && fn(dbs)
} }
/** 初始化加载db数据 */ /** 初始化加载db数据 */
const loadInitSchema = (inst: any, schema: string)=>{ const loadInitSchema = (inst: any, schema: string) => {
state.scanParam.id = inst.id state.scanParam.id = inst.id
state.scanParam.db = schema.replace('db','') state.scanParam.db = schema.replace('db', '')
scan() scan()
} }
</script> </script>

View File

@@ -1,100 +1,78 @@
<template> <template>
<div class="instances-box layout-aside"> <tag-menu :instanceMenuMaxHeight="state.instanceMenuMaxHeight" :tags="instances.tags">
<el-row type="flex" justify="space-between"> <template #submenu="props">
<el-col :span="24" :style="{ <el-sub-menu v-for="inst in instances.tree[props.tag.tagId]" :index="'redis-instance-' + inst.id"
maxHeight: state.instanceMenuMaxHeight, :key="'redis-instance-' + inst.id" @click.stop="changeInstance(inst)">
height: state.instanceMenuMaxHeight,
overflow:'auto'
}" class="el-scrollbar flex-auto">
<el-menu background-color="transparent" ref="menuRef">
<!-- 第一级tag -->
<el-sub-menu v-for="tag of instances.tags" :index="tag.tagPath" :key="tag.tagPath">
<template #title>
<el-icon><FolderOpened color="#e6a23c"/></el-icon>
<span>{{ tag.tagPath }}</span>
</template>
<!-- 第二级数据库实例 -->
<el-sub-menu v-for="inst in instances.tree[tag.tagId]"
:index="'redis-instance-' + inst.id"
:key="'redis-instance-' + inst.id"
@click.prevent="changeInstance(inst)"
>
<template #title>
<el-popover
placement="right-start"
title="mongo数据库实例信息"
trigger="hover"
:width="210"
>
<template #reference>
<span>&nbsp;&nbsp;<el-icon><MostlyCloudy color="#409eff"/></el-icon>{{ inst.name }}</span>
</template>
<template #default>
<el-form class="instances-pop-form" label-width="55px" :size="'small'">
<el-form-item label="名称:">{{inst.name}}</el-form-item>
<el-form-item label="链接:">{{inst.host}}</el-form-item>
</el-form>
</template>
</el-popover>
</template>
<!-- 第三级数据库 -->
<el-sub-menu v-for="db in instances.dbs[inst.id]"
:index="inst.id + db.name"
:key="inst.id + db.name"
:class="state.nowSchema === (inst.id+db.name) && 'checked'"
@click.prevent="changeSchema(inst, db.name)"
>
<template #title> <template #title>
&nbsp;&nbsp;&nbsp;&nbsp;<el-icon><Coin color="#67c23a"/></el-icon> <el-popover placement="right-start" title="redis实例信息" trigger="hover" :width="210">
<span class="checked-schema"> <template #reference>
{{ db.name }} [{{db.keys}}] <span>&nbsp;&nbsp;<el-icon>
</span> <MostlyCloudy color="#409eff" />
</el-icon>{{ inst.name }}</span>
</template>
<template #default>
<el-form class="instances-pop-form" label-width="55px" :size="'small'">
<el-form-item label="名称:">{{ inst.name }}</el-form-item>
<el-form-item label="链接:">{{ inst.host }}</el-form-item>
<el-form-item label="备注:">{{ inst.remark }}</el-form-item>
</el-form>
</template>
</el-popover>
</template> </template>
</el-sub-menu> <!-- 第三级数据库 -->
<el-menu-item v-for="db in instances.dbs[inst.id]" :index="inst.id + db.name" :key="inst.id + db.name"
:class="state.nowSchema === (inst.id + db.name) && 'checked'"
@click="changeSchema(inst, db.name)">
<template #title>
&nbsp;&nbsp;&nbsp;&nbsp;<el-icon>
<Coin color="#67c23a" />
</el-icon>
<span class="checked-schema">
{{ db.name }} [{{ db.keys }}]
</span>
</template>
</el-menu-item>
</el-sub-menu> </el-sub-menu>
</el-sub-menu> </template>
</el-menu> </tag-menu>
</el-col>
</el-row>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {onBeforeMount, onMounted, reactive, Ref, ref, watch} from 'vue'; import { onBeforeMount, onMounted, reactive, Ref, ref, watch } from 'vue';
import {store} from '@/store'; import { store } from '@/store';
import TagMenu from '../component/TagMenu.vue';
defineProps({ defineProps({
instances: { instances: {
type: Object, required: true type: Object, required: true
}, },
}) })
const emits = defineEmits(['initLoadInstances','changeInstance','changeSchema']) const emits = defineEmits(['initLoadInstances', 'changeInstance', 'changeSchema'])
onBeforeMount(async ()=>{ onBeforeMount(async () => {
await initLoadInstances() await initLoadInstances()
setHeight() setHeight()
}) })
const setHeight = () => { const setHeight = () => {
state.instanceMenuMaxHeight = window.innerHeight - 140 + 'px'; state.instanceMenuMaxHeight = window.innerHeight - 115 + 'px';
} }
const menuRef = ref(null) as Ref; const menuRef = ref(null) as Ref;
const state = reactive({ const state = reactive({
instanceMenuMaxHeight: '800px', instanceMenuMaxHeight: '800px',
nowSchema: '', nowSchema: '',
filterParam: {}, filterParam: {},
loading: {}, loading: {},
}) })
/** /**
* 初始化加载实例数据 * 初始化加载实例数据
*/ */
const initLoadInstances = () => { const initLoadInstances = () => {
emits('initLoadInstances') emits('initLoadInstances')
} }
/** /**
@@ -102,78 +80,45 @@ const initLoadInstances = () => {
* @param inst 选中的实例对象 * @param inst 选中的实例对象
* @param fn 选中的实例后的回调函数 * @param fn 选中的实例后的回调函数
*/ */
const changeInstance = (inst : any, fn?:Function) => { const changeInstance = (inst: any, fn?: Function) => {
emits('changeInstance', inst, fn) emits('changeInstance', inst, fn)
} }
/** /**
* 改变选中的数据库schema * 改变选中的数据库schema
* @param inst 选中的实例对象 * @param inst 选中的实例对象
* @param schema 选中的数据库schema * @param schema 选中的数据库schema
*/ */
const changeSchema = (inst : any, schema: string) => { const changeSchema = (inst: any, schema: string) => {
state.nowSchema = inst.id + schema state.nowSchema = inst.id + schema
emits('changeSchema', inst, schema) emits('changeSchema', inst, schema)
} }
const selectDb = async (val?: any) => { const selectDb = async (val?: any) => {
const info = val || store.state.redisDbOptInfo.dbOptInfo const info = val || store.state.redisDbOptInfo.dbOptInfo
if (info && info.dbId) { if (info && info.dbId) {
const {tagPath, dbId} = info const { tagPath, dbId } = info
menuRef.value.open(tagPath); menuRef.value.open(tagPath);
menuRef.value.open('redis-instance-' + dbId); menuRef.value.open('redis-instance-' + dbId);
await changeInstance({id: dbId}, async (dbs: any[]) => { await changeInstance({ id: dbId }, async (dbs: any[]) => {
await changeSchema({id: dbId}, dbs[0]?.name) await changeSchema({ id: dbId }, dbs[0]?.name)
}) })
} }
} }
onMounted(()=>{ onMounted(() => {
selectDb(); selectDb();
}) })
watch(()=>store.state.redisDbOptInfo.dbOptInfo, async newValue =>{ watch(() => store.state.redisDbOptInfo.dbOptInfo, async newValue => {
await selectDb(newValue) await selectDb(newValue)
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
.instances-box { .instances-pop-form {
.el-menu{ .el-form-item {
width: 275px; margin-bottom: unset;
}
.el-sub-menu{
.checked{
.checked-schema{
color: var(--el-color-primary);
}
} }
}
.el-sub-menu__title{
padding-left: 0 !important;
height: 30px !important;
line-height: 30px !important;
}
.el-menu--vertical:not(.el-menu--collapse):not(.el-menu--popup-container) .el-sub-menu__title{
padding-right: 10px;
}
.el-menu-item{
padding-left: 0 !important;
height: 20px !important;
line-height: 20px !important;
}
.el-icon{
margin: 0;
}
.el-sub-menu__icon-arrow{
top:inherit;
right: 10px;
}
}
.instances-pop-form{
.el-form-item{
margin-bottom: unset;
}
} }
</style> </style>

View File

@@ -76,36 +76,42 @@ func (r *Redis) RedisInfo(rc *req.Ctx) {
g := rc.GinCtx g := rc.GinCtx
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), 0) ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), 0)
var res string section := rc.GinCtx.Query("section")
var err error
mode := ri.Info.Mode mode := ri.Info.Mode
ctx := context.Background() ctx := context.Background()
var redisCli *redis.Client
if mode == "" || mode == entity.RedisModeStandalone || mode == entity.RedisModeSentinel { if mode == "" || mode == entity.RedisModeStandalone || mode == entity.RedisModeSentinel {
res, err = ri.Cli.Info(ctx).Result() redisCli = ri.Cli
} else if mode == entity.RedisModeCluster { } else if mode == entity.RedisModeCluster {
host := rc.GinCtx.Query("host") host := rc.GinCtx.Query("host")
biz.NotEmpty(host, "集群模式host信息不能为空") biz.NotEmpty(host, "集群模式host信息不能为空")
clusterClient := ri.ClusterCli clusterClient := ri.ClusterCli
var redisClient *redis.Client
// 遍历集群的master节点找到该redis client // 遍历集群的master节点找到该redis client
clusterClient.ForEachMaster(ctx, func(ctx context.Context, client *redis.Client) error { clusterClient.ForEachMaster(ctx, func(ctx context.Context, client *redis.Client) error {
if host == client.Options().Addr { if host == client.Options().Addr {
redisClient = client redisCli = client
} }
return nil return nil
}) })
if redisClient == nil { if redisCli == nil {
// 遍历集群的slave节点找到该redis client // 遍历集群的slave节点找到该redis client
clusterClient.ForEachSlave(ctx, func(ctx context.Context, client *redis.Client) error { clusterClient.ForEachSlave(ctx, func(ctx context.Context, client *redis.Client) error {
if host == client.Options().Addr { if host == client.Options().Addr {
redisClient = client redisCli = client
} }
return nil return nil
}) })
} }
biz.NotNil(redisClient, "该实例不在该集群中") biz.NotNil(redisCli, "该实例不在该集群中")
res, err = redisClient.Info(ctx).Result() }
var res string
var err error
if section == "" {
res, err = ri.Cli.Info(ctx).Result()
} else {
res, err = ri.Cli.Info(ctx, section).Result()
} }
biz.ErrIsNilAppendErr(err, "获取redis info失败: %s") biz.ErrIsNilAppendErr(err, "获取redis info失败: %s")

View File

@@ -7,8 +7,6 @@ type Redis struct {
Name *string `json:"name"` Name *string `json:"name"`
Host *string `json:"host"` Host *string `json:"host"`
Db string `json:"db"` Db string `json:"db"`
ProjectId *int64 `json:"projectId"`
Project *string `json:"project"`
Mode *string `json:"mode"` Mode *string `json:"mode"`
EnableSshTunnel *int8 `json:"enableSshTunnel"` // 是否启用ssh隧道 EnableSshTunnel *int8 `json:"enableSshTunnel"` // 是否启用ssh隧道
SshTunnelMachineId *uint64 `json:"sshTunnelMachineId"` // ssh隧道机器id SshTunnelMachineId *uint64 `json:"sshTunnelMachineId"` // ssh隧道机器id