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