Files
kaidi/frontend/src/layouts/components/SideNav.vue
T
2026-08-17 23:03:03 +08:00

200 lines
5.0 KiB
Vue

<template>
<div :class="sideNavCls">
<t-menu
:class="menuCls"
:theme="theme"
:value="active"
:collapsed="collapsed"
:expanded="expanded"
:expand-mutex="menuAutoCollapsed"
@expand="onExpanded"
>
<template #logo>
<span v-if="showLogo" :class="`${prefix}-side-nav-logo-wrapper brand-logo`" @click="goHome">
<span class="brand-name" :class="{ 'brand-name--compact': collapsed }">财务系统</span>
</span>
</template>
<menu-content :nav-data="menu" />
<template #operations>
<t-button
variant="text"
shape="square"
:aria-label="collapsed ? '展开侧栏' : '收起侧栏'"
:title="collapsed ? '展开侧栏' : '收起侧栏'"
@click="changeCollapsed"
>
<template #icon><t-icon name="view-list" /></template>
</t-button>
<span v-show="!isCompact" :class="versionCls">
{{ !collapsed ? t('common.appName') : '' }} {{ pgk.version }}
</span>
</template>
</t-menu>
<div :class="`${prefix}-side-nav-placeholder${collapsed ? '-hidden' : ''}`"></div>
</div>
</template>
<script setup lang="ts">
import difference from 'lodash/difference';
import remove from 'lodash/remove';
import union from 'lodash/union';
import type { MenuValue } from 'tdesign-vue-next';
import type { PropType } from 'vue';
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
import { useRouter } from 'vue-router';
import { prefix } from '@/config/global';
import { t } from '@/locales';
import { getActive } from '@/router';
import { useSettingStore, useUserStore } from '@/store';
import type { MenuRoute, ModeType } from '@/types/interface';
import pgk from '../../../package.json';
import MenuContent from './MenuContent.vue';
const { menu, showLogo, isFixed, layout, theme, isCompact } = defineProps({
menu: {
type: Array as PropType<MenuRoute[]>,
default: () => [],
},
showLogo: {
type: Boolean as PropType<boolean>,
default: true,
},
isFixed: {
type: Boolean as PropType<boolean>,
default: true,
},
layout: {
type: String as PropType<string>,
default: '',
},
headerHeight: {
type: String as PropType<string>,
default: '64px',
},
theme: {
type: String as PropType<ModeType>,
default: 'light',
},
isCompact: {
type: Boolean as PropType<boolean>,
default: false,
},
});
const MIN_POINT = 992 - 1;
const collapsed = computed(() => useSettingStore().isSidebarCompact);
const menuAutoCollapsed = computed(() => useSettingStore().menuAutoCollapsed);
const active = computed(() => getActive());
const expanded = ref<MenuValue[]>([]);
const getExpanded = () => {
const path = getActive();
const parts = path.split('/').slice(1);
const result = parts.map((_, index) => `/${parts.slice(0, index + 1).join('/')}`);
const allRoutes = router.getRoutes();
const allRoutesExpanded = allRoutes.filter((item) => item.meta?.expanded).map((item) => item.path);
expanded.value = menuAutoCollapsed.value
? union(result, allRoutesExpanded)
: union(result, expanded.value, allRoutesExpanded);
};
watch(
() => active.value,
() => {
getExpanded();
},
);
const onExpanded = (value: MenuValue[]) => {
const currentOperationMenu = difference(expanded.value, value);
const allExpanded = union(value, expanded.value);
remove(allExpanded, (item) => currentOperationMenu.includes(item));
expanded.value = allExpanded;
};
const changeCollapsed = () => {
settingStore.updateConfig({
isSidebarCompact: !settingStore.isSidebarCompact,
});
};
const sideMode = computed(() => {
return theme === 'dark';
});
const versionCls = computed(() => {
return [
`version-container`,
{
[`${prefix}-side-nav-dark`]: sideMode.value,
},
];
});
const sideNavCls = computed(() => {
return [
`${prefix}-sidebar-layout`,
{
[`${prefix}-sidebar-compact`]: isCompact,
},
];
});
const menuCls = computed(() => {
return [
`${prefix}-side-nav`,
{
[`${prefix}-side-nav-no-logo`]: !showLogo,
[`${prefix}-side-nav-no-fixed`]: !isFixed,
[`${prefix}-side-nav-mix-fixed`]: layout === 'mix' && isFixed,
},
];
});
const router = useRouter();
const settingStore = useSettingStore();
const userStore = useUserStore();
const autoCollapsed = () => {
const isCompact = window.innerWidth <= MIN_POINT;
settingStore.updateConfig({
isSidebarCompact: isCompact,
});
};
onMounted(() => {
getExpanded();
autoCollapsed();
window.addEventListener('resize', autoCollapsed);
});
onUnmounted(() => {
window.removeEventListener('resize', autoCollapsed);
});
const goHome = () => {
router.push(userStore.workbenchRoute);
};
</script>
<style lang="less" scoped>
.brand-logo {
display: inline-flex;
align-items: center;
width: 100%;
cursor: pointer;
}
.brand-name {
color: var(--td-text-color-primary);
font-size: 14px;
font-weight: 600;
white-space: nowrap;
}
.brand-name--compact {
font-size: 12px;
}
</style>