- 新增产品管理相关路由和页面(dashboard、list、requirement、setting) - 实现产品基础信息编辑弹窗组件(base-info-dialog.vue) - 添加运行时字典功能(dict-select、dict-text、dict-tag组件) - 集成字典管理store和API调用 - 规范ID类型定义为string避免精度丢失问题 - 完善国际化资源文件支持中英文对照 - 新增对象上下文业务域入口页导航实现说明 - 添加Vue DevTools浮动入口注释说明 - 统一权限控制支持全局和对象作用域区分 - 规范分页查询参数类型定义与使用方式
136 lines
4.5 KiB
Vue
136 lines
4.5 KiB
Vue
<script setup lang="ts">
|
|
import { computed, ref, watch } from 'vue';
|
|
import { useRoute } from 'vue-router';
|
|
import { SimpleScrollbar } from '@sa/materials';
|
|
import { useBoolean } from '@sa/hooks';
|
|
import type { RouteKey } from '@elegant-router/types';
|
|
import { GLOBAL_SIDER_MENU_ID } from '@/constants/app';
|
|
import { getObjectContextDomainConfigByPath } from '@/constants/object-context';
|
|
import { useAppStore } from '@/store/modules/app';
|
|
import { useObjectContextStore } from '@/store/modules/object-context';
|
|
import { useThemeStore } from '@/store/modules/theme';
|
|
import { useRouteStore } from '@/store/modules/route';
|
|
import { useRouterPush } from '@/hooks/common/router';
|
|
import { $t } from '@/locales';
|
|
import { useMenu, useMixMenuContext } from '../../../context';
|
|
import FirstLevelMenu from '../components/first-level-menu.vue';
|
|
import GlobalLogo from '../../global-logo/index.vue';
|
|
import MenuItem from '../components/menu-item.vue';
|
|
|
|
defineOptions({
|
|
name: 'VerticalMixMenu'
|
|
});
|
|
|
|
const route = useRoute();
|
|
const appStore = useAppStore();
|
|
const objectContextStore = useObjectContextStore();
|
|
const themeStore = useThemeStore();
|
|
const routeStore = useRouteStore();
|
|
const { routerPush, routerPushByKeyWithMetaQuery } = useRouterPush();
|
|
const { bool: drawerVisible, setBool: setDrawerVisible } = useBoolean();
|
|
const {
|
|
allMenus,
|
|
childLevelMenus,
|
|
activeFirstLevelMenuKey,
|
|
setActiveFirstLevelMenuKey,
|
|
getActiveFirstLevelMenuKey
|
|
//
|
|
} = useMixMenuContext();
|
|
const { selectedKey, selectedKeyDummy, handleSelect } = useMenu();
|
|
|
|
const inverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted);
|
|
|
|
const hasChildMenus = computed(() => childLevelMenus.value.length > 0);
|
|
|
|
const showDrawer = computed(() => hasChildMenus.value && (drawerVisible.value || appStore.mixSiderFixed));
|
|
|
|
function handleSelectMixMenu(menu: App.Global.Menu) {
|
|
setActiveFirstLevelMenuKey(menu.key);
|
|
|
|
const domainConfig = getObjectContextDomainConfigByPath(menu.routePath);
|
|
|
|
if (domainConfig) {
|
|
objectContextStore.clearContext();
|
|
routerPush({ path: domainConfig.entryRoutePath });
|
|
return;
|
|
}
|
|
|
|
if (menu.children?.length) {
|
|
setDrawerVisible(true);
|
|
} else {
|
|
routerPushByKeyWithMetaQuery(menu.routeKey);
|
|
}
|
|
}
|
|
|
|
function handleResetActiveMenu() {
|
|
setDrawerVisible(false);
|
|
|
|
if (!appStore.mixSiderFixed) {
|
|
getActiveFirstLevelMenuKey();
|
|
}
|
|
}
|
|
|
|
const expandedKeys = ref<string[]>([]);
|
|
|
|
function updateExpandedKeys() {
|
|
if (appStore.siderCollapse || !selectedKey.value) {
|
|
expandedKeys.value = [];
|
|
return;
|
|
}
|
|
expandedKeys.value = routeStore.getSelectedMenuKeyPath(selectedKey.value);
|
|
}
|
|
|
|
watch(
|
|
() => route.name,
|
|
() => {
|
|
updateExpandedKeys();
|
|
},
|
|
{ immediate: true }
|
|
);
|
|
</script>
|
|
|
|
<template>
|
|
<Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">
|
|
<div class="h-full flex" @mouseleave="handleResetActiveMenu">
|
|
<FirstLevelMenu
|
|
:menus="allMenus"
|
|
:active-menu-key="activeFirstLevelMenuKey"
|
|
:inverted="inverted"
|
|
:sider-collapse="appStore.siderCollapse"
|
|
:dark-mode="themeStore.darkMode"
|
|
:theme-color="themeStore.themeColor"
|
|
@select="handleSelectMixMenu"
|
|
@toggle-sider-collapse="appStore.toggleSiderCollapse"
|
|
>
|
|
<GlobalLogo :show-title="false" :style="{ height: themeStore.header.height + 'px' }" />
|
|
</FirstLevelMenu>
|
|
<div
|
|
class="relative h-full transition-width-300"
|
|
:style="{ width: appStore.mixSiderFixed && hasChildMenus ? themeStore.sider.mixChildMenuWidth + 'px' : '0px' }"
|
|
>
|
|
<DarkModeContainer
|
|
class="absolute-lt h-full flex-col-stretch nowrap-hidden shadow-sm transition-all-300"
|
|
:inverted="inverted"
|
|
:style="{ width: showDrawer ? themeStore.sider.mixChildMenuWidth + 'px' : '0px' }"
|
|
>
|
|
<header class="flex-y-center justify-between px-12px" :style="{ height: themeStore.header.height + 'px' }">
|
|
<h2 class="text-16px text-primary font-bold">{{ $t('system.title') }}</h2>
|
|
<PinToggler
|
|
:pin="appStore.mixSiderFixed"
|
|
:class="{ 'text-white:88 !hover:text-white': inverted }"
|
|
@click="appStore.toggleMixSiderFixed"
|
|
/>
|
|
</header>
|
|
<SimpleScrollbar>
|
|
<ElMenu mode="vertical" :default-active="selectedKeyDummy" @select="val => handleSelect(val as RouteKey)">
|
|
<MenuItem v-for="item in childLevelMenus" :key="item.key" :item="item" :index="item.key" />
|
|
</ElMenu>
|
|
</SimpleScrollbar>
|
|
</DarkModeContainer>
|
|
</div>
|
|
</div>
|
|
</Teleport>
|
|
</template>
|
|
|
|
<style scoped></style>
|