mirror of
https://github.com/vbenjs/vben-admin-thin-next.git
synced 2025-02-03 02:18:40 +08:00
wip(menu): perf menu
This commit is contained in:
parent
f81c401959
commit
cbcd909867
@ -66,8 +66,9 @@
|
|||||||
.@{prefix-cls} {
|
.@{prefix-cls} {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding-left: 12px;
|
padding-left: 7px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
&.collapsed-show-title {
|
&.collapsed-show-title {
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
|
@ -2,6 +2,8 @@ import { withInstall } from '../util';
|
|||||||
|
|
||||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||||
|
|
||||||
export const BasicMenu = createAsyncComponent(() => import('./src/BasicMenu'), { loading: false });
|
export const BasicMenu = createAsyncComponent(() => import('./src/BasicMenu.vue'), {
|
||||||
|
loading: false,
|
||||||
|
});
|
||||||
|
|
||||||
withInstall(BasicMenu);
|
withInstall(BasicMenu);
|
||||||
|
@ -1,267 +0,0 @@
|
|||||||
import './index.less';
|
|
||||||
|
|
||||||
import type { MenuState } from './types';
|
|
||||||
import type { Menu as MenuType } from '/@/router/types';
|
|
||||||
|
|
||||||
import {
|
|
||||||
computed,
|
|
||||||
defineComponent,
|
|
||||||
unref,
|
|
||||||
reactive,
|
|
||||||
watch,
|
|
||||||
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';
|
|
||||||
|
|
||||||
import { appStore } from '/@/store/modules/app';
|
|
||||||
|
|
||||||
import { useOpenKeys } from './useOpenKeys';
|
|
||||||
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: [],
|
|
||||||
mode: props.mode,
|
|
||||||
theme: computed(() => props.theme) as ComputedRef<ThemeEnum>,
|
|
||||||
openKeys: [],
|
|
||||||
selectedKeys: [],
|
|
||||||
collapsedOpenKeys: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const { prefixCls } = useDesign('basic-menu');
|
|
||||||
|
|
||||||
const { items, mode, accordion } = toRefs(props);
|
|
||||||
|
|
||||||
const { getCollapsed, getIsHorizontal, getTopMenuAlign, getSplit } = useMenuSetting();
|
|
||||||
|
|
||||||
const { currentRoute } = useRouter();
|
|
||||||
|
|
||||||
const { handleOpenChange, setOpenKeys, getOpenKeys } = useOpenKeys(
|
|
||||||
menuState,
|
|
||||||
items,
|
|
||||||
mode,
|
|
||||||
accordion
|
|
||||||
);
|
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
});
|
|
||||||
|
|
||||||
const showTitle = computed(() => props.collapsedShowTitle && unref(getCollapsed));
|
|
||||||
|
|
||||||
const getInlineCollapseOptions = computed(() => {
|
|
||||||
const isInline = props.mode === MenuModeEnum.INLINE;
|
|
||||||
|
|
||||||
const inlineCollapseOptions: { inlineCollapsed?: boolean } = {};
|
|
||||||
if (isInline) {
|
|
||||||
inlineCollapseOptions.inlineCollapsed = unref(getCollapsed);
|
|
||||||
}
|
|
||||||
return inlineCollapseOptions;
|
|
||||||
});
|
|
||||||
|
|
||||||
const getWrapperStyle = computed(
|
|
||||||
(): CSSProperties => {
|
|
||||||
const isHorizontal = unref(getIsHorizontal) || getSplit.value;
|
|
||||||
|
|
||||||
return {
|
|
||||||
height: isHorizontal ? '100%' : `calc(100% - ${props.showLogo ? '48px' : '0px'})`,
|
|
||||||
overflowY: isHorizontal ? 'hidden' : 'auto',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => tabStore.getCurrentTab,
|
|
||||||
() => {
|
|
||||||
if (unref(currentRoute).name === REDIRECT_NAME) return;
|
|
||||||
handleMenuChange();
|
|
||||||
unref(getSplit) && getParentPath();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.items,
|
|
||||||
() => {
|
|
||||||
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({ key, keyPath }: { key: string; keyPath: string[] }) {
|
|
||||||
const { beforeClickFn } = props;
|
|
||||||
if (beforeClickFn && isFunction(beforeClickFn)) {
|
|
||||||
const flag = await beforeClickFn(key);
|
|
||||||
if (!flag) return;
|
|
||||||
}
|
|
||||||
emit('menuClick', key);
|
|
||||||
|
|
||||||
isClickGo.value = true;
|
|
||||||
menuState.openKeys = keyPath;
|
|
||||||
menuState.selectedKeys = [key];
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleMenuChange() {
|
|
||||||
if (unref(isClickGo)) {
|
|
||||||
isClickGo.value = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const path = unref(currentRoute).path;
|
|
||||||
if (menuState.mode !== MenuModeEnum.HORIZONTAL) {
|
|
||||||
setOpenKeys(path);
|
|
||||||
}
|
|
||||||
menuState.selectedKeys = [path];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 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 { selectedKeys, defaultSelectedKeys, mode, theme } = menuState;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Menu
|
|
||||||
selectedKeys={selectedKeys}
|
|
||||||
defaultSelectedKeys={defaultSelectedKeys}
|
|
||||||
mode={mode}
|
|
||||||
openKeys={unref(getOpenKeys)}
|
|
||||||
inlineIndent={props.inlineIndent}
|
|
||||||
theme={unref(theme)}
|
|
||||||
onOpenChange={handleOpenChange}
|
|
||||||
class={unref(getMenuClass)}
|
|
||||||
onClick={handleMenuClick}
|
|
||||||
subMenuOpenDelay={0.2}
|
|
||||||
{...unref(getInlineCollapseOptions)}
|
|
||||||
>
|
|
||||||
{{
|
|
||||||
default: () => unref(items).map((item) => renderItem(item)),
|
|
||||||
}}
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{!unref(getIsHorizontal) && getSlot(slots, 'header')}
|
|
||||||
<div class={`${prefixCls}-wrapper`} style={unref(getWrapperStyle)}>
|
|
||||||
{renderMenu()}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
209
src/components/Menu/src/BasicMenu.vue
Normal file
209
src/components/Menu/src/BasicMenu.vue
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
<template>
|
||||||
|
<slot name="header" v-if="!getIsHorizontal" />
|
||||||
|
<ScrollContainer :class="`${prefixCls}-wrapper`" :style="getWrapperStyle">
|
||||||
|
<Menu
|
||||||
|
:selectedKeys="selectedKeys"
|
||||||
|
:defaultSelectedKeys="defaultSelectedKeys"
|
||||||
|
:mode="mode"
|
||||||
|
:openKeys="getOpenKeys"
|
||||||
|
:inlineIndent="inlineIndent"
|
||||||
|
:theme="theme"
|
||||||
|
@openChange="handleOpenChange"
|
||||||
|
:class="getMenuClass"
|
||||||
|
@click="handleMenuClick"
|
||||||
|
:subMenuOpenDelay="0.2"
|
||||||
|
v-bind="getInlineCollapseOptions"
|
||||||
|
>
|
||||||
|
<template v-for="item in items" :key="item.path">
|
||||||
|
<BasicSubMenuItem
|
||||||
|
:item="item"
|
||||||
|
:theme="theme"
|
||||||
|
:level="1"
|
||||||
|
:appendClass="appendClass"
|
||||||
|
:parentPath="currentParentPath"
|
||||||
|
:showTitle="showTitle"
|
||||||
|
:isHorizontal="isHorizontal"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Menu>
|
||||||
|
</ScrollContainer>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import type { MenuState } from './types';
|
||||||
|
|
||||||
|
import {
|
||||||
|
computed,
|
||||||
|
defineComponent,
|
||||||
|
unref,
|
||||||
|
reactive,
|
||||||
|
watch,
|
||||||
|
toRefs,
|
||||||
|
ref,
|
||||||
|
CSSProperties,
|
||||||
|
} from 'vue';
|
||||||
|
import { Menu } from 'ant-design-vue';
|
||||||
|
import BasicSubMenuItem from './components/BasicSubMenuItem.vue';
|
||||||
|
import { ScrollContainer } from '/@/components/Container';
|
||||||
|
|
||||||
|
import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
|
||||||
|
|
||||||
|
import { appStore } from '/@/store/modules/app';
|
||||||
|
|
||||||
|
import { useOpenKeys } from './useOpenKeys';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
import { isFunction } from '/@/utils/is';
|
||||||
|
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';
|
||||||
|
// import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'BasicMenu',
|
||||||
|
components: {
|
||||||
|
Menu,
|
||||||
|
ScrollContainer,
|
||||||
|
BasicSubMenuItem,
|
||||||
|
// BasicSubMenuItem: createAsyncComponent(() => import('./components/BasicSubMenuItem.vue')),
|
||||||
|
},
|
||||||
|
props: basicProps,
|
||||||
|
emits: ['menuClick'],
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const currentParentPath = ref('');
|
||||||
|
const isClickGo = ref(false);
|
||||||
|
|
||||||
|
const menuState = reactive<MenuState>({
|
||||||
|
defaultSelectedKeys: [],
|
||||||
|
openKeys: [],
|
||||||
|
selectedKeys: [],
|
||||||
|
collapsedOpenKeys: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { prefixCls } = useDesign('basic-menu');
|
||||||
|
const { items, mode, accordion } = toRefs(props);
|
||||||
|
|
||||||
|
const { getCollapsed, getIsHorizontal, getTopMenuAlign, getSplit } = useMenuSetting();
|
||||||
|
|
||||||
|
const { currentRoute } = useRouter();
|
||||||
|
|
||||||
|
const { handleOpenChange, setOpenKeys, getOpenKeys } = useOpenKeys(
|
||||||
|
menuState,
|
||||||
|
items,
|
||||||
|
mode,
|
||||||
|
accordion
|
||||||
|
);
|
||||||
|
|
||||||
|
const getMenuClass = computed(() => {
|
||||||
|
const { type, mode } = props;
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
const showTitle = computed(() => props.collapsedShowTitle && unref(getCollapsed));
|
||||||
|
|
||||||
|
const getInlineCollapseOptions = computed(() => {
|
||||||
|
const isInline = props.mode === MenuModeEnum.INLINE;
|
||||||
|
|
||||||
|
const inlineCollapseOptions: { inlineCollapsed?: boolean } = {};
|
||||||
|
if (isInline) {
|
||||||
|
inlineCollapseOptions.inlineCollapsed = unref(getCollapsed);
|
||||||
|
}
|
||||||
|
return inlineCollapseOptions;
|
||||||
|
});
|
||||||
|
|
||||||
|
const getWrapperStyle = computed(
|
||||||
|
(): CSSProperties => {
|
||||||
|
return {
|
||||||
|
height: `calc(100% - ${props.showLogo ? '48px' : '0px'})`,
|
||||||
|
overflowY: 'hidden',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => tabStore.getCurrentTab,
|
||||||
|
() => {
|
||||||
|
if (unref(currentRoute).name === REDIRECT_NAME) return;
|
||||||
|
handleMenuChange();
|
||||||
|
unref(getSplit) && getParentPath();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.items,
|
||||||
|
() => {
|
||||||
|
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({ key, keyPath }: { key: string; keyPath: string[] }) {
|
||||||
|
const { beforeClickFn } = props;
|
||||||
|
if (beforeClickFn && isFunction(beforeClickFn)) {
|
||||||
|
const flag = await beforeClickFn(key);
|
||||||
|
if (!flag) return;
|
||||||
|
}
|
||||||
|
emit('menuClick', key);
|
||||||
|
|
||||||
|
isClickGo.value = true;
|
||||||
|
menuState.openKeys = keyPath;
|
||||||
|
menuState.selectedKeys = [key];
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMenuChange() {
|
||||||
|
if (unref(isClickGo)) {
|
||||||
|
isClickGo.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const path = unref(currentRoute).path;
|
||||||
|
if (props.mode !== MenuModeEnum.HORIZONTAL) {
|
||||||
|
setOpenKeys(path);
|
||||||
|
}
|
||||||
|
menuState.selectedKeys = [path];
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
prefixCls,
|
||||||
|
getIsHorizontal,
|
||||||
|
getWrapperStyle,
|
||||||
|
handleMenuClick,
|
||||||
|
getInlineCollapseOptions,
|
||||||
|
getMenuClass,
|
||||||
|
handleOpenChange,
|
||||||
|
getOpenKeys,
|
||||||
|
currentParentPath,
|
||||||
|
showTitle,
|
||||||
|
...toRefs(menuState),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="less">
|
||||||
|
@import './index.less';
|
||||||
|
</style>
|
39
src/components/Menu/src/components/BasicMenuItem.vue
Normal file
39
src/components/Menu/src/components/BasicMenuItem.vue
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<template>
|
||||||
|
<MenuItem :class="getLevelClass">
|
||||||
|
<MenuContent v-bind="$props" :item="item" />
|
||||||
|
</MenuItem>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, computed } from 'vue';
|
||||||
|
import { Menu } from 'ant-design-vue';
|
||||||
|
import { useDesign } from '/@/hooks/web/useDesign';
|
||||||
|
import { itemProps } from '../props';
|
||||||
|
|
||||||
|
import MenuContent from '../MenuContent';
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'BasicMenuItem',
|
||||||
|
components: { MenuItem: Menu.Item, MenuContent },
|
||||||
|
props: itemProps,
|
||||||
|
setup(props) {
|
||||||
|
const { prefixCls } = useDesign('basic-menu-item');
|
||||||
|
|
||||||
|
const getLevelClass = computed(() => {
|
||||||
|
const { appendClass, level, item, parentPath, theme } = props;
|
||||||
|
const isAppendActiveCls = appendClass && level === 1 && item.path === parentPath;
|
||||||
|
|
||||||
|
const levelCls = [
|
||||||
|
`${prefixCls}__level${level}`,
|
||||||
|
theme,
|
||||||
|
{
|
||||||
|
'top-active-menu': isAppendActiveCls,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return levelCls;
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
prefixCls,
|
||||||
|
getLevelClass,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
53
src/components/Menu/src/components/BasicSubMenuItem.vue
Normal file
53
src/components/Menu/src/components/BasicSubMenuItem.vue
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<template>
|
||||||
|
<BasicMenuItem v-if="!menuHasChildren(item)" v-bind="$props" />
|
||||||
|
<SubMenu v-else :class="[`${prefixCls}__level${level}`, theme]">
|
||||||
|
<template #title>
|
||||||
|
<MenuContent v-bind="$props" :item="item" />
|
||||||
|
</template>
|
||||||
|
<!-- <template #expandIcon="{ key }">
|
||||||
|
<ExpandIcon :key="key" />
|
||||||
|
</template> -->
|
||||||
|
|
||||||
|
<template v-for="childrenItem in item.children || []" :key="childrenItem.path">
|
||||||
|
<BasicSubMenuItem v-bind="$props" :item="childrenItem" :level="level + 1" />
|
||||||
|
</template>
|
||||||
|
</SubMenu>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import type { Menu as MenuType } from '/@/router/types';
|
||||||
|
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import { Menu } from 'ant-design-vue';
|
||||||
|
import { useDesign } from '/@/hooks/web/useDesign';
|
||||||
|
import { itemProps } from '../props';
|
||||||
|
import BasicMenuItem from './BasicMenuItem.vue';
|
||||||
|
import MenuContent from '../MenuContent';
|
||||||
|
// import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'BasicSubMenuItem',
|
||||||
|
|
||||||
|
components: {
|
||||||
|
BasicMenuItem,
|
||||||
|
SubMenu: Menu.SubMenu,
|
||||||
|
MenuItem: Menu.Item,
|
||||||
|
MenuContent,
|
||||||
|
// ExpandIcon: createAsyncComponent(() => import('./ExpandIcon.vue')),
|
||||||
|
},
|
||||||
|
props: itemProps,
|
||||||
|
setup() {
|
||||||
|
const { prefixCls } = useDesign('basic-menu-item');
|
||||||
|
function menuHasChildren(menuTreeItem: MenuType): boolean {
|
||||||
|
return (
|
||||||
|
Reflect.has(menuTreeItem, 'children') &&
|
||||||
|
!!menuTreeItem.children &&
|
||||||
|
menuTreeItem.children.length > 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
prefixCls,
|
||||||
|
menuHasChildren,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
43
src/components/Menu/src/components/ExpandIcon.vue
Normal file
43
src/components/Menu/src/components/ExpandIcon.vue
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<template>
|
||||||
|
<BasicArrow :expand="getIsOpen" bottom inset :class="getWrapperClass" />
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, PropType, computed } from 'vue';
|
||||||
|
import { useDesign } from '/@/hooks/web/useDesign';
|
||||||
|
import { BasicArrow } from '/@/components/Basic';
|
||||||
|
|
||||||
|
import { propTypes } from '/@/utils/propTypes';
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'BasicMenuItem',
|
||||||
|
components: { BasicArrow },
|
||||||
|
props: {
|
||||||
|
key: propTypes.string,
|
||||||
|
openKeys: {
|
||||||
|
type: Array as PropType<string[]>,
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
|
collapsed: propTypes.bool,
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const { prefixCls } = useDesign('basic-menu');
|
||||||
|
|
||||||
|
const getIsOpen = computed(() => {
|
||||||
|
return props.openKeys.includes(props.key);
|
||||||
|
});
|
||||||
|
|
||||||
|
const getWrapperClass = computed(() => {
|
||||||
|
return [
|
||||||
|
`${prefixCls}__expand-icon`,
|
||||||
|
{
|
||||||
|
[`${prefixCls}__expand-icon--collapsed`]: props.collapsed,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
prefixCls,
|
||||||
|
getIsOpen,
|
||||||
|
getWrapperClass,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
@ -1,12 +0,0 @@
|
|||||||
import type { Menu as MenuType } from '/@/router/types';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: Whether the menu has child nodes
|
|
||||||
*/
|
|
||||||
export function menuHasChildren(menuTreeItem: MenuType): boolean {
|
|
||||||
return (
|
|
||||||
Reflect.has(menuTreeItem, 'children') &&
|
|
||||||
!!menuTreeItem.children &&
|
|
||||||
menuTreeItem.children.length > 0
|
|
||||||
);
|
|
||||||
}
|
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
.active-style() {
|
.active-style() {
|
||||||
color: @white;
|
color: @white;
|
||||||
|
// background: @primary-color !important;
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
118deg,
|
118deg,
|
||||||
rgba(@primary-color, 0.8),
|
rgba(@primary-color, 0.8),
|
||||||
@ -27,6 +28,7 @@
|
|||||||
// right: 16px;
|
// right: 16px;
|
||||||
// width: 10px;
|
// width: 10px;
|
||||||
// transform-origin: none;
|
// transform-origin: none;
|
||||||
|
// opacity: 0.45;
|
||||||
|
|
||||||
// span[role='img'] {
|
// span[role='img'] {
|
||||||
// margin-right: 0;
|
// margin-right: 0;
|
||||||
@ -52,9 +54,9 @@
|
|||||||
> .ant-menu-item-group-list
|
> .ant-menu-item-group-list
|
||||||
> .ant-menu-submenu
|
> .ant-menu-submenu
|
||||||
> .ant-menu-submenu-title,
|
> .ant-menu-submenu-title,
|
||||||
&.ant-menu-inline-collapsed > .ant-menu-submenu > .ant-menu-submenu-title {
|
&.ant-menu-inline-collapsed .ant-menu-submenu-title {
|
||||||
padding-right: 20px !important;
|
padding-right: 16px !important;
|
||||||
padding-left: 20px !important;
|
padding-left: 16px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,32 +89,33 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// .ant-menu-item {
|
.ant-menu-item {
|
||||||
// transition: unset;
|
transition: unset;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// scrollbar -s tart
|
// scrollbar -s tart
|
||||||
&-wrapper {
|
// &-wrapper {
|
||||||
/* 滚动槽 */
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
width: 5px;
|
|
||||||
height: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-track {
|
/* 滚动槽 */
|
||||||
background: rgba(0, 0, 0, 0);
|
// &::-webkit-scrollbar {
|
||||||
}
|
// width: 5px;
|
||||||
|
// height: 5px;
|
||||||
|
// }
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
// &::-webkit-scrollbar-track {
|
||||||
background: rgba(255, 255, 255, 0.2);
|
// background: rgba(0, 0, 0, 0);
|
||||||
border-radius: 3px;
|
// }
|
||||||
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb:hover {
|
// &::-webkit-scrollbar-thumb {
|
||||||
background: @border-color-dark;
|
// background: rgba(255, 255, 255, 0.2);
|
||||||
}
|
// border-radius: 3px;
|
||||||
}
|
// box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// ::-webkit-scrollbar-thumb:hover {
|
||||||
|
// background: @border-color-dark;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
// scrollbar end
|
// scrollbar end
|
||||||
|
|
||||||
@ -225,14 +228,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(.@{basic-menu-prefix-cls}__sidebar-hor).ant-menu-inline-collapsed {
|
|
||||||
.@{basic-menu-prefix-cls}-item__level1 {
|
|
||||||
> div {
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.ant-menu-dark:not(.@{basic-menu-prefix-cls}__sidebar-hor):not(.@{basic-menu-prefix-cls}__second) {
|
&.ant-menu-dark:not(.@{basic-menu-prefix-cls}__sidebar-hor):not(.@{basic-menu-prefix-cls}__second) {
|
||||||
// Reset menu item row height
|
// Reset menu item row height
|
||||||
.ant-menu-item:not(.@{basic-menu-prefix-cls}-item__level1),
|
.ant-menu-item:not(.@{basic-menu-prefix-cls}-item__level1),
|
||||||
@ -255,10 +250,6 @@
|
|||||||
background: @sider-dark-bg-color;
|
background: @sider-dark-bg-color;
|
||||||
.active-menu-style();
|
.active-menu-style();
|
||||||
|
|
||||||
// .menu-item-icon.app-iconify {
|
|
||||||
// display: inline-block !important;
|
|
||||||
// }
|
|
||||||
|
|
||||||
.ant-menu-item.ant-menu-item-selected.@{basic-menu-prefix-cls}-menu-item__level1,
|
.ant-menu-item.ant-menu-item-selected.@{basic-menu-prefix-cls}-menu-item__level1,
|
||||||
.ant-menu-submenu-selected.@{basic-menu-prefix-cls}-menu-item__level1 {
|
.ant-menu-submenu-selected.@{basic-menu-prefix-cls}-menu-item__level1 {
|
||||||
color: @white;
|
color: @white;
|
||||||
@ -304,10 +295,6 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-right: none;
|
border-right: none;
|
||||||
|
|
||||||
// .menu-item-icon.app-iconify {
|
|
||||||
// display: inline-block !important;
|
|
||||||
// }
|
|
||||||
|
|
||||||
.ant-menu-item.ant-menu-item-selected.@{basic-menu-prefix-cls}-menu-item__level1,
|
.ant-menu-item.ant-menu-item-selected.@{basic-menu-prefix-cls}-menu-item__level1,
|
||||||
.ant-menu-submenu-selected.@{basic-menu-prefix-cls}-menu-item__level1 {
|
.ant-menu-submenu-selected.@{basic-menu-prefix-cls}-menu-item__level1 {
|
||||||
color: @primary-color;
|
color: @primary-color;
|
||||||
@ -332,6 +319,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.@{basic-menu-prefix-cls}__tag {
|
.@{basic-menu-prefix-cls}__tag {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: calc(50% - 8px);
|
top: calc(50% - 8px);
|
||||||
@ -368,6 +356,20 @@
|
|||||||
background: @warning-color;
|
background: @warning-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-menu-submenu,
|
||||||
|
.ant-menu-submenu-inline {
|
||||||
|
transition: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// .ant-menu-submenu-arrow {
|
||||||
|
// transition: all 0.15s ease 0s;
|
||||||
|
// }
|
||||||
|
|
||||||
|
.ant-menu-inline.ant-menu-sub {
|
||||||
|
box-shadow: unset !important;
|
||||||
|
transition: unset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-menu-dark {
|
.ant-menu-dark {
|
||||||
@ -375,7 +377,6 @@
|
|||||||
> ul {
|
> ul {
|
||||||
background: @sider-dark-bg-color;
|
background: @sider-dark-bg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.active-menu-style();
|
.active-menu-style();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,57 +3,47 @@ import type { PropType } from 'vue';
|
|||||||
|
|
||||||
import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
|
import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
|
||||||
import { ThemeEnum } from '/@/enums/appEnum';
|
import { ThemeEnum } from '/@/enums/appEnum';
|
||||||
|
import { propTypes } from '/@/utils/propTypes';
|
||||||
export const basicProps = {
|
export const basicProps = {
|
||||||
items: {
|
items: {
|
||||||
type: Array as PropType<Menu[]>,
|
type: Array as PropType<Menu[]>,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
appendClass: {
|
appendClass: propTypes.bool,
|
||||||
type: Boolean as PropType<boolean>,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
collapsedShowTitle: {
|
collapsedShowTitle: propTypes.bool,
|
||||||
type: Boolean as PropType<boolean>,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
// 最好是4 倍数
|
// 最好是4 倍数
|
||||||
inlineIndent: {
|
inlineIndent: propTypes.number.def(20),
|
||||||
type: Number as PropType<number>,
|
|
||||||
default: 20,
|
|
||||||
},
|
|
||||||
// 菜单组件的mode属性
|
// 菜单组件的mode属性
|
||||||
mode: {
|
mode: {
|
||||||
type: String as PropType<MenuModeEnum>,
|
type: String as PropType<MenuModeEnum>,
|
||||||
default: MenuModeEnum.INLINE,
|
default: MenuModeEnum.INLINE,
|
||||||
},
|
},
|
||||||
showLogo: {
|
showLogo: propTypes.bool,
|
||||||
type: Boolean as PropType<boolean>,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
type: {
|
type: {
|
||||||
type: String as PropType<MenuTypeEnum>,
|
type: String as PropType<MenuTypeEnum>,
|
||||||
default: MenuTypeEnum.MIX,
|
default: MenuTypeEnum.MIX,
|
||||||
},
|
},
|
||||||
theme: {
|
theme: propTypes.string.def(ThemeEnum.DARK),
|
||||||
type: String as PropType<string>,
|
inlineCollapsed: propTypes.bool,
|
||||||
default: ThemeEnum.DARK,
|
|
||||||
},
|
|
||||||
inlineCollapsed: {
|
|
||||||
type: Boolean as PropType<boolean>,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
isHorizontal: {
|
isHorizontal: propTypes.bool,
|
||||||
type: Boolean as PropType<boolean>,
|
accordion: propTypes.bool.def(true),
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
accordion: {
|
|
||||||
type: Boolean as PropType<boolean>,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
beforeClickFn: {
|
beforeClickFn: {
|
||||||
type: Function as PropType<(key: string) => Promise<boolean>>,
|
type: Function as PropType<(key: string) => Promise<boolean>>,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const itemProps = {
|
||||||
|
item: {
|
||||||
|
type: Object as PropType<Menu>,
|
||||||
|
default: {},
|
||||||
|
},
|
||||||
|
level: propTypes.number,
|
||||||
|
theme: propTypes.oneOf(['dark', 'light']),
|
||||||
|
appendClass: propTypes.bool,
|
||||||
|
parentPath: propTypes.string,
|
||||||
|
showTitle: propTypes.bool,
|
||||||
|
isHorizontal: propTypes.bool,
|
||||||
|
};
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import { ComputedRef } from 'vue';
|
// import { ComputedRef } from 'vue';
|
||||||
import { ThemeEnum } from '/@/enums/appEnum';
|
// import { ThemeEnum } from '/@/enums/appEnum';
|
||||||
import { MenuModeEnum } from '/@/enums/menuEnum';
|
// import { MenuModeEnum } from '/@/enums/menuEnum';
|
||||||
export interface MenuState {
|
export interface MenuState {
|
||||||
// 默认选中的列表
|
// 默认选中的列表
|
||||||
defaultSelectedKeys: string[];
|
defaultSelectedKeys: string[];
|
||||||
|
|
||||||
// 模式
|
// 模式
|
||||||
mode: MenuModeEnum;
|
// mode: MenuModeEnum;
|
||||||
|
|
||||||
// 主题
|
// // 主题
|
||||||
theme: ComputedRef<ThemeEnum> | ThemeEnum;
|
// theme: ComputedRef<ThemeEnum> | ThemeEnum;
|
||||||
|
|
||||||
// 缩进
|
// 缩进
|
||||||
inlineIndent?: number;
|
inlineIndent?: number;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export const SIDE_BAR_MINI_WIDTH = 58;
|
export const SIDE_BAR_MINI_WIDTH = 48;
|
||||||
export const SIDE_BAR_SHOW_TIT_MINI_WIDTH = 80;
|
export const SIDE_BAR_SHOW_TIT_MINI_WIDTH = 80;
|
||||||
|
|
||||||
export enum ContentEnum {
|
export enum ContentEnum {
|
||||||
|
@ -34,9 +34,9 @@
|
|||||||
border: 1px solid darken(@border-color-light, 6%);
|
border: 1px solid darken(@border-color-light, 6%);
|
||||||
transition: none;
|
transition: none;
|
||||||
|
|
||||||
&:not(.ant-tabs-tab-active)::before {
|
&:not(.ant-tabs-tab-active)::after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -1px;
|
bottom: -1px;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 2px;
|
height: 2px;
|
||||||
@ -53,7 +53,7 @@
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(.ant-tabs-tab-active)::before {
|
&:not(.ant-tabs-tab-active)::after {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translate(-50%, 0) scaleX(1);
|
transform: translate(-50%, 0) scaleX(1);
|
||||||
transition: all 0.3s ease-in-out;
|
transition: all 0.3s ease-in-out;
|
||||||
|
@ -42,6 +42,7 @@ export function transformMenuModule(menuModule: MenuModule): Menu {
|
|||||||
forEach(menuList, (m) => {
|
forEach(menuList, (m) => {
|
||||||
!isUrl(m.path) && joinParentPath(menuList, m);
|
!isUrl(m.path) && joinParentPath(menuList, m);
|
||||||
});
|
});
|
||||||
|
|
||||||
return menuList[0];
|
return menuList[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user