ソースを参照

style: format code and rename some files and code

nian 1 ヶ月 前
コミット
28e1007809

+ 1 - 0
eslint.config.ts

@@ -48,6 +48,7 @@ export default defineConfigWithVueTs(
           groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'type'],
         },
       ],
+      'perfectionist/sort-objects': 'off',
     },
   },
 )

+ 3 - 3
src/components/Avatar.vue → src/components/UserAvatar.vue

@@ -1,11 +1,11 @@
 <script setup lang="ts">
 import { NAvatar } from 'naive-ui'
 
-import type { AvatarProps as AvatarPropsRaw } from 'naive-ui'
+import type { AvatarProps } from 'naive-ui'
 
-interface AvatarProps extends /** @vue-ignore */ AvatarPropsRaw {}
+interface UserAvatarProps extends /** @vue-ignore */ AvatarProps {}
 
-defineProps<AvatarProps>()
+defineProps<UserAvatarProps>()
 </script>
 <template>
   <NAvatar

+ 3 - 4
src/components/collapse-transition/CollapseTransition.vue

@@ -59,10 +59,9 @@ const DIRECTION_CLASSES_MAP = {
       class="grid"
       :class="containerClass"
       :style="[
-        typeof duration === 'number' &&
-          duration > 0 && {
-            '--default-transition-duration': `${duration}ms`,
-          },
+        typeof duration === 'number' && {
+          '--default-transition-duration': `${duration}ms`,
+        },
         containerStyle,
       ]"
     >

+ 5 - 1
src/injection/index.ts

@@ -1,4 +1,4 @@
-import type { MediaQueryProvider, LayoutProvider } from './interface'
+import type { MediaQueryProvider, LayoutProvider, HeaderLayoutProvider } from './interface'
 import type { InjectionKey } from 'vue'
 
 export const mediaQueryInjectionKey: InjectionKey<MediaQueryProvider> =
@@ -6,4 +6,8 @@ export const mediaQueryInjectionKey: InjectionKey<MediaQueryProvider> =
 
 export const layoutInjectionKey: InjectionKey<LayoutProvider> = Symbol('layoutInjectionKey')
 
+export const headerLayoutInjectionKey: InjectionKey<HeaderLayoutProvider> = Symbol(
+  'headerLayoutInjectionKey',
+)
+
 export * from './interface'

+ 4 - 0
src/injection/interface.ts

@@ -15,3 +15,7 @@ export interface LayoutProvider {
   layoutSlideDirection: Ref<LayoutSlideDirection>
   setLayoutSlideDirection: (direction: LayoutSlideDirection) => void
 }
+
+export interface HeaderLayoutProvider {
+  navigationContainerElement: Ref<HTMLElement | null>
+}

+ 3 - 1
src/layout/aside/component/SidebarUserPanel.vue

@@ -2,12 +2,14 @@
 import { useMessage } from 'naive-ui'
 
 import { ButtonAnimation } from '@/components'
-import Avatar from '@/components/Avatar.vue'
+import Avatar from '@/components/UserAvatar.vue'
 import UserDropdown from '@/components/UserDropdown.vue'
 import { usePreferencesStore, useUserStore } from '@/stores'
 
 const preferencesStore = usePreferencesStore()
+
 const userStore = useUserStore()
