Sfoglia il codice sorgente

pref: responsive design

nian 3 mesi fa
parent
commit
ab5b8dc21f

+ 0 - 15
src/components/__tests__/comp.test.ts

@@ -62,19 +62,4 @@ describe('EmptyPlaceholder Component', () => {
     expect(wrapper.exists()).toBe(true)
     expect(wrapper.find('.n-empty').exists()).toBe(true)
   })
-
-  it('add content slot', () => {
-    const wrapper = mount(EmptyPlaceholder, {
-      props: {
-        show: true,
-      },
-      slots: {
-        content: '<div class="content">context</div>',
-      },
-    })
-
-    expect(wrapper.exists()).toBe(true)
-    expect(wrapper.find('.n-empty').exists()).toBe(true)
-    expect(wrapper.find('.content').exists()).toBe(true)
-  })
 })

+ 5 - 12
src/components/ButtonAnimation.vue → src/components/button-animation/ButtonAnimation.vue

@@ -3,25 +3,18 @@ import { mergeWith } from 'lodash-es'
 import { NButton } from 'naive-ui'
 import { ref, inject, computed, useAttrs } from 'vue'
 
-import type { ButtonProps } from 'naive-ui'
+import { buttonAnimationInjectionKey } from './injection'
 
-type Animation = 'beat' | 'rotate' | 'shake'
+import type { ButtonAnimationProps } from './interface'
 
-interface ButtonAnimationProps extends /* @vue-ignore */ ButtonProps {
-  duration?: number
-  animation?: Animation | boolean
-}
-
-const injectButtonAnimation = inject('ButtonAnimationInject', null) as ButtonProps | null
+const buttonAnimationInject = inject(buttonAnimationInjectionKey, null)
 
 const { duration = 600, animation = 'beat' } = defineProps<ButtonAnimationProps>()
 
-const buttonAnimationAttrs = useAttrs() as ButtonProps
-
 const isAnimating = ref(false)
 
