Commit a65ad9edd56994ae79b91288e36c8306e2c23057
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
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
src/components/Application/src/useAppContext.ts
@@ -3,6 +3,8 @@ import { createContext, useContext } from '/@/hooks/core/useContext'; | @@ -3,6 +3,8 @@ import { createContext, useContext } from '/@/hooks/core/useContext'; | ||
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
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
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) => void) | @@ -54,8 +57,8 @@ export function createBreakpointListen(fn?: (opt: CreateCallbackParams) => 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) => void) | @@ -65,12 +68,14 @@ export function createBreakpointListen(fn?: (opt: CreateCallbackParams) => 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 'vue'; | @@ -4,7 +4,6 @@ import { computed, unref } from 'vue'; | ||
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 '/@/hooks/web/useFullContent'; | @@ -12,7 +11,6 @@ import { useFullContent } from '/@/hooks/web/useFullContent'; | ||
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(() => unref(getHeaderSetting).bgColor); | @@ -53,8 +51,6 @@ const getHeaderBgColor = computed(() => 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(() => { | @@ -65,6 +65,10 @@ const getIsHorizontal = computed(() => { | ||
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(() => unref(getMultipleTabSetting).show); | @@ -10,6 +10,8 @@ const getShowMultipleTab = computed(() => 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
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 './index.less'; | @@ -3,38 +3,19 @@ import './index.less'; | ||
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 '/@/enums/menuEnum'; | @@ -49,8 +30,10 @@ import { MenuModeEnum, MenuSplitTyeEnum } from '/@/enums/menuEnum'; | ||
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 './LayoutMultipleHeader.less'; | @@ -2,7 +2,7 @@ import './LayoutMultipleHeader.less'; | ||
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 '/@/hooks/setting/useMenuSetting'; | @@ -10,6 +10,7 @@ import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; | ||
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 '/@/components/Application'; | @@ -9,6 +9,7 @@ import { AppLogo } from '/@/components/Application'; | ||
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 '/@/utils'; | @@ -16,6 +17,7 @@ import { openWindow } from '/@/utils'; | ||
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 '/@/store/modules/permission'; | @@ -14,9 +14,7 @@ import { permissionStore } from '/@/store/modules/permission'; | ||
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<MenuSplitTyeEnum>) { | @@ -25,9 +23,11 @@ export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) { | ||
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<MenuSplitTyeEnum>) { | @@ -65,7 +65,7 @@ export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) { | ||
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<MenuSplitTyeEnum>) { | @@ -88,7 +88,7 @@ export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) { | ||
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
src/layouts/default/setting/handler.ts
@@ -113,6 +113,8 @@ export function handler(event: HandlerEnum, value: any): DeepPartial<ProjectConf | @@ -113,6 +113,8 @@ export function handler(event: HandlerEnum, value: any): DeepPartial<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 '../menu'; | @@ -8,14 +8,13 @@ import LayoutMenu from '../menu'; | ||
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 '/@/hooks/web/useTabs'; | @@ -10,7 +10,6 @@ import { useTabs } from '/@/hooks/web/useTabs'; | ||
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 '/@/hooks/core/useContext'; | @@ -3,7 +3,6 @@ import { createContext, useContext } from '/@/hooks/core/useContext'; | ||
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
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
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 './pageLoadingGuard'; | @@ -8,13 +8,12 @@ import { createPageLoadingGuard } from './pageLoadingGuard'; | ||
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 'vuex-module-dec | @@ -5,7 +5,6 @@ import { Action, Module, Mutation, VuexModule, getModule } from '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 '/@/router/constant'; | @@ -13,8 +12,7 @@ import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '/@/router/constant'; | ||
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