Files
mayfly-go/mayfly_go_web/src/views/ops/db/component/InstanceTree.vue
meilin.huang f22badb861 feat: 优化
2023-02-15 21:28:01 +08:00

346 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<tag-menu :instanceMenuMaxHeight="instanceMenuMaxHeight" :tags="tags" ref="menuRef">
<template #submenu="props">
<!-- 第二级数据库实例 -->
<el-sub-menu v-for="inst in tree[props.tag.tagId]" :index="'instance-' + inst.id"
: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>
<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.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-menu-item v-if="dbs[inst.id]?.length > 20" :index="'schema-filter-' + inst.id"
:key="'schema-filter-' + inst.id">
<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]" />
</template>
</el-menu-item>
<!-- 第三级数据库 -->
<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'"
@click.stop="changeSchema(inst, schema.name)">
<template #title>
<span class="checked-schema ml20">
<el-icon>
<Coin color="#67c23a" />
</el-icon>
<span v-html="schema.showName || schema.name"></span>
</span>
</template>
<!-- 第四级 01 -->
<el-sub-menu :index="inst.id + schema.name + '-table'">
<template #title>
<div class="ml30" style="width: 100%" @click="loadSchemaTables(inst, schema.name)">
<el-icon>
<Calendar color="#409eff" />
</el-icon>
<span></span>
<el-icon v-show="state.loading[inst.id + schema.name]" class="is-loading">
<Loading />
</el-icon>
</div>
</template>
<el-menu-item v-if="tables[inst.id + schema.name]?.length > 20"
:index="inst.id + schema.name + '-tableSearch'"
:key="inst.id + schema.name + '-tableSearch'">
<template #title>
<span class="ml35">
<el-input size="small" placeholder="表名、备注过滤表" clearable
@change="filterTableName(inst.id, schema.name)"
@keyup="(e: any) => filterTableName(inst.id, schema.name, e)"
v-model="state.filterParam[inst.id + schema.name]" />
</span>
</template>
</el-menu-item>
<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)">
<template #title>
<div class="ml35" style="width: 100%">
<el-icon>
<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>
</el-tooltip>
<span v-else v-html="tb.showName || tb.tableName"></span>
</div>
</template>
</el-menu-item>
</template>
</el-sub-menu>
<!-- 第四级 02sql -->
<el-sub-menu @click.stop="loadSqls(inst, schema.name)" :index="inst.id + schema.name + '-sql'">
<template #title>
<span class="ml30">
<el-icon>
<List color="#f56c6c" />
</el-icon>
<span>sql</span>
</span>
</template>
<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)">
<template #title>
<div class="ml35" style="width: 100%">
<el-icon>
<Document />
</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>
<script lang="ts" setup>
import { onBeforeMount, reactive, toRefs } from 'vue';
import TagMenu from '../../component/TagMenu.vue';
import { dbApi } from '../api';
import { DbInst } from '../db';
const emits = defineEmits(['changeInstance', 'clickSqlName', 'clickSchemaTable', 'changeSchema', 'loadSqlNames'])
onBeforeMount(async () => {
await loadInstances();
state.instanceMenuMaxHeight = window.innerHeight - 140 + 'px';
})
const state = reactive({
tags: {},
tree: {},
dbs: {},
tables: {},
sqls: {},
nowSchema: '',
filterParam: {},
schemaFilterParam: {},
loading: {},
instanceMenuMaxHeight: '850px',
})
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
let databases = db.database.split(' ')
let dbs = [] as any[];
databases.forEach((a: string) => dbs.push({ name: a, show: true }))
state.dbs[db.id] = dbs
}
}
/**
* 改变选中的数据库实例
* @param inst 选中的实例对象
* @param fn 选中的实例对象后的回调函数
*/
const changeInstance = (inst: any, fn: Function) => {
emits('changeInstance', inst, fn)
}
/**
* 改变选中的数据库schema
* @param inst 选中的实例对象
* @param schema 选中的数据库schema
*/
const changeSchema = (inst: any, schema: string) => {
state.nowSchema = inst.id + schema
emits('changeSchema', inst, schema)
}
/** 加载schema下所有表
*
* @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
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
}
}
/**
* 加载选中表数据
* @param inst 数据库实例
* @param schema database名
* @param tableName 表名
*/
const clickSchemaTable = (inst: any, schema: string, tableName: string) => {
emits('clickSchemaTable', inst, schema, tableName)
}
const filterTableName = (instId: number, schema: string, event?: any) => {
const key = getSchemaKey(instId, schema)
if (event) {
state.filterParam[key] = event.target.value
}
let param = state.filterParam[key] as string
state.tables[key].forEach((a: any) => {
let { match, showName } = matchAndHighLight(param, a.tableName + a.tableComment, a.tableName)
a.show = match;
a.showName = showName
})
}
const filterSchemaName = (instId: number, event?: any) => {
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)
a.show = match
a.showName = showName
})
}
const matchAndHighLight = (searchParam: string, param: string, title: string): { match: boolean, showName: string } => {
if (!searchParam) {
return { match: true, showName: '' }
}
let str = '';
for (let c of searchParam?.replace('/', '\/')) {
str += `(${c}).*`
}
let regex = eval(`/${str}/i`)
let res = param.match(regex);
if (res?.length) {
if (res?.length) {
let tmp = '', showName = '';
for (let i = 1; i <= res.length - 1; i++) {
let head = (tmp || title).replace(res[i], `###${res[i]}!!!`);
let idx = head.lastIndexOf('!!!') + 3;
tmp = head.substring(idx);
showName += head.substring(0, idx)
if (!tmp) {
break
}
}
showName += tmp;
showName = showName.replaceAll('###', '<span style="color: red">')
showName = showName.replaceAll('!!!', '</span>')
return { match: true, showName }
}
}
return { match: false, showName: '' }
}
/**
* 加载用户保存的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);
}
}
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);
}
/**
* 根据实例以及库获取对应的唯一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,
})
</script>
<style lang="scss">
.instances-pop-form {
.el-form-item {
margin-bottom: unset;
}
}
</style>