-const buttonAnimationProps = computed<ButtonProps>(() => {
-  return mergeWith({}, injectButtonAnimation, buttonAnimationAttrs)
+const buttonAnimationProps = computed<ButtonAnimationProps>(() => {
+  return mergeWith({}, buttonAnimationInject, useAttrs())
 })
 
 const onButtonClicked = () => {

+ 18 - 0
src/components/button-animation/ButtonAnimationProvider.vue

@@ -0,0 +1,18 @@
+<script setup lang="ts">
+import { provide, useAttrs } from 'vue'
+
+import { buttonAnimationInjectionKey } from './injection'
+
+import type { ButtonAnimationProps } from './interface'
+
+defineProps<ButtonAnimationProps>()
+
+defineOptions({
+  inheritAttrs: false,
+})
+
+provide(buttonAnimationInjectionKey, useAttrs() as ButtonAnimationProps)
+</script>
+<template>
+  <slot />
+</template>

+ 4 - 0
src/components/button-animation/index.ts

@@ -0,0 +1,4 @@
+export { default as ButtonAnimation } from './ButtonAnimation.vue'
+export { default as ButtonAnimationProvider } from './ButtonAnimationProvider.vue'
+export * from './injection'
+export * from './interface'

+ 4 - 0
src/components/button-animation/injection.ts

@@ -0,0 +1,4 @@
+import type { ButtonAnimationProps } from './interface'
+import type { InjectionKey } from 'vue'
+
+export const buttonAnimationInjectionKey = Symbol() as InjectionKey<Partial<ButtonAnimationProps>>

+ 8 - 0
src/components/button-animation/interface.ts

@@ -0,0 +1,8 @@
+import type { ButtonProps } from 'naive-ui'
+
+type Animation = 'beat' | 'rotate' | 'shake'
+
+export interface ButtonAnimationProps extends /* @vue-ignore */ ButtonProps {
+  duration?: number
+  animation?: Animation | boolean
+}

+ 2 - 3
src/components/index.ts

@@ -1,9 +1,8 @@
-import ButtonAnimation from './ButtonAnimation.vue'
 import EmptyPlaceholder from './EmptyPlaceholder.vue'
 import HintHelp from './HintHelp.vue'
 
-export { EmptyPlaceholder, HintHelp, ButtonAnimation }
+export { EmptyPlaceholder, HintHelp }
 
-export * from './ButtonAnimation.vue'
+export * from './button-animation'
 export * from './EmptyPlaceholder.vue'
 export * from './HintHelp.vue'

+ 0 - 18
src/helpers/resetSystemData.ts

@@ -1,18 +0,0 @@
-import { DEFAULT_PREFERENCES_OPTIONS, usePreferencesStore } from '@/stores/preferences'
-import { useTabsStore } from '@/stores/tabs'
-import { useUserStore } from '@/stores/user'
-import { haveSameKeys } from '@/utils/lodash-helpers'
-
-const preferencesStore = usePreferencesStore()
-const tabsStore = useTabsStore()
-const userStore = useUserStore()
-
-export function resetSystemData() {
-  const oldLocalStorage = localStorage.getItem('configure')
-  if (oldLocalStorage || !haveSameKeys(preferencesStore.preferences, DEFAULT_PREFERENCES_OPTIONS)) {
-    tabsStore.clearTabs()
-    preferencesStore.reset()
-    userStore.cleanup()
-    localStorage.clear()
-  }
-}

+ 1 - 1
src/layouts/aside/index.vue

@@ -28,7 +28,7 @@ function handleCollapseClick() {
 </script>
 <template>
   <div
-    class="relative flex h-full flex-col justify-between gap-y-4 border-r border-naive-border bg-naive-card pb-4 transition-[background-color,border-color,width] max-sm:hidden"
+    class="relative flex h-full flex-col justify-between gap-y-4 border-r border-naive-border bg-naive-card pb-4 transition-[background-color,border-color,width]"
     :style="{
       width: `${menuCollapseWidth}px`,
     }"

+ 1 - 1
src/layouts/aside/mobile/MobileLeftAside.vue

@@ -9,7 +9,7 @@ const preferencesStore = usePreferencesStore()
 </script>
 <template>
   <div
-    class="absolute top-0 left-0 flex h-svh flex-col gap-y-4 py-4 transition-[translate] sm:hidden"
+    class="absolute top-0 left-0 flex h-svh flex-col gap-y-4 pt-6 pb-4 transition-[translate]"
     :class="{
       '-translate-x-full': preferencesStore.layoutSlideDirection !== 'right',
     }"

+ 8 - 13
src/layouts/aside/mobile/MobileRightAside.vue

@@ -1,30 +1,25 @@
 <script setup lang="ts">
-import { provide, reactive } from 'vue'
-
+import { ButtonAnimationProvider } from '@/components'
 import { usePreferencesStore } from '@/stores/preferences'
 
 import Actions from '../../header/actions/index.vue'
 
-import type { ButtonProps } from 'naive-ui'
-
 const preferencesStore = usePreferencesStore()
-
-const provideButtonAnimation = reactive<ButtonProps>({
-  size: 'large',
-  circle: false,
-})
-
-provide('ButtonAnimationInject', provideButtonAnimation)
 </script>
 <template>
   <div
-    class="absolute top-0 right-0 h-svh p-4 transition-[translate] sm:hidden"
+    class="absolute top-0 right-0 h-svh p-4 transition-[translate]"
     :class="{
       'translate-x-full': preferencesStore.layoutSlideDirection !== 'left',
     }"
   >
     <div class="flex h-full items-center justify-between">
-      <Actions class="flex-col justify-center gap-y-4" />
+      <ButtonAnimationProvider
+        size="large"
+        :circle="false"
+      >
+        <Actions class="flex-col justify-center gap-y-4" />
+      </ButtonAnimationProvider>
     </div>
   </div>
 </template>

+ 202 - 216
src/layouts/header/actions/component/PreferencesDrawer.vue

@@ -9,23 +9,23 @@ import {
   useModal,
   NScrollbar,
 } from 'naive-ui'
-import { h, ref, inject, watch } from 'vue'
+import { h, ref, inject } from 'vue'
 
-import packageJson from '@/../package.json'
-import { ButtonAnimation } from '@/components'
+import { ButtonAnimation, ButtonAnimationProvider } from '@/components'
 import { useComponentThemeOverrides } from '@/composable/useComponentThemeOverrides'
 import { usePersonalization } from '@/composable/usePersonalization'
 import { mediaQueryInjectionKey } from '@/injection'
 import { usePreferencesStore } from '@/stores/preferences'
+import { useSystemStore } from '@/stores/system'
 import twColors from '@/utils/tailwindColor'
 
 import NoiseModal from './NoiseModal.vue'
 import WatermarkModal from './WatermarkModal.vue'
 
-const mediaQuery = inject(mediaQueryInjectionKey, null)
+const mediaQueryInject = inject(mediaQueryInjectionKey, null)
 
 const preferencesStore = usePreferencesStore()
-
+const systemStore = useSystemStore()
 const { color, setColor } = usePersonalization()
 
 const { modify, reset } = preferencesStore
@@ -95,24 +95,6 @@ const showNoiseModal = () => {
     zIndex: 99999,
   })
 }
