feat: 面包屑组件 & 顶部菜单调整
This commit is contained in:
parent
b73f2b3270
commit
1124cca16d
|
@ -0,0 +1,59 @@
|
||||||
|
<template>
|
||||||
|
<a-breadcrumb v-if="showBreadcrumb" class="z-10 mb-[-8px] mt-[8px]">
|
||||||
|
<a-breadcrumb-item v-for="crumb of appStore.breadcrumbList" :key="crumb.name" @click="jumpTo(crumb.name)">
|
||||||
|
{{ t(crumb.locale) }}
|
||||||
|
</a-breadcrumb-item>
|
||||||
|
</a-breadcrumb>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { useRouter, RouteRecordName } from 'vue-router';
|
||||||
|
import { useAppStore } from '@/store';
|
||||||
|
import { listenerRouteChange } from '@/utils/route-listener';
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const { t } = useI18n();
|
||||||
|
const router = useRouter();
|
||||||
|
const showBreadcrumb = computed(() => {
|
||||||
|
const b = appStore.getCurrentTopMenu.meta?.breadcrumbs;
|
||||||
|
return b && b.length > 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听路由变化,存储打开及选中的菜单
|
||||||
|
*/
|
||||||
|
listenerRouteChange((newRoute) => {
|
||||||
|
const { name } = newRoute;
|
||||||
|
if (name === appStore.currentTopMenu.name) {
|
||||||
|
appStore.setBreadcrumbList(appStore.currentTopMenu?.meta?.breadcrumbs);
|
||||||
|
} else {
|
||||||
|
appStore.setBreadcrumbList([]);
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
function jumpTo(name: RouteRecordName) {
|
||||||
|
router.push({ name });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
/** 面包屑 **/
|
||||||
|
.arco-breadcrumb-item {
|
||||||
|
@apply cursor-pointer;
|
||||||
|
|
||||||
|
color: var(--color-text-4);
|
||||||
|
&:hover {
|
||||||
|
color: rgb(var(--primary-5));
|
||||||
|
}
|
||||||
|
&:disabled {
|
||||||
|
color: var(--color-text-brand);
|
||||||
|
}
|
||||||
|
&:last-child {
|
||||||
|
@apply cursor-auto;
|
||||||
|
|
||||||
|
color: var(--color-text-2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,9 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<a-menu
|
<a-menu
|
||||||
v-if="appStore.topMenus.length > 0"
|
v-show="appStore.topMenus.length > 0"
|
||||||
|
v-model:selected-keys="activeMenus"
|
||||||
class="bg-transparent"
|
class="bg-transparent"
|
||||||
mode="horizontal"
|
mode="horizontal"
|
||||||
:default-selected-keys="[defaultActiveMenu]"
|
@menu-item-click="setCurrentTopMenu"
|
||||||
>
|
>
|
||||||
<a-menu-item v-for="menu of appStore.topMenus" :key="(menu.name as string)" @click="jumpPath(menu.name)">
|
<a-menu-item v-for="menu of appStore.topMenus" :key="(menu.name as string)" @click="jumpPath(menu.name)">
|
||||||
{{ t(menu.meta?.locale || '') }}
|
{{ t(menu.meta?.locale || '') }}
|
||||||
|
@ -12,7 +13,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { Ref, ref, watch } from 'vue';
|
||||||
import { useRouter, RouteRecordRaw, RouteRecordNormalized, RouteRecordName } from 'vue-router';
|
import { useRouter, RouteRecordRaw, RouteRecordNormalized, RouteRecordName } from 'vue-router';
|
||||||
import { cloneDeep } from 'lodash-es';
|
import { cloneDeep } from 'lodash-es';
|
||||||
import { useAppStore } from '@/store';
|
import { useAppStore } from '@/store';
|
||||||
|
@ -26,11 +27,24 @@
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
const activeMenus: Ref<RouteRecordName[]> = ref([]);
|
||||||
|
|
||||||
const defaultActiveMenu = computed(() => {
|
watch(
|
||||||
const { name } = router.currentRoute.value;
|
() => appStore.getCurrentTopMenu?.name,
|
||||||
return name;
|
(val) => {
|
||||||
});
|
activeMenus.value = [val || ''];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
function setCurrentTopMenu(key: string) {
|
||||||
|
const secParent = appStore.topMenus.find((el: RouteRecordRaw) => {
|
||||||
|
return (el?.name as string).includes(key);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (secParent) {
|
||||||
|
appStore.setCurrentTopMenu(secParent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 监听路由变化,存储打开的三级子路由
|
* 监听路由变化,存储打开的三级子路由
|
||||||
|
@ -45,6 +59,7 @@
|
||||||
(item) => name && (name as string).includes((item?.name as string) || '')
|
(item) => name && (name as string).includes((item?.name as string) || '')
|
||||||
);
|
);
|
||||||
appStore.setTopMenus(currentParent?.children?.filter((item) => item.meta?.isTopMenu));
|
appStore.setTopMenus(currentParent?.children?.filter((item) => item.meta?.isTopMenu));
|
||||||
|
setCurrentTopMenu(name as string);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
|
@ -182,7 +182,7 @@
|
||||||
import { LOCALE_OPTIONS } from '@/locale';
|
import { LOCALE_OPTIONS } from '@/locale';
|
||||||
import useLocale from '@/locale/useLocale';
|
import useLocale from '@/locale/useLocale';
|
||||||
// import useUser from '@/hooks/useUser';
|
// import useUser from '@/hooks/useUser';
|
||||||
import TopMenu from '@/components/pure/ms-top-menu/index.vue';
|
import TopMenu from '@/components/bussiness/ms-top-menu/index.vue';
|
||||||
import MessageBox from '../message-box/index.vue';
|
import MessageBox from '../message-box/index.vue';
|
||||||
import { NOT_SHOW_PROJECT_SELECT_MODULE } from '@/router/constants';
|
import { NOT_SHOW_PROJECT_SELECT_MODULE } from '@/router/constants';
|
||||||
import { getProjectList } from '@/api/modules/system/project';
|
import { getProjectList } from '@/api/modules/system/project';
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
<Menu />
|
<Menu />
|
||||||
</a-drawer>
|
</a-drawer>
|
||||||
<a-layout class="layout-content" :style="paddingStyle">
|
<a-layout class="layout-content" :style="paddingStyle">
|
||||||
|
<MsBreadCrumb />
|
||||||
<a-spin :loading="appStore.loading" :tip="appStore.loadingTip">
|
<a-spin :loading="appStore.loading" :tip="appStore.loadingTip">
|
||||||
<a-scrollbar
|
<a-scrollbar
|
||||||
:style="{
|
:style="{
|
||||||
|
@ -64,6 +65,7 @@
|
||||||
import TabBar from '@/components/pure/tab-bar/index.vue';
|
import TabBar from '@/components/pure/tab-bar/index.vue';
|
||||||
import usePermission from '@/hooks/usePermission';
|
import usePermission from '@/hooks/usePermission';
|
||||||
import PageLayout from './page-layout.vue';
|
import PageLayout from './page-layout.vue';
|
||||||
|
import MsBreadCrumb from '@/components/bussiness/ms-breadcrumb/index.vue';
|
||||||
|
|
||||||
const isInit = ref(false);
|
const isInit = ref(false);
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
|
|
@ -12,5 +12,7 @@ declare module 'vue-router' {
|
||||||
order?: number; // 排序权重
|
order?: number; // 排序权重
|
||||||
noAffix?: boolean; // tab展示设置,设置为true则不在tab列表展示激活页面的tab
|
noAffix?: boolean; // tab展示设置,设置为true则不在tab列表展示激活页面的tab
|
||||||
ignoreCache?: boolean; // 缓存设置,true则不缓存
|
ignoreCache?: boolean; // 缓存设置,true则不缓存
|
||||||
|
isTopMenu?: boolean; // 是否为顶部菜单
|
||||||
|
breadcrumbs?: BreadcrumbItem[]; // 面包屑
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,9 @@ import { Notification } from '@arco-design/web-vue';
|
||||||
import defaultSettings from '@/config/settings.json';
|
import defaultSettings from '@/config/settings.json';
|
||||||
import { getMenuList } from '@/api/modules/user';
|
import { getMenuList } from '@/api/modules/user';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
import type { AppState } from './types';
|
import type { AppState, BreadcrumbItem } from './types';
|
||||||
import type { NotificationReturn } from '@arco-design/web-vue/es/notification/interface';
|
import type { NotificationReturn } from '@arco-design/web-vue/es/notification/interface';
|
||||||
import type { RouteRecordNormalized, RouteRecordRaw } from 'vue-router';
|
import type { RouteRecordNormalized, RouteRecordRaw } from 'vue-router';
|
||||||
|
|
||||||
|
@ -14,6 +15,8 @@ const useAppStore = defineStore('app', {
|
||||||
loading: false,
|
loading: false,
|
||||||
loadingTip: '',
|
loadingTip: '',
|
||||||
topMenus: [] as RouteRecordRaw[],
|
topMenus: [] as RouteRecordRaw[],
|
||||||
|
currentTopMenu: {} as RouteRecordRaw,
|
||||||
|
breadcrumbList: [] as BreadcrumbItem[],
|
||||||
currentOrgId: '',
|
currentOrgId: '',
|
||||||
currentProjectId: '',
|
currentProjectId: '',
|
||||||
}),
|
}),
|
||||||
|
@ -37,6 +40,12 @@ const useAppStore = defineStore('app', {
|
||||||
getTopMenus(state: AppState): RouteRecordRaw[] {
|
getTopMenus(state: AppState): RouteRecordRaw[] {
|
||||||
return state.topMenus;
|
return state.topMenus;
|
||||||
},
|
},
|
||||||
|
getCurrentTopMenu(state: AppState): RouteRecordRaw {
|
||||||
|
return state.currentTopMenu;
|
||||||
|
},
|
||||||
|
getBreadcrumbList(state: AppState): BreadcrumbItem[] {
|
||||||
|
return cloneDeep(state.breadcrumbList);
|
||||||
|
},
|
||||||
getCurrentOrgId(state: AppState): string {
|
getCurrentOrgId(state: AppState): string {
|
||||||
return state.currentOrgId;
|
return state.currentOrgId;
|
||||||
},
|
},
|
||||||
|
@ -124,6 +133,18 @@ const useAppStore = defineStore('app', {
|
||||||
setTopMenus(menus: RouteRecordRaw[] | undefined) {
|
setTopMenus(menus: RouteRecordRaw[] | undefined) {
|
||||||
this.topMenus = menus ? [...menus] : [];
|
this.topMenus = menus ? [...menus] : [];
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 设置顶部菜单组
|
||||||
|
*/
|
||||||
|
setCurrentTopMenu(menu: RouteRecordRaw) {
|
||||||
|
this.currentTopMenu = cloneDeep(menu);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 设置面包屑
|
||||||
|
*/
|
||||||
|
setBreadcrumbList(breadcrumbs: BreadcrumbItem[] | undefined) {
|
||||||
|
this.breadcrumbList = breadcrumbs ? cloneDeep(breadcrumbs) : [];
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* 设置当前组织 ID
|
* 设置当前组织 ID
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
import type { RouteRecordNormalized, RouteRecordRaw } from 'vue-router';
|
import type { RouteRecordNormalized, RouteRecordRaw, RouteRecordName } from 'vue-router';
|
||||||
|
|
||||||
|
export interface BreadcrumbItem {
|
||||||
|
name: RouteRecordName;
|
||||||
|
locale: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AppState {
|
export interface AppState {
|
||||||
theme: string;
|
theme: string;
|
||||||
|
@ -17,6 +22,8 @@ export interface AppState {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
loadingTip: string;
|
loadingTip: string;
|
||||||
topMenus: RouteRecordRaw[];
|
topMenus: RouteRecordRaw[];
|
||||||
|
currentTopMenu: RouteRecordRaw;
|
||||||
|
breadcrumbList: BreadcrumbItem[];
|
||||||
currentOrgId: string;
|
currentOrgId: string;
|
||||||
currentProjectId: string;
|
currentProjectId: string;
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
|
|
Loading…
Reference in New Issue