mirror of
https://github.com/vbenjs/vben-admin-thin-next.git
synced 2025-01-23 17:50:22 +08:00
wip(menu): perf menu
This commit is contained in:
parent
f81c401959
commit
cbcd909867
@ -66,8 +66,9 @@
|
||||
.@{prefix-cls} {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 12px;
|
||||
padding-left: 7px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&.collapsed-show-title {
|
||||
padding-left: 20px;
|
||||
|
@ -2,6 +2,8 @@ import { withInstall } from '../util';
|
||||
|
||||
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);
|
||||
|
@ -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() {
|
||||
color: @white;
|
||||
// background: @primary-color !important;
|
||||
background: linear-gradient(
|
||||
118deg,
|
||||
rgba(@primary-color, 0.8),
|
||||
@ -27,6 +28,7 @@
|
||||
// right: 16px;
|
||||
// width: 10px;
|
||||
// transform-origin: none;
|
||||
// opacity: 0.45;
|
||||
|
||||
// span[role='img'] {
|
||||
// margin-right: 0;
|
||||
@ -52,9 +54,9 @@
|
||||
> .ant-menu-item-group-list
|
||||
> .ant-menu-submenu
|
||||
> .ant-menu-submenu-title,
|
||||
&.ant-menu-inline-collapsed > .ant-menu-submenu > .ant-menu-submenu-title {
|
||||
padding-right: 20px !important;
|
||||
padding-left: 20px !important;
|
||||
&.ant-menu-inline-collapsed .ant-menu-submenu-title {
|
||||
padding-right: 16px !important;
|
||||
padding-left: 16px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,32 +89,33 @@
|
||||
}
|
||||
}
|
||||
|
||||
// .ant-menu-item {
|
||||
// transition: unset;
|
||||
// }
|
||||
.ant-menu-item {
|
||||
transition: unset;
|
||||
}
|
||||
|
||||
// scrollbar -s tart
|
||||
&-wrapper {
|
||||
/* 滚动槽 */
|
||||
&::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
}
|
||||
// &-wrapper {
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0);
|
||||
}
|
||||
/* 滚动槽 */
|
||||
// &::-webkit-scrollbar {
|
||||
// width: 5px;
|
||||
// height: 5px;
|
||||
// }
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 3px;
|
||||
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
// &::-webkit-scrollbar-track {
|
||||
// background: rgba(0, 0, 0, 0);
|
||||
// }
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: @border-color-dark;
|
||||
}
|
||||
}
|
||||
// &::-webkit-scrollbar-thumb {
|
||||
// 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
|
||||
|
||||
@ -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) {
|
||||
// Reset menu item row height
|
||||
.ant-menu-item:not(.@{basic-menu-prefix-cls}-item__level1),
|
||||
@ -255,10 +250,6 @@
|
||||
background: @sider-dark-bg-color;
|
||||
.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-submenu-selected.@{basic-menu-prefix-cls}-menu-item__level1 {
|
||||
color: @white;
|
||||
@ -304,10 +295,6 @@
|
||||
overflow: hidden;
|
||||
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-submenu-selected.@{basic-menu-prefix-cls}-menu-item__level1 {
|
||||
color: @primary-color;
|
||||
@ -332,6 +319,7 @@
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.@{basic-menu-prefix-cls}__tag {
|
||||
position: absolute;
|
||||
top: calc(50% - 8px);
|
||||
@ -368,6 +356,20 @@
|
||||
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 {
|
||||
@ -375,7 +377,6 @@
|
||||
> ul {
|
||||
background: @sider-dark-bg-color;
|
||||
}
|
||||
|
||||
.active-menu-style();
|
||||
}
|
||||
}
|
||||
|
@ -3,57 +3,47 @@ import type { PropType } from 'vue';
|
||||
|
||||
import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
|
||||
import { ThemeEnum } from '/@/enums/appEnum';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
export const basicProps = {
|
||||
items: {
|
||||
type: Array as PropType<Menu[]>,
|
||||
default: () => [],
|
||||
},
|
||||
appendClass: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
appendClass: propTypes.bool,
|
||||
|
||||
collapsedShowTitle: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
collapsedShowTitle: propTypes.bool,
|
||||
|
||||
// 最好是4 倍数
|
||||
inlineIndent: {
|
||||
type: Number as PropType<number>,
|
||||
default: 20,
|
||||
},
|
||||
inlineIndent: propTypes.number.def(20),
|
||||
// 菜单组件的mode属性
|
||||
mode: {
|
||||
type: String as PropType<MenuModeEnum>,
|
||||
default: MenuModeEnum.INLINE,
|
||||
},
|
||||
showLogo: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
showLogo: propTypes.bool,
|
||||
type: {
|
||||
type: String as PropType<MenuTypeEnum>,
|
||||
default: MenuTypeEnum.MIX,
|
||||
},
|
||||
theme: {
|
||||
type: String as PropType<string>,
|
||||
default: ThemeEnum.DARK,
|
||||
},
|
||||
inlineCollapsed: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
theme: propTypes.string.def(ThemeEnum.DARK),
|
||||
inlineCollapsed: propTypes.bool,
|
||||
|
||||
isHorizontal: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
accordion: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true,
|
||||
},
|
||||
isHorizontal: propTypes.bool,
|
||||
accordion: propTypes.bool.def(true),
|
||||
beforeClickFn: {
|
||||
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 { ThemeEnum } from '/@/enums/appEnum';
|
||||
import { MenuModeEnum } from '/@/enums/menuEnum';
|
||||
// import { ComputedRef } from 'vue';
|
||||
// import { ThemeEnum } from '/@/enums/appEnum';
|
||||
// import { MenuModeEnum } from '/@/enums/menuEnum';
|
||||
export interface MenuState {
|
||||
// 默认选中的列表
|
||||
defaultSelectedKeys: string[];
|
||||
|
||||
// 模式
|
||||
mode: MenuModeEnum;
|
||||
// mode: MenuModeEnum;
|
||||
|
||||
// 主题
|
||||
theme: ComputedRef<ThemeEnum> | ThemeEnum;
|
||||
// // 主题
|
||||
// theme: ComputedRef<ThemeEnum> | ThemeEnum;
|
||||
|
||||
// 缩进
|
||||
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 enum ContentEnum {
|
||||
|
@ -34,9 +34,9 @@
|
||||
border: 1px solid darken(@border-color-light, 6%);
|
||||
transition: none;
|
||||
|
||||
&:not(.ant-tabs-tab-active)::before {
|
||||
&:not(.ant-tabs-tab-active)::after {
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
bottom: -1px;
|
||||
left: 50%;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
@ -53,7 +53,7 @@
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:not(.ant-tabs-tab-active)::before {
|
||||
&:not(.ant-tabs-tab-active)::after {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, 0) scaleX(1);
|
||||
transition: all 0.3s ease-in-out;
|
||||
|
@ -42,6 +42,7 @@ export function transformMenuModule(menuModule: MenuModule): Menu {
|
||||
forEach(menuList, (m) => {
|
||||
!isUrl(m.path) && joinParentPath(menuList, m);
|
||||
});
|
||||
|
||||
return menuList[0];
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user