-
-watch(
-  () => mediaQuery?.sm.value,
-  (newVal) => {
-    if (newVal) {
-      modify({
-        menu: {
-          collapsed: false,
-        },
-        showTabs: false,
-        showFooter: false,
-      })
-    }
-  },
-  {
-    immediate: true,
-  },
-)
 </script>
 <template>
   <div>
@@ -120,228 +102,232 @@ watch(
       @click="showDrawer = true"
       title="侧边栏"
     >
-      <span class="iconify size-5 rotate-180 ph--sidebar-simple-duotone" />
+      <span class="iconify rotate-180 ph--sidebar-simple-duotone" />
     </ButtonAnimation>
-    <NDrawer
-      v-model:show="showDrawer"
-      :auto-focus="false"
-      :width="320"
-      :theme-overrides="{
-        footerPadding: '14px 16px',
-      }"
-    >
-      <NDrawerContent :native-scrollbar="false">
-        <template #header>
-          <div class="flex items-center gap-x-1">
-            <span>系统设定</span>
-            <ButtonAnimation
-              animation="rotate"
-              @click="reset"
-            >
-              <span class="iconify size-5 ph--arrow-clockwise" />
-            </ButtonAnimation>
-          </div>
-        </template>
-        <div>
-          <NDivider>主题颜色</NDivider>
-          <NColorPicker
-            v-bind="$attrs"
-            :default-value="color"
-            :swatches="colorSwatches"
-            @update-value="(value) => modifyColor(value)"
-          />
-        </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"
-                :checked-value="false"
-                :unchecked-value="true"
-                :disabled="mediaQuery?.sm.value"
-                @update-value="
-                  (value) =>
-                    modify({
-                      menu: {
-                        collapsed: value,
-                      },
-                    })
-                "
-              />
-            </div>
-            <div class="flex items-center justify-between">
-              <span>显示顶部加载条</span>
-              <NSwitch
-                :value="preferencesStore.preferences.showTopLoadingBar"
-                @update-value="
-                  (value) =>
-                    modify({
-                      showTopLoadingBar: value,
-                    })
-                "
-              />
-            </div>
-            <div class="flex items-center justify-between">
-              <span>显示Logo</span>
-              <NSwitch
-                :value="preferencesStore.preferences.showLogo"
-                @update-value="
-                  (value) =>
-                    modify({
-                      showLogo: value,
-                    })
-                "
-              />
-            </div>
-            <div class="flex items-center justify-between">
-              <span>显示导航按钮</span>
-              <NSwitch
-                :value="preferencesStore.preferences.showNavigation"
-                :disabled="mediaQuery?.sm.value"
-                @update-value="
-                  (value) =>
-                    modify({
-                      showNavigation: value,
-                    })
-                "
-              />
-            </div>
-            <div class="flex items-center justify-between">
-              <span>显示面包屑</span>
-              <NSwitch
-                :value="preferencesStore.preferences.showBreadcrumb"
-                :disabled="mediaQuery?.sm.value"
-                @update-value="
-                  (value) =>
-                    modify({
-                      showBreadcrumb: value,
-                    })
-                "
-              />
-            </div>
-            <div class="flex items-center justify-between">
-              <span>显示标签页</span>
-              <NSwitch
-                :value="preferencesStore.preferences.showTabs"
-                :disabled="mediaQuery?.sm.value"
-                @update-value="
-                  (value) =>
-                    modify({
-                      showTabs: value,
-                    })
-                "
-              />
-            </div>
-            <div class="flex items-center justify-between">
-              <span>常显标签关闭按钮</span>
-              <NSwitch
-                :value="preferencesStore.preferences.showTabClose"
-                @update-value="
-                  (value) =>
-                    modify({
-                      showTabClose: value,
-                    })
-                "
-              />
-            </div>
-            <div class="flex items-center justify-between">
-              <span>显示底部</span>
-              <NSwitch
-                :value="preferencesStore.preferences.showFooter"
-                :disabled="mediaQuery?.sm.value"
-                @update-value="
-                  (value) =>
-                    modify({
-                      showFooter: value,
-                    })
-                "
-              />
-            </div>
-          </div>
-        </div>
-        <div>
-          <NDivider>页面相关</NDivider>
-          <div class="flex items-center justify-between">
+    <ButtonAnimationProvider>
+      <NDrawer
+        v-model:show="showDrawer"
+        :auto-focus="false"
+        :width="320"
+        :theme-overrides="{
+          footerPadding: '14px 16px',
+        }"
+      >
+        <NDrawerContent :native-scrollbar="false">
+          <template #header>
             <div class="flex items-center gap-x-1">
