Ben Lin
2024-06-19 f96d4ed77603ca1f908dcdc4a51bd2ce2178d10c
src/components/SimpleMenu/src/SimpleMenu.vue
@@ -1,10 +1,10 @@
<template>
  <Menu
    v-bind="getBindValues"
    :activeName="activeName"
    :activeName="menuState.activeName"
    :openNames="getOpenKeys"
    :class="prefixCls"
    :activeSubMenuNames="activeSubMenuNames"
    :activeSubMenuNames="menuState.activeSubMenuNames"
    @select="handleSelect"
  >
    <template v-for="item in items" :key="item.path">
@@ -17,144 +17,131 @@
    </template>
  </Menu>
</template>
<script lang="ts">
<script lang="ts" setup>
  import type { MenuState } from './types';
  import type { Menu as MenuType } from '/@/router/types';
  import type { Menu as MenuType } from '@/router/types';
  import type { RouteLocationNormalizedLoaded } from 'vue-router';
  import { defineComponent, computed, ref, unref, reactive, toRefs, watch, PropType } from 'vue';
  import { useDesign } from '/@/hooks/web/useDesign';
  import { computed, ref, unref, reactive, toRefs, watch, PropType, useAttrs } from 'vue';
  import { useDesign } from '@/hooks/web/useDesign';
  import Menu from './components/Menu.vue';
  import SimpleSubMenu from './SimpleSubMenu.vue';
  import { listenerRouteChange } from '/@/logics/mitt/routeChange';
  import { propTypes } from '/@/utils/propTypes';
  import { REDIRECT_NAME } from '/@/router/constant';
  import { listenerRouteChange } from '@/logics/mitt/routeChange';
  import { propTypes } from '@/utils/propTypes';
  import { REDIRECT_NAME } from '@/router/constant';
  import { useRouter } from 'vue-router';
  import { isFunction, isUrl } from '/@/utils/is';
  import { openWindow } from '/@/utils';
  import { isFunction, isHttpUrl } from '@/utils/is';
  import { openWindow } from '@/utils';
  import { useOpenKeys } from './useOpenKeys';
  export default defineComponent({
    name: 'SimpleMenu',
    components: {
      Menu,
      SimpleSubMenu,
  defineOptions({ name: 'SimpleMenu', inheritAttrs: false });
  const props = defineProps({
    items: {
      type: Array as PropType<MenuType[]>,
      default: () => [],
    },
    inheritAttrs: false,
    props: {
      items: {
        type: Array as PropType<MenuType[]>,
        default: () => [],
      },
      collapse: propTypes.bool,
      mixSider: propTypes.bool,
      theme: propTypes.string,
      accordion: propTypes.bool.def(true),
      collapsedShowTitle: propTypes.bool,
      beforeClickFn: {
        type: Function as PropType<(key: string) => Promise<boolean>>,
      },
      isSplitMenu: propTypes.bool,
    collapse: propTypes.bool,
    mixSider: propTypes.bool,
    theme: propTypes.string,
    accordion: propTypes.bool.def(true),
    collapsedShowTitle: propTypes.bool,
    beforeClickFn: {
      type: Function as PropType<(key: string) => Promise<boolean>>,
    },
    emits: ['menuClick'],
    setup(props, { attrs, emit }) {
      const currentActiveMenu = ref('');
      const isClickGo = ref(false);
      const menuState = reactive<MenuState>({
        activeName: '',
        openNames: [],
        activeSubMenuNames: [],
      });
      const { currentRoute } = useRouter();
      const { prefixCls } = useDesign('simple-menu');
      const { items, accordion, mixSider, collapse } = toRefs(props);
      const { setOpenKeys, getOpenKeys } = useOpenKeys(
        menuState,
        items,
        accordion,
        mixSider as any,
        collapse as any,
      );
      const getBindValues = computed(() => ({ ...attrs, ...props }));
      watch(
        () => props.collapse,
        (collapse) => {
          if (collapse) {
            menuState.openNames = [];
          } else {
            setOpenKeys(currentRoute.value.path);
          }
        },
        { immediate: true },
      );
      watch(
        () => props.items,
        () => {
          if (!props.isSplitMenu) {
            return;
          }
          setOpenKeys(currentRoute.value.path);
        },
        { flush: 'post' },
      );
      listenerRouteChange((route) => {
        if (route.name === REDIRECT_NAME) return;
        currentActiveMenu.value = route.meta?.currentActiveMenu as string;
        handleMenuChange(route);
        if (unref(currentActiveMenu)) {
          menuState.activeName = unref(currentActiveMenu);
          setOpenKeys(unref(currentActiveMenu));
        }
      });
      async function handleMenuChange(route?: RouteLocationNormalizedLoaded) {
        if (unref(isClickGo)) {
          isClickGo.value = false;
          return;
        }
        const path = (route || unref(currentRoute)).path;
        menuState.activeName = path;
        setOpenKeys(path);
      }
      async function handleSelect(key: string) {
        if (isUrl(key)) {
          openWindow(key);
          return;
        }
        const { beforeClickFn } = props;
        if (beforeClickFn && isFunction(beforeClickFn)) {
          const flag = await beforeClickFn(key);
          if (!flag) return;
        }
        emit('menuClick', key);
        isClickGo.value = true;
        setOpenKeys(key);
        menuState.activeName = key;
      }
      return {
        prefixCls,
        getBindValues,
        handleSelect,
        getOpenKeys,
        ...toRefs(menuState),
      };
    },
    isSplitMenu: propTypes.bool,
  });
  const emit = defineEmits(['menuClick']);
  const attrs = useAttrs();
  const currentActiveMenu = ref('');
  const isClickGo = ref(false);
  const menuState = reactive<MenuState>({
    activeName: '',
    openNames: [],
    activeSubMenuNames: [],
  });
  const { currentRoute } = useRouter();
  const { prefixCls } = useDesign('simple-menu');
  const { items, accordion, mixSider, collapse } = toRefs(props);
  const { setOpenKeys, getOpenKeys } = useOpenKeys(
    menuState,
    items,
    accordion,
    mixSider as any,
    collapse as any,
  );
  const getBindValues = computed(() => ({ ...attrs, ...props }));
  watch(
    () => props.collapse,
    (collapse) => {
      if (collapse) {
        menuState.openNames = [];
      } else {
        setOpenKeys(currentRoute.value.path);
      }
    },
    { immediate: true },
  );
  watch(
    () => props.items,
    () => {
      if (!props.isSplitMenu) {
        return;
      }
      setOpenKeys(currentRoute.value.path);
    },
    { flush: 'post' },
  );
  listenerRouteChange((route) => {
    if (route.name === REDIRECT_NAME) return;
    currentActiveMenu.value = route.meta?.currentActiveMenu as string;
    handleMenuChange(route);
    if (unref(currentActiveMenu)) {
      menuState.activeName = unref(currentActiveMenu);
      setOpenKeys(unref(currentActiveMenu));
    }
  });
  async function handleMenuChange(route?: RouteLocationNormalizedLoaded) {
    if (unref(isClickGo)) {
      isClickGo.value = false;
      return;
    }
    const path = (route || unref(currentRoute)).path;
    menuState.activeName = path;
    setOpenKeys(path);
  }
  async function handleSelect(key: string) {
    if (isHttpUrl(key)) {
      openWindow(key);
      return;
    }
    const { beforeClickFn } = props;
    if (beforeClickFn && isFunction(beforeClickFn)) {
      const flag = await beforeClickFn(key);
      if (!flag) return;
    }
    emit('menuClick', key);
    isClickGo.value = true;
    setOpenKeys(key);
    menuState.activeName = key;
  }
</script>
<style lang="less">
  @import url('./index.less');