mirror of
				https://gitee.com/dromara/mayfly-go
				synced 2025-11-04 08:20:25 +08:00 
			
		
		
		
	refactor: 界面优化
This commit is contained in:
		
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										135
									
								
								mayfly_go_web/src/components/contextmenu/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								mayfly_go_web/src/components/contextmenu/index.vue
									
									
									
									
									
										Normal 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>
 | 
			
		||||
@@ -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>
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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 = () => {
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user