mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-02 15:30:25 +08:00
feat: 小功能优化
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
"countup.js": "^2.0.7",
|
||||
"cropperjs": "^1.5.11",
|
||||
"echarts": "^5.4.0",
|
||||
"element-plus": "^2.2.26",
|
||||
"element-plus": "^2.2.29",
|
||||
"jsencrypt": "^3.2.1",
|
||||
"lodash": "^4.17.21",
|
||||
"mitt": "^3.0.0",
|
||||
@@ -44,10 +44,10 @@
|
||||
"eslint": "^8.5.0",
|
||||
"eslint-plugin-vue": "^8.2.0",
|
||||
"prettier": "^2.3.0",
|
||||
"sass": "^1.45.1",
|
||||
"sass-loader": "^12.4.0",
|
||||
"sass": "^1.58.0",
|
||||
"sass-loader": "^13.2.0",
|
||||
"typescript": "^4.7.4",
|
||||
"vite": "^4.0.4",
|
||||
"vite": "^4.1.1",
|
||||
"vue-eslint-parser": "^8.0.1"
|
||||
},
|
||||
"browserslist": [
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
function getBaseApiUrl() {
|
||||
let path = window.location.pathname;
|
||||
if (path == '/') {
|
||||
return window.location.host;
|
||||
}
|
||||
return window.location.host + path;
|
||||
}
|
||||
|
||||
const config = {
|
||||
baseApiUrl: `${(window as any).globalConfig.BaseApiUrl}/api`,
|
||||
baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${location.host}`}/api`,
|
||||
baseApiUrl: `${(window as any).globalConfig.BaseApiUrl || location.protocol + '//' + getBaseApiUrl()}/api`,
|
||||
baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
|
||||
|
||||
// 系统版本
|
||||
version: 'v1.3.1'
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,163 +1,172 @@
|
||||
<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 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="'instance-' + inst.id"
|
||||
:key="'instance-' + inst.id"
|
||||
@click="changeInstance(inst)"
|
||||
>
|
||||
<template #title>
|
||||
<el-popover
|
||||
placement="right-start"
|
||||
title="数据库实例信息"
|
||||
trigger="hover"
|
||||
:width="210"
|
||||
>
|
||||
<template #reference>
|
||||
<span> <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-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="changeSchema(inst, schema)"
|
||||
>
|
||||
<template #title>
|
||||
<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)">
|
||||
<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>
|
||||
|
||||
<el-input size="small" placeholder="过滤表" clearable
|
||||
@change="filterTableName(inst.id, schema)"
|
||||
@keyup="e => 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%" >
|
||||
<el-icon><Calendar color="#409eff"/></el-icon>
|
||||
<span :title="tb.tableComment||''">{{tb.tableName}}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</el-sub-menu>
|
||||
<!-- 第四级 02:sql -->
|
||||
<el-sub-menu :index="inst.id + schema + '-sql'">
|
||||
<template #title>
|
||||
<el-icon><List color="#f56c6c"/></el-icon>
|
||||
<span>sql</span>
|
||||
</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 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="'instance-' + inst.id"
|
||||
:key="'instance-' + inst.id" @click="changeInstance(inst)">
|
||||
<template #title>
|
||||
<el-popover placement="right-start" title="数据库实例信息" trigger="hover" :width="210">
|
||||
<template #reference>
|
||||
<span> <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-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="changeSchema(inst, schema)">
|
||||
<template #title>
|
||||
<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)">
|
||||
<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>
|
||||
|
||||
<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="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%" >
|
||||
<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-menu>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<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%">
|
||||
<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>
|
||||
<!-- 第四级 02:sql -->
|
||||
<el-sub-menu :index="inst.id + schema + '-sql'">
|
||||
<template #title>
|
||||
<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%">
|
||||
<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-menu>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {onBeforeMount, reactive} from 'vue';
|
||||
import { onBeforeMount, reactive } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
instanceMenuMaxHeight: {
|
||||
type: [Number, String],
|
||||
},
|
||||
instances: {
|
||||
type: Object, required: true
|
||||
},
|
||||
instanceMenuMaxHeight: {
|
||||
type: [Number, String],
|
||||
},
|
||||
instances: {
|
||||
type: Object, required: true
|
||||
},
|
||||
})
|
||||
|
||||
const emits = defineEmits(['initLoadInstances','changeInstance','loadTableNames','loadTableData','changeSchema'])
|
||||
const emits = defineEmits(['initLoadInstances', 'changeInstance', 'loadTableNames', 'loadTableData', 'changeSchema'])
|
||||
|
||||
onBeforeMount(async ()=>{
|
||||
await initLoadInstances()
|
||||
onBeforeMount(async () => {
|
||||
await initLoadInstances()
|
||||
})
|
||||
|
||||
const state = reactive({
|
||||
nowSchema: '',
|
||||
filterParam: {},
|
||||
loading: {}
|
||||
nowSchema: '',
|
||||
filterParam: {},
|
||||
loading: {}
|
||||
})
|
||||
|
||||
/**
|
||||
* 初始化加载实例数据
|
||||
*/
|
||||
const initLoadInstances = () => {
|
||||
emits('initLoadInstances')
|
||||
emits('initLoadInstances')
|
||||
}
|
||||
|
||||
/**
|
||||
* 改变选中的数据库实例
|
||||
* @param inst 选中的实例对象
|
||||
*/
|
||||
const changeInstance = (inst : any) => {
|
||||
emits('changeInstance', inst)
|
||||
const changeInstance = (inst: any) => {
|
||||
emits('changeInstance', inst)
|
||||
}
|
||||
/**
|
||||
* 改变选中的数据库schema
|
||||
* @param inst 选中的实例对象
|
||||
* @param schema 选中的数据库schema
|
||||
*/
|
||||
const changeSchema = (inst : any, schema: string) => {
|
||||
state.nowSchema = inst.id + schema
|
||||
emits('changeSchema', inst, schema)
|
||||
const changeSchema = (inst: any, schema: string) => {
|
||||
state.nowSchema = inst.id + schema
|
||||
emits('changeSchema', inst, schema)
|
||||
}
|
||||
/**
|
||||
* 加载schema下所有表
|
||||
@@ -165,10 +174,10 @@ const changeSchema = (inst : any, schema: string) => {
|
||||
* @param schema database名
|
||||
*/
|
||||
const loadTableNames = async (inst: any, schema: string) => {
|
||||
state.loading[inst.id+schema] = true
|
||||
await emits('loadTableNames', inst, schema, ()=>{
|
||||
state.loading[inst.id+schema] = false
|
||||
})
|
||||
state.loading[inst.id + schema] = true
|
||||
await emits('loadTableNames', inst, schema, () => {
|
||||
state.loading[inst.id + schema] = false
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 加载选中表数据
|
||||
@@ -177,60 +186,67 @@ const loadTableNames = async (inst: any, schema: string) => {
|
||||
* @param tableName 表名
|
||||
*/
|
||||
const loadTableData = (inst: any, schema: string, tableName: string) => {
|
||||
emits('loadTableData', inst, schema, tableName)
|
||||
emits('loadTableData', inst, schema, tableName)
|
||||
}
|
||||
|
||||
const filterTableName = (instId: number, schema: string, event?: any) => {
|
||||
if(event){
|
||||
state.filterParam[instId+schema] = event.target.value
|
||||
}
|
||||
let param = state.filterParam[instId+schema] as string
|
||||
param = param?.replace('/','\/')
|
||||
const key = instId + schema;
|
||||
props.instances.tables[key].forEach((a:any) =>{
|
||||
a.show = param?eval('/'+param.split('').join('[_\w]*')+'[_\w]*/ig').test(a.tableName):true
|
||||
})
|
||||
if (event) {
|
||||
state.filterParam[instId + schema] = event.target.value
|
||||
}
|
||||
let param = state.filterParam[instId + schema] as string
|
||||
param = param?.replace('/', '\/')
|
||||
const key = instId + schema;
|
||||
props.instances.tables[key].forEach((a: any) => {
|
||||
a.show = param ? eval('/' + param.split('').join('[_\w]*') + '[_\w]*/ig').test(a.tableName) : true
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.instances-box {
|
||||
.el-menu{
|
||||
width: 275px;
|
||||
}
|
||||
.el-sub-menu{
|
||||
.checked{
|
||||
.checked-schema{
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
.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;
|
||||
}
|
||||
}
|
||||
.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;
|
||||
}
|
||||
|
||||
.instances-pop-form {
|
||||
.el-form-item {
|
||||
margin-bottom: unset;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,152 +1,153 @@
|
||||
<template>
|
||||
<div class="instances-box layout-aside">
|
||||
<el-row type="flex" justify="space-between">
|
||||
<el-col :span="24" :style="{
|
||||
maxHeight: state.instanceMenuMaxHeight,
|
||||
height: state.instanceMenuMaxHeight,
|
||||
overflow:'auto'
|
||||
}" class="el-scrollbar flex-auto">
|
||||
|
||||
<el-menu background-color="transparent" :collapse-transition="false">
|
||||
<!-- 第一级: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="'mongo-instance-' + inst.id"
|
||||
:key="'mongo-instance-' + inst.id"
|
||||
@click.prevent="changeInstance(inst)"
|
||||
>
|
||||
<template #title>
|
||||
<el-popover
|
||||
placement="right-start"
|
||||
title="mongo数据库实例信息"
|
||||
trigger="hover"
|
||||
:width="210"
|
||||
>
|
||||
<template #reference>
|
||||
<span> <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.uri}}</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>
|
||||
<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)">
|
||||
<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>
|
||||
|
||||
<el-input size="small" placeholder="过滤" clearable
|
||||
@change="filterTableName(inst.id, db.Name)"
|
||||
@keyup="e => 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]" >
|
||||
<el-menu-item :index="inst.id + db.Name + tb.tableName"
|
||||
:key="inst.id + db.Name + tb.tableName"
|
||||
v-if="tb.show"
|
||||
@click="loadTableData(inst, db.Name, tb.tableName)"
|
||||
>
|
||||
<template #title>
|
||||
<div style="width: 100%" >
|
||||
<el-icon><Calendar color="#409eff"/></el-icon>
|
||||
<span :title="tb.tableComment||''">{{tb.tableName}}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</el-sub-menu>
|
||||
</el-sub-menu>
|
||||
</el-sub-menu>
|
||||
</el-sub-menu>
|
||||
</el-menu>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<div class="instances-box layout-aside">
|
||||
<el-row type="flex" justify="space-between">
|
||||
<el-col :span="24" :style="{
|
||||
maxHeight: state.instanceMenuMaxHeight,
|
||||
height: state.instanceMenuMaxHeight,
|
||||
overflow: 'auto'
|
||||
}" class="el-scrollbar flex-auto">
|
||||
|
||||
<el-menu background-color="transparent" :collapse-transition="false">
|
||||
<!-- 第一级: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="'mongo-instance-' + inst.id"
|
||||
:key="'mongo-instance-' + inst.id" @click.prevent="changeInstance(inst)">
|
||||
<template #title>
|
||||
<el-popover placement="right-start" title="mongo数据库实例信息" trigger="hover" :width="210">
|
||||
<template #reference>
|
||||
<span> <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.uri }}</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>
|
||||
<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)">
|
||||
<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>
|
||||
|
||||
<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]">
|
||||
<el-menu-item :index="inst.id + db.Name + tb.tableName"
|
||||
:key="inst.id + db.Name + tb.tableName" v-if="tb.show"
|
||||
@click="loadTableData(inst, db.Name, tb.tableName)">
|
||||
<template #title>
|
||||
<div style="width: 100%">
|
||||
<el-icon>
|
||||
<Calendar color="#409eff" />
|
||||
</el-icon>
|
||||
<span :title="tb.tableComment || ''">{{ tb.tableName }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</el-sub-menu>
|
||||
</el-sub-menu>
|
||||
</el-sub-menu>
|
||||
</el-sub-menu>
|
||||
</el-menu>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {onBeforeMount, reactive} from 'vue';
|
||||
import {formatByteSize} from '@/common/utils/format';
|
||||
import { onBeforeMount, reactive } from 'vue';
|
||||
import { formatByteSize } from '@/common/utils/format';
|
||||
|
||||
const props = defineProps({
|
||||
instances: {
|
||||
type: Object, required: true
|
||||
},
|
||||
instances: {
|
||||
type: Object, required: true
|
||||
},
|
||||
})
|
||||
|
||||
const emits = defineEmits(['initLoadInstances','changeInstance','loadTableNames','loadTableData','changeSchema'])
|
||||
const emits = defineEmits(['initLoadInstances', 'changeInstance', 'loadTableNames', 'loadTableData', 'changeSchema'])
|
||||
|
||||
onBeforeMount(async ()=>{
|
||||
await initLoadInstances()
|
||||
setHeight()
|
||||
onBeforeMount(async () => {
|
||||
await initLoadInstances()
|
||||
setHeight()
|
||||
})
|
||||
|
||||
const setHeight = () => {
|
||||
state.instanceMenuMaxHeight = window.innerHeight - 140 + 'px';
|
||||
state.instanceMenuMaxHeight = window.innerHeight - 140 + 'px';
|
||||
}
|
||||
|
||||
const state = reactive({
|
||||
instanceMenuMaxHeight: '800px',
|
||||
nowSchema: '',
|
||||
filterParam: {},
|
||||
loading: {},
|
||||
|
||||
instanceMenuMaxHeight: '800px',
|
||||
nowSchema: '',
|
||||
filterParam: {},
|
||||
loading: {},
|
||||
|
||||
})
|
||||
|
||||
/**
|
||||
* 初始化加载实例数据
|
||||
*/
|
||||
const initLoadInstances = () => {
|
||||
emits('initLoadInstances')
|
||||
emits('initLoadInstances')
|
||||
}
|
||||
|
||||
/**
|
||||
* 改变选中的数据库实例
|
||||
* @param inst 选中的实例对象
|
||||
*/
|
||||
const changeInstance = (inst : any) => {
|
||||
emits('changeInstance', inst)
|
||||
const changeInstance = (inst: any) => {
|
||||
emits('changeInstance', inst)
|
||||
}
|
||||
/**
|
||||
* 改变选中的数据库schema
|
||||
* @param inst 选中的实例对象
|
||||
* @param schema 选中的数据库schema
|
||||
*/
|
||||
const changeSchema = (inst : any, schema: string) => {
|
||||
state.nowSchema = inst.id + schema
|
||||
emits('changeSchema', inst, schema)
|
||||
const changeSchema = (inst: any, schema: string) => {
|
||||
state.nowSchema = inst.id + schema
|
||||
emits('changeSchema', inst, schema)
|
||||
}
|
||||
/**
|
||||
* 加载schema下所有表
|
||||
@@ -154,10 +155,10 @@ const changeSchema = (inst : any, schema: string) => {
|
||||
* @param schema database名
|
||||
*/
|
||||
const loadTableNames = async (inst: any, schema: string) => {
|
||||
state.loading[inst.id+schema] = true
|
||||
await emits('loadTableNames', inst, schema, ()=>{
|
||||
state.loading[inst.id+schema] = false
|
||||
})
|
||||
state.loading[inst.id + schema] = true
|
||||
await emits('loadTableNames', inst, schema, () => {
|
||||
state.loading[inst.id + schema] = false
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 加载选中表数据
|
||||
@@ -166,60 +167,67 @@ const loadTableNames = async (inst: any, schema: string) => {
|
||||
* @param tableName 表名
|
||||
*/
|
||||
const loadTableData = (inst: any, schema: string, tableName: string) => {
|
||||
emits('loadTableData', inst, schema, tableName)
|
||||
emits('loadTableData', inst, schema, tableName)
|
||||
}
|
||||
|
||||
const filterTableName = (instId: number, schema: string, event?: any) => {
|
||||
if(event){
|
||||
state.filterParam[instId+schema] = event.target.value
|
||||
}
|
||||
let param = state.filterParam[instId+schema] as string
|
||||
param = param?.replace('/','\/')
|
||||
const key = instId + schema;
|
||||
props.instances.tables[key].forEach((a:any) =>{
|
||||
a.show = param?eval('/'+param.split('').join('[_\w]*')+'[_\w]*/ig').test(a.tableName):true
|
||||
})
|
||||
if (event) {
|
||||
state.filterParam[instId + schema] = event.target.value
|
||||
}
|
||||
let param = state.filterParam[instId + schema] as string
|
||||
param = param?.replace('/', '\/')
|
||||
const key = instId + schema;
|
||||
props.instances.tables[key].forEach((a: any) => {
|
||||
a.show = param ? eval('/' + param.split('').join('[_\w]*') + '[_\w]*/ig').test(a.tableName) : true
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.instances-box {
|
||||
.el-menu{
|
||||
width: 275px;
|
||||
}
|
||||
.el-sub-menu{
|
||||
.checked{
|
||||
.checked-schema{
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
.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;
|
||||
}
|
||||
}
|
||||
.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;
|
||||
}
|
||||
|
||||
.instances-pop-form {
|
||||
.el-form-item {
|
||||
margin-bottom: unset;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -122,7 +122,6 @@ const cancel = () => {
|
||||
|
||||
watch(props, async (newValue: any) => {
|
||||
state.dialogVisible = newValue.visible;
|
||||
state.key = newValue.key;
|
||||
state.redisId = newValue.redisId;
|
||||
state.db = newValue.db;
|
||||
state.key = newValue.keyInfo;
|
||||
|
||||
@@ -106,7 +106,6 @@ const cancel = () => {
|
||||
|
||||
watch(props, async (newValue: any) => {
|
||||
state.dialogVisible = newValue.visible;
|
||||
state.key = newValue.key;
|
||||
state.redisId = newValue.redisId;
|
||||
state.db = newValue.db;
|
||||
state.key = newValue.keyInfo;
|
||||
|
||||
@@ -120,7 +120,6 @@ watch(
|
||||
|
||||
watch(props, async (newValue: any) => {
|
||||
state.dialogVisible = newValue.visible;
|
||||
state.key = newValue.key;
|
||||
state.redisId = newValue.redisId;
|
||||
state.db = newValue.db;
|
||||
state.key = newValue.keyInfo;
|
||||
|
||||
@@ -772,10 +772,10 @@ echarts@^5.4.0:
|
||||
tslib "2.3.0"
|
||||
zrender "5.4.0"
|
||||
|
||||
element-plus@^2.2.26:
|
||||
version "2.2.26"
|
||||
resolved "https://registry.npmmirror.com/element-plus/-/element-plus-2.2.26.tgz#5e46aa5d8127786bb158713957f8a253b35bf019"
|
||||
integrity sha512-O/rdY5m9DkclpVg8r3GynyqCunm7MxSR142xSsjrZA77bi7bcwA3SIy6SPEDqHi5R4KqgkGYgKSp4Q4e3irbYg==
|
||||
element-plus@^2.2.29:
|
||||
version "2.2.29"
|
||||
resolved "https://registry.npmmirror.com/element-plus/-/element-plus-2.2.29.tgz#7dd72f9cafdc102ae3f9e4efe612e403ef713a74"
|
||||
integrity sha512-g4dcrURrKkR5uUX8n5RVnnqGnimoki9HfqS4yHHG6XwCHBkZGozdq4x+478BzeWUe31h++BO+7dakSx4VnM8RQ==
|
||||
dependencies:
|
||||
"@ctrl/tinycolor" "^3.4.1"
|
||||
"@element-plus/icons-vue" "^2.0.6"
|
||||
@@ -800,7 +800,7 @@ enquirer@^2.3.5:
|
||||
dependencies:
|
||||
ansi-colors "^4.1.1"
|
||||
|
||||
esbuild@^0.16.3:
|
||||
esbuild@^0.16.14:
|
||||
version "0.16.17"
|
||||
resolved "https://registry.npmmirror.com/esbuild/-/esbuild-0.16.17.tgz#fc2c3914c57ee750635fee71b89f615f25065259"
|
||||
integrity sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==
|
||||
@@ -1451,7 +1451,7 @@ postcss@^8.1.10:
|
||||
picocolors "^1.0.0"
|
||||
source-map-js "^1.0.1"
|
||||
|
||||
postcss@^8.4.20:
|
||||
postcss@^8.4.21:
|
||||
version "8.4.21"
|
||||
resolved "https://registry.npmmirror.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4"
|
||||
integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==
|
||||
@@ -1533,10 +1533,10 @@ rimraf@^3.0.2:
|
||||
dependencies:
|
||||
glob "^7.1.3"
|
||||
|
||||
rollup@^3.7.0:
|
||||
version "3.10.0"
|
||||
resolved "https://registry.npmmirror.com/rollup/-/rollup-3.10.0.tgz#6eb19196d8b3b375ca651cb78261faac48e24cd6"
|
||||
integrity sha512-JmRYz44NjC1MjVF2VKxc0M1a97vn+cDxeqWmnwyAF4FvpjK8YFdHpaqvQB+3IxCvX05vJxKZkoMDU8TShhmJVA==
|
||||
rollup@^3.10.0:
|
||||
version "3.12.1"
|
||||
resolved "https://registry.npmmirror.com/rollup/-/rollup-3.12.1.tgz#2975b97713e4af98c15e7024b88292d7fddb3853"
|
||||
integrity sha512-t9elERrz2i4UU9z7AwISj3CQcXP39cWxgRWLdf4Tm6aKm1eYrqHIgjzXBgb67GNY1sZckTFFi0oMozh3/S++Ig==
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
@@ -1547,18 +1547,18 @@ run-parallel@^1.1.9:
|
||||
dependencies:
|
||||
queue-microtask "^1.2.2"
|
||||
|
||||
sass-loader@^12.4.0:
|
||||
version "12.4.0"
|
||||
resolved "https://registry.npmmirror.com/sass-loader/download/sass-loader-12.4.0.tgz"
|
||||
integrity sha512-7xN+8khDIzym1oL9XyS6zP6Ges+Bo2B2xbPrjdMHEYyV3AQYhd/wXeru++3ODHF0zMjYmVadblSKrPrjEkL8mg==
|
||||
sass-loader@^13.2.0:
|
||||
version "13.2.0"
|
||||
resolved "https://registry.npmmirror.com/sass-loader/-/sass-loader-13.2.0.tgz#80195050f58c9aac63b792fa52acb6f5e0f6bdc3"
|
||||
integrity sha512-JWEp48djQA4nbZxmgC02/Wh0eroSUutulROUusYJO9P9zltRbNN80JCBHqRGzjd4cmZCa/r88xgfkjGD0TXsHg==
|
||||
dependencies:
|
||||
klona "^2.0.4"
|
||||
neo-async "^2.6.2"
|
||||
|
||||
sass@^1.45.1:
|
||||
version "1.48.0"
|
||||
resolved "https://registry.npmmirror.com/sass/download/sass-1.48.0.tgz"
|
||||
integrity sha512-hQi5g4DcfjcipotoHZ80l7GNJHGqQS5LwMBjVYB/TaT0vcSSpbgM8Ad7cgfsB2M0MinbkEQQPO9+sjjSiwxqmw==
|
||||
sass@^1.58.0:
|
||||
version "1.58.0"
|
||||
resolved "https://registry.npmmirror.com/sass/-/sass-1.58.0.tgz#ee8aea3ad5ea5c485c26b3096e2df6087d0bb1cc"
|
||||
integrity sha512-PiMJcP33DdKtZ/1jSjjqVIKihoDc6yWmYr9K/4r3fVVIEDAluD0q7XZiRKrNJcPK3qkLRF/79DND1H5q1LBjgg==
|
||||
dependencies:
|
||||
chokidar ">=3.0.0 <4.0.0"
|
||||
immutable "^4.0.0"
|
||||
@@ -1724,15 +1724,15 @@ v8-compile-cache@^2.0.3:
|
||||
resolved "https://registry.nlark.com/v8-compile-cache/download/v8-compile-cache-2.3.0.tgz"
|
||||
integrity sha1-LeGWGMZtwkfc+2+ZM4A12CRaLO4=
|
||||
|
||||
vite@^4.0.4:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.npmmirror.com/vite/-/vite-4.0.4.tgz#4612ce0b47bbb233a887a54a4ae0c6e240a0da31"
|
||||
integrity sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==
|
||||
vite@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.npmmirror.com/vite/-/vite-4.1.1.tgz#3b18b81a4e85ce3df5cbdbf4c687d93ebf402e6b"
|
||||
integrity sha512-LM9WWea8vsxhr782r9ntg+bhSFS06FJgCvvB0+8hf8UWtvaiDagKYWXndjfX6kGl74keHJUcpzrQliDXZlF5yg==
|
||||
dependencies:
|
||||
esbuild "^0.16.3"
|
||||
postcss "^8.4.20"
|
||||
esbuild "^0.16.14"
|
||||
postcss "^8.4.21"
|
||||
resolve "^1.22.1"
|
||||
rollup "^3.7.0"
|
||||
rollup "^3.10.0"
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ mysql:
|
||||
host: localhost:3306
|
||||
username: root
|
||||
password: 111049
|
||||
db-name: mayfly-go
|
||||
db-name: mayfly_go
|
||||
config: charset=utf8&loc=Local&parseTime=true
|
||||
max-idle-conns: 5
|
||||
# 若同时部署多台机器,则需要配置redis信息用于缓存权限码、验证码、公私钥等
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module mayfly-go
|
||||
|
||||
go 1.19
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/gin-gonic/gin v1.8.2
|
||||
|
||||
@@ -74,7 +74,6 @@ func InitRouter() *gin.Engine {
|
||||
|
||||
sys_router.Init(api)
|
||||
|
||||
// project_router.Init(api)
|
||||
tag_router.Init(api)
|
||||
machine_router.Init(api)
|
||||
db_router.Init(api)
|
||||
|
||||
@@ -461,7 +461,7 @@ func valueConvert(data []byte, colType *sql.ColumnType) interface{} {
|
||||
return intV
|
||||
}
|
||||
}
|
||||
if strings.Contains(colScanType, "float") {
|
||||
if strings.Contains(colScanType, "float") || strings.Contains(colDatabaseTypeName, "decimal") {
|
||||
floatV, _ := strconv.ParseFloat(stringV, 64)
|
||||
return floatV
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package starter
|
||||
|
||||
import (
|
||||
"log"
|
||||
"mayfly-go/pkg/config"
|
||||
"mayfly-go/pkg/global"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
@@ -30,10 +33,20 @@ func gormMysql() *gorm.DB {
|
||||
SkipInitializeWithVersion: false, // 根据版本自动配置
|
||||
}
|
||||
|
||||
gormLogger := logger.New(
|
||||
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
|
||||
logger.Config{
|
||||
SlowThreshold: time.Second, // 慢 SQL 阈值
|
||||
LogLevel: logger.Error, // 日志级别, 改为logger.Info即可显示sql语句
|
||||
IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误
|
||||
Colorful: true, // 禁用彩色打印
|
||||
},
|
||||
)
|
||||
|
||||
ormConfig := &gorm.Config{NamingStrategy: schema.NamingStrategy{
|
||||
TablePrefix: "t_",
|
||||
SingularTable: true,
|
||||
}, Logger: logger.Default.LogMode(logger.Error)} // 改为logger.Info即可显示sql语句
|
||||
}, Logger: gormLogger}
|
||||
|
||||
if db, err := gorm.Open(mysql.New(mysqlConfig), ormConfig); err != nil {
|
||||
global.Log.Panicf("连接mysql失败! [%s]", err.Error())
|
||||
|
||||
Reference in New Issue
Block a user