Browse Source

refactor: adjust layout refresh button and watermark; allow static i18n on language switch (#4579)

* refactor: adjust layout refresh button and watermark; allow static i18n on language switch

* chore: typo
Vben 5 months ago
parent
commit
60c615ce8a

+ 18 - 1
apps/web-antd/src/layouts/basic.vue

@@ -1,10 +1,11 @@
 <script lang="ts" setup>
 import type { NotificationItem } from '@vben/layouts';
 
-import { computed, ref } from 'vue';
+import { computed, ref, watch } from 'vue';
 
 import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
 import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
+import { useWatermark } from '@vben/hooks';
 import { BookOpenText, CircleHelp, MdiGithub } from '@vben/icons';
 import {
   BasicLayout,
@@ -54,6 +55,7 @@ const notifications = ref<NotificationItem[]>([
 const userStore = useUserStore();
 const authStore = useAuthStore();
 const accessStore = useAccessStore();
+const { destroyWatermark, updateWatermark } = useWatermark();
 const showDot = computed(() =>
   notifications.value.some((item) => !item.isRead),
 );
@@ -103,6 +105,21 @@ function handleNoticeClear() {
 function handleMakeAll() {
   notifications.value.forEach((item) => (item.isRead = true));
 }
+watch(
+  () => preferences.app.watermark,
+  async (enable) => {
+    if (enable) {
+      await updateWatermark({
+        content: `${userStore.userInfo?.username}`,
+      });
+    } else {
+      destroyWatermark();
+    }
+  },
+  {
+    immediate: true,
+  },
+);
 </script>
 
 <template>

+ 1 - 3
apps/web-antd/src/router/guard.ts

@@ -34,9 +34,7 @@ function setupCommonGuard(router: Router) {
   router.afterEach((to) => {
     // 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行
 
-    if (preferences.tabbar.enable) {
-      loadedPaths.add(to.path);
-    }
+    loadedPaths.add(to.path);
 
     // 关闭页面加载进度条
     if (preferences.transition.progress) {

+ 18 - 1
apps/web-ele/src/layouts/basic.vue

@@ -1,10 +1,11 @@
 <script lang="ts" setup>
 import type { NotificationItem } from '@vben/layouts';
 
-import { computed, ref } from 'vue';
+import { computed, ref, watch } from 'vue';
 
 import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
 import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
+import { useWatermark } from '@vben/hooks';
 import { BookOpenText, CircleHelp, MdiGithub } from '@vben/icons';
 import {
   BasicLayout,
@@ -54,6 +55,7 @@ const notifications = ref<NotificationItem[]>([
 const userStore = useUserStore();
 const authStore = useAuthStore();
 const accessStore = useAccessStore();
+const { destroyWatermark, updateWatermark } = useWatermark();
 const showDot = computed(() =>
   notifications.value.some((item) => !item.isRead),
 );
@@ -103,6 +105,21 @@ function handleNoticeClear() {
 function handleMakeAll() {
   notifications.value.forEach((item) => (item.isRead = true));
 }
+watch(
+  () => preferences.app.watermark,
+  async (enable) => {
+    if (enable) {
+      await updateWatermark({
+        content: `${userStore.userInfo?.username}`,
+      });
+    } else {
+      destroyWatermark();
+    }
+  },
+  {
+    immediate: true,
+  },
+);
 </script>
 
 <template>

+ 1 - 3
apps/web-ele/src/router/guard.ts

@@ -34,9 +34,7 @@ function setupCommonGuard(router: Router) {
   router.afterEach((to) => {
     // 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行
 
-    if (preferences.tabbar.enable) {
-      loadedPaths.add(to.path);
-    }
+    loadedPaths.add(to.path);
 
     // 关闭页面加载进度条
     if (preferences.transition.progress) {

+ 19 - 1
apps/web-naive/src/layouts/basic.vue

@@ -1,10 +1,11 @@
 <script lang="ts" setup>
 import type { NotificationItem } from '@vben/layouts';
 
-import { computed, ref } from 'vue';
+import { computed, ref, watch } from 'vue';
 
 import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
 import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
+import { useWatermark } from '@vben/hooks';
 import { BookOpenText, CircleHelp, MdiGithub } from '@vben/icons';
 import {
   BasicLayout,
@@ -54,6 +55,7 @@ const notifications = ref<NotificationItem[]>([
 const userStore = useUserStore();
 const authStore = useAuthStore();
 const accessStore = useAccessStore();
+const { destroyWatermark, updateWatermark } = useWatermark();
 const showDot = computed(() =>
   notifications.value.some((item) => !item.isRead),
 );
@@ -103,6 +105,22 @@ function handleNoticeClear() {
 function handleMakeAll() {
   notifications.value.forEach((item) => (item.isRead = true));
 }
+
+watch(
+  () => preferences.app.watermark,
+  async (enable) => {
+    if (enable) {
+      await updateWatermark({
+        content: `${userStore.userInfo?.username}`,
+      });
+    } else {
+      destroyWatermark();
+    }
+  },
+  {
+    immediate: true,
+  },
+);
 </script>
 
 <template>

+ 1 - 3
apps/web-naive/src/router/guard.ts

@@ -34,9 +34,7 @@ function setupCommonGuard(router: Router) {
   router.afterEach((to) => {
     // 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行
 
-    if (preferences.tabbar.enable) {
-      loadedPaths.add(to.path);
-    }
+    loadedPaths.add(to.path);
 
     // 关闭页面加载进度条
     if (preferences.transition.progress) {

+ 3 - 3
docs/src/en/guide/essentials/settings.md

@@ -234,7 +234,6 @@ const defaultPreferences: Preferences = {
     showIcon: true,
     showMaximize: true,
     showMore: true,
-    showRefresh: true,
     styleType: 'chrome',
   },
   theme: {
@@ -262,6 +261,7 @@ const defaultPreferences: Preferences = {
     notification: true,
     sidebarToggle: true,
     themeToggle: true,
+    refresh: true,
   },
 };
 ```
@@ -421,8 +421,6 @@ interface TabbarPreferences {
   showMaximize: boolean;
   /** Whether to show the more button */
   showMore: boolean;
-  /** Whether to show the refresh button */
-  showRefresh: boolean;
   /** Tab style */
   styleType: TabsStyleType;
 }
@@ -469,6 +467,8 @@ interface WidgetPreferences {
   lockScreen: boolean;
   /** Whether notification widget is displayed */
   notification: boolean;
+  /** Whether to show the refresh button */
+  refresh: boolean;
   /** Whether sidebar show/hide widget is displayed */
   sidebarToggle: boolean;
   /** Whether theme switch widget is displayed */

+ 3 - 3
docs/src/guide/essentials/settings.md

@@ -256,7 +256,6 @@ const defaultPreferences: Preferences = {
     showIcon: true,
     showMaximize: true,
     showMore: true,
-    showRefresh: true,
     styleType: 'chrome',
   },
   theme: {
@@ -282,6 +281,7 @@ const defaultPreferences: Preferences = {
     languageToggle: true,
     lockScreen: true,
     notification: true,
+    refresh: true,
     sidebarToggle: true,
     themeToggle: true,
   },
@@ -445,8 +445,6 @@ interface TabbarPreferences {
   showMaximize: boolean;
   /** 显示更多按钮 */
   showMore: boolean;
-  /** 显示刷新按钮 */
-  showRefresh: boolean;
   /** 标签页风格 */
   styleType: TabsStyleType;
 }
@@ -494,6 +492,8 @@ interface WidgetPreferences {
   lockScreen: boolean;
   /** 是否显示通知部件 */
   notification: boolean;
+  /** 显示刷新按钮 */
+  refresh: boolean;
   /** 是否显示侧边栏显示/隐藏部件 */
   sidebarToggle: boolean;
   /** 是否显示主题切换部件 */

+ 1 - 1
packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap

@@ -81,7 +81,6 @@ exports[`defaultPreferences immutability test > should not modify the config obj
     "showIcon": true,
     "showMaximize": true,
     "showMore": true,
-    "showRefresh": true,
     "styleType": "chrome",
   },
   "theme": {
@@ -107,6 +106,7 @@ exports[`defaultPreferences immutability test > should not modify the config obj
     "languageToggle": true,
     "lockScreen": true,
     "notification": true,
+    "refresh": true,
     "sidebarToggle": true,
     "themeToggle": true,
   },

+ 2 - 1
packages/@core/preferences/src/config.ts

@@ -81,7 +81,7 @@ const defaultPreferences: Preferences = {
     showIcon: true,
     showMaximize: true,
     showMore: true,
-    showRefresh: true,
+
     styleType: 'chrome',
   },
   theme: {
@@ -107,6 +107,7 @@ const defaultPreferences: Preferences = {
     languageToggle: true,
     lockScreen: true,
     notification: true,
+    refresh: true,
     sidebarToggle: true,
     themeToggle: true,
   },

+ 2 - 2
packages/@core/preferences/src/types.ts

@@ -169,8 +169,6 @@ interface TabbarPreferences {
   showMaximize: boolean;
   /** 显示更多按钮 */
   showMore: boolean;
-  /** 显示刷新按钮 */
-  showRefresh: boolean;
   /** 标签页风格 */
   styleType: TabsStyleType;
 }
@@ -218,6 +216,8 @@ interface WidgetPreferences {
   lockScreen: boolean;
   /** 是否显示通知部件 */
   notification: boolean;
+  /** 显示刷新按钮 */
+  refresh: boolean;
   /** 是否显示侧边栏显示/隐藏部件 */
   sidebarToggle: boolean;
   /** 是否显示主题切换部件 */

+ 0 - 1
packages/@core/ui-kit/tabs-ui/src/components/widgets/index.ts

@@ -1,3 +1,2 @@
 export { default as TabsToolMore } from './tool-more.vue';
-export { default as TabsToolRefresh } from './tool-refresh.vue';
 export { default as TabsToolScreen } from './tool-screen.vue';

+ 0 - 31
packages/@core/ui-kit/tabs-ui/src/components/widgets/tool-refresh.vue

@@ -1,31 +0,0 @@
-<script lang="ts" setup>
-import { ref } from 'vue';
-
-import { RotateCw } from '@vben-core/icons';
-
-const emit = defineEmits<{ refresh: [] }>();
-
-const loading = ref(false);
-function handleClick() {
-  loading.value = true;
-
-  setTimeout(() => {
-    loading.value = false;
-  }, 1000);
-  emit('refresh');
-}
-</script>
-
-<template>
-  <div
-    class="flex-center hover:bg-muted hover:text-foreground text-muted-foreground border-border h-full cursor-pointer border-l px-[9px] text-lg font-semibold"
-    @click="handleClick"
-  >
-    <RotateCw
-      :class="{
-        'animate-spin duration-1000': loading,
-      }"
-      class="size-4"
-    />
-  </div>
-</template>

+ 1 - 12
packages/effects/hooks/src/use-watermark.ts

@@ -1,8 +1,6 @@
 import type { Watermark, WatermarkOptions } from 'watermark-js-plus';
 
-import { nextTick, onUnmounted, ref, watch } from 'vue';
-
-import { preferences } from '@vben/preferences';
+import { nextTick, onUnmounted, ref } from 'vue';
 
 const watermark = ref<Watermark>();
 const cachedOptions = ref<Partial<WatermarkOptions>>({
@@ -67,15 +65,6 @@ export function useWatermark() {
     watermark.value?.destroy();
   }
 
-  watch(
-    () => preferences.app.watermark,
-    (enable) => {
-      if (!enable) {
-        destroyWatermark();
-      }
-    },
-  );
-
   onUnmounted(() => {
     destroyWatermark();
   });

+ 2 - 2
packages/effects/layouts/src/basic/README.md

@@ -3,5 +3,5 @@
 ### header
 
 - 支持N个自定义插槽,命名方式:header-right-n,header-left-n
-- header-left-n ,排序方式:1-4 ,breadcrumb,6-x
-- header-right-n ,排序方式:1-4,global-search,6-9,theme-toggle,11-14,language-toggle,16-19,fullscreen,21-24,notification,26-29,user-dropdown,30-x
+- header-left-n ,排序方式:0-19 ,breadcrumb 21-x
+- header-right-n ,排序方式:0-49,global-search,51-59,theme-toggle,61-69,language-toggle,71-79,fullscreen,81-89,notification,91-149,user-dropdown,151-x

+ 31 - 12
packages/effects/layouts/src/basic/header/header.vue

@@ -1,9 +1,11 @@
 <script lang="ts" setup>
 import { computed, useSlots } from 'vue';
 
+import { useRefresh } from '@vben/hooks';
+import { RotateCw } from '@vben/icons';
 import { preferences, usePreferences } from '@vben/preferences';
 import { useAccessStore } from '@vben/stores';
-import { VbenFullScreen } from '@vben-core/shadcn-ui';
+import { VbenFullScreen, VbenIconButton } from '@vben-core/shadcn-ui';
 
 import {
   GlobalSearch,
@@ -29,45 +31,49 @@ withDefaults(defineProps<Props>(), {
 
 const emit = defineEmits<{ clearPreferencesAndLogout: [] }>();
 
+const REFERENCE_VALUE = 50;
+
 const accessStore = useAccessStore();
 const { globalSearchShortcutKey, preferencesButtonPosition } = usePreferences();
 const slots = useSlots();
+const { refresh } = useRefresh();
+
 const rightSlots = computed(() => {
-  const list = [{ index: 100, name: 'user-dropdown' }];
+  const list = [{ index: REFERENCE_VALUE + 100, name: 'user-dropdown' }];
   if (preferences.widget.globalSearch) {
     list.push({
-      index: 5,
+      index: REFERENCE_VALUE,
       name: 'global-search',
     });
   }
 
   if (preferencesButtonPosition.value.header) {
     list.push({
-      index: 10,
+      index: REFERENCE_VALUE + 10,
       name: 'preferences',
     });
   }
   if (preferences.widget.themeToggle) {
     list.push({
-      index: 15,
+      index: REFERENCE_VALUE + 20,
       name: 'theme-toggle',
     });
   }
   if (preferences.widget.languageToggle) {
     list.push({
-      index: 20,
+      index: REFERENCE_VALUE + 30,
       name: 'language-toggle',
     });
   }
   if (preferences.widget.fullscreen) {
     list.push({
-      index: 25,
+      index: REFERENCE_VALUE + 40,
       name: 'fullscreen',
     });
   }
   if (preferences.widget.notification) {
     list.push({
-      index: 30,
+      index: REFERENCE_VALUE + 50,
       name: 'notification',
     });
   }
@@ -82,7 +88,14 @@ const rightSlots = computed(() => {
 });
 
 const leftSlots = computed(() => {
-  const list: any[] = [];
+  const list: Array<{ index: number; name: string }> = [];
+
+  if (preferences.widget.refresh) {
+    list.push({
+      index: 0,
+      name: 'refresh',
+    });
+  }
 
   Object.keys(slots).forEach((key) => {
     const name = key.split('-');
@@ -100,16 +113,22 @@ function clearPreferencesAndLogout() {
 
 <template>
   <template
-    v-for="slot in leftSlots.filter((item) => item.index < 5)"
+    v-for="slot in leftSlots.filter((item) => item.index < REFERENCE_VALUE)"
     :key="slot.name"
   >
-    <slot :name="slot.name"></slot>
+    <slot :name="slot.name">
+      <template v-if="slot.name === 'refresh'">
+        <VbenIconButton class="my-0 rounded-md" @click="refresh">
+          <RotateCw class="size-4" />
+        </VbenIconButton>
+      </template>
+    </slot>
   </template>
   <div class="flex-center hidden lg:block">
     <slot name="breadcrumb"></slot>
   </div>
   <template
-    v-for="slot in leftSlots.filter((item) => item.index > 5)"
+    v-for="slot in leftSlots.filter((item) => item.index > REFERENCE_VALUE)"
     :key="slot.name"
   >
     <slot :name="slot.name"></slot>

+ 7 - 19
packages/effects/layouts/src/basic/layout.vue

@@ -3,14 +3,14 @@ import type { MenuRecordRaw } from '@vben/types';
 
 import { computed, useSlots, watch } from 'vue';
 
-import { useWatermark } from '@vben/hooks';
+import { useRefresh } from '@vben/hooks';
 import { $t } from '@vben/locales';
 import {
   preferences,
   updatePreferences,
   usePreferences,
 } from '@vben/preferences';
-import { useLockStore, useUserStore } from '@vben/stores';
+import { useLockStore } from '@vben/stores';
 import { deepToRaw, mapTree } from '@vben/utils';
 import { VbenAdminLayout } from '@vben-core/layout-ui';
 import { Toaster, VbenBackTop, VbenLogo } from '@vben-core/shadcn-ui';
@@ -44,9 +44,8 @@ const {
   sidebarCollapsed,
   theme,
 } = usePreferences();
-const userStore = useUserStore();
-const { updateWatermark } = useWatermark();
 const lockStore = useLockStore();
+const { refresh } = useRefresh();
 
 const sidebarTheme = computed(() => {
   const dark = isDark.value || preferences.theme.semiDarkSidebar;
@@ -129,20 +128,6 @@ function clearPreferencesAndLogout() {
   emit('clearPreferencesAndLogout');
 }
 
-watch(
-  () => preferences.app.watermark,
-  async (val) => {
-    if (val) {
-      await updateWatermark({
-        content: `${userStore.userInfo?.username}`,
-      });
-    }
-  },
-  {
-    immediate: true,
-  },
-);
-
 watch(
   () => preferences.app.layout,
   async (val) => {
@@ -156,6 +141,9 @@ watch(
   },
 );
 
+// 语言更新后,刷新页面
+watch(() => preferences.app.locale, refresh);
+
 const slots = useSlots();
 const headerSlots = computed(() => {
   return Object.keys(slots).filter((key) => key.startsWith('header-'));
@@ -267,7 +255,6 @@ const headerSlots = computed(() => {
       />
     </template>
     <template #mixed-menu>
-      <!-- :collapse="!preferences.sidebar.collapsedShowTitle" -->
       <LayoutMixedMenu
         :active-path="extraActiveMenu"
         :menus="wrapperMenus(headerMenus)"
@@ -308,6 +295,7 @@ const headerSlots = computed(() => {
     <template #content>
       <LayoutContent />
     </template>
+
     <template v-if="preferences.transition.loading" #content-overlay>
       <LayoutContentSpinner />
     </template>

+ 2 - 11
packages/effects/layouts/src/basic/tabbar/tabbar.vue

@@ -5,12 +5,7 @@ import { useRoute } from 'vue-router';
 import { useContentMaximize, useTabs } from '@vben/hooks';
 import { preferences } from '@vben/preferences';
 import { useTabbarStore } from '@vben/stores';
-import {
-  TabsToolMore,
-  TabsToolRefresh,
-  TabsToolScreen,
-  TabsView,
-} from '@vben-core/tabs-ui';
+import { TabsToolMore, TabsToolScreen, TabsView } from '@vben-core/tabs-ui';
 
 import { useTabbar } from './use-tabbar';
 
@@ -23,7 +18,7 @@ defineProps<{ showIcon?: boolean; theme?: string }>();
 const route = useRoute();
 const tabbarStore = useTabbarStore();
 const { contentIsMaximize, toggleMaximize } = useContentMaximize();
-const { refreshTab, unpinTab } = useTabs();
+const { unpinTab } = useTabs();
 
 const {
   createContextMenus,
@@ -66,10 +61,6 @@ if (!preferences.tabbar.persist) {
     @update:active="handleClick"
   />
   <div class="flex-center h-full">
-    <TabsToolRefresh
-      v-if="preferences.tabbar.showRefresh"
-      @refresh="refreshTab"
-    />
     <TabsToolMore v-if="preferences.tabbar.showMore" :menus="menus" />
     <TabsToolScreen
       v-if="preferences.tabbar.showMaximize"

+ 0 - 4
packages/effects/layouts/src/widgets/preferences/blocks/layout/tabbar.vue

@@ -20,7 +20,6 @@ const tabbarPersist = defineModel<boolean>('tabbarPersist');
 const tabbarDragable = defineModel<boolean>('tabbarDragable');
 const tabbarStyleType = defineModel<string>('tabbarStyleType');
 const tabbarShowMore = defineModel<boolean>('tabbarShowMore');
-const tabbarShowRefresh = defineModel<boolean>('tabbarShowRefresh');
 const tabbarShowMaximize = defineModel<boolean>('tabbarShowMaximize');
 
 const styleItems = computed((): SelectOption[] => [
@@ -60,9 +59,6 @@ const styleItems = computed((): SelectOption[] => [
   <SwitchItem v-model="tabbarShowMore" :disabled="!tabbarEnable">
     {{ $t('preferences.tabbar.showMore') }}
   </SwitchItem>
-  <SwitchItem v-model="tabbarShowRefresh" :disabled="!tabbarEnable">
-    {{ $t('preferences.tabbar.showRefresh') }}
-  </SwitchItem>
   <SwitchItem v-model="tabbarShowMaximize" :disabled="!tabbarEnable">
     {{ $t('preferences.tabbar.showMaximize') }}
   </SwitchItem>

+ 4 - 0
packages/effects/layouts/src/widgets/preferences/blocks/layout/widget.vue

@@ -22,6 +22,7 @@ const widgetLockScreen = defineModel<boolean>('widgetLockScreen');
 const appPreferencesButtonPosition = defineModel<string>(
   'appPreferencesButtonPosition',
 );
+const widgetRefresh = defineModel<boolean>('widgetRefresh');
 
 const positionItems = computed((): SelectOption[] => [
   {
@@ -61,6 +62,9 @@ const positionItems = computed((): SelectOption[] => [
   <SwitchItem v-model="widgetSidebarToggle">
     {{ $t('preferences.widget.sidebarToggle') }}
   </SwitchItem>
+  <SwitchItem v-model="widgetRefresh">
+    {{ $t('preferences.widget.refresh') }}
+  </SwitchItem>
   <SelectItem v-model="appPreferencesButtonPosition" :items="positionItems">
     {{ $t('preferences.position.title') }}
   </SelectItem>

+ 2 - 2
packages/effects/layouts/src/widgets/preferences/preferences-drawer.vue

@@ -100,7 +100,6 @@ const breadcrumbHideOnlyOne = defineModel<boolean>('breadcrumbHideOnlyOne');
 const tabbarEnable = defineModel<boolean>('tabbarEnable');
 const tabbarShowIcon = defineModel<boolean>('tabbarShowIcon');
 const tabbarShowMore = defineModel<boolean>('tabbarShowMore');
-const tabbarShowRefresh = defineModel<boolean>('tabbarShowRefresh');
 const tabbarShowMaximize = defineModel<boolean>('tabbarShowMaximize');
 const tabbarPersist = defineModel<boolean>('tabbarPersist');
 const tabbarDragable = defineModel<boolean>('tabbarDragable');
@@ -145,6 +144,7 @@ const widgetNotification = defineModel<boolean>('widgetNotification');
 const widgetThemeToggle = defineModel<boolean>('widgetThemeToggle');
 const widgetSidebarToggle = defineModel<boolean>('widgetSidebarToggle');
 const widgetLockScreen = defineModel<boolean>('widgetLockScreen');
+const widgetRefresh = defineModel<boolean>('widgetRefresh');
 
 const {
   diffPreference,
@@ -345,7 +345,6 @@ async function handleReset() {
                 v-model:tabbar-show-icon="tabbarShowIcon"
                 v-model:tabbar-show-maximize="tabbarShowMaximize"
                 v-model:tabbar-show-more="tabbarShowMore"
-                v-model:tabbar-show-refresh="tabbarShowRefresh"
                 v-model:tabbar-style-type="tabbarStyleType"
               />
             </Block>
@@ -359,6 +358,7 @@ async function handleReset() {
                 v-model:widget-language-toggle="widgetLanguageToggle"
                 v-model:widget-lock-screen="widgetLockScreen"
                 v-model:widget-notification="widgetNotification"
+                v-model:widget-refresh="widgetRefresh"
                 v-model:widget-sidebar-toggle="widgetSidebarToggle"
                 v-model:widget-theme-toggle="widgetThemeToggle"
               />

+ 2 - 2
packages/locales/src/langs/en-US.json

@@ -204,7 +204,6 @@
       "enable": "Enable Tab Bar",
       "icon": "Show Tabbar Icon",
       "showMore": "Show More Button",
-      "showRefresh": "Show Refresh Button",
       "showMaximize": "Show Maximize Button",
       "persist": "Persist Tabs",
       "dragable": "Enable Dragable Sort",
@@ -316,7 +315,8 @@
       "languageToggle": "Enable Language Toggle",
       "notification": "Enable Notification",
       "sidebarToggle": "Enable Sidebar Toggle",
-      "lockScreen": "Enable Lock Screen"
+      "lockScreen": "Enable Lock Screen",
+      "refresh": "Enable Refresh"
     }
   },
   "ui": {

+ 2 - 2
packages/locales/src/langs/zh-CN.json

@@ -204,7 +204,6 @@
       "enable": "启用标签栏",
       "icon": "显示标签栏图标",
       "showMore": "显示更多按钮",
-      "showRefresh": "显示刷新按钮",
       "showMaximize": "显示最大化按钮",
       "persist": "持久化标签页",
       "dragable": "启动拖拽排序",
@@ -316,7 +315,8 @@
       "languageToggle": "启用语言切换",
       "notification": "启用通知",
       "sidebarToggle": "启用侧边栏切换",
-      "lockScreen": "启用锁屏"
+      "lockScreen": "启用锁屏",
+      "refresh": "启用刷新"
     }
   },
   "ui": {

+ 19 - 1
playground/src/layouts/basic.vue

@@ -1,10 +1,11 @@
 <script lang="ts" setup>
 import type { NotificationItem } from '@vben/layouts';
 
-import { computed, ref } from 'vue';
+import { computed, ref, watch } from 'vue';
 
 import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
 import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
+import { useWatermark } from '@vben/hooks';
 import { BookOpenText, CircleHelp, MdiGithub } from '@vben/icons';
 import {
   BasicLayout,
@@ -54,6 +55,7 @@ const notifications = ref<NotificationItem[]>([
 const userStore = useUserStore();
 const authStore = useAuthStore();
 const accessStore = useAccessStore();
+const { destroyWatermark, updateWatermark } = useWatermark();
 const showDot = computed(() =>
   notifications.value.some((item) => !item.isRead),
 );
@@ -103,6 +105,22 @@ function handleNoticeClear() {
 function handleMakeAll() {
   notifications.value.forEach((item) => (item.isRead = true));
 }
+
+watch(
+  () => preferences.app.watermark,
+  async (enable) => {
+    if (enable) {
+      await updateWatermark({
+        content: `${userStore.userInfo?.username}`,
+      });
+    } else {
+      destroyWatermark();
+    }
+  },
+  {
+    immediate: true,
+  },
+);
 </script>
 
 <template>

+ 1 - 4
playground/src/router/guard.ts

@@ -33,10 +33,7 @@ function setupCommonGuard(router: Router) {
 
   router.afterEach((to) => {
     // 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行
-
-    if (preferences.tabbar.enable) {
-      loadedPaths.add(to.path);
-    }
+    loadedPaths.add(to.path);
 
     // 关闭页面加载进度条
     if (preferences.transition.progress) {