Commit a65ad9edd56994ae79b91288e36c8306e2c23057

Authored by vben
1 parent ec7efcf0

wip(menu): perf menu

Showing 80 changed files with 1335 additions and 969 deletions
CHANGELOG.zh_CN.md
... ... @@ -7,11 +7,13 @@
7 7 ### ⚡ Performance Improvements
8 8  
9 9 - 异步引入组件
  10 +- 优化整体结构
10 11  
11 12 ### 🎫 Chores
12 13  
13 14 - 返回顶部样式调整,避免遮住其他元素
14   -- 升级`ant-design-vue`到`2.0.0-rc.4`
  15 +- 升级`ant-design-vue`到`2.0.0-rc.5`
  16 +- 刷新按钮布局调整
15 17  
16 18 ### 🐛 Bug Fixes
17 19  
... ... @@ -23,6 +25,8 @@
23 25 - 修复按钮样式问题
24 26 - 修复菜单分割模式问题
25 27 - 修复 `Modal`与`Drawer`组件在使用 emits 数据传递失效问题
  28 +- 修复菜单已知问题
  29 +- 修复上传组件 api 失效问题
26 30  
27 31 ## 2.0.0-rc.13 (2020-12-10)
28 32  
... ...
src/components/Application/index.ts
... ... @@ -2,9 +2,7 @@ import { withInstall } from '../util';
2 2 import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
3 3 import AppLogo from './src/AppLogo.vue';
4 4  
5   -export const AppLocalePicker = createAsyncComponent(() => import('./src/AppLocalePicker.vue'), {
6   - loading: true,
7   -});
  5 +export const AppLocalePicker = createAsyncComponent(() => import('./src/AppLocalePicker.vue'));
