|
|
|
@ -9,14 +9,15 @@ import { |
|
|
|
unref, |
|
|
|
reactive, |
|
|
|
watch, |
|
|
|
onMounted, |
|
|
|
ref, |
|
|
|
toRefs, |
|
|
|
ComputedRef, |
|
|
|
ref, |
|
|
|
CSSProperties, |
|
|
|
} from 'vue'; |
|
|
|
import { Menu } from 'ant-design-vue'; |
|
|
|
import MenuContent from './MenuContent'; |
|
|
|
// import { ScrollContainer } from '/@/components/Container';
|
|
|
|
// import { BasicArrow } from '/@/components/Basic';
|
|
|
|
|
|
|
|
import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum'; |
|
|
|
import { ThemeEnum } from '/@/enums/appEnum'; |
|
|
|
@ -29,18 +30,20 @@ import { useRouter } from 'vue-router'; |
|
|
|
import { isFunction } from '/@/utils/is'; |
|
|
|
import { getSlot } from '/@/utils/helper/tsxHelper'; |
|
|
|
import { menuHasChildren } from './helper'; |
|
|
|
|
|
|
|
import { getCurrentParentPath } from '/@/router/menus'; |
|
|
|
|
|
|
|
import { basicProps } from './props'; |
|
|
|
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; |
|
|
|
import { REDIRECT_NAME } from '/@/router/constant'; |
|
|
|
import { tabStore } from '/@/store/modules/tab'; |
|
|
|
import { useDesign } from '/@/hooks/web/useDesign'; |
|
|
|
export default defineComponent({ |
|
|
|
name: 'BasicMenu', |
|
|
|
props: basicProps, |
|
|
|
emits: ['menuClick'], |
|
|
|
setup(props, { slots, emit }) { |
|
|
|
const currentParentPath = ref(''); |
|
|
|
const isClickGo = ref(false); |
|
|
|
|
|
|
|
const menuState = reactive<MenuState>({ |
|
|
|
defaultSelectedKeys: [], |
|
|
|
@ -51,170 +54,184 @@ export default defineComponent({ |
|
|
|
collapsedOpenKeys: [], |
|
|
|
}); |
|
|
|
|
|
|
|
const { getCollapsed } = useMenuSetting(); |
|
|
|
const { prefixCls } = useDesign('basic-menu'); |
|
|
|
|
|
|
|
const { currentRoute } = useRouter(); |
|
|
|
const { items, mode, accordion } = toRefs(props); |
|
|
|
|
|
|
|
const { getCollapsed, getIsHorizontal, getTopMenuAlign, getSplit } = useMenuSetting(); |
|
|
|
|
|
|
|
const { items, flatItems, mode, accordion } = toRefs(props); |
|
|
|
const { currentRoute } = useRouter(); |
|
|
|
|
|
|
|
const { handleOpenChange, resetKeys, setOpenKeys } = useOpenKeys( |
|
|
|
const { handleOpenChange, setOpenKeys, getOpenKeys } = useOpenKeys( |
|
|
|
menuState, |
|
|
|
items, |
|
|
|
flatItems, |
|
|
|
mode, |
|
|
|
accordion |
|
|
|
); |
|
|
|
|
|
|
|
const getOpenKeys = computed(() => { |
|
|
|
return unref(getCollapsed) ? menuState.collapsedOpenKeys : menuState.openKeys; |
|
|
|
const getMenuClass = computed(() => { |
|
|
|
const { type } = props; |
|
|
|
const { mode } = menuState; |
|
|
|
return [ |
|
|
|
prefixCls, |
|
|
|
`justify-${unref(getTopMenuAlign)}`, |
|
|
|
{ |
|
|
|
[`${prefixCls}--hide-title`]: !unref(showTitle), |
|
|
|
[`${prefixCls}--collapsed-show-title`]: props.collapsedShowTitle, |
|
|
|
[`${prefixCls}__second`]: |
|
|
|
!props.isHorizontal && appStore.getProjectConfig.menuSetting.split, |
|
|
|
[`${prefixCls}__sidebar-hor`]: |
|
|
|
type === MenuTypeEnum.TOP_MENU && mode === MenuModeEnum.HORIZONTAL, |
|
|
|
}, |
|
|
|
]; |
|
|
|
}); |
|
|
|
|
|
|
|
// menu外层样式
|
|
|
|
const getMenuWrapStyle = computed((): any => { |
|
|
|
const { showLogo } = props; |
|
|
|
let offset = 0; |
|
|
|
|
|
|
|
if (showLogo) { |
|
|
|
offset += 46; |
|
|
|
} |
|
|
|
return { |
|
|
|
height: `calc(100% - ${offset}px)`, |
|
|
|
position: 'relative', |
|
|
|
overflowY: 'auto', |
|
|
|
}; |
|
|
|
}); |
|
|
|
const showTitle = computed(() => props.collapsedShowTitle && unref(getCollapsed)); |
|
|
|
|
|
|
|
// 是否透明化左侧一级菜单
|
|
|
|
const transparentMenuClass = computed(() => { |
|
|
|
const { type } = props; |
|
|
|
const { mode } = menuState; |
|
|
|
const cls: string[] = []; |
|
|
|
if ( |
|
|
|
(type === MenuTypeEnum.TOP_MENU && mode === MenuModeEnum.HORIZONTAL) || |
|
|
|
props.appendClass |
|
|
|
) { |
|
|
|
cls.push('basic-menu__sidebar-hor'); |
|
|
|
} |
|
|
|
const getInlineCollapseOptions = computed(() => { |
|
|
|
const isInline = props.mode === MenuModeEnum.INLINE; |
|
|
|
|
|
|
|
if (!props.isHorizontal && appStore.getProjectConfig.menuSetting.split) { |
|
|
|
cls.push('basic-menu__second'); |
|
|
|
const inlineCollapseOptions: { inlineCollapsed?: boolean } = {}; |
|
|
|
if (isInline) { |
|
|
|
inlineCollapseOptions.inlineCollapsed = unref(getCollapsed); |
|
|
|
} |
|
|
|
return cls; |
|
|
|
return inlineCollapseOptions; |
|
|
|
}); |
|
|
|
|
|
|
|
const showTitle = computed(() => props.collapsedShowTitle && unref(getCollapsed)); |
|
|
|
const getWrapperStyle = computed( |
|
|
|
(): CSSProperties => { |
|
|
|
const isHorizontal = unref(getIsHorizontal); |
|
|
|
return { |
|
|
|
height: isHorizontal |
|
|
|
? `calc(100% + 1px)` |
|
|
|
: `calc(100% - ${props.showLogo ? '48px' : '0'})`, |
|
|
|
overflowY: isHorizontal ? 'hidden' : 'auto', |
|
|
|
}; |
|
|
|
} |
|
|
|
); |
|
|
|
|
|
|
|
watch( |
|
|
|
() => currentRoute.value.name, |
|
|
|
(name: string) => { |
|
|
|
if (name === REDIRECT_NAME) return; |
|
|
|
() => tabStore.getCurrentTab, |
|
|
|
() => { |
|
|
|
if (unref(currentRoute).name === REDIRECT_NAME) return; |
|
|
|
handleMenuChange(); |
|
|
|
props.isHorizontal && appStore.getProjectConfig.menuSetting.split && getParentPath(); |
|
|
|
unref(getSplit) && getParentPath(); |
|
|
|
} |
|
|
|
); |
|
|
|
|
|
|
|
watch( |
|
|
|
() => props.items, |
|
|
|
() => { |
|
|
|
if (props.items) { |
|
|
|
handleMenuChange(); |
|
|
|
} |
|
|
|
handleMenuChange(); |
|
|
|
}, |
|
|
|
{ |
|
|
|
immediate: true, |
|
|
|
} |
|
|
|
); |
|
|
|
|
|
|
|
getParentPath(); |
|
|
|
|
|
|
|
async function getParentPath() { |
|
|
|
const { appendClass } = props; |
|
|
|
if (!appendClass) return ''; |
|
|
|
const parentPath = await getCurrentParentPath(unref(currentRoute).path); |
|
|
|
|
|
|
|
currentParentPath.value = parentPath; |
|
|
|
} |
|
|
|
|
|
|
|
async function handleMenuClick(menu: MenuType) { |
|
|
|
async function handleMenuClick({ key, keyPath }: { key: string; keyPath: string[] }) { |
|
|
|
const { beforeClickFn } = props; |
|
|
|
if (beforeClickFn && isFunction(beforeClickFn)) { |
|
|
|
const flag = await beforeClickFn(menu); |
|
|
|
const flag = await beforeClickFn(key); |
|
|
|
if (!flag) return; |
|
|
|
} |
|
|
|
emit('menuClick', menu); |
|
|
|
const { path } = menu; |
|
|
|
menuState.selectedKeys = [path]; |
|
|
|
emit('menuClick', key); |
|
|
|
|
|
|
|
isClickGo.value = true; |
|
|
|
menuState.openKeys = keyPath; |
|
|
|
menuState.selectedKeys = [key]; |
|
|
|
} |
|
|
|
|
|
|
|
function handleMenuChange() { |
|
|
|
const { flatItems } = props; |
|
|
|
if (!unref(flatItems) || flatItems.length === 0) return; |
|
|
|
const findMenu = flatItems.find((menu) => menu.path === unref(currentRoute).path); |
|
|
|
if (findMenu) { |
|
|
|
if (menuState.mode !== MenuModeEnum.HORIZONTAL) { |
|
|
|
setOpenKeys(findMenu); |
|
|
|
} |
|
|
|
menuState.selectedKeys = [findMenu.path]; |
|
|
|
} else { |
|
|
|
resetKeys(); |
|
|
|
if (unref(isClickGo)) { |
|
|
|
isClickGo.value = false; |
|
|
|
return; |
|
|
|
} |
|
|
|
const path = unref(currentRoute).path; |
|
|
|
if (menuState.mode !== MenuModeEnum.HORIZONTAL) { |
|
|
|
setOpenKeys(path); |
|
|
|
} |
|
|
|
menuState.selectedKeys = [path]; |
|
|
|
} |
|
|
|
|
|
|
|
// render menu item
|
|
|
|
function renderMenuItem(menuList?: MenuType[], index = 1) { |
|
|
|
if (!menuList) return; |
|
|
|
// function renderExpandIcon({ key }: { key: string }) {
|
|
|
|
// const isOpen = getOpenKeys.value.includes(key);
|
|
|
|
// const collapsed = unref(getCollapsed);
|
|
|
|
// return (
|
|
|
|
// <BasicArrow
|
|
|
|
// expand={isOpen}
|
|
|
|
// bottom
|
|
|
|
// inset
|
|
|
|
// class={[
|
|
|
|
// `${prefixCls}__expand-icon`,
|
|
|
|
// {
|
|
|
|
// [`${prefixCls}__expand-icon--collapsed`]: collapsed,
|
|
|
|
// },
|
|
|
|
// ]}
|
|
|
|
// />
|
|
|
|
// );
|
|
|
|
// }
|
|
|
|
|
|
|
|
function renderItem(menu: MenuType, level = 1) { |
|
|
|
return !menuHasChildren(menu) ? renderMenuItem(menu, level) : renderSubMenu(menu, level); |
|
|
|
} |
|
|
|
|
|
|
|
function renderMenuItem(menu: MenuType, level: number) { |
|
|
|
const { appendClass } = props; |
|
|
|
const levelCls = `basic-menu-item__level${index} ${menuState.theme} `; |
|
|
|
return menuList.map((menu) => { |
|
|
|
if (!menu) { |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
const isAppendActiveCls = |
|
|
|
appendClass && index === 1 && menu.path === unref(currentParentPath); |
|
|
|
// 没有子节点
|
|
|
|
if (!menuHasChildren(menu)) { |
|
|
|
return ( |
|
|
|
<Menu.Item |
|
|
|
key={menu.path} |
|
|
|
class={`${levelCls}${isAppendActiveCls ? ' top-active-menu ' : ''}`} |
|
|
|
onClick={handleMenuClick.bind(null, menu)} |
|
|
|
> |
|
|
|
{() => [ |
|
|
|
<MenuContent |
|
|
|
item={menu} |
|
|
|
level={index} |
|
|
|
isHorizontal={props.isHorizontal} |
|
|
|
showTitle={unref(showTitle)} |
|
|
|
/>, |
|
|
|
]} |
|
|
|
</Menu.Item> |
|
|
|
); |
|
|
|
} |
|
|
|
return ( |
|
|
|
<Menu.SubMenu key={menu.path} class={levelCls}> |
|
|
|
{{ |
|
|
|
title: () => [ |
|
|
|
<MenuContent |
|
|
|
showTitle={unref(showTitle)} |
|
|
|
item={menu} |
|
|
|
level={index} |
|
|
|
isHorizontal={props.isHorizontal} |
|
|
|
/>, |
|
|
|
], |
|
|
|
default: () => renderMenuItem(menu.children, index + 1), |
|
|
|
}} |
|
|
|
</Menu.SubMenu> |
|
|
|
); |
|
|
|
}); |
|
|
|
const isAppendActiveCls = |
|
|
|
appendClass && level === 1 && menu.path === unref(currentParentPath); |
|
|
|
const levelCls = [ |
|
|
|
`${prefixCls}-item__level${level}`, |
|
|
|
` ${menuState.theme} `, |
|
|
|
{ |
|
|
|
'top-active-menu': isAppendActiveCls, |
|
|
|
}, |
|
|
|
]; |
|
|
|
return ( |
|
|
|
<Menu.Item key={menu.path} class={levelCls}> |
|
|
|
{() => [ |
|
|
|
<MenuContent |
|
|
|
item={menu} |
|
|
|
showTitle={unref(showTitle)} |
|
|
|
isHorizontal={props.isHorizontal} |
|
|
|
/>, |
|
|
|
]} |
|
|
|
</Menu.Item> |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
function renderSubMenu(menu: MenuType, level: number) { |
|
|
|
const levelCls = `${prefixCls}-item__level${level} ${menuState.theme} `; |
|
|
|
return ( |
|
|
|
<Menu.SubMenu key={menu.path} class={levelCls}> |
|
|
|
{{ |
|
|
|
title: () => [ |
|
|
|
<MenuContent |
|
|
|
showTitle={unref(showTitle)} |
|
|
|
item={menu} |
|
|
|
isHorizontal={props.isHorizontal} |
|
|
|
/>, |
|
|
|
], |
|
|
|
// expandIcon: renderExpandIcon,
|
|
|
|
default: () => (menu.children || []).map((item) => renderItem(item, level + 1)), |
|
|
|
}} |
|
|
|
</Menu.SubMenu> |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
function renderMenu() { |
|
|
|
const isInline = props.mode === MenuModeEnum.INLINE; |
|
|
|
const { selectedKeys, defaultSelectedKeys, mode, theme } = menuState; |
|
|
|
|
|
|
|
const inlineCollapsedObj = isInline |
|
|
|
? { |
|
|
|
inlineCollapsed: unref(getCollapsed), |
|
|
|
} |
|
|
|
: {}; |
|
|
|
return ( |
|
|
|
<Menu |
|
|
|
selectedKeys={selectedKeys} |
|
|
|
@ -224,36 +241,25 @@ export default defineComponent({ |
|
|
|
inlineIndent={props.inlineIndent} |
|
|
|
theme={unref(theme)} |
|
|
|
onOpenChange={handleOpenChange} |
|
|
|
class={[ |
|
|
|
'basic-menu', |
|
|
|
props.collapsedShowTitle && 'collapsed-show-title', |
|
|
|
...unref(transparentMenuClass), |
|
|
|
]} |
|
|
|
{...inlineCollapsedObj} |
|
|
|
class={unref(getMenuClass)} |
|
|
|
onClick={handleMenuClick} |
|
|
|
{...unref(getInlineCollapseOptions)} |
|
|
|
> |
|
|
|
{{ |
|
|
|
default: () => renderMenuItem(props.items, 1), |
|
|
|
default: () => unref(items).map((item) => renderItem(item)), |
|
|
|
}} |
|
|
|
</Menu> |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
onMounted(async () => { |
|
|
|
getParentPath(); |
|
|
|
}); |
|
|
|
|
|
|
|
return () => { |
|
|
|
const { mode } = props; |
|
|
|
return mode === MenuModeEnum.HORIZONTAL ? ( |
|
|
|
renderMenu() |
|
|
|
) : ( |
|
|
|
<section class={[`basic-menu-wrap`, !unref(showTitle) && 'hide-title']}> |
|
|
|
{getSlot(slots, 'header')} |
|
|
|
|
|
|
|
<section style={unref(getMenuWrapStyle)} class="basic-menu__content"> |
|
|
|
return ( |
|
|
|
<> |
|
|
|
{!unref(getIsHorizontal) && getSlot(slots, 'header')} |
|
|
|
<div class={`${prefixCls}-wrapper`} style={unref(getWrapperStyle)}> |
|
|
|
{renderMenu()} |
|
|
|
</section> |
|
|
|
</section> |
|
|
|
</div> |
|
|
|
</> |
|
|
|
); |
|
|
|
}; |
|
|
|
}, |
|
|
|
|