-              <span>显示水印</span>
+              <span>系统设定</span>
               <ButtonAnimation
-                size="small"
-                @click="showWatermarkModal"
-                label="修改"
+                animation="rotate"
+                @click="reset"
               >
-                <span class="iconify size-4 ph--pencil-simple-line" />
+                <span class="iconify size-5 ph--arrow-clockwise" />
               </ButtonAnimation>
             </div>
-            <NSwitch
-              :value="preferencesStore.preferences.showWatermark"
-              @update-value="
-                (value) =>
-                  modify({
-                    showWatermark: value,
-                  })
-              "
+          </template>
+          <div>
+            <NDivider>主题颜色</NDivider>
+            <NColorPicker
+              v-bind="$attrs"
+              :default-value="color"
+              :swatches="colorSwatches"
+              @update-value="(value) => modifyColor(value)"
             />
           </div>
-          <div class="flex items-center justify-between">
-            <div class="flex items-center gap-x-1">
-              <span>显示磨砂效果</span>
-              <ButtonAnimation
-                size="small"
-                @click="showNoiseModal"
-                label="修改"
-              >
-                <span class="iconify size-4 ph--pencil-simple-line" />
-              </ButtonAnimation>
+          <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"
+                  :checked-value="false"
+                  :unchecked-value="true"
+                  :disabled="mediaQueryInject?.sm.value"
+                  @update-value="
+                    (value) =>
+                      modify({
+                        menu: {
+                          collapsed: value,
+                        },
+                      })
+                  "
+                />
+              </div>
+              <div class="flex items-center justify-between">
+                <span>显示顶部加载条</span>
+                <NSwitch
+                  :value="preferencesStore.preferences.showTopLoadingBar"
+                  @update-value="
+                    (value) =>
+                      modify({
+                        showTopLoadingBar: value,
+                      })
+                  "
+                />
+              </div>
+              <div class="flex items-center justify-between">
+                <span>显示Logo</span>
+                <NSwitch
+                  :value="preferencesStore.preferences.showLogo"
+                  @update-value="
+                    (value) =>
+                      modify({
+                        showLogo: value,
+                      })
+                  "
+                />
+              </div>
+              <div class="flex items-center justify-between">
+                <span>显示导航按钮</span>
+                <NSwitch
+                  :value="preferencesStore.preferences.showNavigation"
+                  :disabled="mediaQueryInject?.sm.value"
+                  @update-value="
+                    (value) =>
+                      modify({
+                        showNavigation: value,
+                      })
+                  "
+                />
+              </div>
+              <div class="flex items-center justify-between">
+                <span>显示面包屑</span>
+                <NSwitch
+                  :value="preferencesStore.preferences.showBreadcrumb"
+                  :disabled="mediaQueryInject?.sm.value"
+                  @update-value="
+                    (value) =>
+                      modify({
+                        showBreadcrumb: value,
+                      })
+                  "
+                />
+              </div>
+              <div class="flex items-center justify-between">
+                <span>显示标签页</span>
+                <NSwitch
+                  :value="preferencesStore.preferences.showTabs"
+                  :disabled="mediaQueryInject?.sm.value"
+                  @update-value="
+                    (value) =>
+                      modify({
+                        showTabs: value,
+                      })
+                  "
+                />
+              </div>
+              <div class="flex items-center justify-between">
+                <span>常显标签关闭按钮</span>
+                <NSwitch
+                  :value="preferencesStore.preferences.showTabClose"
+                  :disabled="mediaQueryInject?.sm.value"
+                  @update-value="
+                    (value) =>
+                      modify({
+                        showTabClose: value,
+                      })
+                  "
+                />
+              </div>
+              <div class="flex items-center justify-between">
+                <span>显示底部</span>
+                <NSwitch
+                  :value="preferencesStore.preferences.showFooter"
+                  :disabled="mediaQueryInject?.sm.value"
+                  @update-value="
+                    (value) =>
+                      modify({
+                        showFooter: value,
+                      })
+                  "
+                />
+              </div>
             </div>
