mirror of
https://gitee.com/orangeform/orange-admin.git
synced 2026-01-18 02:56:30 +08:00
commit:升级到vue3,更新最近工作流技术栈,支持sa-token
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<el-breadcrumb class="app-breadcrumb" separator="/">
|
||||
<el-breadcrumb-item :to="{ name: layoutStore.indexName }" :replace="true" @click="hadleClick">
|
||||
<div class="breadcrumb-home">
|
||||
<img src="@/assets/img/s-home.png" alt="" /><span>首页</span>
|
||||
</div>
|
||||
</el-breadcrumb-item>
|
||||
<el-breadcrumb-item v-for="item in layoutStore.currentMenuPath" :key="item.menuId">
|
||||
{{ item.menuName }}
|
||||
</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useLayoutStore } from '@/store';
|
||||
const layoutStore = useLayoutStore();
|
||||
// 返回首页时,设置当前菜单为空
|
||||
const hadleClick = () => {
|
||||
layoutStore.setCurrentMenu(null);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
||||
.breadcrumb-home {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
img {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
margin-right: 4px;
|
||||
vertical-align: top;
|
||||
}
|
||||
span {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
.el-breadcrumb__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
span {
|
||||
color: #999;
|
||||
}
|
||||
:deep(.el-breadcrumb__inner) {
|
||||
color: #999;
|
||||
}
|
||||
&:last-child :deep(.el-breadcrumb__inner) {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
.app-breadcrumb.el-breadcrumb {
|
||||
display: inline-block;
|
||||
line-height: 60px;
|
||||
margin-left: 25px;
|
||||
.no-redirect {
|
||||
color: #97a8be;
|
||||
cursor: text;
|
||||
}
|
||||
img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
:deep(.el-breadcrumb__inner.is-link) {
|
||||
color: #606266 !important;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,141 @@
|
||||
<template>
|
||||
<div style="position: relative; height: 100%" class="sidebar-bg">
|
||||
<multiColumn v-if="layoutStore.supportColumn" :menuList="layoutStore.menuList" />
|
||||
<div
|
||||
v-else
|
||||
class="left-menu"
|
||||
:class="layoutStore.collapsed ? 'collapse' : ''"
|
||||
style="height: 100%; padding-top: 16px; padding-bottom: 16px"
|
||||
>
|
||||
<el-scrollbar wrap-class="scrollbar_dropdown__wrap" style="height: 100%">
|
||||
<el-menu
|
||||
mode="vertical"
|
||||
active-text-color="#ffd04b"
|
||||
text-color="#a4a5a7"
|
||||
background-color="#2d3039"
|
||||
:default-active="layoutStore.currentMenuId"
|
||||
:unique-opened="true"
|
||||
@select="selectMenuById"
|
||||
:collapse="layoutStore.collapsed"
|
||||
>
|
||||
<template v-for="menu in layoutStore.menuList" :key="menu.menuId">
|
||||
<sub-menu :menu="menu" />
|
||||
</template>
|
||||
</el-menu>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useLayoutStore } from '@/store';
|
||||
import SubMenu from './SubMenu.vue';
|
||||
import multiColumn from './multi-column.vue';
|
||||
import { useSelectMenu } from './hooks';
|
||||
|
||||
const layoutStore = useLayoutStore();
|
||||
const { selectMenuById } = useSelectMenu();
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.sidebar-title-text {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.left-menu .el-submenu__title {
|
||||
height: 50px;
|
||||
color: #a4a5a7;
|
||||
line-height: 50px;
|
||||
i {
|
||||
color: #a4a5a7 !important;
|
||||
}
|
||||
.el-submenu__icon-arrow {
|
||||
margin-top: -4px;
|
||||
}
|
||||
}
|
||||
.sidebar-bg {
|
||||
.el-menu-item {
|
||||
color: #a4a5a7;
|
||||
}
|
||||
}
|
||||
.collapse .is-active .el-submenu__title {
|
||||
position: relative;
|
||||
background-color: #43474e !important;
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: block;
|
||||
width: 4px;
|
||||
height: 100%;
|
||||
background: $color-primary;
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
.collapse .el-icon-arrow-right {
|
||||
display: none;
|
||||
}
|
||||
.el-menu-item:hover,
|
||||
.el-submenu__title:hover {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
.el-menu-item.is-active {
|
||||
color: white !important;
|
||||
background-color: #43474e !important;
|
||||
border-radius: 4px;
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: block;
|
||||
width: 4px;
|
||||
height: 100%;
|
||||
background: $color-primary !important;
|
||||
content: '';
|
||||
}
|
||||
i {
|
||||
color: white !important;
|
||||
}
|
||||
}
|
||||
.el-menu--vertical .el-submenu__title {
|
||||
& > span {
|
||||
display: inline !important;
|
||||
}
|
||||
}
|
||||
.el-menu--vertical .el-menu.el-menu--popup {
|
||||
padding: 16px 8px;
|
||||
background-color: white !important;
|
||||
.el-menu-item,
|
||||
.el-submenu__title {
|
||||
height: 40px;
|
||||
padding: 0 3px !important;
|
||||
color: #333;
|
||||
line-height: 40px;
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
&:hover {
|
||||
background-color: #f6f6f6 !important;
|
||||
& > .multi-column-menu-popover {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
&.is-active {
|
||||
color: $color-primary !important;
|
||||
background-color: $color-primary-light-9 !important;
|
||||
}
|
||||
.el-submenu__icon-arrow {
|
||||
margin-top: -5px;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
.el-menu--vertical {
|
||||
transform: translateX(6px);
|
||||
}
|
||||
}
|
||||
.sidebar-bg .el-submenu {
|
||||
.el-menu {
|
||||
background-color: #1d1f24 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<div class="menu-wrapper">
|
||||
<el-menu-item
|
||||
ref="item"
|
||||
:index="menu.menuId"
|
||||
v-if="menu.children == null || menu.children.length == 0"
|
||||
>
|
||||
<template v-slot:title>
|
||||
<orange-icon v-if="menu.icon" :icon="menu.icon" class="menu-icon" />
|
||||
<span :style="getTextStyle(!menu.icon)">{{ menu.menuName }}</span>
|
||||
</template>
|
||||
<orange-icon
|
||||
v-if="menu.icon && !isChild && store.collapsed"
|
||||
:icon="menu.icon"
|
||||
class="menu-icon"
|
||||
/>
|
||||
</el-menu-item>
|
||||
<el-sub-menu v-else :index="menu.menuId">
|
||||
<template v-slot:title>
|
||||
<orange-icon v-if="menu.icon" :icon="menu.icon" class="menu-icon" />
|
||||
<span :style="getTextStyle(!menu.icon)" v-show="!store.collapsed || isChild">{{
|
||||
menu.menuName
|
||||
}}</span>
|
||||
</template>
|
||||
<template v-for="child in menu.children" :key="child.menuId">
|
||||
<sub-menu class="nest-menu" :menu="child" :isChild="true" />
|
||||
</template>
|
||||
</el-sub-menu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { MenuItem } from '@/types/upms/menu';
|
||||
import { useLayoutStore } from '@/store';
|
||||
import OrangeIcon from '@/components/icons/index.vue';
|
||||
|
||||
interface IProps {
|
||||
menu: MenuItem;
|
||||
isChild?: boolean;
|
||||
}
|
||||
|
||||
const store = useLayoutStore();
|
||||
const props: IProps = defineProps<IProps>();
|
||||
|
||||
// const getIconStyle = (isShow: boolean) => {
|
||||
// if (isShow && props.isChild) {
|
||||
// return [{ 'margin-left': '13px' }];
|
||||
// }
|
||||
// };
|
||||
|
||||
const getTextStyle = (isShow: boolean) => {
|
||||
if (isShow && props.isChild) {
|
||||
return [{ 'padding-left': '13px' }];
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.menu-icon {
|
||||
margin-right: 10px;
|
||||
font-size: 18px;
|
||||
}
|
||||
.nest-menu :deep(.el-menu-item span:first-child),
|
||||
.nest-menu :deep(.el-menu-item .menu-icon:first-child),
|
||||
.nest-menu :deep(.el-submenu__title span:first-child) {
|
||||
padding-left: 8px !important;
|
||||
}
|
||||
.nest-menu :deep(.el-submenu__title .menu-icon:first-child) {
|
||||
margin-left: 8px !important;
|
||||
}
|
||||
</style>
|
||||
@/types/upms/menu
|
||||
@@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<div
|
||||
ref="root"
|
||||
class="tags-item"
|
||||
:class="{ active: active }"
|
||||
@mouseenter.prevent="mouseEnterHandle"
|
||||
@mouseout.prevent="mouseLeaveHandle"
|
||||
@mousemove.prevent="mouseMoveHandle"
|
||||
>
|
||||
<span class="title">{{ title }}</span>
|
||||
<el-icon
|
||||
ref="icon"
|
||||
v-if="supportClose && (mouseEnter || active)"
|
||||
style="margin-left: 6px; color: #999"
|
||||
@mouseenter.prevent="iconMouseEnterHandle"
|
||||
@mouseleave.prevent="iconMouseLeaveHandle"
|
||||
@click.stop="onClose"
|
||||
>
|
||||
<Close v-if="!iconEnter" /><CircleCloseFilled class="hover-close" v-if="iconEnter" />
|
||||
</el-icon>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
title: string;
|
||||
supportClose?: boolean;
|
||||
active: boolean;
|
||||
}>(),
|
||||
{
|
||||
supportClose: true,
|
||||
active: false,
|
||||
},
|
||||
);
|
||||
|
||||
const root = ref<HTMLElement>();
|
||||
const icon = ref<ComponentPublicInstance>();
|
||||
const mouseEnter = ref(false);
|
||||
const iconEnter = ref(false);
|
||||
|
||||
const onClose = () => {
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const mouseEnterHandle = () => {
|
||||
//iconEnter.value = false;
|
||||
mouseEnter.value = true;
|
||||
};
|
||||
// 解决鼠标移动过快mouseleave失效,图标不能正确改变样式
|
||||
const mouseMoveHandle = (e: MouseEvent) => {
|
||||
if (['DIV', 'SPAN'].includes((e.target as HTMLElement).nodeName)) {
|
||||
iconEnter.value = false;
|
||||
}
|
||||
};
|
||||
const mouseLeaveHandle = (e: MouseEvent) => {
|
||||
iconEnter.value = false;
|
||||
//console.log(props.title, root.value?.contains(e.relatedTarget as Node), e);
|
||||
if (e.type == 'mouseout' && root.value?.contains(e.relatedTarget as Node)) {
|
||||
return;
|
||||
}
|
||||
mouseEnter.value = false;
|
||||
};
|
||||
const iconMouseEnterHandle = () => {
|
||||
iconEnter.value = true;
|
||||
};
|
||||
const iconMouseLeaveHandle = () => {
|
||||
iconEnter.value = false;
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
root,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tags-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 28px;
|
||||
padding: 0 10px;
|
||||
color: #999;
|
||||
background: white;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 3px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
}
|
||||
.tags-item .title {
|
||||
font-size: 12px;
|
||||
}
|
||||
.close {
|
||||
display: none;
|
||||
vertical-align: middle;
|
||||
color: #999;
|
||||
}
|
||||
.tags-item.active .close {
|
||||
display: inline-block;
|
||||
margin-left: 6px;
|
||||
}
|
||||
.hover-close {
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
.tags-item:hover {
|
||||
color: #333;
|
||||
}
|
||||
.tags-item.active {
|
||||
color: #333;
|
||||
}
|
||||
.tags-item + .tags-item {
|
||||
margin-left: 8px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,282 @@
|
||||
<template>
|
||||
<div class="tags-panel" ref="panel">
|
||||
<el-icon class="arrow left"><el-icon-arrow-left @click="leftClick" /></el-icon>
|
||||
<el-icon class="arrow right"><el-icon-arrow-right @click="rightClick" /></el-icon>
|
||||
<div class="main-panel">
|
||||
<div
|
||||
class="scroll-box"
|
||||
ref="scroll"
|
||||
:style="{ transform: 'translateX(' + translateX + 'px)' }"
|
||||
>
|
||||
<TagItem
|
||||
ref="home"
|
||||
class="item"
|
||||
title="主页"
|
||||
:active="layoutStore.currentMenuId == null"
|
||||
:supportClose="false"
|
||||
@click="onTagItemClick(null)"
|
||||
@contextmenu.prevent="openMenu(null, $event)"
|
||||
/>
|
||||
<TagItem
|
||||
ref="items"
|
||||
class="item"
|
||||
v-for="item in layoutStore.tagList"
|
||||
:key="item.menuId"
|
||||
:title="item.menuName"
|
||||
:active="item.menuId == layoutStore.currentMenuId"
|
||||
@close="onTagItemClose(item)"
|
||||
@click="onTagItemClick(item)"
|
||||
@contextmenu.prevent="openMenu(item, $event)"
|
||||
></TagItem>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-show="contextMenuVisible"
|
||||
@click.stop="onMenuMaskClick"
|
||||
@contextmenu="openMaskMenu"
|
||||
style="
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 99999;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: rgb(0 0 0 / 1%);
|
||||
"
|
||||
>
|
||||
<ul
|
||||
class="contextmenu"
|
||||
style="z-index: 99999; background: white"
|
||||
:style="{ left: left + 'px', top: top + 'px' }"
|
||||
>
|
||||
<li @click="closeSelectTag" :class="currentMenu == null ? 'disabled' : ''">关闭</li>
|
||||
<li @click="closeOthersTags">关闭其他</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useLayoutStore } from '@/store';
|
||||
import { MenuItem } from '@/types/upms/menu';
|
||||
import { T } from '@/types/generic';
|
||||
import TagItem from './TagItem.vue';
|
||||
|
||||
const layoutStore = useLayoutStore();
|
||||
|
||||
const panel = ref();
|
||||
const scroll = ref();
|
||||
const home = ref();
|
||||
const items = ref<T[]>([]);
|
||||
|
||||
const contextMenuVisible = ref(false);
|
||||
const translateX = ref(0);
|
||||
const left = ref(0);
|
||||
const top = ref(0);
|
||||
|
||||
let currentMenu: MenuItem | null = null;
|
||||
|
||||
const onTagItemClick = (item: MenuItem | null) => {
|
||||
layoutStore.setCurrentMenu(item);
|
||||
};
|
||||
const openMenu = (item: MenuItem | null, event: MouseEvent) => {
|
||||
// console.log(item, event);
|
||||
currentMenu = item;
|
||||
contextMenuVisible.value = true;
|
||||
left.value = event.clientX;
|
||||
top.value = event.clientY;
|
||||
};
|
||||
const onMenuMaskClick = () => {
|
||||
console.log('onMenuMaskClick');
|
||||
contextMenuVisible.value = false;
|
||||
};
|
||||
const openMaskMenu = (e: MouseEvent) => {
|
||||
// console.log('openMaskMenu');
|
||||
e.preventDefault();
|
||||
};
|
||||
const closeSelectTag = () => {
|
||||
if (currentMenu != null) {
|
||||
layoutStore.removeTag(currentMenu.menuId);
|
||||
}
|
||||
};
|
||||
const closeOthersTags = () => {
|
||||
//console.log('closeOthersTags');
|
||||
if (currentMenu != null) {
|
||||
layoutStore.closeOtherTags(currentMenu.menuId);
|
||||
} else {
|
||||
layoutStore.clearAllTags();
|
||||
}
|
||||
};
|
||||
const onTagItemClose = (item: MenuItem) => {
|
||||
layoutStore.removeTag(item.menuId);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initTagPanel(layoutStore.tagList);
|
||||
scrollToMenu(layoutStore.currentMenu);
|
||||
});
|
||||
|
||||
function initTagPanel(tagList: MenuItem[]) {
|
||||
nextTick(() => {
|
||||
let width = (home.value ? home.value.root.offsetWidth : 0) + 60 + tagList.length * 5;
|
||||
//console.log('width', width);
|
||||
items.value.forEach(row => {
|
||||
//console.log(row);
|
||||
width += row.root.offsetWidth + 5;
|
||||
});
|
||||
//console.log('width', width);
|
||||
scroll.value.style.width = width + 'px';
|
||||
const showArrow = width > panel.value.offsetWidth;
|
||||
if (!showArrow) {
|
||||
translateX.value = 0;
|
||||
} else if (panel.value.offsetWidth - width >= translateX.value && translateX.value !== 0) {
|
||||
translateX.value = panel.value.offsetWidth - width;
|
||||
}
|
||||
//console.log('xxx', translateX.value);
|
||||
});
|
||||
}
|
||||
|
||||
function scrollToMenu(menu: MenuItem) {
|
||||
nextTick(() => {
|
||||
layoutStore.tagList.forEach((row, index) => {
|
||||
if (row.menuId === menu.menuId) {
|
||||
let el = items.value[index].root;
|
||||
if (-el.offsetLeft > translateX.value) {
|
||||
translateX.value = -el.offsetLeft;
|
||||
} else if (
|
||||
el.offsetLeft + el.offsetWidth + 60 >
|
||||
panel.value.offsetWidth - translateX.value
|
||||
) {
|
||||
translateX.value = panel.value.offsetWidth - el.offsetLeft - el.offsetWidth - 60;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => layoutStore.currentMenu,
|
||||
(menu: MenuItem) => {
|
||||
scrollToMenu(menu);
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => layoutStore.tagList,
|
||||
tagList => {
|
||||
if (tagList && tagList.length) {
|
||||
initTagPanel(tagList);
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
const leftClick = () => {
|
||||
if (!items.value) return;
|
||||
let x = 0;
|
||||
|
||||
for (let i = layoutStore.tagList.length - 1; i >= 0; i--) {
|
||||
const el = items.value[i].root;
|
||||
//console.log(el.innerText, layoutStore.tagList[i].menuName);
|
||||
console.log(el.offsetLeft, translateX.value, -el.offsetLeft > translateX.value);
|
||||
if (-el.offsetLeft > translateX.value) {
|
||||
x = -el.offsetLeft;
|
||||
break;
|
||||
}
|
||||
console.log('x', x);
|
||||
}
|
||||
if (x > 0) {
|
||||
x = 0;
|
||||
}
|
||||
|
||||
console.log('x', x);
|
||||
|
||||
translateX.value = x;
|
||||
};
|
||||
|
||||
const rightClick = () => {
|
||||
if (!items.value) return;
|
||||
let x = translateX.value;
|
||||
for (let i = 0; i < layoutStore.tagList.length; i++) {
|
||||
const el = items.value[i].root;
|
||||
if (el.offsetLeft + el.offsetWidth + 60 > panel.value.offsetWidth - translateX.value) {
|
||||
x = panel.value.offsetWidth - el.offsetLeft - el.offsetWidth - 60;
|
||||
break;
|
||||
}
|
||||
}
|
||||
console.log('x', x, panel.value.offsetWidth, scroll.value.offsetWidth);
|
||||
let max = panel.value.offsetWidth - scroll.value.offsetWidth - 60;
|
||||
console.log('max', max);
|
||||
if (x < max) {
|
||||
x = max;
|
||||
}
|
||||
if (x > 0) x = 0;
|
||||
translateX.value = x;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tags-panel {
|
||||
width: 100px;
|
||||
background-color: white;
|
||||
flex: 1;
|
||||
}
|
||||
.main-panel {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
margin: 0 30px;
|
||||
}
|
||||
.scroll-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
white-space: nowrap;
|
||||
transition: 0.3s;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
.arrow {
|
||||
z-index: 100;
|
||||
width: 30px;
|
||||
height: 48px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
line-height: 48px;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.arrow.left {
|
||||
float: left;
|
||||
}
|
||||
.arrow.right {
|
||||
float: right;
|
||||
}
|
||||
.contextmenu {
|
||||
position: fixed;
|
||||
z-index: 2;
|
||||
padding: 5px 0;
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
border-radius: 5px;
|
||||
box-shadow: 2px 2px 3px 0 rgb(0 0 0 / 30%);
|
||||
list-style-type: none;
|
||||
font-weight: 400;
|
||||
}
|
||||
.contextmenu li {
|
||||
padding: 7px 16px;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.contextmenu li.disabled {
|
||||
padding: 7px 16px;
|
||||
margin: 0;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.contextmenu li:hover {
|
||||
background: #eee;
|
||||
}
|
||||
</style>
|
||||
@/types/upms/menu
|
||||
@@ -0,0 +1,51 @@
|
||||
import { SysMenuBindType } from '@/common/staticDict';
|
||||
import { getToken } from '@/common/utils';
|
||||
import { useLayoutStore } from '@/store';
|
||||
import { findMenuItemById } from '@/store/utils';
|
||||
import { MenuItem } from '@/types/upms/menu';
|
||||
|
||||
export const useSelectMenu = () => {
|
||||
const layoutStore = useLayoutStore();
|
||||
|
||||
/**
|
||||
* 选择菜单,跳转到目标页面
|
||||
* 外链弹出新窗口
|
||||
*
|
||||
* @param menuId 菜单ID
|
||||
*/
|
||||
const selectMenuById = (menuId: string) => {
|
||||
const menuItem: MenuItem | null = findMenuItemById(menuId, layoutStore.menuList);
|
||||
if (menuItem) selectMenu(menuItem);
|
||||
};
|
||||
|
||||
/**
|
||||
* 选择菜单,跳转到目标页面
|
||||
* 外链弹出新窗口
|
||||
*
|
||||
* @param menu 菜单项
|
||||
*/
|
||||
const selectMenu = (menu: MenuItem) => {
|
||||
// TODO 外链暂时直接弹出新窗口,有其它规则时,可以从这里开始修改
|
||||
if (
|
||||
menu != null &&
|
||||
menu.bindType === SysMenuBindType.THRID_URL &&
|
||||
menu.targetUrl != null &&
|
||||
menu.targetUrl !== ''
|
||||
) {
|
||||
const token = getToken();
|
||||
let targetUrl = menu.targetUrl;
|
||||
if (targetUrl.indexOf('?') === -1) {
|
||||
targetUrl = targetUrl + '?';
|
||||
}
|
||||
targetUrl = targetUrl + 'token=' + token;
|
||||
window.open(targetUrl);
|
||||
return;
|
||||
}
|
||||
layoutStore.setCurrentMenu(menu);
|
||||
};
|
||||
|
||||
return {
|
||||
selectMenuById,
|
||||
selectMenu,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<ul class="multi-column-menu">
|
||||
<template v-for="menu in menuList" :key="menu.menuId">
|
||||
<el-popover
|
||||
placement="right-start"
|
||||
width="220"
|
||||
trigger="hover"
|
||||
:disabled="!menu.children || (menu.children || []).length === 0 || level >= 1"
|
||||
:show-arrow="false"
|
||||
>
|
||||
<template v-slot:reference>
|
||||
<li
|
||||
@click="selectMenuItem(menu)"
|
||||
:class="{ active: layoutStore.currentMenuId === menu.menuId }"
|
||||
>
|
||||
<div class="menu-name">
|
||||
<orange-icon
|
||||
v-if="menu.icon"
|
||||
:icon="menu.icon"
|
||||
style="margin-right: 5px; font-size: 18px"
|
||||
/>
|
||||
{{ menu.menuName }}
|
||||
</div>
|
||||
<el-icon v-if="menu.children && menu.children.length"><el-icon-arrow-right /></el-icon>
|
||||
<div
|
||||
class="multi-column-menu-popover"
|
||||
:class="{ level2: level > 1 }"
|
||||
v-if="level >= 1 && (menu.children || []).length"
|
||||
>
|
||||
<div class="popover-box">
|
||||
<multiColumnMenu
|
||||
:menuList="menu.children"
|
||||
:key="column?.menuId + '-' + menu.menuId"
|
||||
:level="2"
|
||||
:column="column"
|
||||
@select="select"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
<multiColumnMenu
|
||||
v-if="(menu.children || []).length && level < 1"
|
||||
:menuList="menu.children"
|
||||
:level="level + 1"
|
||||
:column="column"
|
||||
@select="select"
|
||||
/>
|
||||
</el-popover>
|
||||
</template>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ArrowRight as ElIconArrowRight } from '@element-plus/icons-vue';
|
||||
import { MenuItem } from '@/types/upms/menu';
|
||||
import { useLayoutStore } from '@/store';
|
||||
import OrangeIcon from '@/components/icons/index.vue';
|
||||
import { SysMenuType } from '@/common/staticDict';
|
||||
import { useSelectMenu } from './hooks';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'select'): void;
|
||||
}>();
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
level: number;
|
||||
menuList?: Array<MenuItem>;
|
||||
column?: MenuItem;
|
||||
}>(),
|
||||
{
|
||||
level: 0,
|
||||
},
|
||||
);
|
||||
|
||||
const layoutStore = useLayoutStore();
|
||||
|
||||
const { selectMenu } = useSelectMenu();
|
||||
|
||||
const selectMenuItem = (menu: MenuItem) => {
|
||||
if (layoutStore.currentMenuId == menu.menuId || menu.menuType == SysMenuType.DIRECTORY) return;
|
||||
// 单页面清空所有tags和cachePage
|
||||
// if (!layoutStore.supportTags) {
|
||||
// layoutStore.clearAllTags();
|
||||
// }
|
||||
|
||||
if (props.column && props.column.menuId !== layoutStore.currentColumnId) {
|
||||
layoutStore.setCurrentColumn(props.column);
|
||||
}
|
||||
nextTick(() => {
|
||||
selectMenu(menu);
|
||||
select();
|
||||
});
|
||||
};
|
||||
const select = () => {
|
||||
emit('select');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.multi-column-menu {
|
||||
width: 200px;
|
||||
padding: 0 8px;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
li {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
padding: 0 16px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
.menu-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
i {
|
||||
color: #999;
|
||||
}
|
||||
&:hover {
|
||||
background-color: #f6f6f6;
|
||||
& > .multi-column-menu-popover {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
&.active {
|
||||
color: $color-primary;
|
||||
background-color: $color-primary-light-9;
|
||||
}
|
||||
}
|
||||
}
|
||||
.multi-column-menu-popover {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 100%;
|
||||
display: none;
|
||||
padding-left: 16px;
|
||||
&.level2 {
|
||||
padding-left: 22px;
|
||||
}
|
||||
.popover-box {
|
||||
padding: 12px;
|
||||
background-color: white;
|
||||
box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@/types/upms/menu
|
||||
@@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<div class="multi-column-wrap">
|
||||
<el-scrollbar
|
||||
wrap-class="scrollbar_dropdown__wrap"
|
||||
style="width: 80px; height: calc(100vh - 60px)"
|
||||
v-if="menuList && menuList.length"
|
||||
>
|
||||
<ul class="multi-column-list">
|
||||
<template v-for="(menu, index) in menuList" :key="menu.menuId">
|
||||
<el-popover
|
||||
ref="popover"
|
||||
placement="right-start"
|
||||
width="220"
|
||||
trigger="hover"
|
||||
:disabled="
|
||||
!menu.children || (menu.children || []).length === 0 || !layoutStore.collapsed
|
||||
"
|
||||
:show-arrow="false"
|
||||
>
|
||||
<template v-slot:reference>
|
||||
<li
|
||||
@click="onColumnChange(menu)"
|
||||
:class="{ active: layoutStore.currentColumnId === menu.menuId }"
|
||||
>
|
||||
<orange-icon v-if="menu.icon" :icon="menu.icon" />
|
||||
<p :title="menu.menuName.length > 4 ? menu.menuName : undefined">
|
||||
{{ menu.menuName }}
|
||||
</p>
|
||||
</li>
|
||||
</template>
|
||||
<multiColumnMenu
|
||||
v-if="(menu.children || []).length"
|
||||
:menuList="menu.children"
|
||||
:level="1"
|
||||
@select="selectMenu(index)"
|
||||
:column="menu"
|
||||
/>
|
||||
</el-popover>
|
||||
</template>
|
||||
</ul>
|
||||
</el-scrollbar>
|
||||
<el-scrollbar
|
||||
v-if="children && children.length"
|
||||
class="children-menu-scrollbar"
|
||||
wrap-class="scrollbar_dropdown__wrap"
|
||||
style="height: calc(100vh - 60px); background-color: white"
|
||||
:scroll-x="false"
|
||||
>
|
||||
<div style="padding: 24px 0">
|
||||
<multiColumnMenu
|
||||
:menuList="children"
|
||||
:level="0"
|
||||
:key="layoutStore.currentColumnId"
|
||||
:columnId="layoutStore.currentColumnId"
|
||||
/>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PopoverInstance } from 'element-plus';
|
||||
import { MenuItem } from '@/types/upms/menu';
|
||||
import { useLayoutStore } from '@/store';
|
||||
import OrangeIcon from '@/components/icons/index.vue';
|
||||
import multiColumnMenu from './multi-column-menu.vue';
|
||||
|
||||
defineProps<{ menuList: MenuItem[] }>();
|
||||
const popover: Ref<PopoverInstance[]> = ref([]);
|
||||
const children: Ref<MenuItem[]> = ref([]);
|
||||
|
||||
const layoutStore = useLayoutStore();
|
||||
|
||||
const onColumnChange = (column: MenuItem) => {
|
||||
layoutStore.setCurrentColumn(column);
|
||||
};
|
||||
|
||||
const selectMenu = (index: number) => {
|
||||
// console.log('selectMenu', index);
|
||||
// 自动隐藏弹出体,只在侧边栏折叠状态下才会触发
|
||||
popover.value[index].hide();
|
||||
};
|
||||
|
||||
watch(
|
||||
() => layoutStore.currentColumn,
|
||||
(newVal, oldVal) => {
|
||||
if (newVal == oldVal) return;
|
||||
children.value = newVal.children || [];
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.multi-column-wrap {
|
||||
display: flex;
|
||||
min-width: 81px;
|
||||
height: 100%;
|
||||
border-right: 1px solid #e8e8e8;
|
||||
.children-menu-scrollbar {
|
||||
width: 0;
|
||||
flex: 1;
|
||||
.el-scrollbar__bar.is-horizontal {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.multi-column-list {
|
||||
width: 80px;
|
||||
padding: 16px 0;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
list-style: none;
|
||||
li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 80px;
|
||||
font-size: 14px !important;
|
||||
color: #a4a5a7;
|
||||
flex-direction: column;
|
||||
justify-items: center;
|
||||
cursor: pointer;
|
||||
&.active,
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background-color: rgb(246 246 246 / 30%);
|
||||
}
|
||||
i {
|
||||
margin-top: 14px;
|
||||
font-size: 24px !important;
|
||||
}
|
||||
p {
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
padding: 0 10px;
|
||||
margin: 12px 0 0;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@/types/upms/menu
|
||||
495
OrangeFormsOpen-VUE3/src/components/layout/index.vue
Normal file
495
OrangeFormsOpen-VUE3/src/components/layout/index.vue
Normal file
@@ -0,0 +1,495 @@
|
||||
<template>
|
||||
<el-container :class="'container-' + defaultFormItemSize" :style="getMainStyle">
|
||||
<el-container style="background-color: #f5f8f9">
|
||||
<el-header
|
||||
class="header"
|
||||
style="padding: 0"
|
||||
:class="layoutStore.supportColumn ? 'multi-column-header' : ''"
|
||||
>
|
||||
<div class="logo has-multi-column" v-if="layoutStore.supportColumn">
|
||||
<img src="@/assets/img/logo_white.png" alt="" />
|
||||
</div>
|
||||
<div class="header-main">
|
||||
<div
|
||||
class="logo"
|
||||
v-if="!layoutStore.supportColumn"
|
||||
style="padding-left: 8px; margin-right: 8px"
|
||||
>
|
||||
<img src="@/assets/img/login_logo.png" alt="" />
|
||||
</div>
|
||||
<div class="title">{{ projectName }}</div>
|
||||
<bread-crumb class="breadcrumb-container" />
|
||||
<div class="header-menu" style="flex-grow: 1">
|
||||
<el-dropdown trigger="click" style="margin-right: 14px" @command="handleMessage">
|
||||
<el-badge
|
||||
is-dot
|
||||
:hidden="!messageCount.totalCount || messageCount.totalCount <= 0"
|
||||
style="height: 20px; line-height: 20px; cursor: pointer"
|
||||
>
|
||||
<i class="online-icon icon-message" style="font-size: 16px; color: #333" />
|
||||
</el-badge>
|
||||
<template v-slot:dropdown>
|
||||
<el-dropdown-menu style="min-width: 130px">
|
||||
<el-dropdown-item class="user-dropdown-item" command="remindingMessage">
|
||||
催办消息
|
||||
<el-badge
|
||||
:value="messageCount.remindingMessageCount"
|
||||
:hidden="
|
||||
!messageCount.remindingMessageCount ||
|
||||
messageCount.remindingMessageCount <= 0
|
||||
"
|
||||
/>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item class="user-dropdown-item" command="copyMessage">
|
||||
抄送消息
|
||||
<el-badge
|
||||
:value="messageCount.copyMessageCount"
|
||||
:hidden="!messageCount.copyMessageCount || messageCount.copyMessageCount <= 0"
|
||||
/>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<span class="line"></span>
|
||||
<img :src="headerImg ? headerImg : defaultHeaderImg" class="header-img" />
|
||||
<el-dropdown class="user-dropdown" trigger="click" @command="handleCommand">
|
||||
<span class="el-dropdown-link"
|
||||
>{{ (userInfo || {}).showName
|
||||
}}<el-icon class="el-icon--right"><el-icon-arrow-down /></el-icon>
|
||||
</span>
|
||||
<template v-slot:dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item class="user-dropdown-item" command="modifyPassword"
|
||||
>修改密码</el-dropdown-item
|
||||
>
|
||||
<el-dropdown-item class="user-dropdown-item" command="modifyHeadImage"
|
||||
>修改头像</el-dropdown-item
|
||||
>
|
||||
<el-dropdown-item class="user-dropdown-item" command="logout"
|
||||
>退出登录</el-dropdown-item
|
||||
>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</el-header>
|
||||
<el-main class="layout-main">
|
||||
<el-aside
|
||||
:width="
|
||||
layoutStore.collapsed
|
||||
? layoutStore.supportColumn
|
||||
? '80px'
|
||||
: '64px'
|
||||
: layoutStore.supportColumn
|
||||
? '280px'
|
||||
: '204px'
|
||||
"
|
||||
class="sidebar"
|
||||
>
|
||||
<sidebar style="overflow: hidden"></sidebar>
|
||||
</el-aside>
|
||||
<div class="layout-content">
|
||||
<div class="tag-wrap" v-if="layoutStore.supportTags">
|
||||
<i
|
||||
class="online-icon"
|
||||
:class="layoutStore.collapsed ? 'icon-expand' : 'icon-unexpand'"
|
||||
style="font-size: 16px; color: #333; cursor: pointer"
|
||||
@click="() => layoutStore.toggleCollapsed()"
|
||||
/>
|
||||
<tag-panel />
|
||||
</div>
|
||||
<el-scrollbar wrap-class="scrollbar_dropdown__wrap" :style="getContextStyle">
|
||||
<router-view
|
||||
v-slot="{ Component }"
|
||||
class="page-box"
|
||||
style="overflow: hidden; margin: 16px"
|
||||
:style="getRouterViewStyle"
|
||||
>
|
||||
<transition name="el-fade-in-linear" :appear="true">
|
||||
<keep-alive :include="layoutStore.cachePages">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'Layout',
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import defaultHeaderImg from '@/assets/img/default-header.jpg';
|
||||
import { getToken, setToken } from '@/common/utils/index';
|
||||
import { useLayoutStore, useLoginStore, useMessage } from '@/store';
|
||||
import { SysMenuBindType, SysOnlineFormType } from '@/common/staticDict';
|
||||
import LoginController from '@/api/system/LoginController';
|
||||
import { MenuItem } from '@/types/upms/menu';
|
||||
import { useUpload } from '@/common/hooks/useUpload';
|
||||
import Sidebar from './components/Sidebar.vue';
|
||||
import BreadCrumb from './components/BreadCrumb.vue';
|
||||
import TagPanel from './components/TagPanel.vue';
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const layoutStore = useLayoutStore();
|
||||
const loginStore = useLoginStore();
|
||||
const documentClientHeight = inject('documentClientHeight', ref(500));
|
||||
const projectName = import.meta.env.VITE_PROJECT_NAME;
|
||||
|
||||
const { getUploadFileUrl } = useUpload();
|
||||
|
||||
const userInfo = loginStore.userInfo;
|
||||
// TODO 切换菜单会触发该方法?
|
||||
// 用户头像
|
||||
const headerImg = computed(() => {
|
||||
if (userInfo && userInfo.headImageUrl) {
|
||||
let temp = getUploadFileUrl(userInfo.headImageUrl, {
|
||||
filename: userInfo.headImageUrl.filename,
|
||||
});
|
||||
return temp;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
// 消息数量
|
||||
const messageStore = useMessage();
|
||||
const messageCount = computed(() => {
|
||||
return messageStore.messageCount || {};
|
||||
});
|
||||
|
||||
// TODO 监听当前菜单变化,跳转到目标路由
|
||||
watch(
|
||||
() => layoutStore.currentMenu,
|
||||
(newVal, oldVal) => {
|
||||
//console.log('当前页发生变化', newVal, oldVal);
|
||||
if (newVal != oldVal) {
|
||||
jumpTo(newVal);
|
||||
}
|
||||
// else {
|
||||
// if (route.name != layoutStore.indexName) {
|
||||
// router.replace({
|
||||
// name: layoutStore.indexName,
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
},
|
||||
);
|
||||
|
||||
// 路由跳转
|
||||
function jumpTo(menuItem: MenuItem) {
|
||||
if (menuItem != null) {
|
||||
// 路由菜单
|
||||
if (
|
||||
menuItem.bindType === SysMenuBindType.ROUTER &&
|
||||
menuItem.formRouterName != null &&
|
||||
menuItem.formRouterName !== ''
|
||||
) {
|
||||
router.replace({
|
||||
name: menuItem.formRouterName,
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 在线表单菜单
|
||||
if (
|
||||
menuItem.bindType === SysMenuBindType.ONLINE_FORM &&
|
||||
menuItem.onlineFormId != null &&
|
||||
menuItem.onlineFormId !== ''
|
||||
) {
|
||||
router.replace({
|
||||
name: 'onlineForm',
|
||||
query: {
|
||||
formId: menuItem.onlineFormId,
|
||||
formType: SysOnlineFormType.QUERY,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 工单列表菜单
|
||||
if (
|
||||
menuItem.bindType === SysMenuBindType.WORK_ORDER &&
|
||||
menuItem.onlineFormId != null &&
|
||||
menuItem.onlineFormId !== '' &&
|
||||
menuItem.onlineFlowEntryId != null &&
|
||||
menuItem.onlineFlowEntryId !== ''
|
||||
) {
|
||||
router.replace({
|
||||
name: 'onlineForm',
|
||||
query: {
|
||||
formId: menuItem.onlineFormId,
|
||||
entryId: menuItem.onlineFlowEntryId,
|
||||
formType: SysOnlineFormType.WORK_ORDER,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 报表菜单
|
||||
if (
|
||||
menuItem.bindType === SysMenuBindType.REPORT &&
|
||||
menuItem.reportPageId != null &&
|
||||
menuItem.reportPageId !== ''
|
||||
) {
|
||||
router.replace({
|
||||
name: 'reportRender',
|
||||
query: {
|
||||
pageId: menuItem.reportPageId,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 外部链接
|
||||
if (
|
||||
menuItem.bindType === SysMenuBindType.THRID_URL &&
|
||||
menuItem.targetUrl != null &&
|
||||
menuItem.targetUrl !== ''
|
||||
) {
|
||||
let token = getToken();
|
||||
let targetUrl = menuItem.targetUrl;
|
||||
if (targetUrl.indexOf('?') === -1) {
|
||||
targetUrl = targetUrl + '?';
|
||||
}
|
||||
targetUrl = targetUrl + 'token=' + token;
|
||||
window.open(targetUrl);
|
||||
return;
|
||||
}
|
||||
}
|
||||
console.warn('没有匹配到目标路由');
|
||||
if (route.name !== layoutStore.indexName) {
|
||||
router.replace({
|
||||
name: layoutStore.indexName,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const getMainStyle = computed(() => {
|
||||
return [
|
||||
{
|
||||
height: documentClientHeight.value + 'px',
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
const getContextStyle = computed(() => {
|
||||
return [
|
||||
{
|
||||
height: documentClientHeight.value - (layoutStore.supportTags ? 108 : 60) + 'px',
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
const getRouterViewStyle = computed(() => {
|
||||
return [
|
||||
{
|
||||
'min-height': documentClientHeight.value - (layoutStore.supportTags ? 108 : 60) - 32 + 'px',
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
const handleMessage = (command: string) => {
|
||||
router.push({
|
||||
name: 'formMessage',
|
||||
query: {
|
||||
type: command,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleCommand = (command: string) => {
|
||||
switch (command) {
|
||||
case 'logout':
|
||||
ElMessageBox.confirm('是否退出登录?', '', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
LoginController.logout()
|
||||
.then(() => {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '退出成功',
|
||||
});
|
||||
setToken(null);
|
||||
router.replace('/login');
|
||||
})
|
||||
.catch(e => {
|
||||
console.log(e);
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: e,
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
type: 'info',
|
||||
message: '取消退出',
|
||||
});
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
ElMessage.warning(`click on item ${command}`);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
messageStore.startMessage();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
messageStore.stopMessage();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
//@import url('@/assets/style/element-variables.scss');
|
||||
//$--color-primary: #f70;
|
||||
|
||||
.header {
|
||||
z-index: 1;
|
||||
.header-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 4px;
|
||||
box-shadow: 0 2px 10px 1px rgb(65 64 133 / 10%);
|
||||
flex: 1;
|
||||
.title {
|
||||
overflow: hidden;
|
||||
width: 144px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
.logo {
|
||||
padding-left: 16px;
|
||||
img {
|
||||
width: 40px;
|
||||
}
|
||||
}
|
||||
.has-multi-column {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 80px;
|
||||
height: 60px;
|
||||
padding-left: 0;
|
||||
background-color: $color-primary;
|
||||
img {
|
||||
width: 36px;
|
||||
}
|
||||
}
|
||||
.title {
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
color: #434344;
|
||||
}
|
||||
}
|
||||
.sidebar {
|
||||
transition: 0.3s;
|
||||
}
|
||||
.el-menu {
|
||||
background-color: #2d3039 !important;
|
||||
}
|
||||
.layout-main {
|
||||
display: flex !important;
|
||||
.layout-content {
|
||||
width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
.tag-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 25px;
|
||||
background-color: white;
|
||||
.collapse-img {
|
||||
padding: 4px;
|
||||
margin-right: 12px;
|
||||
background-color: #f6f7f9;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.collapse {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
.message-popover {
|
||||
padding: 5px !important;
|
||||
}
|
||||
|
||||
.message-popover .el-table::before {
|
||||
height: 0 !important;
|
||||
}
|
||||
|
||||
.message-popover .el-table td {
|
||||
border: none;
|
||||
}
|
||||
.header-menu {
|
||||
align-items: center;
|
||||
padding-right: 45px;
|
||||
.header-img {
|
||||
width: 31px !important;
|
||||
height: 31px !important;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.line {
|
||||
display: inline-block;
|
||||
width: 1px;
|
||||
height: 24px;
|
||||
background-color: #e8e8e8;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
.user-dropdown {
|
||||
color: #333 !important;
|
||||
.el-icon-arrow-down {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
.code-generation-btn {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 82px;
|
||||
height: 32px;
|
||||
padding: 0 !important;
|
||||
margin-right: 20px !important;
|
||||
color: $color-primary !important;
|
||||
border-color: $color-primary !important;
|
||||
span {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
img {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.multi-column-header .header-main {
|
||||
padding-left: 16px;
|
||||
.title {
|
||||
width: 185px;
|
||||
}
|
||||
}
|
||||
.page-box {
|
||||
display: flex !important;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user