useOpenedMenus.ts 2.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
  1. import { ref, watch } from "vue";
  2. import type { Ref } from "vue";
  3. import type { RouteLocationNormalizedLoaded } from "vue-router";
  4. interface UseOpenedMenusOptions {
  5. route: RouteLocationNormalizedLoaded;
  6. accordion: Ref<boolean>;
  7. }
  8. export const useOpenedMenus = ({ route, accordion }: UseOpenedMenusOptions) => {
  9. const openedMenus = ref<string[]>([]);
  10. const stringify = (arr: string[]) => JSON.stringify([...arr].sort());
  11. const normalizePath = (path: string) => (path.startsWith("/") ? path : `/${path}`);
  12. const collectAncestorPaths = (path?: string) => {
  13. if (!path) return [];
  14. const normalized = normalizePath(path);
  15. const segments = normalized.split("/").filter(Boolean);
  16. return segments.reduce<string[]>((acc, _, index) => {
  17. if (index === segments.length - 1) return acc;
  18. acc.push(`/${segments.slice(0, index + 1).join("/")}`);
  19. return acc;
  20. }, []);
  21. };
  22. const buildTargetOpenedPaths = () => {
  23. const targetSet = new Set<string>();
  24. collectAncestorPaths(route.path).forEach(path => targetSet.add(path));
  25. const activeMenuPath = route.meta.activeMenu as string | undefined;
  26. if (activeMenuPath) {
  27. const normalizedActive = normalizePath(activeMenuPath);
  28. collectAncestorPaths(normalizedActive).forEach(path => targetSet.add(path));
  29. targetSet.add(normalizedActive);
  30. }
  31. return Array.from(targetSet);
  32. };
  33. const calculateOpenedMenus = () => {
  34. const targetPaths = buildTargetOpenedPaths();
  35. if (!targetPaths.length) {
  36. return accordion.value ? [] : [...openedMenus.value];
  37. }
  38. if (accordion.value) return targetPaths;
  39. const menuSet = new Set(openedMenus.value);
  40. targetPaths.forEach(path => menuSet.add(path));
  41. return Array.from(menuSet);
  42. };
  43. watch(
  44. () => [route.path, route.meta.activeMenu, accordion.value],
  45. () => {
  46. const newOpenedMenus = calculateOpenedMenus();
  47. if (stringify(newOpenedMenus) !== stringify(openedMenus.value)) {
  48. openedMenus.value = newOpenedMenus;
  49. }
  50. },
  51. { immediate: true }
  52. );
  53. return {
  54. openedMenus
  55. };
  56. };