Commit 819bcbe5263c721f1f77cb277d670a9868b229f7

Authored by vben
1 parent 930383f9

feat(breadcrumb): add breadcrumb demo #143

CHANGELOG.zh_CN.md
@@ -6,6 +6,8 @@ @@ -6,6 +6,8 @@
6 - 新增左侧菜单混合模式 6 - 新增左侧菜单混合模式
7 - 新增 markdown 嵌入表单内示例 7 - 新增 markdown 嵌入表单内示例
8 - 新增主框架外页面示例 8 - 新增主框架外页面示例
  9 +- `route.meta` 新增`currentActiveMenu`,`hideTab`,`hideMenu`参数 用于控制详情页面包屑级菜单显示隐藏。
  10 +- 新增面包屑导航示例
9 11
10 ### 🐛 Bug Fixes 12 ### 🐛 Bug Fixes
11 13
@@ -14,6 +16,7 @@ @@ -14,6 +16,7 @@
14 - 修复图表库切换页面导致宽高计算错误 16 - 修复图表库切换页面导致宽高计算错误
15 - 修复多语言配置 `Locale.show`导致配置不生效 17 - 修复多语言配置 `Locale.show`导致配置不生效
16 - 修复路由类型错误 18 - 修复路由类型错误
  19 +- 修复菜单分割时权限失效问题
17 20
18 ## 2.0.0-rc.14 (2020-12-15) 21 ## 2.0.0-rc.14 (2020-12-15)
19 22
src/components/Menu/src/BasicMenu.vue
@@ -118,16 +118,21 @@ @@ -118,16 +118,21 @@
118 listenerLastChangeTab((route) => { 118 listenerLastChangeTab((route) => {
119 if (route.name === REDIRECT_NAME) return; 119 if (route.name === REDIRECT_NAME) return;
120 handleMenuChange(route); 120 handleMenuChange(route);
121 - }, false); 121 + const currentActiveMenu = route.meta?.currentActiveMenu;
  122 + if (currentActiveMenu) {
  123 + menuState.selectedKeys = [currentActiveMenu];
  124 + setOpenKeys(currentActiveMenu);
  125 + }
  126 + });
