mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-03 16:00:25 +08:00
refactor: contextmenu组件优化、标签&资源替换为contextmenu操作
This commit is contained in:
@@ -6,6 +6,9 @@ import { useUserInfo } from '@/store/userInfo';
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function hasPerm(code: string) {
|
export function hasPerm(code: string) {
|
||||||
|
if (!code) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return useUserInfo().userInfo.permissions.some((v: any) => v === code);
|
return useUserInfo().userInfo.permissions.some((v: any) => v === code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
55
mayfly_go_web/src/components/contextmenu/index.ts
Normal file
55
mayfly_go_web/src/components/contextmenu/index.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
export class ContextmenuItem {
|
||||||
|
clickId: any;
|
||||||
|
|
||||||
|
txt: string;
|
||||||
|
|
||||||
|
icon: string;
|
||||||
|
|
||||||
|
affix: boolean;
|
||||||
|
|
||||||
|
permission: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否隐藏回调函数
|
||||||
|
*/
|
||||||
|
hideFunc: (data: any) => boolean;
|
||||||
|
|
||||||
|
onClickFunc: (data: any) => void;
|
||||||
|
|
||||||
|
constructor(clickId: any, txt: string) {
|
||||||
|
this.clickId = clickId;
|
||||||
|
this.txt = txt;
|
||||||
|
}
|
||||||
|
|
||||||
|
withIcon(icon: string) {
|
||||||
|
this.icon = icon;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withPermission(permission: string) {
|
||||||
|
this.permission = permission;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withHideFunc(func: (data: any) => boolean) {
|
||||||
|
this.hideFunc = func;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withOnClick(func: (data: any) => void) {
|
||||||
|
this.onClickFunc = func;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否隐藏
|
||||||
|
* @param data 点击数据项
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
isHide(data: any) {
|
||||||
|
if (this.hideFunc) {
|
||||||
|
return this.hideFunc(data);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,17 +7,18 @@
|
|||||||
data-popper-placement="bottom"
|
data-popper-placement="bottom"
|
||||||
:style="`top: ${dropdowns.y + 5}px;left: ${dropdowns.x}px;`"
|
:style="`top: ${dropdowns.y + 5}px;left: ${dropdowns.x}px;`"
|
||||||
:key="Math.random()"
|
:key="Math.random()"
|
||||||
v-show="state.isShow"
|
v-show="state.isShow && !allHide"
|
||||||
>
|
>
|
||||||
<ul class="el-dropdown-menu">
|
<ul class="el-dropdown-menu">
|
||||||
<template v-for="(v, k) in state.dropdownList">
|
<template v-for="(v, k) in state.dropdownList">
|
||||||
<li
|
<li
|
||||||
|
v-auth="v.permission"
|
||||||
class="el-dropdown-menu__item"
|
class="el-dropdown-menu__item"
|
||||||
aria-disabled="false"
|
aria-disabled="false"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
:key="k"
|
:key="k"
|
||||||
v-if="!v.affix"
|
v-if="!v.affix && !v.isHide(state.item)"
|
||||||
@click="onCurrentContextmenuClick(v.contextMenuClickId)"
|
@click="onCurrentContextmenuClick(v)"
|
||||||
>
|
>
|
||||||
<SvgIcon :name="v.icon" />
|
<SvgIcon :name="v.icon" />
|
||||||
<span>{{ v.txt }}</span>
|
<span>{{ v.txt }}</span>
|
||||||
@@ -31,6 +32,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts" name="layoutTagsViewContextmenu">
|
<script setup lang="ts" name="layoutTagsViewContextmenu">
|
||||||
import { computed, reactive, onMounted, onUnmounted, watch } from 'vue';
|
import { computed, reactive, onMounted, onUnmounted, watch } from 'vue';
|
||||||
|
import { ContextmenuItem } from './index';
|
||||||
|
|
||||||
// 定义父组件传过来的值
|
// 定义父组件传过来的值
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -44,7 +46,7 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
items: {
|
items: {
|
||||||
type: Array,
|
type: Array<ContextmenuItem>,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -55,11 +57,20 @@ const emit = defineEmits(['currentContextmenuClick']);
|
|||||||
// 定义变量内容
|
// 定义变量内容
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
isShow: false,
|
isShow: false,
|
||||||
dropdownList: [{ contextMenuClickId: 0, txt: '刷新', affix: false, icon: 'RefreshRight' }],
|
dropdownList: [] as ContextmenuItem[],
|
||||||
item: {} as any,
|
item: {} as any,
|
||||||
arrowLeft: 10,
|
arrowLeft: 10,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const allHide = computed(() => {
|
||||||
|
for (let item of state.dropdownList) {
|
||||||
|
if (!item.isHide(state.item)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
// 父级传过来的坐标 x,y 值
|
// 父级传过来的坐标 x,y 值
|
||||||
const dropdowns = computed(() => {
|
const dropdowns = computed(() => {
|
||||||
// 117 为 `Dropdown 下拉菜单` 的宽度
|
// 117 为 `Dropdown 下拉菜单` 的宽度
|
||||||
@@ -73,8 +84,12 @@ const dropdowns = computed(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
// 当前项菜单点击
|
// 当前项菜单点击
|
||||||
const onCurrentContextmenuClick = (contextMenuClickId: number) => {
|
const onCurrentContextmenuClick = (ci: ContextmenuItem) => {
|
||||||
emit('currentContextmenuClick', { id: contextMenuClickId, item: state.item });
|
// 存在点击事件,则触发该事件函数
|
||||||
|
if (ci.onClickFunc) {
|
||||||
|
ci.onClickFunc(state.item);
|
||||||
|
}
|
||||||
|
emit('currentContextmenuClick', { id: ci.clickId, item: state.item });
|
||||||
};
|
};
|
||||||
// 打开右键菜单:判断是否固定,固定则不显示关闭按钮
|
// 打开右键菜单:判断是否固定,固定则不显示关闭按钮
|
||||||
const openContextmenu = (item: any) => {
|
const openContextmenu = (item: any) => {
|
||||||
@@ -91,6 +106,7 @@ const closeContextmenu = () => {
|
|||||||
// 监听页面监听进行右键菜单的关闭
|
// 监听页面监听进行右键菜单的关闭
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
document.body.addEventListener('click', closeContextmenu);
|
document.body.addEventListener('click', closeContextmenu);
|
||||||
|
state.dropdownList = props.items;
|
||||||
});
|
});
|
||||||
// 页面卸载时,移除右键菜单监听事件
|
// 页面卸载时,移除右键菜单监听事件
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
@@ -140,3 +156,4 @@ defineExpose({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
.
|
||||||
|
|||||||
@@ -1,138 +0,0 @@
|
|||||||
<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,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// 定义子组件向父组件传值/事件
|
|
||||||
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.path });
|
|
||||||
};
|
|
||||||
// 打开右键菜单:判断是否固定,固定则不显示关闭按钮
|
|
||||||
const openContextmenu = (item: any) => {
|
|
||||||
state.item = item;
|
|
||||||
item.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;
|
|
||||||
white-space: nowrap;
|
|
||||||
i {
|
|
||||||
font-size: 12px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -42,19 +42,20 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
<Contextmenu :dropdown="state.dropdown" ref="contextmenuRef" @currentContextmenuClick="onCurrentContextmenuClick" />
|
<Contextmenu :items="state.contextmenu.items" :dropdown="state.contextmenu.dropdown" ref="contextmenuRef" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup name="layoutTagsView">
|
<script lang="ts" setup name="layoutTagsView">
|
||||||
import { reactive, onMounted, computed, ref, nextTick, onBeforeUpdate, onBeforeMount, onUnmounted, getCurrentInstance, watch } from 'vue';
|
import { reactive, onMounted, computed, ref, nextTick, onBeforeUpdate, onBeforeMount, onUnmounted, getCurrentInstance } from 'vue';
|
||||||
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
|
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
|
||||||
import screenfull from 'screenfull';
|
import screenfull from 'screenfull';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useThemeConfig } from '@/store/themeConfig';
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
import mittBus from '@/common/utils/mitt';
|
import mittBus from '@/common/utils/mitt';
|
||||||
import Sortable from 'sortablejs';
|
import Sortable from 'sortablejs';
|
||||||
import Contextmenu from '@/layout/navBars/tagsView/contextmenu.vue';
|
import Contextmenu from '@/components/contextmenu/index.vue';
|
||||||
|
import { ContextmenuItem } from '@/components/contextmenu/index';
|
||||||
import { getTagViews, setTagViews, removeTagViews } from '@/common/utils/storage';
|
import { getTagViews, setTagViews, removeTagViews } from '@/common/utils/storage';
|
||||||
import { useTagsViews } from '@/store/tagsViews';
|
import { useTagsViews } from '@/store/tagsViews';
|
||||||
import { useKeepALiveNames } from '@/store/keepAliveNames';
|
import { useKeepALiveNames } from '@/store/keepAliveNames';
|
||||||
@@ -73,11 +74,38 @@ const keepAliveNamesStores = useKeepALiveNames();
|
|||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const contextmenuItems = [
|
||||||
|
new ContextmenuItem(0, '刷新').withIcon('RefreshRight').withOnClick((data: any) => {
|
||||||
|
// path为fullPath
|
||||||
|
let { path } = data;
|
||||||
|
let currentTag = tagsViews.value.find((v: any) => v.path === path);
|
||||||
|
refreshCurrentTagsView(path);
|
||||||
|
router.push({ path, query: currentTag?.query });
|
||||||
|
}),
|
||||||
|
|
||||||
|
new ContextmenuItem(1, '关闭').withIcon('Close').withOnClick((data: any) => closeCurrentTagsView(data.path)),
|
||||||
|
|
||||||
|
new ContextmenuItem(2, '关闭其他').withIcon('CircleClose').withOnClick((data: any) => {
|
||||||
|
let { path } = data;
|
||||||
|
let currentTag = tagsViews.value.find((v: any) => v.path === path);
|
||||||
|
router.push({ path, query: currentTag?.query });
|
||||||
|
closeOtherTagsView(path);
|
||||||
|
}),
|
||||||
|
|
||||||
|
new ContextmenuItem(3, '关闭所有').withIcon('FolderDelete').withOnClick((data: any) => closeAllTagsView(data.path)),
|
||||||
|
|
||||||
|
new ContextmenuItem(4, '当前页全屏').withIcon('full-screen').withOnClick((data: any) => openCurrenFullscreen(data.path)),
|
||||||
|
];
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
routePath: route.fullPath,
|
routePath: route.fullPath,
|
||||||
dropdown: { x: '', y: '' },
|
// dropdown: { x: '', y: '' },
|
||||||
tagsRefsIndex: 0,
|
tagsRefsIndex: 0,
|
||||||
sortable: '' as any,
|
sortable: '' as any,
|
||||||
|
contextmenu: {
|
||||||
|
items: contextmenuItems,
|
||||||
|
dropdown: { x: '', y: '' },
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// 动态设置 tagsView 风格样式
|
// 动态设置 tagsView 风格样式
|
||||||
@@ -239,31 +267,7 @@ const openCurrenFullscreen = (path: string) => {
|
|||||||
screenfulls.request(element);
|
screenfulls.request(element);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
// 当前项右键菜单点击
|
|
||||||
const onCurrentContextmenuClick = (data: any) => {
|
|
||||||
// path为fullPath
|
|
||||||
let { id, path } = data;
|
|
||||||
let currentTag = tagsViews.value.find((v: any) => v.path === path);
|
|
||||||
switch (id) {
|
|
||||||
case 0:
|
|
||||||
refreshCurrentTagsView(path);
|
|
||||||
router.push({ path, query: currentTag?.query });
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
closeCurrentTagsView(path);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
router.push({ path, query: currentTag?.query });
|
|
||||||
closeOtherTagsView(path);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
closeAllTagsView(path);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
openCurrenFullscreen(path);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// 判断页面高亮
|
// 判断页面高亮
|
||||||
const isActive = (tagView: TagsView) => {
|
const isActive = (tagView: TagsView) => {
|
||||||
return tagView.path === state.routePath;
|
return tagView.path === state.routePath;
|
||||||
@@ -271,8 +275,8 @@ const isActive = (tagView: TagsView) => {
|
|||||||
// 右键点击时:传 x,y 坐标值到子组件中(props)
|
// 右键点击时:传 x,y 坐标值到子组件中(props)
|
||||||
const onContextmenu = (v: any, e: any) => {
|
const onContextmenu = (v: any, e: any) => {
|
||||||
const { clientX, clientY } = e;
|
const { clientX, clientY } = e;
|
||||||
state.dropdown.x = clientX;
|
state.contextmenu.dropdown.x = clientX;
|
||||||
state.dropdown.y = clientY;
|
state.contextmenu.dropdown.y = clientY;
|
||||||
contextmenuRef.value.openContextmenu(v);
|
contextmenuRef.value.openContextmenu(v);
|
||||||
};
|
};
|
||||||
// 当前的 tagsView 项点击时
|
// 当前的 tagsView 项点击时
|
||||||
@@ -371,10 +375,6 @@ const initSortable = () => {
|
|||||||
|
|
||||||
// 页面加载前
|
// 页面加载前
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
// 监听非本页面调用 0 刷新当前,1 关闭当前,2 关闭其它,3 关闭全部 4 当前页全屏
|
|
||||||
mittBus.on('onCurrentContextmenuClick', (data: object) => {
|
|
||||||
onCurrentContextmenuClick(data);
|
|
||||||
});
|
|
||||||
// 监听布局配置界面开启/关闭拖拽
|
// 监听布局配置界面开启/关闭拖拽
|
||||||
mittBus.on('openOrCloseSortable', () => {
|
mittBus.on('openOrCloseSortable', () => {
|
||||||
initSortable();
|
initSortable();
|
||||||
@@ -382,8 +382,6 @@ onBeforeMount(() => {
|
|||||||
});
|
});
|
||||||
// 页面卸载时
|
// 页面卸载时
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
// 取消非本页面调用监听
|
|
||||||
mittBus.off('onCurrentContextmenuClick');
|
|
||||||
// 取消监听布局配置界面开启/关闭拖拽
|
// 取消监听布局配置界面开启/关闭拖拽
|
||||||
mittBus.off('openOrCloseSortable');
|
mittBus.off('openOrCloseSortable');
|
||||||
});
|
});
|
||||||
@@ -523,8 +521,14 @@ onBeforeRouteUpdate((to) => {
|
|||||||
-webkit-mask-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzAiIGhlaWdodD0iNzAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgZmlsbD0ibm9uZSI+CgogPGc+CiAgPHRpdGxlPkxheWVyIDE8L3RpdGxlPgogIDxwYXRoIHRyYW5zZm9ybT0icm90YXRlKC0wLjEzMzUwNiA1MC4xMTkyIDUwKSIgaWQ9InN2Z18xIiBkPSJtMTAwLjExOTE5LDEwMGMtNTUuMjI4LDAgLTEwMCwtNDQuNzcyIC0xMDAsLTEwMGwwLDEwMGwxMDAsMHoiIG9wYWNpdHk9InVuZGVmaW5lZCIgc3Ryb2tlPSJudWxsIiBmaWxsPSIjRjhFQUU3Ii8+CiAgPHBhdGggZD0ibS0wLjYzNzY2LDcuMzEyMjhjMC4xMTkxOSwwIDAuMjE3MzcsMC4wNTc5NiAwLjQ3Njc2LDAuMTE5MTljMC4yMzIsMC4wNTQ3NyAwLjI3MzI5LDAuMDM0OTEgMC4zNTc1NywwLjExOTE5YzAuMDg0MjgsMC4wODQyOCAwLjM1NzU3LDAgMC40NzY3NiwwbDAuMTE5MTksMGwwLjIzODM4LDAiIGlkPSJzdmdfMiIgc3Ryb2tlPSJudWxsIiBmaWxsPSJub25lIi8+CiAgPHBhdGggZD0ibTI4LjkyMTM0LDY5LjA1MjQ0YzAsMC4xMTkxOSAwLDAuMjM4MzggMCwwLjM1NzU3bDAsMC4xMTkxOWwwLDAuMTE5MTkiIGlkPSJzdmdfMyIgc3Ryb2tlPSJudWxsIiBmaWxsPSJub25lIi8+CiAgPHJlY3QgaWQ9InN2Z180IiBoZWlnaHQ9IjAiIHdpZHRoPSIxLjMxMTA4IiB5PSI2LjgzNTUyIiB4PSItMC4wNDE3MSIgc3Ryb2tlPSJudWxsIiBmaWxsPSJub25lIi8+CiAgPHJlY3QgaWQ9InN2Z181IiBoZWlnaHQ9IjEuNzg3ODQiIHdpZHRoPSIwLjExOTE5IiB5PSI2OC40NTY1IiB4PSIyOC45MjEzNCIgc3Ryb2tlPSJudWxsIiBmaWxsPSJub25lIi8+CiAgPHJlY3QgaWQ9InN2Z182IiBoZWlnaHQ9IjQuODg2NzciIHdpZHRoPSIxOS4wNzAzMiIgeT0iNTEuMjkzMjEiIHg9IjM2LjY2ODY2IiBzdHJva2U9Im51bGwiIGZpbGw9Im5vbmUiLz4KIDwvZz4KPC9zdmc+'),
|
-webkit-mask-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzAiIGhlaWdodD0iNzAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgZmlsbD0ibm9uZSI+CgogPGc+CiAgPHRpdGxlPkxheWVyIDE8L3RpdGxlPgogIDxwYXRoIHRyYW5zZm9ybT0icm90YXRlKC0wLjEzMzUwNiA1MC4xMTkyIDUwKSIgaWQ9InN2Z18xIiBkPSJtMTAwLjExOTE5LDEwMGMtNTUuMjI4LDAgLTEwMCwtNDQuNzcyIC0xMDAsLTEwMGwwLDEwMGwxMDAsMHoiIG9wYWNpdHk9InVuZGVmaW5lZCIgc3Ryb2tlPSJudWxsIiBmaWxsPSIjRjhFQUU3Ii8+CiAgPHBhdGggZD0ibS0wLjYzNzY2LDcuMzEyMjhjMC4xMTkxOSwwIDAuMjE3MzcsMC4wNTc5NiAwLjQ3Njc2LDAuMTE5MTljMC4yMzIsMC4wNTQ3NyAwLjI3MzI5LDAuMDM0OTEgMC4zNTc1NywwLjExOTE5YzAuMDg0MjgsMC4wODQyOCAwLjM1NzU3LDAgMC40NzY3NiwwbDAuMTE5MTksMGwwLjIzODM4LDAiIGlkPSJzdmdfMiIgc3Ryb2tlPSJudWxsIiBmaWxsPSJub25lIi8+CiAgPHBhdGggZD0ibTI4LjkyMTM0LDY5LjA1MjQ0YzAsMC4xMTkxOSAwLDAuMjM4MzggMCwwLjM1NzU3bDAsMC4xMTkxOWwwLDAuMTE5MTkiIGlkPSJzdmdfMyIgc3Ryb2tlPSJudWxsIiBmaWxsPSJub25lIi8+CiAgPHJlY3QgaWQ9InN2Z180IiBoZWlnaHQ9IjAiIHdpZHRoPSIxLjMxMTA4IiB5PSI2LjgzNTUyIiB4PSItMC4wNDE3MSIgc3Ryb2tlPSJudWxsIiBmaWxsPSJub25lIi8+CiAgPHJlY3QgaWQ9InN2Z181IiBoZWlnaHQ9IjEuNzg3ODQiIHdpZHRoPSIwLjExOTE5IiB5PSI2OC40NTY1IiB4PSIyOC45MjEzNCIgc3Ryb2tlPSJudWxsIiBmaWxsPSJub25lIi8+CiAgPHJlY3QgaWQ9InN2Z182IiBoZWlnaHQ9IjQuODg2NzciIHdpZHRoPSIxOS4wNzAzMiIgeT0iNTEuMjkzMjEiIHg9IjM2LjY2ODY2IiBzdHJva2U9Im51bGwiIGZpbGw9Im5vbmUiLz4KIDwvZz4KPC9zdmc+'),
|
||||||
url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzAiIGhlaWdodD0iNzAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgZmlsbD0ibm9uZSI+CiA8Zz4KICA8dGl0bGU+TGF5ZXIgMTwvdGl0bGU+CiAgPHBhdGggdHJhbnNmb3JtPSJyb3RhdGUoLTg5Ljc2MjQgNy4zMzAxNCA1NS4xMjUyKSIgc3Ryb2tlPSJudWxsIiBpZD0ic3ZnXzEiIGZpbGw9IiNGOEVBRTciIGQ9Im02Mi41NzQ0OSwxMTcuNTIwODZjLTU1LjIyOCwwIC0xMDAsLTQ0Ljc3MiAtMTAwLC0xMDBsMCwxMDBsMTAwLDB6IiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPgogIDxwYXRoIGQ9Im0tMC42Mzc2Niw3LjMxMjI4YzAuMTE5MTksMCAwLjIxNzM3LDAuMDU3OTYgMC40NzY3NiwwLjExOTE5YzAuMjMyLDAuMDU0NzcgMC4yNzMyOSwwLjAzNDkxIDAuMzU3NTcsMC4xMTkxOWMwLjA4NDI4LDAuMDg0MjggMC4zNTc1NywwIDAuNDc2NzYsMGwwLjExOTE5LDBsMC4yMzgzOCwwIiBpZD0ic3ZnXzIiIHN0cm9rZT0ibnVsbCIgZmlsbD0ibm9uZSIvPgogIDxwYXRoIGQ9Im0yOC45MjEzNCw2OS4wNTI0NGMwLDAuMTE5MTkgMCwwLjIzODM4IDAsMC4zNTc1N2wwLDAuMTE5MTlsMCwwLjExOTE5IiBpZD0ic3ZnXzMiIHN0cm9rZT0ibnVsbCIgZmlsbD0ibm9uZSIvPgogIDxyZWN0IGlkPSJzdmdfNCIgaGVpZ2h0PSIwIiB3aWR0aD0iMS4zMTEwOCIgeT0iNi44MzU1MiIgeD0iLTAuMDQxNzEiIHN0cm9rZT0ibnVsbCIgZmlsbD0ibm9uZSIvPgogIDxyZWN0IGlkPSJzdmdfNSIgaGVpZ2h0PSIxLjc4Nzg0IiB3aWR0aD0iMC4xMTkxOSIgeT0iNjguNDU2NSIgeD0iMjguOTIxMzQiIHN0cm9rZT0ibnVsbCIgZmlsbD0ibm9uZSIvPgogIDxyZWN0IGlkPSJzdmdfNiIgaGVpZ2h0PSI0Ljg4Njc3IiB3aWR0aD0iMTkuMDcwMzIiIHk9IjUxLjI5MzIxIiB4PSIzNi42Njg2NiIgc3Ryb2tlPSJudWxsIiBmaWxsPSJub25lIi8+CiA8L2c+Cjwvc3ZnPg=='),
|
url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzAiIGhlaWdodD0iNzAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgZmlsbD0ibm9uZSI+CiA8Zz4KICA8dGl0bGU+TGF5ZXIgMTwvdGl0bGU+CiAgPHBhdGggdHJhbnNmb3JtPSJyb3RhdGUoLTg5Ljc2MjQgNy4zMzAxNCA1NS4xMjUyKSIgc3Ryb2tlPSJudWxsIiBpZD0ic3ZnXzEiIGZpbGw9IiNGOEVBRTciIGQ9Im02Mi41NzQ0OSwxMTcuNTIwODZjLTU1LjIyOCwwIC0xMDAsLTQ0Ljc3MiAtMTAwLC0xMDBsMCwxMDBsMTAwLDB6IiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPgogIDxwYXRoIGQ9Im0tMC42Mzc2Niw3LjMxMjI4YzAuMTE5MTksMCAwLjIxNzM3LDAuMDU3OTYgMC40NzY3NiwwLjExOTE5YzAuMjMyLDAuMDU0NzcgMC4yNzMyOSwwLjAzNDkxIDAuMzU3NTcsMC4xMTkxOWMwLjA4NDI4LDAuMDg0MjggMC4zNTc1NywwIDAuNDc2NzYsMGwwLjExOTE5LDBsMC4yMzgzOCwwIiBpZD0ic3ZnXzIiIHN0cm9rZT0ibnVsbCIgZmlsbD0ibm9uZSIvPgogIDxwYXRoIGQ9Im0yOC45MjEzNCw2OS4wNTI0NGMwLDAuMTE5MTkgMCwwLjIzODM4IDAsMC4zNTc1N2wwLDAuMTE5MTlsMCwwLjExOTE5IiBpZD0ic3ZnXzMiIHN0cm9rZT0ibnVsbCIgZmlsbD0ibm9uZSIvPgogIDxyZWN0IGlkPSJzdmdfNCIgaGVpZ2h0PSIwIiB3aWR0aD0iMS4zMTEwOCIgeT0iNi44MzU1MiIgeD0iLTAuMDQxNzEiIHN0cm9rZT0ibnVsbCIgZmlsbD0ibm9uZSIvPgogIDxyZWN0IGlkPSJzdmdfNSIgaGVpZ2h0PSIxLjc4Nzg0IiB3aWR0aD0iMC4xMTkxOSIgeT0iNjguNDU2NSIgeD0iMjguOTIxMzQiIHN0cm9rZT0ibnVsbCIgZmlsbD0ibm9uZSIvPgogIDxyZWN0IGlkPSJzdmdfNiIgaGVpZ2h0PSI0Ljg4Njc3IiB3aWR0aD0iMTkuMDcwMzIiIHk9IjUxLjI5MzIxIiB4PSIzNi42Njg2NiIgc3Ryb2tlPSJudWxsIiBmaWxsPSJub25lIi8+CiA8L2c+Cjwvc3ZnPg=='),
|
||||||
url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><rect rx='8' width='100%' height='100%' fill='%23F8EAE7'/></svg>");
|
url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><rect rx='8' width='100%' height='100%' fill='%23F8EAE7'/></svg>");
|
||||||
-webkit-mask-size: 18px 30px, 20px 30px, calc(100% - 30px) calc(100% + 17px);
|
-webkit-mask-size:
|
||||||
-webkit-mask-position: right bottom, left bottom, center top;
|
18px 30px,
|
||||||
|
20px 30px,
|
||||||
|
calc(100% - 30px) calc(100% + 17px);
|
||||||
|
-webkit-mask-position:
|
||||||
|
right bottom,
|
||||||
|
left bottom,
|
||||||
|
center top;
|
||||||
-webkit-mask-repeat: no-repeat;
|
-webkit-mask-repeat: no-repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -545,14 +549,14 @@ onBeforeRouteUpdate((to) => {
|
|||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@extend .tgs-style-three-svg;
|
@extend .tgs-style-three-svg;
|
||||||
background: var(--el-color-primary-light-9);
|
background: var(--tagsview3-active-background-color);
|
||||||
color: unset;
|
color: unset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.is-active {
|
.is-active {
|
||||||
@extend .tgs-style-three-svg;
|
@extend .tgs-style-three-svg;
|
||||||
background: var(--el-color-primary-light-9) !important;
|
background: var(--tagsview3-active-background-color) !important;
|
||||||
color: var(--el-color-primary) !important;
|
color: var(--el-color-primary) !important;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
@@ -563,3 +567,4 @@ onBeforeRouteUpdate((to) => {
|
|||||||
box-shadow: rgb(0 21 41 / 4%) 0px 1px 4px;
|
box-shadow: rgb(0 21 41 / 4%) 0px 1px 4px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@/components/contextmenu
|
||||||
|
|||||||
@@ -23,6 +23,8 @@
|
|||||||
--color-seting-main: #e9eef3;
|
--color-seting-main: #e9eef3;
|
||||||
--color-seting-aside: #d3dce6;
|
--color-seting-aside: #d3dce6;
|
||||||
--color-seting-header: #b3c0d1;
|
--color-seting-header: #b3c0d1;
|
||||||
|
|
||||||
|
--tagsview3-active-background-color: var(--el-color-primary-light-9);
|
||||||
}
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
|
|||||||
@@ -24,4 +24,6 @@ html.dark {
|
|||||||
--bg-menuBarActiveColor: var(--next-color-hover-rgba) !important;
|
--bg-menuBarActiveColor: var(--next-color-hover-rgba) !important;
|
||||||
--bg-columnsMenuBar: var(--next-color-disabled) !important;
|
--bg-columnsMenuBar: var(--next-color-disabled) !important;
|
||||||
--bg-columnsMenuBarColor: var(--next-color-bar) !important;
|
--bg-columnsMenuBarColor: var(--next-color-bar) !important;
|
||||||
|
|
||||||
|
--tagsview3-active-background-color: var(--next-color-hover);
|
||||||
}
|
}
|
||||||
@@ -345,4 +345,5 @@
|
|||||||
.el-dialog {
|
.el-dialog {
|
||||||
border-radius: 6px; /* 设置圆角 */
|
border-radius: 6px; /* 设置圆角 */
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); /* 添加轻微阴影效果 */
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); /* 添加轻微阴影效果 */
|
||||||
|
border: 1px solid var(--el-border-color-lighter);
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { ContextmenuItem } from '@/components/contextmenu';
|
||||||
|
|
||||||
export class TagTreeNode {
|
export class TagTreeNode {
|
||||||
/**
|
/**
|
||||||
* 节点id
|
* 节点id
|
||||||
@@ -73,7 +75,7 @@ export class NodeType {
|
|||||||
*/
|
*/
|
||||||
value: number;
|
value: number;
|
||||||
|
|
||||||
contextMenuItems: [];
|
contextMenuItems: ContextmenuItem[];
|
||||||
|
|
||||||
loadNodesFunc: (parentNode: TagTreeNode) => Promise<TagTreeNode[]>;
|
loadNodesFunc: (parentNode: TagTreeNode) => Promise<TagTreeNode[]>;
|
||||||
|
|
||||||
@@ -108,7 +110,7 @@ export class NodeType {
|
|||||||
* @param contextMenuItems 右击菜单按钮选项
|
* @param contextMenuItems 右击菜单按钮选项
|
||||||
* @returns this
|
* @returns this
|
||||||
*/
|
*/
|
||||||
withContextMenuItems(contextMenuItems: []) {
|
withContextMenuItems(contextMenuItems: ContextmenuItem[]) {
|
||||||
this.contextMenuItems = contextMenuItems;
|
this.contextMenuItems = contextMenuItems;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
|
|
||||||
<el-row type="flex">
|
<el-row type="flex">
|
||||||
<el-col :span="4">
|
<el-col :span="4">
|
||||||
<tag-tree ref="tagTreeRef" :loadTags="loadTags" @current-contextmenu-click="onCurrentContextmenuClick" :height="state.tagTreeHeight">
|
<tag-tree ref="tagTreeRef" :loadTags="loadTags" :height="state.tagTreeHeight">
|
||||||
<template #prefix="{ data }">
|
<template #prefix="{ data }">
|
||||||
<span v-if="data.type.value == SqlExecNodeType.DbInst">
|
<span v-if="data.type.value == SqlExecNodeType.DbInst">
|
||||||
<el-popover :show-after="500" placement="right-start" title="数据库实例信息" trigger="hover" :width="250">
|
<el-popover :show-after="500" placement="right-start" title="数据库实例信息" trigger="hover" :width="250">
|
||||||
@@ -159,6 +159,7 @@ import TagTree from '../component/TagTree.vue';
|
|||||||
import { dbApi } from './api';
|
import { dbApi } from './api';
|
||||||
import { dispposeCompletionItemProvider } from '../../../components/monaco/completionItemProvider';
|
import { dispposeCompletionItemProvider } from '../../../components/monaco/completionItemProvider';
|
||||||
import SvgIcon from '@/components/svgIcon/index.vue';
|
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||||
|
import { ContextmenuItem } from '@/components/contextmenu/index';
|
||||||
|
|
||||||
const DbSqlEditor = defineAsyncComponent(() => import('./component/sqleditor/DbSqlEditor.vue'));
|
const DbSqlEditor = defineAsyncComponent(() => import('./component/sqleditor/DbSqlEditor.vue'));
|
||||||
const DbTableDataOp = defineAsyncComponent(() => import('./component/table/DbTableDataOp.vue'));
|
const DbTableDataOp = defineAsyncComponent(() => import('./component/table/DbTableDataOp.vue'));
|
||||||
@@ -199,11 +200,6 @@ const SqlIcon = {
|
|||||||
color: '#f56c6c',
|
color: '#f56c6c',
|
||||||
};
|
};
|
||||||
|
|
||||||
class ContextmenuClickId {
|
|
||||||
static ReloadTable = 0;
|
|
||||||
static TableOp = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// node节点点击时,触发改变db事件
|
// node节点点击时,触发改变db事件
|
||||||
const nodeClickChangeDb = (nodeData: TagTreeNode) => {
|
const nodeClickChangeDb = (nodeData: TagTreeNode) => {
|
||||||
const params = nodeData.params;
|
const params = nodeData.params;
|
||||||
@@ -290,9 +286,13 @@ const NodeTypePostgresScheam = new NodeType(SqlExecNodeType.PgSchema)
|
|||||||
// 数据库表菜单节点
|
// 数据库表菜单节点
|
||||||
const NodeTypeTableMenu = new NodeType(SqlExecNodeType.TableMenu)
|
const NodeTypeTableMenu = new NodeType(SqlExecNodeType.TableMenu)
|
||||||
.withContextMenuItems([
|
.withContextMenuItems([
|
||||||
{ contextMenuClickId: ContextmenuClickId.ReloadTable, txt: '刷新', icon: 'RefreshRight' },
|
new ContextmenuItem('reloadTables', '刷新').withIcon('RefreshRight').withOnClick((data: any) => reloadTables(data.key)),
|
||||||
{ contextMenuClickId: ContextmenuClickId.TableOp, txt: '表操作', icon: 'Setting' },
|
|
||||||
] as any)
|
new ContextmenuItem('tablesOp', '表操作').withIcon('Setting').withOnClick((data: any) => {
|
||||||
|
const params = data.params;
|
||||||
|
addTablesOpTab({ id: params.id, db: params.db, type: params.type, nodeKey: data.key });
|
||||||
|
}),
|
||||||
|
])
|
||||||
.withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
.withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
||||||
const params = parentNode.params;
|
const params = parentNode.params;
|
||||||
let { id, db } = params;
|
let { id, db } = params;
|
||||||
@@ -423,19 +423,6 @@ const loadTags = async () => {
|
|||||||
return tagNodes;
|
return tagNodes;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 当前右击菜单点击事件
|
|
||||||
const onCurrentContextmenuClick = (clickData: any) => {
|
|
||||||
const clickId = clickData.id;
|
|
||||||
if (clickId == ContextmenuClickId.ReloadTable) {
|
|
||||||
reloadTables(clickData.item.key);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (clickId == ContextmenuClickId.TableOp) {
|
|
||||||
const params = clickData.item.params;
|
|
||||||
addTablesOpTab({ id: params.id, db: params.db, type: params.type, nodeKey: clickData.item.key });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 选择数据库,改变当前正在操作的数据库信息
|
// 选择数据库,改变当前正在操作的数据库信息
|
||||||
const changeDb = (db: any, dbName: string) => {
|
const changeDb = (db: any, dbName: string) => {
|
||||||
state.nowDbInst = DbInst.getOrNewInst(db);
|
state.nowDbInst = DbInst.getOrNewInst(db);
|
||||||
@@ -655,3 +642,4 @@ const getNowDbInfo = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
../../../components/contextmenu
|
||||||
|
|||||||
@@ -68,10 +68,10 @@
|
|||||||
<template #prepend>
|
<template #prepend>
|
||||||
<el-popover :visible="state.condPopVisible" trigger="click" :width="320" placement="right">
|
<el-popover :visible="state.condPopVisible" trigger="click" :width="320" placement="right">
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<el-link @click.stop="state.condPopVisible = !state.condPopVisible" type="success" :underline="false">选择列</el-link>
|
<el-link @click.stop="chooseCondColumnName" type="success" :underline="false">选择列</el-link>
|
||||||
</template>
|
</template>
|
||||||
<el-table
|
<el-table
|
||||||
:data="columns"
|
:data="filterCondColumns"
|
||||||
max-height="500"
|
max-height="500"
|
||||||
size="small"
|
size="small"
|
||||||
@row-click="
|
@row-click="
|
||||||
@@ -81,7 +81,17 @@
|
|||||||
"
|
"
|
||||||
style="cursor: pointer"
|
style="cursor: pointer"
|
||||||
>
|
>
|
||||||
<el-table-column property="columnName" label="列名" show-overflow-tooltip> </el-table-column>
|
<el-table-column property="columnName" label="列名" show-overflow-tooltip>
|
||||||
|
<template #header>
|
||||||
|
<el-input
|
||||||
|
ref="columnNameSearchInputRef"
|
||||||
|
v-model="state.columnNameSearch"
|
||||||
|
size="small"
|
||||||
|
placeholder="列名: 输入可过滤"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column property="columnComment" label="备注" show-overflow-tooltip> </el-table-column>
|
<el-table-column property="columnComment" label="备注" show-overflow-tooltip> </el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
@@ -185,7 +195,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, watch, reactive, toRefs, ref, Ref, onUnmounted } from 'vue';
|
import { onMounted, computed, watch, reactive, toRefs, ref, Ref, onUnmounted } from 'vue';
|
||||||
import { isTrue, notEmpty } from '@/common/assert';
|
import { isTrue, notEmpty } from '@/common/assert';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
|
|
||||||
@@ -217,6 +227,7 @@ const props = defineProps({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const dbTableRef = ref(null) as Ref;
|
const dbTableRef = ref(null) as Ref;
|
||||||
|
const columnNameSearchInputRef = ref(null) as Ref;
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
datas: [],
|
datas: [],
|
||||||
@@ -231,6 +242,7 @@ const state = reactive({
|
|||||||
count: 0,
|
count: 0,
|
||||||
selectionDatas: [] as any,
|
selectionDatas: [] as any,
|
||||||
condPopVisible: false,
|
condPopVisible: false,
|
||||||
|
columnNameSearch: '',
|
||||||
conditionDialog: {
|
conditionDialog: {
|
||||||
title: '',
|
title: '',
|
||||||
placeholder: '',
|
placeholder: '',
|
||||||
@@ -351,6 +363,35 @@ const exportData = () => {
|
|||||||
exportCsv(`数据导出-${props.tableName}-${dateStrFormat('yyyyMMddHHmm', new Date().toString())}`, columnNames, dataList);
|
exportCsv(`数据导出-${props.tableName}-${dateStrFormat('yyyyMMddHHmm', new Date().toString())}`, columnNames, dataList);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选择条件列
|
||||||
|
*/
|
||||||
|
const chooseCondColumnName = () => {
|
||||||
|
state.condPopVisible = !state.condPopVisible;
|
||||||
|
if (state.condPopVisible) {
|
||||||
|
columnNameSearchInputRef.value.clear();
|
||||||
|
columnNameSearchInputRef.value.focus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 过滤条件列名
|
||||||
|
*/
|
||||||
|
const filterCondColumns = computed(() => {
|
||||||
|
const columns = state.columns;
|
||||||
|
const columnNameSearch = state.columnNameSearch;
|
||||||
|
if (!columnNameSearch) {
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
return columns.filter((data: any) => {
|
||||||
|
let tnMatch = true;
|
||||||
|
if (columnNameSearch) {
|
||||||
|
tnMatch = data.columnName.toLowerCase().includes(columnNameSearch.toLowerCase());
|
||||||
|
}
|
||||||
|
return tnMatch;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 条件查询,点击列信息后显示输入对应的值
|
* 条件查询,点击列信息后显示输入对应的值
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -128,23 +128,7 @@
|
|||||||
</el-tree>
|
</el-tree>
|
||||||
|
|
||||||
<!-- right context menu -->
|
<!-- right context menu -->
|
||||||
<div ref="rightMenuRef" class="key-list-right-menu">
|
<contextmenu :dropdown="state.contextmenu.dropdown" :items="state.contextmenu.items" ref="contextmenuRef" />
|
||||||
<!-- folder right menu -->
|
|
||||||
<div v-if="!state.rightClickNode?.isLeaf"></div>
|
|
||||||
<!-- key right menu -->
|
|
||||||
<div v-else>
|
|
||||||
<el-row>
|
|
||||||
<el-link @click="showKeyDetail(state.rightClickNode.key, true)" type="primary" icon="plus" :underline="false"
|
|
||||||
>新tab打开</el-link
|
|
||||||
>
|
|
||||||
</el-row>
|
|
||||||
<el-row class="mt5">
|
|
||||||
<el-link @click="delKey(state.rightClickNode.key)" v-auth="'redis:data:del'" type="danger" icon="delete" :underline="false"
|
|
||||||
>删除</el-link
|
|
||||||
>
|
|
||||||
</el-row>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|
||||||
@@ -195,9 +179,24 @@ import { isTrue, notBlank, notNull } from '@/common/assert';
|
|||||||
import { TagTreeNode, NodeType } from '../component/tag';
|
import { TagTreeNode, NodeType } from '../component/tag';
|
||||||
import TagTree from '../component/TagTree.vue';
|
import TagTree from '../component/TagTree.vue';
|
||||||
import { keysToTree, sortByTreeNodes, keysToList } from './utils';
|
import { keysToTree, sortByTreeNodes, keysToList } from './utils';
|
||||||
|
import Contextmenu from '@/components/contextmenu/index.vue';
|
||||||
|
import { ContextmenuItem } from '@/components/contextmenu/index';
|
||||||
|
|
||||||
const KeyDetail = defineAsyncComponent(() => import('./KeyDetail.vue'));
|
const KeyDetail = defineAsyncComponent(() => import('./KeyDetail.vue'));
|
||||||
|
|
||||||
|
const contextmenuRef = ref();
|
||||||
|
|
||||||
|
const cmNewTabOpen = new ContextmenuItem('newTabOpenKey', '新tab打开')
|
||||||
|
.withIcon('plus')
|
||||||
|
.withHideFunc((data: any) => !data.isLeaf)
|
||||||
|
.withOnClick((data: any) => showKeyDetail(data.key, true));
|
||||||
|
|
||||||
|
const cmDelKey = new ContextmenuItem('delKey', '删除')
|
||||||
|
.withIcon('delete')
|
||||||
|
.withPermission('redis:data:del')
|
||||||
|
.withHideFunc((data: any) => !data.isLeaf)
|
||||||
|
.withOnClick((data: any) => delKey(data.key));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 树节点类型
|
* 树节点类型
|
||||||
*/
|
*/
|
||||||
@@ -265,7 +264,6 @@ const treeProps = {
|
|||||||
const defaultCount = 250;
|
const defaultCount = 250;
|
||||||
|
|
||||||
const keyTreeRef: any = ref(null);
|
const keyTreeRef: any = ref(null);
|
||||||
const rightMenuRef: any = ref(null);
|
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
tags: [],
|
tags: [],
|
||||||
@@ -297,6 +295,13 @@ const state = reactive({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dbsize: 0,
|
dbsize: 0,
|
||||||
|
contextmenu: {
|
||||||
|
dropdown: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
items: [cmNewTabOpen, cmDelKey],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { scanParam, keyTreeData, newKeyDialog } = toRefs(state);
|
const { scanParam, keyTreeData, newKeyDialog } = toRefs(state);
|
||||||
@@ -406,7 +411,8 @@ const expandAllKeyNode = (nodes: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyTreeNodeClick = async (data: any) => {
|
const handleKeyTreeNodeClick = async (data: any) => {
|
||||||
hideAllMenus();
|
// 关闭可能存在的右击菜单
|
||||||
|
contextmenuRef.value.closeContextmenu();
|
||||||
// 目录则不做处理
|
// 目录则不做处理
|
||||||
if (data.type == 1) {
|
if (data.type == 1) {
|
||||||
return;
|
return;
|
||||||
@@ -479,40 +485,11 @@ const keyTreeNodeCollapse = (data: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const rightClickNode = (event: any, data: any, node: any) => {
|
const rightClickNode = (event: any, data: any, node: any) => {
|
||||||
hideAllMenus();
|
const { clientX, clientY } = event;
|
||||||
|
state.contextmenu.dropdown.x = clientX;
|
||||||
|
state.contextmenu.dropdown.y = clientY;
|
||||||
|
contextmenuRef.value.openContextmenu(node);
|
||||||
keyTreeRef.value.setCurrentKey(node.key);
|
keyTreeRef.value.setCurrentKey(node.key);
|
||||||
state.rightClickNode = node;
|
|
||||||
|
|
||||||
// nextTick for dom render
|
|
||||||
nextTick(() => {
|
|
||||||
let top = event.clientY;
|
|
||||||
const menu = rightMenuRef.value;
|
|
||||||
menu.style.display = 'block';
|
|
||||||
|
|
||||||
// position in bottom
|
|
||||||
if (document.body.clientHeight - top < menu.clientHeight) {
|
|
||||||
top -= menu.clientHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
menu.style.left = `${event.clientX}px`;
|
|
||||||
menu.style.top = `${top}px`;
|
|
||||||
|
|
||||||
document.addEventListener('click', hideAllMenus, { once: true });
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const hideAllMenus = () => {
|
|
||||||
let menus: any = document.querySelectorAll('.key-list-right-menu');
|
|
||||||
|
|
||||||
if (menus.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
state.rightClickNode = null;
|
|
||||||
for (const menu of menus) {
|
|
||||||
menu.style.display = 'none';
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchKey = async () => {
|
const searchKey = async () => {
|
||||||
@@ -643,21 +620,4 @@ const delKey = (key: string) => {
|
|||||||
height: 22px;
|
height: 22px;
|
||||||
line-height: 22px;
|
line-height: 22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* right menu style start */
|
|
||||||
.key-list-right-menu {
|
|
||||||
display: none;
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
padding: 5px;
|
|
||||||
z-index: 99999;
|
|
||||||
overflow: hidden;
|
|
||||||
border-radius: 3px;
|
|
||||||
border: 2px solid lightgrey;
|
|
||||||
background: #fafafa;
|
|
||||||
}
|
|
||||||
.dark-mode .key-list-right-menu {
|
|
||||||
background: #263238;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<el-input v-model="filterTag" placeholder="输入标签关键字过滤" style="width: 200px; margin-right: 10px" />
|
<el-input v-model="filterTag" placeholder="输入关键字过滤(右击进行操作)" style="width: 220px; margin-right: 10px" />
|
||||||
<el-button v-auth="'tag:save'" type="primary" icon="plus" @click="showSaveTabDialog(null)">添加</el-button>
|
<el-button v-auth="'tag:save'" type="primary" icon="plus" @click="showSaveTagDialog(null)">添加</el-button>
|
||||||
<div style="float: right">
|
<div style="float: right">
|
||||||
<el-tooltip effect="dark" placement="top">
|
<el-tooltip effect="dark" placement="top">
|
||||||
<template #content>
|
<template #content>
|
||||||
@@ -26,8 +26,10 @@
|
|||||||
:data="data"
|
:data="data"
|
||||||
@node-expand="handleNodeExpand"
|
@node-expand="handleNodeExpand"
|
||||||
@node-collapse="handleNodeCollapse"
|
@node-collapse="handleNodeCollapse"
|
||||||
|
@node-contextmenu="nodeContextmenu"
|
||||||
|
@node-click="treeNodeClick"
|
||||||
:default-expanded-keys="defaultExpandedKeys"
|
:default-expanded-keys="defaultExpandedKeys"
|
||||||
:expand-on-click-node="false"
|
:expand-on-click-node="true"
|
||||||
:filter-node-method="filterNode"
|
:filter-node-method="filterNode"
|
||||||
>
|
>
|
||||||
<template #default="{ data }">
|
<template #default="{ data }">
|
||||||
@@ -39,44 +41,6 @@
|
|||||||
<span style="color: #3c8dbc">】</span>
|
<span style="color: #3c8dbc">】</span>
|
||||||
<el-tag v-if="data.children !== null" size="small">{{ data.children.length }}</el-tag>
|
<el-tag v-if="data.children !== null" size="small">{{ data.children.length }}</el-tag>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<el-link @click.prevent="info(data)" style="margin-left: 25px" icon="view" type="info" :underline="false" />
|
|
||||||
|
|
||||||
<el-link v-auth="'tag:save'" @click.prevent="showEditTagDialog(data)" class="ml5" type="primary" icon="edit" :underline="false" />
|
|
||||||
|
|
||||||
<el-link v-auth="'tag:save'" @click.prevent="showSaveTabDialog(data)" icon="circle-plus" :underline="false" type="success" class="ml5" />
|
|
||||||
|
|
||||||
<!-- <el-link
|
|
||||||
v-auth="'resource:changeStatus'"
|
|
||||||
@click.prevent="changeStatus(data, -1)"
|
|
||||||
v-if="data.status === 1 && data.type === enums.ResourceTypeEnum.PERMISSION.value"
|
|
||||||
icon="circle-close"
|
|
||||||
:underline="false"
|
|
||||||
type="warning"
|
|
||||||
class="ml5"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<el-link
|
|
||||||
v-auth="'resource:changeStatus'"
|
|
||||||
@click.prevent="changeStatus(data, 1)"
|
|
||||||
v-if="data.status === -1 && data.type === enums.ResourceTypeEnum.PERMISSION.value"
|
|
||||||
type="success"
|
|
||||||
icon="circle-check"
|
|
||||||
:underline="false"
|
|
||||||
plain
|
|
||||||
class="ml5"
|
|
||||||
/> -->
|
|
||||||
|
|
||||||
<el-link
|
|
||||||
v-auth="'tag:del'"
|
|
||||||
@click.prevent="deleteTag(data)"
|
|
||||||
v-if="data.children == null"
|
|
||||||
type="danger"
|
|
||||||
icon="delete"
|
|
||||||
:underline="false"
|
|
||||||
plain
|
|
||||||
class="ml5"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-tree>
|
</el-tree>
|
||||||
@@ -114,6 +78,8 @@
|
|||||||
<el-descriptions-item label="更新时间">{{ dateFormat(infoDialog.data.updateTime) }}</el-descriptions-item>
|
<el-descriptions-item label="更新时间">{{ dateFormat(infoDialog.data.updateTime) }}</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<contextmenu :dropdown="state.contextmenu.dropdown" :items="state.contextmenu.items" ref="contextmenuRef" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -122,6 +88,8 @@ import { toRefs, ref, watch, reactive, onMounted } from 'vue';
|
|||||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
import { tagApi } from './api';
|
import { tagApi } from './api';
|
||||||
import { dateFormat } from '@/common/utils/date';
|
import { dateFormat } from '@/common/utils/date';
|
||||||
|
import Contextmenu from '@/components/contextmenu/index.vue';
|
||||||
|
import { ContextmenuItem } from '@/components/contextmenu/index';
|
||||||
|
|
||||||
interface Tree {
|
interface Tree {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -133,6 +101,28 @@ interface Tree {
|
|||||||
const tagForm: any = ref(null);
|
const tagForm: any = ref(null);
|
||||||
const tagTreeRef: any = ref(null);
|
const tagTreeRef: any = ref(null);
|
||||||
const filterTag = ref('');
|
const filterTag = ref('');
|
||||||
|
const contextmenuRef = ref();
|
||||||
|
|
||||||
|
const contextmenuInfo = new ContextmenuItem('info', '详情').withIcon('view').withOnClick((data: any) => info(data));
|
||||||
|
|
||||||
|
const contextmenuAdd = new ContextmenuItem('addTag', '添加子标签')
|
||||||
|
.withIcon('circle-plus')
|
||||||
|
.withPermission('tag:save')
|
||||||
|
.withOnClick((data: any) => showSaveTagDialog(data));
|
||||||
|
|
||||||
|
const contextmenuEdit = new ContextmenuItem('edit', '编辑')
|
||||||
|
.withIcon('edit')
|
||||||
|
.withPermission('tag:save')
|
||||||
|
.withOnClick((data: any) => showEditTagDialog(data));
|
||||||
|
|
||||||
|
const contextmenuDel = new ContextmenuItem('delete', '删除')
|
||||||
|
.withIcon('delete')
|
||||||
|
.withPermission('tag:del')
|
||||||
|
.withHideFunc((data: any) => {
|
||||||
|
// 存在子标签,则不允许删除
|
||||||
|
return data.children;
|
||||||
|
})
|
||||||
|
.withOnClick((data: any) => deleteTag(data));
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
data: [],
|
data: [],
|
||||||
@@ -149,6 +139,13 @@ const state = reactive({
|
|||||||
},
|
},
|
||||||
// 展开的节点
|
// 展开的节点
|
||||||
defaultExpandedKeys: [] as any,
|
defaultExpandedKeys: [] as any,
|
||||||
|
contextmenu: {
|
||||||
|
dropdown: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
items: [contextmenuInfo, contextmenuEdit, contextmenuAdd, contextmenuDel],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, saveTabDialog, infoDialog, defaultExpandedKeys } = toRefs(state);
|
const { data, saveTabDialog, infoDialog, defaultExpandedKeys } = toRefs(state);
|
||||||
@@ -188,12 +185,25 @@ const search = async () => {
|
|||||||
state.data = res;
|
state.data = res;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 树节点右击事件
|
||||||
|
const nodeContextmenu = (event: any, data: any) => {
|
||||||
|
const { clientX, clientY } = event;
|
||||||
|
state.contextmenu.dropdown.x = clientX;
|
||||||
|
state.contextmenu.dropdown.y = clientY;
|
||||||
|
contextmenuRef.value.openContextmenu(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
const treeNodeClick = () => {
|
||||||
|
// 关闭可能存在的右击菜单
|
||||||
|
contextmenuRef.value.closeContextmenu();
|
||||||
|
};
|
||||||
|
|
||||||
const info = async (data: any) => {
|
const info = async (data: any) => {
|
||||||
state.infoDialog.data = data;
|
state.infoDialog.data = data;
|
||||||
state.infoDialog.visible = true;
|
state.infoDialog.visible = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const showSaveTabDialog = (data: any) => {
|
const showSaveTagDialog = (data: any) => {
|
||||||
if (data) {
|
if (data) {
|
||||||
state.saveTabDialog.form.pid = data.id;
|
state.saveTabDialog.form.pid = data.id;
|
||||||
state.saveTabDialog.title = `新增 [${data.codePath}] 子标签信息`;
|
state.saveTabDialog.title = `新增 [${data.codePath}] 子标签信息`;
|
||||||
@@ -300,3 +310,4 @@ const removeDeafultExpandId = (id: any) => {
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@/components/contextmenu
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="menu">
|
<div class="menu">
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<div>
|
<div>
|
||||||
<span style="font-size: 14px"> <SvgIcon name="info-filled" />红色、橙色字体表示禁用状态 </span>
|
<span style="font-size: 14px"> <SvgIcon name="info-filled" />红色、橙色字体表示禁用状态 (右击资源进行操作) </span>
|
||||||
</div>
|
</div>
|
||||||
<el-button v-auth="perms.addResource" type="primary" icon="plus" @click="addResource(false)">添加</el-button>
|
<el-button v-auth="perms.addResource" type="primary" icon="plus" @click="addResource(false)">添加</el-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -14,8 +14,10 @@
|
|||||||
:data="data"
|
:data="data"
|
||||||
@node-expand="handleNodeExpand"
|
@node-expand="handleNodeExpand"
|
||||||
@node-collapse="handleNodeCollapse"
|
@node-collapse="handleNodeCollapse"
|
||||||
|
@node-contextmenu="nodeContextmenu"
|
||||||
|
@node-click="treeNodeClick"
|
||||||
:default-expanded-keys="defaultExpandedKeys"
|
:default-expanded-keys="defaultExpandedKeys"
|
||||||
:expand-on-click-node="false"
|
:expand-on-click-node="true"
|
||||||
draggable
|
draggable
|
||||||
:allow-drop="allowDrop"
|
:allow-drop="allowDrop"
|
||||||
@node-drop="handleDrop"
|
@node-drop="handleDrop"
|
||||||
@@ -34,43 +36,6 @@
|
|||||||
<span :style="data.status == 1 ? 'color: #67c23a;' : 'color: #f67c6c;'">{{ data.name }}</span>
|
<span :style="data.status == 1 ? 'color: #67c23a;' : 'color: #f67c6c;'">{{ data.name }}</span>
|
||||||
<span style="color: #3c8dbc">】</span>
|
<span style="color: #3c8dbc">】</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<el-link @click.prevent="info(data)" style="margin-left: 25px" icon="view" type="info" :underline="false" />
|
|
||||||
|
|
||||||
<el-link v-auth="perms.updateResource" @click.prevent="editResource(data)" class="ml5" type="primary" icon="edit" :underline="false" />
|
|
||||||
|
|
||||||
<el-link
|
|
||||||
v-auth="perms.addResource"
|
|
||||||
@click.prevent="addResource(data)"
|
|
||||||
v-if="data.type === menuTypeValue"
|
|
||||||
icon="circle-plus"
|
|
||||||
:underline="false"
|
|
||||||
type="success"
|
|
||||||
class="ml5"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<el-link
|
|
||||||
v-auth="perms.changeStatus"
|
|
||||||
@click.prevent="changeStatus(data, -1)"
|
|
||||||
v-if="data.status === 1"
|
|
||||||
icon="circle-close"
|
|
||||||
:underline="false"
|
|
||||||
type="warning"
|
|
||||||
class="ml5"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<el-link
|
|
||||||
v-auth="perms.changeStatus"
|
|
||||||
@click.prevent="changeStatus(data, 1)"
|
|
||||||
v-if="data.status === -1"
|
|
||||||
type="success"
|
|
||||||
icon="circle-check"
|
|
||||||
:underline="false"
|
|
||||||
plain
|
|
||||||
class="ml5"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<el-link v-auth="perms.delResource" @click.prevent="deleteMenu(data)" type="danger" icon="delete" :underline="false" plain class="ml5" />
|
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-tree>
|
</el-tree>
|
||||||
@@ -123,17 +88,21 @@
|
|||||||
<el-descriptions-item label="更新时间">{{ dateFormat(infoDialog.data.updateTime) }} </el-descriptions-item>
|
<el-descriptions-item label="更新时间">{{ dateFormat(infoDialog.data.updateTime) }} </el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<contextmenu :dropdown="state.contextmenu.dropdown" :items="state.contextmenu.items" ref="contextmenuRef" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { toRefs, reactive, onMounted } from 'vue';
|
import { ref, toRefs, reactive, onMounted } from 'vue';
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
import ResourceEdit from './ResourceEdit.vue';
|
import ResourceEdit from './ResourceEdit.vue';
|
||||||
import { ResourceTypeEnum } from '../enums';
|
import { ResourceTypeEnum } from '../enums';
|
||||||
import { resourceApi } from '../api';
|
import { resourceApi } from '../api';
|
||||||
import { dateFormat } from '@/common/utils/date';
|
import { dateFormat } from '@/common/utils/date';
|
||||||
import EnumValue from '@/common/Enum';
|
import EnumValue from '@/common/Enum';
|
||||||
|
import Contextmenu from '@/components/contextmenu/index.vue';
|
||||||
|
import { ContextmenuItem } from '@/components/contextmenu/index';
|
||||||
|
|
||||||
const menuTypeValue = ResourceTypeEnum.Menu.value;
|
const menuTypeValue = ResourceTypeEnum.Menu.value;
|
||||||
const permissionTypeValue = ResourceTypeEnum.Permission.value;
|
const permissionTypeValue = ResourceTypeEnum.Permission.value;
|
||||||
@@ -150,7 +119,46 @@ const props = {
|
|||||||
children: 'children',
|
children: 'children',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const contextmenuRef = ref();
|
||||||
|
|
||||||
|
const contextmenuInfo = new ContextmenuItem('info', '详情').withIcon('View').withOnClick((data: any) => info(data));
|
||||||
|
|
||||||
|
const contextmenuAdd = new ContextmenuItem('add', '添加子资源')
|
||||||
|
.withIcon('circle-plus')
|
||||||
|
.withPermission(perms.addResource)
|
||||||
|
.withHideFunc((data: any) => data.type !== menuTypeValue)
|
||||||
|
.withOnClick((data: any) => addResource(data));
|
||||||
|
|
||||||
|
const contextmenuEdit = new ContextmenuItem('edit', '编辑')
|
||||||
|
.withIcon('edit')
|
||||||
|
.withPermission(perms.updateResource)
|
||||||
|
.withOnClick((data: any) => editResource(data));
|
||||||
|
|
||||||
|
const contextmenuEnable = new ContextmenuItem('enable', '启用')
|
||||||
|
.withIcon('circle-check')
|
||||||
|
.withPermission(perms.updateResource)
|
||||||
|
.withHideFunc((data: any) => data.status === 1)
|
||||||
|
.withOnClick((data: any) => changeStatus(data, 1));
|
||||||
|
|
||||||
|
const contextmenuDisable = new ContextmenuItem('disable', '禁用')
|
||||||
|
.withIcon('circle-close')
|
||||||
|
.withPermission(perms.updateResource)
|
||||||
|
.withHideFunc((data: any) => data.status === -1)
|
||||||
|
.withOnClick((data: any) => changeStatus(data, -1));
|
||||||
|
|
||||||
|
const contextmenuDel = new ContextmenuItem('delete', '删除')
|
||||||
|
.withIcon('delete')
|
||||||
|
.withPermission(perms.delResource)
|
||||||
|
.withOnClick((data: any) => deleteMenu(data));
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
|
contextmenu: {
|
||||||
|
dropdown: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
items: [contextmenuInfo, contextmenuAdd, contextmenuEdit, contextmenuEnable, contextmenuDisable, contextmenuDel],
|
||||||
|
},
|
||||||
//弹出框对象
|
//弹出框对象
|
||||||
dialogForm: {
|
dialogForm: {
|
||||||
type: null,
|
type: null,
|
||||||
@@ -193,6 +201,19 @@ const search = async () => {
|
|||||||
state.data = res;
|
state.data = res;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 树节点右击事件
|
||||||
|
const nodeContextmenu = (event: any, data: any) => {
|
||||||
|
const { clientX, clientY } = event;
|
||||||
|
state.contextmenu.dropdown.x = clientX;
|
||||||
|
state.contextmenu.dropdown.y = clientY;
|
||||||
|
contextmenuRef.value.openContextmenu(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
const treeNodeClick = () => {
|
||||||
|
// 关闭可能存在的右击菜单
|
||||||
|
contextmenuRef.value.closeContextmenu();
|
||||||
|
};
|
||||||
|
|
||||||
const deleteMenu = (data: any) => {
|
const deleteMenu = (data: any) => {
|
||||||
ElMessageBox.confirm(`此操作将删除 [${data.name}], 是否继续?`, '提示', {
|
ElMessageBox.confirm(`此操作将删除 [${data.name}], 是否继续?`, '提示', {
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
@@ -378,3 +399,4 @@ const info = async (data: any) => {
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@/components/contextmenu
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ func (r *resourceAppImpl) checkCode(code string) error {
|
|||||||
if strings.Contains(code, ",") {
|
if strings.Contains(code, ",") {
|
||||||
return errorx.NewBiz("code不能包含','")
|
return errorx.NewBiz("code不能包含','")
|
||||||
}
|
}
|
||||||
if gormx.CountBy(&entity.Resource{Code: code}) == 0 {
|
if gormx.CountBy(&entity.Resource{Code: code}) != 0 {
|
||||||
return errorx.NewBiz("该code已存在")
|
return errorx.NewBiz("该code已存在")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
Reference in New Issue
Block a user