Commit cbcd9098671e2fff9152976b7e9e815fea29d074
1 parent
f81c4019
wip(menu): perf menu
Showing
13 changed files
with
411 additions
and
351 deletions
src/components/Application/src/AppLogo.vue
@@ -66,8 +66,9 @@ | @@ -66,8 +66,9 @@ | ||
66 | .@{prefix-cls} { | 66 | .@{prefix-cls} { |
67 | display: flex; | 67 | display: flex; |
68 | align-items: center; | 68 | align-items: center; |
69 | - padding-left: 12px; | 69 | + padding-left: 7px; |
70 | cursor: pointer; | 70 | cursor: pointer; |
71 | + transition: all 0.2s ease; | ||
71 | 72 | ||
72 | &.collapsed-show-title { | 73 | &.collapsed-show-title { |
73 | padding-left: 20px; | 74 | padding-left: 20px; |
src/components/Menu/index.ts
@@ -2,6 +2,8 @@ import { withInstall } from '../util'; | @@ -2,6 +2,8 @@ import { withInstall } from '../util'; | ||
2 | 2 | ||
3 | import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; | 3 | import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; |
4 | 4 | ||
5 | -export const BasicMenu = createAsyncComponent(() => import('./src/BasicMenu'), { loading: false }); | 5 | +export const BasicMenu = createAsyncComponent(() => import('./src/BasicMenu.vue'), { |
6 | + loading: false, | ||
7 | +}); | ||
6 | 8 | ||
7 | withInstall(BasicMenu); | 9 | withInstall(BasicMenu); |
src/components/Menu/src/BasicMenu.tsx renamed to src/components/Menu/src/BasicMenu.vue
1 | -import './index.less'; | ||
2 | - | ||
3 | -import type { MenuState } from './types'; | ||
4 | -import type { Menu as MenuType } from '/@/router/types'; | ||
5 | - | ||
6 | -import { | ||
7 | - computed, | ||
8 | - defineComponent, | ||
9 | - unref, | ||
10 | - reactive, | ||
11 | - watch, | ||
12 | - toRefs, | ||
13 | - ComputedRef, | ||
14 | - ref, | ||
15 | - CSSProperties, | ||
16 | -} from 'vue'; | ||
17 | -import { Menu } from 'ant-design-vue'; | ||
18 | -import MenuContent from './MenuContent'; | ||
19 | -// import { ScrollContainer } from '/@/components/Container'; | ||
20 | -// import { BasicArrow } from '/@/components/Basic'; | ||
21 | - | ||
22 | -import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum'; | ||
23 | -import { ThemeEnum } from '/@/enums/appEnum'; | ||
24 | - | ||
25 | -import { appStore } from '/@/store/modules/app'; | ||
26 | - | ||
27 | -import { useOpenKeys } from './useOpenKeys'; | ||
28 | -import { useRouter } from 'vue-router'; | ||
29 | - | ||
30 | -import { isFunction } from '/@/utils/is'; | ||
31 | -import { getSlot } from '/@/utils/helper/tsxHelper'; | ||
32 | -import { menuHasChildren } from './helper'; | ||
33 | -import { getCurrentParentPath } from '/@/router/menus'; | ||
34 | - | ||
35 | -import { basicProps } from './props'; | ||
36 | -import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; | ||
37 | -import { REDIRECT_NAME } from '/@/router/constant'; | ||
38 | -import { tabStore } from '/@/store/modules/tab'; | ||
39 | -import { useDesign } from '/@/hooks/web/useDesign'; | ||
40 | -export default defineComponent({ | ||
41 | - name: 'BasicMenu', | ||
42 | - props: basicProps, | ||
43 | - emits: ['menuClick'], | ||
44 | - setup(props, { slots, emit }) { | ||
45 | - const currentParentPath = ref(''); | ||
46 | - const isClickGo = ref(false); | ||
47 | - | ||
48 | - const menuState = reactive<MenuState>({ | ||
49 | - defaultSelectedKeys: [], | ||
50 | - mode: props.mode, | ||
51 | - theme: computed(() => props.theme) as ComputedRef<ThemeEnum>, | ||
52 | - openKeys: [], | ||
53 | - selectedKeys: [], | ||
54 | - collapsedOpenKeys: [], | ||
55 | - }); | ||
56 | - | ||
57 | - const { prefixCls } = useDesign('basic-menu'); | ||
58 | - | ||
59 | - const { items, mode, accordion } = toRefs(props); | ||
60 | - | ||
61 | - const { getCollapsed, getIsHorizontal, getTopMenuAlign, getSplit } = useMenuSetting(); | 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> | ||
30 | +</template> | ||
31 | +<script lang="ts"> | ||
32 | + import type { MenuState } from './types'; | ||
33 | + | ||
34 | + import { | ||
35 | + computed, | ||
36 | + defineComponent, | ||
37 | + unref, | ||
38 | + reactive, | ||
39 | + watch, | ||
40 | + toRefs, | ||
41 | + ref, | ||
42 | + CSSProperties, | ||
43 | + } from 'vue'; | ||
44 | + import { Menu } from 'ant-design-vue'; | ||
45 | + import BasicSubMenuItem from './components/BasicSubMenuItem.vue'; | ||
46 | + import { ScrollContainer } from '/@/components/Container'; | ||
47 | + | ||
48 | + import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum'; | ||
49 | + | ||
50 | + import { appStore } from '/@/store/modules/app'; | ||
51 | + | ||
52 | + import { useOpenKeys } from './useOpenKeys'; | ||
53 | + import { useRouter } from 'vue-router'; | ||
54 | + | ||
55 | + import { isFunction } from '/@/utils/is'; | ||
56 | + import { getCurrentParentPath } from '/@/router/menus'; | ||
57 | + | ||
58 | + import { basicProps } from './props'; | ||
59 | + import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; | ||
60 | + import { REDIRECT_NAME } from '/@/router/constant'; | ||
61 | + import { tabStore } from '/@/store/modules/tab'; | ||
62 | + import { useDesign } from '/@/hooks/web/useDesign'; | ||
63 | + // import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; | ||
64 | + | ||
65 | + export default defineComponent({ | ||
66 | + name: 'BasicMenu', | ||
67 | + components: { | ||
68 | + Menu, | ||
69 | + ScrollContainer, | ||
70 | + BasicSubMenuItem, | ||
71 | + // BasicSubMenuItem: createAsyncComponent(() => import('./components/BasicSubMenuItem.vue')), | ||
72 | + }, | ||
73 | + props: basicProps, | ||
74 | + emits: ['menuClick'], | ||
75 | + setup(props, { emit }) { | ||
76 | + const currentParentPath = ref(''); | ||
77 | + const isClickGo = ref(false); | ||
78 | + | ||
79 | + const menuState = reactive<MenuState>({ | ||
80 | + defaultSelectedKeys: [], | ||
81 | + openKeys: [], | ||
82 | + selectedKeys: [], | ||
83 | + collapsedOpenKeys: [], | ||
84 | + }); | ||
85 | + | ||
86 | + const { prefixCls } = useDesign('basic-menu'); | ||
87 | + const { items, mode, accordion } = toRefs(props); | ||
88 | + | ||
89 | + const { getCollapsed, getIsHorizontal, getTopMenuAlign, getSplit } = useMenuSetting(); | ||
90 | + | ||
91 | + const { currentRoute } = useRouter(); | ||
92 | + | ||
93 | + const { handleOpenChange, setOpenKeys, getOpenKeys } = useOpenKeys( | ||
94 | + menuState, | ||
95 | + items, | ||
96 | + mode, | ||
97 | + accordion | ||
98 | + ); | ||
62 | 99 | ||
63 | - const { currentRoute } = useRouter(); | 100 | + const getMenuClass = computed(() => { |
101 | + const { type, mode } = props; | ||
102 | + return [ | ||
103 | + prefixCls, | ||
104 | + `justify-${unref(getTopMenuAlign)}`, | ||
105 | + { | ||
106 | + [`${prefixCls}--hide-title`]: !unref(showTitle), | ||
107 | + [`${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, | ||
112 | + }, | ||
113 | + ]; | ||
114 | + }); | ||
115 | + | ||
116 | + const showTitle = computed(() => props.collapsedShowTitle && unref(getCollapsed)); | ||
117 | + | ||
118 | + const getInlineCollapseOptions = computed(() => { | ||
119 | + const isInline = props.mode === MenuModeEnum.INLINE; | ||
120 | + | ||
121 | + const inlineCollapseOptions: { inlineCollapsed?: boolean } = {}; | ||
122 | + if (isInline) { | ||
123 | + inlineCollapseOptions.inlineCollapsed = unref(getCollapsed); | ||
124 | + } | ||
125 | + return inlineCollapseOptions; | ||
126 | + }); | ||
127 | + | ||
128 | + const getWrapperStyle = computed( | ||
129 | + (): CSSProperties => { | ||
130 | + return { | ||
131 | + height: `calc(100% - ${props.showLogo ? '48px' : '0px'})`, | ||
132 | + overflowY: 'hidden', | ||
133 | + }; | ||
134 | + } | ||
135 | + ); | ||
64 | 136 | ||
65 | - const { handleOpenChange, setOpenKeys, getOpenKeys } = useOpenKeys( | ||
66 | - menuState, | ||
67 | - items, | ||
68 | - mode, | ||
69 | - accordion | ||
70 | - ); | 137 | + watch( |
138 | + () => tabStore.getCurrentTab, | ||
139 | + () => { | ||
140 | + if (unref(currentRoute).name === REDIRECT_NAME) return; | ||
141 | + handleMenuChange(); | ||
142 | + unref(getSplit) && getParentPath(); | ||
143 | + } | ||
144 | + ); | ||
71 | 145 | ||
72 | - const getMenuClass = computed(() => { | ||
73 | - const { type } = props; | ||
74 | - const { mode } = menuState; | ||
75 | - return [ | ||
76 | - prefixCls, | ||
77 | - `justify-${unref(getTopMenuAlign)}`, | ||
78 | - { | ||
79 | - [`${prefixCls}--hide-title`]: !unref(showTitle), | ||
80 | - [`${prefixCls}--collapsed-show-title`]: props.collapsedShowTitle, | ||
81 | - [`${prefixCls}__second`]: | ||
82 | - !props.isHorizontal && appStore.getProjectConfig.menuSetting.split, | ||
83 | - [`${prefixCls}__sidebar-hor`]: | ||
84 | - type === MenuTypeEnum.TOP_MENU && mode === MenuModeEnum.HORIZONTAL, | 146 | + watch( |
147 | + () => props.items, | ||
148 | + () => { | ||
149 | + handleMenuChange(); | ||
85 | }, | 150 | }, |
86 | - ]; | ||
87 | - }); | ||
88 | - | ||
89 | - const showTitle = computed(() => props.collapsedShowTitle && unref(getCollapsed)); | ||
90 | - | ||
91 | - const getInlineCollapseOptions = computed(() => { | ||
92 | - const isInline = props.mode === MenuModeEnum.INLINE; | ||
93 | - | ||
94 | - const inlineCollapseOptions: { inlineCollapsed?: boolean } = {}; | ||
95 | - if (isInline) { | ||
96 | - inlineCollapseOptions.inlineCollapsed = unref(getCollapsed); | ||
97 | - } | ||
98 | - return inlineCollapseOptions; | ||
99 | - }); | ||
100 | - | ||
101 | - const getWrapperStyle = computed( | ||
102 | - (): CSSProperties => { | ||
103 | - const isHorizontal = unref(getIsHorizontal) || getSplit.value; | 151 | + { |
152 | + immediate: true, | ||
153 | + } | ||
154 | + ); | ||
104 | 155 | ||
105 | - return { | ||
106 | - height: isHorizontal ? '100%' : `calc(100% - ${props.showLogo ? '48px' : '0px'})`, | ||
107 | - overflowY: isHorizontal ? 'hidden' : 'auto', | ||
108 | - }; | ||
109 | - } | ||
110 | - ); | 156 | + getParentPath(); |
111 | 157 | ||
112 | - watch( | ||
113 | - () => tabStore.getCurrentTab, | ||
114 | - () => { | ||
115 | - if (unref(currentRoute).name === REDIRECT_NAME) return; | ||
116 | - handleMenuChange(); | ||
117 | - unref(getSplit) && getParentPath(); | ||
118 | - } | ||
119 | - ); | 158 | + async function getParentPath() { |
159 | + const { appendClass } = props; | ||
160 | + if (!appendClass) return ''; | ||
161 | + const parentPath = await getCurrentParentPath(unref(currentRoute).path); | ||
120 | 162 | ||
121 | - watch( | ||
122 | - () => props.items, | ||
123 | - () => { | ||
124 | - handleMenuChange(); | ||
125 | - }, | ||
126 | - { | ||
127 | - immediate: true, | 163 | + currentParentPath.value = parentPath; |
128 | } | 164 | } |
129 | - ); | ||
130 | - | ||
131 | - getParentPath(); | ||
132 | - | ||
133 | - async function getParentPath() { | ||
134 | - const { appendClass } = props; | ||
135 | - if (!appendClass) return ''; | ||
136 | - const parentPath = await getCurrentParentPath(unref(currentRoute).path); | ||
137 | - | ||
138 | - currentParentPath.value = parentPath; | ||
139 | - } | ||
140 | 165 | ||
141 | - async function handleMenuClick({ key, keyPath }: { key: string; keyPath: string[] }) { | ||
142 | - const { beforeClickFn } = props; | ||
143 | - if (beforeClickFn && isFunction(beforeClickFn)) { | ||
144 | - const flag = await beforeClickFn(key); | ||
145 | - if (!flag) return; | 166 | + async function handleMenuClick({ key, keyPath }: { key: string; keyPath: string[] }) { |
167 | + const { beforeClickFn } = props; | ||
168 | + if (beforeClickFn && isFunction(beforeClickFn)) { | ||
169 | + const flag = await beforeClickFn(key); | ||
170 | + if (!flag) return; | ||
171 | + } | ||
172 | + emit('menuClick', key); | ||
173 | + | ||
174 | + isClickGo.value = true; | ||
175 | + menuState.openKeys = keyPath; | ||
176 | + menuState.selectedKeys = [key]; | ||
146 | } | 177 | } |
147 | - emit('menuClick', key); | ||
148 | 178 | ||
149 | - isClickGo.value = true; | ||
150 | - menuState.openKeys = keyPath; | ||
151 | - menuState.selectedKeys = [key]; | ||
152 | - } | ||
153 | - | ||
154 | - function handleMenuChange() { | ||
155 | - if (unref(isClickGo)) { | ||
156 | - isClickGo.value = false; | ||
157 | - return; | ||
158 | - } | ||
159 | - const path = unref(currentRoute).path; | ||
160 | - if (menuState.mode !== MenuModeEnum.HORIZONTAL) { | ||
161 | - setOpenKeys(path); | 179 | + function handleMenuChange() { |
180 | + if (unref(isClickGo)) { | ||
181 | + isClickGo.value = false; | ||
182 | + return; | ||
183 | + } | ||
184 | + const path = unref(currentRoute).path; | ||
185 | + if (props.mode !== MenuModeEnum.HORIZONTAL) { | ||
186 | + setOpenKeys(path); | ||
187 | + } | ||
188 | + menuState.selectedKeys = [path]; | ||
162 | } | 189 | } |
163 | - menuState.selectedKeys = [path]; | ||
164 | - } | ||
165 | - | ||
166 | - // function renderExpandIcon({ key }: { key: string }) { | ||
167 | - // const isOpen = getOpenKeys.value.includes(key); | ||
168 | - // const collapsed = unref(getCollapsed); | ||
169 | - // return ( | ||
170 | - // <BasicArrow | ||
171 | - // expand={isOpen} | ||
172 | - // bottom | ||
173 | - // inset | ||
174 | - // class={[ | ||
175 | - // `${prefixCls}__expand-icon`, | ||
176 | - // { | ||
177 | - // [`${prefixCls}__expand-icon--collapsed`]: collapsed, | ||
178 | - // }, | ||
179 | - // ]} | ||
180 | - // /> | ||
181 | - // ); | ||
182 | - // } | ||
183 | - | ||
184 | - function renderItem(menu: MenuType, level = 1) { | ||
185 | - return !menuHasChildren(menu) ? renderMenuItem(menu, level) : renderSubMenu(menu, level); | ||
186 | - } | ||
187 | - | ||
188 | - function renderMenuItem(menu: MenuType, level: number) { | ||
189 | - const { appendClass } = props; | ||
190 | - const isAppendActiveCls = | ||
191 | - appendClass && level === 1 && menu.path === unref(currentParentPath); | ||
192 | 190 | ||
193 | - const levelCls = [ | ||
194 | - `${prefixCls}-item__level${level}`, | ||
195 | - ` ${menuState.theme} `, | ||
196 | - { | ||
197 | - 'top-active-menu': isAppendActiveCls, | ||
198 | - }, | ||
199 | - ]; | ||
200 | - return ( | ||
201 | - <Menu.Item key={menu.path} class={levelCls}> | ||
202 | - {() => [ | ||
203 | - <MenuContent | ||
204 | - item={menu} | ||
205 | - showTitle={unref(showTitle)} | ||
206 | - isHorizontal={props.isHorizontal} | ||
207 | - />, | ||
208 | - ]} | ||
209 | - </Menu.Item> | ||
210 | - ); | ||
211 | - } | ||
212 | - | ||
213 | - function renderSubMenu(menu: MenuType, level: number) { | ||
214 | - const levelCls = `${prefixCls}-item__level${level} ${menuState.theme} `; | ||
215 | - return ( | ||
216 | - <Menu.SubMenu key={menu.path} class={levelCls}> | ||
217 | - {{ | ||
218 | - title: () => [ | ||
219 | - <MenuContent | ||
220 | - showTitle={unref(showTitle)} | ||
221 | - item={menu} | ||
222 | - isHorizontal={props.isHorizontal} | ||
223 | - />, | ||
224 | - ], | ||
225 | - // expandIcon: renderExpandIcon, | ||
226 | - default: () => (menu.children || []).map((item) => renderItem(item, level + 1)), | ||
227 | - }} | ||
228 | - </Menu.SubMenu> | ||
229 | - ); | ||
230 | - } | ||
231 | - | ||
232 | - function renderMenu() { | ||
233 | - const { selectedKeys, defaultSelectedKeys, mode, theme } = menuState; | ||
234 | - | ||
235 | - return ( | ||
236 | - <Menu | ||
237 | - selectedKeys={selectedKeys} | ||
238 | - defaultSelectedKeys={defaultSelectedKeys} | ||
239 | - mode={mode} | ||
240 | - openKeys={unref(getOpenKeys)} | ||
241 | - inlineIndent={props.inlineIndent} | ||
242 | - theme={unref(theme)} | ||
243 | - onOpenChange={handleOpenChange} | ||
244 | - class={unref(getMenuClass)} | ||
245 | - onClick={handleMenuClick} | ||
246 | - subMenuOpenDelay={0.2} | ||
247 | - {...unref(getInlineCollapseOptions)} | ||
248 | - > | ||
249 | - {{ | ||
250 | - default: () => unref(items).map((item) => renderItem(item)), | ||
251 | - }} | ||
252 | - </Menu> | ||
253 | - ); | ||
254 | - } | ||
255 | - | ||
256 | - return () => { | ||
257 | - return ( | ||
258 | - <> | ||
259 | - {!unref(getIsHorizontal) && getSlot(slots, 'header')} | ||
260 | - <div class={`${prefixCls}-wrapper`} style={unref(getWrapperStyle)}> | ||
261 | - {renderMenu()} | ||
262 | - </div> | ||
263 | - </> | ||
264 | - ); | ||
265 | - }; | ||
266 | - }, | ||
267 | -}); | 191 | + return { |
192 | + prefixCls, | ||
193 | + getIsHorizontal, | ||
194 | + getWrapperStyle, | ||
195 | + handleMenuClick, | ||
196 | + getInlineCollapseOptions, | ||
197 | + getMenuClass, | ||
198 | + handleOpenChange, | ||
199 | + getOpenKeys, | ||
200 | + currentParentPath, | ||
201 | + showTitle, | ||
202 | + ...toRefs(menuState), | ||
203 | + }; | ||
204 | + }, | ||
205 | + }); | ||
206 | +</script> | ||
207 | +<style lang="less"> | ||
208 | + @import './index.less'; | ||
209 | +</style> |
src/components/Menu/src/components/BasicMenuItem.vue
0 → 100644
1 | +<template> | ||
2 | + <MenuItem :class="getLevelClass"> | ||
3 | + <MenuContent v-bind="$props" :item="item" /> | ||
4 | + </MenuItem> | ||
5 | +</template> | ||
6 | +<script lang="ts"> | ||
7 | + import { defineComponent, computed } from 'vue'; | ||
8 | + import { Menu } from 'ant-design-vue'; | ||
9 | + import { useDesign } from '/@/hooks/web/useDesign'; | ||
10 | + import { itemProps } from '../props'; | ||
11 | + | ||
12 | + import MenuContent from '../MenuContent'; | ||
13 | + export default defineComponent({ | ||
14 | + name: 'BasicMenuItem', | ||
15 | + components: { MenuItem: Menu.Item, MenuContent }, | ||
16 | + props: itemProps, | ||
17 | + setup(props) { | ||
18 | + const { prefixCls } = useDesign('basic-menu-item'); | ||
19 | + | ||
20 | + const getLevelClass = computed(() => { | ||
21 | + const { appendClass, level, item, parentPath, theme } = props; | ||
22 | + const isAppendActiveCls = appendClass && level === 1 && item.path === parentPath; | ||
23 | + | ||
24 | + const levelCls = [ | ||
25 | + `${prefixCls}__level${level}`, | ||
26 | + theme, | ||
27 | + { | ||
28 | + 'top-active-menu': isAppendActiveCls, | ||
29 | + }, | ||
30 | + ]; | ||
31 | + return levelCls; | ||
32 | + }); | ||
33 | + return { | ||
34 | + prefixCls, | ||
35 | + getLevelClass, | ||
36 | + }; | ||
37 | + }, | ||
38 | + }); | ||
39 | +</script> |
src/components/Menu/src/components/BasicSubMenuItem.vue
0 → 100644
1 | +<template> | ||
2 | + <BasicMenuItem v-if="!menuHasChildren(item)" v-bind="$props" /> | ||
3 | + <SubMenu v-else :class="[`${prefixCls}__level${level}`, theme]"> | ||
4 | + <template #title> | ||
5 | + <MenuContent v-bind="$props" :item="item" /> | ||
6 | + </template> | ||
7 | + <!-- <template #expandIcon="{ key }"> | ||
8 | + <ExpandIcon :key="key" /> | ||
9 | + </template> --> | ||
10 | + | ||
11 | + <template v-for="childrenItem in item.children || []" :key="childrenItem.path"> | ||
12 | + <BasicSubMenuItem v-bind="$props" :item="childrenItem" :level="level + 1" /> | ||
13 | + </template> | ||
14 | + </SubMenu> | ||
15 | +</template> | ||
16 | +<script lang="ts"> | ||
17 | + import type { Menu as MenuType } from '/@/router/types'; | ||
18 | + | ||
19 | + import { defineComponent } from 'vue'; | ||
20 | + import { Menu } from 'ant-design-vue'; | ||
21 | + import { useDesign } from '/@/hooks/web/useDesign'; | ||
22 | + import { itemProps } from '../props'; | ||
23 | + import BasicMenuItem from './BasicMenuItem.vue'; | ||
24 | + import MenuContent from '../MenuContent'; | ||
25 | + // import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; | ||
26 | + | ||
27 | + export default defineComponent({ | ||
28 | + name: 'BasicSubMenuItem', | ||
29 | + | ||
30 | + components: { | ||
31 | + BasicMenuItem, | ||
32 | + SubMenu: Menu.SubMenu, | ||
33 | + MenuItem: Menu.Item, | ||
34 | + MenuContent, | ||
35 | + // ExpandIcon: createAsyncComponent(() => import('./ExpandIcon.vue')), | ||
36 | + }, | ||
37 | + props: itemProps, | ||
38 | + setup() { | ||
39 | + const { prefixCls } = useDesign('basic-menu-item'); | ||
40 | + function menuHasChildren(menuTreeItem: MenuType): boolean { | ||
41 | + return ( | ||
42 | + Reflect.has(menuTreeItem, 'children') && | ||
43 | + !!menuTreeItem.children && | ||
44 | + menuTreeItem.children.length > 0 | ||
45 | + ); | ||
46 | + } | ||
47 | + return { | ||
48 | + prefixCls, | ||
49 | + menuHasChildren, | ||
50 | + }; | ||
51 | + }, | ||
52 | + }); | ||
53 | +</script> |
src/components/Menu/src/components/ExpandIcon.vue
0 → 100644
1 | +<template> | ||
2 | + <BasicArrow :expand="getIsOpen" bottom inset :class="getWrapperClass" /> | ||
3 | +</template> | ||
4 | +<script lang="ts"> | ||
5 | + import { defineComponent, PropType, computed } from 'vue'; | ||
6 | + import { useDesign } from '/@/hooks/web/useDesign'; | ||
7 | + import { BasicArrow } from '/@/components/Basic'; | ||
8 | + | ||
9 | + import { propTypes } from '/@/utils/propTypes'; | ||
10 | + export default defineComponent({ | ||
11 | + name: 'BasicMenuItem', | ||
12 | + components: { BasicArrow }, | ||
13 | + props: { | ||
14 | + key: propTypes.string, | ||
15 | + openKeys: { | ||
16 | + type: Array as PropType<string[]>, | ||
17 | + default: [], | ||
18 | + }, | ||
19 | + collapsed: propTypes.bool, | ||
20 | + }, | ||
21 | + setup(props) { | ||
22 | + const { prefixCls } = useDesign('basic-menu'); | ||
23 | + | ||
24 | + const getIsOpen = computed(() => { | ||
25 | + return props.openKeys.includes(props.key); | ||
26 | + }); | ||
27 | + | ||
28 | + const getWrapperClass = computed(() => { | ||
29 | + return [ | ||
30 | + `${prefixCls}__expand-icon`, | ||
31 | + { | ||
32 | + [`${prefixCls}__expand-icon--collapsed`]: props.collapsed, | ||
33 | + }, | ||
34 | + ]; | ||
35 | + }); | ||
36 | + return { | ||
37 | + prefixCls, | ||
38 | + getIsOpen, | ||
39 | + getWrapperClass, | ||
40 | + }; | ||
41 | + }, | ||
42 | + }); | ||
43 | +</script> |
src/components/Menu/src/helper.ts deleted
100644 → 0
1 | -import type { Menu as MenuType } from '/@/router/types'; | ||
2 | - | ||
3 | -/** | ||
4 | - * @description: Whether the menu has child nodes | ||
5 | - */ | ||
6 | -export function menuHasChildren(menuTreeItem: MenuType): boolean { | ||
7 | - return ( | ||
8 | - Reflect.has(menuTreeItem, 'children') && | ||
9 | - !!menuTreeItem.children && | ||
10 | - menuTreeItem.children.length > 0 | ||
11 | - ); | ||
12 | -} |
src/components/Menu/src/index.less
@@ -4,6 +4,7 @@ | @@ -4,6 +4,7 @@ | ||
4 | 4 | ||
5 | .active-style() { | 5 | .active-style() { |
6 | color: @white; | 6 | color: @white; |
7 | + // background: @primary-color !important; | ||
7 | background: linear-gradient( | 8 | background: linear-gradient( |
8 | 118deg, | 9 | 118deg, |
9 | rgba(@primary-color, 0.8), | 10 | rgba(@primary-color, 0.8), |
@@ -27,6 +28,7 @@ | @@ -27,6 +28,7 @@ | ||
27 | // right: 16px; | 28 | // right: 16px; |
28 | // width: 10px; | 29 | // width: 10px; |
29 | // transform-origin: none; | 30 | // transform-origin: none; |
31 | + // opacity: 0.45; | ||
30 | 32 | ||
31 | // span[role='img'] { | 33 | // span[role='img'] { |
32 | // margin-right: 0; | 34 | // margin-right: 0; |
@@ -52,9 +54,9 @@ | @@ -52,9 +54,9 @@ | ||
52 | > .ant-menu-item-group-list | 54 | > .ant-menu-item-group-list |
53 | > .ant-menu-submenu | 55 | > .ant-menu-submenu |
54 | > .ant-menu-submenu-title, | 56 | > .ant-menu-submenu-title, |
55 | - &.ant-menu-inline-collapsed > .ant-menu-submenu > .ant-menu-submenu-title { | ||
56 | - padding-right: 20px !important; | ||
57 | - padding-left: 20px !important; | 57 | + &.ant-menu-inline-collapsed .ant-menu-submenu-title { |
58 | + padding-right: 16px !important; | ||
59 | + padding-left: 16px !important; | ||
58 | } | 60 | } |
59 | } | 61 | } |
60 | 62 | ||
@@ -87,32 +89,33 @@ | @@ -87,32 +89,33 @@ | ||
87 | } | 89 | } |
88 | } | 90 | } |
89 | 91 | ||
90 | - // .ant-menu-item { | ||
91 | - // transition: unset; | ||
92 | - // } | 92 | + .ant-menu-item { |
93 | + transition: unset; | ||
94 | + } | ||
93 | 95 | ||
94 | // scrollbar -s tart | 96 | // scrollbar -s tart |
95 | - &-wrapper { | ||
96 | - /* 滚动槽 */ | ||
97 | - &::-webkit-scrollbar { | ||
98 | - width: 5px; | ||
99 | - height: 5px; | ||
100 | - } | 97 | + // &-wrapper { |
101 | 98 | ||
102 | - &::-webkit-scrollbar-track { | ||
103 | - background: rgba(0, 0, 0, 0); | ||
104 | - } | 99 | + /* 滚动槽 */ |
100 | + // &::-webkit-scrollbar { | ||
101 | + // width: 5px; | ||
102 | + // height: 5px; | ||
103 | + // } | ||
105 | 104 | ||
106 | - &::-webkit-scrollbar-thumb { | ||
107 | - background: rgba(255, 255, 255, 0.2); | ||
108 | - border-radius: 3px; | ||
109 | - box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.1); | ||
110 | - } | 105 | + // &::-webkit-scrollbar-track { |
106 | + // background: rgba(0, 0, 0, 0); | ||
107 | + // } | ||
111 | 108 | ||
112 | - ::-webkit-scrollbar-thumb:hover { | ||
113 | - background: @border-color-dark; | ||
114 | - } | ||
115 | - } | 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 | + // } | ||
116 | 119 | ||
117 | // scrollbar end | 120 | // scrollbar end |
118 | 121 | ||
@@ -225,14 +228,6 @@ | @@ -225,14 +228,6 @@ | ||
225 | } | 228 | } |
226 | } | 229 | } |
227 | 230 | ||
228 | - &:not(.@{basic-menu-prefix-cls}__sidebar-hor).ant-menu-inline-collapsed { | ||
229 | - .@{basic-menu-prefix-cls}-item__level1 { | ||
230 | - > div { | ||
231 | - align-items: center; | ||
232 | - } | ||
233 | - } | ||
234 | - } | ||
235 | - | ||
236 | &.ant-menu-dark:not(.@{basic-menu-prefix-cls}__sidebar-hor):not(.@{basic-menu-prefix-cls}__second) { | 231 | &.ant-menu-dark:not(.@{basic-menu-prefix-cls}__sidebar-hor):not(.@{basic-menu-prefix-cls}__second) { |
237 | // Reset menu item row height | 232 | // Reset menu item row height |
238 | .ant-menu-item:not(.@{basic-menu-prefix-cls}-item__level1), | 233 | .ant-menu-item:not(.@{basic-menu-prefix-cls}-item__level1), |
@@ -255,10 +250,6 @@ | @@ -255,10 +250,6 @@ | ||
255 | background: @sider-dark-bg-color; | 250 | background: @sider-dark-bg-color; |
256 | .active-menu-style(); | 251 | .active-menu-style(); |
257 | 252 | ||
258 | - // .menu-item-icon.app-iconify { | ||
259 | - // display: inline-block !important; | ||
260 | - // } | ||
261 | - | ||
262 | .ant-menu-item.ant-menu-item-selected.@{basic-menu-prefix-cls}-menu-item__level1, | 253 | .ant-menu-item.ant-menu-item-selected.@{basic-menu-prefix-cls}-menu-item__level1, |
263 | .ant-menu-submenu-selected.@{basic-menu-prefix-cls}-menu-item__level1 { | 254 | .ant-menu-submenu-selected.@{basic-menu-prefix-cls}-menu-item__level1 { |
264 | color: @white; | 255 | color: @white; |
@@ -304,10 +295,6 @@ | @@ -304,10 +295,6 @@ | ||
304 | overflow: hidden; | 295 | overflow: hidden; |
305 | border-right: none; | 296 | border-right: none; |
306 | 297 | ||
307 | - // .menu-item-icon.app-iconify { | ||
308 | - // display: inline-block !important; | ||
309 | - // } | ||
310 | - | ||
311 | .ant-menu-item.ant-menu-item-selected.@{basic-menu-prefix-cls}-menu-item__level1, | 298 | .ant-menu-item.ant-menu-item-selected.@{basic-menu-prefix-cls}-menu-item__level1, |
312 | .ant-menu-submenu-selected.@{basic-menu-prefix-cls}-menu-item__level1 { | 299 | .ant-menu-submenu-selected.@{basic-menu-prefix-cls}-menu-item__level1 { |
313 | color: @primary-color; | 300 | color: @primary-color; |
@@ -332,6 +319,7 @@ | @@ -332,6 +319,7 @@ | ||
332 | align-items: center; | 319 | align-items: center; |
333 | } | 320 | } |
334 | } | 321 | } |
322 | + | ||
335 | .@{basic-menu-prefix-cls}__tag { | 323 | .@{basic-menu-prefix-cls}__tag { |
336 | position: absolute; | 324 | position: absolute; |
337 | top: calc(50% - 8px); | 325 | top: calc(50% - 8px); |
@@ -368,6 +356,20 @@ | @@ -368,6 +356,20 @@ | ||
368 | background: @warning-color; | 356 | background: @warning-color; |
369 | } | 357 | } |
370 | } | 358 | } |
359 | + | ||
360 | + .ant-menu-submenu, | ||
361 | + .ant-menu-submenu-inline { | ||
362 | + transition: unset; | ||
363 | + } | ||
364 | + | ||
365 | + // .ant-menu-submenu-arrow { | ||
366 | + // transition: all 0.15s ease 0s; | ||
367 | + // } | ||
368 | + | ||
369 | + .ant-menu-inline.ant-menu-sub { | ||
370 | + box-shadow: unset !important; | ||
371 | + transition: unset; | ||
372 | + } | ||
371 | } | 373 | } |
372 | 374 | ||
373 | .ant-menu-dark { | 375 | .ant-menu-dark { |
@@ -375,7 +377,6 @@ | @@ -375,7 +377,6 @@ | ||
375 | > ul { | 377 | > ul { |
376 | background: @sider-dark-bg-color; | 378 | background: @sider-dark-bg-color; |
377 | } | 379 | } |
378 | - | ||
379 | .active-menu-style(); | 380 | .active-menu-style(); |
380 | } | 381 | } |
381 | } | 382 | } |
src/components/Menu/src/props.ts
@@ -3,57 +3,47 @@ import type { PropType } from 'vue'; | @@ -3,57 +3,47 @@ import type { PropType } from 'vue'; | ||
3 | 3 | ||
4 | import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum'; | 4 | import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum'; |
5 | import { ThemeEnum } from '/@/enums/appEnum'; | 5 | import { ThemeEnum } from '/@/enums/appEnum'; |
6 | +import { propTypes } from '/@/utils/propTypes'; | ||
6 | export const basicProps = { | 7 | export const basicProps = { |
7 | items: { | 8 | items: { |
8 | type: Array as PropType<Menu[]>, | 9 | type: Array as PropType<Menu[]>, |
9 | default: () => [], | 10 | default: () => [], |
10 | }, | 11 | }, |
11 | - appendClass: { | ||
12 | - type: Boolean as PropType<boolean>, | ||
13 | - default: false, | ||
14 | - }, | 12 | + appendClass: propTypes.bool, |
15 | 13 | ||
16 | - collapsedShowTitle: { | ||
17 | - type: Boolean as PropType<boolean>, | ||
18 | - default: false, | ||
19 | - }, | 14 | + collapsedShowTitle: propTypes.bool, |
20 | 15 | ||
21 | // 最好是4 倍数 | 16 | // 最好是4 倍数 |
22 | - inlineIndent: { | ||
23 | - type: Number as PropType<number>, | ||
24 | - default: 20, | ||
25 | - }, | 17 | + inlineIndent: propTypes.number.def(20), |
26 | // 菜单组件的mode属性 | 18 | // 菜单组件的mode属性 |
27 | mode: { | 19 | mode: { |
28 | type: String as PropType<MenuModeEnum>, | 20 | type: String as PropType<MenuModeEnum>, |
29 | default: MenuModeEnum.INLINE, | 21 | default: MenuModeEnum.INLINE, |
30 | }, | 22 | }, |
31 | - showLogo: { | ||
32 | - type: Boolean as PropType<boolean>, | ||
33 | - default: false, | ||
34 | - }, | 23 | + showLogo: propTypes.bool, |
35 | type: { | 24 | type: { |
36 | type: String as PropType<MenuTypeEnum>, | 25 | type: String as PropType<MenuTypeEnum>, |
37 | default: MenuTypeEnum.MIX, | 26 | default: MenuTypeEnum.MIX, |
38 | }, | 27 | }, |
39 | - theme: { | ||
40 | - type: String as PropType<string>, | ||
41 | - default: ThemeEnum.DARK, | ||
42 | - }, | ||
43 | - inlineCollapsed: { | ||
44 | - type: Boolean as PropType<boolean>, | ||
45 | - default: false, | ||
46 | - }, | 28 | + theme: propTypes.string.def(ThemeEnum.DARK), |
29 | + inlineCollapsed: propTypes.bool, | ||
47 | 30 | ||
48 | - isHorizontal: { | ||
49 | - type: Boolean as PropType<boolean>, | ||
50 | - default: false, | ||
51 | - }, | ||
52 | - accordion: { | ||
53 | - type: Boolean as PropType<boolean>, | ||
54 | - default: true, | ||
55 | - }, | 31 | + isHorizontal: propTypes.bool, |
32 | + accordion: propTypes.bool.def(true), | ||
56 | beforeClickFn: { | 33 | beforeClickFn: { |
57 | type: Function as PropType<(key: string) => Promise<boolean>>, | 34 | type: Function as PropType<(key: string) => Promise<boolean>>, |
58 | }, | 35 | }, |
59 | }; | 36 | }; |
37 | + | ||
38 | +export const itemProps = { | ||
39 | + item: { | ||
40 | + type: Object as PropType<Menu>, | ||
41 | + default: {}, | ||
42 | + }, | ||
43 | + level: propTypes.number, | ||
44 | + theme: propTypes.oneOf(['dark', 'light']), | ||
45 | + appendClass: propTypes.bool, | ||
46 | + parentPath: propTypes.string, | ||
47 | + showTitle: propTypes.bool, | ||
48 | + isHorizontal: propTypes.bool, | ||
49 | +}; |
src/components/Menu/src/types.ts
1 | -import { ComputedRef } from 'vue'; | ||
2 | -import { ThemeEnum } from '/@/enums/appEnum'; | ||
3 | -import { MenuModeEnum } from '/@/enums/menuEnum'; | 1 | +// import { ComputedRef } from 'vue'; |
2 | +// import { ThemeEnum } from '/@/enums/appEnum'; | ||
3 | +// import { MenuModeEnum } from '/@/enums/menuEnum'; | ||
4 | export interface MenuState { | 4 | export interface MenuState { |
5 | // 默认选中的列表 | 5 | // 默认选中的列表 |
6 | defaultSelectedKeys: string[]; | 6 | defaultSelectedKeys: string[]; |
7 | 7 | ||
8 | // 模式 | 8 | // 模式 |
9 | - mode: MenuModeEnum; | 9 | + // mode: MenuModeEnum; |
10 | 10 | ||
11 | - // 主题 | ||
12 | - theme: ComputedRef<ThemeEnum> | ThemeEnum; | 11 | + // // 主题 |
12 | + // theme: ComputedRef<ThemeEnum> | ThemeEnum; | ||
13 | 13 | ||
14 | // 缩进 | 14 | // 缩进 |
15 | inlineIndent?: number; | 15 | inlineIndent?: number; |
src/enums/appEnum.ts
src/layouts/default/tabs/index.less
@@ -34,9 +34,9 @@ | @@ -34,9 +34,9 @@ | ||
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)::before { | 37 | + &:not(.ant-tabs-tab-active)::after { |
38 | position: absolute; | 38 | position: absolute; |
39 | - top: -1px; | 39 | + bottom: -1px; |
40 | left: 50%; | 40 | left: 50%; |
41 | width: 100%; | 41 | width: 100%; |
42 | height: 2px; | 42 | height: 2px; |
@@ -53,7 +53,7 @@ | @@ -53,7 +53,7 @@ | ||
53 | opacity: 1; | 53 | opacity: 1; |
54 | } | 54 | } |
55 | 55 | ||
56 | - &:not(.ant-tabs-tab-active)::before { | 56 | + &:not(.ant-tabs-tab-active)::after { |
57 | opacity: 1; | 57 | opacity: 1; |
58 | transform: translate(-50%, 0) scaleX(1); | 58 | transform: translate(-50%, 0) scaleX(1); |
59 | transition: all 0.3s ease-in-out; | 59 | transition: all 0.3s ease-in-out; |
src/router/helper/menuHelper.ts
@@ -42,6 +42,7 @@ export function transformMenuModule(menuModule: MenuModule): Menu { | @@ -42,6 +42,7 @@ export function transformMenuModule(menuModule: MenuModule): Menu { | ||
42 | forEach(menuList, (m) => { | 42 | forEach(menuList, (m) => { |
43 | !isUrl(m.path) && joinParentPath(menuList, m); | 43 | !isUrl(m.path) && joinParentPath(menuList, m); |
44 | }); | 44 | }); |
45 | + | ||
45 | return menuList[0]; | 46 | return menuList[0]; |
46 | } | 47 | } |
47 | 48 |