mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-02 23:40:24 +08:00
feat: 资源操作相关标签树调整为el-tree
This commit is contained in:
@@ -29,6 +29,7 @@ body,
|
||||
.layout-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.layout-aside {
|
||||
background: var(--bg-menuBar);
|
||||
box-shadow: 2px 0 6px rgb(0 21 41 / 1%);
|
||||
@@ -38,22 +39,27 @@ body,
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: hidden !important;
|
||||
|
||||
.el-scrollbar__view {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.layout-header {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.layout-main {
|
||||
padding: 0 !important;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.el-scrollbar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.layout-view-bg-white {
|
||||
background: white;
|
||||
width: 100%;
|
||||
@@ -61,33 +67,41 @@ body,
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.layout-el-aside-br-color {
|
||||
border-right: 1px solid rgb(238, 238, 238);
|
||||
}
|
||||
|
||||
.layout-aside-width-default {
|
||||
width: 220px !important;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.layout-aside-width64 {
|
||||
width: 64px !important;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.layout-aside-width1 {
|
||||
width: 1px !important;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.layout-scrollbar {
|
||||
@extend .el-scrollbar;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.layout-mian-height-50 {
|
||||
height: calc(100vh - 50px);
|
||||
}
|
||||
|
||||
.layout-columns-warp {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.layout-hide {
|
||||
display: none;
|
||||
}
|
||||
@@ -104,6 +118,7 @@ body,
|
||||
margin-bottom: 0 !important;
|
||||
border-bottom: 1px solid rgb(230, 230, 230);
|
||||
}
|
||||
|
||||
.el-divider {
|
||||
background-color: rgb(230, 230, 230);
|
||||
}
|
||||
@@ -123,25 +138,31 @@ body,
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flex-auto {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
@extend .flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.flex-margin {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.flex-warp {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-content: flex-start;
|
||||
margin: 0 -5px;
|
||||
|
||||
.flex-warp-item {
|
||||
padding: 5px;
|
||||
|
||||
.flex-warp-item-box {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -154,15 +175,19 @@ body,
|
||||
.w100 {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.h100 {
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.vh100 {
|
||||
height: 100vh !important;
|
||||
}
|
||||
|
||||
.max100vh {
|
||||
max-height: 100vh !important;
|
||||
}
|
||||
|
||||
.min100vh {
|
||||
min-height: 100vh !important;
|
||||
}
|
||||
@@ -172,15 +197,19 @@ body,
|
||||
.color-primary {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.color-success {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.color-warning {
|
||||
color: var(--color-warning);
|
||||
}
|
||||
|
||||
.color-danger {
|
||||
color: var(--color-danger);
|
||||
}
|
||||
|
||||
.color-info {
|
||||
color: var(--color-info);
|
||||
}
|
||||
@@ -199,24 +228,31 @@ body,
|
||||
.mt#{$i} {
|
||||
margin-top: #{$i}px !important;
|
||||
}
|
||||
|
||||
.mr#{$i} {
|
||||
margin-right: #{$i}px !important;
|
||||
}
|
||||
|
||||
.mb#{$i} {
|
||||
margin-bottom: #{$i}px !important;
|
||||
}
|
||||
|
||||
.ml#{$i} {
|
||||
margin-left: #{$i}px !important;
|
||||
}
|
||||
|
||||
.pt#{$i} {
|
||||
padding-top: #{$i}px !important;
|
||||
}
|
||||
|
||||
.pr#{$i} {
|
||||
padding-right: #{$i}px !important;
|
||||
}
|
||||
|
||||
.pb#{$i} {
|
||||
padding-bottom: #{$i}px !important;
|
||||
}
|
||||
|
||||
.pl#{$i} {
|
||||
padding-left: #{$i}px !important;
|
||||
}
|
||||
@@ -249,15 +285,22 @@ body,
|
||||
.el-menu .fa:not(.is-children) {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.gray-mode {
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
|
||||
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity .2s ease-in-out;
|
||||
}
|
||||
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
|
||||
|
||||
.fade-enter,
|
||||
.fade-leave-to
|
||||
|
||||
/* .fade-leave-active below version 2.1.8 */
|
||||
{
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@@ -298,3 +341,13 @@ body,
|
||||
.f12 {
|
||||
font-size: 12px
|
||||
}
|
||||
|
||||
.icon-middle {
|
||||
.el-icon {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
<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" ref="menuRef">
|
||||
<!-- 第一级:tag -->
|
||||
<el-sub-menu v-for="tag of tags" :index="tag.tagPath" :key="tag.tagPath"
|
||||
@click.stop="clickTag(tag.tagPath)">
|
||||
<template #title>
|
||||
<tag-info :tag-path="tag.tagPath" />
|
||||
|
||||
<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, ref, Ref, toRefs } from 'vue';
|
||||
import TagInfo from './TagInfo.vue';
|
||||
|
||||
const props = defineProps({
|
||||
instanceMenuMaxHeight: {
|
||||
type: [Number, String],
|
||||
},
|
||||
tags: {
|
||||
type: Object, required: true
|
||||
},
|
||||
})
|
||||
|
||||
const menuRef = ref(null) as Ref
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
const open = (index: string, isTag: boolean = false) => {
|
||||
if (!index) {
|
||||
return;
|
||||
}
|
||||
menuRef.value.open(index)
|
||||
if (isTag) {
|
||||
clickTag(index)
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open
|
||||
})
|
||||
|
||||
</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>
|
||||
122
mayfly_go_web/src/views/ops/component/TagTree.vue
Normal file
122
mayfly_go_web/src/views/ops/component/TagTree.vue
Normal file
@@ -0,0 +1,122 @@
|
||||
<template>
|
||||
<div class="instances-box layout-aside">
|
||||
<el-row type="flex" justify="space-between">
|
||||
<el-col :span="24" class="el-scrollbar flex-auto" style="overflow: auto">
|
||||
<el-input v-model="filterText" placeholder="输入关键字->搜索已展开节点信息" clearable size="small" class="mb5" />
|
||||
|
||||
<el-tree ref="treeRef" :style="{ maxHeight: state.height, height: state.height, overflow: 'auto' }"
|
||||
:highlight-current="true" :indent="7" :load="loadNode" :props="treeProps" lazy node-key="key"
|
||||
:expand-on-click-node="true" :filter-node-method="filterNode" @node-click="treeNodeClick">
|
||||
<template #default="{ node, data }">
|
||||
<span class="icon-middle ">
|
||||
<span v-if="data.type == TagTreeNode.TagPath">
|
||||
<tag-info :tag-path="data.label" />
|
||||
</span>
|
||||
|
||||
<slot v-else :node="node" :data="data" name="prefix"></slot>
|
||||
|
||||
<span class="ml3">
|
||||
<slot name="label" :data="data"> {{ data.label }}</slot>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-tree>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, reactive, ref, watch, toRefs } from 'vue';
|
||||
import { TagTreeNode } from './tag';
|
||||
import TagInfo from './TagInfo.vue';
|
||||
|
||||
const props = defineProps({
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
load: {
|
||||
type: Function,
|
||||
required: true,
|
||||
}
|
||||
})
|
||||
|
||||
const treeProps = {
|
||||
label: 'name',
|
||||
children: 'zones',
|
||||
isLeaf: 'isLeaf',
|
||||
}
|
||||
|
||||
const emit = defineEmits(['nodeClick'])
|
||||
const treeRef: any = ref(null)
|
||||
|
||||
const state = reactive({
|
||||
height: 600 as any,
|
||||
filterText: '',
|
||||
opend: {},
|
||||
})
|
||||
const { filterText } = toRefs(state)
|
||||
|
||||
onMounted(async () => {
|
||||
if (!props.height) {
|
||||
state.height = window.innerHeight - 145 + 'px';
|
||||
} else {
|
||||
state.height = props.height;
|
||||
}
|
||||
})
|
||||
|
||||
watch(filterText, (val) => {
|
||||
treeRef.value?.filter(val)
|
||||
})
|
||||
|
||||
const filterNode = (value: string, data: any) => {
|
||||
if (!value) return true
|
||||
return data.label.includes(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载树节点
|
||||
* @param { Object } node
|
||||
* @param { Object } resolve
|
||||
*/
|
||||
const loadNode = async (node: any, resolve: any) => {
|
||||
if (typeof resolve !== 'function') {
|
||||
return;
|
||||
}
|
||||
return resolve(await props.load(node));
|
||||
};
|
||||
|
||||
const treeNodeClick = (data: any) => {
|
||||
emit('nodeClick', data);
|
||||
}
|
||||
|
||||
const reloadNode = (nodeKey: any) => {
|
||||
let node = getNode(nodeKey);
|
||||
node.loaded = false;
|
||||
node.expand();
|
||||
}
|
||||
|
||||
const getNode = (nodeKey: any) => {
|
||||
let node = treeRef.value.getNode(nodeKey);
|
||||
if (!node) {
|
||||
throw new Error('未找到节点: ' + nodeKey);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
reloadNode,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.instances-box {
|
||||
overflow: 'auto';
|
||||
|
||||
.el-tree {
|
||||
display: inline-block;
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
38
mayfly_go_web/src/views/ops/component/tag.ts
Normal file
38
mayfly_go_web/src/views/ops/component/tag.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
export class TagTreeNode {
|
||||
/**
|
||||
* 节点id
|
||||
*/
|
||||
key: any
|
||||
|
||||
/**
|
||||
* 节点名称
|
||||
*/
|
||||
label: string
|
||||
|
||||
/**
|
||||
* 树节点类型
|
||||
*/
|
||||
type: any
|
||||
|
||||
isLeaf: boolean = false;
|
||||
|
||||
params: any;
|
||||
|
||||
static TagPath = -1;
|
||||
|
||||
constructor(key: any, label: string, type?: any) {
|
||||
this.key = key;
|
||||
this.label = label;
|
||||
this.type = type || TagTreeNode.TagPath;
|
||||
}
|
||||
|
||||
withIsLeaf(isLeaf: boolean) {
|
||||
this.isLeaf = isLeaf;
|
||||
return this;
|
||||
}
|
||||
|
||||
withParams(params: any) {
|
||||
this.params = params;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,51 @@
|
||||
</el-row>
|
||||
<el-row type="flex">
|
||||
<el-col :span="4" style="border-left: 1px solid #eee; margin-top: 10px">
|
||||
<InstanceTree ref="instanceTreeRef" @change-instance="changeInstance" @change-schema="changeSchema"
|
||||
@clickSqlName="onClickSqlName" @clickSchemaTable="loadTableData" />
|
||||
<tag-tree ref="tagTreeRef" @node-click="nodeClick" :load="loadNode" :height="state.tagTreeHeight">
|
||||
<template #prefix="{ data }">
|
||||
<span v-if="data.type == NodeType.DbInst">
|
||||
<el-popover placement="right-start" title="数据库实例信息" trigger="hover" :width="210">
|
||||
<template #reference>
|
||||
<el-icon>
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
</template>
|
||||
<template #default>
|
||||
<el-form class="instances-pop-form" label-width="55px" :size="'small'">
|
||||
<el-form-item label="类型:">{{ data.params.type }}</el-form-item>
|
||||
<el-form-item label="链接:">{{ data.params.host }}:{{
|
||||
data.params.port
|
||||
}}</el-form-item>
|
||||
<el-form-item label="用户:">{{ data.params.username }}</el-form-item>
|
||||
<el-form-item v-if="data.params.remark" label="备注:">{{
|
||||
data.params.remark
|
||||
}}</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
</el-popover>
|
||||
</span>
|
||||
|
||||
<el-icon v-if="data.type == NodeType.Db">
|
||||
<Coin color="#67c23a" />
|
||||
</el-icon>
|
||||
|
||||
<el-icon v-if="data.type == NodeType.TableMenu">
|
||||
<Calendar color="#409eff" />
|
||||
</el-icon>
|
||||
<el-tooltip v-if="data.type == NodeType.Table" effect="customized"
|
||||
:content="data.params.tableComment" placement="top-end">
|
||||
<el-icon>
|
||||
<Calendar color="#409eff" />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
|
||||
<el-icon v-if="data.type == NodeType.SqlMenu || data.type == NodeType.Sql">
|
||||
<Files color="#f56c6c" />
|
||||
</el-icon>
|
||||
|
||||
|
||||
</template>
|
||||
</tag-tree>
|
||||
</el-col>
|
||||
<el-col :span="20">
|
||||
<el-container id="data-exec" style="border-left: 1px solid #eee; margin-top: 10px">
|
||||
@@ -53,19 +96,33 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, reactive, ref, Ref, toRefs } from 'vue';
|
||||
import { onMounted, reactive, ref, toRefs } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import { language as sqlLanguage } from 'monaco-editor/esm/vs/basic-languages/mysql/mysql.js';
|
||||
import * as monaco from 'monaco-editor';
|
||||
import { editor, languages, Position } from 'monaco-editor';
|
||||
import InstanceTree from '@/views/ops/db/component/InstanceTree.vue';
|
||||
|
||||
import { DbInst, TabInfo, TabType } from './db'
|
||||
import TableData from './component/tab/TableData.vue'
|
||||
import Query from './component/tab/Query.vue'
|
||||
import { TagTreeNode } from '../component/tag';
|
||||
import TagTree from '../component/TagTree.vue';
|
||||
import { dbApi } from './api';
|
||||
|
||||
const instanceTreeRef = ref(null) as Ref;
|
||||
/**
|
||||
* 树节点类型
|
||||
*/
|
||||
class NodeType {
|
||||
static DbInst = 1
|
||||
static Db = 2
|
||||
static TableMenu = 3;
|
||||
static SqlMenu = 4;
|
||||
static Table = 5;
|
||||
static Sql = 6;
|
||||
}
|
||||
|
||||
const tagTreeRef: any = ref(null)
|
||||
|
||||
const tabs: Map<string, TabInfo> = new Map();
|
||||
const state = reactive({
|
||||
@@ -74,10 +131,11 @@ const state = reactive({
|
||||
*/
|
||||
nowDbInst: {} as DbInst,
|
||||
db: '', // 当前操作的数据库
|
||||
activeName: 'Query',
|
||||
activeName: '',
|
||||
tabs,
|
||||
dataTabsTableHeight: '600',
|
||||
editorHeight: '600',
|
||||
tagTreeHeight: window.innerHeight - 178 + 'px',
|
||||
genSqlDialog: {
|
||||
visible: false,
|
||||
sql: '',
|
||||
@@ -99,15 +157,140 @@ onMounted(() => {
|
||||
* 设置editor高度和数据表高度
|
||||
*/
|
||||
const setHeight = () => {
|
||||
// 默认300px
|
||||
// state.monacoOptions.height = window.innerHeight - 518 + 'px'
|
||||
state.editorHeight = window.innerHeight - 518 + 'px';
|
||||
state.dataTabsTableHeight = window.innerHeight - 219 - 36 + 'px';
|
||||
state.tagTreeHeight = window.innerHeight - 165 + 'px';
|
||||
};
|
||||
|
||||
// 选择数据库实例
|
||||
const changeInstance = (inst: any, fn?: Function) => {
|
||||
fn && fn()
|
||||
/**
|
||||
* instmap; tagPaht -> redis info[]
|
||||
*/
|
||||
const instMap: Map<string, any[]> = new Map();
|
||||
|
||||
const getInsts = async () => {
|
||||
const res = await dbApi.dbs.request({ pageNum: 1, pageSize: 1000, })
|
||||
if (!res.total) return
|
||||
for (const db of res.list) {
|
||||
const tagPath = db.tagPath;
|
||||
let redisInsts = instMap.get(tagPath) || [];
|
||||
redisInsts.push(db);
|
||||
instMap.set(tagPath, redisInsts);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载树节点
|
||||
* @param {Object} node
|
||||
* @param {Object} resolve
|
||||
*/
|
||||
const loadNode = async (node: any) => {
|
||||
// 一级为tagPath
|
||||
if (node.level === 0) {
|
||||
await getInsts();
|
||||
const tagPaths = instMap.keys();
|
||||
const tagNodes = [];
|
||||
for (let tagPath of tagPaths) {
|
||||
tagNodes.push(new TagTreeNode(tagPath, tagPath));
|
||||
}
|
||||
return tagNodes;
|
||||
}
|
||||
|
||||
const data = node.data;
|
||||
const nodeType = data.type;
|
||||
const params = data.params;
|
||||
|
||||
// 点击tagPath -> 加载数据库实例信息列表
|
||||
if (nodeType === TagTreeNode.TagPath) {
|
||||
const dbInfos = instMap.get(data.key)
|
||||
return dbInfos?.map((x: any) => {
|
||||
return new TagTreeNode(`${data.key}.${x.id}`, x.name, NodeType.DbInst).withParams(x);
|
||||
});
|
||||
}
|
||||
|
||||
// 点击数据库实例 -> 加载库列表
|
||||
if (nodeType === NodeType.DbInst) {
|
||||
const dbs = params.database.split(' ');
|
||||
return dbs.map((x: any) => {
|
||||
return new TagTreeNode(`${data.key}.${x}`, x, NodeType.Db).withParams({
|
||||
tagPath: params.tagPath,
|
||||
id: params.id,
|
||||
name: params.name,
|
||||
type: params.type,
|
||||
dbs: dbs,
|
||||
db: x
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 点击数据库 -> 加载 表&Sql 菜单
|
||||
if (nodeType === NodeType.Db) {
|
||||
return [new TagTreeNode(`${params.id}.${params.db}.table-menu`, '表', NodeType.TableMenu).withParams(params),
|
||||
new TagTreeNode(getSqlMenuNodeKey(params.id, params.db), 'SQL', NodeType.SqlMenu).withParams(params)];
|
||||
}
|
||||
|
||||
// 点击表菜单 -> 加载表列表
|
||||
if (nodeType === NodeType.TableMenu) {
|
||||
return await getTables(params);
|
||||
}
|
||||
|
||||
if (nodeType === NodeType.SqlMenu) {
|
||||
return await loadSqls(params.id, params.db, params.dbs);
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
const nodeClick = async (data: any) => {
|
||||
const params = data.params;
|
||||
const nodeKey = data.key;
|
||||
const dataType = data.type;
|
||||
// 点击数据库,修改当前数据库信息
|
||||
if (dataType === NodeType.Db || dataType === NodeType.SqlMenu || dataType === NodeType.TableMenu) {
|
||||
changeSchema({ id: params.id, name: params.name, type: params.type, tagPath: params.tagPath }, params.db);
|
||||
return;
|
||||
}
|
||||
|
||||
// 点击表加载表数据tab
|
||||
if (dataType === NodeType.Table) {
|
||||
await loadTableData({ id: params.id, nodeKey: nodeKey }, params.db, params.tableName);
|
||||
return;
|
||||
}
|
||||
|
||||
// 点击表加载表数据tab
|
||||
if (dataType === NodeType.Sql) {
|
||||
await addQueryTab({ id: params.id, nodeKey: nodeKey, dbs: params.dbs }, params.db, params.sqlName);
|
||||
}
|
||||
}
|
||||
|
||||
const getTables = async (params: any) => {
|
||||
const { id, db } = params;
|
||||
let tables = await DbInst.getInst(id).loadTables(db);
|
||||
return tables.map((x: any) => {
|
||||
return new TagTreeNode(`${id}.${db}.${x.tableName}`, x.tableName, NodeType.Table).withIsLeaf(true).withParams({
|
||||
id,
|
||||
db,
|
||||
tableName: x.tableName,
|
||||
tableComment: x.tableComment,
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载用户保存的sql脚本
|
||||
*
|
||||
* @param inst
|
||||
* @param schema
|
||||
*/
|
||||
const loadSqls = async (id: any, db: string, dbs: any) => {
|
||||
const sqls = await dbApi.getSqlNames.request({ id: id, db: db, })
|
||||
return sqls.map((x: any) => {
|
||||
return new TagTreeNode(`${id}.${db}.${x.name}`, x.name, NodeType.Sql).withIsLeaf(true).withParams({
|
||||
id,
|
||||
db,
|
||||
dbs,
|
||||
sqlName: x.name,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 选择数据库
|
||||
@@ -132,6 +315,7 @@ const loadTableData = async (inst: any, schema: string, tableName: string) => {
|
||||
}
|
||||
tab = new TabInfo();
|
||||
tab.key = label;
|
||||
tab.treeNodeKey = inst.nodeKey;
|
||||
tab.dbId = inst.id;
|
||||
tab.db = schema;
|
||||
tab.type = TabType.TableData;
|
||||
@@ -147,6 +331,7 @@ const addQueryTab = async (inst: any, db: string, sqlName: string = '') => {
|
||||
ElMessage.warning('请选择数据库实例及对应的schema')
|
||||
return
|
||||
}
|
||||
|
||||
const dbId = inst.id;
|
||||
let label;
|
||||
// 存在sql模板名,则该模板名只允许一个tab
|
||||
@@ -168,12 +353,13 @@ const addQueryTab = async (inst: any, db: string, sqlName: string = '') => {
|
||||
}
|
||||
tab = new TabInfo();
|
||||
tab.key = label;
|
||||
tab.treeNodeKey = inst.nodeKey;
|
||||
tab.dbId = dbId;
|
||||
tab.db = db;
|
||||
tab.type = TabType.Query;
|
||||
tab.params = {
|
||||
sqlName: sqlName,
|
||||
dbs: instanceTreeRef.value.getSchemas(dbId)
|
||||
dbs: inst.dbs,
|
||||
}
|
||||
state.tabs.set(label, tab)
|
||||
registerSqlCompletionItemProvider();
|
||||
@@ -204,7 +390,9 @@ const onTabChange = () => {
|
||||
state.db = '';
|
||||
return;
|
||||
}
|
||||
state.nowDbInst = DbInst.getInst(state.tabs.get(state.activeName)?.dbId);
|
||||
const nowTab = state.tabs.get(state.activeName);
|
||||
state.nowDbInst = DbInst.getInst(nowTab?.dbId);
|
||||
state.db = nowTab?.db as string;
|
||||
}
|
||||
|
||||
const onGenerateInsertSql = async (sql: string) => {
|
||||
@@ -212,19 +400,19 @@ const onGenerateInsertSql = async (sql: string) => {
|
||||
state.genSqlDialog.visible = true;
|
||||
};
|
||||
|
||||
const onClickSqlName = (inst: any, schema: string, sqlName: string) => {
|
||||
addQueryTab(inst, schema, sqlName);
|
||||
}
|
||||
|
||||
const reloadSqls = (dbId: number, db: string) => {
|
||||
instanceTreeRef.value.reloadSqls({ id: dbId }, db);
|
||||
tagTreeRef.value.reloadNode(getSqlMenuNodeKey(dbId, db));
|
||||
}
|
||||
|
||||
const deleteSqlScript = (ti: TabInfo) => {
|
||||
instanceTreeRef.value.reloadSqls({ id: ti.dbId }, ti.db);
|
||||
reloadSqls(ti.dbId, ti.db);
|
||||
onRemoveTab(ti.key);
|
||||
}
|
||||
|
||||
const getSqlMenuNodeKey = (dbId: number, db: string) => {
|
||||
return `${dbId}.${db}.sql-menu`
|
||||
}
|
||||
|
||||
const registerSqlCompletionItemProvider = () => {
|
||||
// 参考 https://microsoft.github.io/monaco-editor/playground.html#extending-language-services-completion-provider-example
|
||||
self.completionItemProvider = self.completionItemProvider || monaco.languages.registerCompletionItemProvider('sql', {
|
||||
@@ -288,7 +476,7 @@ const registerSqlCompletionItemProvider = () => {
|
||||
let str = lastToken.substring(0, lastToken.lastIndexOf('.'))
|
||||
// 库.表名联想
|
||||
|
||||
if (dbs.filter((a: any) => a.name === str)?.length > 0) {
|
||||
if (dbs && dbs.filter((a: any) => a === str)?.length > 0) {
|
||||
let tables = await dbInst.loadTables(str)
|
||||
let suggestions: languages.CompletionItem[] = []
|
||||
for (let item of tables) {
|
||||
@@ -390,17 +578,19 @@ const registerSqlCompletionItemProvider = () => {
|
||||
})
|
||||
|
||||
// 库名提示
|
||||
if (dbs) {
|
||||
dbs.forEach((a: any) => {
|
||||
suggestions.push({
|
||||
label: {
|
||||
label: a.name,
|
||||
label: a,
|
||||
description: 'schema'
|
||||
},
|
||||
kind: monaco.languages.CompletionItemKind.Folder,
|
||||
insertText: a.name,
|
||||
insertText: a,
|
||||
range
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
const tables = await dbInst.loadTables(db);
|
||||
// 表名联想
|
||||
@@ -518,4 +708,10 @@ select * from invisit v where`.match(/(join|from)\s+(\w*-?\w*\.?\w+)\s*(as)?\s*(
|
||||
.update_field_active {
|
||||
background-color: var(--el-color-success)
|
||||
}
|
||||
|
||||
.instances-pop-form {
|
||||
.el-form-item {
|
||||
margin-bottom: unset;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,346 +0,0 @@
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<!-- 第四级 02:sql -->
|
||||
<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>
|
||||
@@ -411,6 +411,11 @@ export class TabInfo {
|
||||
*/
|
||||
key: string
|
||||
|
||||
/**
|
||||
* 菜单树节点key
|
||||
*/
|
||||
treeNodeKey: string
|
||||
|
||||
/**
|
||||
* 数据库实例id
|
||||
*/
|
||||
|
||||
@@ -446,7 +446,6 @@ const showCreateFileDialog = (node: any) => {
|
||||
|
||||
const createFile = async () => {
|
||||
const node = state.createFileDialog.node;
|
||||
console.log(node.data);
|
||||
const name = state.createFileDialog.name;
|
||||
const type = state.createFileDialog.type;
|
||||
const path = node.data.path + '/' + name;
|
||||
|
||||
@@ -2,12 +2,50 @@
|
||||
<div>
|
||||
<el-row>
|
||||
<el-col :span="4">
|
||||
<mongo-instance-tree @init-load-instances="loadInstances" @change-instance="changeInstance"
|
||||
@load-table-names="loadTableNames" @load-table-data="changeCollection"
|
||||
:instances="state.instances" />
|
||||
<tag-tree @node-click="nodeClick" :load="loadNode">
|
||||
<template #prefix="{ data }">
|
||||
<span v-if="data.type == NodeType.Mongo">
|
||||
<el-popover placement="right-start" title="mongo实例信息" trigger="hover" :width="210">
|
||||
<template #reference>
|
||||
<el-icon>
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
</template>
|
||||
<template #default>
|
||||
<el-form class="instances-pop-form" label-width="50px" :size="'small'">
|
||||
<el-form class="instances-pop-form" label-width="55px" :size="'small'">
|
||||
<el-form-item label="名称:">{{ data.params.name }}</el-form-item>
|
||||
<el-form-item label="链接:">{{ data.params.uri }}</el-form-item>
|
||||
</el-form>
|
||||
</el-form>
|
||||
</template>
|
||||
</el-popover>
|
||||
</span>
|
||||
|
||||
<el-icon v-if="data.type == NodeType.Dbs">
|
||||
<Coin color="#67c23a" />
|
||||
</el-icon>
|
||||
|
||||
<el-icon v-if="data.type == NodeType.Coll || data.type == NodeType.CollMenu">
|
||||
<Document class="color-primary" />
|
||||
</el-icon>
|
||||
</template>
|
||||
|
||||
<template #label="{ data }">
|
||||
<span v-if="data.type == NodeType.Dbs">
|
||||
{{ data.params.dbName }}
|
||||
<span style="color: #8492a6;font-size: 13px">
|
||||
[{{ formatByteSize(data.params.size) }}]
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span v-else>{{ data.label }}</span>
|
||||
</template>
|
||||
</tag-tree>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="20">
|
||||
<el-container id="data-exec" style="border: 1px solid #eee; margin-top: 1px">
|
||||
<el-container id="mongo-tab" style="border: 1px solid #eee; margin-top: 1px">
|
||||
<el-tabs @tab-remove="removeDataTab" style="width: 100%; margin-left: 5px"
|
||||
v-model="state.activeName">
|
||||
<el-tab-pane closable v-for="dt in state.dataTabs" :key="dt.key" :label="dt.label"
|
||||
@@ -67,8 +105,8 @@
|
||||
<el-dialog width="600px" title="find参数" v-model="findDialog.visible">
|
||||
<el-form label-width="70px">
|
||||
<el-form-item label="filter">
|
||||
<el-input v-model="findDialog.findParam.filter" type="textarea" :rows="6" clearable
|
||||
auto-complete="off"></el-input>
|
||||
<monaco-editor style="width: 100%;" height="150px" ref="monacoEditorRef"
|
||||
v-model="findDialog.findParam.filter" language="json" />
|
||||
</el-form-item>
|
||||
<el-form-item label="sort">
|
||||
<el-input v-model="findDialog.findParam.sort" type="textarea" :rows="3" clearable
|
||||
@@ -116,7 +154,19 @@ import { ElMessage } from 'element-plus';
|
||||
|
||||
import { isTrue, notBlank } from '@/common/assert';
|
||||
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
|
||||
import MongoInstanceTree from '@/views/ops/mongo/MongoInstanceTree.vue';
|
||||
import { TagTreeNode } from '../component/tag';
|
||||
import TagTree from '../component/TagTree.vue';
|
||||
import { formatByteSize } from '@/common/utils/format';
|
||||
|
||||
/**
|
||||
* 树节点类型
|
||||
*/
|
||||
class NodeType {
|
||||
static Mongo = 1
|
||||
static Dbs = 2
|
||||
static CollMenu = 3
|
||||
static Coll = 4
|
||||
}
|
||||
|
||||
const findParamInputRef: any = ref(null);
|
||||
const state = reactive({
|
||||
@@ -142,7 +192,6 @@ const state = reactive({
|
||||
doc: '',
|
||||
item: {} as any,
|
||||
},
|
||||
instances: { tags: {}, tree: {}, dbs: {}, tables: {} }
|
||||
});
|
||||
|
||||
const {
|
||||
@@ -151,28 +200,110 @@ const {
|
||||
jsonEditorDialog,
|
||||
} = toRefs(state)
|
||||
|
||||
const changeInstance = async (inst: any, fn: Function) => {
|
||||
if (inst) {
|
||||
if (!state.instances.dbs[inst.id]) {
|
||||
/**
|
||||
* instmap; tagPaht -> mongo info[]
|
||||
*/
|
||||
const instMap: Map<string, any[]> = new Map();
|
||||
|
||||
const getInsts = async () => {
|
||||
const res = await mongoApi.mongoList.request({ pageNum: 1, pageSize: 1000, });
|
||||
if (!res.total) return
|
||||
for (const mongoInfo of res.list) {
|
||||
const tagPath = mongoInfo.tagPath;
|
||||
let mongoInsts = instMap.get(tagPath) || [];
|
||||
mongoInsts.push(mongoInfo);
|
||||
instMap.set(tagPath, mongoInsts);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载文件树节点
|
||||
* @param {Object} node
|
||||
* @param {Object} resolve
|
||||
*/
|
||||
const loadNode = async (node: any) => {
|
||||
// 一级为tagPath
|
||||
if (node.level === 0) {
|
||||
await getInsts();
|
||||
const tagPaths = instMap.keys();
|
||||
const tagNodes = [];
|
||||
for (let tagPath of tagPaths) {
|
||||
tagNodes.push(new TagTreeNode(tagPath, tagPath));
|
||||
}
|
||||
return tagNodes;
|
||||
}
|
||||
|
||||
const data = node.data;
|
||||
const params = data.params;
|
||||
const nodeType = data.type;
|
||||
|
||||
// 点击标签 -> 显示mongo信息列表
|
||||
if (nodeType === TagTreeNode.TagPath) {
|
||||
const mongoInfos = instMap.get(data.key)
|
||||
return mongoInfos?.map((x: any) => {
|
||||
return new TagTreeNode(`${data.key}.${x.id}`, x.name, NodeType.Mongo).withParams(x);
|
||||
});
|
||||
}
|
||||
|
||||
// 点击mongo -> 加载mongo数据库列表
|
||||
if (nodeType === NodeType.Mongo) {
|
||||
return await getDatabases(params);
|
||||
}
|
||||
|
||||
// 点击数据库列表 -> 加载数据库下拥有的菜单列表
|
||||
if (nodeType === NodeType.Dbs) {
|
||||
return [new TagTreeNode(`${params.id}.${params.dbName}.mongo-coll`, '集合', NodeType.CollMenu).withParams(params)];
|
||||
}
|
||||
|
||||
// 点击数据库集合节点 -> 加载集合列表
|
||||
if (nodeType === NodeType.CollMenu) {
|
||||
return await getCollections(params.id, params.dbName)
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取实例的所有库信息
|
||||
* @param inst 实例信息
|
||||
*/
|
||||
const getDatabases = async (inst: any) => {
|
||||
const res = await mongoApi.databases.request({ id: inst.id });
|
||||
state.instances.dbs[inst.id] = res.Databases;
|
||||
fn && fn(res.Databases)
|
||||
return res.Databases.map((x: any) => {
|
||||
const dbName = x.Name;
|
||||
return new TagTreeNode(`${inst.id}.${dbName}`, dbName, NodeType.Dbs).withParams({
|
||||
id: inst.id,
|
||||
dbName,
|
||||
size: x.SizeOnDisk,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取集合列表信息
|
||||
* @param inst
|
||||
*/
|
||||
const getCollections = async (id: any, database: string) => {
|
||||
const colls = await mongoApi.collections.request({ id, database });
|
||||
return colls.map((x: any) => {
|
||||
return new TagTreeNode(`${id}.${database}.${x}`, x, NodeType.Coll).withIsLeaf(true).withParams({
|
||||
id,
|
||||
database,
|
||||
collection: x,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const nodeClick = (data: any) => {
|
||||
// 点击集合
|
||||
if (data.type === NodeType.Coll) {
|
||||
const { id, database, collection } = data.params;
|
||||
changeCollection(id, database, collection);
|
||||
}
|
||||
}
|
||||
|
||||
const loadTableNames = async (inst: any, database: string, fn: Function) => {
|
||||
let tbs = await mongoApi.collections.request({ id: inst.id, database });
|
||||
let tables = [];
|
||||
for (let tb of tbs) {
|
||||
tables.push({ tableName: tb, show: true })
|
||||
}
|
||||
state.instances.tables[inst.id + database] = tables
|
||||
fn(tables)
|
||||
}
|
||||
|
||||
const changeCollection = (inst: any, schema: string, collection: string) => {
|
||||
const label = `${inst.id}:\`${schema}\`.${collection}`;
|
||||
const changeCollection = (id: any, schema: string, collection: string) => {
|
||||
const label = `${id}:\`${schema}\`.${collection}`;
|
||||
let dataTab = state.dataTabs[label];
|
||||
if (!dataTab) {
|
||||
// 默认查询参数
|
||||
@@ -186,7 +317,7 @@ const changeCollection = (inst: any, schema: string, collection: string) => {
|
||||
key: label,
|
||||
label: label,
|
||||
name: label,
|
||||
mongoId: inst.id,
|
||||
mongoId: id,
|
||||
database: schema,
|
||||
collection,
|
||||
datas: [],
|
||||
@@ -359,28 +490,13 @@ const removeDataTab = (targetName: string) => {
|
||||
delete state.dataTabs[targetName];
|
||||
};
|
||||
|
||||
const loadInstances = async () => {
|
||||
const res = await mongoApi.mongoList.request({ pageNum: 1, pageSize: 1000, });
|
||||
if (!res.total) return
|
||||
state.instances = { tags: {}, tree: {}, dbs: {}, tables: {} }; // 初始化变量
|
||||
for (const db of res.list) {
|
||||
let arr = state.instances.tree[db.tagId] || []
|
||||
const { tagId, tagPath } = db
|
||||
// tags
|
||||
state.instances.tags[db.tagId] = { tagId, tagPath }
|
||||
// 实例
|
||||
arr.push(db)
|
||||
state.instances.tree[db.tagId] = arr;
|
||||
}
|
||||
}
|
||||
|
||||
const getNowDataTab = () => {
|
||||
return state.dataTabs[state.activeName]
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
<style lang="scss">
|
||||
.mongo-doc-btns {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
@@ -388,4 +504,14 @@ const getNowDataTab = () => {
|
||||
top: 2px;
|
||||
max-width: 120px;
|
||||
}
|
||||
|
||||
#mongo-tab {
|
||||
.el-tabs__header {
|
||||
margin: 0 0 5px;
|
||||
|
||||
.el-tabs__item {
|
||||
padding: 0 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,9 +2,38 @@
|
||||
<div>
|
||||
<el-row>
|
||||
<el-col :span="4">
|
||||
<redis-instance-tree @init-load-instances="initLoadInstances" @change-instance="changeInstance"
|
||||
@change-schema="loadInitSchema" :instances="state.instances" />
|
||||
<el-row type="flex" justify="space-between">
|
||||
<el-col :span="24" class="el-scrollbar flex-auto">
|
||||
<tag-tree @node-click="nodeClick" :load="loadNode">
|
||||
<template #prefix="{ data }">
|
||||
<span v-if="data.type == NodeType.Redis">
|
||||
<el-popover placement="right-start" title="redis实例信息" trigger="hover" :width="210">
|
||||
<template #reference>
|
||||
<el-icon>
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
</template>
|
||||
<template #default>
|
||||
<el-form class="instances-pop-form" label-width="50px" :size="'small'">
|
||||
<el-form-item label="名称:">{{ data.params.name }}</el-form-item>
|
||||
<el-form-item label="链接:">{{ data.params.host }}</el-form-item>
|
||||
<el-form-item label="备注:">{{
|
||||
data.params.remark
|
||||
}}</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
</el-popover>
|
||||
</span>
|
||||
|
||||
<el-icon v-if="data.type == NodeType.Db">
|
||||
<Coin color="#67c23a" />
|
||||
</el-icon>
|
||||
</template>
|
||||
</tag-tree>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="20" style="border-left: 1px solid var(--el-card-border-color);">
|
||||
<div class="mt10 ml5">
|
||||
<el-col>
|
||||
@@ -87,17 +116,26 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { redisApi } from './api';
|
||||
import { toRefs, reactive } from 'vue';
|
||||
import { toRefs, reactive, onMounted } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import HashValue from './HashValue.vue';
|
||||
import StringValue from './StringValue.vue';
|
||||
import SetValue from './SetValue.vue';
|
||||
import ListValue from './ListValue.vue';
|
||||
import { isTrue, notBlank, notNull } from '@/common/assert';
|
||||
import { TagTreeNode } from '../component/tag';
|
||||
import TagTree from '../component/TagTree.vue';
|
||||
|
||||
import RedisInstanceTree from '@/views/ops/redis/RedisInstanceTree.vue';
|
||||
/**
|
||||
* 树节点类型
|
||||
*/
|
||||
class NodeType {
|
||||
static Redis = 1
|
||||
static Db = 2
|
||||
}
|
||||
|
||||
const state = reactive({
|
||||
instanceMenuMaxHeight: '600',
|
||||
loading: false,
|
||||
tags: [],
|
||||
redisList: [] as any,
|
||||
@@ -137,7 +175,6 @@ const state = reactive({
|
||||
},
|
||||
keys: [],
|
||||
dbsize: 0,
|
||||
instances: { tags: {}, tree: {}, dbs: {}, tables: {} }
|
||||
});
|
||||
|
||||
const {
|
||||
@@ -149,6 +186,103 @@ const {
|
||||
listValueDialog,
|
||||
} = toRefs(state)
|
||||
|
||||
|
||||
onMounted(async () => {
|
||||
setHeight();
|
||||
})
|
||||
|
||||
const setHeight = () => {
|
||||
state.instanceMenuMaxHeight = window.innerHeight - 115 + 'px';
|
||||
}
|
||||
|
||||
/**
|
||||
* instmap; tagPaht -> redis info[]
|
||||
*/
|
||||
const instMap: Map<string, any[]> = new Map();
|
||||
|
||||
const getInsts = async () => {
|
||||
const res = await redisApi.redisList.request({});
|
||||
if (!res.total) return
|
||||
for (const redisInfo of res.list) {
|
||||
const tagPath = redisInfo.tagPath;
|
||||
let redisInsts = instMap.get(tagPath) || [];
|
||||
redisInsts.push(redisInfo);
|
||||
instMap.set(tagPath, redisInsts);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载文件树节点
|
||||
* @param {Object} node
|
||||
* @param {Object} resolve
|
||||
*/
|
||||
const loadNode = async (node: any) => {
|
||||
// 一级为tagPath
|
||||
if (node.level === 0) {
|
||||
await getInsts();
|
||||
const tagPaths = instMap.keys();
|
||||
const tagNodes = [];
|
||||
for (let tagPath of tagPaths) {
|
||||
tagNodes.push(new TagTreeNode(tagPath, tagPath));
|
||||
}
|
||||
return tagNodes;
|
||||
}
|
||||
|
||||
const data = node.data;
|
||||
// 点击tagPath -> 加载数据库信息列表
|
||||
if (data.type === TagTreeNode.TagPath) {
|
||||
const redisInfos = instMap.get(data.key)
|
||||
return redisInfos?.map((x: any) => {
|
||||
return new TagTreeNode(`${data.key}.${x.id}`, x.name, NodeType.Redis).withParams(x);
|
||||
});
|
||||
}
|
||||
|
||||
// 点击redis实例 -> 加载库列表
|
||||
if (data.type === NodeType.Redis) {
|
||||
return await getDbs(data.params);
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
const nodeClick = (data: any) => {
|
||||
// 点击库事件
|
||||
if (data.type === NodeType.Db) {
|
||||
resetScanParam();
|
||||
state.scanParam.id = data.params.id;
|
||||
state.scanParam.db = data.params.db;
|
||||
scan();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有库信息
|
||||
* @param redisInfo redis信息
|
||||
*/
|
||||
const getDbs = async (redisInfo: any) => {
|
||||
let dbs: TagTreeNode[] = redisInfo.db.split(',').map((x: string) => {
|
||||
return new TagTreeNode(x, x, NodeType.Db).withIsLeaf(true).withParams({
|
||||
id: redisInfo.id,
|
||||
db: x,
|
||||
name: `db${x}`,
|
||||
keys: 0,
|
||||
})
|
||||
})
|
||||
const res = await redisApi.redisInfo.request({ id: redisInfo.id, host: redisInfo.host, section: "Keyspace" });
|
||||
for (let db in res.Keyspace) {
|
||||
for (let d of dbs) {
|
||||
if (db == d.params.name) {
|
||||
d.params.keys = res.Keyspace[db]?.split(',')[0]?.split('=')[1] || 0
|
||||
}
|
||||
}
|
||||
}
|
||||
// 替换label
|
||||
dbs.forEach((e: any) => {
|
||||
e.label = `${e.params.name} [${e.params.keys}]`
|
||||
});
|
||||
return dbs;
|
||||
}
|
||||
|
||||
const scan = async () => {
|
||||
isTrue(state.scanParam.id != null, '请先选择redis');
|
||||
notBlank(state.scanParam.count, 'count不能为空');
|
||||
@@ -312,49 +446,12 @@ const getTypeColor = (type: string) => {
|
||||
return '#A8DEE0';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const initLoadInstances = async () => {
|
||||
const res = await redisApi.redisList.request({});
|
||||
if (!res.total) return
|
||||
state.instances = { tags: {}, tree: {}, dbs: {}, tables: {} }; // 初始化变量
|
||||
for (const db of res.list) {
|
||||
let arr = state.instances.tree[db.tagId] || []
|
||||
const { tagId, tagPath } = db
|
||||
// tags
|
||||
state.instances.tags[db.tagId] = { tagId, tagPath }
|
||||
// 实例
|
||||
arr.push(db)
|
||||
state.instances.tree[db.tagId] = arr;
|
||||
}
|
||||
}
|
||||
|
||||
const changeInstance = async (inst: any, fn: Function) => {
|
||||
let dbs = inst.db.split(',').map((x: string) => {
|
||||
return { name: `db${x}`, keys: 0 }
|
||||
})
|
||||
const res = await redisApi.redisInfo.request({ id: inst.id, host: inst.host, section: "Keyspace" });
|
||||
for (let db in res.Keyspace) {
|
||||
for (let d of dbs) {
|
||||
if (db == d.name) {
|
||||
d.keys = res.Keyspace[db]?.split(',')[0]?.split('=')[1] || 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.instances.dbs[inst.id] = dbs
|
||||
fn && fn(dbs)
|
||||
}
|
||||
|
||||
/** 初始化加载db数据 */
|
||||
const loadInitSchema = (inst: any, schema: string) => {
|
||||
state.scanParam.id = inst.id
|
||||
state.scanParam.db = schema.replace('db', '')
|
||||
scan()
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
<style lang="scss">
|
||||
.instances-pop-form {
|
||||
.el-form-item {
|
||||
margin-bottom: unset;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -32,6 +32,13 @@
|
||||
</el-popover>
|
||||
</template></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="db" label="库号:" required>
|
||||
<el-select @change="changeDb" v-model="dbList" multiple allow-create filterable
|
||||
placeholder="请选择可操作库号" style="width: 100%">
|
||||
<el-option v-for="db in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]" :key="db"
|
||||
:label="db" :value="db" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item prop="remark" label="备注:">
|
||||
<el-input v-model.trim="form.remark" auto-complete="off" type="textarea"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
<template>
|
||||
<tag-menu :instanceMenuMaxHeight="state.instanceMenuMaxHeight" :tags="instances.tags" ref="menuRef">
|
||||
<template #submenu="props">
|
||||
<el-sub-menu v-for="inst in instances.tree[props.tag.tagId]" :index="'redis-' + inst.id"
|
||||
:key="'redis-' + inst.id" @click.stop="changeInstance(inst)">
|
||||
<template #title>
|
||||
<el-popover placement="right-start" title="redis实例信息" 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.host }}</el-form-item>
|
||||
<el-form-item label="备注:">{{ inst.remark }}</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
</el-popover>
|
||||
</template>
|
||||
<!-- 第三级:数据库 -->
|
||||
<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>
|
||||
<el-icon>
|
||||
<Coin color="#67c23a" />
|
||||
</el-icon>
|
||||
<span class="checked-schema">
|
||||
{{ db.name }} [{{ db.keys }}]
|
||||
</span>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</el-sub-menu>
|
||||
</template>
|
||||
</tag-menu>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeMount, reactive, Ref, ref } from 'vue';
|
||||
import TagMenu from '../component/TagMenu.vue';
|
||||
|
||||
defineProps({
|
||||
instances: {
|
||||
type: Object, required: true
|
||||
},
|
||||
})
|
||||
|
||||
const emits = defineEmits(['initLoadInstances', 'changeInstance', 'changeSchema'])
|
||||
|
||||
onBeforeMount(async () => {
|
||||
await initLoadInstances();
|
||||
setHeight();
|
||||
})
|
||||
|
||||
const setHeight = () => {
|
||||
state.instanceMenuMaxHeight = window.innerHeight - 115 + 'px';
|
||||
}
|
||||
|
||||
const state = reactive({
|
||||
instanceMenuMaxHeight: '800px',
|
||||
nowSchema: '',
|
||||
filterParam: {},
|
||||
loading: {},
|
||||
})
|
||||
|
||||
/**
|
||||
* 初始化加载实例数据
|
||||
*/
|
||||
const initLoadInstances = () => {
|
||||
emits('initLoadInstances')
|
||||
}
|
||||
|
||||
/**
|
||||
* 改变选中的数据库实例
|
||||
* @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);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.instances-pop-form {
|
||||
.el-form-item {
|
||||
margin-bottom: unset;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -155,13 +155,16 @@ func doSelect(selectStmt *sqlparser.Select, execSqlReq *DbSqlExecReq) (*DbSqlExe
|
||||
selectExprsStr := sqlparser.String(selectStmt.SelectExprs)
|
||||
if selectExprsStr == "*" || strings.Contains(selectExprsStr, ".*") ||
|
||||
len(strings.Split(selectExprsStr, ",")) > 1 {
|
||||
// 如果配置为0,则不校验分页参数
|
||||
maxCount := sysapp.GetConfigApp().GetConfig(sysentity.ConfigKeyDbQueryMaxCount).IntValue(200)
|
||||
if maxCount != 0 {
|
||||
limit := selectStmt.Limit
|
||||
biz.NotNil(limit, "请完善分页信息后执行")
|
||||
count, err := strconv.Atoi(sqlparser.String(limit.Rowcount))
|
||||
biz.ErrIsNil(err, "分页参数有误")
|
||||
maxCount := sysapp.GetConfigApp().GetConfig(sysentity.ConfigKeyDbQueryMaxCount).IntValue(200)
|
||||
biz.IsTrue(count <= maxCount, fmt.Sprintf("查询结果集数需小于系统配置的%d条", maxCount))
|
||||
}
|
||||
}
|
||||
|
||||
return doRead(execSqlReq)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user