122 127
123 watch( 128 watch(
124 () => props.items, 129 () => props.items,
125 () => { 130 () => {
126 handleMenuChange(); 131 handleMenuChange();
127 - },  
128 - {  
129 - immediate: true,  
130 } 132 }
  133 + // {
  134 + // immediate: true,
  135 + // }
131 ); 136 );
132 137
133 async function handleMenuClick({ key, keyPath }: { key: string; keyPath: string[] }) { 138 async function handleMenuClick({ key, keyPath }: { key: string; keyPath: string[] }) {
@@ -149,9 +154,7 @@ @@ -149,9 +154,7 @@
149 return; 154 return;
150 } 155 }
151 const path = (route || unref(currentRoute)).path; 156 const path = (route || unref(currentRoute)).path;
152 - if (props.mode !== MenuModeEnum.HORIZONTAL) {  
153 - setOpenKeys(path);  
154 - } 157 + setOpenKeys(path);
155 if (props.isHorizontal && unref(getSplit)) { 158 if (props.isHorizontal && unref(getSplit)) {
156 const parentPath = await getCurrentParentPath(path); 159 const parentPath = await getCurrentParentPath(path);
157 menuState.selectedKeys = [parentPath]; 160 menuState.selectedKeys = [parentPath];
src/components/Menu/src/components/BasicSubMenuItem.vue
1 <template> 1 <template>
2 - <BasicMenuItem v-if="!menuHasChildren(item)" v-bind="$props" />  
3 - <SubMenu v-else :class="[`${prefixCls}__level${level}`, theme]"> 2 + <BasicMenuItem v-if="!menuHasChildren(item) && getShowMenu" v-bind="$props" />
  3 + <SubMenu
  4 + v-if="menuHasChildren(item) && getShowMenu"
  5 + :class="[`${prefixCls}__level${level}`, theme]"
  6 + >
4 <template #title> 7 <template #title>
5 <MenuItemContent v-bind="$props" :item="item" /> 8 <MenuItemContent v-bind="$props" :item="item" />
6 </template> 9 </template>
@@ -16,7 +19,7 @@ @@ -16,7 +19,7 @@
16 <script lang="ts"> 19 <script lang="ts">
17 import type { Menu as MenuType } from '/@/router/types'; 20 import type { Menu as MenuType } from '/@/router/types';
18 21
19 - import { defineComponent } from 'vue'; 22 + import { defineComponent, computed } from 'vue';
20 import { Menu } from 'ant-design-vue'; 23 import { Menu } from 'ant-design-vue';
21 import { useDesign } from '/@/hooks/web/useDesign'; 24 import { useDesign } from '/@/hooks/web/useDesign';
22 import { itemProps } from '../props'; 25 import { itemProps } from '../props';
@@ -35,8 +38,12 @@ @@ -35,8 +38,12 @@
35 // ExpandIcon: createAsyncComponent(() => import('./ExpandIcon.vue')), 38 // ExpandIcon: createAsyncComponent(() => import('./ExpandIcon.vue')),
36 }, 39 },
37 props: itemProps, 40 props: itemProps,
38 - setup() { 41 + setup(props) {
39 const { prefixCls } = useDesign('basic-menu-item'); 42 const { prefixCls } = useDesign('basic-menu-item');
  43 +
  44 + const getShowMenu = computed(() => {
  45 + return !props.item.meta?.hideMenu;
  46 + });
40 function menuHasChildren(menuTreeItem: MenuType): boolean { 47 function menuHasChildren(menuTreeItem: MenuType): boolean {
41 return ( 48 return (
42 Reflect.has(menuTreeItem, 'children') && 49 Reflect.has(menuTreeItem, 'children') &&
@@ -47,6 +54,7 @@ @@ -47,6 +54,7 @@
47 return { 54 return {
48 prefixCls, 55 prefixCls,
49 menuHasChildren, 56 menuHasChildren,
  57 + getShowMenu,
50 }; 58 };
51 }, 59 },
52 }); 60 });
src/components/Menu/src/useOpenKeys.ts
@@ -18,6 +18,9 @@ export function useOpenKeys( @@ -18,6 +18,9 @@ export function useOpenKeys(
18 const { getCollapsed } = useMenuSetting(); 18 const { getCollapsed } = useMenuSetting();
19 19
20 function setOpenKeys(path: string) { 20 function setOpenKeys(path: string) {
  21 + if (mode.value === MenuModeEnum.HORIZONTAL) {
  22 + return;
  23 + }
21 const menuList = toRaw(menus.value); 24 const menuList = toRaw(menus.value);
22 if (!unref(accordion)) { 25 if (!unref(accordion)) {
23 menuState.openKeys = es6Unique([...menuState.openKeys, ...getAllParentPath(menuList, path)]); 26 menuState.openKeys = es6Unique([...menuState.openKeys, ...getAllParentPath(menuList, path)]);
src/layouts/default/header/components/Breadcrumb.vue
1 <template> 1 <template>
2 <div :class="[prefixCls, `${prefixCls}--${theme}`]"> 2 <div :class="[prefixCls, `${prefixCls}--${theme}`]">
3 <a-breadcrumb :routes="routes"> 3 <a-breadcrumb :routes="routes">
4 - <template #itemRender="{ route, routes }"> 4 + <template #itemRender="{ route, routes, paths }">
5 <Icon :icon="route.meta.icon" v-if="getShowBreadCrumbIcon && route.meta.icon" /> 5 <Icon :icon="route.meta.icon" v-if="getShowBreadCrumbIcon && route.meta.icon" />
6 - <span v-if="routes.indexOf(route) === routes.length - 1"> 6 + <span v-if="!hasRedirect(routes, route)">
7 {{ t(route.meta.title) }} 7 {{ t(route.meta.title) }}
8 </span> 8 </span>
9 - <router-link v-else :to="route.path"> 9 + <router-link v-else to="" @click="handleClick(route, paths, $event)">
10 {{ t(route.meta.title) }} 10 {{ t(route.meta.title) }}
11 </router-link> 11 </router-link>
12 </template> 12 </template>
@@ -30,6 +30,8 @@ @@ -30,6 +30,8 @@
30 import { useRootSetting } from '/@/hooks/setting/useRootSetting'; 30 import { useRootSetting } from '/@/hooks/setting/useRootSetting';
31 31
32 import { propTypes } from '/@/utils/propTypes'; 32 import { propTypes } from '/@/utils/propTypes';
  33 + import { useGo } from '/@/hooks/web/usePage';
  34 + import { isString } from '/@/utils/is';
33 35
34 export default defineComponent({ 36 export default defineComponent({
35 name: 'LayoutBreadcrumb', 37 name: 'LayoutBreadcrumb',
@@ -45,22 +47,12 @@ @@ -45,22 +47,12 @@
45 47
46 const { t } = useI18n(); 48 const { t } = useI18n();
47 watchEffect(() => { 49 watchEffect(() => {
48 - if (currentRoute.value.name === REDIRECT_NAME) {  
49 - return;  
50 - } 50 + if (currentRoute.value.name === REDIRECT_NAME) return;
  51 +
51 const matched = currentRoute.value?.matched; 52 const matched = currentRoute.value?.matched;
52 if (!matched || matched.length === 0) return; 53 if (!matched || matched.length === 0) return;
53 54
54 - let breadcrumbList = filter(toRaw(matched), (item) => {  
55 - if (!item.meta) {  
56 - return false;  
57 - }  
58 - const { title, hideBreadcrumb } = item.meta;  
59 - if (!title || hideBreadcrumb) {  
60 - return false;  
61 - }  
62 - return true;  
63 - }); 55 + let breadcrumbList = filterItem(toRaw(matched));
64 56
65 const filterBreadcrumbList = breadcrumbList.filter( 57 const filterBreadcrumbList = breadcrumbList.filter(
66 (item) => item.path !== PageEnum.BASE_HOME 58 (item) => item.path !== PageEnum.BASE_HOME
@@ -71,13 +63,86 @@ @@ -71,13 +63,86 @@
71 path: PageEnum.BASE_HOME, 63 path: PageEnum.BASE_HOME,
72 meta: { 64 meta: {
73 title: t('layout.header.home'), 65 title: t('layout.header.home'),
  66 + isLink: true,
74 }, 67 },
75 } as unknown) as RouteLocationMatched); 68 } as unknown) as RouteLocationMatched);
76 } 69 }
77 - routes.value = filterBreadcrumbList.length === 1 ? [] : filterBreadcrumbList; 70 +
  71 + if (currentRoute.value.meta?.currentActiveMenu) {
  72 + filterBreadcrumbList.push((currentRoute.value as unknown) as RouteLocationMatched);
  73 + }
  74 + // routes.value = filterBreadcrumbList.length === 1 ? [] : filterBreadcrumbList;
  75 + routes.value = filterBreadcrumbList;
78 }); 76 });
79 77
80 - return { routes, t, prefixCls, getShowBreadCrumbIcon }; 78 + function filterItem(list: RouteLocationMatched[]) {
  79 + let resultList = filter(list, (item) => {
  80 + const { meta } = item;
  81 +
  82 + if (!meta) {
  83 + return false;
  84 + }
  85 + const { title, hideBreadcrumb, hideMenu } = meta;
  86 + if (!title || hideBreadcrumb || hideMenu) {
  87 + return false;
  88 + }
  89 +
  90 + return true;
  91 + }).filter((item) => !item.meta?.hideBreadcrumb || !item.meta?.hideMenu);
  92 +
  93 + resultList = resultList.filter((item) => item.path !== PageEnum.BASE_HOME);
  94 + return resultList;
  95 + }
  96 +
  97 + function handleClick(route: RouteLocationMatched, paths: string[], e: Event) {
  98 + e?.preventDefault();
  99 + const {
  100 + children,
  101 + redirect,
  102 + meta,
  103 + // components
  104 + } = route;
  105 +
  106 + // const isParent =
  107 + // components?.default?.name === 'DefaultLayout' || (components?.default as any)?.parentView;
  108 +
  109 + if (
  110 + children?.length &&
  111 + !redirect
  112 + // && !isParent
  113 + ) {
  114 + e?.stopPropagation();
  115 + return;
  116 + }
  117 + if (meta?.carryParam) {
  118 + return;
  119 + }
  120 +
  121 + const go = useGo();
  122 + if (redirect && isString(redirect)) {
  123 + go(redirect);
  124 + } else {
  125 + const ps = paths.slice(1);
  126 + const lastPath = ps.pop() || '';
  127 + const parentPath = ps.pop() || '';
  128 + let path = `${parentPath}/${lastPath}`;
  129 + path = /^\//.test(path) ? path : `/${path}`;
  130 + go(path);
  131 + }
  132 + }
  133 +
  134 + function hasRedirect(routes: RouteLocationMatched[], route: RouteLocationMatched) {
  135 + if (route?.meta?.isLink) {
  136 + return true;
  137 + }
  138 +
  139 + if (routes.indexOf(route) === routes.length - 1) {
  140 + return false;
  141 + }
  142 + return true;
  143 + }
  144 +
  145 + return { routes, t, prefixCls, getShowBreadCrumbIcon, handleClick, hasRedirect };
81 }, 146 },
82 }); 147 });
83 </script> 148 </script>
src/layouts/default/setting/handler.ts
@@ -59,7 +59,7 @@ export function handler(event: HandlerEnum, value: any): DeepPartial&lt;ProjectConf @@ -59,7 +59,7 @@ export function handler(event: HandlerEnum, value: any): DeepPartial&lt;ProjectConf
59 return { menuSetting: { bgColor: value } }; 59 return { menuSetting: { bgColor: value } };
60 60
61 case HandlerEnum.MENU_SPLIT: 61 case HandlerEnum.MENU_SPLIT:
62 - return { menuSetting: { split: value, collapsedShowTitle: true } }; 62 + return { menuSetting: { split: value } };
63 63
64 case HandlerEnum.MENU_CLOSE_MIX_SIDEBAR_ON_CHANGE: 64 case HandlerEnum.MENU_CLOSE_MIX_SIDEBAR_ON_CHANGE:
65 return { menuSetting: { closeMixSidebarOnChange: value } }; 65 return { menuSetting: { closeMixSidebarOnChange: value } };
src/layouts/default/sider/MixSider.vue
@@ -38,6 +38,7 @@ @@ -38,6 +38,7 @@
38 38
39 <div :class="`${prefixCls}-menu-list`" ref="sideRef" :style="getMenuStyle"> 39 <div :class="`${prefixCls}-menu-list`" ref="sideRef" :style="getMenuStyle">
40 <div 40 <div
  41 + v-show="openMenu"