8 6 export const AppProvider = createAsyncComponent(() => import('./src/AppProvider.vue'));
9 7 export const AppSearch = createAsyncComponent(() => import('./src/search/AppSearch.vue'), {
10 8 loading: true,
... ...
src/components/Application/src/AppLocalePicker.vue
... ... @@ -11,8 +11,8 @@
11 11 :overlayClassName="`${prefixCls}-overlay`"
12 12 >
13 13 <span :class="prefixCls">
14   - <GlobalOutlined :class="`${prefixCls}__icon`" />
15   - <span v-if="showText">{{ getLangText }}</span>
  14 + <Icon icon="cil:language" />
  15 + <span v-if="showText" :class="`${prefixCls}__text`">{{ getLangText }}</span>
16 16 </span>
17 17 </Dropdown>
18 18 </template>
... ... @@ -30,9 +30,10 @@
30 30 import { propTypes } from '/@/utils/propTypes';
31 31 import { useDesign } from '/@/hooks/web/useDesign';
32 32  
  33 + import Icon from '/@/components/Icon';
33 34 export default defineComponent({
34 35 name: 'AppLocalPicker',
35   - components: { GlobalOutlined, Dropdown },
  36 + components: { GlobalOutlined, Dropdown, Icon },
36 37 props: {
37 38 // Whether to display text
38 39 showText: propTypes.bool.def(true),
... ... @@ -88,8 +89,8 @@
88 89 align-items: center;
89 90 cursor: pointer;
90 91  
91   - &__icon {
92   - margin-right: 4px;
  92 + &__text {
  93 + margin-left: 6px;
93 94 }
94 95 }
95 96 </style>
... ...
src/components/Application/src/AppLogo.vue
... ... @@ -87,7 +87,7 @@
87 87 }
88 88  
89 89 &__title {
90   - font-size: 18px;
  90 + font-size: 16px;
91 91 font-weight: 700;
92 92 opacity: 0;
93 93 transition: all 0.5s;
... ...
src/components/Application/src/AppProvider.vue
... ... @@ -3,11 +3,13 @@
3 3 </template>
4 4 <script lang="ts">
5 5 import type { PropType } from 'vue';
6   - import { defineComponent, toRefs } from 'vue';
  6 + import { defineComponent, toRefs, ref } from 'vue';
7 7  
8 8 import { createAppProviderContext } from './useAppContext';
9 9  
10 10 import designSetting from '/@/settings/designSetting';
  11 + import { createBreakpointListen } from '/@/hooks/event/useBreakpoint';
  12 +
11 13 export default defineComponent({
12 14 name: 'AppProvider',
13 15 inheritAttrs: false,
... ... @@ -18,8 +20,17 @@
18 20 },
19 21 },
20 22 setup(props) {
  23 + const isMobileRef = ref(false);
  24 +
  25 + createBreakpointListen(({ screenMap, sizeEnum, width }) => {
  26 + const lgWidth = screenMap.get(sizeEnum.LG);
  27 + if (lgWidth) {
  28 + isMobileRef.value = width.value - 1 < lgWidth;
  29 + }
  30 + });
  31 +
21 32 const { prefixCls } = toRefs(props);
22   - createAppProviderContext({ prefixCls });
  33 + createAppProviderContext({ prefixCls, isMobile: isMobileRef });
23 34 return {};
24 35 },
25 36 });
... ...
src/components/Application/src/search/AppSearch.vue
... ... @@ -50,6 +50,6 @@
50 50 @prefix-cls: ~'@{namespace}-app-search';
51 51  
52 52 .@{prefix-cls} {
53   - padding: 0 10px;
  53 + padding: 2px;
54 54 }
55 55 </style>
... ...
src/components/Application/src/useAppContext.ts
... ... @@ -3,6 +3,8 @@ import { createContext, useContext } from &#39;/@/hooks/core/useContext&#39;;
3 3  
4 4 export interface AppProviderContextProps {
5 5 prefixCls: Ref<string>;
  6 +
  7 + isMobile: Ref<boolean>;
6 8 }
7 9  
8 10 const key: InjectionKey<AppProviderContextProps> = Symbol();
... ...
src/components/Icon/index.tsx
... ... @@ -77,7 +77,7 @@ export default defineComponent({
77 77 onMounted(update);
78 78  
79 79 return () => (
80   - <div ref={elRef} class={[attrs.class, 'app-iconify anticon']} style={unref(wrapStyleRef)} />
  80 + <span ref={elRef} class={[attrs.class, 'app-iconify anticon']} style={unref(wrapStyleRef)} />
81 81 );
82 82 },
83 83 });
... ...
src/components/Menu/src/BasicMenu.vue
1 1 <template>
2   - <slot name="header" v-if="!getIsHorizontal" />
3   - <ScrollContainer :class="`${prefixCls}-wrapper`" :style="getWrapperStyle">
4   - <Menu
5   - :selectedKeys="selectedKeys"
6   - :defaultSelectedKeys="defaultSelectedKeys"
7   - :mode="mode"
8   - :openKeys="getOpenKeys"
9   - :inlineIndent="inlineIndent"
10   - :theme="theme"
11   - @openChange="handleOpenChange"
12   - :class="getMenuClass"
13   - @click="handleMenuClick"
14   - :subMenuOpenDelay="0.2"
15   - v-bind="getInlineCollapseOptions"
16   - >
17   - <template v-for="item in items" :key="item.path">
18   - <BasicSubMenuItem
19   - :item="item"
20   - :theme="theme"
21   - :level="1"
22   - :appendClass="appendClass"
23   - :parentPath="currentParentPath"
24   - :showTitle="showTitle"
25   - :isHorizontal="isHorizontal"
26   - />
27   - </template>
28   - </Menu>
29   - </ScrollContainer>
  2 + <Menu
  3 + :selectedKeys="selectedKeys"
  4 + :defaultSelectedKeys="defaultSelectedKeys"
  5 + :mode="mode"
  6 + :openKeys="getOpenKeys"
  7 + :inlineIndent="inlineIndent"
  8 + :theme="theme"
  9 + @openChange="handleOpenChange"
  10 + :class="getMenuClass"
  11 + @click="handleMenuClick"
  12 + :subMenuOpenDelay="0.2"
  13 + v-bind="getInlineCollapseOptions"
  14 + >
  15 + <template v-for="item in items" :key="item.path">
  16 + <BasicSubMenuItem
  17 + :item="item"
  18 + :theme="theme"
  19 + :level="1"
  20 + :showTitle="showTitle"
  21 + :isHorizontal="isHorizontal"
  22 + />
  23 + </template>
  24 + </Menu>
30 25 </template>
31 26 <script lang="ts">
32 27 import type { MenuState } from './types';
33 28  
34   - import {
35   - computed,
36   - defineComponent,
37   - unref,
38   - reactive,
39   - watch,
40   - toRefs,
41   - ref,
42   - CSSProperties,
43   - } from 'vue';
  29 + import { computed, defineComponent, unref, reactive, watch, toRefs, ref } from 'vue';
44 30 import { Menu } from 'ant-design-vue';
45 31 import BasicSubMenuItem from './components/BasicSubMenuItem.vue';
46   - import { ScrollContainer } from '/@/components/Container';
47 32  
48 33 import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
49 34  
50   - import { appStore } from '/@/store/modules/app';
51   -
52 35 import { useOpenKeys } from './useOpenKeys';
53   - import { useRouter } from 'vue-router';
  36 + import { RouteLocationNormalizedLoaded, useRouter } from 'vue-router';
54 37  
55 38 import { isFunction } from '/@/utils/is';
56   - import { getCurrentParentPath } from '/@/router/menus';
57 39  
58 40 import { basicProps } from './props';
59 41 import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
60 42 import { REDIRECT_NAME } from '/@/router/constant';
61   - import { tabStore } from '/@/store/modules/tab';
62 43 import { useDesign } from '/@/hooks/web/useDesign';
  44 +
  45 + import { getCurrentParentPath } from '/@/router/menus';
  46 +
63 47 // import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
  48 + import { listenerLastChangeTab } from '/@/logics/mitt/tabChange';
64 49  
65 50 export default defineComponent({
66 51 name: 'BasicMenu',
67 52 components: {
68 53 Menu,
69   - ScrollContainer,
70 54 BasicSubMenuItem,
71 55 // BasicSubMenuItem: createAsyncComponent(() => import('./components/BasicSubMenuItem.vue')),
72 56 },
73 57 props: basicProps,
74 58 emits: ['menuClick'],
75 59 setup(props, { emit }) {
76   - const currentParentPath = ref('');
77 60 const isClickGo = ref(false);
78 61  
79 62 const menuState = reactive<MenuState>({
... ... @@ -97,18 +80,24 @@
97 80 accordion
98 81 );
99 82  
100   - const getMenuClass = computed(() => {
  83 + const getIsTopMenu = computed(() => {
101 84 const { type, mode } = props;
  85 +
  86 + return (
  87 + (type === MenuTypeEnum.TOP_MENU && mode === MenuModeEnum.HORIZONTAL) ||
  88 + (props.isHorizontal && unref(getSplit))
  89 + );
  90 + });
  91 +
  92 + const getMenuClass = computed(() => {
102 93 return [
103 94 prefixCls,
104 95 `justify-${unref(getTopMenuAlign)}`,
105 96 {
106 97 [`${prefixCls}--hide-title`]: !unref(showTitle),
107 98 [`${prefixCls}--collapsed-show-title`]: props.collapsedShowTitle,
108   - [`${prefixCls}__second`]:
109   - !props.isHorizontal && appStore.getProjectConfig.menuSetting.split,
110   - [`${prefixCls}__sidebar-hor`]:
111   - type === MenuTypeEnum.TOP_MENU && mode === MenuModeEnum.HORIZONTAL,
  99 + [`${prefixCls}__second`]: !props.isHorizontal && unref(getSplit),
  100 + [`${prefixCls}__sidebar-hor`]: unref(getIsTopMenu),
112 101 },
113 102 ];
114 103 });
... ... @@ -125,23 +114,10 @@
125 114 return inlineCollapseOptions;
126 115 });
127 116  
128   - const getWrapperStyle = computed(
129   - (): CSSProperties => {
130   - return {
131   - height: `calc(100% - ${props.showLogo ? '48px' : '0px'})`,
132   - overflowY: 'hidden',
133   - };
134   - }
135   - );
136   -
137   - watch(
138   - () => tabStore.getCurrentTab,
139   - () => {
140   - if (unref(currentRoute).name === REDIRECT_NAME) return;
141   - handleMenuChange();
142   - unref(getSplit) && getParentPath();
143   - }
144   - );
  117 + listenerLastChangeTab((route) => {
  118 + if (route.name === REDIRECT_NAME) return;
  119 + handleMenuChange(route);
  120 + }, false);
145 121  
146 122 watch(
147 123 () => props.items,
... ... @@ -153,16 +129,6 @@
153 129 }
154 130 );
155 131  
156   - getParentPath();
157   -
158   - async function getParentPath() {
159   - const { appendClass } = props;
160   - if (!appendClass) return '';
161   - const parentPath = await getCurrentParentPath(unref(currentRoute).path);
162   -
163   - currentParentPath.value = parentPath;
164   - }
165   -
166 132 async function handleMenuClick({ key, keyPath }: { key: string; keyPath: string[] }) {
167 133 const { beforeClickFn } = props;
168 134 if (beforeClickFn && isFunction(beforeClickFn)) {
... ... @@ -176,28 +142,31 @@
176 142 menuState.selectedKeys = [key];
177 143 }
178 144  
179   - function handleMenuChange() {
  145 + async function handleMenuChange(route?: RouteLocationNormalizedLoaded) {
180 146 if (unref(isClickGo)) {
181 147 isClickGo.value = false;
182 148 return;
183 149 }
184   - const path = unref(currentRoute).path;
  150 + const path = (route || unref(currentRoute)).path;
185 151 if (props.mode !== MenuModeEnum.HORIZONTAL) {
186 152 setOpenKeys(path);
187 153 }
188   - menuState.selectedKeys = [path];
  154 + if (unref(getIsTopMenu)) {
  155 + const parentPath = await getCurrentParentPath(path);
  156 + menuState.selectedKeys = [parentPath];
  157 + } else {
  158 + menuState.selectedKeys = [path];
  159 + }
189 160 }
190 161  
191 162 return {
192 163 prefixCls,
193 164 getIsHorizontal,
194   - getWrapperStyle,
195 165 handleMenuClick,
196 166 getInlineCollapseOptions,
197 167 getMenuClass,
198 168 handleOpenChange,
199 169 getOpenKeys,
200   - currentParentPath,
201 170 showTitle,
202 171 ...toRefs(menuState),
203 172 };
... ...
src/components/Menu/src/MenuContent.tsx deleted 100644 → 0
1   -import type { Menu as MenuType } from '/@/router/types';
2   -import type { PropType } from 'vue';
3   -import { computed, unref } from 'vue';
4   -
5   -import { defineComponent } from 'vue';
6   -import Icon from '/@/components/Icon/index';
7   -import { useI18n } from '/@/hooks/web/useI18n';
8   -import { useDesign } from '/@/hooks/web/useDesign';
9   -
10   -const { t } = useI18n();
11   -
12   -export default defineComponent({
13   - name: 'MenuContent',
14   - props: {
15   - item: {
16   - type: Object as PropType<MenuType>,
17   - default: null,
18   - },
19   - showTitle: {
20   - type: Boolean as PropType<boolean>,
21   - default: true,
22   - },
23   - level: {
24   - type: Number as PropType<number>,
25   - default: 0,
26   - },
27   - isHorizontal: {
28   - type: Boolean as PropType<boolean>,
29   - default: true,
30   - },
31   - },
32   - setup(props) {
33   - const { prefixCls } = useDesign('basic-menu');
34   -
35   - const getI18nName = computed(() => t(props.item?.name));
36   -
37   - const getTagClass = computed(() => {
38   - const { item } = props;
39   - const { tag = {} } = item || {};
40   - const { dot, type = 'error' } = tag;
41   - return [
42   - `${prefixCls}__tag`,
43   - type,
44   - {
45   - dot,
46   - },
47   - ];
48   - });
49   -
50   - const getNameClass = computed(() => {
51   - const { showTitle } = props;
52   - return { [`${prefixCls}--show-title`]: showTitle, [`${prefixCls}__name`]: !showTitle };
53   - });
54   -
55   - /**
56   - * @description: 渲染图标
57   - */
58   - function renderIcon(icon?: string) {
59   - return icon ? <Icon icon={icon} size={18} class="menu-item-icon" /> : null;
60   - }
61   -
62   - function renderTag() {
63   - const { item, showTitle, isHorizontal } = props;
64   - if (!item || showTitle || isHorizontal) return null;
65   -
66   - const { tag } = item;
67   - if (!tag) return null;
68   -
69   - const { dot, content } = tag;
70   - if (!dot && !content) return null;
71   -
72   - return <span class={unref(getTagClass)}>{dot ? '' : content}</span>;
73   - }
74   -
75   - return () => {
76   - const { item } = props;
77   - if (!item) {
78   - return null;
79   - }
80   - const { icon } = item;
81   - const name = unref(getI18nName);
82   -
83   - return (
84   - <span class={`${prefixCls}__content-wrapper`}>
85   - {renderIcon(icon)}
86   - {
87   - <span class={unref(getNameClass)}>
88   - {name}
89   - {renderTag()}
90   - </span>
91   - }
92   - </span>
93   - );
94   - };
95   - },
96   -});
src/components/Menu/src/components/BasicMenuItem.vue
1 1 <template>
2 2 <MenuItem :class="getLevelClass">
3   - <MenuContent v-bind="$props" :item="item" />
  3 + <MenuItemContent v-bind="$props" :item="item" />
4 4 </MenuItem>
5 5 </template>
6 6 <script lang="ts">
... ... @@ -9,25 +9,18 @@
9 9 import { useDesign } from '/@/hooks/web/useDesign';
10 10 import { itemProps } from '../props';
11 11  
12   - import MenuContent from '../MenuContent';
  12 + import MenuItemContent from './MenuItemContent.vue';
13 13 export default defineComponent({
14 14 name: 'BasicMenuItem',
15   - components: { MenuItem: Menu.Item, MenuContent },
  15 + components: { MenuItem: Menu.Item, MenuItemContent },
16 16 props: itemProps,
17 17 setup(props) {
18 18 const { prefixCls } = useDesign('basic-menu-item');
19 19  
20 20 const getLevelClass = computed(() => {
21   - const { appendClass, level, item, parentPath, theme } = props;
22   - const isAppendActiveCls = appendClass && level === 1 && item.path === parentPath;
  21 + const { level, theme } = props;
23 22  
24   - const levelCls = [
25   - `${prefixCls}__level${level}`,
26   - theme,
27   - {
28   - 'top-active-menu': isAppendActiveCls,
29   - },
30   - ];
  23 + const levelCls = [`${prefixCls}__level${level}`, theme];
31 24 return levelCls;
32 25 });
33 26 return {
... ...
src/components/Menu/src/components/BasicSubMenuItem.vue
... ... @@ -2,7 +2,7 @@
2 2 <BasicMenuItem v-if="!menuHasChildren(item)" v-bind="$props" />
3 3 <SubMenu v-else :class="[`${prefixCls}__level${level}`, theme]">
4 4 <template #title>
5   - <MenuContent v-bind="$props" :item="item" />
  5 + <MenuItemContent v-bind="$props" :item="item" />
6 6 </template>
7 7 <!-- <template #expandIcon="{ key }">
8 8 <ExpandIcon :key="key" />
... ... @@ -21,17 +21,17 @@
21 21 import { useDesign } from '/@/hooks/web/useDesign';
22 22 import { itemProps } from '../props';
23 23 import BasicMenuItem from './BasicMenuItem.vue';
24   - import MenuContent from '../MenuContent';
25   - // import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
  24 + import MenuItemContent from './MenuItemContent.vue';
26 25  
  26 + // import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
27 27 export default defineComponent({
28 28 name: 'BasicSubMenuItem',
29   -
  29 + isSubMenu: true,
30 30 components: {
31 31 BasicMenuItem,
32 32 SubMenu: Menu.SubMenu,
33 33 MenuItem: Menu.Item,
34   - MenuContent,
  34 + MenuItemContent,
35 35 // ExpandIcon: createAsyncComponent(() => import('./ExpandIcon.vue')),
36 36 },
37 37 props: itemProps,
... ...
src/components/Menu/src/components/MenuItemContent.vue 0 → 100644
  1 +<template>
  2 + <span :class="`${prefixCls}-wrapper`">
  3 + <Icon v-if="getIcon" :icon="getIcon" :size="18" :class="`${prefixCls}-wrapper__icon`" />
  4 + <span :class="getNameClass">
  5 + {{ getI18nName }}
  6 + <MenuItemTag v-bind="$props" />
  7 + </span>
  8 + </span>
  9 +</template>
  10 +<script lang="ts">
  11 + import { computed, defineComponent } from 'vue';
  12 +
  13 + import Icon from '/@/components/Icon/index';
  14 + import { useI18n } from '/@/hooks/web/useI18n';
  15 + import { useDesign } from '/@/hooks/web/useDesign';
  16 + import { contentProps } from '../props';
  17 + import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
  18 + const { t } = useI18n();
  19 +
  20 + export default defineComponent({
  21 + name: 'MenuItemContent',
  22 + components: { Icon, MenuItemTag: createAsyncComponent(() => import('./MenuItemTag.vue')) },
  23 + props: contentProps,
  24 + setup(props) {
  25 + const { prefixCls } = useDesign('basic-menu-item-content');
  26 + const getI18nName = computed(() => t(props.item?.name));
  27 + const getIcon = computed(() => props.item?.icon);
  28 +
  29 + const getNameClass = computed(() => {
  30 + const { showTitle } = props;
  31 + return { [`${prefixCls}--show-title`]: showTitle, [`${prefixCls}__name`]: !showTitle };
  32 + });
  33 + return {
  34 + prefixCls,
  35 + getNameClass,
  36 + getI18nName,
  37 + getIcon,
  38 + };
  39 + },
  40 + });
  41 +</script>
... ...
src/components/Menu/src/components/MenuItemTag.vue 0 → 100644
  1 +<template>
  2 + <span :class="getTagClass" v-if="getShowTag">{{ getContent }}</span>
  3 +</template>
  4 +<script lang="ts">
  5 + import { defineComponent, computed } from 'vue';
  6 +
  7 + import { useDesign } from '/@/hooks/web/useDesign';
  8 + import { contentProps } from '../props';
  9 +
  10 + export default defineComponent({
  11 + name: 'MenuItemTag',
  12 + props: contentProps,
  13 + setup(props) {
  14 + const { prefixCls } = useDesign('basic-menu-item-tag');
  15 +
  16 + const getShowTag = computed(() => {
  17 + const { item, showTitle, isHorizontal } = props;
  18 + if (!item || showTitle || isHorizontal) return false;
  19 +
  20 + const { tag } = item;
  21 + if (!tag) return false;
  22 +
  23 + const { dot, content } = tag;
  24 + if (!dot && !content) return false;
  25 + return true;
  26 + });
  27 +
  28 + const getContent = computed(() => {
  29 + if (!getShowTag.value) return '';
  30 + const { item } = props;
  31 + const { tag } = item;
  32 + const { dot, content } = tag!;
  33 + return dot ? '' : content;
  34 + });
  35 +
  36 + const getTagClass = computed(() => {
  37 + const { item } = props;
  38 + const { tag = {} } = item || {};
  39 + const { dot, type = 'error' } = tag;
  40 + return [
  41 + prefixCls,
  42 + [`${prefixCls}--${type}`],
  43 + {
  44 + [`${prefixCls}--dot`]: dot,
  45 + },
  46 + ];
  47 + });
  48 + return {
  49 + prefixCls,
  50 + getTagClass,
  51 + getShowTag,
  52 + getContent,
  53 + };
  54 + },
  55 + });
  56 +</script>
... ...
src/components/Menu/src/index.less
1 1 @import (reference) '../../../design/index.less';
2 2  
3 3 @basic-menu-prefix-cls: ~'@{namespace}-basic-menu';
  4 +@basic-menu-content-prefix-cls: ~'@{namespace}-basic-menu-item-content';
  5 +@basic-menu-tag-prefix-cls: ~'@{namespace}-basic-menu-item-tag';
4 6  
5 7 .active-style() {
6 8 color: @white;
... ... @@ -41,7 +43,7 @@
41 43 // }
42 44  
43 45 // collapsed show title start
44   - &--show-title {
  46 + .@{basic-menu-content-prefix-cls}--show-title {
45 47 max-width: unset !important;
46 48 opacity: 1 !important;
47 49 }
... ... @@ -78,104 +80,34 @@
78 80 & > li > .ant-menu-submenu-title {
79 81 line-height: 24px;
80 82 }
81   - .@{basic-menu-prefix-cls}__content-wrapper {
  83 + .@{basic-menu-content-prefix-cls}-wrapper {
82 84 display: flex;
83 85 justify-content: center;
84 86 align-items: center;
85 87 flex-direction: column;
86   - .@{basic-menu-prefix-cls}--show-title {
  88 + .@{basic-menu-content-prefix-cls}--show-title {
87 89 line-height: 30px;
88 90 }
89 91 }
90 92 }
91 93  
92   - .ant-menu-item {
93   - transition: unset;
94   - }
95   -
96   - // scrollbar -s tart
97   - // &-wrapper {
98   -
99   - /* 滚动槽 */
100   - // &::-webkit-scrollbar {
101   - // width: 5px;
102   - // height: 5px;
103   - // }
104   -
105   - // &::-webkit-scrollbar-track {
106   - // background: rgba(0, 0, 0, 0);
107   - // }
108   -
109   - // &::-webkit-scrollbar-thumb {
110   - // background: rgba(255, 255, 255, 0.2);
111   - // border-radius: 3px;
112   - // box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.1);
113   - // }
114   -
115   - // ::-webkit-scrollbar-thumb:hover {
116   - // background: @border-color-dark;
117   - // }
118   - // }
119   -
120   - // scrollbar end
  94 + .@{basic-menu-content-prefix-cls}-wrapper {
  95 + width: 100%;
121 96  
122   - &-item__level1.light {
123   - &.top-active-menu {
124   - top: 0 !important;
  97 + &__icon {
  98 + vertical-align: text-top;
125 99 }
  100 + }
126 101  
127   - &.top-active-menu:not(.ant-menu-item-selected) {
128   - color: @primary-color;
129   - border-bottom: 3px solid @primary-color;
130   - }
  102 + .ant-menu-item {
  103 + transition: unset;
131 104 }
132 105  
133 106 &__sidebar-hor {
134   - // overflow: hidden;
135   -
136 107 &.ant-menu-horizontal {
137 108 display: flex;
138   - border: 0;
139 109 align-items: center;
140 110  
141   - .@{basic-menu-prefix-cls}-item__level1 {
142   - margin-right: 2px;
143   - }
144   -
145   - &.ant-menu-light {
146   - .ant-menu-item {
147   - &.@{basic-menu-prefix-cls}-item__level1 {
148   - height: @header-height;
149   - line-height: @header-height;
150   - }
151   - }
152   -
153   - .ant-menu-submenu:hover,
154   - .ant-menu-item-open,
155   - .ant-menu-submenu-open,
156   - .ant-menu-item-selected,
157   - .ant-menu-submenu-selected,
158   - .ant-menu-item:hover,
159   - .ant-menu-item-active,
160   - .ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open,
161   - .ant-menu-submenu-active,
162   - .ant-menu-submenu-title:hover {
163   - color: @primary-color !important;
164   - border-bottom: 3px solid @primary-color;
165   - }
166   -
167   - .ant-menu-submenu {
168   - &:hover {
169   - border-bottom: 3px solid @primary-color;
170   - }
171   -
172   - &.ant-menu-selected,
173   - &.ant-menu-submenu-selected {
174   - border-bottom: 3px solid @primary-color;
175   - }
176   - }
177   - }
178   -
179 111 &.ant-menu-dark {
180 112 background: transparent;
181 113  
... ... @@ -204,12 +136,6 @@
204 136 .@{basic-menu-prefix-cls}-item__level1 {
205 137 background: transparent;
206 138  
207   - &.top-active-menu {
208   - color: @white;
209   - background: @top-menu-active-bg-color;
210   - border-radius: 2px 2px 0 0;
211   - }
212   -
213 139 &.ant-menu-item-selected,
214 140 &.ant-menu-submenu-selected {
215 141 background: @top-menu-active-bg-color !important;
... ... @@ -292,7 +218,7 @@
292 218 }
293 219  
294 220 &.ant-menu-light:not(.@{basic-menu-prefix-cls}__sidebar-hor) {
295   - overflow: hidden;
  221 + // overflow: hidden;
296 222 border-right: none;
297 223  
298 224 .ant-menu-item.ant-menu-item-selected.@{basic-menu-prefix-cls}-menu-item__level1,
... ... @@ -301,26 +227,32 @@
301 227 }
302 228 }
303 229  
304   - // 激活的子菜单样式
305   - .ant-menu-item.ant-menu-item-selected {
306   - position: relative;
307   - }
308   -
309 230 &.@{basic-menu-prefix-cls}__second.ant-menu-inline-collapsed:not(.@{basic-menu-prefix-cls}__sidebar-hor) {
310 231 // Reset menu item row height
311   - .ant-menu-item,
312   - .ant-menu-sub.ant-menu-inline > .ant-menu-item,
313   - .ant-menu-sub.ant-menu-inline > .ant-menu-submenu > .ant-menu-submenu-title {
  232 + .@{basic-menu-prefix-cls}-item__level1 {
314 233 display: flex;
315 234 height: @app-menu-item-height * 1.4;
316 235 padding: 6px 0 !important;
317 236 margin: 0;
  237 + font-size: 12px;
318 238 line-height: @app-menu-item-height;
319 239 align-items: center;
  240 + text-align: center;
  241 +
  242 + > div {
  243 + padding: 6px 0 !important;
  244 + font-size: 12px;
  245 + }
  246 +
  247 + .@{basic-menu-content-prefix-cls}__name {
  248 + display: inline-block;
  249 + width: 50%;
  250 + overflow: hidden;
  251 + }
320 252 }
321 253 }
322 254  
323   - .@{basic-menu-prefix-cls}__tag {
  255 + .@{basic-menu-tag-prefix-cls} {
324 256 position: absolute;
325 257 top: calc(50% - 8px);
326 258 right: 30px;
... ... @@ -332,7 +264,7 @@
332 264 color: #fff;
333 265 border-radius: 2px;
334 266  
335   - &.dot {
  267 + &--dot {
336 268 top: calc(50% - 4px);
337 269 width: 8px;
338 270 height: 8px;
... ... @@ -340,19 +272,19 @@
340 272 border-radius: 50%;
341 273 }
342 274  
343   - &.primary {
  275 + &--primary {
344 276 background: @primary-color;
345 277 }
346 278  
347   - &.error {
  279 + &--error {
348 280 background: @error-color;
349 281 }
350 282  
351   - &.success {
  283 + &--success {
352 284 background: @success-color;
353 285 }
354 286  
355   - &.warn {
  287 + &--warn {
356 288 background: @warning-color;
357 289 }
358 290 }
... ... @@ -362,10 +294,6 @@
362 294 transition: unset;
363 295 }
364 296  
365   - // .ant-menu-submenu-arrow {
366   - // transition: all 0.15s ease 0s;
367   - // }
368   -
369 297 .ant-menu-inline.ant-menu-sub {
370 298 box-shadow: unset !important;
371 299 transition: unset;
... ... @@ -384,10 +312,10 @@
384 312 // collapsed show title end
385 313 .ant-menu-item,
386 314 .ant-menu-submenu-title {
387   - > .@{basic-menu-prefix-cls}__name {
  315 + > .@{basic-menu-content-prefix-cls}__name {
388 316 width: 100%;
389 317  
390   - .@{basic-menu-prefix-cls}__tag {
  318 + .@{basic-menu-tag-prefix-cls} {
391 319 float: right;
392 320 margin-top: @app-menu-item-height / 2;
393 321 transform: translate(0%, -50%);
... ...
src/components/Menu/src/props.ts
... ... @@ -9,7 +9,6 @@ export const basicProps = {
9 9 type: Array as PropType<Menu[]>,
10 10 default: () => [],
11 11 },
12   - appendClass: propTypes.bool,
13 12  
14 13 collapsedShowTitle: propTypes.bool,
15 14  
... ... @@ -42,8 +41,16 @@ export const itemProps = {
42 41 },
43 42 level: propTypes.number,
44 43 theme: propTypes.oneOf(['dark', 'light']),
45   - appendClass: propTypes.bool,
46   - parentPath: propTypes.string,
47 44 showTitle: propTypes.bool,
48 45 isHorizontal: propTypes.bool,
49 46 };
  47 +
  48 +export const contentProps = {
  49 + item: {
  50 + type: Object as PropType<Menu>,
  51 + default: null,
  52 + },
  53 + showTitle: propTypes.bool.def(true),
  54 + level: propTypes.number.def(0),
  55 + isHorizontal: propTypes.bool.def(true),
  56 +};
... ...
src/components/Menu/src/useOpenKeys.ts
... ... @@ -42,6 +42,8 @@ export function useOpenKeys(
42 42 if (unref(mode) === MenuModeEnum.HORIZONTAL || !unref(accordion)) {
43 43 menuState.openKeys = openKeys;
44 44 } else {
  45 + // const menuList = toRaw(menus.value);
  46 + // getAllParentPath(menuList, path);
45 47 const rootSubMenuKeys: string[] = [];
46 48 for (const { children, path } of unref(menus)) {
47 49 if (children && children.length > 0) {
... ...
src/components/Page/src/PageFooter.vue
... ... @@ -22,11 +22,13 @@
22 22 });
23 23 </script>
24 24 <style lang="less" scoped>
  25 + @import (reference) '../../../design/index.less';
  26 +
25 27 .app-footer {
26 28 position: fixed;
27 29 right: 0;
28 30 bottom: 0;
29   - z-index: 99;
  31 + z-index: @page-footer-z-index;
30 32 display: flex;
31 33 width: 100%;
32 34 align-items: center;
... ...
src/components/Preview/src/index.less
... ... @@ -6,7 +6,7 @@
6 6 right: 0;
7 7 bottom: 0;
8 8 left: 0;
9   - z-index: 1000;
  9 + z-index: @preview-comp-z-index;
10 10 background: rgba(0, 0, 0, 0.5);
11 11 user-select: none;
12 12  
... ...
src/components/Upload/src/UploadModal.vue
... ... @@ -55,7 +55,6 @@
55 55 import { checkFileType, checkImgType, getBase64WithFile } from './helper';
56 56 import { buildUUID } from '/@/utils/uuid';
57 57 import { createImgPreview } from '/@/components/Preview/index';
58   - import { uploadApi } from '/@/api/sys/upload';
59 58 import { isFunction } from '/@/utils/is';
60 59 import { warn } from '/@/utils/log';
61 60 import FileList from './FileList';
... ... @@ -176,7 +175,7 @@
176 175 }
177 176 try {
178 177 item.status = UploadResultStatus.UPLOADING;
179   - const { data } = await uploadApi(
  178 + const { data } = await props.api?.(
180 179 {
181 180 ...(props.uploadParams || {}),
182 181 file: item.file,
... ...
src/design/color.less
... ... @@ -128,7 +128,7 @@
128 128 // =================================
129 129 // ==============breadcrumb=========
130 130 // =================================
131   -@breadcrumb-item-normal-color: #6e90a7;
  131 +@breadcrumb-item-normal-color: #999;
132 132 // =================================
133 133 // ==============button=============
134 134 // =================================
... ...
src/design/index.less
... ... @@ -31,6 +31,10 @@ html,
31 31 background-color: #fff !important;
32 32 }
33 33  
  34 +html {
  35 + overflow: hidden;
  36 +}
  37 +
34 38 html,
35 39 body {
36 40 width: 100%;
... ...
src/design/var/index.less
... ... @@ -11,12 +11,24 @@
11 11 @header-height: 48px;
12 12  
13 13 // logo width
14   -@logo-width: 36px;
  14 +@logo-width: 32px;
15 15  
16 16 //
17 17 @side-drag-z-index: 200;
18 18  
19 19 @page-loading-z-index: 10000;
20 20  
  21 +@lock-page-z-index: 3000;
  22 +
  23 +@layout-header-fixed-z-index: 500;
  24 +
  25 +@multiple-tab-fixed-z-index: 505;
  26 +
  27 +@layout-sider-fixed-z-index: 510;
  28 +
  29 +@preview-comp-z-index: 1000;
  30 +
  31 +@page-footer-z-index: 99;
  32 +
21 33 // left-menu
22 34 @app-menu-item-height: 42px;
... ...
src/hooks/event/useBreakpoint.ts
... ... @@ -10,6 +10,9 @@ export interface CreateCallbackParams {
10 10 screen: ComputedRef<sizeEnum | undefined>;
11 11 width: ComputedRef<number>;
12 12 realWidth: ComputedRef<number>;
  13 + screenEnum: typeof screenEnum;
  14 + screenMap: Map<sizeEnum, number>;
  15 + sizeEnum: typeof sizeEnum;
13 16 }
14 17  
15 18 export function useBreakpoint() {
... ... @@ -54,8 +57,8 @@ export function createBreakpointListen(fn?: (opt: CreateCallbackParams) =&gt; void)
54 57 name: 'resize',
55 58  
56 59 listener: () => {
57   - resizeFn();
58 60 getWindowWidth();
  61 + resizeFn();
59 62 },
60 63 });
61 64  
... ... @@ -65,12 +68,14 @@ export function createBreakpointListen(fn?: (opt: CreateCallbackParams) =&gt; void)
65 68 globalRealWidthRef = computed((): number => unref(realWidthRef));
66 69  
67 70 function resizeFn() {
68   - fn &&
69   - fn({
70   - screen: globalScreenRef,
71   - width: globalWidthRef,
72   - realWidth: globalRealWidthRef,
73   - });
  71 + fn?.({
  72 + screen: globalScreenRef,
  73 + width: globalWidthRef,
  74 + realWidth: globalRealWidthRef,
  75 + screenEnum,
  76 + screenMap,
  77 + sizeEnum,
  78 + });
74 79 }
75 80  
76 81 resizeFn();
... ...
src/hooks/setting/useHeaderSetting.ts
... ... @@ -4,7 +4,6 @@ import { computed, unref } from &#39;vue&#39;;
4 4  
5 5 import { appStore } from '/@/store/modules/app';
6 6  
7   -import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
8 7 import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
9 8 import { useRootSetting } from '/@/hooks/setting/useRootSetting';
10 9 import { useFullContent } from '/@/hooks/web/useFullContent';
... ... @@ -12,7 +11,6 @@ import { useFullContent } from &#39;/@/hooks/web/useFullContent&#39;;
12 11 import { MenuModeEnum } from '/@/enums/menuEnum';
13 12  
14 13 const { getFullContent } = useFullContent();
15   -const { getShowMultipleTab } = useMultipleTabSetting();
16 14 const {
17 15 getMenuMode,
18 16 getSplit,
... ... @@ -53,8 +51,6 @@ const getHeaderBgColor = computed(() =&gt; unref(getHeaderSetting).bgColor);
53 51  
54 52 const getShowSearch = computed(() => unref(getHeaderSetting).showSearch);
55 53  
56   -const getShowRedo = computed(() => unref(getHeaderSetting).showRedo && unref(getShowMultipleTab));
57   -
58 54 const getUseLockPage = computed(() => unref(getHeaderSetting).useLockPage);
59 55  
60 56 const getShowFullScreen = computed(() => unref(getHeaderSetting).showFullScreen);
... ... @@ -91,7 +87,6 @@ export function useHeaderSetting() {
91 87 getShowDoc,
92 88 getShowSearch,
93 89 getHeaderTheme,
94   - getShowRedo,
95 90 getUseLockPage,
96 91 getShowFullScreen,
97 92 getShowNotice,
... ...
src/hooks/setting/useMenuSetting.ts
... ... @@ -65,6 +65,10 @@ const getIsHorizontal = computed(() =&gt; {
65 65 return unref(getMenuMode) === MenuModeEnum.HORIZONTAL;
66 66 });
67 67  
  68 +const getIsMixMode = computed(() => {
  69 + return unref(getMenuMode) === MenuModeEnum.INLINE && unref(getMenuType) === MenuTypeEnum.MIX;
  70 +});
  71 +
68 72 const getRealWidth = computed(() => {
69 73 return unref(getCollapsed) ? unref(getMiniWidthNumber) : unref(getMenuWidth);
70 74 });
... ... @@ -130,5 +134,6 @@ export function useMenuSetting() {
130 134 getIsTopMenu,
131 135 getMenuBgColor,
132 136 getShowSidebar,
  137 + getIsMixMode,
133 138 };
134 139 }
... ...
src/hooks/setting/useMultipleTabSetting.ts
... ... @@ -10,6 +10,8 @@ const getShowMultipleTab = computed(() =&gt; unref(getMultipleTabSetting).show);
10 10  
11 11 const getShowQuick = computed(() => unref(getMultipleTabSetting).showQuick);
12 12  
  13 +const getShowRedo = computed(() => unref(getMultipleTabSetting).showRedo);
  14 +
13 15 function setMultipleTabSetting(multiTabsSetting: Partial<MultiTabsSetting>) {
14 16 appStore.commitProjectConfigState({ multiTabsSetting });
15 17 }
... ... @@ -21,5 +23,6 @@ export function useMultipleTabSetting() {
21 23 getMultipleTabSetting,
22 24 getShowMultipleTab,
23 25 getShowQuick,
  26 + getShowRedo,
24 27 };
25 28 }
... ...
src/hooks/web/useAppInject.ts 0 → 100644
  1 +import { useAppProviderContext } from '/@/components/Application';
  2 +import { computed, unref } from 'vue';
  3 +
  4 +export function useAppInject() {
  5 + const values = useAppProviderContext();
  6 +
  7 + return {
  8 + getIsMobile: computed(() => unref(values.isMobile)),
  9 + };
  10 +}
... ...
src/hooks/web/useI18n.ts
... ... @@ -25,7 +25,7 @@ export function useI18n(namespace?: string) {
25 25  
26 26 return {
27 27 ...methods,
28   - t: (key: string, ...arg: Partial<Parameters<typeof t>>) => {
  28 + t: (key: string, ...arg: any) => {
29 29 if (!key) return '';
30 30 return t(getKey(key), ...(arg as Parameters<typeof t>));
31 31 },
... ...
src/hooks/web/usePage.ts
... ... @@ -33,11 +33,13 @@ export function useGo() {
33 33 export const useRedo = () => {
34 34 const { push, currentRoute } = router;
35 35 const { query, params } = currentRoute.value;
36   - function redo() {
37   - push({
38   - path: '/redirect' + unref(currentRoute).fullPath,
39   - query,
40   - params,
  36 + function redo(): Promise<boolean> {
  37 + return new Promise((resolve) => {
  38 + push({
  39 + path: '/redirect' + unref(currentRoute).fullPath,
  40 + query,
  41 + params,
  42 + }).then(() => resolve(true));
41 43 });
42 44 }
43 45 return redo;
... ...
src/hooks/web/useTabs.ts
... ... @@ -11,7 +11,11 @@ export function useTabs() {
11 11 }
12 12  
13 13 return {
14   - refreshPage: () => canIUseFn() && tabStore.commitRedoPage(),
  14 + refreshPage: async () => {
  15 + if (canIUseFn()) {
  16 + await tabStore.commitRedoPage();
  17 + }
  18 + },
15 19 closeAll: () => canIUseFn() && tabStore.closeAllTabAction(),
16 20 closeLeft: () => canIUseFn() && tabStore.closeLeftTabAction(tabStore.getCurrentTab),
17 21 closeRight: () => canIUseFn() && tabStore.closeRightTabAction(tabStore.getCurrentTab),
... ...
src/layouts/default/content/index.vue
... ... @@ -20,11 +20,10 @@
20 20 import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting';
21 21 import PageLayout from '/@/layouts/page/index';
22 22 import { Loading } from '/@/components/Loading';
23   - import Transition from '/@/views/demo/comp/lazy/Transition.vue';
24 23  
25 24 export default defineComponent({
26 25 name: 'LayoutContent',
27   - components: { PageLayout, Loading, Transition },
  26 + components: { PageLayout, Loading },
28 27 setup() {
29 28 const { prefixCls } = useDesign('layout-content');
30 29 const { getOpenPageLoading } = useTransitionSetting();
... ...
src/layouts/default/header/LayoutHeader.tsx
... ... @@ -3,38 +3,19 @@ import &#39;./index.less&#39;;
3 3 import type { FunctionalComponent } from 'vue';
4 4 import type { Component } from '/@/components/types';
5 5  
6   -import {
7   - defineComponent,
8   - unref,
9   - computed,
10   - ref,
11   - nextTick,
12   - watchEffect,
13   - // nextTick
14   -} from 'vue';
  6 +import { defineComponent, unref, computed } from 'vue';
15 7  
16 8 import { Layout, Tooltip, Badge } from 'ant-design-vue';
17 9 import { AppLogo } from '/@/components/Application';
18   -import UserDropdown from './UserDropdown';
19 10 import LayoutMenu from '../menu';
20   -import LayoutBreadcrumb from './LayoutBreadcrumb.vue';
21 11 import LockAction from './actions/LockAction';
22 12 import LayoutTrigger from '../trigger/index.vue';
23 13 import NoticeAction from './notice/NoticeActionItem.vue';
24   -import {
25   - RedoOutlined,
26   - FullscreenExitOutlined,
27   - FullscreenOutlined,
28   - LockOutlined,
29   - BugOutlined,
30   -} from '@ant-design/icons-vue';
  14 +import { LockOutlined, BugOutlined } from '@ant-design/icons-vue';
31 15  
32 16 import { AppSearch } from '/@/components/Application';
33 17 import { useModal } from '/@/components/Modal';
34 18  
35   -import { useFullscreen } from '/@/hooks/web/useFullScreen';
36   -import { useTabs } from '/@/hooks/web/useTabs';
37   -import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
38 19 import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
39 20 import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
40 21 import { useRootSetting } from '/@/hooks/setting/useRootSetting';
... ... @@ -49,8 +30,10 @@ import { MenuModeEnum, MenuSplitTyeEnum } from &#39;/@/enums/menuEnum&#39;;
49 30 import { AppLocalePicker } from '/@/components/Application';
50 31 import { useI18n } from '/@/hooks/web/useI18n';
51 32 import { propTypes } from '/@/utils/propTypes';
52   -import { useLayoutContext } from '../useLayoutContext';
53 33  
  34 +import { UserDropDown, LayoutBreadcrumb, FullScreen } from './components';
  35 +import { useAppInject } from '/@/hooks/web/useAppInject';
  36 +import { useDesign } from '../../../hooks/web/useDesign';
54 37 interface TooltipItemProps {
55 38 title: string;
56 39 }
... ... @@ -72,24 +55,14 @@ export default defineComponent({
72 55 fixed: propTypes.bool,
73 56 },
74 57 setup(props) {
75   - let logoEl: Element | null | undefined;
76   -
77   - const logoWidthRef = ref(200);
78   - const logoRef = ref<ComponentRef>(null);
79   -
80   - const injectValue = useLayoutContext();
81   -
82   - const { refreshPage } = useTabs();
83 58 const { t } = useI18n();
84   -
85   - const { getShowTopMenu, getShowHeaderTrigger, getSplit, getIsHorizontal } = useMenuSetting();
86   -
  59 + const { prefixCls } = useDesign('layout-header');
  60 + const { getShowTopMenu, getShowHeaderTrigger, getSplit } = useMenuSetting();
87 61 const { getShowLocale } = useLocaleSetting();
88   - const { getUseErrorHandle, getShowBreadCrumbIcon } = useRootSetting();
  62 + const { getUseErrorHandle } = useRootSetting();
89 63  
90 64 const {
91 65 getHeaderTheme,
92   - getShowRedo,
93 66 getUseLockPage,
94 67 getShowFullScreen,
95 68 getShowNotice,
... ... @@ -100,23 +73,11 @@ export default defineComponent({
100 73  
101 74 const { push } = useRouter();
102 75 const [register, { openModal }] = useModal();
103   - const { toggleFullscreen, isFullscreenRef } = useFullscreen();
104   -
105   - useWindowSizeFn(
106   - () => {
107   - calcTopMenuWidth();
108   - },
109   - 100,
110   - { immediate: false }
111   - );
  76 + const { getIsMobile } = useAppInject();
112 77  
113 78 const headerClass = computed(() => {
114 79 const theme = unref(getHeaderTheme);
115   - return theme ? `layout-header__header--${theme}` : '';
116   - });
117   -
118   - const isPc = computed(() => {
119   - return !unref(injectValue.isMobile);
  80 + return theme ? `${prefixCls}__header--${theme}` : '';
120 81 });
121 82  
122 83 const getSplitType = computed(() => {
... ... @@ -127,25 +88,6 @@ export default defineComponent({
127 88 return unref(getSplit) ? MenuModeEnum.HORIZONTAL : null;
128 89 });
129 90  
130   - watchEffect(() => {
131   - if (unref(getIsHorizontal)) {
132   - calcTopMenuWidth();
133   - }
134   - });
135   -
136   - function calcTopMenuWidth() {
137   - nextTick(() => {
138   - if (!unref(getShowTopMenu)) return;
139   - let width = 0;
140   - if (!logoEl) {
141   - logoEl = unref(logoRef)?.$el;
142   - }
143   - if (!logoEl) return;
144   - width += logoEl.clientWidth;
145   - logoWidthRef.value = width + 80;
146   - });
147   - }
148   -
149 91 function handleToErrorList() {
150 92 push(PageEnum.ERROR_LOG_PAGE).then(() => {
151 93 errorStore.commitErrorListCountState(0);
... ... @@ -156,27 +98,28 @@ export default defineComponent({
156 98 openModal(true);
157 99 }
158 100  
159   - function renderHeaderContent() {
160   - const width = unref(logoWidthRef);
  101 + function renderHeaderLeft() {
161 102 return (
162   - <div class="layout-header__content ">
163   - {unref(getShowHeaderLogo) && (
164   - <AppLogo class={`layout-header__logo`} ref={logoRef} theme={unref(getHeaderTheme)} />
165   - )}
166   -
  103 + <>
167 104 {unref(getShowContent) && (
168   - <div class="layout-header__left">
  105 + <div class={`${prefixCls}__left`}>
169 106 {unref(getShowHeaderTrigger) && (
170 107 <LayoutTrigger theme={unref(getHeaderTheme)} sider={false} />
171 108 )}
172   - {unref(getShowBread) && unref(isPc) && (
173   - <LayoutBreadcrumb showIcon={unref(getShowBreadCrumbIcon)} />
  109 + {unref(getShowBread) && !unref(getIsMobile) && (
  110 + <LayoutBreadcrumb theme={unref(getHeaderTheme)} />
174 111 )}
175 112 </div>
176 113 )}
  114 + </>
  115 + );
  116 + }
177 117  
178   - {unref(getShowTopMenu) && unref(isPc) && (
179   - <div class={[`layout-header__menu `]} style={{ width: `calc(100% - ${width}px)` }}>
  118 + function renderHeaderContent() {
  119 + return (
  120 + <div class={`${prefixCls}__content`}>
  121 + {unref(getShowTopMenu) && !unref(getIsMobile) && (
  122 + <div class={[`${prefixCls}__menu `]}>
180 123 {/* <div class={[`layout-header__menu `]}> */}
181 124 <LayoutMenu
182 125 isHorizontal={true}
... ... @@ -193,18 +136,18 @@ export default defineComponent({
193 136  
194 137 function renderActionDefault(Comp: Component | any, event: Fn) {
195 138 return (
196   - <div class="layout-header__action-item" onClick={event}>
197   - <Comp class="layout-header__action-icon" />
  139 + <div class={`${prefixCls}__action-item`} onClick={event}>
  140 + <Comp class={`${prefixCls}__action-icon`} />
198 141 </div>
199 142 );
200 143 }
201 144  
202 145 function renderAction() {
203 146 return (
204   - <div class={`layout-header__action`}>
205   - {unref(isPc) && <AppSearch class="layout-header__action-item" />}
  147 + <div class={`${prefixCls}__action`}>
  148 + {!unref(getIsMobile) && <AppSearch class={`${prefixCls}__action-item`} />}
206 149  
207   - {unref(getUseErrorHandle) && unref(isPc) && (
  150 + {unref(getUseErrorHandle) && !unref(getIsMobile) && (
208 151 <TooltipItem title={t('layout.header.tooltipErrorLog')}>
209 152 {() => (
210 153 <Badge
... ... @@ -219,48 +162,27 @@ export default defineComponent({
219 162 </TooltipItem>
220 163 )}
221 164  
222   - {unref(getUseLockPage) && unref(isPc) && (
  165 + {unref(getUseLockPage) && !unref(getIsMobile) && (
223 166 <TooltipItem title={t('layout.header.tooltipLock')}>
224 167 {() => renderActionDefault(LockOutlined, handleLockPage)}
225 168 </TooltipItem>
226 169 )}
227 170  
228   - {unref(getShowNotice) && unref(isPc) && (
  171 + {unref(getShowNotice) && !unref(getIsMobile) && (
229 172 <TooltipItem title={t('layout.header.tooltipNotify')}>
230 173 {() => <NoticeAction />}
231 174 </TooltipItem>
232 175 )}
233 176  
234   - {unref(getShowRedo) && unref(isPc) && (
235   - <TooltipItem title={t('layout.header.tooltipRedo')}>
236   - {() => renderActionDefault(RedoOutlined, refreshPage)}
237   - </TooltipItem>
238   - )}
  177 + {unref(getShowFullScreen) && !unref(getIsMobile) && <FullScreen />}
  178 +
  179 + <UserDropDown theme={unref(getHeaderTheme)} />
239 180  
240   - {unref(getShowFullScreen) && unref(isPc) && (
241   - <TooltipItem
242   - title={
243   - unref(isFullscreenRef)
244   - ? t('layout.header.tooltipExitFull')
245   - : t('layout.header.tooltipEntryFull')
246   - }
247   - >
248   - {() => {
249   - const Icon = !unref(isFullscreenRef) ? (
250   - <FullscreenOutlined />
251   - ) : (
252   - <FullscreenExitOutlined />
253   - );
254   - return renderActionDefault(Icon, toggleFullscreen);
255   - }}
256   - </TooltipItem>
257   - )}
258   - <UserDropdown class="layout-header__user-dropdown" />
259 181 {unref(getShowLocale) && (
260 182 <AppLocalePicker
261 183 reload={true}
262 184 showText={false}
263   - class="layout-header__action-item locale"
  185 + class={`${prefixCls}__action-item locale`}
264 186 />
265 187 )}
266 188 </div>
... ... @@ -270,6 +192,10 @@ export default defineComponent({
270 192 function renderHeaderDefault() {
271 193 return (
272 194 <>
  195 + {unref(getShowHeaderLogo) && (
  196 + <AppLogo class={`${prefixCls}__logo`} theme={unref(getHeaderTheme)} />
  197 + )}
  198 + {renderHeaderLeft()}
273 199 {renderHeaderContent()}
274 200 {renderAction()}
275 201 <LockAction onRegister={register} />
... ... @@ -279,9 +205,7 @@ export default defineComponent({
279 205  
280 206 return () => {
281 207 return (
282   - <Layout.Header
283   - class={['layout-header', 'flex p-0 px-4 ', unref(headerClass), { fixed: props.fixed }]}
284   - >
  208 + <Layout.Header class={[prefixCls, unref(headerClass), { fixed: props.fixed }]}>
285 209 {() => renderHeaderDefault()}
286 210 </Layout.Header>
287 211 );
... ...
src/layouts/default/header/LayoutMultipleHeader.less
  1 +@import (reference) '../../../design/index.less';
  2 +
1 3 .multiple-tab-header {
2 4 margin-left: 1px;
3 5 transition: width 0.2s;
... ... @@ -10,7 +12,7 @@
10 12 &.fixed {
11 13 position: fixed;
12 14 top: 0;
13   - z-index: 100;
  15 + z-index: @multiple-tab-fixed-z-index;
14 16 width: 100%;
15 17 }
16 18 }
... ...
src/layouts/default/header/LayoutMultipleHeader.tsx
... ... @@ -2,7 +2,7 @@ import &#39;./LayoutMultipleHeader.less&#39;;
2 2  
3 3 import { defineComponent, unref, computed, ref, watch, nextTick, CSSProperties } from 'vue';
4 4  
5   -import LayoutHeader from './LayoutHeader';
  5 +import LayoutHeader from './index.vue';
6 6 import MultipleTabs from '../tabs/index.vue';
7 7  
8 8 import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
... ... @@ -10,6 +10,7 @@ import { useMenuSetting } from &#39;/@/hooks/setting/useMenuSetting&#39;;
10 10 import { useFullContent } from '/@/hooks/web/useFullContent';
11 11 import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
12 12 import { useLayoutContext } from '../useLayoutContext';
  13 +import { useAppInject } from '/@/hooks/web/useAppInject';
13 14  
14 15 export default defineComponent({
15 16 name: 'LayoutMultipleHeader',
... ... @@ -21,8 +22,8 @@ export default defineComponent({
21 22  
22 23 const injectValue = useLayoutContext();
23 24  
24   - const { getCalcContentWidth } = useMenuSetting();
25   -
  25 + const { getCalcContentWidth, getSplit } = useMenuSetting();
  26 + const { getIsMobile } = useAppInject();
26 27 const {
27 28 getFixed,
28 29 getShowInsetHeaderRef,
... ... @@ -36,7 +37,7 @@ export default defineComponent({
36 37  
37 38 const { getShowMultipleTab } = useMultipleTabSetting();
38 39  
39   - const showTabsRef = computed(() => {
  40 + const getShowTabs = computed(() => {
40 41 return unref(getShowMultipleTab) && !unref(getFullContent);
41 42 });
42 43  
... ... @@ -56,7 +57,7 @@ export default defineComponent({
56 57 (): CSSProperties => {
57 58 const style: CSSProperties = {};
58 59 if (unref(getFixed)) {
59   - style.width = unref(injectValue.isMobile) ? '100%' : unref(getCalcContentWidth);
  60 + style.width = unref(getIsMobile) ? '100%' : unref(getCalcContentWidth);
60 61 }
61 62 if (unref(getShowFullHeaderRef)) {
62 63 style.top = `${unref(fullHeaderHeightRef)}px`;
... ... @@ -84,7 +85,7 @@ export default defineComponent({
84 85 const fullHeaderEl = unref(injectValue.fullHeader)?.$el;
85 86  
86 87 let height = 0;
87   - if (headerEl && !unref(getShowFullHeaderRef)) {
  88 + if (headerEl && !unref(getShowFullHeaderRef) && !unref(getSplit)) {
88 89 height += headerEl.offsetHeight;
89 90 }
90 91  
... ... @@ -97,6 +98,7 @@ export default defineComponent({
97 98 height += fullHeaderHeight;
98 99 fullHeaderHeightRef.value = fullHeaderHeight;
99 100 }
  101 +
100 102 placeholderHeightRef.value = height;
101 103 });
102 104 },
... ... @@ -114,7 +116,7 @@ export default defineComponent({
114 116 class={['multiple-tab-header', unref(getHeaderTheme), { fixed: unref(getIsFixed) }]}
115 117 >
116 118 {unref(getShowInsetHeaderRef) && <LayoutHeader ref={headerElRef} />}
117   - {unref(showTabsRef) && <MultipleTabs ref={tabElRef} />}
  119 + {unref(getShowTabs) && <MultipleTabs ref={tabElRef} />}
118 120 </div>
119 121 </>
120 122 );
... ...
src/layouts/default/header/UserDropdown.tsx deleted 100644 → 0
1   -// components
2   -import { Dropdown, Menu } from 'ant-design-vue';
3   -
4   -import { defineComponent, computed, unref } from 'vue';
5   -
6   -// res
7   -import headerImg from '/@/assets/images/header.jpg';
8   -
9   -import Icon from '/@/components/Icon/index';
10   -
11   -import { userStore } from '/@/store/modules/user';
12   -
13   -import { DOC_URL } from '/@/settings/siteSetting';
14   -
15   -import { openWindow } from '/@/utils';
16   -
17   -import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
18   -import { FunctionalComponent } from 'vue';
19   -import { useI18n } from '/@/hooks/web/useI18n';
20   -
21   -type MenuEvent = 'loginOut' | 'doc';
22   -interface MenuItemProps {
23   - icon: string;
24   - text: string;
25   - key: MenuEvent;
26   -}
27   -
28   -const prefixCls = 'user-dropdown';
29   -
30   -const MenuItem: FunctionalComponent<MenuItemProps> = (props) => {
31   - const { key, icon, text } = props;
32   - return (
33   - <Menu.Item key={key}>
34   - {() => (
35   - <span class="flex items-center">
36   - <Icon icon={icon} class="mr-1" />
37   - <span>{text}</span>
38   - </span>
39   - )}
40   - </Menu.Item>
41   - );
42   -};
43   -
44   -export default defineComponent({
45   - name: 'UserDropdown',
46   - setup() {
47   - const { t } = useI18n();
48   - const { getShowDoc } = useHeaderSetting();
49   -
50   - const getUserInfo = computed(() => {
51   - const { realName = '', desc } = userStore.getUserInfoState || {};
52   - return { realName, desc };
53   - });
54   -
55   - // login out
56   - function handleLoginOut() {
57   - userStore.confirmLoginOut();
58   - }
59   -
60   - // open doc
61   - function openDoc() {
62   - openWindow(DOC_URL);
63   - }
64   -
65   - function handleMenuClick(e: { key: MenuEvent }) {
66   - switch (e.key) {
67   - case 'loginOut':
68   - handleLoginOut();
69   - break;
70   - case 'doc':
71   - openDoc();
72   - break;
73   - }
74   - }
75   -
76   - function renderSlotsDefault() {
77   - const { realName } = unref(getUserInfo);
78   - return (
79   - <section class={prefixCls}>
80   - <img class={`${prefixCls}__header`} src={headerImg} />
81   - <section class={`${prefixCls}__info`}>
82   - <section class={`${prefixCls}__name`}>{realName}</section>
83   - </section>
84   - </section>
85   - );
86   - }
87   -
88   - function renderSlotOverlay() {
89   - const showDoc = unref(getShowDoc);
90   - return (
91   - <Menu onClick={handleMenuClick}>
92   - {() => (
93   - <>
94   - {showDoc && (
95   - <MenuItem
96   - key="doc"
97   - text={t('layout.header.dropdownItemDoc')}
98   - icon="gg:loadbar-doc"
99   - />
100   - )}
101   - {/* @ts-ignore */}
102   - {showDoc && <Menu.Divider />}
103   - <MenuItem
104   - key="loginOut"
105   - text={t('layout.header.dropdownItemLoginOut')}
106   - icon="carbon:power"
107   - />
108   - </>
109   - )}
110   - </Menu>
111   - );
112   - }
113   -
114   - return () => {
115   - return (
116   - <Dropdown placement="bottomLeft" overlayClassName="app-layout-header-user-dropdown-overlay">
117   - {{
118   - default: () => renderSlotsDefault(),
119   - overlay: () => renderSlotOverlay(),
120   - }}
121   - </Dropdown>
122   - );
123   - };
124   - },
125   -});
src/layouts/default/header/LayoutBreadcrumb.vue renamed to src/layouts/default/header/components/Breadcrumb.vue
1 1 <template>
2   - <div class="layout-breadcrumb">
  2 + <div :class="[prefixCls, `${prefixCls}--${theme}`]">
3 3 <a-breadcrumb :routes="routes">
4 4 <template #itemRender="{ route, routes }">
5   - <Icon :icon="route.meta.icon" v-if="showIcon && route.meta.icon" />
  5 + <Icon :icon="route.meta.icon" v-if="getShowBreadCrumbIcon && route.meta.icon" />
6 6 <span v-if="routes.indexOf(route) === routes.length - 1">
7 7 {{ t(route.meta.title) }}
8 8 </span>
... ... @@ -14,7 +14,6 @@
14 14 </div>
15 15 </template>
16 16 <script lang="ts">
17   - import { PropType } from 'vue';
18 17 import { defineComponent, ref, toRaw, watchEffect } from 'vue';
19 18 import { useI18n } from 'vue-i18n';
20 19  
... ... @@ -26,18 +25,23 @@
26 25  
27 26 import { HomeOutlined } from '@ant-design/icons-vue';
28 27 import { PageEnum } from '/@/enums/pageEnum';
  28 +
  29 + import { useDesign } from '/@/hooks/web/useDesign';
  30 + import { useRootSetting } from '/@/hooks/setting/useRootSetting';
  31 +
  32 + import { propTypes } from '/@/utils/propTypes';
  33 +
29 34 export default defineComponent({
30 35 name: 'LayoutBreadcrumb',
31 36 components: { HomeOutlined, Icon },
32 37 props: {
33   - showIcon: {
34   - type: Boolean as PropType<boolean>,
35   - default: false,
36   - },
  38 + theme: propTypes.oneOf(['dark', 'light']),
37 39 },
38 40 setup() {
39 41 const routes = ref<RouteLocationMatched[]>([]);
40 42 const { currentRoute } = useRouter();
  43 + const { prefixCls } = useDesign('layout-breadcrumb');
  44 + const { getShowBreadCrumbIcon } = useRootSetting();
41 45  
42 46 const { t } = useI18n();
43 47 watchEffect(() => {
... ... @@ -63,17 +67,71 @@
63 67 );
64 68  
65 69 if (filterBreadcrumbList.length === breadcrumbList.length) {
66   - filterBreadcrumbList.unshift({
  70 + filterBreadcrumbList.unshift(({
67 71 path: PageEnum.BASE_HOME,
68 72 meta: {
69 73 title: t('layout.header.home'),
70 74 },
71   - });
  75 + } as unknown) as RouteLocationMatched);
72 76 }
73   - routes.value = filterBreadcrumbList;
  77 + routes.value = filterBreadcrumbList.length === 1 ? [] : filterBreadcrumbList;
74 78 });
75 79  
76   - return { routes, t };
  80 + return { routes, t, prefixCls, getShowBreadCrumbIcon };
77 81 },
78 82 });
79 83 </script>
  84 +<style lang="less">
  85 + @import (reference) '../../../../design/index.less';
  86 + @prefix-cls: ~'@{namespace}-layout-breadcrumb';
  87 +
  88 + .@{prefix-cls} {
  89 + display: flex;
  90 + padding: 0 8px;
  91 + align-items: center;
  92 +
  93 + .ant-breadcrumb-link {
  94 + .anticon {
  95 + margin-right: 4px;
  96 + margin-bottom: 2px;
  97 + }
  98 + }
  99 +
  100 + &--light {
  101 + .ant-breadcrumb-link {
  102 + color: @breadcrumb-item-normal-color;
  103 +
  104 + a {
  105 + color: @text-color-base;
  106 +
  107 + &:hover {
  108 + color: @primary-color;
  109 + }
  110 + }
  111 + }
  112 +
  113 + .ant-breadcrumb-separator {
  114 + color: @breadcrumb-item-normal-color;
  115 + }
  116 + }
  117 +
  118 + &--dark {
  119 + .ant-breadcrumb-link {
  120 + color: rgba(255, 255, 255, 0.6);
  121 +
  122 + a {
  123 + color: rgba(255, 255, 255, 0.8);
  124 +
  125 + &:hover {
  126 + color: @white;
  127 + }
  128 + }
  129 + }
  130 +
  131 + .ant-breadcrumb-separator,
  132 + .anticon {
  133 + color: rgba(255, 255, 255, 0.8);
  134 + }
  135 + }
  136 + }
  137 +</style>
... ...
src/layouts/default/header/components/ErrorAction.vue 0 → 100644
  1 +<template>
  2 + <Tooltip
  3 + :title="t('layout.header.tooltipErrorLog')"
  4 + placement="bottom"
  5 + :mouseEnterDelay="0.5"
  6 + @click="handleToErrorList"
  7 + >
  8 + <Badge :count="getCount" :offset="[0, 10]" dot :overflowCount="99">
  9 + <BugOutlined />
  10 + </Badge>
  11 + </Tooltip>
  12 +</template>
  13 +<script lang="ts">
  14 + import { defineComponent, computed } from 'vue';
  15 + import { Tooltip, Badge } from 'ant-design-vue';
  16 + import { useI18n } from '/@/hooks/web/useI18n';
  17 + import { BugOutlined } from '@ant-design/icons-vue';
  18 + import { errorStore } from '/@/store/modules/error';
  19 + import { PageEnum } from '/@/enums/pageEnum';
  20 + import { useRouter } from 'vue-router';
  21 +
  22 + export default defineComponent({
  23 + name: 'ErrorAction',
  24 + components: { BugOutlined, Tooltip, Badge },
  25 +
  26 + setup() {
  27 + const { t } = useI18n();
  28 + const { push } = useRouter();
  29 +
  30 + const getCount = computed(() => {
  31 + return errorStore.getErrorListCountState;
  32 + });
  33 +
  34 + function handleToErrorList() {
  35 + push(PageEnum.ERROR_LOG_PAGE).then(() => {
  36 + errorStore.commitErrorListCountState(0);
  37 + });
  38 + }
  39 +
  40 + return {
  41 + t,
  42 + getCount,
  43 + handleToErrorList,
  44 + };
  45 + },
  46 + });
  47 +</script>
... ...
src/layouts/default/header/components/FullScreen.vue 0 → 100644
  1 +<template>
  2 + <Tooltip :title="getTitle" placement="bottom" :mouseEnterDelay="0.5">
  3 + <span @click="toggleFullscreen">
  4 + <FullscreenOutlined v-if="!isFullscreen" />
  5 + <FullscreenExitOutlined v-else />
  6 + </span>
  7 + </Tooltip>
  8 +</template>
  9 +<script lang="ts">
  10 + import { defineComponent, computed, unref } from 'vue';
  11 + import { Tooltip } from 'ant-design-vue';
  12 + import { useI18n } from '/@/hooks/web/useI18n';
  13 + import { useFullscreen } from '/@/hooks/web/useFullScreen';
  14 + import { FullscreenExitOutlined, FullscreenOutlined } from '@ant-design/icons-vue';
  15 + export default defineComponent({
  16 + name: 'FullScreen',
  17 + components: { FullscreenExitOutlined, FullscreenOutlined, Tooltip },
  18 +
  19 + setup() {
  20 + const { t } = useI18n();
  21 + const { toggleFullscreen, isFullscreenRef } = useFullscreen();
  22 +
  23 + const getTitle = computed(() => {
  24 + return unref(isFullscreenRef)
  25 + ? t('layout.header.tooltipExitFull')
  26 + : t('layout.header.tooltipEntryFull');
  27 + });
  28 +
  29 + return {
  30 + getTitle,
  31 + isFullscreen: isFullscreenRef,
  32 + toggleFullscreen,
  33 + };
  34 + },
  35 + });
  36 +</script>
... ...
src/layouts/default/header/components/index.ts 0 → 100644
  1 +import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
  2 +
  3 +export const UserDropDown = createAsyncComponent(() => import('./user-dropdown/index.vue'), {
  4 + loading: true,
  5 +});
  6 +
  7 +export const LayoutBreadcrumb = createAsyncComponent(() => import('./Breadcrumb.vue'));
  8 +
  9 +export const FullScreen = createAsyncComponent(() => import('./FullScreen.vue'));
  10 +
  11 +export const Notify = createAsyncComponent(() => import('./notify/index.vue'));
  12 +
  13 +export const LockItem = createAsyncComponent(() => import('./lock/index.vue'));
  14 +
  15 +export const ErrorAction = createAsyncComponent(() => import('./ErrorAction.vue'));
... ...
src/layouts/default/header/actions/LockAction.less renamed to src/layouts/default/header/components/lock/LockAction.less
src/layouts/default/header/actions/LockAction.tsx renamed to src/layouts/default/header/components/lock/LockAction.tsx
src/layouts/default/header/components/lock/LockModal.vue 0 → 100644
  1 +<template>
  2 + <BasicModal
  3 + :footer="null"
  4 + :title="t('layout.header.lockScreen')"
  5 + v-bind="$attrs"
  6 + :class="prefixCls"
  7 + @register="register"
  8 + >
  9 + <div :class="`${prefixCls}__entry`">
  10 + <div :class="`${prefixCls}__header`">
  11 + <img src="/@/assets/images/header.jpg" :class="`${prefixCls}__header-img`" />
  12 + <p :class="`${prefixCls}__header-name`">{{ getRealName }}</p>
  13 + </div>
  14 +
  15 + <BasicForm @register="registerForm" layout="vertical" />
  16 +
  17 + <div :class="`${prefixCls}__footer`">
  18 + <a-button type="primary" block class="mt-2" @click="handleLock">
  19 + {{ t('layout.header.lockScreenBtn') }}
  20 + </a-button>
  21 + </div>
  22 + </div>
  23 + </BasicModal>
  24 +</template>
  25 +<script lang="ts">
  26 + import { defineComponent, computed } from 'vue';
  27 + import { useI18n } from '/@/hooks/web/useI18n';
  28 + import { useDesign } from '/@/hooks/web/useDesign';
  29 + import { BasicModal, useModalInner } from '/@/components/Modal/index';
  30 + import { BasicForm, useForm } from '/@/components/Form/index';
  31 +
  32 + import { userStore } from '/@/store/modules/user';
  33 + import { lockStore } from '/@/store/modules/lock';
  34 + export default defineComponent({
  35 + name: 'LockModal',
  36 + components: { BasicModal, BasicForm },
  37 +
  38 + setup() {
  39 + const { t } = useI18n();
  40 + const { prefixCls } = useDesign('header-lock-modal');
  41 +
  42 + const getRealName = computed(() => {
  43 + return userStore.getUserInfoState?.realName;
  44 + });
  45 + const [register, { closeModal }] = useModalInner();
  46 +
  47 + const [registerForm, { validateFields, resetFields }] = useForm({
  48 + showActionButtonGroup: false,
  49 + schemas: [
  50 + {
  51 + field: 'password',
  52 + label: t('layout.header.lockScreenPassword'),
  53 + component: 'InputPassword',
  54 + required: true,
  55 + },
  56 + ],
  57 + });
  58 +
  59 + async function handleLock() {
  60 + const values = (await validateFields()) as any;
  61 + const password: string | undefined = values.password;
  62 + closeModal();
  63 +
  64 + lockStore.commitLockInfoState({
  65 + isLock: true,
  66 + pwd: password,
  67 + });
  68 + await resetFields();
  69 + }
  70 +
  71 + return {
  72 + t,
  73 + prefixCls,
  74 + getRealName,
  75 + register,
  76 + registerForm,
  77 + handleLock,
  78 + };
  79 + },
  80 + });
  81 +</script>
  82 +<style lang="less">
  83 + @import (reference) '../../../../../design/index.less';
  84 + @prefix-cls: ~'@{namespace}-header-lock-modal';
  85 +
  86 + .@{prefix-cls} {
  87 + &__entry {
  88 + position: relative;
  89 + height: 240px;
  90 + padding: 130px 30px 60px 30px;
  91 + background: #fff;
  92 + border-radius: 10px;
  93 + }
  94 +
  95 + &__header {
  96 + position: absolute;
  97 + top: 0;
  98 + left: calc(50% - 45px);
  99 + width: auto;
  100 + text-align: center;
  101 +
  102 + &-img {
  103 + width: 70px;
  104 + border-radius: 50%;
  105 + }
  106 +
  107 + &-name {
  108 + margin-top: 5px;
  109 + }
  110 + }
  111 +
  112 + &__footer {
  113 + text-align: center;
  114 + }
  115 + }
  116 +</style>
... ...
src/layouts/default/header/components/lock/index.vue 0 → 100644
  1 +<template>
  2 + <span @click="handleLock">
  3 + <Tooltip :title="t('layout.header.tooltipLock')" placement="bottom" :mouseEnterDelay="0.5">
  4 + <LockOutlined />
  5 + </Tooltip>
  6 + <LockAction @register="register" />
  7 + </span>
  8 +</template>
  9 +<script lang="ts">
  10 + import { defineComponent } from 'vue';
  11 + import { Tooltip } from 'ant-design-vue';
  12 + import { useI18n } from '/@/hooks/web/useI18n';
  13 + import { LockOutlined } from '@ant-design/icons-vue';
  14 + import { useModal } from '/@/components/Modal';
  15 + import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
  16 + export default defineComponent({
  17 + name: 'FullScreen',
  18 + components: {
  19 + LockOutlined,
  20 + Tooltip,
  21 + LockAction: createAsyncComponent(() => import('./LockModal.vue')),
  22 + },
  23 +
  24 + setup() {
  25 + const { t } = useI18n();
  26 + const [register, { openModal }] = useModal();
  27 +
  28 + function handleLock() {
  29 + openModal(true);
  30 + }
  31 + return {
  32 + t,
  33 + register,
  34 + handleLock,
  35 + };
  36 + },
  37 + });
  38 +</script>
... ...
src/layouts/default/header/notice/NoticeList.vue renamed to src/layouts/default/header/components/notify/NoticeList.vue
1 1 <template>
2   - <a-list class="list">
  2 + <a-list :class="prefixCls">
3 3 <template v-for="item in list" :key="item.id">
4 4 <a-list-item class="list-item">
5 5 <a-list-item-meta>
... ... @@ -33,6 +33,7 @@
33 33 <script lang="ts">
34 34 import { defineComponent, PropType } from 'vue';
35 35 import { ListItem } from './data';
  36 + import { useDesign } from '/@/hooks/web/useDesign';
36 37  
37 38 export default defineComponent({
38 39 props: {
... ... @@ -41,10 +42,17 @@
41 42 default: () => [],
42 43 },
43 44 },
  45 + setup() {
  46 + const { prefixCls } = useDesign('header-notify-list');
  47 + return { prefixCls };
  48 + },
44 49 });
45 50 </script>
46 51 <style lang="less" scoped>
47   - .list {
  52 + @import (reference) '../../../../../design/index.less';
  53 + @prefix-cls: ~'@{namespace}-header-notify-list';
  54 +
  55 + .@{prefix-cls} {
48 56 &::-webkit-scrollbar {
49 57 display: none;
50 58 }
... ...
src/layouts/default/header/notice/data.ts renamed to src/layouts/default/header/components/notify/data.ts
src/layouts/default/header/notice/NoticeActionItem.vue renamed to src/layouts/default/header/components/notify/index.vue
1 1 <template>
2   - <div class="layout-header__action-item notify-action">
3   - <Popover title="" trigger="click" overlayClassName="layout-header__notify-action">
  2 + <div :class="prefixCls">
  3 + <Popover title="" trigger="click" :overlayClassName="`${prefixCls}__overlay`">
4 4 <Badge :count="count" dot :numberStyle="numberStyle">
5   - <BellOutlined class="layout-header__action-icon" />
  5 + <BellOutlined />
6 6 </Badge>
7 7 <template #content>
8 8 <Tabs>
... ... @@ -26,10 +26,13 @@
26 26 import { BellOutlined } from '@ant-design/icons-vue';
27 27 import { tabListData } from './data';
28 28 import NoticeList from './NoticeList.vue';
  29 + import { useDesign } from '/@/hooks/web/useDesign';
29 30  
30 31 export default defineComponent({
31 32 components: { Popover, BellOutlined, Tabs, TabPane: Tabs.TabPane, Badge, NoticeList },
32 33 setup() {
  34 + const { prefixCls } = useDesign('header-notify');
  35 +
33 36 let count = 0;
34 37  
35 38 for (let i = 0; i < tabListData.length; i++) {
... ... @@ -37,6 +40,7 @@
37 40 }
38 41  
39 42 return {
  43 + prefixCls,
40 44 tabListData,
41 45 count,
42 46 numberStyle: {},
... ... @@ -45,13 +49,16 @@
45 49 });
46 50 </script>
47 51 <style lang="less">
48   - .layout-header__notify-action {
49   - max-width: 360px;
50   - }
  52 + @import (reference) '../../../../../design/index.less';
  53 + @prefix-cls: ~'@{namespace}-header-notify';
51 54  
52   - .notify-action {
  55 + .@{prefix-cls} {
53 56 padding-top: 2px;
54 57  
  58 + &__overlay {
  59 + max-width: 360px;
  60 + }
  61 +
55 62 .ant-tabs-content {
56 63 width: 300px;
57 64 }
... ...
src/layouts/default/header/components/user-dropdown/DropMenuItem.vue 0 → 100644
  1 +<template>
  2 + <MenuItem :key="key">
  3 + <span class="flex items-center">
  4 + <Icon :icon="icon" class="mr-1" />
  5 + <span>{{ text }}</span>
  6 + </span>
  7 + </MenuItem>
  8 +</template>
  9 +<script lang="ts">
  10 + // components
  11 + import { Menu } from 'ant-design-vue';
  12 +
  13 + import { defineComponent } from 'vue';
  14 +
  15 + import Icon from '/@/components/Icon/index';
  16 + import { propTypes } from '/@/utils/propTypes';
  17 +
  18 + export default defineComponent({
  19 + name: 'DropdownMenuItem',
  20 + components: { MenuItem: Menu.Item, Icon },
  21 + props: {
  22 + key: propTypes.string,
  23 + text: propTypes.string,
  24 + icon: propTypes.string,
  25 + },
  26 + });
  27 +</script>
... ...
src/layouts/default/header/components/user-dropdown/index.vue 0 → 100644
  1 +<template>
  2 + <Dropdown placement="bottomLeft" :overlayClassName="`${prefixCls}-dropdown-overlay`">
  3 + <span :class="[prefixCls, `${prefixCls}--${theme}`]">
  4 + <img :class="`${prefixCls}__header`" src="/@/assets/images/header.jpg" />
  5 + <span :class="`${prefixCls}__info`">
  6 + <span :class="`${prefixCls}__name anticon`">{{ getUserInfo.realName }}</span>
  7 + </span>
  8 + </span>
  9 +
  10 + <template #overlay>
  11 + <Menu @click="handleMenuClick">
  12 + <MenuItem key="doc" :text="t('layout.header.dropdownItemDoc')" icon="gg:loadbar-doc" />
  13 + <MenuDivider v-if="getShowDoc" />
  14 + <MenuItem
  15 + key="loginOut"
  16 + :text="t('layout.header.dropdownItemLoginOut')"
  17 + icon="carbon:power"
  18 + />
  19 + </Menu>
  20 + </template>
  21 + </Dropdown>
  22 +</template>
  23 +<script lang="ts">
  24 + // components
  25 + import { Dropdown, Menu } from 'ant-design-vue';
  26 +
  27 + import { defineComponent, computed } from 'vue';
  28 +
  29 + // res
  30 +
  31 + import Icon from '/@/components/Icon/index';
  32 +
  33 + import { userStore } from '/@/store/modules/user';
  34 +
  35 + import { DOC_URL } from '/@/settings/siteSetting';
  36 +
  37 + import { openWindow } from '/@/utils';
  38 +
  39 + import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
  40 + import { useI18n } from '/@/hooks/web/useI18n';
  41 +
  42 + import { useDesign } from '/@/hooks/web/useDesign';
  43 + import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
  44 + import { propTypes } from '/@/utils/propTypes';
  45 +
  46 + type MenuEvent = 'loginOut' | 'doc';
  47 +
  48 + export default defineComponent({
  49 + name: 'UserDropdown',
  50 + components: {
  51 + Dropdown,
  52 + Menu,
  53 + MenuItem: createAsyncComponent(() => import('./DropMenuItem.vue')),
  54 + MenuDivider: Menu.Divider,
  55 + Icon,
  56 + },
  57 + props: {
  58 + theme: propTypes.oneOf(['dark', 'light']),
  59 + },
  60 + setup() {
  61 + const { prefixCls } = useDesign('header-user-dropdown');
  62 + const { t } = useI18n();
  63 + const { getShowDoc } = useHeaderSetting();
  64 +
  65 + const getUserInfo = computed(() => {
  66 + const { realName = '', desc } = userStore.getUserInfoState || {};
  67 + return { realName, desc };
  68 + });
  69 +
  70 + // login out
  71 + function handleLoginOut() {
  72 + userStore.confirmLoginOut();
  73 + }
  74 +
  75 + // open doc
  76 + function openDoc() {
  77 + openWindow(DOC_URL);
  78 + }
  79 +
  80 + function handleMenuClick(e: { key: MenuEvent }) {
  81 + switch (e.key) {
  82 + case 'loginOut':
  83 + handleLoginOut();
  84 + break;
  85 + case 'doc':
  86 + openDoc();
  87 + break;
  88 + }
  89 + }
  90 +
  91 + return {
  92 + prefixCls,
  93 + t,
  94 + getUserInfo,
  95 + handleMenuClick,
  96 + getShowDoc,
  97 + };
  98 + },
  99 + });
  100 +</script>
  101 +<style lang="less">
  102 + @import (reference) '../../../../../design/index.less';
  103 + @prefix-cls: ~'@{namespace}-header-user-dropdown';
  104 +
  105 + .@{prefix-cls} {
  106 + display: flex;
  107 + height: @header-height;
  108 + min-width: 100px;
  109 + padding: 0 0 0 10px;
  110 + padding-right: 10px;
  111 + overflow: hidden;
  112 + font-size: 12px;
  113 + cursor: pointer;
  114 + align-items: center;
  115 +
  116 + &:hover {
  117 + background: @header-light-bg-hover-color;
  118 + }
  119 +
  120 + img {
  121 + width: 26px;
  122 + height: 26px;
  123 + margin-right: 12px;
  124 + }
  125 +
  126 + &__header {
  127 + border-radius: 50%;
  128 + }
  129 +
  130 + &__name {
  131 + font-size: 14px;
  132 + }
  133 +
  134 + &--dark {
  135 + &:hover {
  136 + background: @header-dark-bg-hover-color;
  137 + }
  138 + }
  139 +
  140 + &--light {
  141 + .@{prefix-cls}__name {
  142 + color: @text-color-base;
  143 + }
  144 +
  145 + .@{prefix-cls}__desc {
  146 + color: @header-light-desc-color;
  147 + }
  148 + }
  149 +
  150 + &-dropdown-overlay {
  151 + .ant-dropdown-menu-item {
  152 + min-width: 160px;
  153 + }
  154 + }
  155 + }
  156 +</style>
... ...
src/layouts/default/header/index.less
1 1 @import (reference) '../../../design/index.less';
2 2 @header-trigger-prefix-cls: ~'@{namespace}-layout-header-trigger';
  3 +@header-prefix-cls: ~'@{namespace}-layout-header';
  4 +@locale-prefix-cls: ~'@{namespace}-app-locale-picker';
3 5  
4   -.layout-header {
  6 +.@{header-prefix-cls} {
5 7 display: flex;
6 8 height: @header-height;
7   - padding: 0 20px 0 0;
  9 + padding: 0;
8 10 margin-left: -1px;
9 11 line-height: @header-height;
10 12 color: @white;
... ... @@ -12,15 +14,28 @@
12 14 align-items: center;
13 15 justify-content: space-between;
14 16  
15   - &.fixed {
  17 + &--fixed {
16 18 position: fixed;
17 19 top: 0;
18 20 left: 0;
19   - z-index: 1000;
  21 + z-index: @layout-header-fixed-z-index;
20 22 width: 100%;
21 23 }
22 24  
23   - &__left {
  25 + &-logo {
  26 + height: @header-height;
  27 + min-width: 192px;
  28 + padding: 0 10px;
  29 + font-size: 14px;
  30 +
  31 + img {
  32 + width: @logo-width;
  33 + height: @logo-width;
  34 + margin-right: 2px;
  35 + }
  36 + }
  37 +
  38 + &-left {
24 39 display: flex;
25 40 height: 100%;
26 41 align-items: center;
... ... @@ -33,7 +48,7 @@
33 48 align-items: center;
34 49  
35 50 .anticon {
36   - font-size: 17px;
  51 + font-size: 16px;
37 52 }
38 53  
39 54 &.light {
... ... @@ -52,82 +67,65 @@
52 67 }
53 68 }
54 69 }
55   -
56   - .layout-breadcrumb {
57   - display: flex;
58   - padding: 0 8px;
59   - align-items: center;
60   -
61   - .ant-breadcrumb-link {
62   - .anticon {
63   - margin-right: 4px;
64   - margin-bottom: 2px;
65   - }
66   - }
67   - }
68 70 }
69 71  
70   - &__content {
71   - display: flex;
  72 + &-menu {
72 73 height: 100%;
73   - flex-grow: 1;
  74 + min-width: 0;
  75 + flex: 1;
74 76 align-items: center;
75 77 }
76 78  
77   - &__header--light {
78   - background: @white;
79   - border-bottom: 1px solid @header-light-bottom-border-color;
  79 + &-action {
  80 + display: flex;
  81 + min-width: 200px;
  82 + padding-right: 12px;
  83 + align-items: center;
80 84  
81   - .layout-header__menu {
82   - height: calc(@header-height - 1px);
  85 + &__item {
  86 + display: flex;
  87 + height: @header-height;
  88 + padding: 0 2px;
  89 + font-size: 1.2em;
  90 + cursor: pointer;
  91 + align-items: center;
83 92  
84   - .ant-menu-submenu {
  93 + .ant-badge {
85 94 height: @header-height;
86 95 line-height: @header-height;
87 96 }
88   - }
89   -
90   - .layout-breadcrumb {
91   - .ant-breadcrumb-link {
92   - color: @breadcrumb-item-normal-color;
93 97  
94   - a {
95   - color: @text-color-base;
96   -
97   - &:hover {
98   - color: @primary-color;
99   - }
100   - }
  98 + .ant-badge-dot {
  99 + top: 10px;
  100 + right: 2px;
101 101 }
  102 + }
102 103  
103   - .ant-breadcrumb-separator {
104   - color: @breadcrumb-item-normal-color;
105   - }
  104 + span[role='img'] {
  105 + padding: 0 8px;
106 106 }
  107 + }
107 108  
108   - .layout-header__logo {
109   - height: @header-height;
110   - color: @text-color-base;
  109 + &--light {
  110 + background: @white;
  111 + border-bottom: 1px solid @header-light-bottom-border-color;
111 112  
112   - img {
113   - width: @logo-width;
114   - height: @logo-width;
115   - margin-right: 6px;
116   - }
  113 + .@{header-prefix-cls}-logo {
  114 + color: @text-color-base;
117 115  
118 116 &:hover {
119 117 background: @header-light-bg-hover-color;
120 118 }
121 119 }
122 120  
123   - .layout-header__action {
124   - &-item {
  121 + .@{header-prefix-cls}-action {
  122 + &__item {
125 123 &:hover {
126 124 background: @header-light-bg-hover-color;
127 125 }
128 126  
129   - &.locale {
130   - padding: 0 10px;
  127 + .@{locale-prefix-cls} {
  128 + padding: 0 6px;
131 129 color: rgba(0, 0, 0, 0.65);
132 130 }
133 131 }
... ... @@ -137,134 +135,23 @@
137 135 color: @text-color-base;
138 136 }
139 137 }
140   -
141   - .layout-header__user-dropdown {
142   - &:hover {
143   - background: @header-light-bg-hover-color;
144   - }
145   - }
146   -
147   - .user-dropdown {
148   - &__name {
149   - color: @text-color-base;
150   - }
151   -
152   - &__desc {
153   - color: @header-light-desc-color;
154   - }
155   - }
156 138 }
157 139  
158   - &__header--dark {
  140 + &--dark {
159 141 background: @header-dark-bg-color;
160 142  
161   - .layout-header__action {
162   - &-item {
163   - &:hover {
164   - background: @header-dark-bg-hover-color;
165   - }
166   - }
167   - }
168   -
169   - .layout-header__logo {
170   - height: @header-height;
171   -
172   - img {
173   - width: @logo-width;
174   - height: @logo-width;
175   - margin-right: 10px;
176   - }
177   -
178   - &:hover {
179   - background: @header-dark-bg-hover-color;
180   - }
181   - }
182   -
183   - .layout-header__user-dropdown {
  143 + .@{header-prefix-cls}-logo {
184 144 &:hover {
185 145 background: @header-dark-bg-hover-color;
186 146 }
187 147 }
188 148  
189   - .layout-breadcrumb {
190   - .ant-breadcrumb-link {
191   - color: rgba(255, 255, 255, 0.6);
192   -
193   - a {
194   - color: rgba(255, 255, 255, 0.8);
195   -
196   - &:hover {
197   - color: @white;
198   - }
  149 + .@{header-prefix-cls}-action {
  150 + &__item {
  151 + &:hover {
  152 + background: @header-dark-bg-hover-color;
199 153 }
200 154 }
201   -
202   - .ant-breadcrumb-separator,
203   - .anticon {
204   - color: rgba(255, 255, 255, 0.8);
205   - }
206   - }
207   - }
208   -
209   - &__logo {
210   - padding: 0 10px;
211   - }
212   -
213   - &__bread {
214   - display: none;
215   - flex: 1;
216   - }
217   -
218   - &__action {
219   - display: flex;
220   - align-items: center;
221   -
222   - &-item {
223   - display: flex;
224   - height: @header-height;
225   - padding: 0 2px;
226   - font-size: 1.2em;
227   - cursor: pointer;
228   - align-items: center;
229   - }
230   -
231   - &-icon {
232   - padding: 0 8px;
233   - }
234   - }
235   -
236   - &__menu {
237   - margin-left: 4px;
238   - overflow: hidden;
239   - align-items: center;
240   - }
241   -
242   - &__user-dropdown {
243   - height: @header-height;
244   - padding: 0 0 0 10px;
245   - }
246   -
247   - .user-dropdown {
248   - display: flex;
249   - padding-right: 10px;
250   - font-size: 12px;
251   - cursor: pointer;
252   - align-items: center;
253   -
254   - img {
255   - width: 26px;
256   - height: 26px;
257   - margin-right: 12px;
258   - }
259   -
260   - &__header {
261   - border-radius: 50%;
262 155 }
263 156 }
264 157 }
265   -
266   -.app-layout-header-user-dropdown-overlay {
267   - .ant-dropdown-menu-item {
268   - min-width: 160px;
269   - }
270   -}
... ...
src/layouts/default/header/index.vue 0 → 100644
  1 +<template>
  2 + <Header :class="getHeaderClass">
  3 + <!-- left start -->
  4 + <div :class="`${prefixCls}-left`">
  5 + <!-- logo -->
  6 + <AppLogo v-if="getShowHeaderLogo" :class="`${prefixCls}-logo`" :theme="getHeaderTheme" />
  7 +
  8 + <LayoutTrigger
  9 + v-if="getShowContent && getShowHeaderTrigger"
  10 + :theme="getHeaderTheme"
  11 + :sider="false"
  12 + />
  13 + <LayoutBreadcrumb
  14 + v-if="getShowContent && getShowBread && !getIsMobile"
  15 + :theme="getHeaderTheme"
  16 + />
  17 + </div>
  18 + <!-- left end -->
  19 +
  20 + <!-- menu start -->
  21 + <div :class="`${prefixCls}-menu`" v-if="getShowTopMenu && !getIsMobile">
  22 + <LayoutMenu
  23 + :isHorizontal="true"
  24 + :theme="getHeaderTheme"
  25 + :splitType="getSplitType"
  26 + :menuMode="getMenuMode"
  27 + />
  28 + </div>
  29 + <!-- menu-end -->
  30 +
  31 + <!-- action -->
  32 + <div :class="`${prefixCls}-action`">
  33 + <AppSearch v-if="!getIsMobile" :class="`${prefixCls}-action__item`" />
  34 +
  35 + <ErrorAction v-if="getUseErrorHandle && !getIsMobile" :class="`${prefixCls}-action__item`" />
  36 +
  37 + <LockItem v-if="getUseLockPage && !getIsMobile" :class="`${prefixCls}-action__item`" />
  38 +
  39 + <Notify v-if="getShowNotice && !getIsMobile" :class="`${prefixCls}-action__item`" />
  40 +
  41 + <FullScreen v-if="getShowFullScreen && !getIsMobile" :class="`${prefixCls}-action__item`" />
  42 +
  43 + <UserDropDown :theme="getHeaderTheme" />
  44 +
  45 + <AppLocalePicker
  46 + v-if="getShowLocale"
  47 + :reload="true"
  48 + :showText="false"
  49 + :class="`${prefixCls}-action__item`"
  50 + />
  51 + </div>
  52 + </Header>
  53 +</template>
  54 +<script lang="ts">
  55 + import { defineComponent, unref, computed } from 'vue';
  56 +
  57 + import { propTypes } from '/@/utils/propTypes';
  58 +
  59 + import { Layout } from 'ant-design-vue';
  60 + import { AppLogo } from '/@/components/Application';
  61 + import LayoutMenu from '../menu';
  62 + import LayoutTrigger from '../trigger/index.vue';
  63 +
  64 + import { AppSearch } from '/@/components/Application';
  65 +
  66 + import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
  67 + import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
  68 + import { useRootSetting } from '/@/hooks/setting/useRootSetting';
  69 + import { useLocaleSetting } from '/@/hooks/setting/useLocaleSetting';
  70 +
  71 + import { MenuModeEnum, MenuSplitTyeEnum } from '/@/enums/menuEnum';
  72 + import { AppLocalePicker } from '/@/components/Application';
  73 +
  74 + import {
  75 + UserDropDown,
  76 + LayoutBreadcrumb,
  77 + FullScreen,
  78 + Notify,
  79 + LockItem,
  80 + ErrorAction,
  81 + } from './components';
  82 + import { useAppInject } from '/@/hooks/web/useAppInject';
  83 + import { useDesign } from '/@/hooks/web/useDesign';
  84 +
  85 + export default defineComponent({
  86 + name: 'LayoutHeader',
  87 + components: {
  88 + Header: Layout.Header,
  89 + AppLogo,
  90 + LayoutTrigger,
  91 + LayoutBreadcrumb,
  92 + LayoutMenu,
  93 + UserDropDown,
  94 + AppLocalePicker,
  95 + FullScreen,
  96 + Notify,
  97 + LockItem,
  98 + AppSearch,
  99 + ErrorAction,
  100 + },
  101 + props: {
  102 + fixed: propTypes.bool,
  103 + },
  104 + setup(props) {
  105 + const { prefixCls } = useDesign('layout-header');
  106 + const { getShowTopMenu, getShowHeaderTrigger, getSplit } = useMenuSetting();
  107 + const { getShowLocale } = useLocaleSetting();
  108 + const { getUseErrorHandle } = useRootSetting();
  109 +
  110 + const {
  111 + getHeaderTheme,
  112 + getUseLockPage,
  113 + getShowFullScreen,
  114 + getShowNotice,
  115 + getShowContent,
  116 + getShowBread,
  117 + getShowHeaderLogo,
  118 + } = useHeaderSetting();
  119 +
  120 + const { getIsMobile } = useAppInject();
  121 +
  122 + const getHeaderClass = computed(() => {
  123 + const theme = unref(getHeaderTheme);
  124 + return [
  125 + prefixCls,
  126 + { [`${prefixCls}--fixed`]: props.fixed, [`${prefixCls}--${theme}`]: theme },
  127 + ];
  128 + });
  129 +
  130 + const getSplitType = computed(() => {
  131 + return unref(getSplit) ? MenuSplitTyeEnum.TOP : MenuSplitTyeEnum.NONE;
  132 + });
  133 +
  134 + const getMenuMode = computed(() => {
  135 + return unref(getSplit) ? MenuModeEnum.HORIZONTAL : null;
  136 + });
  137 +
  138 + return {
  139 + prefixCls,
  140 + getHeaderClass,
  141 + getShowHeaderLogo,
  142 + getHeaderTheme,
  143 + getShowHeaderTrigger,
  144 + getIsMobile,
  145 + getShowBread,
  146 + getShowContent,
  147 + getSplitType,
  148 + getMenuMode,
  149 + getShowTopMenu,
  150 + getShowLocale,
  151 + getShowFullScreen,
  152 + getShowNotice,
  153 + getUseLockPage,
  154 + getUseErrorHandle,
  155 + };
  156 + },
  157 + });
  158 +</script>
  159 +<style lang="less">
  160 + @import './index.less';
  161 +</style>
... ...
src/layouts/default/index.vue
... ... @@ -18,7 +18,7 @@
18 18 import { Layout } from 'ant-design-vue';
19 19 import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
20 20  
21   - import LayoutHeader from './header/LayoutHeader';
  21 + import LayoutHeader from './header/index.vue';
22 22 import LayoutContent from './content/index.vue';
23 23 import LayoutSideBar from './sider';
24 24 import LayoutMultipleHeader from './header/LayoutMultipleHeader';
... ... @@ -29,8 +29,6 @@
29 29 import { createLayoutContext } from './useLayoutContext';
30 30  
31 31 import { registerGlobComp } from '/@/components/registerGlobComp';
32   - import { createBreakpointListen } from '/@/hooks/event/useBreakpoint';
33   - import { isMobile } from '/@/utils/is';
34 32  
35 33 export default defineComponent({
36 34 name: 'DefaultLayout',
... ... @@ -44,22 +42,17 @@
44 42 Layout,
45 43 },
46 44 setup() {
47   - const headerRef = ref<ComponentRef>(null);
48   - const isMobileRef = ref(false);
49   -
50   - const { prefixCls } = useDesign('default-layout');
51   -
52   - createLayoutContext({ fullHeader: headerRef, isMobile: isMobileRef });
53   -
54   - createBreakpointListen(() => {
55   - isMobileRef.value = isMobile();
56   - });
57   -
58 45 // ! Only register global components here
59 46 // ! Can reduce the size of the first screen code
60 47 // default layout It is loaded after login. So it won’t be packaged to the first screen
61 48 registerGlobComp();
62 49  
  50 + const headerRef = ref<ComponentRef>(null);
  51 +
  52 + const { prefixCls } = useDesign('default-layout');
  53 +
  54 + createLayoutContext({ fullHeader: headerRef });
  55 +
63 56 const { getShowFullHeaderRef } = useHeaderSetting();
64 57  
65 58 const { getShowSidebar } = useMenuSetting();
... ...
src/layouts/default/menu/index.tsx
... ... @@ -9,6 +9,7 @@ import { AppLogo } from &#39;/@/components/Application&#39;;
9 9 import { MenuModeEnum, MenuSplitTyeEnum } from '/@/enums/menuEnum';
10 10  
11 11 import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
  12 +import { ScrollContainer } from '/@/components/Container';
12 13  
13 14 import { useGo } from '/@/hooks/web/usePage';
14 15 import { useSplitMenu } from './useLayoutMenu';
... ... @@ -16,6 +17,7 @@ import { openWindow } from &#39;/@/utils&#39;;
16 17 import { propTypes } from '/@/utils/propTypes';
17 18 import { isUrl } from '/@/utils/is';
18 19 import { useRootSetting } from '/@/hooks/setting/useRootSetting';
  20 +import { CSSProperties } from 'vue';
19 21  
20 22 export default defineComponent({
21 23 name: 'LayoutMenu',
... ... @@ -53,12 +55,25 @@ export default defineComponent({
53 55 const getComputedMenuMode = computed(() => props.menuMode || unref(getMenuMode));
54 56  
55 57 const getComputedMenuTheme = computed(() => props.theme || unref(getMenuTheme));
56   - const showLogo = computed(() => unref(getShowLogo) && unref(getIsSidebarType));
57   - const appendClass = computed(() => props.splitType === MenuSplitTyeEnum.TOP);
  58 +
  59 + const getIsShowLogo = computed(() => unref(getShowLogo) && unref(getIsSidebarType));
  60 +
  61 + const getUseScroll = computed(() => {
  62 + return unref(getIsSidebarType) || props.splitType === MenuSplitTyeEnum.LEFT;
  63 + });
  64 +
  65 + const getWrapperStyle = computed(
  66 + (): CSSProperties => {
  67 + return {
  68 + height: `calc(100% - ${unref(getIsShowLogo) ? '48px' : '0px'})`,
  69 + };
  70 + }
  71 + );
58 72 /**
59 73 * click menu
60 74 * @param menu
61 75 */
  76 +
62 77 function handleMenuClick(path: string) {
63 78 go(path);
64 79 }
... ... @@ -76,7 +91,7 @@ export default defineComponent({
76 91 }
77 92  
78 93 function renderHeader() {
79   - if (!unref(showLogo)) return null;
  94 + if (!unref(getIsShowLogo)) return null;
80 95  
81 96 return (
82 97 <AppLogo
... ... @@ -87,7 +102,7 @@ export default defineComponent({
87 102 );
88 103 }
89 104  
90   - return () => {
  105 + function renderMenu() {
91 106 return (
92 107 <BasicMenu
93 108 beforeClickFn={beforeMenuClickFn}
... ... @@ -99,13 +114,22 @@ export default defineComponent({
99 114 items={unref(menusRef)}
100 115 accordion={unref(getAccordion)}
101 116 onMenuClick={handleMenuClick}
102   - appendClass={unref(appendClass)}
103   - showLogo={unref(showLogo)}
104   - >
105   - {{
106   - header: () => renderHeader(),
107   - }}
108   - </BasicMenu>
  117 + showLogo={unref(getIsShowLogo)}
  118 + />
  119 + );
  120 + }
  121 +
  122 + return () => {
  123 + return (
  124 + <>
  125 + {renderHeader()}
  126 + {unref(getUseScroll) ? (
  127 + <ScrollContainer style={unref(getWrapperStyle)}>{() => renderMenu()}</ScrollContainer>
  128 + ) : (
  129 + renderMenu()
  130 + )}
  131 + ;
  132 + </>
109 133 );
110 134 };
111 135 },
... ...
src/layouts/default/menu/useLayoutMenu.ts
... ... @@ -14,9 +14,7 @@ import { permissionStore } from &#39;/@/store/modules/permission&#39;;
14 14 export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) {
15 15 // Menu array
16 16 const menusRef = ref<Menu[]>([]);
17   -
18 17 const { currentRoute } = useRouter();
19   -
20 18 const { setMenuSetting, getIsHorizontal, getSplit } = useMenuSetting();
21 19  
22 20 const [throttleHandleSplitLeftMenu] = useThrottle(handleSplitLeftMenu, 50);
... ... @@ -25,9 +23,11 @@ export function useSplitMenu(splitType: Ref&lt;MenuSplitTyeEnum&gt;) {
25 23 () => unref(splitType) !== MenuSplitTyeEnum.LEFT && !unref(getIsHorizontal)
26 24 );
27 25  
28   - const splitLeft = computed(() => !unref(getSplit) || unref(splitType) !== MenuSplitTyeEnum.LEFT);
  26 + const getSplitLeft = computed(
  27 + () => !unref(getSplit) || unref(splitType) !== MenuSplitTyeEnum.LEFT
  28 + );
29 29  
30   - const spiltTop = computed(() => unref(splitType) === MenuSplitTyeEnum.TOP);
  30 + const getSpiltTop = computed(() => unref(splitType) === MenuSplitTyeEnum.TOP);
31 31  
32 32 const normalType = computed(() => {
33 33 return unref(splitType) === MenuSplitTyeEnum.NONE || !unref(getSplit);
... ... @@ -65,7 +65,7 @@ export function useSplitMenu(splitType: Ref&lt;MenuSplitTyeEnum&gt;) {
65 65  
66 66 // Handle left menu split
67 67 async function handleSplitLeftMenu(parentPath: string) {
68   - if (unref(splitLeft)) return;
  68 + if (unref(getSplitLeft)) return;
69 69  
70 70 // spilt mode left
71 71 const children = await getChildrenMenus(parentPath);
... ... @@ -88,7 +88,7 @@ export function useSplitMenu(splitType: Ref&lt;MenuSplitTyeEnum&gt;) {
88 88 }
89 89  
90 90 // split-top
91   - if (unref(spiltTop)) {
  91 + if (unref(getSpiltTop)) {
92 92 const shallowMenus = await getShallowMenus();
93 93  
94 94 menusRef.value = shallowMenus;
... ...
src/layouts/default/setting/SettingDrawer.tsx
... ... @@ -80,7 +80,7 @@ export default defineComponent({
80 80 getShowSearch,
81 81 } = useHeaderSetting();
82 82  
83   - const { getShowMultipleTab, getShowQuick } = useMultipleTabSetting();
  83 + const { getShowMultipleTab, getShowQuick, getShowRedo } = useMultipleTabSetting();
84 84  
85 85 const getShowMenuRef = computed(() => {
86 86 return unref(getShowMenu) && !unref(getIsHorizontal);
... ... @@ -247,6 +247,13 @@ export default defineComponent({
247 247 />
248 248  
249 249 <SwitchItem
  250 + title={t('layout.setting.tabsRedoBtn')}
  251 + event={HandlerEnum.TABS_SHOW_REDO}
  252 + def={unref(getShowRedo)}
  253 + disabled={!unref(getShowMultipleTab)}
  254 + />
  255 +
  256 + <SwitchItem
250 257 title={t('layout.setting.tabsQuickBtn')}
251 258 event={HandlerEnum.TABS_SHOW_QUICK}
252 259 def={unref(getShowQuick)}
... ...
src/layouts/default/setting/enum.ts
... ... @@ -31,6 +31,7 @@ export enum HandlerEnum {
31 31 HEADER_SEARCH,
32 32  
33 33 TABS_SHOW_QUICK,
  34 + TABS_SHOW_REDO,
34 35 TABS_SHOW,
35 36  
36 37 LOCK_TIME,
... ...
src/layouts/default/setting/handler.ts
... ... @@ -113,6 +113,8 @@ export function handler(event: HandlerEnum, value: any): DeepPartial&lt;ProjectConf
113 113  
114 114 case HandlerEnum.TABS_SHOW:
115 115 return { multiTabsSetting: { show: value } };
  116 + case HandlerEnum.TABS_SHOW_REDO:
  117 + return { multiTabsSetting: { showRedo: value } };
116 118  
117 119 // ============header==================
118 120 case HandlerEnum.HEADER_THEME:
... ...
src/layouts/default/sider/index.less
1 1 @import (reference) '../../../design/index.less';
  2 +@prefix-cls: ~'@{namespace}-layout-sideBar';
2 3  
3   -.layout-sidebar {
4   - // overflow: hidden;
  4 +.@{prefix-cls} {
  5 + z-index: @layout-sider-fixed-z-index;
5 6  
6   - &.fixed {
  7 + &--fixed {
7 8 position: fixed;
8 9 top: 0;
9 10 left: 0;
10 11 height: 100%;
11 12 }
12 13  
  14 + &--mix {
  15 + top: @header-height;
  16 + height: calc(100% - @header-height);
  17 + }
  18 +
13 19 &.ant-layout-sider-dark {
14 20 background: @sider-dark-bg-color;
15 21  
... ...
src/layouts/default/sider/index.tsx
1 1 import './index.less';
2 2  
3   -import { computed, defineComponent, ref, unref, watch, nextTick, CSSProperties } from 'vue';
  3 +import { computed, defineComponent, ref, unref, CSSProperties } from 'vue';
4 4  
5 5 import { Layout } from 'ant-design-vue';
6 6 import LayoutMenu from '../menu';
... ... @@ -8,14 +8,13 @@ import LayoutMenu from &#39;../menu&#39;;
8 8 import { MenuModeEnum, MenuSplitTyeEnum } from '/@/enums/menuEnum';
9 9  
10 10 import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
11   -import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
12 11 import { useTrigger, useDragLine, useSiderEvent } from './useLayoutSider';
13   -import { useLayoutContext } from '../useLayoutContext';
  12 +import { useAppInject } from '/@/hooks/web/useAppInject';
  13 +import { useDesign } from '/@/hooks/web/useDesign';
14 14  
15 15 export default defineComponent({
16 16 name: 'LayoutSideBar',
17 17 setup() {
18   - const topRef = ref(0);
19 18 const dragBarRef = ref<ElRef>(null);
20 19 const sideRef = ref<ElRef>(null);
21 20  
... ... @@ -27,22 +26,18 @@ export default defineComponent({
27 26 getRealWidth,
28 27 getMenuHidden,
29 28 getMenuFixed,
  29 + getIsMixMode,
30 30 } = useMenuSetting();
31 31  
32   - const { getShowFullHeaderRef, getUnFixedAndFull } = useHeaderSetting();
33   -
34   - const injectValue = useLayoutContext();
  32 + const { prefixCls } = useDesign('layout-sideBar');
35 33  
36 34 const { getTriggerAttr, getTriggerSlot } = useTrigger();
37 35  
  36 + const { getIsMobile } = useAppInject();
  37 +
38 38 const { renderDragLine } = useDragLine(sideRef, dragBarRef);
39 39  
40   - const {
41   - getCollapsedWidth,
42   - onBreakpointChange,
43   - onCollapseChange,
44   - onSiderClick,
45   - } = useSiderEvent();
  40 + const { getCollapsedWidth, onBreakpointChange, onCollapseChange } = useSiderEvent();
46 41  
47 42 const getMode = computed(() => {
48 43 return unref(getSplit) ? MenuModeEnum.INLINE : null;
... ... @@ -57,40 +52,16 @@ export default defineComponent({
57 52 });
58 53  
59 54 const getSiderClass = computed(() => {
60   - return {
61   - 'layout-sidebar': true,
62   - fixed: unref(getMenuFixed),
63   - hidden: !unref(showClassSideBarRef),
64   - };
  55 + return [
  56 + prefixCls,
  57 + {
  58 + [`${prefixCls}--fixed`]: unref(getMenuFixed),
  59 + hidden: !unref(showClassSideBarRef),
  60 + [`${prefixCls}--mix`]: unref(getIsMixMode),
  61 + },
  62 + ];
65 63 });
66 64  
67   - const getSiderStyle = computed(() => {
68   - const top = `${unref(topRef)}px`;
69   - if (!unref(getMenuFixed)) {
70   - return { top };
71   - }
72   - return {
73   - top,
74   - height: `calc(100% - ${top})`,
75   - };
76   - });
77   -
78   - watch(
79   - () => getShowFullHeaderRef.value,
80   - () => {
81   - topRef.value = 0;
82   - if (unref(getUnFixedAndFull)) return;
83   - nextTick(() => {
84   - const fullHeaderEl = unref(injectValue.fullHeader)?.$el;
85   - if (!fullHeaderEl) return;
86   - topRef.value = fullHeaderEl.offsetHeight;
87   - });
88   - },
89   - {
90   - immediate: true,
91   - }
92   - );
93   -
94 65 const getHiddenDomStyle = computed(
95 66 (): CSSProperties => {
96 67 const width = `${unref(getRealWidth)}px`;
... ... @@ -121,7 +92,7 @@ export default defineComponent({
121 92 return () => {
122 93 return (
123 94 <>
124   - {unref(getMenuFixed) && !unref(injectValue.isMobile) && (
  95 + {unref(getMenuFixed) && !unref(getIsMobile) && (
125 96 <div style={unref(getHiddenDomStyle)} class={{ hidden: !unref(showClassSideBarRef) }} />
126 97 )}
127 98 <Layout.Sider
... ... @@ -129,12 +100,10 @@ export default defineComponent({
129 100 breakpoint="lg"
130 101 collapsible
131 102 class={unref(getSiderClass)}
132   - style={unref(getSiderStyle)}
133 103 width={unref(getMenuWidth)}
134 104 collapsed={unref(getCollapsed)}
135 105 collapsedWidth={unref(getCollapsedWidth)}
136 106 theme={unref(getMenuTheme)}
137   - onClick={onSiderClick}
138 107 onCollapse={onCollapseChange}
139 108 onBreakpoint={onBreakpointChange}
140 109 {...unref(getTriggerAttr)}
... ...
src/layouts/default/sider/useLayoutSider.tsx
... ... @@ -16,7 +16,7 @@ export function useSiderEvent() {
16 16 const brokenRef = ref(false);
17 17 const collapseRef = ref(true);
18 18  
19   - const { setMenuSetting, getCollapsed, getMiniWidthNumber, getShowMenu } = useMenuSetting();
  19 + const { setMenuSetting, getCollapsed, getMiniWidthNumber } = useMenuSetting();
20 20  
21 21 const getCollapsedWidth = computed(() => {
22 22 return unref(brokenRef) ? 0 : unref(getMiniWidthNumber);
... ... @@ -36,12 +36,7 @@ export function useSiderEvent() {
36 36 brokenRef.value = broken;
37 37 }
38 38  
39   - function onSiderClick(e: ChangeEvent) {
40   - if (!e || !e.target || e.target.className !== 'basic-menu__content') return;
41   - if (!unref(getCollapsed) || !unref(getShowMenu)) return;
42   - setMenuSetting({ collapsed: false });
43   - }
44   - return { getCollapsedWidth, onCollapseChange, onBreakpointChange, onSiderClick };
  39 + return { getCollapsedWidth, onCollapseChange, onBreakpointChange };
45 40 }
46 41  
47 42 /**
... ...
src/layouts/default/tabs/components/TabContent.vue
... ... @@ -4,7 +4,7 @@
4 4 <span class="ml-1">{{ getTitle }}</span>
5 5 </div>
6 6  
7   - <span :class="`${prefixCls}__extra`" v-else>
  7 + <span :class="`${prefixCls}__extra-quick`" v-else @click="handleContext">
8 8 <RightOutlined />
9 9 </span>
10 10 </Dropdown>
... ...
src/layouts/default/tabs/components/TabRedo.vue 0 → 100644
  1 +<template>
  2 + <Tooltip :title="t('layout.multipleTab.tooltipRedo')" placement="bottom" :mouseEnterDelay="0.5">
  3 + <span :class="`${prefixCls}__extra-redo`" @click="handleRedo">
  4 + <RedoOutlined :spin="loading" />
  5 + </span>
  6 + </Tooltip>
  7 +</template>
  8 +<script lang="ts">
  9 + import { defineComponent, ref } from 'vue';
  10 + import { RedoOutlined } from '@ant-design/icons-vue';
  11 + import { useDesign } from '/@/hooks/web/useDesign';
  12 + import { Tooltip } from 'ant-design-vue';
  13 + import { useI18n } from '/@/hooks/web/useI18n';
  14 + import { useTabs } from '/@/hooks/web/useTabs';
  15 +
  16 + export default defineComponent({
  17 + name: 'TabContent',
  18 + components: { RedoOutlined, Tooltip },
  19 +
  20 + setup() {
  21 + const loading = ref(false);
  22 + const { prefixCls } = useDesign('multiple-tabs-content');
  23 + const { t } = useI18n();
  24 + const { refreshPage } = useTabs();
  25 +
  26 + async function handleRedo() {
  27 + loading.value = true;
  28 + await refreshPage();
  29 + setTimeout(() => {
  30 + loading.value = false;
  31 + // Animation execution time
  32 + }, 1000);
  33 + }
  34 + return { prefixCls, t, handleRedo, loading };
  35 + },
  36 + });
  37 +</script>
... ...
src/layouts/default/tabs/index.less
... ... @@ -34,30 +34,30 @@
34 34 border: 1px solid darken(@border-color-light, 6%);
35 35 transition: none;
36 36  
37   - &:not(.ant-tabs-tab-active)::after {
38   - position: absolute;
39   - bottom: -1px;
40   - left: 50%;
41   - width: 100%;
42   - height: 2px;
43   - background-color: @primary-color;
44   - content: '';
45   - opacity: 0;
46   - transform: translate(-50%, 0) scaleX(0);
47   - transform-origin: center;
48   - transition: none;
49   - }
  37 + // &:not(.ant-tabs-tab-active)::before {
  38 + // position: absolute;
  39 + // top: -1px;
  40 + // left: 50%;
  41 + // width: 100%;
  42 + // height: 2px;
  43 + // background-color: @primary-color;
  44 + // content: '';
  45 + // opacity: 0;
  46 + // transform: translate(-50%, 0) scaleX(0);
  47 + // transform-origin: center;
  48 + // transition: none;
  49 + // }
50 50  
51 51 &:hover {
52 52 .ant-tabs-close-x {
53 53 opacity: 1;
54 54 }
55 55  
56   - &:not(.ant-tabs-tab-active)::after {
57   - opacity: 1;
58   - transform: translate(-50%, 0) scaleX(1);
59   - transition: all 0.3s ease-in-out;
60   - }
  56 + // &:not(.ant-tabs-tab-active)::before {
  57 + // opacity: 1;
  58 + // transform: translate(-50%, 0) scaleX(1);
  59 + // transition: all 0.3s ease-in-out;
  60 + // }
61 61 }
62 62  
63 63 .ant-tabs-close-x {
... ... @@ -152,12 +152,13 @@
152 152 }
153 153  
154 154 &-content {
155   - &__extra {
  155 + &__extra-quick,
  156 + &__extra-redo {
156 157 display: inline-block;
157   - width: @multiple-height;
  158 + width: 36px;
158 159 height: @multiple-height;
159 160 line-height: @multiple-height;
160   - color: #999;
  161 + color: #666;
161 162 text-align: center;
162 163 cursor: pointer;
163 164 border-left: 1px solid #eee;
... ... @@ -171,6 +172,12 @@
171 172 }
172 173 }
173 174  
  175 + &__extra-redo {
  176 + span[role='img'] {
  177 + transform: rotate(0deg);
  178 + }
  179 + }
  180 +
174 181 &__info {
175 182 display: inline-block;
176 183 width: 100%;
... ...
src/layouts/default/tabs/index.vue
... ... @@ -17,14 +17,16 @@
17 17 </template>
18 18 </TabPane>
19 19 </template>
20   - <template #tabBarExtraContent>
21   - <QuickButton />
  20 +
  21 + <template #tabBarExtraContent v-if="getShowRedo || getShowQuick">
  22 + <TabRedo v-if="getShowRedo" />
  23 + <QuickButton v-if="getShowQuick" />
22 24 </template>
23 25 </Tabs>
24 26 </div>
25 27 </template>
26 28 <script lang="ts">
27   - import { defineComponent, watch, computed, unref, ref } from 'vue';
  29 + import { defineComponent, computed, unref, ref } from 'vue';
28 30  
29 31 import { Tabs } from 'ant-design-vue';
30 32 import TabContent from './components/TabContent.vue';
... ... @@ -38,61 +40,52 @@
38 40 import { REDIRECT_NAME } from '/@/router/constant';
39 41 import { useDesign } from '/@/hooks/web/useDesign';
40 42 import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
  43 + import { listenerLastChangeTab } from '/@/logics/mitt/tabChange';
  44 + import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
41 45  
42 46 export default defineComponent({
43 47 name: 'MultipleTabs',
44 48 components: {
45 49 QuickButton: createAsyncComponent(() => import('./components/QuickButton.vue')),
  50 + TabRedo: createAsyncComponent(() => import('./components/TabRedo.vue')),
46 51 Tabs,
47 52 TabPane: Tabs.TabPane,
48 53 TabContent,
49 54 },
50 55 setup() {
51 56 const affixTextList = initAffixTabs();
52   -
53 57 const activeKeyRef = ref('');
54 58  
55 59 useTabsDrag(affixTextList);
56 60 const { prefixCls } = useDesign('multiple-tabs');
57 61 const go = useGo();
  62 + const { getShowQuick, getShowRedo } = useMultipleTabSetting();
58 63  
59 64 const getTabsState = computed(() => tabStore.getTabsState);
60 65  
61   - const unClose = computed(() => {
62   - return getTabsState.value.length === 1;
63   - });
  66 + const unClose = computed(() => unref(getTabsState).length === 1);
64 67  
65 68 const getWrapClass = computed(() => {
66 69 return [
67 70 prefixCls,
68 71 {
69   - [`${prefixCls}--hide-close`]: unClose,
  72 + [`${prefixCls}--hide-close`]: unref(unClose),
70 73 },
71 74 ];
72 75 });
73 76  
74   - watch(
75   - () => tabStore.getLastChangeRouteState?.path,
76   - () => {
77   - if (tabStore.getLastChangeRouteState?.name === REDIRECT_NAME) {
78   - return;
79   - }
80   - const lastChangeRoute = unref(tabStore.getLastChangeRouteState);
81   - if (!lastChangeRoute || !userStore.getTokenState) return;
82   -
83   - const { path, fullPath } = lastChangeRoute;
84   - const p = fullPath || path;
85   -
86   - if (activeKeyRef.value !== p) {
87   - activeKeyRef.value = p;
88   - }
89   -
90   - tabStore.addTabAction(lastChangeRoute);
91   - },
92   - {
93   - immediate: true,
  77 + listenerLastChangeTab((route) => {
  78 + const { name } = route;
  79 + if (name === REDIRECT_NAME || !route || !userStore.getTokenState) return;
  80 +
  81 + const { path, fullPath } = route;
  82 + const p = fullPath || path;
  83 +
  84 + if (activeKeyRef.value !== p) {
  85 + activeKeyRef.value = p;
94 86 }
95   - );
  87 + tabStore.addTabAction(unref(route));
  88 + });
96 89  
97 90 function handleChange(activeKey: any) {
98 91 activeKeyRef.value = activeKey;
... ... @@ -114,6 +107,8 @@
114 107 handleChange,
115 108 activeKeyRef,
116 109 getTabsState,
  110 + getShowQuick,
  111 + getShowRedo,
117 112 };
118 113 },
119 114 });
... ...
src/layouts/default/tabs/useTabDropdown.ts
... ... @@ -10,7 +10,6 @@ import { useTabs } from &#39;/@/hooks/web/useTabs&#39;;
10 10 import { useI18n } from '/@/hooks/web/useI18n';
11 11 import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
12 12 import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
13   -import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
14 13  
15 14 const { t } = useI18n();
16 15  
... ... @@ -24,11 +23,8 @@ export function useTabDropdown(tabContentProps: TabContentProps) {
24 23  
25 24 const { getShowMenu, setMenuSetting } = useMenuSetting();
26 25 const { getShowHeader, setHeaderSetting } = useHeaderSetting();
27   - const { getShowQuick } = useMultipleTabSetting();
28 26  
29   - const isTabs = computed(() =>
30   - !unref(getShowQuick) ? true : tabContentProps.type === TabContentEnum.TAB_TYPE
31   - );
  27 + const isTabs = computed(() => tabContentProps.type === TabContentEnum.TAB_TYPE);
32 28  
33 29 const getCurrentTab = computed(
34 30 (): RouteLocationNormalized => {
... ...
src/layouts/default/useLayoutContext.ts
... ... @@ -3,7 +3,6 @@ import { createContext, useContext } from &#39;/@/hooks/core/useContext&#39;;
3 3  
4 4 export interface LayoutContextProps {
5 5 fullHeader: Ref<ComponentRef>;
6   - isMobile: Ref<boolean>;
7 6 }
8 7  
9 8 const key: InjectionKey<LayoutContextProps> = Symbol();
... ...
src/locales/lang/en/layout/header.ts
... ... @@ -7,7 +7,7 @@ export default {
7 7 tooltipErrorLog: 'Error log',
8 8 tooltipLock: 'Lock screen',
9 9 tooltipNotify: 'Notification',
10   - tooltipRedo: 'Refresh',
  10 +
11 11 tooltipEntryFull: 'Full Screen',
12 12 tooltipExitFull: 'Exit Full Screen',
13 13  
... ...
src/locales/lang/en/layout/multipleTab.ts
... ... @@ -7,4 +7,5 @@ export default {
7 7 closeAll: 'Close All',
8 8 putAway: 'PutAway',
9 9 unfold: 'Unfold',
  10 + tooltipRedo: 'Refresh',
10 11 };
... ...
src/locales/lang/en/layout/setting.ts
... ... @@ -53,6 +53,7 @@ export default {
53 53 breadcrumbIcon: 'Breadcrumbs Icon',
54 54 tabs: 'Tabs',
55 55 tabsQuickBtn: 'Tabs quick button',
  56 + tabsRedoBtn: 'Tabs redo button',
56 57 sidebar: 'Sidebar',
57 58 header: 'Header',
58 59 footer: 'Footer',
... ...
src/locales/lang/zh_CN/layout/header.ts
... ... @@ -8,7 +8,7 @@ export default {
8 8 tooltipErrorLog: '错误日志',
9 9 tooltipLock: '锁定屏幕',
10 10 tooltipNotify: '消息通知',
11   - tooltipRedo: '刷新',
  11 +
12 12 tooltipEntryFull: '全屏',
13 13 tooltipExitFull: '退出全屏',
14 14  
... ...
src/locales/lang/zh_CN/layout/multipleTab.ts
... ... @@ -7,4 +7,5 @@ export default {
7 7 closeAll: '关闭全部',
8 8 putAway: '收起',
9 9 unfold: '展开',
  10 + tooltipRedo: '刷新',
10 11 };
... ...
src/locales/lang/zh_CN/layout/setting.ts
... ... @@ -52,6 +52,7 @@ export default {
52 52 breadcrumbIcon: '面包屑图标',
53 53 tabs: '标签页',
54 54 tabsQuickBtn: '标签页快捷按钮',
  55 + tabsRedoBtn: '标签页刷新按钮',
55 56 sidebar: '左侧菜单',
56 57 header: '顶栏',
57 58 footer: '页脚',
... ...
src/logics/mitt/tabChange.ts 0 → 100644
  1 +/**
  2 + * Used to monitor routing changes to change the status of menus and tabs. There is no need to monitor the route, because the route status change is affected by the page rendering time, which will be slow
  3 + */
  4 +
  5 +import Mitt from '/@/utils/mitt';
  6 +import type { RouteLocationNormalized } from 'vue-router';
  7 +import { getRoute } from '/@/router/helper/routeHelper';
  8 +
  9 +const mitt = new Mitt();
  10 +
  11 +const key = Symbol();
  12 +
  13 +let lastChangeTab: RouteLocationNormalized;
  14 +
  15 +export function setLastChangeTab(lastChangeRoute: RouteLocationNormalized) {
  16 + mitt.emit(key, getRoute(lastChangeRoute));
  17 + lastChangeTab = getRoute(lastChangeRoute);
  18 +}
  19 +
  20 +export function listenerLastChangeTab(
  21 + callback: (route: RouteLocationNormalized) => void,
  22 + immediate = true
  23 +) {
  24 + mitt.on(key, callback);
  25 + if (immediate) {
  26 + callback(lastChangeTab);
  27 + }
  28 +}
... ...
src/router/guard/index.ts
... ... @@ -8,13 +8,12 @@ import { createPageLoadingGuard } from &#39;./pageLoadingGuard&#39;;
8 8  
9 9 import { useGlobSetting, useProjectSetting } from '/@/hooks/setting';
10 10  
11   -import { getRoute } from '/@/router/helper/routeHelper';
12 11 import { setTitle } from '/@/utils/browser';
13 12 import { AxiosCanceler } from '/@/utils/http/axios/axiosCancel';
14 13  
15   -import { tabStore } from '/@/store/modules/tab';
16 14 import { useI18n } from '/@/hooks/web/useI18n';
17 15 import { REDIRECT_NAME } from '/@/router/constant';
  16 +import { setLastChangeTab } from '/@/logics/mitt/tabChange';
18 17  
19 18 const { closeMessageOnSwitch, removeAllHttpPending } = useProjectSetting();
20 19 const globSetting = useGlobSetting();
... ... @@ -35,8 +34,7 @@ export function createGuard(router: Router) {
35 34 router.beforeEach(async (to) => {
36 35 to.meta.loaded = !!loadedPageMap.get(to.path);
37 36 // Notify routing changes
38   - tabStore.commitLastChangeRouteState(getRoute(to));
39   -
  37 + setLastChangeTab(to);
40 38 try {
41 39 if (closeMessageOnSwitch) {
42 40 Modal.destroyAll();
... ...
src/router/menus/index.ts
... ... @@ -68,6 +68,7 @@ export async function getCurrentParentPath(currentPath: string) {
68 68 export async function getShallowMenus(): Promise<Menu[]> {
69 69 const menus = await getAsyncMenus();
70 70 const routes = router.getRoutes();
  71 +
71 72 const shallowMenuList = menus.map((item) => ({ ...item, children: undefined }));
72 73 return !isBackMode() ? shallowMenuList.filter(basicFilter(routes)) : shallowMenuList;
73 74 }
... ...
src/settings/projectSetting.ts
... ... @@ -62,8 +62,7 @@ const setting: ProjectConfig = {
62 62 theme: ThemeEnum.LIGHT,
63 63 // Whether to enable the lock screen function
64 64 useLockPage: true,
65   - // Whether to show the refresh button
66   - showRedo: true,
  65 +
67 66 // Whether to show the full screen button
68 67 showFullScreen: true,
69 68 // Whether to show the document button
... ... @@ -117,6 +116,9 @@ const setting: ProjectConfig = {
117 116 canDrag: true,
118 117 // Turn on quick actions
119 118 showQuick: true,
  119 +
  120 + // Whether to show the refresh button
  121 + showRedo: true,
120 122 },
121 123  
122 124 // Transition Setting
... ...
src/store/modules/tab.ts
... ... @@ -5,7 +5,6 @@ import { Action, Module, Mutation, VuexModule, getModule } from &#39;vuex-module-dec
5 5 import { hotModuleUnregisterModule } from '/@/utils/helper/vuexHelper';
6 6  
7 7 import { PageEnum } from '/@/enums/pageEnum';
8   -import { userStore } from './user';
9 8  
10 9 import store from '/@/store';
11 10 import router from '/@/router';
... ... @@ -13,8 +12,7 @@ import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from &#39;/@/router/constant&#39;;
13 12 import { RouteLocationNormalized, RouteLocationRaw } from 'vue-router';
14 13 import { getRoute } from '/@/router/helper/routeHelper';
15 14 import { useGo, useRedo } from '/@/hooks/web/usePage';
16   -
17   -// declare namespace TabsStore {
  15 +import { cloneDeep } from 'lodash-es';
18 16  
19 17 const NAME = 'tab';
20 18  
... ... @@ -34,19 +32,12 @@ class Tab extends VuexModule {
34 32 // tab list
35 33 tabsState: RouteLocationNormalized[] = [];
36 34  
37   - // Last route change
38   - lastChangeRouteState: RouteLocationNormalized | null = null;
39   -
40 35 lastDragEndIndexState = 0;
41 36  
42 37 get getTabsState() {
43 38 return this.tabsState;
44 39 }
45 40  
46   - get getLastChangeRouteState() {
47   - return this.lastChangeRouteState;
48   - }
49   -
50 41 get getCurrentTab(): RouteLocationNormalized {
51 42 const route = unref(router.currentRoute);
52 43 return this.tabsState.find((item) => item.path === route.path)!;
... ... @@ -61,12 +52,6 @@ class Tab extends VuexModule {
61 52 }
62 53  
63 54 @Mutation
64   - commitLastChangeRouteState(route: RouteLocationNormalized): void {
65   - if (!userStore.getTokenState) return;
66   - this.lastChangeRouteState = route;
67   - }
68   -
69   - @Mutation
70 55 commitClearCache(): void {
71 56 this.cachedMapState = new Map();
72 57 }
... ... @@ -152,7 +137,7 @@ class Tab extends VuexModule {
152 137 this.tabsState.splice(updateIndex, 1, curTab);
153 138 return;
154 139 }
155   - this.tabsState.push(route);
  140 + this.tabsState = cloneDeep([...this.tabsState, route]);
156 141 }
157 142  
158 143 /**
... ... @@ -210,7 +195,7 @@ class Tab extends VuexModule {
210 195 }
211 196  
212 197 @Mutation
213   - commitRedoPage() {
  198 + async commitRedoPage() {
214 199 const route = router.currentRoute.value;
215 200 for (const [key, value] of this.cachedMapState) {
216 201 const index = value.findIndex((item) => item === (route.name as string));
... ... @@ -225,7 +210,7 @@ class Tab extends VuexModule {
225 210 this.cachedMapState.set(key, value);
226 211 }
227 212 const redo = useRedo();
228   - redo();
  213 + await redo();
229 214 }
230 215  
231 216 @Action
... ...
src/types/config.d.ts
... ... @@ -27,6 +27,9 @@ export interface MultiTabsSetting {
27 27 // 开启快速操作
28 28 showQuick: boolean;
29 29 canDrag: boolean;
  30 +
  31 + // 显示刷新按钮
  32 + showRedo: boolean;
30 33 }
31 34  
32 35 export interface HeaderSetting {
... ... @@ -34,8 +37,7 @@ export interface HeaderSetting {
34 37 fixed: boolean;
35 38 show: boolean;
36 39 theme: ThemeEnum;
37   - // 显示刷新按钮
38   - showRedo: boolean;
  40 +
39 41 // 显示全屏按钮
40 42 showFullScreen: boolean;
41 43 // 开启全屏功能
... ...
src/utils/mitt.ts
... ... @@ -6,13 +6,13 @@
6 6 * @returns {Function} The function's instance
7 7 */
8 8 export default class Mitt {
9   - private cache: Map<string, Array<(data: any) => void>>;
  9 + private cache: Map<string | Symbol, Array<(...data: any) => void>>;
10 10 constructor(all = []) {
11 11 // A Map of event names to registered handler functions.
12 12 this.cache = new Map(all);
13 13 }
14 14  
15   - once(type: string, handler: Fn) {
  15 + once(type: string | Symbol, handler: Fn) {
16 16 const decor = (...args: any[]) => {
17 17 handler && handler.apply(this, args);
18 18 this.off(type, decor);
... ... @@ -27,7 +27,7 @@ export default class Mitt {
27 27 * @param {string|symbol} type Type of event to listen for, or `"*"` for all events
28 28 * @param {Function} handler Function to call in response to given event
29 29 */
30   - on(type: string, handler: Fn) {
  30 + on(type: string | Symbol, handler: Fn) {
31 31 const handlers = this.cache.get(type);
32 32 const added = handlers && handlers.push(handler);
33 33 if (!added) {
... ... @@ -41,7 +41,7 @@ export default class Mitt {
41 41 * @param {string|symbol} type Type of event to unregister `handler` from, or `"*"`
42 42 * @param {Function} handler Handler function to remove
43 43 */
44   - off(type: string, handler: Fn) {
  44 + off(type: string | Symbol, handler: Fn) {
45 45 const handlers = this.cache.get(type);
46 46 if (handlers) {
47 47 handlers.splice(handlers.indexOf(handler) >>> 0, 1);
... ... @@ -57,7 +57,7 @@ export default class Mitt {
57 57 * @param {string|symbol} type The event type to invoke
58 58 * @param {*} [evt] Any value (object is recommended and powerful), passed to each handler
59 59 */
60   - emit(type: string, evt: any) {
  60 + emit(type: string | Symbol, evt: any) {
61 61 for (const handler of (this.cache.get(type) || []).slice()) handler(evt);
62 62 for (const handler of (this.cache.get('*') || []).slice()) handler(type, evt);
63 63 }
... ...
src/views/sys/lock/LockPage.vue
... ... @@ -144,7 +144,7 @@
144 144 right: 0;
145 145 bottom: 0;
146 146 left: 0;
147   - z-index: 3000;
  147 + z-index: @lock-page-z-index;
148 148 display: flex;
149 149 width: 100vw;
150 150 height: 100vh;
... ...