Quellcode durchsuchen

feat: horizontal menu closes #3

nian vor 1 Monat
Ursprung
Commit
834cd6adfe

+ 101 - 0
src/layout/header/HorizontalMenu.vue

@@ -0,0 +1,101 @@
+<script setup lang="ts">
+import { isFunction, isEmpty } from 'lodash-es'
+import { NDropdown } from 'naive-ui'
+import { h, ref, watch } from 'vue'
+
+import router from '@/router'
+import { useUserStore } from '@/stores'
+
+import type { DropdownProps } from 'naive-ui'
+
+const userStore = useUserStore()
+
+const menuActiveKey = ref('')
+
+const renderIcon: DropdownProps['renderIcon'] = (option) => {
+  return isFunction(option.icon)
+    ? h(option.icon, {
+        class: 'ml-2 size-5',
+      })
+    : null
+}
+
+function hasActiveChild(children: any[]): boolean {
+  const stack = [...children]
+  while (stack.length) {
+    const node = stack.pop()
+    if (node?.key === menuActiveKey.value) return true
+    if (Array.isArray(node?.children)) {
+      stack.push(...node.children)
+    }
+  }
+  return false
+}
+
+watch(
+  () => router.currentRoute.value,
+  (newRoute) => {
+    menuActiveKey.value = newRoute.name as string
+  },
+  {
+    immediate: true,
+  },
+)
+</script>
+<template>
+  <div class="relative flex w-full items-center gap-x-1 truncate">
+    <template
+      v-for="{ disabled, key, type, label, icon, children } in userStore.menuList"
+      :key="key"
+    >
+      <div
+        v-if="!type && isEmpty(children)"
+        :data-key="key"
+        class="relative flex items-center rounded-[var(--border-radius)] px-2.5 py-2 transition-[background-color,color]"
+        :class="[
+          disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer',
+          menuActiveKey === key
+            ? 'menu-active bg-primary/15 text-primary'
+            : 'hover:bg-neutral-150 dark:hover:bg-neutral-800',
+        ]"
+      >
+        <component
+          v-if="isFunction(icon)"
+          :is="icon()"
+          class="mr-2 size-5"
+        />
+        <component
+          v-if="isFunction(label)"
+          :is="label()"
+          class="before:absolute before:inset-0 before:size-full before:content-['']"
+        />
+        <span v-else>{{ label }}</span>
+      </div>
+      <NDropdown
+        v-if="!type && Array.isArray(children) && !isEmpty(children)"
+        :options="children"
+        :value="menuActiveKey"
+        :render-icon="renderIcon"
+      >
+        <div
+          :data-key="key"
+          class="flex items-center rounded-[var(--border-radius)] py-2 pr-2 pl-2.5 transition-[background-color,color]"
+          :class="[
+            disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer',
+            hasActiveChild(children)
+              ? 'menu-active bg-primary/15 text-primary'
+              : 'hover:bg-neutral-150 dark:hover:bg-neutral-800',
+          ]"
+        >
+          <component
+            v-if="isFunction(icon)"
+            :is="icon()"
+            class="mr-2 size-5"
+          />
+          <span>{{ label }}</span>
+          <span class="ml-1.5 iconify ph--caret-down" />
+        </div>
+      </NDropdown>
+    </template>
+  </div>
+</template>

+ 16 - 5
src/layout/header/actions/component/PreferencesDrawer.vue

@@ -18,6 +18,7 @@ import { ccAPCA } from '@/utils/chromaHelper'
 import twColors from '@/utils/tailwindColor'
 import twc from '@/utils/tailwindColor'
 
+import LayoutThumbnail from './LayoutThumbnail.vue'
 import NoiseModal from './NoiseModal.vue'
 import WatermarkModal from './WatermarkModal.vue'
 
@@ -140,20 +141,26 @@ const showNoiseModal = () => {
               </template>
             </NColorPicker>
           </div>
+          <div>
+            <NDivider>导航模式</NDivider>
+            <LayoutThumbnail />
+          </div>
           <div>
             <NDivider>布局相关</NDivider>
             <div class="flex flex-col gap-y-1.5">
               <div class="flex items-center justify-between">
                 <span>展开侧边菜单</span>
                 <NSwitch
-                  :value="preferencesStore.preferences.menu.collapsed"
+                  :value="preferencesStore.preferences.sidebarMenu.collapsed"
                   :checked-value="false"
                   :unchecked-value="true"
-                  :disabled="isSmallScreen"
+                  :disabled="
+                    isSmallScreen || preferencesStore.preferences.navigationMode !== 'sidebar'
+                  "
                   @update-value="
                     (value) =>
                       modify({
-                        menu: {
+                        sidebarMenu: {
                           collapsed: value,
                         },
                       })
@@ -188,7 +195,9 @@ const showNoiseModal = () => {
                 <span>显示导航按钮</span>
                 <NSwitch
                   :value="preferencesStore.preferences.showNavigation"
-                  :disabled="isSmallScreen"
+                  :disabled="
+                    isSmallScreen || preferencesStore.preferences.navigationMode !== 'sidebar'
+                  "
                   @update-value="
                     (value) =>
                       modify({
@@ -201,7 +210,9 @@ const showNoiseModal = () => {
                 <span>显示面包屑</span>
                 <NSwitch
                   :value="preferencesStore.preferences.showBreadcrumb"
-                  :disabled="isSmallScreen"
+                  :disabled="
+                    isSmallScreen || preferencesStore.preferences.navigationMode !== 'sidebar'
+                  "
                   @update-value="
                     (value) =>
                       modify({

+ 9 - 5
src/stores/preferences.ts

@@ -5,8 +5,11 @@ import { watch } from 'vue'
 
 import type { WatermarkProps } from 'naive-ui'
 
+type NavigationMode = 'sidebar' | 'horizontal'
+
 export interface PreferencesOptions {
-  menu: Partial<{
+  navigationMode: NavigationMode
+  sidebarMenu: Partial<{
     collapsed: boolean
     width: number
     maxWidth: number
@@ -26,11 +29,12 @@ export interface PreferencesOptions {
   noiseOpacity: number
 }
 
-export const DEFAULT_PREFERENCES_OPTIONS: PreferencesOptions = {
-  menu: {
+export const DEFAULT_PREFERENCES_OPTIONS = {
+  navigationMode: 'sidebar',
+  sidebarMenu: {
     collapsed: false,
     width: 64,
-    maxWidth: 272,
+    maxWidth: 256,
   },
   showFooter: true,
   showTabs: true,
@@ -66,7 +70,7 @@ export const DEFAULT_PREFERENCES_OPTIONS: PreferencesOptions = {
     imageOpacity: 0.5,
   },
   noiseOpacity: 0.02,
-}
+} as const
 
 export const usePreferencesStore = defineStore('preferencesStore', () => {
   const preferences = useStorage<PreferencesOptions>('preferences', DEFAULT_PREFERENCES_OPTIONS)