// src/components/menu/MenuContent.tsx import { defineComponent, ref, h, compile, computed } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRoute, useRouter, RouteRecordRaw } from 'vue-router'; import type { RouteMeta } from 'vue-router'; import { useAppStore } from '@/store'; import { listenerRouteChange } from '@/utils/route-listener'; import { openWindow, regexUrl } from '@/utils'; import useMenuTree from './use-menu-tree'; // 直接导出 TSX 组件(无需 .vue 包裹) export default defineComponent({ setup() { const { t } = useI18n(); const appStore = useAppStore(); const router = useRouter(); const route = useRoute(); const { menuTree } = useMenuTree(); const collapsed = computed({ get() { if (appStore.device === 'desktop') return appStore.menuCollapse; return false; }, set(value: boolean) { appStore.updateSettings({ menuCollapse: value }); }, }); const topMenu = computed(() => appStore.topMenu); const openKeys = ref([]); const selectedKey = ref([]); const goto = (item: RouteRecordRaw) => { if (regexUrl.test(item.path)) { openWindow(item.path); selectedKey.value = [item.name as string]; return; } if (item.redirect) { router.push(item.redirect); return; } const { hideInMenu, activeMenu } = item.meta as RouteMeta; if (route.name === item.name && !hideInMenu && !activeMenu) { selectedKey.value = [item.name as string]; return; } router.push({ name: item.name }); }; const findMenuOpenKeys = (target: string) => { const result: string[] = []; let isFind = false; const backtrack = (item: RouteRecordRaw, keys: string[]) => { if (item.name === target) { isFind = true; result.push(...keys); return; } if (item.children?.length) { item.children.forEach((el) => backtrack(el, [...keys, el.name as string]) ); } }; menuTree.value.forEach((el: RouteRecordRaw) => { if (isFind) return; backtrack(el, [el.name as string]); }); return result; }; listenerRouteChange((newRoute) => { const { requiresAuth, activeMenu, hideInMenu } = newRoute.meta; if (requiresAuth && (!hideInMenu || activeMenu)) { const menuOpenKeys = findMenuOpenKeys( (activeMenu || newRoute.name) as string ); const keySet = new Set([...menuOpenKeys, ...openKeys.value]); openKeys.value = [...keySet]; let targetKey = activeMenu || newRoute.name; if (targetKey && !activeMenu) { const findParentWithHiddenChildren = ( routes: RouteRecordRaw[], targetName: string ): string | null => { for (const route of routes) { if (route.children) { const hasTargetChild = route.children.some( (child) => child.name === targetName || (child.children && findParentWithHiddenChildren([child], targetName)) ); if (hasTargetChild && route.meta?.hideChildrenInMenu) { return route.name as string; } const parentResult = findParentWithHiddenChildren( route.children, targetName ); if (parentResult) return parentResult; } } return null; }; const parentWithHiddenChildren = findParentWithHiddenChildren( menuTree.value, targetKey as string ); if (parentWithHiddenChildren) targetKey = parentWithHiddenChildren; } if (targetKey) selectedKey.value = [targetKey as string]; } }, true); const setCollapse = (val: boolean) => { if (appStore.device === 'desktop') appStore.updateSettings({ menuCollapse: val }); }; const renderSubMenu = () => { function travel(_route: RouteRecordRaw[], nodes = []) { if (_route) { _route.forEach((element) => { const icon = element?.meta?.icon ? () => h(compile(`<${element?.meta?.icon}/>`)) : null; const hideChildrenInMenu = element?.meta?.hideChildrenInMenu; const hasChildren = element?.children && element?.children.length !== 0; const node = hasChildren && !hideChildrenInMenu ? ( h(compile(t(element?.meta?.locale || ''))), }} > {element?.children ? travel(element?.children) : null} ) : ( goto(element)} > {t(element?.meta?.locale || '')} ); nodes.push(node as never); }); } return nodes; } return travel(menuTree.value); }; // 返回 TSX 渲染函数(直接渲染菜单) return () => ( {renderSubMenu()} ); }, });