Files
mayfly-go/mayfly_go_web/src/views/ops/db/component/InstanceTree.vue

346 lines
13 KiB
Vue
Raw Normal View History

2023-01-31 10:37:40 +08:00
<template>
<tag-menu :instanceMenuMaxHeight="instanceMenuMaxHeight" :tags="tags" ref="menuRef">
2023-02-07 16:54:44 +08:00
<template #submenu="props">
<!-- 第二级数据库实例 -->
<el-sub-menu v-for="inst in tree[props.tag.tagId]" :index="'instance-' + inst.id"
2023-02-07 16:54:44 +08:00
:key="'instance-' + inst.id" @click.stop="changeInstance(inst, () => { })">
<template #title>
<el-popover placement="right-start" title="数据库实例信息" trigger="hover" :width="210">
<template #reference>
<span class="ml10">
<el-icon>
2023-02-07 16:54:44 +08:00
<MostlyCloudy color="#409eff" />
</el-icon>{{ inst.name }}
</span>
2023-02-07 16:54:44 +08:00
</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>
2023-02-07 16:54:44 +08:00
</el-form>
</template>
</el-popover>
</template>
<el-menu-item v-if="dbs[inst.id]?.length > 20" :index="'schema-filter-' + inst.id"
:key="'schema-filter-' + inst.id">
2023-02-13 21:11:16 +08:00
<template #title>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<el-input size="small" placeholder="过滤数据库" clearable @change="filterSchemaName(inst.id)"
@keyup="(e: any) => filterSchemaName(inst.id, e)"
v-model="state.schemaFilterParam[inst.id]" />
2023-02-13 21:11:16 +08:00
</template>
</el-menu-item>
2023-02-07 16:54:44 +08:00
<!-- 第三级数据库 -->
<el-sub-menu v-show="schema.show" v-for="schema in dbs[inst.id]" :index="inst.id + schema.name"
:key="inst.id + schema.name" :class="state.nowSchema === (inst.id + schema.name) && 'checked'"
2023-02-13 21:11:16 +08:00
@click.stop="changeSchema(inst, schema.name)">
2023-02-07 16:54:44 +08:00
<template #title>
<span class="checked-schema ml20">
<el-icon>
<Coin color="#67c23a" />
2023-02-14 10:33:57 +08:00
</el-icon>
<span v-html="schema.showName || schema.name"></span>
</span>
2023-02-07 16:54:44 +08:00
</template>
<!-- 第四级 01 -->
2023-02-13 21:11:16 +08:00
<el-sub-menu :index="inst.id + schema.name + '-table'">
2023-02-06 17:14:16 +08:00
<template #title>
2023-02-13 21:11:16 +08:00
<div class="ml30" style="width: 100%" @click="loadSchemaTables(inst, schema.name)">
<el-icon>
2023-02-07 16:54:44 +08:00
<Calendar color="#409eff" />
</el-icon>
<span></span>
2023-02-13 21:11:16 +08:00
<el-icon v-show="state.loading[inst.id + schema.name]" class="is-loading">
2023-02-07 16:54:44 +08:00
<Loading />
</el-icon>
</div>
2023-02-06 17:14:16 +08:00
</template>
<el-menu-item v-if="tables[inst.id + schema.name]?.length > 20"
:index="inst.id + schema.name + '-tableSearch'"
2023-02-13 21:11:16 +08:00
:key="inst.id + schema.name + '-tableSearch'">
2023-02-06 17:14:16 +08:00
<template #title>
<span class="ml35">
<el-input size="small" placeholder="表名、备注过滤表" clearable
2023-02-13 21:11:16 +08:00
@change="filterTableName(inst.id, schema.name)"
@keyup="(e: any) => filterTableName(inst.id, schema.name, e)"
v-model="state.filterParam[inst.id + schema.name]" />
</span>
2023-02-06 17:14:16 +08:00
</template>
2023-02-07 16:54:44 +08:00
</el-menu-item>
2023-02-13 21:11:16 +08:00
<template v-for="tb in tables[inst.id + schema.name]">
<el-menu-item :index="inst.id + schema.name + tb.tableName"
:key="inst.id + schema.name + tb.tableName" v-if="tb.show"
@click="clickSchemaTable(inst, schema.name, tb.tableName)">
2023-02-06 17:14:16 +08:00
<template #title>
<div class="ml35" style="width: 100%">
<el-icon>
2023-02-07 16:54:44 +08:00
<Calendar color="#409eff" />
</el-icon>
<el-tooltip v-if="tb.tableComment" effect="customized"
:content="tb.tableComment" placement="right">
<span v-html="tb.showName || tb.tableName"></span>
2023-02-07 16:54:44 +08:00
</el-tooltip>
2023-02-14 10:33:57 +08:00
<span v-else v-html="tb.showName || tb.tableName"></span>
2023-02-07 16:54:44 +08:00
</div>
2023-02-06 17:14:16 +08:00
</template>
2023-02-07 16:54:44 +08:00
</el-menu-item>
</template>
</el-sub-menu>
<!-- 第四级 02sql -->
2023-02-13 21:11:16 +08:00
<el-sub-menu @click.stop="loadSqls(inst, schema.name)" :index="inst.id + schema.name + '-sql'">
2023-02-07 16:54:44 +08:00
<template #title>
<span class="ml30">
<el-icon>
<List color="#f56c6c" />
</el-icon>
<span>sql</span>
</span>
2023-02-07 16:54:44 +08:00
</template>
2023-01-31 10:37:40 +08:00
2023-02-13 21:11:16 +08:00
<template v-for="sql in sqls[inst.id + schema.name]">
<el-menu-item v-if="sql.show" :index="inst.id + schema.name + sql.name"
:key="inst.id + schema.name + sql.name"
@click="clickSqlName(inst, schema.name, sql.name)">
2023-02-07 16:54:44 +08:00
<template #title>
<div class="ml35" style="width: 100%">
<el-icon>
<Document />
2023-02-06 17:14:16 +08:00
</el-icon>
2023-02-07 16:54:44 +08:00
<span>{{ sql.name }}</span>
</div>
</template>
</el-menu-item>
</template>
2023-02-06 17:14:16 +08:00
</el-sub-menu>
2023-02-07 16:54:44 +08:00
</el-sub-menu>
</el-sub-menu>
</template>
</tag-menu>
2023-01-31 10:37:40 +08:00
</template>
<script lang="ts" setup>
import { onBeforeMount, reactive, toRefs } from 'vue';
2023-02-07 16:54:44 +08:00
import TagMenu from '../../component/TagMenu.vue';
import { dbApi } from '../api';
import { DbInst } from '../db';
2023-01-31 10:37:40 +08:00
const emits = defineEmits(['changeInstance', 'clickSqlName', 'clickSchemaTable', 'changeSchema', 'loadSqlNames'])
2023-01-31 10:37:40 +08:00
2023-02-06 17:14:16 +08:00
onBeforeMount(async () => {
await loadInstances();
state.instanceMenuMaxHeight = window.innerHeight - 140 + 'px';
2023-01-31 10:37:40 +08:00
})
const state = reactive({
tags: {},
tree: {},
dbs: {},
tables: {},
sqls: {},
2023-02-06 17:14:16 +08:00
nowSchema: '',
filterParam: {},
2023-02-13 21:11:16 +08:00
schemaFilterParam: {},
loading: {},
instanceMenuMaxHeight: '850px',
2023-01-31 10:37:40 +08:00
})
const {
instanceMenuMaxHeight,
tags,
tree,
dbs,
sqls,
tables,
} = toRefs(state)
// 加载实例数据
const loadInstances = async () => {
const res = await dbApi.dbs.request({ pageNum: 1, pageSize: 1000, })
if (!res.total) return
// state.instances = { tags: {}, tree: {}, dbs: {}, tables: {}, sqls: {} }; // 初始化变量
for (const db of res.list) {
let arr = state.tree[db.tagId] || []
const { tagId, tagPath } = db
// tags
state.tags[db.tagId] = { tagId, tagPath }
// tree
arr.push(db)
state.tree[db.tagId] = arr;
// dbs
2023-02-13 21:11:16 +08:00
let databases = db.database.split(' ')
let dbs = [] as any[];
databases.forEach((a: string) => dbs.push({ name: a, show: true }))
2023-02-13 21:11:16 +08:00
state.dbs[db.id] = dbs
}
2023-01-31 10:37:40 +08:00
}
/**
* 改变选中的数据库实例
* @param inst 选中的实例对象
2023-02-07 10:32:42 +08:00
* @param fn 选中的实例对象后的回调函数
2023-01-31 10:37:40 +08:00
*/
2023-02-07 16:54:44 +08:00
const changeInstance = (inst: any, fn: Function) => {
emits('changeInstance', inst, fn)
2023-01-31 10:37:40 +08:00
}
/**
* 改变选中的数据库schema
* @param inst 选中的实例对象
* @param schema 选中的数据库schema
*/
2023-02-06 17:14:16 +08:00
const changeSchema = (inst: any, schema: string) => {
state.nowSchema = inst.id + schema
emits('changeSchema', inst, schema)
2023-01-31 10:37:40 +08:00
}
/** schema
*
2023-01-31 10:37:40 +08:00
* @param inst 数据库实例
* @param schema database名
*/
const loadSchemaTables = async (inst: any, schema: string) => {
const key = getSchemaKey(inst.id, schema);
state.loading[key] = true
try {
let { id } = inst
2023-02-15 21:28:01 +08:00
let tables = await DbInst.getInst(id).loadTables(schema);
tables && tables.forEach((a: any) => a.show = true)
state.tables[key] = tables;
changeSchema(inst, schema);
} finally {
state.loading[key] = false
}
2023-01-31 10:37:40 +08:00
}
2023-01-31 10:37:40 +08:00
/**
* 加载选中表数据
* @param inst 数据库实例
* @param schema database名
* @param tableName 表名
*/
const clickSchemaTable = (inst: any, schema: string, tableName: string) => {
emits('clickSchemaTable', inst, schema, tableName)
2023-01-31 10:37:40 +08:00
}
const filterTableName = (instId: number, schema: string, event?: any) => {
const key = getSchemaKey(instId, schema)
2023-02-06 17:14:16 +08:00
if (event) {
state.filterParam[key] = event.target.value
2023-02-06 17:14:16 +08:00
}
let param = state.filterParam[key] as string
state.tables[key].forEach((a: any) => {
let { match, showName } = matchAndHighLight(param, a.tableName + a.tableComment, a.tableName)
2023-02-14 10:33:57 +08:00
a.show = match;
a.showName = showName
2023-02-06 17:14:16 +08:00
})
2023-01-31 10:37:40 +08:00
}
2023-02-13 21:11:16 +08:00
const filterSchemaName = (instId: number, event?: any) => {
2023-02-14 10:33:57 +08:00
if (event) {
state.schemaFilterParam[instId] = event.target.value
}
let param = state.schemaFilterParam[instId] as string
param = param?.replace('/', '\/')
state.dbs[instId].forEach((a: any) => {
let { match, showName } = matchAndHighLight(param, a.name, a.name)
2023-02-14 10:33:57 +08:00
a.show = match
a.showName = showName
})
}
const matchAndHighLight = (searchParam: string, param: string, title: string): { match: boolean, showName: string } => {
if (!searchParam) {
return { match: true, showName: '' }
2023-02-14 10:33:57 +08:00
}
let str = '';
for (let c of searchParam?.replace('/', '\/')) {
2023-02-14 10:33:57 +08:00
str += `(${c}).*`
}
let regex = eval(`/${str}/i`)
let res = param.match(regex);
if (res?.length) {
if (res?.length) {
2023-02-14 10:33:57 +08:00
let tmp = '', showName = '';
for (let i = 1; i <= res.length - 1; i++) {
2023-02-14 10:33:57 +08:00
let head = (tmp || title).replace(res[i], `###${res[i]}!!!`);
let idx = head.lastIndexOf('!!!') + 3;
2023-02-14 10:33:57 +08:00
tmp = head.substring(idx);
showName += head.substring(0, idx)
if (!tmp) {
2023-02-14 10:33:57 +08:00
break
}
}
showName += tmp;
showName = showName.replaceAll('###', '<span style="color: red">')
showName = showName.replaceAll('!!!', '</span>')
return { match: true, showName }
2023-02-14 10:33:57 +08:00
}
}
return { match: false, showName: '' }
2023-02-14 10:33:57 +08:00
2023-02-13 21:11:16 +08:00
}
/**
* 加载用户保存的sql脚本
*
* @param inst
* @param schema
*/
const loadSqls = async (inst: any, schema: string) => {
const key = getSchemaKey(inst.id, schema)
let sqls = state.sqls[key];
if (!sqls) {
const sqls = await dbApi.getSqlNames.request({ id: inst.id, db: schema, })
sqls && sqls.forEach((a: any) => a.show = true)
state.sqls[key] = sqls;
} else {
sqls.forEach((a: any) => a.show = true);
2023-02-07 16:54:44 +08:00
}
2023-02-07 10:32:42 +08:00
}
const reloadSqls = async (inst: any, schema: string) => {
const sqls = await dbApi.getSqlNames.request({ id: inst.id, db: schema, })
sqls && sqls.forEach((a: any) => a.show = true)
state.sqls[getSchemaKey(inst.id, schema)] = sqls;
}
/**
* 点击sql模板名称时间加载用户保存的指定名称的sql内容并回调子组件指定事件
*/
const clickSqlName = async (inst: any, schema: string, sqlName: string) => {
emits('clickSqlName', inst, schema, sqlName)
changeSchema(inst, schema);
}
2023-02-07 10:32:42 +08:00
/**
* 根据实例以及库获取对应的唯一id
*
* @param inst 数据库实例
* @param schema 数据库
*/
const getSchemaKey = (instId: any, schema: string) => {
return instId + schema;
}
const getSchemas = (dbId: any) => {
return state.dbs[dbId] || []
}
defineExpose({
getSchemas,
reloadSqls,
})
2023-01-31 10:37:40 +08:00
</script>
<style lang="scss">
2023-02-06 17:14:16 +08:00
.instances-pop-form {
.el-form-item {
margin-bottom: unset;
}
2023-01-31 10:37:40 +08:00
}
</style>