-            <NSwitch
-              :value="preferencesStore.preferences.showNoise"
-              @update-value="
-                (value) =>
-                  modify({
-                    showNoise: value,
-                  })
-              "
-            />
           </div>
-          <div class="flex flex-col gap-y-1.5">
+          <div>
+            <NDivider>页面相关</NDivider>
             <div class="flex items-center justify-between">
-              <span>启用导航过渡效果</span>
+              <div class="flex items-center gap-x-1">
+                <span>显示水印</span>
+                <ButtonAnimation
+                  size="small"
+                  @click="showWatermarkModal"
+                  label="修改"
+                >
+                  <span class="iconify size-4 ph--pencil-simple-line" />
+                </ButtonAnimation>
+              </div>
               <NSwitch
-                :value="preferencesStore.preferences.enableNavigationTransition"
+                :value="preferencesStore.preferences.showWatermark"
                 @update-value="
                   (value) =>
                     modify({
-                      enableNavigationTransition: value,
+                      showWatermark: value,
                     })
                 "
               />
             </div>
             <div class="flex items-center justify-between">
-              <span>文字可选中</span>
+              <div class="flex items-center gap-x-1">
+                <span>显示磨砂效果</span>
+                <ButtonAnimation
+                  size="small"
+                  :duration="3000"
+                  @click="showNoiseModal"
+                  label="修改"
+                >
+                  <span class="iconify size-4 ph--pencil-simple-line" />
+                </ButtonAnimation>
+              </div>
               <NSwitch
-                :value="preferencesStore.preferences.enableTextSelect"
+                :value="preferencesStore.preferences.showNoise"
                 @update-value="
                   (value) =>
                     modify({
-                      enableTextSelect: value,
+                      showNoise: value,
                     })
                 "
               />
             </div>
-          </div>
-        </div>
-        <template #footer>
-          <div class="flex w-full items-center justify-between">
-            <div class="flex items-center gap-x-1">
-              <span class="iconify size-5 ph--gear-fine" />
-              <span class="leading-4">当前版本</span>
+            <div class="flex flex-col gap-y-1.5">
+              <div class="flex items-center justify-between">
+                <span>启用导航过渡效果</span>
+                <NSwitch
+                  :value="preferencesStore.preferences.enableNavigationTransition"
+                  @update-value="
+                    (value) =>
+                      modify({
+                        enableNavigationTransition: value,
+                      })
+                  "
+                />
+              </div>
+              <div class="flex items-center justify-between">
+                <span>文字可选中</span>
+                <NSwitch
+                  :value="preferencesStore.preferences.enableTextSelect"
+                  @update-value="
+                    (value) =>
+                      modify({
+                        enableTextSelect: value,
+                      })
+                  "
+                />
+              </div>
             </div>
-            <span class="leading-4">{{ packageJson.version }}</span>
           </div>
-        </template>
-      </NDrawerContent>
-    </NDrawer>
+          <template #footer>
+            <div class="flex w-full items-center justify-between">
+              <div class="flex items-center gap-x-1">
+                <span class="iconify size-5 ph--gear-fine" />
+                <span class="leading-4">当前版本</span>
+              </div>
+              <span class="leading-4">{{ systemStore.version }}</span>
+            </div>
+          </template>
+        </NDrawerContent>
+      </NDrawer>
+    </ButtonAnimationProvider>
   </div>
 </template>

+ 1 - 1
src/layouts/header/actions/component/ThemeDropdown.vue

