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
src/components/Menu/index.ts
... | ... | @@ -2,6 +2,8 @@ import { withInstall } from '../util'; |
2 | 2 | |
3 | 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 | 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 | 4 | |
5 | 5 | .active-style() { |
6 | 6 | color: @white; |
7 | + // background: @primary-color !important; | |
7 | 8 | background: linear-gradient( |
8 | 9 | 118deg, |
9 | 10 | rgba(@primary-color, 0.8), |
... | ... | @@ -27,6 +28,7 @@ |
27 | 28 | // right: 16px; |
28 | 29 | // width: 10px; |
29 | 30 | // transform-origin: none; |
31 | + // opacity: 0.45; | |
30 | 32 | |
31 | 33 | // span[role='img'] { |
32 | 34 | // margin-right: 0; |
... | ... | @@ -52,9 +54,9 @@ |
52 | 54 | > .ant-menu-item-group-list |
53 | 55 | > .ant-menu-submenu |
54 | 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 | 89 | } |
88 | 90 | } |
89 | 91 | |
90 | - // .ant-menu-item { | |
91 | - // transition: unset; | |
92 | - // } | |
92 | + .ant-menu-item { | |
93 | + transition: unset; | |
94 | + } | |
93 | 95 | |
94 | 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 | 120 | // scrollbar end |
118 | 121 | |
... | ... | @@ -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 | 231 | &.ant-menu-dark:not(.@{basic-menu-prefix-cls}__sidebar-hor):not(.@{basic-menu-prefix-cls}__second) { |
237 | 232 | // Reset menu item row height |
238 | 233 | .ant-menu-item:not(.@{basic-menu-prefix-cls}-item__level1), |
... | ... | @@ -255,10 +250,6 @@ |
255 | 250 | background: @sider-dark-bg-color; |
256 | 251 | .active-menu-style(); |
257 | 252 | |
258 | - // .menu-item-icon.app-iconify { | |
259 | - // display: inline-block !important; | |
260 | - // } | |
261 | - | |
262 | 253 | .ant-menu-item.ant-menu-item-selected.@{basic-menu-prefix-cls}-menu-item__level1, |
263 | 254 | .ant-menu-submenu-selected.@{basic-menu-prefix-cls}-menu-item__level1 { |
264 | 255 | color: @white; |
... | ... | @@ -304,10 +295,6 @@ |
304 | 295 | overflow: hidden; |
305 | 296 | border-right: none; |
306 | 297 | |
307 | - // .menu-item-icon.app-iconify { | |
308 | - // display: inline-block !important; | |
309 | - // } | |
310 | - | |
311 | 298 | .ant-menu-item.ant-menu-item-selected.@{basic-menu-prefix-cls}-menu-item__level1, |
312 | 299 | .ant-menu-submenu-selected.@{basic-menu-prefix-cls}-menu-item__level1 { |
313 | 300 | color: @primary-color; |
... | ... | @@ -332,6 +319,7 @@ |
332 | 319 | align-items: center; |
333 | 320 | } |
334 | 321 | } |
322 | + | |
335 | 323 | .@{basic-menu-prefix-cls}__tag { |
336 | 324 | position: absolute; |
337 | 325 | top: calc(50% - 8px); |
... | ... | @@ -368,6 +356,20 @@ |
368 | 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 | 375 | .ant-menu-dark { |
... | ... | @@ -375,7 +377,6 @@ |
375 | 377 | > ul { |
376 | 378 | background: @sider-dark-bg-color; |
377 | 379 | } |
378 | - | |
379 | 380 | .active-menu-style(); |
380 | 381 | } |
381 | 382 | } | ... | ... |
src/components/Menu/src/props.ts
... | ... | @@ -3,57 +3,47 @@ import type { PropType } from 'vue'; |
3 | 3 | |
4 | 4 | import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum'; |
5 | 5 | import { ThemeEnum } from '/@/enums/appEnum'; |
6 | +import { propTypes } from '/@/utils/propTypes'; | |
6 | 7 | export const basicProps = { |
7 | 8 | items: { |
8 | 9 | type: Array as PropType<Menu[]>, |
9 | 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 | 16 | // 最好是4 倍数 |
22 | - inlineIndent: { | |
23 | - type: Number as PropType<number>, | |
24 | - default: 20, | |
25 | - }, | |
17 | + inlineIndent: propTypes.number.def(20), | |
26 | 18 | // 菜单组件的mode属性 |
27 | 19 | mode: { |
28 | 20 | type: String as PropType<MenuModeEnum>, |
29 | 21 | default: MenuModeEnum.INLINE, |
30 | 22 | }, |
31 | - showLogo: { | |
32 | - type: Boolean as PropType<boolean>, | |
33 | - default: false, | |
34 | - }, | |
23 | + showLogo: propTypes.bool, | |
35 | 24 | type: { |
36 | 25 | type: String as PropType<MenuTypeEnum>, |
37 | 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 | 33 | beforeClickFn: { |
57 | 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 | 4 | export interface MenuState { |
5 | 5 | // 默认选中的列表 |
6 | 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 | 15 | inlineIndent?: number; | ... | ... |
src/enums/appEnum.ts
src/layouts/default/tabs/index.less
... | ... | @@ -34,9 +34,9 @@ |
34 | 34 | border: 1px solid darken(@border-color-light, 6%); |
35 | 35 | transition: none; |
36 | 36 | |
37 | - &:not(.ant-tabs-tab-active)::before { | |
37 | + &:not(.ant-tabs-tab-active)::after { | |
38 | 38 | position: absolute; |
39 | - top: -1px; | |
39 | + bottom: -1px; | |
40 | 40 | left: 50%; |
41 | 41 | width: 100%; |
42 | 42 | height: 2px; |
... | ... | @@ -53,7 +53,7 @@ |
53 | 53 | opacity: 1; |
54 | 54 | } |
55 | 55 | |
56 | - &:not(.ant-tabs-tab-active)::before { | |
56 | + &:not(.ant-tabs-tab-active)::after { | |
57 | 57 | opacity: 1; |
58 | 58 | transform: translate(-50%, 0) scaleX(1); |
59 | 59 | transition: all 0.3s ease-in-out; | ... | ... |