41 :class="[ 42 :class="[
42 `${prefixCls}-menu-list__title`, 43 `${prefixCls}-menu-list__title`,
43 { 44 {
src/layouts/default/tabs/index.vue
@@ -30,6 +30,7 @@ @@ -30,6 +30,7 @@
30 30
31 import { Tabs } from 'ant-design-vue'; 31 import { Tabs } from 'ant-design-vue';
32 import TabContent from './components/TabContent.vue'; 32 import TabContent from './components/TabContent.vue';
  33 + import type { RouteLocationNormalized } from 'vue-router';
33 34
34 import { useGo } from '/@/hooks/web/usePage'; 35 import { useGo } from '/@/hooks/web/usePage';
35 36
@@ -43,6 +44,8 @@ @@ -43,6 +44,8 @@
43 import { listenerLastChangeTab } from '/@/logics/mitt/tabChange'; 44 import { listenerLastChangeTab } from '/@/logics/mitt/tabChange';
44 import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting'; 45 import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
45 46
  47 + import router from '/@/router';
  48 +
46 export default defineComponent({ 49 export default defineComponent({
47 name: 'MultipleTabs', 50 name: 'MultipleTabs',
48 components: { 51 components: {
@@ -61,7 +64,9 @@ @@ -61,7 +64,9 @@
61 const go = useGo(); 64 const go = useGo();
62 const { getShowQuick, getShowRedo } = useMultipleTabSetting(); 65 const { getShowQuick, getShowRedo } = useMultipleTabSetting();
63 66
64 - const getTabsState = computed(() => tabStore.getTabsState); 67 + const getTabsState = computed(() => {
  68 + return tabStore.getTabsState.filter((item) => !item.meta?.hideTab);
  69 + });
65 70
66 const unClose = computed(() => unref(getTabsState).length === 1); 71 const unClose = computed(() => unref(getTabsState).length === 1);
67 72
@@ -78,13 +83,24 @@ @@ -78,13 +83,24 @@
78 const { name } = route; 83 const { name } = route;
79 if (name === REDIRECT_NAME || !route || !userStore.getTokenState) return; 84 if (name === REDIRECT_NAME || !route || !userStore.getTokenState) return;
80 85
81 - const { path, fullPath } = route;  
82 - const p = fullPath || path; 86 + const { path, fullPath, meta = {} } = route;
83 87
  88 + const { currentActiveMenu, hideTab } = meta;
  89 + const isHide = !hideTab ? null : currentActiveMenu;
  90 + const p = isHide || fullPath || path;
84 if (activeKeyRef.value !== p) { 91 if (activeKeyRef.value !== p) {
85 activeKeyRef.value = p; 92 activeKeyRef.value = p;
86 } 93 }
87 - tabStore.addTabAction(unref(route)); 94 +
  95 + if (isHide) {
  96 + const findParentRoute = router
  97 + .getRoutes()
  98 + .find((item) => item.path === currentActiveMenu);
  99 + findParentRoute &&
  100 + tabStore.addTabAction((findParentRoute as unknown) as RouteLocationNormalized);
  101 + } else {
  102 + tabStore.addTabAction(unref(route));
  103 + }
88 }); 104 });
89 105
90 function handleChange(activeKey: any) { 106 function handleChange(activeKey: any) {
src/locales/lang/en/routes/demo/feat.ts
@@ -15,4 +15,11 @@ export default { @@ -15,4 +15,11 @@ export default {
15 tab: 'Tab with parameters', 15 tab: 'Tab with parameters',
16 tab1: 'Tab with parameter 1', 16 tab1: 'Tab with parameter 1',
17 tab2: 'Tab with parameter 2', 17 tab2: 'Tab with parameter 2',
  18 +
  19 + breadcrumb: 'Breadcrumbs',
  20 + breadcrumbFlat: 'Flat Mode',
  21 + breadcrumbFlatDetail: 'Flat mode details',
  22 +
  23 + breadcrumbChildren: 'Level mode',
  24 + breadcrumbChildrenDetail: 'Level mode detail',
18 }; 25 };
src/locales/lang/zh_CN/routes/demo/feat.ts
@@ -15,4 +15,11 @@ export default { @@ -15,4 +15,11 @@ export default {
15 tab: 'Tab带参', 15 tab: 'Tab带参',
16 tab1: 'Tab带参1', 16 tab1: 'Tab带参1',
17 tab2: 'Tab带参2', 17 tab2: 'Tab带参2',
  18 +
  19 + breadcrumb: '面包屑导航',
  20 + breadcrumbFlat: '平级模式',
  21 + breadcrumbFlatDetail: '平级详情',
  22 +
  23 + breadcrumbChildren: '层级模式',
  24 + breadcrumbChildrenDetail: '层级详情',
18 }; 25 };
src/router/helper/routeHelper.ts
@@ -16,7 +16,7 @@ function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) { @@ -16,7 +16,7 @@ function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) {
16 const { component, name } = item; 16 const { component, name } = item;
17 const { children } = item; 17 const { children } = item;
18 if (component) { 18 if (component) {
19 - item.component = dynamicImport(component); 19 + item.component = dynamicImport(component as string);
20 } else if (name) { 20 } else if (name) {
21 item.component = getParentLayout(name); 21 item.component = getParentLayout(name);
22 } 22 }
@@ -31,7 +31,7 @@ export function transformObjToRoute&lt;T = AppRouteModule&gt;(routeList: AppRouteModul @@ -31,7 +31,7 @@ export function transformObjToRoute&lt;T = AppRouteModule&gt;(routeList: AppRouteModul
31 routeList.forEach((route) => { 31 routeList.forEach((route) => {
32 if (route.component) { 32 if (route.component) {
33 if ((route.component as string).toUpperCase() === 'LAYOUT') { 33 if ((route.component as string).toUpperCase() === 'LAYOUT') {
34 - route.component = LayoutMap.get(route.component); 34 + route.component = LayoutMap.get(route.component as LayoutMapKey);
35 } else { 35 } else {
36 route.children = [cloneDeep(route)]; 36 route.children = [cloneDeep(route)];
37 route.component = LAYOUT; 37 route.component = LAYOUT;
src/router/menus/index.ts
@@ -71,8 +71,10 @@ export async function getShallowMenus(): Promise&lt;Menu[]&gt; { @@ -71,8 +71,10 @@ export async function getShallowMenus(): Promise&lt;Menu[]&gt; {
71 export async function getChildrenMenus(parentPath: string) { 71 export async function getChildrenMenus(parentPath: string) {
72 const menus = await getAsyncMenus(); 72 const menus = await getAsyncMenus();
73 const parent = menus.find((item) => item.path === parentPath); 73 const parent = menus.find((item) => item.path === parentPath);
74 - if (!parent) return [] as Menu[];  
75 - return parent.children; 74 + if (!parent || !parent.children) return [] as Menu[];
  75 + const routes = router.getRoutes();
  76 +
  77 + return !isBackMode() ? filter(parent.children, basicFilter(routes)) : parent.children;
76 } 78 }
77 79
78 // 通用过滤方法 80 // 通用过滤方法
src/router/menus/modules/demo/comp.ts
@@ -151,9 +151,6 @@ const menu: MenuModule = { @@ -151,9 +151,6 @@ const menu: MenuModule = {
151 { 151 {
152 path: 'loading', 152 path: 'loading',
153 name: t('routes.demo.comp.loading'), 153 name: t('routes.demo.comp.loading'),
154 - tag: {  
155 - content: 'new',  
156 - },  
157 }, 154 },
158 { 155 {
159 path: 'tree', 156 path: 'tree',
@@ -176,6 +173,9 @@ const menu: MenuModule = { @@ -176,6 +173,9 @@ const menu: MenuModule = {
176 { 173 {
177 name: t('routes.demo.editor.editor'), 174 name: t('routes.demo.editor.editor'),
178 path: 'editor', 175 path: 'editor',
  176 + tag: {
  177 + content: 'new',
  178 + },
179 children: [ 179 children: [
180 { 180 {
181 path: 'markdown', 181 path: 'markdown',
src/router/menus/modules/demo/feat.ts
@@ -19,6 +19,7 @@ const menu: MenuModule = { @@ -19,6 +19,7 @@ const menu: MenuModule = {
19 path: 'tabs', 19 path: 'tabs',
20 name: t('routes.demo.feat.tabs'), 20 name: t('routes.demo.feat.tabs'),
21 }, 21 },
  22 +
22 { 23 {
23 path: 'context-menu', 24 path: 'context-menu',
24 name: t('routes.demo.feat.contextMenu'), 25 name: t('routes.demo.feat.contextMenu'),
@@ -86,6 +87,27 @@ const menu: MenuModule = { @@ -86,6 +87,27 @@ const menu: MenuModule = {
86 ], 87 ],
87 }, 88 },
88 { 89 {
  90 + name: t('routes.demo.feat.breadcrumb'),
  91 + path: 'breadcrumb',
  92 + tag: {
  93 + content: 'new',
  94 + },
  95 + children: [
  96 + {
  97 + path: 'flat',
  98 + name: t('routes.demo.feat.breadcrumbFlat'),
  99 + },
  100 + {
  101 + path: 'flatDetail',
  102 + name: t('routes.demo.feat.breadcrumbFlatDetail'),
  103 + },
  104 + {
  105 + path: 'children',
  106 + name: t('routes.demo.feat.breadcrumbChildrenDetail'),
  107 + },
  108 + ],
  109 + },
  110 + {
89 path: 'testTab', 111 path: 'testTab',
90 name: t('routes.demo.feat.tab'), 112 name: t('routes.demo.feat.tab'),
91 children: [ 113 children: [
src/router/menus/modules/demo/iframe.ts
@@ -8,14 +8,14 @@ const menu: MenuModule = { @@ -8,14 +8,14 @@ const menu: MenuModule = {
8 path: '/frame', 8 path: '/frame',
9 children: [ 9 children: [
10 { 10 {
11 - path: 'antv',  
12 - name: t('routes.demo.iframe.antv'),  
13 - },  
14 - {  
15 path: 'doc', 11 path: 'doc',
16 name: t('routes.demo.iframe.doc'), 12 name: t('routes.demo.iframe.doc'),
17 }, 13 },
18 { 14 {
  15 + path: 'antv',
  16 + name: t('routes.demo.iframe.antv'),
  17 + },
  18 + {
19 path: 'https://vvbin.cn/doc-next/', 19 path: 'https://vvbin.cn/doc-next/',
20 name: t('routes.demo.iframe.docExternal'), 20 name: t('routes.demo.iframe.docExternal'),
21 }, 21 },
src/router/menus/modules/demo/level.ts
@@ -6,9 +6,6 @@ const menu: MenuModule = { @@ -6,9 +6,6 @@ const menu: MenuModule = {
6 menu: { 6 menu: {
7 name: t('routes.demo.level.level'), 7 name: t('routes.demo.level.level'),
8 path: '/level', 8 path: '/level',
9 - tag: {  
10 - dot: true,  
11 - },  
12 children: [ 9 children: [
13 { 10 {
14 path: 'menu1', 11 path: 'menu1',
src/router/menus/modules/demo/page.ts
@@ -6,9 +6,7 @@ const menu: MenuModule = { @@ -6,9 +6,7 @@ const menu: MenuModule = {
6 menu: { 6 menu: {
7 name: t('routes.demo.page.page'), 7 name: t('routes.demo.page.page'),
8 path: '/page-demo', 8 path: '/page-demo',
9 - tag: {  
10 - dot: true,  
11 - }, 9 +
12 children: [ 10 children: [
13 { 11 {
14 path: 'form', 12 path: 'form',
@@ -102,9 +100,6 @@ const menu: MenuModule = { @@ -102,9 +100,6 @@ const menu: MenuModule = {
102 { 100 {
103 path: 'list', 101 path: 'list',
104 name: t('routes.demo.page.list'), 102 name: t('routes.demo.page.list'),
105 - tag: {  
106 - content: 'new',  
107 - },  
108 children: [ 103 children: [
109 { 104 {
110 path: 'basic', 105 path: 'basic',
src/router/routes/modules/demo/charts.ts
@@ -14,12 +14,21 @@ const charts: AppRouteModule = { @@ -14,12 +14,21 @@ const charts: AppRouteModule = {
14 }, 14 },
15 children: [ 15 children: [
16 { 16 {
  17 + path: 'apexChart',
  18 + name: 'ApexChart',
  19 + meta: {
  20 + title: t('routes.demo.charts.apexChart'),
  21 + },
  22 + component: () => import('/@/views/demo/echarts/apex/index.vue'),
  23 + },
  24 + {
17 path: 'echarts', 25 path: 'echarts',
18 name: 'Echarts', 26 name: 'Echarts',
19 component: getParentLayout('Echarts'), 27 component: getParentLayout('Echarts'),
20 meta: { 28 meta: {
21 title: 'Echarts', 29 title: 'Echarts',
22 }, 30 },
  31 + redirect: '/charts/echarts/map',
23 children: [ 32 children: [
24 { 33 {
25 path: 'map', 34 path: 'map',
@@ -47,14 +56,6 @@ const charts: AppRouteModule = { @@ -47,14 +56,6 @@ const charts: AppRouteModule = {
47 }, 56 },
48 ], 57 ],
49 }, 58 },
50 - {  
51 - path: 'apexChart',  
52 - name: 'ApexChart',  
53 - meta: {  
54 - title: t('routes.demo.charts.apexChart'),  
55 - },  
56 - component: () => import('/@/views/demo/echarts/apex/index.vue'),  
57 - },  
58 ], 59 ],
59 }; 60 };
60 61
src/router/routes/modules/demo/feat.ts
@@ -29,6 +29,68 @@ const feat: AppRouteModule = { @@ -29,6 +29,68 @@ const feat: AppRouteModule = {
29 title: t('routes.demo.feat.tabs'), 29 title: t('routes.demo.feat.tabs'),
30 }, 30 },
31 }, 31 },
  32 + {
  33 + path: 'breadcrumb',
  34 + name: 'BreadcrumbDemo',
  35 + redirect: '/feat/breadcrumb/flat',
  36 + component: getParentLayout('BreadcrumbDemo'),
  37 + meta: {
  38 + title: t('routes.demo.feat.breadcrumb'),
  39 + },
  40 +
  41 + children: [
  42 + {
  43 + path: 'flat',
  44 + name: 'BreadcrumbFlatDemo',
  45 + component: () => import('/@/views/demo/feat/breadcrumb/FlatList.vue'),
  46 + meta: {
  47 + title: t('routes.demo.feat.breadcrumbFlat'),
  48 + },
  49 + },
  50 + {
  51 + path: 'flatDetail',
  52 + name: 'BreadcrumbFlatDetailDemo',
  53 + component: () => import('/@/views/demo/feat/breadcrumb/FlatListDetail.vue'),
  54 + meta: {
  55 + title: t('routes.demo.feat.breadcrumbFlatDetail'),
  56 + hideMenu: true,
  57 + hideTab: true,
  58 + currentActiveMenu: '/feat/breadcrumb/flat',
  59 + },
  60 + },
  61 + {
  62 + path: 'children',
  63 + name: 'BreadcrumbChildrenDemo',
  64 + component: getParentLayout('BreadcrumbChildrenDemo'),
  65 + redirect: '/feat/breadcrumb/children',
  66 + meta: {
  67 + title: t('routes.demo.feat.breadcrumbFlat'),
  68 + },
  69 + children: [
  70 + {
  71 + path: '',
  72 + name: 'BreadcrumbChildren',
  73 + component: () => import('/@/views/demo/feat/breadcrumb/ChildrenList.vue'),
  74 + meta: {
  75 + title: t('routes.demo.feat.breadcrumbChildren'),
  76 + hideBreadcrumb: true,
  77 + },
  78 + },
  79 + {
  80 + path: 'childrenDetail',
  81 + name: 'BreadcrumbChildrenDetailDemo',
  82 + component: () => import('/@/views/demo/feat/breadcrumb/ChildrenListDetail.vue'),
  83 + meta: {
  84 + currentActiveMenu: '/feat/breadcrumb/children',
  85 + title: t('routes.demo.feat.breadcrumbChildrenDetail'),
  86 + hideTab: true,
  87 + hideMenu: true,
  88 + },
  89 + },
  90 + ],
  91 + },
  92 + ],
  93 + },
32 94
33 { 95 {
34 path: 'context-menu', 96 path: 'context-menu',
src/router/routes/modules/demo/iframe.ts
@@ -8,7 +8,7 @@ const iframe: AppRouteModule = { @@ -8,7 +8,7 @@ const iframe: AppRouteModule = {
8 path: '/frame', 8 path: '/frame',
9 name: 'Frame', 9 name: 'Frame',
10 component: LAYOUT, 10 component: LAYOUT,
11 - redirect: '/frame/antv', 11 + redirect: '/frame/doc',
12 meta: { 12 meta: {
13 icon: 'mdi:page-next-outline', 13 icon: 'mdi:page-next-outline',
14 title: t('routes.demo.iframe.frame'), 14 title: t('routes.demo.iframe.frame'),
@@ -16,21 +16,21 @@ const iframe: AppRouteModule = { @@ -16,21 +16,21 @@ const iframe: AppRouteModule = {
16 16
17 children: [ 17 children: [
18 { 18 {
19 - path: 'antv',  
20 - name: 'Antv', 19 + path: 'doc',
  20 + name: 'Doc',
21 component: IFrame, 21 component: IFrame,
22 meta: { 22 meta: {
23 - frameSrc: 'https://2x.antdv.com/docs/vue/introduce-cn/',  
24 - title: t('routes.demo.iframe.antv'), 23 + frameSrc: 'https://vvbin.cn/doc-next/',
  24 + title: t('routes.demo.iframe.doc'),
25 }, 25 },
26 }, 26 },
27 { 27 {
28 - path: 'doc',  
29 - name: 'Doc', 28 + path: 'antv',
  29 + name: 'Antv',
30 component: IFrame, 30 component: IFrame,
31 meta: { 31 meta: {
32 - frameSrc: 'https://vvbin.cn/doc-next/',  
33 - title: t('routes.demo.iframe.doc'), 32 + frameSrc: 'https://2x.antdv.com/docs/vue/introduce-cn/',
  33 + title: t('routes.demo.iframe.antv'),
34 }, 34 },
35 }, 35 },
36 { 36 {
src/router/routes/modules/demo/level.ts
@@ -7,7 +7,7 @@ const permission: AppRouteModule = { @@ -7,7 +7,7 @@ const permission: AppRouteModule = {
7 path: '/level', 7 path: '/level',
8 name: 'Level', 8 name: 'Level',
9 component: LAYOUT, 9 component: LAYOUT,
10 - redirect: '/level/menu1/menu1-1', 10 + redirect: '/level/menu1/menu1-1/menu1-1-1',
11 meta: { 11 meta: {
12 icon: 'carbon:user-role', 12 icon: 'carbon:user-role',
13 title: t('routes.demo.level.level'), 13 title: t('routes.demo.level.level'),
@@ -21,6 +21,7 @@ const permission: AppRouteModule = { @@ -21,6 +21,7 @@ const permission: AppRouteModule = {
21 meta: { 21 meta: {
22 title: 'Menu1', 22 title: 'Menu1',
23 }, 23 },
  24 + redirect: '/level/menu1/menu1-1/menu1-1-1',
24 children: [ 25 children: [
25 { 26 {
26 path: 'menu1-1', 27 path: 'menu1-1',
@@ -29,6 +30,7 @@ const permission: AppRouteModule = { @@ -29,6 +30,7 @@ const permission: AppRouteModule = {
29 meta: { 30 meta: {
30 title: 'Menu1-1', 31 title: 'Menu1-1',
31 }, 32 },
  33 + redirect: '/level/menu1/menu1-1/menu1-1-1',
32 children: [ 34 children: [
33 { 35 {
34 path: 'menu1-1-1', 36 path: 'menu1-1-1',
src/router/types.ts
@@ -30,13 +30,22 @@ export interface RouteMeta { @@ -30,13 +30,22 @@ export interface RouteMeta {
30 30
31 // Used internally to mark single-level menus 31 // Used internally to mark single-level menus
32 single?: boolean; 32 single?: boolean;
  33 +
  34 + // Currently active menu
  35 + currentActiveMenu?: string;
  36 +
  37 + // Never show in tab
  38 + hideTab?: boolean;
  39 +
  40 + // Never show in menu
  41 + hideMenu?: boolean;
33 } 42 }
34 43
35 // @ts-ignore 44 // @ts-ignore
36 export interface AppRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> { 45 export interface AppRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> {
37 name: string; 46 name: string;
38 meta: RouteMeta; 47 meta: RouteMeta;
39 - component?: Component; 48 + component?: Component | string;
40 components?: Component; 49 components?: Component;
41 children?: AppRouteRecordRaw[]; 50 children?: AppRouteRecordRaw[];
42 props?: Record<string, any>; 51 props?: Record<string, any>;
src/views/demo/feat/breadcrumb/ChildrenList.vue 0 → 100644
  1 +<template>
  2 + <div class="p-5">
  3 + <router-link to="/feat/breadcrumb/children/childrenDetail">进入子级详情页</router-link>
  4 + </div>
  5 +</template>
  6 +<script lang="ts">
  7 + import { defineComponent } from 'vue';
  8 +
  9 + export default defineComponent({});
  10 +</script>
src/views/demo/feat/breadcrumb/ChildrenListDetail.vue 0 → 100644
  1 +<template>
  2 + <div> 子级详情页 </div>
  3 +</template>
  4 +<script lang="ts">
  5 + import { defineComponent } from 'vue';
  6 +
  7 + export default defineComponent({});
  8 +</script>
src/views/demo/feat/breadcrumb/FlatList.vue 0 → 100644
  1 +<template>
  2 + <div class="p-5">
  3 + <router-link to="/feat/breadcrumb/flatDetail">进入平级详情页</router-link>
  4 + </div>
  5 +</template>
  6 +<script lang="ts">
  7 + import { defineComponent } from 'vue';
  8 +
  9 + export default defineComponent({});
  10 +</script>
src/views/demo/feat/breadcrumb/FlatListDetail.vue 0 → 100644
  1 +<template>
  2 + <div> 平级详情页 </div>
  3 +</template>
  4 +<script lang="ts">
  5 + import { defineComponent } from 'vue';
  6 +
  7 + export default defineComponent({});
  8 +</script>