refactor: 界面优化

This commit is contained in:
meilin.huang
2023-04-05 22:41:53 +08:00
parent f6e9076a40
commit 58fb11b78f
7 changed files with 349 additions and 131 deletions

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,135 @@
<template>
<transition name="el-zoom-in-center">
<div aria-hidden="true" class="el-dropdown__popper el-popper is-light is-pure custom-contextmenu" role="tooltip"
data-popper-placement="bottom" :style="`top: ${dropdowns.y + 5}px;left: ${dropdowns.x}px;`" :key="Math.random()"
v-show="state.isShow">
<ul class="el-dropdown-menu">
<template v-for="(v, k) in state.dropdownList">
<li class="el-dropdown-menu__item" aria-disabled="false" tabindex="-1" :key="k" v-if="!v.affix"
@click="onCurrentContextmenuClick(v.contextMenuClickId)">
<SvgIcon :name="v.icon" />
<span>{{ v.txt }}</span>
</li>
</template>
</ul>
<div class="el-popper__arrow" :style="{ left: `${state.arrowLeft}px` }"></div>
</div>
</transition>
</template>
<script setup lang="ts" name="layoutTagsViewContextmenu">
import { computed, reactive, onMounted, onUnmounted, watch } from 'vue';
// 定义父组件传过来的值
const props = defineProps({
dropdown: {
type: Object,
default: () => {
return {
x: 0,
y: 0,
};
},
},
items: {
type: Array,
default: [],
},
});
// 定义子组件向父组件传值/事件
const emit = defineEmits(['currentContextmenuClick']);
// 定义变量内容
const state = reactive({
isShow: false,
dropdownList: [
{ contextMenuClickId: 0, txt: '刷新', affix: false, icon: 'RefreshRight' },
{ contextMenuClickId: 1, txt: '关闭', affix: false, icon: 'Close' },
{ contextMenuClickId: 2, txt: '关闭其他', affix: false, icon: 'CircleClose' },
{ contextMenuClickId: 3, txt: '关闭所有', affix: false, icon: 'FolderDelete' },
],
item: {} as any,
arrowLeft: 10,
});
// 父级传过来的坐标 x,y 值
const dropdowns = computed(() => {
// 117 为 `Dropdown 下拉菜单` 的宽度
if (props.dropdown.x + 117 > document.documentElement.clientWidth) {
return {
x: document.documentElement.clientWidth - 117 - 5,
y: props.dropdown.y,
};
} else {
return props.dropdown;
}
});
// 当前项菜单点击
const onCurrentContextmenuClick = (contextMenuClickId: number) => {
emit('currentContextmenuClick', { id: contextMenuClickId, item: state.item });
};
// 打开右键菜单:判断是否固定,固定则不显示关闭按钮
const openContextmenu = (item: any) => {
state.item = item;
closeContextmenu();
setTimeout(() => {
state.isShow = true;
}, 10);
};
// 关闭右键菜单
const closeContextmenu = () => {
state.isShow = false;
};
// 监听页面监听进行右键菜单的关闭
onMounted(() => {
document.body.addEventListener('click', closeContextmenu);
});
// 页面卸载时,移除右键菜单监听事件
onUnmounted(() => {
document.body.removeEventListener('click', closeContextmenu);
});
// 监听下拉菜单位置
watch(
() => props.dropdown,
({ x }) => {
if (x + 117 > document.documentElement.clientWidth) state.arrowLeft = 117 - (document.documentElement.clientWidth - x);
else state.arrowLeft = 10;
},
{
deep: true,
}
);
watch(
() => props.items,
(x: any) => {
state.dropdownList = x
},
{
deep: true,
}
);
// 暴露变量
defineExpose({
openContextmenu,
closeContextmenu,
});
</script>
<style scoped lang="scss">
.custom-contextmenu {
transform-origin: center top;
z-index: 2190;
position: fixed;
.el-dropdown-menu__item {
font-size: 12px !important;
white-space: nowrap;
i {
font-size: 12px !important;
}
}
}
</style>

