mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-02 15:30:25 +08:00
refactor: review
This commit is contained in:
@@ -2,4 +2,8 @@
|
||||
ENV = 'development'
|
||||
|
||||
# 本地环境接口地址
|
||||
VITE_API_URL = '/api'
|
||||
VITE_API_URL = '/api'
|
||||
|
||||
# 路由模式
|
||||
# Optional: hash | history
|
||||
VITE_ROUTER_MODE = hash
|
||||
@@ -2,4 +2,8 @@
|
||||
ENV = 'production'
|
||||
|
||||
# 线上环境接口地址
|
||||
VITE_API_URL = '/api'
|
||||
VITE_API_URL = '/api'
|
||||
|
||||
# 路由模式
|
||||
# Optional: hash | history
|
||||
VITE_ROUTER_MODE = hash
|
||||
@@ -11,8 +11,8 @@ import { useRoute } from 'vue-router';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import { getLocal } from '@/common/utils/storage';
|
||||
import LockScreen from '@/views/layout/lockScreen/index.vue';
|
||||
import Setings from '@/views/layout/navBars/breadcrumb/setings.vue';
|
||||
import LockScreen from '@/layout/lockScreen/index.vue';
|
||||
import Setings from '@/layout/navBars/breadcrumb/setings.vue';
|
||||
import Watermark from '@/common/utils/wartermark';
|
||||
import mittBus from '@/common/utils/mitt';
|
||||
import { getThemeConfig } from './common/utils/storage';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const TokenKey = 'token';
|
||||
const UserKey = 'user';
|
||||
const TagViewsKey = 'tagViews';
|
||||
|
||||
// 获取请求token
|
||||
export function getToken(): string {
|
||||
@@ -38,12 +39,24 @@ export function saveUseWatermark(useWatermark: boolean) {
|
||||
setLocal('useWatermark', useWatermark);
|
||||
}
|
||||
|
||||
// 清楚用户相关的用户信息
|
||||
// 清除用户相关的用户信息
|
||||
export function clearUser() {
|
||||
removeLocal(TokenKey);
|
||||
removeLocal(UserKey);
|
||||
}
|
||||
|
||||
export function getTagViews() {
|
||||
return getSession(TagViewsKey);
|
||||
}
|
||||
|
||||
export function setTagViews(tagViews: Array<object>) {
|
||||
setSession(TagViewsKey, tagViews);
|
||||
}
|
||||
|
||||
export function removeTagViews() {
|
||||
removeSession(TagViewsKey);
|
||||
}
|
||||
|
||||
// 1. localStorage
|
||||
// 设置永久缓存
|
||||
export function setLocal(key: string, val: any) {
|
||||
|
||||
13
mayfly_go_web/src/common/utils/url.ts
Normal file
13
mayfly_go_web/src/common/utils/url.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
const mode = import.meta.env.VITE_ROUTER_MODE;
|
||||
|
||||
/**
|
||||
* @description 获取不同路由模式所对应的 url
|
||||
* @returns {String}
|
||||
*/
|
||||
export function getNowUrl() {
|
||||
const url = {
|
||||
hash: location.hash.substring(1),
|
||||
history: location.pathname + location.search,
|
||||
};
|
||||
return url[mode];
|
||||
}
|
||||
@@ -21,8 +21,8 @@ import pinia from '@/store/index';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import { useRoutesList } from '@/store/routesList';
|
||||
import Logo from '@/views/layout/logo/index.vue';
|
||||
import Vertical from '@/views/layout/navMenu/vertical.vue';
|
||||
import Logo from '@/layout/logo/index.vue';
|
||||
import Vertical from '@/layout/navMenu/vertical.vue';
|
||||
import mittBus from '@/common/utils/mitt';
|
||||
|
||||
const { proxy } = getCurrentInstance() as any;
|
||||
@@ -38,8 +38,7 @@ const state: any = reactive({
|
||||
// 设置菜单展开/收起时的宽度
|
||||
const setCollapseWidth = computed(() => {
|
||||
let { layout, isCollapse, menuBar } = themeConfig.value;
|
||||
let asideBrColor =
|
||||
menuBar === '#FFFFFF' || menuBar === '#FFF' || menuBar === '#fff' || menuBar === '#ffffff' ? 'layout-el-aside-br-color' : '';
|
||||
let asideBrColor = menuBar === '#FFFFFF' || menuBar === '#FFF' || menuBar === '#fff' || menuBar === '#ffffff' ? 'layout-el-aside-br-color' : '';
|
||||
if (layout === 'columns') {
|
||||
// 分栏布局,菜单收起时宽度给 1px
|
||||
if (isCollapse) {
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import NavBarsIndex from '@/views/layout/navBars/index.vue';
|
||||
import NavBarsIndex from '@/layout/navBars/index.vue';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
export default {
|
||||
name: 'layoutHeader',
|
||||
@@ -1,16 +1,25 @@
|
||||
<template>
|
||||
<el-main class="layout-main">
|
||||
<el-scrollbar class="layout-scrollbar" ref="layoutScrollbarRef"
|
||||
<el-scrollbar
|
||||
class="layout-scrollbar"
|
||||
ref="layoutScrollbarRef"
|
||||
v-show="!state.currentRouteMeta.link && state.currentRouteMeta.linkType != 1"
|
||||
:style="{ minHeight: `calc(100vh - ${state.headerHeight}` }">
|
||||
:style="{ minHeight: `calc(100vh - ${state.headerHeight}` }"
|
||||
>
|
||||
<LayoutParentView />
|
||||
<Footer v-if="themeConfig.isFooter" />
|
||||
</el-scrollbar>
|
||||
<Link :style="{ height: `calc(100vh - ${state.headerHeight}` }" :meta="state.currentRouteMeta"
|
||||
v-if="state.currentRouteMeta.link && state.currentRouteMeta.linkType == 2" />
|
||||
<Iframes :style="{ height: `calc(100vh - ${state.headerHeight}` }" :meta="state.currentRouteMeta"
|
||||
<Link
|
||||
:style="{ height: `calc(100vh - ${state.headerHeight}` }"
|
||||
:meta="state.currentRouteMeta"
|
||||
v-if="state.currentRouteMeta.link && state.currentRouteMeta.linkType == 2"
|
||||
/>
|
||||
<Iframes
|
||||
:style="{ height: `calc(100vh - ${state.headerHeight}` }"
|
||||
:meta="state.currentRouteMeta"
|
||||
v-if="state.currentRouteMeta.link && state.currentRouteMeta.linkType == 1 && state.isShowLink"
|
||||
@getCurrentRouteMeta="onGetCurrentRouteMeta" />
|
||||
@getCurrentRouteMeta="onGetCurrentRouteMeta"
|
||||
/>
|
||||
</el-main>
|
||||
</template>
|
||||
|
||||
@@ -19,10 +28,10 @@ import { reactive, getCurrentInstance, watch, onBeforeMount } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import LayoutParentView from '@/views/layout/routerView/parent.vue';
|
||||
import Footer from '@/views/layout/footer/index.vue';
|
||||
import Link from '@/views/layout/routerView/link.vue';
|
||||
import Iframes from '@/views/layout/routerView/iframes.vue';
|
||||
import LayoutParentView from '@/layout/routerView/parent.vue';
|
||||
import Footer from '@/layout/footer/index.vue';
|
||||
import Link from '@/layout/routerView/link.vue';
|
||||
import Iframes from '@/layout/routerView/iframes.vue';
|
||||
|
||||
const { proxy } = getCurrentInstance() as any;
|
||||
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||
@@ -10,10 +10,10 @@ import { onBeforeMount, onUnmounted } from 'vue';
|
||||
import { getLocal, setLocal } from '@/common/utils/storage';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import Defaults from '@/views/layout/main/defaults.vue';
|
||||
import Classic from '@/views/layout/main/classic.vue';
|
||||
import Transverse from '@/views/layout/main/transverse.vue';
|
||||
import Columns from '@/views/layout/main/columns.vue';
|
||||
import Defaults from '@/layout/main/defaults.vue';
|
||||
import Classic from '@/layout/main/classic.vue';
|
||||
import Transverse from '@/layout/main/transverse.vue';
|
||||
import Columns from '@/layout/main/columns.vue';
|
||||
import mittBus from '@/common/utils/mitt';
|
||||
|
||||
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||
@@ -15,10 +15,10 @@
|
||||
<script lang="ts" setup name="layoutClassic">
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import Aside from '@/views/layout/component/aside.vue';
|
||||
import Header from '@/views/layout/component/header.vue';
|
||||
import Main from '@/views/layout/component/main.vue';
|
||||
import TagsView from '@/views/layout/navBars/tagsView/tagsView.vue';
|
||||
import Aside from '@/layout/component/aside.vue';
|
||||
import Header from '@/layout/component/header.vue';
|
||||
import Main from '@/layout/component/main.vue';
|
||||
import TagsView from '@/layout/navBars/tagsView/tagsView.vue';
|
||||
|
||||
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||
</script>
|
||||
@@ -17,10 +17,10 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import Aside from '@/views/layout/component/aside.vue';
|
||||
import Header from '@/views/layout/component/header.vue';
|
||||
import Main from '@/views/layout/component/main.vue';
|
||||
import ColumnsAside from '@/views/layout/component/columnsAside.vue';
|
||||
import Aside from '@/layout/component/aside.vue';
|
||||
import Header from '@/layout/component/header.vue';
|
||||
import Main from '@/layout/component/main.vue';
|
||||
import ColumnsAside from '@/layout/component/columnsAside.vue';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
export default {
|
||||
name: 'layoutColumns',
|
||||
@@ -15,9 +15,9 @@
|
||||
<script lang="ts">
|
||||
import { computed, getCurrentInstance, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import Aside from '@/views/layout/component/aside.vue';
|
||||
import Header from '@/views/layout/component/header.vue';
|
||||
import Main from '@/views/layout/component/main.vue';
|
||||
import Aside from '@/layout/component/aside.vue';
|
||||
import Header from '@/layout/component/header.vue';
|
||||
import Main from '@/layout/component/main.vue';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
export default {
|
||||
name: 'layoutDefaults',
|
||||
@@ -7,8 +7,8 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Header from '@/views/layout/component/header.vue';
|
||||
import Main from '@/views/layout/component/main.vue';
|
||||
import Header from '@/layout/component/header.vue';
|
||||
import Main from '@/layout/component/main.vue';
|
||||
export default {
|
||||
name: 'layoutTransverse',
|
||||
components: { Header, Main },
|
||||
@@ -14,10 +14,10 @@ import pinia from '@/store/index';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import { useRoutesList } from '@/store/routesList';
|
||||
import Breadcrumb from '@/views/layout/navBars/breadcrumb/breadcrumb.vue';
|
||||
import User from '@/views/layout/navBars/breadcrumb/user.vue';
|
||||
import Logo from '@/views/layout/logo/index.vue';
|
||||
import Horizontal from '@/views/layout/navMenu/horizontal.vue';
|
||||
import Breadcrumb from '@/layout/navBars/breadcrumb/breadcrumb.vue';
|
||||
import User from '@/layout/navBars/breadcrumb/user.vue';
|
||||
import Logo from '@/layout/logo/index.vue';
|
||||
import Horizontal from '@/layout/navMenu/horizontal.vue';
|
||||
import mittBus from '@/common/utils/mitt';
|
||||
|
||||
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||
@@ -84,8 +84,8 @@ import { storeToRefs } from 'pinia';
|
||||
import { useUserInfo } from '@/store/userInfo';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import { clearSession, removeLocal } from '@/common/utils/storage';
|
||||
import UserNews from '@/views/layout/navBars/breadcrumb/userNews.vue';
|
||||
import SearchMenu from '@/views/layout/navBars/breadcrumb/search.vue';
|
||||
import UserNews from '@/layout/navBars/breadcrumb/userNews.vue';
|
||||
import SearchMenu from '@/layout/navBars/breadcrumb/search.vue';
|
||||
import mittBus from '@/common/utils/mitt';
|
||||
import openApi from '@/common/openApi';
|
||||
import { saveThemeConfig, getThemeConfig } from '@/common/utils/storage';
|
||||
@@ -8,8 +8,8 @@
|
||||
<script lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import BreadcrumbIndex from '@/views/layout/navBars/breadcrumb/index.vue';
|
||||
import TagsView from '@/views/layout/navBars/tagsView/tagsView.vue';
|
||||
import BreadcrumbIndex from '@/layout/navBars/breadcrumb/index.vue';
|
||||
import TagsView from '@/layout/navBars/tagsView/tagsView.vue';
|
||||
export default {
|
||||
name: 'layoutNavBars',
|
||||
components: { BreadcrumbIndex, TagsView },
|
||||
138
mayfly_go_web/src/layout/navBars/tagsView/contextmenu.vue
Normal file
138
mayfly_go_web/src/layout/navBars/tagsView/contextmenu.vue
Normal file
@@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<transition name="el-zoom-in-center">
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="el-dropdown__popper el-popper is-light is-pure custom-contextmenu"
|
||||
role="tooltip"
|
||||
data-popper-placement="bottom"
|
||||
:style="`top: ${dropdowns.y + 5}px;left: ${dropdowns.x}px;`"
|
||||
:key="Math.random()"
|
||||
v-show="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>
|
||||
@@ -3,7 +3,7 @@
|
||||
<el-scrollbar ref="scrollbarRef" @wheel.prevent="onHandleScroll">
|
||||
<ul class="layout-navbars-tagsview-ul" :class="setTagsStyle" ref="tagsUlRef">
|
||||
<li
|
||||
v-for="(v, k) in state.tagsViewList"
|
||||
v-for="(v, k) in tagsViews"
|
||||
:key="k"
|
||||
class="layout-navbars-tagsview-ul-li"
|
||||
:data-name="v.name"
|
||||
@@ -17,8 +17,8 @@
|
||||
"
|
||||
>
|
||||
<SvgIcon name="iconfont icon-tag-view-active" class="layout-navbars-tagsview-ul-li-iconfont font14" v-if="isActive(v)" />
|
||||
<SvgIcon :name="v.meta.icon" class="layout-navbars-tagsview-ul-li-iconfont" v-if="!isActive(v) && themeConfig.isTagsviewIcon" />
|
||||
<span>{{ v.meta.title }}</span>
|
||||
<SvgIcon :name="v.icon" class="layout-navbars-tagsview-ul-li-iconfont" v-if="!isActive(v) && themeConfig.isTagsviewIcon" />
|
||||
<span>{{ v.title }}</span>
|
||||
<template v-if="isActive(v)">
|
||||
<SvgIcon
|
||||
name="RefreshRight"
|
||||
@@ -28,7 +28,7 @@
|
||||
<SvgIcon
|
||||
name="Close"
|
||||
class="font14 layout-navbars-tagsview-ul-li-icon layout-icon-active"
|
||||
v-if="!v.meta.isAffix"
|
||||
v-if="!v.isAffix"
|
||||
@click.stop="closeCurrentTagsView(themeConfig.isShareTagsView ? v.path : v.path)"
|
||||
/>
|
||||
</template>
|
||||
@@ -36,7 +36,7 @@
|
||||
<SvgIcon
|
||||
name="Close"
|
||||
class="font14 layout-navbars-tagsview-ul-li-icon layout-icon-three"
|
||||
v-if="!v.meta.isAffix"
|
||||
v-if="!v.isAffix"
|
||||
@click.stop="closeCurrentTagsView(themeConfig.isShareTagsView ? v.path : v.path)"
|
||||
/>
|
||||
</li>
|
||||
@@ -52,17 +52,24 @@ import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
|
||||
import screenfull from 'screenfull';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import { getSession, setSession, removeSession } from '@/common/utils/storage';
|
||||
import mittBus from '@/common/utils/mitt';
|
||||
import Sortable from 'sortablejs';
|
||||
import Contextmenu from '@/views/layout/navBars/tagsView/contextmenu.vue';
|
||||
import Contextmenu from '@/layout/navBars/tagsView/contextmenu.vue';
|
||||
import { getTagViews, setTagViews, removeTagViews } from '@/common/utils/storage';
|
||||
import { useTagsViews } from '@/store/tagsViews';
|
||||
import { useKeepALiveNames } from '@/store/keepAliveNames';
|
||||
|
||||
const { proxy } = getCurrentInstance() as any;
|
||||
const tagsRefs = ref([]) as any;
|
||||
const scrollbarRef = ref();
|
||||
const contextmenuRef = ref();
|
||||
const tagsUlRef = ref();
|
||||
|
||||
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||
const { tagsViews } = storeToRefs(useTagsViews());
|
||||
|
||||
const keepAliveNamesStores = useKeepALiveNames();
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
@@ -70,7 +77,6 @@ const state = reactive({
|
||||
routePath: route.fullPath,
|
||||
dropdown: { x: '', y: '' },
|
||||
tagsRefsIndex: 0,
|
||||
tagsViewList: [] as any,
|
||||
sortable: '' as any,
|
||||
});
|
||||
|
||||
@@ -81,119 +87,153 @@ const setTagsStyle = computed(() => {
|
||||
|
||||
// 存储 tagsViewList 到浏览器临时缓存中,页面刷新时,保留记录
|
||||
const addBrowserSetSession = (tagsViewList: Array<object>) => {
|
||||
setSession('tagsViewList', tagsViewList);
|
||||
setTagViews(tagsViewList);
|
||||
};
|
||||
|
||||
// 获取 vuex 中的 tagsViewRoutes 列表
|
||||
// 获取 tagsViewRoutes 列表
|
||||
const getTagsViewRoutes = () => {
|
||||
state.routePath = route.fullPath;
|
||||
state.tagsViewList = [];
|
||||
if (!themeConfig.value.isCacheTagsView) removeSession('tagsViewList');
|
||||
tagsViews.value = [];
|
||||
if (!themeConfig.value.isCacheTagsView) {
|
||||
removeTagViews();
|
||||
}
|
||||
initTagsView();
|
||||
};
|
||||
// vuex 中获取路由信息:如果是设置了固定的(isAffix),进行初始化显示
|
||||
// 获取路由信息:如果是设置了固定的(isAffix),进行初始化显示
|
||||
const initTagsView = () => {
|
||||
if (getSession('tagsViewList') && themeConfig.value.isCacheTagsView) {
|
||||
state.tagsViewList = getSession('tagsViewList');
|
||||
const tagViews = getTagViews();
|
||||
if (tagViews && themeConfig.value.isCacheTagsView) {
|
||||
tagsViews.value = tagViews;
|
||||
} else {
|
||||
state.tagsViewList?.map((v: any) => {
|
||||
if (v.meta.isAffix && !v.meta.isHide) state.tagsViewList.push({ ...v });
|
||||
tagsViews.value?.map((v: any) => {
|
||||
if (v.isAffix && !v.isHide) {
|
||||
tagsViews.value.push({ ...v });
|
||||
keepAliveNamesStores.setCacheKeepAlive(v);
|
||||
}
|
||||
});
|
||||
addTagsView(route.fullPath);
|
||||
}
|
||||
// 初始化当前元素(li)的下标
|
||||
getTagsRefsIndex(route.fullPath);
|
||||
setTagsRefsIndex(route.fullPath);
|
||||
// 添加初始化横向滚动条移动到对应位置
|
||||
tagsViewmoveToCurrentTag();
|
||||
};
|
||||
|
||||
// 1、添加 tagsView:未设置隐藏(isHide)也添加到在 tagsView 中
|
||||
// path为fullPath
|
||||
const addTagsView = (path: string, to: any = null) => {
|
||||
if (!to) {
|
||||
to = route;
|
||||
}
|
||||
|
||||
path = decodeURI(path);
|
||||
for (let tv of state.tagsViewList) {
|
||||
if (tv.fullPath === path) {
|
||||
return false;
|
||||
const addTagsView = (path: string, to: any = null, tagViewIndex: number = -1) => {
|
||||
nextTick(async () => {
|
||||
if (!to) {
|
||||
to = route;
|
||||
}
|
||||
}
|
||||
|
||||
const tagView = { ...to };
|
||||
// 防止Converting circular structure to JSON错误
|
||||
tagView.matched = null;
|
||||
tagView.redirectedFrom = null;
|
||||
state.tagsViewList.push(tagView);
|
||||
addBrowserSetSession(state.tagsViewList);
|
||||
for (let tv of tagsViews.value) {
|
||||
if (tv.path === path) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const tagView = {
|
||||
path: path,
|
||||
name: to.name,
|
||||
query: to.query,
|
||||
title: to.meta.title,
|
||||
icon: to.meta.icon,
|
||||
isAffix: to.meta.isAffix,
|
||||
isKeepAlive: to.meta.isKeepAlive,
|
||||
};
|
||||
|
||||
if (tagViewIndex != -1) {
|
||||
tagsViews.value.splice(tagViewIndex + 1, 0, tagView);
|
||||
} else {
|
||||
tagsViews.value.push(tagView);
|
||||
}
|
||||
await keepAliveNamesStores.addCachedView(tagView);
|
||||
addBrowserSetSession(tagsViews.value);
|
||||
});
|
||||
};
|
||||
|
||||
// 2、刷新当前 tagsView:
|
||||
// path为fullPath
|
||||
const refreshCurrentTagsView = (path: string) => {
|
||||
const refreshCurrentTagsView = async (path: string) => {
|
||||
const item = getTagsView(path);
|
||||
await keepAliveNamesStores.delCachedView(item);
|
||||
keepAliveNamesStores.addCachedView(item);
|
||||
mittBus.emit('onTagsViewRefreshRouterView', path);
|
||||
};
|
||||
|
||||
const getTagsView = (path: string) => {
|
||||
return tagsViews.value.find((v: any) => v.path === path);
|
||||
};
|
||||
|
||||
// 3、关闭当前 tagsView:如果是设置了固定的(isAffix),不可以关闭
|
||||
// path为fullPath
|
||||
const closeCurrentTagsView = (path: string) => {
|
||||
state.tagsViewList.map((v: any, k: number, arr: any) => {
|
||||
if (!v.meta.isAffix) {
|
||||
if (v.fullPath === path) {
|
||||
state.tagsViewList.splice(k, 1);
|
||||
tagsViews.value.map((v: TagsView, k: number, arr: any) => {
|
||||
if (!v.isAffix) {
|
||||
if (v.path === path) {
|
||||
keepAliveNamesStores.delCachedView(v);
|
||||
tagsViews.value.splice(k, 1);
|
||||
setTimeout(() => {
|
||||
if (state.routePath !== path) {
|
||||
return;
|
||||
}
|
||||
let next;
|
||||
let next: TagsView;
|
||||
// 最后一个且高亮时
|
||||
if (state.tagsViewList.length === k) {
|
||||
if (tagsViews.value.length === k) {
|
||||
next = k !== arr.length ? arr[k] : arr[arr.length - 1];
|
||||
} else {
|
||||
next = arr[k];
|
||||
}
|
||||
|
||||
if (next.meta.isDynamic) {
|
||||
router.push({ name: next.name, params: next.params });
|
||||
} else {
|
||||
if (next) {
|
||||
router.push({ path: next.path, query: next.query });
|
||||
} else {
|
||||
router.push({ path: '/' });
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
addBrowserSetSession(state.tagsViewList);
|
||||
addBrowserSetSession(tagsViews.value);
|
||||
};
|
||||
|
||||
// 4、关闭其它 tagsView:如果是设置了固定的(isAffix),不进行关闭
|
||||
const closeOtherTagsView = (path: string) => {
|
||||
const oldTagViews = state.tagsViewList;
|
||||
state.tagsViewList = [];
|
||||
oldTagViews.map((v: any) => {
|
||||
if (v.meta.isAffix && !v.meta.isHide) state.tagsViewList.push({ ...v });
|
||||
const oldTagViews = tagsViews.value;
|
||||
tagsViews.value = [];
|
||||
oldTagViews.map((v: TagsView) => {
|
||||
if (v.isAffix && !v.isHide) {
|
||||
keepAliveNamesStores.delOthersCachedViews(v);
|
||||
tagsViews.value.push({ ...v });
|
||||
}
|
||||
});
|
||||
addTagsView(path);
|
||||
};
|
||||
|
||||
// 5、关闭全部 tagsView:如果是设置了固定的(isAffix),不进行关闭
|
||||
const closeAllTagsView = (path: string) => {
|
||||
const oldTagViews = state.tagsViewList;
|
||||
state.tagsViewList = [];
|
||||
keepAliveNamesStores.delAllCachedViews();
|
||||
const oldTagViews = tagsViews.value;
|
||||
tagsViews.value = [];
|
||||
oldTagViews.map((v: any) => {
|
||||
if (v.meta.isAffix && !v.meta.isHide) {
|
||||
state.tagsViewList.push({ ...v });
|
||||
if (state.tagsViewList.some((v: any) => v.path === path)) router.push({ path, query: route.query });
|
||||
else router.push({ path: v.path, query: route.query });
|
||||
if (v.isAffix && !v.isHide) {
|
||||
tagsViews.value.push({ ...v });
|
||||
if (tagsViews.value.some((v: any) => v.path === path)) {
|
||||
router.push({ path, query: route.query });
|
||||
}
|
||||
}
|
||||
});
|
||||
addBrowserSetSession(state.tagsViewList);
|
||||
if (tagsViews.value) {
|
||||
router.push({ path: '/' });
|
||||
}
|
||||
addBrowserSetSession(tagsViews.value);
|
||||
};
|
||||
// 6、开启当前页面全屏
|
||||
const openCurrenFullscreen = (path: string) => {
|
||||
const item = state.tagsViewList.find((v: any) => v.fullPath === path);
|
||||
const item = tagsViews.value.find((v: any) => v.path === path);
|
||||
nextTick(() => {
|
||||
router.push({ path, query: item.query });
|
||||
router.push({ path, query: item?.query });
|
||||
const element = document.querySelector('.layout-main');
|
||||
const screenfulls: any = screenfull;
|
||||
screenfulls.request(element);
|
||||
@@ -203,17 +243,17 @@ const openCurrenFullscreen = (path: string) => {
|
||||
const onCurrentContextmenuClick = (data: any) => {
|
||||
// path为fullPath
|
||||
let { id, path } = data;
|
||||
let currentTag = state.tagsViewList.find((v: any) => v.fullPath === path);
|
||||
let currentTag = tagsViews.value.find((v: any) => v.path === path);
|
||||
switch (id) {
|
||||
case 0:
|
||||
refreshCurrentTagsView(path);
|
||||
router.push({ path, query: currentTag.query });
|
||||
router.push({ path, query: currentTag?.query });
|
||||
break;
|
||||
case 1:
|
||||
closeCurrentTagsView(path);
|
||||
break;
|
||||
case 2:
|
||||
router.push({ path, query: currentTag.query });
|
||||
router.push({ path, query: currentTag?.query });
|
||||
closeOtherTagsView(path);
|
||||
break;
|
||||
case 3:
|
||||
@@ -225,8 +265,8 @@ const onCurrentContextmenuClick = (data: any) => {
|
||||
}
|
||||
};
|
||||
// 判断页面高亮
|
||||
const isActive = (route: any) => {
|
||||
return route.fullPath === state.routePath;
|
||||
const isActive = (tagView: TagsView) => {
|
||||
return tagView.path === state.routePath;
|
||||
};
|
||||
// 右键点击时:传 x,y 坐标值到子组件中(props)
|
||||
const onContextmenu = (v: any, e: any) => {
|
||||
@@ -237,7 +277,7 @@ const onContextmenu = (v: any, e: any) => {
|
||||
};
|
||||
// 当前的 tagsView 项点击时
|
||||
const onTagsClick = (v: any, k: number) => {
|
||||
state.routePath = decodeURI(v.fullPath);
|
||||
state.routePath = decodeURI(v.path);
|
||||
state.tagsRefsIndex = k;
|
||||
router.push(v);
|
||||
};
|
||||
@@ -302,9 +342,9 @@ const tagsViewmoveToCurrentTag = () => {
|
||||
});
|
||||
};
|
||||
// 获取 tagsView 的下标:用于处理 tagsView 点击时的横向滚动
|
||||
const getTagsRefsIndex = (path: string) => {
|
||||
if (state.tagsViewList.length > 0) {
|
||||
state.tagsRefsIndex = state.tagsViewList.findIndex((item: any) => item.fullPath === path);
|
||||
const setTagsRefsIndex = (path: string) => {
|
||||
if (tagsViews.value.length > 0) {
|
||||
state.tagsRefsIndex = tagsViews.value.findIndex((item: any) => item.path === path);
|
||||
}
|
||||
};
|
||||
// 设置 tagsView 可以进行拖拽
|
||||
@@ -319,7 +359,7 @@ const initSortable = () => {
|
||||
onEnd: () => {
|
||||
const sortEndList: any = [];
|
||||
state.sortable.toArray().map((val: any) => {
|
||||
state.tagsViewList.map((v: any) => {
|
||||
tagsViews.value.map((v: any) => {
|
||||
if (v.name === val) sortEndList.push({ ...v });
|
||||
});
|
||||
});
|
||||
@@ -329,18 +369,6 @@ const initSortable = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 监听路由的变化,动态赋值给 tagsView
|
||||
// watch(
|
||||
// pinia.state,
|
||||
// (val) => {
|
||||
// if (val.tagsViewRoutes.tagsViewRoutes.length === state.tagsViewRoutesList.length) return false;
|
||||
// getTagsViewRoutes();
|
||||
// },
|
||||
// {
|
||||
// deep: true,
|
||||
// }
|
||||
// );
|
||||
|
||||
// 页面加载前
|
||||
onBeforeMount(() => {
|
||||
// 监听非本页面调用 0 刷新当前,1 关闭当前,2 关闭其它,3 关闭全部 4 当前页全屏
|
||||
@@ -371,9 +399,10 @@ onMounted(() => {
|
||||
});
|
||||
// 路由更新时
|
||||
onBeforeRouteUpdate((to) => {
|
||||
state.routePath = decodeURI(to.fullPath);
|
||||
addTagsView(to.fullPath, to);
|
||||
getTagsRefsIndex(to.fullPath);
|
||||
const path = decodeURI(to.fullPath);
|
||||
state.routePath = path;
|
||||
addTagsView(path, to, state.tagsRefsIndex);
|
||||
setTagsRefsIndex(path);
|
||||
tagsViewmoveToCurrentTag();
|
||||
});
|
||||
</script>
|
||||
@@ -1,24 +1,23 @@
|
||||
<template>
|
||||
<div class="el-menu-horizontal-warp">
|
||||
<el-scrollbar @wheel.prevent="onElMenuHorizontalScroll" ref="elMenuHorizontalScrollRef">
|
||||
<el-menu router :default-active="state.defaultActive" background-color="transparent" mode="horizontal"
|
||||
@select="onHorizontalSelect">
|
||||
<el-menu router :default-active="state.defaultActive" background-color="transparent" mode="horizontal" @select="onHorizontalSelect">
|
||||
<template v-for="val in menuLists">
|
||||
<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
|
||||
<template #title>
|
||||
<SvgIcon :name="val.meta.icon"/>
|
||||
<SvgIcon :name="val.meta.icon" />
|
||||
<span>{{ val.meta.title }}</span>
|
||||
</template>
|
||||
<SubItem :chil="val.children" />
|
||||
</el-sub-menu>
|
||||
<el-menu-item :index="val.path" :key="val?.path" v-else>
|
||||
<template #title v-if="!val.meta.link || (val.meta.link && val.meta.linkType == 1)">
|
||||
<SvgIcon :name="val.meta.icon"/>
|
||||
<SvgIcon :name="val.meta.icon" />
|
||||
{{ val.meta.title }}
|
||||
</template>
|
||||
<template #title v-else>
|
||||
<a :href="val.meta.link" target="_blank">
|
||||
<SvgIcon :name="val.meta.icon"/>
|
||||
<SvgIcon :name="val.meta.icon" />
|
||||
{{ val.meta.title }}
|
||||
</a>
|
||||
</template>
|
||||
@@ -32,7 +31,7 @@
|
||||
<script lang="ts" setup name="navMenuHorizontal">
|
||||
import { reactive, computed, getCurrentInstance, onMounted, nextTick } from 'vue';
|
||||
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
|
||||
import SubItem from '@/views/layout/navMenu/subItem.vue';
|
||||
import SubItem from '@/layout/navMenu/subItem.vue';
|
||||
import { useRoutesList } from '@/store/routesList';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import mittBus from '@/common/utils/mitt';
|
||||
@@ -1,21 +1,28 @@
|
||||
<template>
|
||||
<el-menu router :default-active="state.defaultActive" background-color="transparent" :collapse="setIsCollapse"
|
||||
:unique-opened="themeConfig.isUniqueOpened" :collapse-transition="false">
|
||||
<el-menu
|
||||
router
|
||||
:default-active="state.defaultActive"
|
||||
background-color="transparent"
|
||||
:collapse="setIsCollapse"
|
||||
:unique-opened="themeConfig.isUniqueOpened"
|
||||
:collapse-transition="false"
|
||||
>
|
||||
<template v-for="val in menuLists">
|
||||
<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
|
||||
<template #title>
|
||||
<SvgIcon :name="val.meta.icon"/>
|
||||
<SvgIcon :name="val.meta.icon" />
|
||||
<span>{{ val.meta.title }}</span>
|
||||
</template>
|
||||
<SubItem :chil="val.children" />
|
||||
</el-sub-menu>
|
||||
<el-menu-item :index="val.path" :key="val?.path" v-else>
|
||||
<SvgIcon :name="val.meta.icon"/>
|
||||
<SvgIcon :name="val.meta.icon" />
|
||||
<template #title v-if="!val.meta.link || (val.meta.link && val.meta.linkType == 1)">
|
||||
<span>{{ val.meta.title }}</span>
|
||||
</template>
|
||||
<template #title v-else>
|
||||
<a :href="val.meta.link" target="_blank">{{ val.meta.title }}</a></template>
|
||||
<a :href="val.meta.link" target="_blank">{{ val.meta.title }}</a></template
|
||||
>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</el-menu>
|
||||
@@ -26,7 +33,7 @@ import { reactive, computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
|
||||
import SubItem from '@/views/layout/navMenu/subItem.vue';
|
||||
import SubItem from '@/layout/navMenu/subItem.vue';
|
||||
import mittBus from '@/common/utils/mitt';
|
||||
|
||||
// 定义父组件传过来的值
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="h100">
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition :name="setTransitionName" mode="out-in">
|
||||
<keep-alive :include="state.keepAliveNameList">
|
||||
<keep-alive :include="getKeepAliveNames">
|
||||
<component :is="Component" :key="state.refreshRouterViewKey" class="w100" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
@@ -11,38 +11,62 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="layoutParentView">
|
||||
import { computed, reactive, onBeforeMount, onUnmounted, nextTick } from 'vue';
|
||||
import { computed, watch, reactive, onBeforeMount, onMounted, onUnmounted, nextTick } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import { useKeepALiveNames } from '@/store/keepAliveNames';
|
||||
import mittBus from '@/common/utils/mitt';
|
||||
import { getTagViews } from '@/common/utils/storage';
|
||||
|
||||
const route = useRoute();
|
||||
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||
const { keepAliveNames } = storeToRefs(useKeepALiveNames());
|
||||
const { keepAliveNames, cachedViews } = storeToRefs(useKeepALiveNames());
|
||||
|
||||
const state: any = reactive({
|
||||
refreshRouterViewKey: null,
|
||||
keepAliveNameList: [],
|
||||
keepAliveNameNewList: [],
|
||||
});
|
||||
// 监听路由的变化,动态赋值给refreshRouterViewKey
|
||||
// onBeforeRouteUpdate((to: any) => {
|
||||
// state.refreshRouterViewKey = decodeURI(to.fullPath);
|
||||
// });
|
||||
|
||||
// 获取组件缓存列表(name值)
|
||||
const getKeepAliveNames = computed(() => {
|
||||
return themeConfig.value.isTagsview ? cachedViews.value : state.keepAliveNameList;
|
||||
});
|
||||
|
||||
// 页面加载前,处理缓存,页面刷新时路由缓存处理
|
||||
onBeforeMount(() => {
|
||||
state.keepAliveNameList = keepAliveNames.value;
|
||||
mittBus.on('onTagsViewRefreshRouterView', (path: string) => {
|
||||
if (decodeURI(route.fullPath) !== path) return false;
|
||||
state.keepAliveNameList = keepAliveNames.value.filter((name: string) => route.name !== name);
|
||||
state.refreshRouterViewKey = route.path;
|
||||
state.refreshRouterViewKey = '';
|
||||
nextTick(() => {
|
||||
state.refreshRouterViewKey = null;
|
||||
state.refreshRouterViewKey = path;
|
||||
state.keepAliveNameList = keepAliveNames.value;
|
||||
});
|
||||
});
|
||||
});
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
if (themeConfig.value.isCacheTagsView) {
|
||||
let tagsViewArr: any = getTagViews() || [];
|
||||
cachedViews.value = tagsViewArr.filter((item: any) => item?.isKeepAlive).map((item: any) => item.name as string);
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
// 监听路由变化,防止 tagsView 多标签时,切换动画消失
|
||||
watch(
|
||||
() => route.fullPath,
|
||||
() => {
|
||||
state.refreshRouterViewKey = decodeURI(route.fullPath);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
// 设置主界面切换动画
|
||||
const setTransitionName = computed(() => {
|
||||
return themeConfig.value.animation;
|
||||
@@ -18,7 +18,7 @@ import { useKeepALiveNames } from '@/store/keepAliveNames';
|
||||
* @method import.meta.glob
|
||||
* @link 参考:https://cn.vitejs.dev/guide/features.html#json
|
||||
*/
|
||||
const viewsModules: any = import.meta.glob(['../views/**/*.{vue,tsx}', '!../views/layout/**/*.{vue,tsx}']);
|
||||
const viewsModules: any = import.meta.glob(['../views/**/*.{vue,tsx}']);
|
||||
const dynamicViewsModules: Record<string, Function> = Object.assign({}, { ...viewsModules });
|
||||
|
||||
// 添加静态路由
|
||||
@@ -37,12 +37,10 @@ export function initAllFun() {
|
||||
useUserInfo().setUserInfo({});
|
||||
router.addRoute(pathMatch); // 添加404界面
|
||||
resetRoute(); // 删除/重置路由
|
||||
// 添加动态路由
|
||||
setFilterRouteEnd().forEach((route: any) => {
|
||||
router.addRoute(route as unknown as RouteRecordRaw);
|
||||
});
|
||||
router.addRoute(dynamicRoutes[0]);
|
||||
|
||||
// 过滤权限菜单
|
||||
useRoutesList().setRoutesList(setFilterMenuFun(dynamicRoutes[0].children, useUserInfo().userInfo.menus));
|
||||
useRoutesList().setRoutesList(dynamicRoutes[0].children);
|
||||
}
|
||||
|
||||
// 后端控制路由:执行路由数据初始化
|
||||
@@ -55,14 +53,25 @@ export async function initBackEndControlRoutesFun() {
|
||||
useUserInfo().setUserInfo({});
|
||||
// 获取路由
|
||||
let menuRoute = await getBackEndControlRoutes();
|
||||
dynamicRoutes[0].children = backEndRouterConverter(menuRoute); // 处理路由(component)
|
||||
|
||||
const cacheList: Array<string> = [];
|
||||
// 处理路由(component)
|
||||
dynamicRoutes[0].children = backEndRouterConverter(menuRoute, (router: any) => {
|
||||
// 可能为false时不存在isKeepAlive属性
|
||||
if (!router.meta.isKeepAlive) {
|
||||
router.meta.isKeepAlive = false;
|
||||
}
|
||||
if (router.meta.isKeepAlive) {
|
||||
cacheList.push(router.name);
|
||||
}
|
||||
});
|
||||
useKeepALiveNames().setCacheKeepAlive(cacheList);
|
||||
|
||||
// 添加404界面
|
||||
router.addRoute(pathMatch);
|
||||
resetRoute(); // 删除/重置路由
|
||||
// 添加动态路由
|
||||
formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes)).forEach((route: any) => {
|
||||
router.addRoute(route as unknown as RouteRecordRaw);
|
||||
});
|
||||
router.addRoute(dynamicRoutes[0] as unknown as RouteRecordRaw);
|
||||
|
||||
useRoutesList().setRoutesList(dynamicRoutes[0].children);
|
||||
}
|
||||
|
||||
@@ -79,8 +88,10 @@ export async function getBackEndControlRoutes() {
|
||||
}
|
||||
}
|
||||
|
||||
type RouterConvCallbackFunc = (router: any) => void;
|
||||
|
||||
// 后端控制路由,后端返回路由 转换为vue route
|
||||
export function backEndRouterConverter(routes: any, parentPath: string = '/') {
|
||||
export function backEndRouterConverter(routes: any, callbackFunc: RouterConvCallbackFunc = null as any, parentPath: string = '/') {
|
||||
if (!routes) return;
|
||||
return routes.map((item: any) => {
|
||||
if (!item.meta) {
|
||||
@@ -115,7 +126,9 @@ export function backEndRouterConverter(routes: any, parentPath: string = '/') {
|
||||
item.redirect = item.meta.redirect;
|
||||
delete item.meta['redirect'];
|
||||
}
|
||||
item.children && backEndRouterConverter(item.children, item.path);
|
||||
// 存在回调,则执行回调
|
||||
callbackFunc && callbackFunc(item);
|
||||
item.children && backEndRouterConverter(item.children, callbackFunc, item.path);
|
||||
return item;
|
||||
});
|
||||
}
|
||||
@@ -141,86 +154,6 @@ export function dynamicImport(dynamicViewsModules: Record<string, Function>, com
|
||||
}
|
||||
}
|
||||
|
||||
// 多级嵌套数组处理成一维数组
|
||||
export function formatFlatteningRoutes(arr: any) {
|
||||
if (arr.length <= 0) return false;
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (arr[i].children) {
|
||||
arr = arr.slice(0, i + 1).concat(arr[i].children, arr.slice(i + 1));
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
// 多级嵌套数组处理后的一维数组,再处理成 `定义动态路由` 的格式
|
||||
// 只保留二级:也就是二级以上全部处理成只有二级,keep-alive 支持二级缓存
|
||||
// isKeepAlive 处理 `name` 值,进行缓存。顶级关闭,全部不缓存
|
||||
export function formatTwoStageRoutes(arr: any) {
|
||||
if (arr.length <= 0) return false;
|
||||
const newArr: any = [];
|
||||
const cacheList: Array<string> = [];
|
||||
arr.forEach((v: any) => {
|
||||
if (v.path === '/') {
|
||||
newArr.push({ component: v.component, name: v.name, path: v.path, redirect: v.redirect, meta: v.meta, children: [] });
|
||||
} else {
|
||||
newArr[0].children.push({ ...v });
|
||||
if (newArr[0].meta.isKeepAlive && v.meta.isKeepAlive) {
|
||||
cacheList.push(v.name);
|
||||
}
|
||||
}
|
||||
});
|
||||
useKeepALiveNames().setCacheKeepAlive(cacheList);
|
||||
return newArr;
|
||||
}
|
||||
|
||||
// 判断路由code 是否包含当前登录用户menus字段中,menus为字符串code数组
|
||||
export function hasAnth(menus: any, route: any) {
|
||||
if (route.meta && route.meta.code) {
|
||||
return menus.includes(route.meta.code);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 递归过滤有权限的路由
|
||||
export function setFilterMenuFun(routes: any, menus: any) {
|
||||
const menu: any = [];
|
||||
routes.forEach((route: any) => {
|
||||
const item = { ...route };
|
||||
if (hasAnth(menus, item)) {
|
||||
if (item.children) {
|
||||
item.children = setFilterMenuFun(item.children, menus);
|
||||
}
|
||||
menu.push(item);
|
||||
}
|
||||
});
|
||||
return menu;
|
||||
}
|
||||
|
||||
// 获取当前用户的权限去比对路由表,用于动态路由的添加
|
||||
export function setFilterRoute(chil: any) {
|
||||
let filterRoute: any = [];
|
||||
chil.forEach((route: any) => {
|
||||
// 如果路由需要拥有指定code才可访问,则校验该用户菜单是否存在该code
|
||||
if (route.meta.code) {
|
||||
useUserInfo().userInfo.menus.forEach((m: any) => {
|
||||
if (route.meta.code == m) {
|
||||
filterRoute.push({ ...route });
|
||||
}
|
||||
});
|
||||
} else {
|
||||
filterRoute.push({ ...route });
|
||||
}
|
||||
});
|
||||
return filterRoute;
|
||||
}
|
||||
|
||||
// 比对后的路由表,进行重新赋值
|
||||
export function setFilterRouteEnd() {
|
||||
let filterRouteEnd: any = formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes));
|
||||
filterRouteEnd[0].children = setFilterRoute(filterRouteEnd[0].children);
|
||||
return filterRouteEnd;
|
||||
}
|
||||
|
||||
// 删除/重置路由
|
||||
export function resetRoute() {
|
||||
useRoutesList().routesList.forEach((route: any) => {
|
||||
@@ -255,7 +188,7 @@ router.beforeEach(async (to, from, next) => {
|
||||
if (to.meta.title) NProgress.start();
|
||||
|
||||
// 如果有标题参数,则再原标题后加上参数来区别
|
||||
if (to.meta.titleRename) {
|
||||
if (to.meta.titleRename && to.meta.title) {
|
||||
to.meta.title = templateResolve(to.meta.title as string, to.query);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
import Layout from '@/views/layout/index.vue';
|
||||
import Layout from '@/layout/index.vue';
|
||||
|
||||
// 定义动态路由
|
||||
export const dynamicRoutes = [
|
||||
|
||||
@@ -18,14 +18,16 @@ export const useKeepALiveNames = defineStore('keepALiveNames', {
|
||||
this.keepAliveNames = data;
|
||||
},
|
||||
async addCachedView(view: any) {
|
||||
if (view.meta.isKeepAlive) this.cachedViews?.push(view.name);
|
||||
if (view.isKeepAlive) {
|
||||
this.cachedViews?.push(view.name);
|
||||
}
|
||||
},
|
||||
async delCachedView(view: any) {
|
||||
const index = this.cachedViews.indexOf(view.name);
|
||||
index > -1 && this.cachedViews.splice(index, 1);
|
||||
},
|
||||
async delOthersCachedViews(view: any) {
|
||||
if (view.meta.isKeepAlive) this.cachedViews = [view.name];
|
||||
if (view.isKeepAlive) this.cachedViews = [view.name];
|
||||
else this.cachedViews = [];
|
||||
},
|
||||
async delAllCachedViews() {
|
||||
|
||||
25
mayfly_go_web/src/store/tagsViews.ts
Normal file
25
mayfly_go_web/src/store/tagsViews.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { getNowUrl } from '@/common/utils/url';
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
/**
|
||||
* tags view
|
||||
*/
|
||||
export const useTagsViews = defineStore('tagsViews', {
|
||||
state: (): TagsViewsState => ({
|
||||
tagsViews: [],
|
||||
}),
|
||||
actions: {
|
||||
setTagsViews(data: Array<TagsView>) {
|
||||
this.tagsViews = data;
|
||||
},
|
||||
// 设置当前页面的tags view title
|
||||
setNowTitle(title: string) {
|
||||
this.tagsViews.forEach((item) => {
|
||||
// console.log(getNowUrl(), item.path);
|
||||
if (item.path == getNowUrl()) {
|
||||
item.title = title;
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
36
mayfly_go_web/src/types/pinia.d.ts
vendored
36
mayfly_go_web/src/types/pinia.d.ts
vendored
@@ -60,10 +60,40 @@ declare interface ThemeConfigState {
|
||||
};
|
||||
}
|
||||
|
||||
declare interface TagsView {
|
||||
/**
|
||||
* 路径
|
||||
*/
|
||||
path: string;
|
||||
|
||||
/**
|
||||
* 标题
|
||||
*/
|
||||
title: string;
|
||||
|
||||
/**
|
||||
* router name
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* router query
|
||||
*/
|
||||
query: any;
|
||||
|
||||
/**
|
||||
* 图标
|
||||
*/
|
||||
icon: string;
|
||||
|
||||
isAffix: boolean;
|
||||
isKeepAlive: boolean;
|
||||
isHide?: boolean;
|
||||
}
|
||||
|
||||
// TagsView 路由列表
|
||||
declare interface TagsViewRoutesState<T = any> {
|
||||
tagsViewRoutes: T[];
|
||||
isTagsViewCurrenFull: Boolean;
|
||||
declare interface TagsViewsState<T = any> {
|
||||
tagsViews: TagsView[];
|
||||
}
|
||||
|
||||
// 路由列表
|
||||
|
||||
@@ -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.fullPath });
|
||||
};
|
||||
// 打开右键菜单:判断是否固定,固定则不显示关闭按钮
|
||||
const openContextmenu = (item: any) => {
|
||||
state.item = item;
|
||||
item.meta?.isAffix ? (state.dropdownList[1].affix = true) : (state.dropdownList[1].affix = false);
|
||||
closeContextmenu();
|
||||
setTimeout(() => {
|
||||
state.isShow = true;
|
||||
}, 10);
|
||||
};
|
||||
// 关闭右键菜单
|
||||
const closeContextmenu = () => {
|
||||
state.isShow = false;
|
||||
};
|
||||
// 监听页面监听进行右键菜单的关闭
|
||||
onMounted(() => {
|
||||
document.body.addEventListener('click', closeContextmenu);
|
||||
});
|
||||
// 页面卸载时,移除右键菜单监听事件
|
||||
onUnmounted(() => {
|
||||
document.body.removeEventListener('click', closeContextmenu);
|
||||
});
|
||||
// 监听下拉菜单位置
|
||||
watch(
|
||||
() => props.dropdown,
|
||||
({ x }) => {
|
||||
if (x + 117 > document.documentElement.clientWidth) state.arrowLeft = 117 - (document.documentElement.clientWidth - x);
|
||||
else state.arrowLeft = 10;
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openContextmenu,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.custom-contextmenu {
|
||||
transform-origin: center top;
|
||||
z-index: 2190;
|
||||
position: fixed;
|
||||
.el-dropdown-menu__item {
|
||||
font-size: 12px !important;
|
||||
white-space: nowrap;
|
||||
i {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user