menu.vue 5.25 KB
<script lang="tsx">
import { compile, computed, defineComponent, h, ref, watch } from "vue";

import { RouteRecordRaw, useRoute, useRouter } from "vue-router";
import { useAppStore } from "@/store";
import usePermission from "@/hooks/permission";
import { last, orderBy } from "lodash";
import { storeToRefs } from "pinia";

export default defineComponent({
  emit: ["collapse"],
  setup() {
    const appStore = useAppStore();
    const permission = usePermission();
    const router = useRouter();
    const route = useRoute();

    const { appMenu } = storeToRefs(appStore);

    const collapsed = ref(false);
    const selectedKey = ref<string[]>([]);
    const openKey = ref<string[]>([]);

    const goto = (item: RouteRecordRaw) => router.push({ name: item.name });

    const syncServicePermission = (item: RouteRecordRaw) => {
      if (item.meta) {
        const serverConfig = appMenu.value.find((menu) => item.name === menu.name);
        item.meta.title = serverConfig?.label || item.meta?.title || "";
        item.meta.order = serverConfig?.weight || item.meta?.order || 0;
        item.meta.icon = serverConfig?.icon || item.meta?.icon || "";
      }
      item.children?.map((child) => syncServicePermission(child));
      return item;
    };

    const appRoute = computed((): RouteRecordRaw[] => {
      return router
        .getRoutes()
        .find((el) => el.name === "root")
        ?.children.filter((element) => element.meta?.hideInMenu !== true)
        .map((item: RouteRecordRaw) => syncServicePermission(item)) as RouteRecordRaw[];
    });

    const menuTree = computed((): RouteRecordRaw[] => {
      function travel(_routes: RouteRecordRaw[], layer: number) {
        if (!_routes) return null;
        const collector: any = orderBy(_routes, "meta.order", "desc").map((element) => {
          // no access
          if (!permission.accessRouter(element)) {
            return null;
          }

          // leaf node
          if (!element.children) {
            return element;
          }

          // route filter hideInMenu true
          element.children = element.children.filter((x) => x.meta?.hideInMenu !== true);

          // Associated child node
          const subItem = travel(element.children, layer);
          if (subItem.length) {
            element.children = subItem;
            return element;
          }
          // the else logic
          if (layer > 1) {
            element.children = subItem;
            return element;
          }

          if (element.meta?.hideInMenu === false) {
            return element;
          }

          return null;
        });
        return collector.filter(Boolean);
      }

      return travel(appRoute.value, 0);
    });

    watch(
      route,
      (newVal) => {
        if (newVal.meta.requiresAuth) {
          const key = newVal.meta.hideInMenu ? last(newVal.matched)?.meta?.menuSelectKey : last(newVal.matched)?.name;
          selectedKey.value = [key as string];
          openKey.value = [...(newVal.meta.breadcrumb || [])];
        }
      },
      { immediate: true }
    );
    watch(
      () => appStore.menuCollapse,
      (newVal) => {
        collapsed.value = newVal;
      },
      { immediate: true }
    );
    const setCollapse = (val: boolean) => {
      appStore.updateSettings({ menuCollapse: val });
    };

    const renderSubMenu = () => {
      function travel(_route: RouteRecordRaw[], nodes = []) {
        if (_route) {
          _route.forEach((element) => {
            // This is demo, modify nodes as needed
            const icon = element?.meta?.icon ? `<${element?.meta?.icon}/>` : ``;
            let r;

            if (element && element.children && element.children.length !== 0) {
              r = (
                <a-sub-menu key={element?.name} title={element.meta?.title} v-slots={{ icon: () => h(compile(icon)) }}>
                  {element?.children?.map((elem) => {
                    return (
                      <a-menu-item key={elem.name} onClick={() => goto(elem)}>
                        {elem.meta?.title}
                        {travel(elem.children ?? [])}
                      </a-menu-item>
                    );
                  })}
                </a-sub-menu>
              );
            } else {
              r = (
                <a-menu-item key={element?.name} v-slots={{ icon: () => h(compile(icon)) }}
                             onClick={() => goto(element)}>
                  {element.meta?.title}
                </a-menu-item>
              );
            }
            nodes.push(r as never);
          });
        }
        return nodes;
      }

      return travel(menuTree.value);
    };

    return () => (
      <a-menu
        v-model:collapsed={collapsed.value}
        show-collapse-button
        auto-open={false}
        v-model:selected-keys={selectedKey.value}
        v-model:open-keys={openKey.value}
        auto-open-selected={true}
        auto-scroll-into-view={true}
        level-indent={34}
        style={{ height: "100%" }}
        onCollapse={setCollapse}
      >
        {renderSubMenu()}
      </a-menu>
    );
  }
});
</script>

<style lang="less" scoped>
:deep(.arco-menu-inner) {
  .arco-menu-inline-header {
    display: flex;
    align-items: center;
  }

  .arco-icon {
    &:not(.arco-icon-down) {
      font-size: 18px;
    }
  }
}
</style>