@@ -41,7 +41,7 @@ const themeDropdownOptions = [
 const themeIconName = computed(
   () =>
     themeDropdownOptions.find((item) => item.key === theme.value)?.iconName ||
-    'iconify ph--desktop',
+    'iconify ph--desktop size-5',
 )
 
 const onThemePopselectUpdated = (key: Theme) => {

+ 9 - 2
src/layouts/header/index.vue

@@ -1,4 +1,7 @@
 <script setup lang="ts">
+import { inject } from 'vue'
+
+import { mediaQueryInjectionKey } from '@/injection'
 import { usePreferencesStore } from '@/stores/preferences'
 
 import Actions from './actions/index.vue'
@@ -12,12 +15,16 @@ defineOptions({
 })
 
 const preferencesStore = usePreferencesStore()
+const mediaQuery = inject(mediaQueryInjectionKey)
 </script>
 <template>
   <header
     class="border-b border-naive-border bg-naive-card transition-[background-color,border-color]"
   >
-    <div class="flex max-sm:hidden">
+    <div
+      v-if="!mediaQuery?.sm.value"
+      class="flex"
+    >
       <LogoArea />
       <div class="flex flex-1 items-center p-4">
         <div class="flex flex-1 items-center">
@@ -57,6 +64,6 @@ const preferencesStore = usePreferencesStore()
         <Actions class="gap-x-3" />
       </div>
     </div>
-    <MobileHeader />
+    <MobileHeader v-else />
   </header>
 </template>

+ 10 - 4
src/layouts/header/mobile/MobileHeader.vue

@@ -14,10 +14,13 @@ const toggleLayoutSlideDirection = (direction: LayoutSlideDirection) => {
 }
 </script>
 <template>
-  <div class="flex items-center justify-between px-4 py-3 sm:hidden">
+  <div
+    class="flex items-center justify-between px-4 py-2"
+    @click="toggleLayoutSlideDirection(null)"
+  >
     <div
-      class="size-9"
-      @click="toggleLayoutSlideDirection('right')"
+      class="size-8"
+      @click.stop="toggleLayoutSlideDirection('right')"
     >
       <Logo />
     </div>
@@ -29,7 +32,10 @@ const toggleLayoutSlideDirection = (direction: LayoutSlideDirection) => {
       <span class="text-base">{{ router.currentRoute.value.meta.title }}</span>
     </div>
     <div class="flex items-center gap-x-2">
-      <ButtonAnimation @click="toggleLayoutSlideDirection('left')">
+      <ButtonAnimation
+        size="large"
+        @click="toggleLayoutSlideDirection('left')"
+      >
         <span class="iconify ph--list" />
       </ButtonAnimation>
     </div>

+ 49 - 26
src/layouts/index.vue

@@ -2,7 +2,7 @@
 import { useMediaQuery } from '@vueuse/core'
 import { isEmpty } from 'lodash-es'
 import { NScrollbar } from 'naive-ui'
-import { computed, provide } from 'vue'
+import { computed, provide, watch } from 'vue'
 
 import texturePng from '@/assets/texture.png'
 import { EmptyPlaceholder } from '@/components'
@@ -19,8 +19,6 @@ import FooterLayout from './footer/index.vue'
 import HeaderLayout from './header/index.vue'
 import MainLayout from './main/index.vue'
 
-import type { CSSProperties } from 'vue'
-
 defineOptions({
   name: 'Layout',
 })
@@ -29,43 +27,60 @@ const tabsStore = useTabsStore()
 const preferencesStore = usePreferencesStore()
 const { scrollbarInMainLayout } = useComponentThemeOverrides()
 
-const mainLayoutStyle = computed<CSSProperties | null>(() => {
-  return preferencesStore.layoutSlideDirection === 'right'
-    ? {
-        transform: `translate(${preferencesStore.preferences.menu.maxWidth || 0}px) scale(0.88)`,
-      }
-    : preferencesStore.layoutSlideDirection === 'left'
-      ? {
-          transform: `translate(-${62}px) scale(0.88)`,
-        }
-      : null
-})
-
-provide(mediaQueryInjectionKey, {
+const mediaQuery = {
   sm: useMediaQuery('(max-width: 640px)'),
   md: useMediaQuery('(max-width: 768px)'),
   lg: useMediaQuery('(max-width: 1024px)'),
   xl: useMediaQuery('(max-width: 1280px)'),
   '2xl': useMediaQuery('(max-width: 1536px)'),
+}
+
+const mobileLayoutTranslateOffset = computed(() => {
+  return preferencesStore.layoutSlideDirection === 'right'
+    ? preferencesStore.preferences.menu.maxWidth || 0
+    : preferencesStore.layoutSlideDirection === 'left'
+      ? -62
+      : 0
 })
+
+watch(
+  () => mediaQuery.sm.value,
+  (isMaxSm) => {
+    if (isMaxSm) {
+      preferencesStore.modify({
+        menu: {
+          collapsed: false,
+        },
+      })
+      preferencesStore.setLayoutSlideDirection(null)
+    }
+  },
+)
+
+provide(mediaQueryInjectionKey, mediaQuery)
 </script>
 <template>
   <div
     class="relative flex h-svh overflow-hidden"
     :style="{ backgroundImage: `url(${texturePng})` }"
   >
-    <MobileLeftAside />
+    <MobileLeftAside v-if="mediaQuery.sm.value" />
     <div
-      class="relative flex h-full w-full flex-col transition-[border-color,rounded,transform] max-sm:rounded-xl sm:transform-none!"
+      class="relative flex h-full w-full flex-col border-naive-border transition-[border-color,rounded,transform] max-sm:rounded-xl sm:transform-none!"
       :class="{
-        'overflow-hidden rounded-xl border border-naive-border': mainLayoutStyle,
-        'border-transparent': !mainLayoutStyle,
+        'overflow-hidden rounded-xl border': mediaQuery.sm.value && mobileLayoutTranslateOffset,
       }"
-      :style="mainLayoutStyle"
+      :style="
+        mediaQuery.sm.value && preferencesStore.layoutSlideDirection
+          ? {
+              transform: `translate(${mobileLayoutTranslateOffset}px) scale(0.88)`,
+            }
+          : null
+      "
     >
       <HeaderLayout />
       <div class="flex flex-1 overflow-hidden">
-        <AsideLayout />
+        <AsideLayout v-if="!mediaQuery.sm.value" />
         <div class="relative flex flex-1 flex-col overflow-x-hidden">
           <Transition
             type="transition"
@@ -77,13 +92,21 @@ provide(mediaQueryInjectionKey, {
             leave-from-class="grid-rows-[1fr]"
           >
             <div
-              v-if="!isEmpty(tabsStore.tabs) && preferencesStore.preferences.showTabs"
-              class="grid shrink-0 items-baseline overflow-hidden max-sm:hidden"
+              v-if="
+                !mediaQuery.sm.value &&
+                !isEmpty(tabsStore.tabs) &&
+                preferencesStore.preferences.showTabs
+              "
+              class="grid shrink-0 items-baseline overflow-hidden"
             >
               <Tabs />
             </div>
           </Transition>
           <NScrollbar
+            class="transition-[padding]"
+            :class="{
+              'pb-4': mediaQuery.sm.value && preferencesStore.layoutSlideDirection,
+            }"
             container-class="main-container"
             :theme-overrides="scrollbarInMainLayout"
           >
@@ -110,7 +133,7 @@ provide(mediaQueryInjectionKey, {
             leave-from-class="grid-rows-[1fr]"
           >
             <div
-              v-if="preferencesStore.preferences.showFooter"
+              v-if="!mediaQuery.sm.value && preferencesStore.preferences.showFooter"
               class="grid shrink-0 items-baseline overflow-hidden"
             >
               <FooterLayout />
@@ -119,6 +142,6 @@ provide(mediaQueryInjectionKey, {
         </div>
       </div>
     </div>
-    <MobileRightAside />
+    <MobileRightAside v-if="mediaQuery.sm.value" />
   </div>
 </template>

+ 0 - 4
src/main.ts

@@ -4,7 +4,6 @@ import { createPinia } from 'pinia'
 import { createApp } from 'vue'
 
 import App from './App.vue'
-import { resetSystemData } from './helpers/resetSystemData'
 import router from './router'
 
 async function setupApp() {
@@ -12,9 +11,6 @@ async function setupApp() {
 
   app.use(createPinia())
   app.use(router)
-
-  resetSystemData()
-
   await router.isReady()
 
   if (window.loaderElement) {

+ 6 - 3
src/stores/preferences.ts

@@ -1,9 +1,8 @@
 import { useStorage } from '@vueuse/core'
+import { mergeWith } from 'lodash-es'
 import { acceptHMRUpdate, defineStore } from 'pinia'
 import { ref, watch } from 'vue'
 
-import { mergeWithArrayReplace } from '@/utils/lodash-helpers'
-
 import type { WatermarkProps } from 'naive-ui'
 
 export type LayoutSlideDirection = 'left' | 'right' | null
@@ -79,7 +78,11 @@ export const usePreferencesStore = defineStore('preferencesStore', () => {
   const layoutSlideDirection = ref<LayoutSlideDirection>(null)
 
   const modify = (options: Partial<PreferencesOptions>) => {
-    preferences.value = mergeWithArrayReplace(preferences.value, options)
+    preferences.value = mergeWith({}, preferences.value, options, (objValue, srcValue) => {
+      if (Array.isArray(objValue) && Array.isArray(srcValue)) {
+        return srcValue
+      }
+    })
   }
 
   const setLayoutSlideDirection = (direction: LayoutSlideDirection) => {

+ 24 - 0
src/stores/system.ts

@@ -0,0 +1,24 @@
+import { useStorage } from '@vueuse/core'
+import { defineStore } from 'pinia'
+
+import packageJson from '@/../package.json'
+import { usePreferencesStore } from '@/stores/preferences'
+import { useTabsStore } from '@/stores/tabs'
+import { useUserStore } from '@/stores/user'
+
+export const useSystemStore = defineStore('systemStore', () => {
+  const version = useStorage('version', '')
+
+  const oldLocalStorage = localStorage.getItem('configure')
+  if (oldLocalStorage || version.value !== packageJson.version) {
+    useTabsStore().clearTabs()
+    usePreferencesStore().reset()
+    useUserStore().cleanup()
+    localStorage.clear()
+    version.value = packageJson.version
+  }
+
+  return {
+    version,
+  }
+})

+ 0 - 24
src/utils/lodash-helpers.ts

@@ -1,24 +0,0 @@
-import { mergeWith, flatMapDeep, keys, sortBy, isEqual, isPlainObject } from 'lodash-es'
-
-export function mergeWithArrayReplace<T>(target: T, source: any): T {
-  return mergeWith({}, target, source, (objValue, srcValue) => {
-    if (Array.isArray(objValue) && Array.isArray(srcValue)) {
-      return srcValue
-    }
-  })
-}
-
-export function getAllKeys(obj: Record<string, any>, prefix = ''): string[] {
-  return flatMapDeep(keys(obj), (key) => {
-    const fullKey = prefix ? `${prefix}.${key}` : key
-    return isPlainObject(obj[key])
-      ? [fullKey, ...getAllKeys(obj[key] as Record<string, any>, fullKey)]
-      : fullKey
-  })
-}
-
-export function haveSameKeys<T extends object, U extends object>(a: T, b: U): boolean {
-  const keysA = sortBy(getAllKeys(a as Record<string, any>))
-  const keysB = sortBy(getAllKeys(b as Record<string, any>))
-  return isEqual(keysA, keysB)
-}

+ 0 - 81
src/utils/lodash-heplers.test.ts

@@ -1,81 +0,0 @@
-import { describe, expect, it } from 'vitest'
-
-import { getAllKeys, haveSameKeys, mergeWithArrayReplace } from './lodash-helpers'
-
-describe('lodash-helpers', () => {
-  it('should be mergeWithArrayReplace', () => {
-    const obj1 = {
-      a: 1,
-      b: 2,
-      c: 3,
-      d: {
-        e: 4,
-        f: 5,
-      },
-      g: [1, 2],
-    }
-
-    const obj2 = {
-      a: 1,
-      b: 2,
-      c: 3,
-      d: {
-        f: 5,
-        z: 10,
-      },
-      g: [3, 2, 1],
-    }
-
-    const result = mergeWithArrayReplace(obj1, obj2)
-
-    expect(result).toEqual({
-      a: 1,
-      b: 2,
-      c: 3,
-      d: {
-        e: 4,
-        f: 5,
-        z: 10,
-      },
-      g: [3, 2, 1],
-    })
-  })
-
-  it('should be getAllKeys', () => {
-    const obj = {
-      a: 1,
-      b: 2,
-      c: 3,
-    }
-
-    const result = getAllKeys(obj)
-
-    expect(result).toEqual(['a', 'b', 'c'])
-  })
-
-  it('should be haveSameKeys', () => {
-    const obj1 = {
-      a: 1,
-      b: 2,
-      c: 3,
-      d: {
-        e: 4,
-        f: 5,
-      },
-    }
-
-    const obj2 = {
-      a: 11,
-      b: 22,
-      c: 32,
-      d: {
-        e: 4,
-        f: 5,
-      },
-    }
-
-    const result = haveSameKeys(obj1, obj2)
-
-    expect(result).toBe(true)
-  })
-})

+ 1 - 1
src/views/sign-in/component/ThemeColorPopover.vue

@@ -35,7 +35,7 @@ const colorSwatches = [
     <template #trigger>
       <ButtonAnimation>
         <template #default>
-          <span class="iconify size-5 ph--palette" />
+          <span class="iconify-[nimbus--color-palette] size-5" />
         </template>
       </ButtonAnimation>
     </template>

+ 1 - 0
vite.config.ts

@@ -17,6 +17,7 @@ export default defineConfig((env) => {
     },
     server: {
       port: 5799,
+      host: true,
     },
     build: {
       rollupOptions: {