From a1d7e81859f554f3a53680cc35f0f49bf1f77098 Mon Sep 17 00:00:00 2001
From: wwf <1971391498@qq.com>
Date: 星期四, 14 五月 2026 14:37:02 +0800
Subject: [PATCH] 导入项目

---
 src/layout/components/Menu/src/Menu.vue |  272 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 272 insertions(+), 0 deletions(-)

diff --git a/src/layout/components/Menu/src/Menu.vue b/src/layout/components/Menu/src/Menu.vue
new file mode 100644
index 0000000..94a1da4
--- /dev/null
+++ b/src/layout/components/Menu/src/Menu.vue
@@ -0,0 +1,272 @@
+<script lang="tsx">
+import { PropType } from 'vue'
+import { ElMenu, ElScrollbar } from 'element-plus'
+import { useAppStore } from '@/store/modules/app'
+import { usePermissionStore } from '@/store/modules/permission'
+import { useRenderMenuItem } from './components/useRenderMenuItem'
+import { isUrl } from '@/utils/is'
+import { useDesign } from '@/hooks/web/useDesign'
+import { LayoutType } from '@/types/layout'
+
+const { getPrefixCls } = useDesign()
+
+const prefixCls = getPrefixCls('menu')
+
+export default defineComponent({
+  // eslint-disable-next-line vue/no-reserved-component-names
+  name: 'Menu',
+  props: {
+    menuSelect: {
+      type: Function as PropType<(index: string) => void>,
+      default: undefined
+    }
+  },
+  setup(props) {
+    const appStore = useAppStore()
+
+    const layout = computed(() => appStore.getLayout)
+
+    const { push, currentRoute } = useRouter()
+
+    const permissionStore = usePermissionStore()
+
+    const menuMode = computed((): 'vertical' | 'horizontal' => {
+      // 绔�
+      const vertical: LayoutType[] = ['classic', 'topLeft', 'cutMenu']
+
+      if (vertical.includes(unref(layout))) {
+        return 'vertical'
+      } else {
+        return 'horizontal'
+      }
+    })
+
+    const routers = computed(() =>
+      unref(layout) === 'cutMenu' ? permissionStore.getMenuTabRouters : permissionStore.getRouters
+    )
+
+    const collapse = computed(() => appStore.getCollapse)
+
+    const uniqueOpened = computed(() => appStore.getUniqueOpened)
+
+    const activeMenu = computed(() => {
+      const { meta, path } = unref(currentRoute)
+      // if set path, the sidebar will highlight the path you set
+      if (meta.activeMenu) {
+        return meta.activeMenu as string
+      }
+      return path
+    })
+
+    const menuSelect = (index: string) => {
+      if (props.menuSelect) {
+        props.menuSelect(index)
+      }
+      // 鑷畾涔変簨浠�
+      if (isUrl(index)) {
+        window.open(index)
+      } else {
+        push(index)
+      }
+    }
+
+    const renderMenuWrap = () => {
+      if (unref(layout) === 'top') {
+        return renderMenu()
+      } else {
+        return <ElScrollbar>{renderMenu()}</ElScrollbar>
+      }
+    }
+
+    const renderMenu = () => {
+      return (
+        <ElMenu
+          defaultActive={unref(activeMenu)}
+          mode={unref(menuMode)}
+          collapse={
+            unref(layout) === 'top' || unref(layout) === 'cutMenu' ? false : unref(collapse)
+          }
+          uniqueOpened={unref(layout) === 'top' ? false : unref(uniqueOpened)}
+          backgroundColor="var(--left-menu-bg-color)"
+          textColor="var(--left-menu-text-color)"
+          activeTextColor="var(--left-menu-text-active-color)"
+          popperClass={
+            unref(menuMode) === 'vertical'
+              ? `${prefixCls}-popper--vertical`
+              : `${prefixCls}-popper--horizontal`
+          }
+          onSelect={menuSelect}
+        >
+          {{
+            default: () => {
+              const { renderMenuItem } = useRenderMenuItem(unref(menuMode))
+              return renderMenuItem(unref(routers))
+            }
+          }}
+        </ElMenu>
+      )
+    }
+
+    return () => (
+      <div
+        id={prefixCls}
+        class={[
+          `${prefixCls} ${prefixCls}__${unref(menuMode)}`,
+          'h-[100%] overflow-hidden flex-col bg-[var(--left-menu-bg-color)]',
+          {
+            'w-[var(--left-menu-min-width)]': unref(collapse) && unref(layout) !== 'cutMenu',
+            'w-[var(--left-menu-max-width)]': !unref(collapse) && unref(layout) !== 'cutMenu'
+          }
+        ]}
+      >
+        {renderMenuWrap()}
+      </div>
+    )
+  }
+})
+</script>
+
+<style lang="scss" scoped>
+$prefix-cls: #{$namespace}-menu;
+
+.#{$prefix-cls} {
+  position: relative;
+  transition: width var(--transition-time-02);
+
+  :deep(.#{$elNamespace}-menu) {
+    width: 100% !important;
+    border-right: none;
+
+    // 璁剧疆閫変腑鏃跺瓙鏍囬鐨勯鑹�
+    .is-active {
+      & > .#{$elNamespace}-sub-menu__title {
+        color: var(--left-menu-text-active-color) !important;
+      }
+    }
+
+    // 璁剧疆瀛愯彍鍗曟偓鍋滅殑楂樹寒鍜岃儗鏅壊
+    .#{$elNamespace}-sub-menu__title,
+    .#{$elNamespace}-menu-item {
+      &:hover {
+        color: var(--left-menu-text-active-color) !important;
+        background-color: var(--left-menu-bg-color) !important;
+      }
+    }
+
+    // 璁剧疆閫変腑鏃剁殑楂樹寒鑳屾櫙鍜岄珮浜鑹�
+    .#{$elNamespace}-menu-item.is-active {
+      color: var(--left-menu-text-active-color) !important;
+      background-color: var(--left-menu-bg-active-color) !important;
+
+      &:hover {
+        background-color: var(--left-menu-bg-active-color) !important;
+      }
+    }
+
+    .#{$elNamespace}-menu-item.is-active {
+      position: relative;
+    }
+
+    // 璁剧疆瀛愯彍鍗曠殑鑳屾櫙棰滆壊
+    .#{$elNamespace}-menu {
+      .#{$elNamespace}-sub-menu__title,
+      .#{$elNamespace}-menu-item:not(.is-active) {
+        background-color: var(--left-menu-bg-light-color) !important;
+      }
+    }
+  }
+
+  // 鎶樺彔鏃剁殑鏈�灏忓搴�
+  :deep(.#{$elNamespace}-menu--collapse) {
+    width: var(--left-menu-min-width);
+
+    & > .is-active,
+    & > .is-active > .#{$elNamespace}-sub-menu__title {
+      position: relative;
+      background-color: var(--left-menu-collapse-bg-active-color) !important;
+    }
+  }
+
+  // 鎶樺彔鍔ㄧ敾鐨勬椂鍊欙紝灏遍渶瑕佹妸鏂囧瓧缁欓殣钘忔帀
+  :deep(.horizontal-collapse-transition) {
+    // transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out !important;
+    .#{$prefix-cls}__title {
+      display: none;
+    }
+  }
+
+  // 鍨傜洿鑿滃崟
+  &__vertical {
+    :deep(.#{$elNamespace}-menu--vertical) {
+      &:not(.#{$elNamespace}-menu--collapse) .#{$elNamespace}-sub-menu__title,
+      .#{$elNamespace}-menu-item {
+        padding-right: 0;
+      }
+    }
+  }
+
+  // 姘村钩鑿滃崟
+  &__horizontal {
+    height: calc(var(--top-tool-height)) !important;
+
+    :deep(.#{$elNamespace}-menu--horizontal) {
+      height: calc(var(--top-tool-height));
+      border-bottom: none;
+      // 閲嶆柊璁剧疆搴曢儴楂樹寒棰滆壊
+      & > .#{$elNamespace}-sub-menu.is-active {
+        .#{$elNamespace}-sub-menu__title {
+          border-bottom-color: var(--el-color-primary) !important;
+        }
+      }
+
+      .#{$elNamespace}-menu-item.is-active {
+        position: relative;
+
+        &::after {
+          display: none !important;
+        }
+      }
+
+      .#{$prefix-cls}__title {
+        /* stylelint-disable-next-line */
+        max-height: calc(var(--top-tool-height) - 2px) !important;
+        /* stylelint-disable-next-line */
+        line-height: calc(var(--top-tool-height) - 2px);
+      }
+    }
+  }
+}
+</style>
+
+<style lang="scss">
+$prefix-cls: #{$namespace}-menu-popper;
+
+.#{$prefix-cls}--vertical,
+.#{$prefix-cls}--horizontal {
+  // 璁剧疆閫変腑鏃跺瓙鏍囬鐨勯鑹�
+  .is-active {
+    & > .el-sub-menu__title {
+      color: var(--left-menu-text-active-color) !important;
+    }
+  }
+
+  // 璁剧疆瀛愯彍鍗曟偓鍋滅殑楂樹寒鍜岃儗鏅壊
+  .el-sub-menu__title,
+  .el-menu-item {
+    &:hover {
+      color: var(--left-menu-text-active-color) !important;
+      background-color: var(--left-menu-bg-color) !important;
+    }
+  }
+
+  // 璁剧疆閫変腑鏃剁殑楂樹寒鑳屾櫙
+  .el-menu-item.is-active {
+    position: relative;
+    background-color: var(--left-menu-bg-active-color) !important;
+
+    &:hover {
+      background-color: var(--left-menu-bg-active-color) !important;
+    }
+  }
+}
+</style>

--
Gitblit v1.8.0