+
 const message = useMessage()
 
 const handleUserCardClick = () => {

+ 1 - 1
src/layout/header/AvatarDropdown.vue

@@ -1,6 +1,6 @@
 <script setup lang="ts">
 import { ButtonAnimation } from '@/components'
-import Avatar from '@/components/Avatar.vue'
+import Avatar from '@/components/UserAvatar.vue'
 import UserDropdown from '@/components/UserDropdown.vue'
 </script>
 <template>

+ 60 - 38
src/layout/header/Breadcrumb.vue

@@ -1,37 +1,66 @@
-<script setup lang="ts">
-import { isEmpty } from 'lodash-es'
+<script setup lang="tsx">
+import { isEmpty, isFunction } from 'lodash-es'
 import { NDropdown } from 'naive-ui'
-import { computed, h } from 'vue'
+import { computed, defineComponent, h } from 'vue'
 
 import router from '@/router'
 
 import type { DropdownProps } from 'naive-ui'
+import type { PropType } from 'vue'
 import type { RouteRecordNameGeneric, RouteRecordRaw } from 'vue-router'
 
-const routerBreadcrumb = computed(() => {
+const routeBreadcrumbList = computed(() => {
   return router.currentRoute.value.matched.filter((item) => item.name !== 'layout')
 })
 
+const currentRouteName = computed(() => {
+  return router.currentRoute.value.name as string
+})
+
+const renderIcon: DropdownProps['renderIcon'] = (option) => {
+  return isFunction(option.icon)
+    ? h(option.icon, {
+        class: 'ml-1.5 size-5',
+      })
+    : null
+}
+
 const onDropdownSelected: DropdownProps['onSelect'] = (key) => {
   router.push({ name: key })
 }
 
+function isCurrentRoute(name: RouteRecordNameGeneric) {
+  return name === currentRouteName.value
+}
+
 function resolveDropdownOptions(route: RouteRecordRaw[]): DropdownProps['options'] {
-  return route.map((item) => {
-    return {
-      label: item.meta?.title || item.meta?.label,
-      key: (item.name as string) || item.path,
-      icon: () => h('span', { class: `${item.meta?.icon} size-5` }),
-      children: !isEmpty(item.children)
-        ? resolveDropdownOptions(item.children as RouteRecordRaw[])
+  return route.map((item) => ({
+    label: item.meta?.title || item.meta?.label,
+    key: (item.name as string) || item.path,
+    icon: item.meta?.icon ? () => h('span', { class: `${item.meta?.icon} size-5` }) : undefined,
+    children:
+      Array.isArray(item.children) && !isEmpty(item.children)
+        ? resolveDropdownOptions(item.children)
         : undefined,
-    }
-  })
+  }))
 }
 
-function isCurrentRoute(name: RouteRecordNameGeneric) {
-  return name === router.currentRoute.value.name
-}
+const BreadcrumbItem = defineComponent({
+  props: {
+    meta: {
+      type: Object as PropType<RouteRecordRaw['meta']>,
+      required: true,
+    },
+  },
+  setup(props) {
+    return () => (
+      <div class='flex shrink-0 items-center gap-x-1.5 rounded px-1.5 py-1'>
+        {props.meta?.icon && <span class={`${props.meta?.icon} size-5`} />}
+        {props.meta?.title}
+      </div>
+    )
+  },
+})
 </script>
 <template>
   <TransitionGroup
@@ -47,34 +76,27 @@ function isCurrentRoute(name: RouteRecordNameGeneric) {
     leave-from-class="grid-cols-[1fr]"
   >
     <li
-      v-for="{ children, meta, name, path } in routerBreadcrumb"
+      v-for="{ children, meta, name, path } in routeBreadcrumbList"
       :key="path"
-      class="grid shrink-0 justify-start overflow-hidden"
+      class="grid overflow-hidden"
     >
-      <div
-        class="flex min-w-0 items-center"
-        :class="{
-          'not-hover:text-[var(--text-color-3)]': !isCurrentRoute(name),
-        }"
-      >
+      <div class="flex min-w-0 items-center">
+        <BreadcrumbItem
+          v-if="isEmpty(children)"
+          :meta="meta"
+        />
         <NDropdown
+          v-else
           :options="resolveDropdownOptions(children)"
-          placement="bottom-start"
+          :disabled="isEmpty(children)"
+          :value="currentRouteName"
+          :render-icon="renderIcon"
           @select="onDropdownSelected"
         >
-          <div
-            class="flex shrink-0 items-center gap-x-1.5 rounded px-1.5 py-1 transition-[background-color,color]"
-            :class="{
-              'cursor-pointer hover:bg-[var(--button-color-2-hover)]': !isCurrentRoute(name),
-            }"
-          >
-            <span
-              v-if="meta?.icon"
-              class="size-5"
-              :class="meta?.icon"
-            />
-            {{ meta?.title }}
-          </div>
+          <BreadcrumbItem
+            :meta="meta"
+            class="cursor-pointer transition-[background-color,color] not-hover:text-[var(--text-color-3)] hover:bg-[var(--button-color-2-hover)]"
+          />
         </NDropdown>
         <span
           class="iconify-[fluent--slash-forward-20-regular] w-3.5 text-[var(--text-color-3)]"

+ 0 - 1
src/layout/header/Navigation.vue → src/layout/header/NavigationButton.vue

@@ -16,7 +16,6 @@ const stopWatch = watch(
       stopWatch()
       return
     }
-
     navigationState.canGoBack = window.navigation.canGoBack
     navigationState.canGoForward = window.navigation.canGoForward
   },

+ 1 - 0
src/layout/header/actions/component/LayoutThumbnail.vue

@@ -3,6 +3,7 @@ import { usePersonalization } from '@/composables'
 import { usePreferencesStore } from '@/stores'
 
 const { color } = usePersonalization()
+
 const preferencesStore = usePreferencesStore()
 </script>
 <template>

+ 2 - 0
src/layout/header/actions/component/PreferencesDrawer.vue

@@ -25,7 +25,9 @@ import WatermarkModal from './WatermarkModal.vue'
 const { isSmallScreen } = useInjection(mediaQueryInjectionKey)
 
 const preferencesStore = usePreferencesStore()
+
 const systemStore = useSystemStore()
+
 const { color, setColor } = usePersonalization()
 
 const { modify, reset } = preferencesStore

+ 20 - 7
src/layout/header/index.vue

@@ -1,19 +1,29 @@
 <script setup lang="ts">
+import { defineAsyncComponent, provide, ref } from 'vue'
+
 import { CollapseTransition } from '@/components'
+import { headerLayoutInjectionKey } from '@/injection'
 import { usePreferencesStore } from '@/stores'
 
 import Actions from './actions/index.vue'
 import AvatarDropdown from './AvatarDropdown.vue'
-import Breadcrumb from './Breadcrumb.vue'
-import HorizontalMenu from './HorizontalMenu.vue'
 import LogoArea from './LogoArea.vue'
-import Navigation from './Navigation.vue'
 
 defineOptions({
   name: 'HeaderLayout',
 })
 
+const AsyncNavigationButton = defineAsyncComponent(() => import('./NavigationButton.vue'))
+const AsyncHorizontalMenu = defineAsyncComponent(() => import('./HorizontalMenu.vue'))
+const AsyncBreadcrumb = defineAsyncComponent(() => import('./Breadcrumb.vue'))
+
 const preferencesStore = usePreferencesStore()
+
+const navigationContainerRef = ref<HTMLElement | null>(null)
+
+provide(headerLayoutInjectionKey, {
+  navigationContainerElement: navigationContainerRef,
+})
 </script>
 <template>
   <header
@@ -21,14 +31,17 @@ const preferencesStore = usePreferencesStore()
   >
     <LogoArea />
     <div class="flex flex-1 items-center px-4 py-3.5">
-      <div class="flex h-9 flex-1 items-center">
+      <div
+        ref="navigationContainerRef"
+        class="flex h-9 flex-1 items-center"
+      >
         <CollapseTransition
           :display="
             preferencesStore.preferences.showNavigation &&
             preferencesStore.preferences.navigationMode === 'sidebar'
           "
         >
-          <Navigation />
+          <AsyncNavigationButton />
         </CollapseTransition>
         <CollapseTransition
           :display="
@@ -37,10 +50,10 @@ const preferencesStore = usePreferencesStore()
           "
           contentTag="nav"
         >
-          <Breadcrumb />
+          <AsyncBreadcrumb />
         </CollapseTransition>
         <CollapseTransition :display="preferencesStore.preferences.navigationMode === 'horizontal'">
-          <HorizontalMenu />
+          <AsyncHorizontalMenu />
         </CollapseTransition>
       </div>
       <Actions class="gap-x-3 pl-4" />

+ 11 - 7
src/layout/index.vue

@@ -18,20 +18,23 @@ defineOptions({
   name: 'Layout',
 })
 
-const MobileHeader = defineAsyncComponent(() => import('./mobile/MobileHeader.vue'))
-const MobileLeftAside = defineAsyncComponent(() => import('./mobile/MobileLeftAside.vue'))
-const MobileRightAside = defineAsyncComponent(() => import('./mobile/MobileRightAside.vue'))
+const AsyncMobileHeader = defineAsyncComponent(() => import('./mobile/MobileHeader.vue'))
+const AsyncMobileLeftAside = defineAsyncComponent(() => import('./mobile/MobileLeftAside.vue'))
+const AsyncMobileRightAside = defineAsyncComponent(() => import('./mobile/MobileRightAside.vue'))
 
 const tabsStore = useTabsStore()
+
 const preferencesStore = usePreferencesStore()
+
 const { isSmallScreen } = useInjection(mediaQueryInjectionKey)
+
 const { layoutSlideDirection, setLayoutSlideDirection } = useInjection(layoutInjectionKey)
 
 const layoutTranslateOffset = computed(() => {
   return layoutSlideDirection.value === 'right'
     ? preferencesStore.preferences.sidebarMenu.maxWidth || 0
     : layoutSlideDirection.value === 'left'
-      ? -64
+      ? -(preferencesStore.preferences.sidebarMenu.width || 0)
       : 0
 })
 
@@ -51,7 +54,7 @@ watch(isSmallScreen, (isSmallScreen) => {
     class="relative flex h-svh overflow-hidden"
     :style="{ backgroundImage: `url(${texturePng})` }"
   >
-    <MobileLeftAside v-if="isSmallScreen" />
+    <AsyncMobileLeftAside v-if="isSmallScreen" />
 
     <div
       class="relative flex h-full w-full flex-col max-sm:bg-naive-card/50"
@@ -68,7 +71,7 @@ watch(isSmallScreen, (isSmallScreen) => {
       "
     >
       <HeaderLayout v-if="!isSmallScreen" />
-      <MobileHeader v-else />
+      <AsyncMobileHeader v-else />
       <div class="flex flex-1 overflow-hidden">
         <CollapseTransition
           v-if="!isSmallScreen"
@@ -114,9 +117,10 @@ watch(isSmallScreen, (isSmallScreen) => {
       <div
         v-if="isSmallScreen && layoutSlideDirection"
         class="absolute inset-0"
+        style="z-index: 9997"
         @click="setLayoutSlideDirection(null)"
       />
     </div>
-    <MobileRightAside v-if="isSmallScreen" />
+    <AsyncMobileRightAside v-if="isSmallScreen" />
   </div>
 </template>

+ 2 - 2
src/stores/user.ts

@@ -7,7 +7,7 @@ import { resolveMenu, resolveRoute } from '@/router/helper'
 import { routeRecordRaw } from '@/router/record'
 
 import type { MenuMixedOptions } from '@/router/helper'
-import type { MenuProps } from 'naive-ui'
+import type { MenuOption } from 'naive-ui'
 import type { RouteRecordRaw } from 'vue-router'
 
 interface User {
@@ -29,7 +29,7 @@ export const useUserStore = defineStore('userStore', () => {
 
   const token = useStorage<string | null>('token', '')
 
-  const menuList = ref<MenuProps['options']>([])
+  const menuList = ref<MenuOption[]>([])
 
   const routeList = ref<RouteRecordRaw[]>([])
 

+ 4 - 4
src/theme/common.ts

@@ -37,10 +37,6 @@ export function commonThemeOverrides(primaryColor = ''): GlobalThemeOverrides {
     Dropdown: {
       padding: '6px 2px',
     },
-    Message: {
-      iconMargin: '0 6px 0 0',
-      padding: '10px 18px',
-    },
     Menu: {
       peers: {
         Dropdown: {
@@ -48,6 +44,10 @@ export function commonThemeOverrides(primaryColor = ''): GlobalThemeOverrides {
         },
       },
     },
+    Message: {
+      iconMargin: '0 6px 0 0',
+      padding: '10px 18px',
+    },
     Popconfirm: {
       iconSize: '20px',
     },

+ 3 - 3
src/theme/dark.ts

@@ -126,6 +126,9 @@ export function baseDarkThemeOverrides(primaryColor = ''): GlobalThemeOverrides
       tdColorHover: cbh(twc.neutral[850], 0.06),
       thColor: twc.neutral[850],
     },
+    Divider: {
+      color: cdh(twc.neutral[750], 0.24),
+    },
     Drawer: {
       footerBorderTop: `1px solid ${twc.neutral[750]}`,
       headerBorderBottom: `1px solid ${twc.neutral[750]}`,
@@ -136,9 +139,6 @@ export function baseDarkThemeOverrides(primaryColor = ''): GlobalThemeOverrides
         },
       },
     },
-    Divider: {
-      color: cdh(twc.neutral[750], 0.24),
-    },
     Menu: {
       itemColorHover: twc.neutral[800],
     },

+ 7 - 7
src/theme/light.ts

@@ -124,6 +124,9 @@ export function baseLightThemeOverrides(primaryColor = ''): GlobalThemeOverrides
       tdColorHover: cdh(twc.neutral[100], 0.06),
       thColor: twc.neutral[100],
     },
+    Divider: {
+      color: twc.neutral[150],
+    },
     Drawer: {
       footerBorderTop: `1px solid ${LIGHT.borderColor}`,
       headerBorderBottom: `1px solid ${LIGHT.borderColor}`,
@@ -134,9 +137,6 @@ export function baseLightThemeOverrides(primaryColor = ''): GlobalThemeOverrides
         },
       },
     },
-    Divider: {
-      color: twc.neutral[150],
-    },
     Message: {
       textColorSuccess: twc.lime[500],
       textColorInfo: twc.sky[500],
@@ -177,10 +177,6 @@ export function baseLightThemeOverrides(primaryColor = ''): GlobalThemeOverrides
         borderColor: twc.neutral[250],
       },
     },
-    Slider: {
-      indicatorColor: twc.neutral[25],
-      indicatorTextColor: LIGHT.textColor2,
-    },
     Select: {
       peers: {
         InternalSelectMenu: {
@@ -194,6 +190,10 @@ export function baseLightThemeOverrides(primaryColor = ''): GlobalThemeOverrides
         },
       },
     },
+    Slider: {
+      indicatorColor: twc.neutral[25],
+      indicatorTextColor: LIGHT.textColor2,
+    },
     Upload: {
       draggerColor: twc.neutral[100],
     },