vben
2020-11-10 4ff6b73c2bb57764db2bcd8212d82f028e25e36d
提交 | 用户 | age
2f6253 1 import type { MenuState } from './types';
2 import type { Menu as MenuType } from '/@/router/types';
3
5f2a92 4 import { computed, defineComponent, unref, reactive, toRef, watch, onMounted, ref } from 'vue';
2f6253 5 import { Menu } from 'ant-design-vue';
6 import SearchInput from './SearchInput.vue';
7 import MenuContent from './MenuContent';
96c10d 8
V 9 import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
10
11 import { menuStore } from '/@/store/modules/menu';
12 import { appStore } from '/@/store/modules/app';
13
2f6253 14 import { useSearchInput } from './useSearchInput';
15 import { useOpenKeys } from './useOpenKeys';
16 import { useRouter } from 'vue-router';
96c10d 17
2f6253 18 import { isFunction } from '/@/utils/is';
96c10d 19 import { getSlot } from '/@/utils/helper/tsxHelper';
V 20 import { menuHasChildren } from './helper';
21
2f6253 22 import { getCurrentParentPath } from '/@/router/menus';
96c10d 23
V 24 import { basicProps } from './props';
25 import './index.less';
2f6253 26 export default defineComponent({
27   name: 'BasicMenu',
28   props: basicProps,
29   emits: ['menuClick'],
30   setup(props, { slots, emit }) {
31     const currentParentPath = ref('');
32     const menuState = reactive<MenuState>({
33       defaultSelectedKeys: [],
34       mode: props.mode,
35       theme: computed(() => props.theme),
36       openKeys: [],
37       searchValue: '',
38       selectedKeys: [],
39       collapsedOpenKeys: [],
40     });
41     const { currentRoute } = useRouter();
42
43     const { handleInputChange, handleInputClick } = useSearchInput({
44       flatMenusRef: toRef(props, 'flatItems'),
45       emit: emit,
46       menuState,
47       handleMenuChange,
48     });
49
50     const { handleOpenChange, resetKeys, setOpenKeys } = useOpenKeys(
51       menuState,
52       toRef(props, 'items'),
53       toRef(props, 'flatItems'),
2f1255 54       toRef(props, 'isAppMenu'),
4ff6b7 55       toRef(props, 'mode'),
V 56       toRef(props, 'accordion')
2f6253 57     );
58
59     const getOpenKeys = computed(() => {
60       if (props.isAppMenu) {
61         return menuStore.getCollapsedState ? menuState.collapsedOpenKeys : menuState.openKeys;
62       }
63       return menuState.openKeys;
64     });
f64568 65
2f6253 66     // menu外层样式
67     const getMenuWrapStyle = computed((): any => {
68       const { showLogo, search } = props;
69       let offset = 0;
70       if (search) {
71         offset += 54;
72       }
84b830 73       if (showLogo) {
V 74         offset += 46;
75       }
2f6253 76       return {
4f6b65 77         height: `calc(100% - ${offset - 12}px)`,
2f6253 78         position: 'relative',
96c10d 79         overflowY: 'auto',
2f6253 80       };
81     });
82
83     // 是否透明化左侧一级菜单
84     const transparentMenuClass = computed(() => {
85       const { type } = props;
86       const { mode } = menuState;
96c10d 87       const cls: string[] = [];
2f6253 88       if (
89         (type === MenuTypeEnum.TOP_MENU && mode === MenuModeEnum.HORIZONTAL) ||
90         props.appendClass
91       ) {
96c10d 92         cls.push('basic-menu__sidebar-hor');
2f6253 93       }
96c10d 94
V 95       if (!props.isTop && props.isAppMenu && appStore.getProjectConfig.menuSetting.split) {
96         cls.push('basic-menu__second');
97       }
98       return cls;
2f6253 99     });
100
101     watch(
8a1bfd 102       () => currentRoute.value.name,
2f6253 103       (name: string) => {
96c10d 104         if (name === 'Redirect') return;
V 105         handleMenuChange();
106         props.isTop && appStore.getProjectConfig.menuSetting.split && getParentPath();
2f6253 107       }
108     );
5f2a92 109
N 110     watch(
111       () => props.items,
112       () => {
113         if (props.items) {
114           handleMenuChange();
115         }
116       },
117       {
118         immediate: true,
2f6253 119       }
5f2a92 120     );
2f6253 121
122     async function getParentPath() {
123       const { appendClass } = props;
124       if (!appendClass) return '';
125       const parentPath = await getCurrentParentPath(unref(currentRoute).path);
126       currentParentPath.value = parentPath;
127     }
128
129     async function handleMenuClick(menu: MenuType) {
130       const { beforeClickFn } = props;
131       if (beforeClickFn && isFunction(beforeClickFn)) {
132         const flag = await beforeClickFn(menu);
133         if (!flag) {
134           return;
135         }
136       }
137       const { path } = menu;
138       menuState.selectedKeys = [path];
139       emit('menuClick', menu);
140     }
f64568 141
2f6253 142     function handleMenuChange() {
143       const { flatItems } = props;
144       if (!unref(flatItems) || flatItems.length === 0) {
145         return;
146       }
147       const findMenu = flatItems.find((menu) => menu.path === unref(currentRoute).path);
148       if (findMenu) {
149         if (menuState.mode !== MenuModeEnum.HORIZONTAL) {
150           setOpenKeys(findMenu);
151         }
152         menuState.selectedKeys = [findMenu.path];
153       } else {
154         resetKeys();
155       }
156     }
66acb2 157
V 158     const showTitle = computed(() => {
96c10d 159       return props.collapsedShowTitle && menuStore.getCollapsedState;
66acb2 160     });
V 161
2f6253 162     // render menu item
163     function renderMenuItem(menuList?: MenuType[], index = 1) {
96c10d 164       if (!menuList) return;
2f6253 165       const { appendClass } = props;
166       const levelCls = `basic-menu-item__level${index} ${menuState.theme} `;
167       return menuList.map((menu) => {
168         if (!menu) {
169           return null;
170         }
171
172         const isAppendActiveCls =
173           appendClass && index === 1 && menu.path === unref(currentParentPath);
174         // 没有子节点
175         if (!menuHasChildren(menu)) {
176           return (
177             <Menu.Item
178               key={menu.path}
179               class={`${levelCls}${isAppendActiveCls ? ' top-active-menu ' : ''}`}
180               onClick={handleMenuClick.bind(null, menu)}
181             >
182               {() => [
183                 <MenuContent
184                   item={menu}
185                   level={index}
5737e4 186                   showTitle={unref(showTitle)}
2f6253 187                   searchValue={menuState.searchValue}
188                 />,
189               ]}
190             </Menu.Item>
191           );
192         }
193         return (
194           <Menu.SubMenu key={menu.path} class={levelCls}>
195             {{
196               title: () => [
197                 <MenuContent
5737e4 198                   showTitle={unref(showTitle)}
2f6253 199                   item={menu}
200                   level={index}
201                   searchValue={menuState.searchValue}
202                 />,
203               ],
204               default: () => renderMenuItem(menu.children, index + 1),
205             }}
206           </Menu.SubMenu>
207         );
208       });
209     }
210
211     function renderMenu() {
212       const isInline = props.mode === MenuModeEnum.INLINE;
213       const { selectedKeys, defaultSelectedKeys, mode, theme } = menuState;
214
215       const inlineCollapsedObj = isInline
216         ? props.isAppMenu
217           ? {
218               inlineCollapsed: menuStore.getCollapsedState,
219             }
220           : { inlineCollapsed: props.inlineCollapsed }
221         : {};
222       return (
223         <Menu
77db3d 224           forceSubMenuRender={props.isAppMenu}
2f6253 225           selectedKeys={selectedKeys}
226           defaultSelectedKeys={defaultSelectedKeys}
227           mode={mode}
228           openKeys={unref(getOpenKeys)}
229           inlineIndent={props.inlineIndent}
230           theme={unref(theme)}
231           onOpenChange={handleOpenChange}
5737e4 232           class={[
V 233             'basic-menu',
234             props.collapsedShowTitle && 'collapsed-show-title',
96c10d 235             ...unref(transparentMenuClass),
5737e4 236           ]}
2f6253 237           {...inlineCollapsedObj}
238         >
239           {{
240             default: () => renderMenuItem(props.items, 1),
241           }}
242         </Menu>
243       );
244     }
245
246     onMounted(async () => {
247       getParentPath();
248     });
96c10d 249
2f6253 250     return () => {
251       const { getCollapsedState } = menuStore;
252       const { mode } = props;
253       return mode === MenuModeEnum.HORIZONTAL ? (
254         renderMenu()
255       ) : (
66acb2 256         <section class={[`basic-menu-wrap`, !unref(showTitle) && 'hide-title']}>
2f6253 257           {getSlot(slots, 'header')}
beb4c3 258           <SearchInput
N 259             class={!props.search ? 'hidden' : ''}
260             theme={props.theme}
261             onChange={handleInputChange}
262             onClick={handleInputClick}
263             collapsed={getCollapsedState}
264           />
96c10d 265           <section style={unref(getMenuWrapStyle)} class="basic-menu__content">
31e271 266             {renderMenu()}
2f6253 267           </section>
268         </section>
269       );
270     };
271   },
272 });