View File

@@ -1,110 +1,138 @@
<template>
<transition name="el-zoom-in-center">
<div
aria-hidden="true"
class="el-dropdown__popper el-popper is-light is-pure custom-contextmenu"
role="tooltip"
data-popper-placement="bottom"
:style="`top: ${dropdowns.y + 5}px;left: ${dropdowns.x}px;`"
:key="Math.random()"
v-show="isShow"
>
<ul class="el-dropdown-menu">
<template v-for="(v, k) in dropdownList">
<li
class="el-dropdown-menu__item"
aria-disabled="false"
tabindex="-1"
:key="k"
v-if="!v.affix"
@click="onCurrentContextmenuClick(v.id)"
>
<i :class="v.icon"></i>
<span>{{ v.txt }}</span>
</li>
</template>
</ul>
<div class="el-popper__arrow" style="left: 10px"></div>
</div>
</transition>
<transition name="el-zoom-in-center">
<div
aria-hidden="true"
class="el-dropdown__popper el-popper is-light is-pure custom-contextmenu"
role="tooltip"
data-popper-placement="bottom"
:style="`top: ${dropdowns.y + 5}px;left: ${dropdowns.x}px;`"
:key="Math.random()"
v-show="state.isShow"
>
<ul class="el-dropdown-menu">
<template v-for="(v, k) in state.dropdownList">
<li
class="el-dropdown-menu__item"
aria-disabled="false"
tabindex="-1"
:key="k"
v-if="!v.affix"
@click="onCurrentContextmenuClick(v.contextMenuClickId)"
>
<SvgIcon :name="v.icon" />
<span>{{ v.txt }}</span>
</li>
</template>
</ul>
<div class="el-popper__arrow" :style="{ left: `${state.arrowLeft}px` }"></div>
</div>
</transition>
</template>
<script lang="ts">
import { computed, defineComponent, reactive, toRefs, onMounted, onUnmounted } from 'vue';
export default defineComponent({
name: 'layoutTagsViewContextmenu',
props: {
dropdown: {
type: Object,
},
},
setup(props, { emit }) {
const state = reactive({
isShow: false,
dropdownList: [
{ id: 0, txt: '刷新', affix: false, icon: 'el-icon-refresh-right' },
{ id: 1, txt: '关闭', affix: false, icon: 'el-icon-close' },
{ id: 2, txt: '关闭其他', affix: false, icon: 'el-icon-circle-close' },
{ id: 3, txt: '关闭所有', affix: false, icon: 'el-icon-folder-delete' },
{
id: 4,
txt: '当前页全屏',
affix: false,
icon: 'el-icon-full-screen',
},
],
path: {},
});
// 父级传过来的坐标 x,y 值
const dropdowns = computed(() => {
return props.dropdown;
});
// 当前项菜单点击
const onCurrentContextmenuClick = (id: number) => {
emit('currentContextmenuClick', { id, path: state.path });
};
// 打开右键菜单:判断是否固定,固定则不显示关闭按钮
const openContextmenu = (item: any) => {
state.path = item.fullPath;
item.meta.isAffix ? (state.dropdownList[1].affix = true) : (state.dropdownList[1].affix = false);
closeContextmenu();
setTimeout(() => {
state.isShow = true;
}, 10);
};
// 关闭右键菜单
const closeContextmenu = () => {
state.isShow = false;
};
// 监听页面监听进行右键菜单的关闭
onMounted(() => {
document.body.addEventListener('click', closeContextmenu);
});
// 页面卸载时,移除右键菜单监听事件
onUnmounted(() => {
document.body.removeEventListener('click', closeContextmenu);
});
return {
dropdowns,
openContextmenu,
closeContextmenu,
onCurrentContextmenuClick,
...toRefs(state),
};
},
<script setup lang="ts" name="layoutTagsViewContextmenu">
import { computed, reactive, onMounted, onUnmounted, watch } from 'vue';
// 定义父组件传过来的值
const props = defineProps({
dropdown: {
type: Object,
default: () => {
return {
x: 0,
y: 0,
};
},
},
});
// 定义子组件向父组件传值/事件
const emit = defineEmits(['currentContextmenuClick']);
// 定义变量内容
const state = reactive({
isShow: false,
dropdownList: [
{ contextMenuClickId: 0, txt: '刷新', affix: false, icon: 'RefreshRight' },
{ contextMenuClickId: 1, txt: '关闭', affix: false, icon: 'Close' },
{ contextMenuClickId: 2, txt: '关闭其他', affix: false, icon: 'CircleClose' },
{ contextMenuClickId: 3, txt: '关闭所有', affix: false, icon: 'FolderDelete' },
{
contextMenuClickId: 4,
txt: '当前页全屏',
affix: false,
icon: 'full-screen',
},
],
item: {} as any,
arrowLeft: 10,
});
// 父级传过来的坐标 x,y 值
const dropdowns = computed(() => {
// 117 为 `Dropdown 下拉菜单` 的宽度
if (props.dropdown.x + 117 > document.documentElement.clientWidth) {
return {
x: document.documentElement.clientWidth - 117 - 5,
y: props.dropdown.y,
};
} else {
return props.dropdown;
}
});
// 当前项菜单点击
const onCurrentContextmenuClick = (contextMenuClickId: number) => {
emit('currentContextmenuClick', { id: contextMenuClickId, path: state.item.fullPath });
};
// 打开右键菜单:判断是否固定,固定则不显示关闭按钮
const openContextmenu = (item: any) => {
state.item = item;
item.meta?.isAffix ? (state.dropdownList[1].affix = true) : (state.dropdownList[1].affix = false);
closeContextmenu();
setTimeout(() => {
state.isShow = true;
}, 10);
};
// 关闭右键菜单
const closeContextmenu = () => {
state.isShow = false;
};
// 监听页面监听进行右键菜单的关闭
onMounted(() => {
document.body.addEventListener('click', closeContextmenu);
});
// 页面卸载时,移除右键菜单监听事件
onUnmounted(() => {
document.body.removeEventListener('click', closeContextmenu);
});
// 监听下拉菜单位置
watch(
() => props.dropdown,
({ x }) => {
if (x + 117 > document.documentElement.clientWidth) state.arrowLeft = 117 - (document.documentElement.clientWidth - x);
else state.arrowLeft = 10;
},
{
deep: true,
}
);
// 暴露变量
defineExpose({
openContextmenu,
});
</script>
<style scoped lang="scss">
.custom-contextmenu {
transform-origin: center top;
z-index: 2190;
position: fixed;
.el-dropdown-menu__item {
font-size: 12px !important;
i {
font-size: 12px !important;
}
}
transform-origin: center top;
z-index: 2190;
position: fixed;
.el-dropdown-menu__item {
font-size: 12px !important;
white-space: nowrap;
i {
font-size: 12px !important;
}
}
}
</style>
</style>

View File

@@ -9,8 +9,8 @@
if (el) tagsRefs[k] = el;
}
">
<i class="iconfont icon-webicon318 layout-navbars-tagsview-ul-li-iconfont font14"
v-if="isActive(v)"></i>
<SvgIcon name="iconfont icon-tag-view-active" class="layout-navbars-tagsview-ul-li-iconfont font14"
v-if="isActive(v)" />
<SvgIcon :name="v.meta.icon" class="layout-navbars-tagsview-ul-li-iconfont"
v-if="!isActive(v) && themeConfig.isTagsviewIcon" />
<span>{{ v.meta.title }}</span>
@@ -82,7 +82,7 @@ const initTagsView = () => {
state.tagsViewList = getSession('tagsViewList');
} else {
state.tagsViews?.map((v: any) => {
if (v.meta.isAffix && !v.meta.isHide) state.tagsViewList.push({ ...v });
if (v.meta.isAffix && !v.meta.isHide) state.tagsViewList.push({ ...v });
});
addTagsView(route.fullPath);
}
@@ -523,4 +523,5 @@ onBeforeRouteUpdate((to) => {
.layout-navbars-tagsview-shadow {
box-shadow: rgb(0 21 41 / 4%) 0px 1px 4px;
}</style>
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<div class="instances-box layout-aside">
<div class="instances-box">
<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" />
@@ -7,7 +7,7 @@
<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"
@node-expand="treeNodeClick">
@node-expand="treeNodeClick" @node-contextmenu="nodeContextmenu">
<template #default="{ node, data }">
<span>
<span v-if="data.type == TagTreeNode.TagPath">
@@ -19,16 +19,13 @@
<span class="ml3">
<slot name="label" :data="data"> {{ data.label }}</slot>
</span>
<span class="ml3">
<slot name="option" :data="data"></slot>
</span>
</span>
</template>
</el-tree>
</el-col>
</el-row>
<contextmenu :dropdown="state.dropdown" :items="state.contextmenuItems" ref="contextmenuRef"
@currentContextmenuClick="onCurrentContextmenuClick" />
</div>
</template>
@@ -36,6 +33,7 @@
import { onMounted, reactive, ref, watch, toRefs } from 'vue';
import { TagTreeNode } from './tag';
import TagInfo from './TagInfo.vue';
import Contextmenu from '@/components/contextmenu/index.vue';
const props = defineProps({
height: {
@@ -45,6 +43,10 @@ const props = defineProps({
load: {
type: Function,
required: true,
},
loadContextmenuItems: {
type: Function,
required: false,
}
})
@@ -54,12 +56,18 @@ const treeProps = {
isLeaf: 'isLeaf',
}
const emit = defineEmits(['nodeClick'])
const emit = defineEmits(['nodeClick', 'currentContextmenuClick'])
const treeRef: any = ref(null)
const contextmenuRef = ref();
const state = reactive({
height: 600 as any,
filterText: '',
dropdown: {
x: 0,
y: 0,
},
contextmenuItems: [],
opend: {},
})
const { filterText } = toRefs(state)
@@ -101,6 +109,29 @@ const loadNode = async (node: any, resolve: any) => {
const treeNodeClick = (data: any) => {
emit('nodeClick', data);
// 关闭可能存在的右击菜单
contextmenuRef.value.closeContextmenu();
}
// 树节点右击事件
const nodeContextmenu = (event: any, data: any) => {
if (!props.loadContextmenuItems) {
return;
}
// 加载当前节点是否需要显示右击菜单
const items = props.loadContextmenuItems(data)
if (!items || items.length == 0) {
return;
}
state.contextmenuItems = items;
const { clientX, clientY } = event;
state.dropdown.x = clientX;
state.dropdown.y = clientY;
contextmenuRef.value.openContextmenu(data);
}
const onCurrentContextmenuClick = (clickData: any) => {
emit('currentContextmenuClick', clickData);
}
const reloadNode = (nodeKey: any) => {
@@ -125,6 +156,7 @@ defineExpose({
<style lang="scss">
.instances-box {
overflow: 'auto';
position: relative;
.el-tree {
display: inline-block;

View File

@@ -23,13 +23,15 @@
</el-row>
<el-row type="flex">
<el-col :span="4" style="border-left: 1px solid #eee; margin-top: 10px">
<tag-tree ref="tagTreeRef" @node-click="nodeClick" :load="loadNode" :height="state.tagTreeHeight">
<tag-tree ref="tagTreeRef" @node-click="nodeClick" :load="loadNode" :load-contextmenu-items="getContextmenuItems" @current-contextmenu-click="onCurrentContextmenuClick"
: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>
<SvgIcon v-if="data.params.type === 'mysql'" name="iconfont icon-op-mysql" :size="18" />
<SvgIcon v-if="data.params.type === 'postgres'" name="iconfont icon-op-postgres" :size="18" />
<SvgIcon v-if="data.params.type === 'postgres'" name="iconfont icon-op-postgres"
:size="18" />
<SvgIcon name="InfoFilled" v-else />
</template>
@@ -54,15 +56,11 @@
<el-tooltip v-if="data.type == NodeType.Table" effect="customized"
:content="data.params.tableComment" placement="top-end">
<SvgIcon name="Calendar" color="#409eff" />
<SvgIcon name="Calendar" color="#409eff" />
</el-tooltip>
<SvgIcon name="Files" v-if="data.type == NodeType.SqlMenu || data.type == NodeType.Sql" color="#f56c6c" />
</template>
<template #option="{data}">
<span v-if="data.type == NodeType.TableMenu">
<el-link @click="reloadTables(data.key)" icon="refresh" :underline="false"></el-link>
</span>
<SvgIcon name="Files" v-if="data.type == NodeType.SqlMenu || data.type == NodeType.Sql"
color="#f56c6c" />
</template>
</tag-tree>
</el-col>
@@ -119,6 +117,9 @@ class NodeType {
static Table = 5;
static Sql = 6;
}
class ContextmenuClickId {
static ReloadTable = 0
}
const tagTreeRef: any = ref(null)
@@ -134,7 +135,7 @@ const state = reactive({
tabs,
dataTabsTableHeight: '600',
editorHeight: '600',
tagTreeHeight: window.innerHeight - 178 + 'px',
tagTreeHeight: window.innerHeight - 173 + 'px',
genSqlDialog: {
visible: false,
sql: '',
@@ -171,9 +172,9 @@ const getInsts = async () => {
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);
let dbInsts = instMap.get(tagPath) || [];
dbInsts.push(db);
instMap.set(tagPath, dbInsts?.sort());
}
}
@@ -261,10 +262,28 @@ const nodeClick = async (data: any) => {
}
}
const getContextmenuItems = (data: any) => {
const dataType = data.type;
if (dataType === NodeType.TableMenu) {
return [
{ contextMenuClickId: ContextmenuClickId.ReloadTable, txt: '刷新', icon: 'RefreshRight' }
]
}
return [];
}
// 当前右击菜单点击事件
const onCurrentContextmenuClick = (clickData: any) => {
const clickId = clickData.id;
if (clickId == ContextmenuClickId.ReloadTable) {
reloadTables(clickData.item.key)
}
}
const getTables = async (params: any) => {
const { id, db } = params;
let tables = await DbInst.getInst(id).loadTables(db, state.reloadStatus);
state.reloadStatus=false
state.reloadStatus = false
return tables.map((x: any) => {
return new TagTreeNode(`${id}.${db}.${x.tableName}`, x.tableName, NodeType.Table).withIsLeaf(true).withParams({
id,
@@ -413,9 +432,9 @@ const getSqlMenuNodeKey = (dbId: number, db: string) => {
return `${dbId}.${db}.sql-menu`
}
const reloadTables = (nodeKey:string) => {
state.reloadStatus=true
tagTreeRef.value.reloadNode(nodeKey);
const reloadTables = (nodeKey: string) => {
state.reloadStatus = true
tagTreeRef.value.reloadNode(nodeKey);
}
const registerSqlCompletionItemProvider = () => {

View File

@@ -68,6 +68,9 @@ export class DbInst {
if (!reload && tables) {
return tables;
}
// 重置列信息缓存与表提示信息
db.columnsMap?.clear();
db.tableHints = null;
console.log(`load tables -> dbName: ${dbName}`);
tables = await dbApi.tableMetadata.request({ id: this.id, db: dbName });
db.tables = tables;