249 lines
6.4 KiB
TypeScript
249 lines
6.4 KiB
TypeScript
import IconFont from '@/components/icon-font';
|
|
import { CaretDownOutlined } from '@ant-design/icons';
|
|
import { Link, useLocation } from '@umijs/max';
|
|
import { Divider, Tooltip } from 'antd';
|
|
import { createStyles } from 'antd-style';
|
|
import React, { useMemo, useState } from 'react';
|
|
|
|
interface MenuItem {
|
|
icon?: string;
|
|
selectedIcon?: string;
|
|
defaultIcon?: string;
|
|
children?: MenuItem[];
|
|
|
|
[key: string]: any;
|
|
}
|
|
|
|
interface SiderMenuProps {
|
|
menuData: MenuItem[];
|
|
collapsed?: boolean;
|
|
}
|
|
|
|
const useStyles = createStyles(({ css, token }) => {
|
|
console.log('useStyles', token);
|
|
|
|
// @ts-ignore
|
|
const { Menu } = token;
|
|
|
|
return {
|
|
siderMenu: css`
|
|
&.sider-menu-collapsed {
|
|
.menu-item {
|
|
justify-content: center;
|
|
padding: 0;
|
|
}
|
|
}
|
|
`,
|
|
groupTitle: css`
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
cursor: pointer;
|
|
white-space: nowrap;
|
|
padding: var(--ant-padding-xs) var(--ant-padding);
|
|
font-size: 12px;
|
|
padding-bottom: 4px;
|
|
overflow: hidden;
|
|
height: 30px;
|
|
&:hover {
|
|
.group-title-text {
|
|
color: var(--ant-color-text);
|
|
}
|
|
}
|
|
.anticon {
|
|
transform: scale(0.8);
|
|
}
|
|
.group-title-text {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
font-size: 12px;
|
|
color: var(--ant-color-text-tertiary);
|
|
font-weight: 400;
|
|
}
|
|
|
|
&.menu-item-group-title-collapsed {
|
|
height: 1px;
|
|
padding-block: 0;
|
|
padding-inline: 0;
|
|
}
|
|
`,
|
|
menuItemContent: css`
|
|
margin: 4px;
|
|
border-radius: 4px;
|
|
overflow: hidden;
|
|
`,
|
|
menuItemWrapper: css`
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: flex-start;
|
|
gap: 12px;
|
|
cursor: pointer;
|
|
position: relative;
|
|
padding-inline: calc(var(--ant-font-size) * 2) var(--ant-padding);
|
|
padding-left: 16px;
|
|
overflow: hidden;
|
|
white-space: nowrap;
|
|
height: ${Menu.itemHeight}px;
|
|
line-height: ${Menu.itemHeight}px;
|
|
color: var(--ant-color-text-secondary);
|
|
&:hover {
|
|
background-color: ${Menu.itemHoverBg};
|
|
color: ${Menu.itemHoverColor};
|
|
}
|
|
&.menu-item-selected {
|
|
background-color: ${Menu.itemSelectedBg};
|
|
color: ${Menu.itemSelectedColor};
|
|
.anticon {
|
|
color: ${Menu.itemSelectedColor};
|
|
}
|
|
}
|
|
&:active {
|
|
background-color: ${Menu.itemActiveBg};
|
|
color: ${Menu.itemActiveColor};
|
|
}
|
|
.anticon {
|
|
font-size: 16px;
|
|
}
|
|
.icon-wrapper {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
`,
|
|
menuItemGroup: css`
|
|
&.menu-item-group-hidden {
|
|
display: none;
|
|
}
|
|
`
|
|
};
|
|
});
|
|
|
|
const SiderMenu: React.FC<SiderMenuProps> = (props) => {
|
|
const { menuData, collapsed } = props;
|
|
const { styles, cx } = useStyles();
|
|
const location = useLocation();
|
|
const [collapseKeys, setCollapseKeys] = useState<Set<string>>(new Set());
|
|
console.log('SiderMenu', props);
|
|
|
|
const dividerStyles = useMemo(() => {
|
|
if (collapsed) {
|
|
return {
|
|
margin: '6px 0'
|
|
};
|
|
}
|
|
return {
|
|
margin: '6px 16px',
|
|
width: 'unset',
|
|
minWidth: 'unset',
|
|
maxWidth: 'unset'
|
|
};
|
|
}, [collapsed]);
|
|
|
|
const handleToggleGroup = (e: any, menuGroup: any) => {
|
|
e.stopPropagation();
|
|
|
|
console.log('handleToggleGroup', menuGroup.key);
|
|
|
|
if (collapseKeys.has(menuGroup.key)) {
|
|
collapseKeys.delete(menuGroup.key);
|
|
} else {
|
|
collapseKeys.add(menuGroup.key);
|
|
}
|
|
setCollapseKeys(new Set(collapseKeys));
|
|
};
|
|
|
|
const menuItemRender = (menuItem: MenuItem, key: string) => {
|
|
return (
|
|
<div
|
|
className={cx(styles.menuItemContent, 'menu-item-content')}
|
|
key={key}
|
|
>
|
|
<Link
|
|
to={menuItem.path.replace('/*', '')}
|
|
target={menuItem.target}
|
|
className={cx(styles.menuItemWrapper, 'menu-item', {
|
|
'menu-item-selected': location.pathname === menuItem.path
|
|
})}
|
|
>
|
|
{collapsed ? (
|
|
<Tooltip title={menuItem.name} placement="right">
|
|
<span className="icon-wrapper">
|
|
<IconFont
|
|
type={
|
|
location.pathname === menuItem.path
|
|
? menuItem.selectedIcon || ''
|
|
: menuItem.defaultIcon || ''
|
|
}
|
|
></IconFont>
|
|
</span>
|
|
</Tooltip>
|
|
) : (
|
|
<>
|
|
<IconFont
|
|
type={
|
|
location.pathname === menuItem.path
|
|
? menuItem.selectedIcon || ''
|
|
: menuItem.defaultIcon || ''
|
|
}
|
|
></IconFont>
|
|
<span>{menuItem.name}</span>
|
|
</>
|
|
)}
|
|
</Link>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className={cx(styles.siderMenu, 'sider-menu', {
|
|
'sider-menu-collapsed': collapsed
|
|
})}
|
|
>
|
|
{menuData.map((item: MenuItem, index: number) => (
|
|
<div key={item.key}>
|
|
{item.children && item.children.length > 0 ? (
|
|
<>
|
|
<div
|
|
className={cx(styles.groupTitle, {
|
|
'menu-item-group-title-collapsed': collapsed
|
|
})}
|
|
onClick={(e) => handleToggleGroup(e, item)}
|
|
>
|
|
{!collapsed ? (
|
|
<span className="group-title-text">
|
|
<span>{item.name}</span>
|
|
<CaretDownOutlined
|
|
rotate={collapseKeys.has(item.key) ? -90 : 0}
|
|
></CaretDownOutlined>
|
|
</span>
|
|
) : (
|
|
<Divider style={dividerStyles} />
|
|
)}
|
|
</div>
|
|
<div
|
|
className={cx(styles.menuItemGroup, {
|
|
'menu-item-group-collapsed': collapsed,
|
|
'menu-item-group-hidden':
|
|
!collapsed && collapseKeys.has(item.key)
|
|
})}
|
|
>
|
|
{item.children?.map((child: MenuItem) =>
|
|
menuItemRender(child, child.key)
|
|
)}
|
|
</div>
|
|
</>
|
|
) : (
|
|
menuItemRender(item, item.key)
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default SiderMenu;
|