浏览代码

feat: add support for custom slot header-index, sorted by index (#4039)

* feat: header右侧支持n个自定义位置插槽,插槽命名方式header-index,根据index大小排序。
框架默认插槽user-dropdown的index:20,notification的index:10 (#4034)

* chore: 将默认组件加入排序

* chore: 更改slot命名方式支持header左侧自定义插槽,命名方式:header-right-n,header-left-n,具体位置看README

---------

Co-authored-by: likui628 <90845831+likui628@users.noreply.github.com>
jinmao88 9 月之前
父节点
当前提交
84c8fb0ecc

+ 7 - 0
packages/effects/layouts/src/basic/README.md

@@ -0,0 +1,7 @@
+## layout
+
+### header
+
+- 支持N个自定义插槽,命名方式:header-right-n,header-left-n
+- header-left-n ,排序方式:1-5 ,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

+ 87 - 11
packages/effects/layouts/src/basic/header/header.vue

@@ -1,4 +1,6 @@
 <script lang="ts" setup>
+import { computed, useSlots } from 'vue';
+
 import { preferences, usePreferences } from '@vben/preferences';
 import { useAccessStore } from '@vben/stores';
 import { VbenFullScreen } from '@vben-core/shadcn-ui';
@@ -22,26 +24,100 @@ withDefaults(defineProps<Props>(), {
 
 const accessStore = useAccessStore();
 const { globalSearchShortcutKey } = usePreferences();
+const slots = useSlots();
+const rightSlots = computed(() => {
+  const list = [{ index: 30, name: 'user-dropdown' }];
+  if (preferences.widget.globalSearch) {
+    list.push({
+      index: 5,
+      name: 'global-search',
+    });
+  }
+  if (preferences.widget.themeToggle) {
+    list.push({
+      index: 10,
+      name: 'theme-toggle',
+    });
+  }
+  if (preferences.widget.languageToggle) {
+    list.push({
+      index: 15,
+      name: 'language-toggle',
+    });
+  }
+  if (preferences.widget.fullscreen) {
+    list.push({
+      index: 20,
+      name: 'fullscreen',
+    });
+  }
+  if (preferences.widget.notification) {
+    list.push({
+      index: 25,
+      name: 'notification',
+    });
+  }
+
+  Object.keys(slots).forEach((key) => {
+    const name = key.split('-');
+    if (key.startsWith('header-right')) {
+      list.push({ index: Number(name[2]), name: key });
+    }
+  });
+  return list.sort((a, b) => a.index - b.index);
+});
+const leftSlots = computed(() => {
+  const list: any[] = [];
+
+  Object.keys(slots).forEach((key) => {
+    const name = key.split('-');
+    if (key.startsWith('header-left')) {
+      list.push({ index: Number(name[2]), name: key });
+    }
+  });
+  return list.sort((a, b) => a.index - b.index);
+});
 </script>
 
 <template>
+  <template
+    v-for="slot in leftSlots.filter((item) => item.index < 5)"
+    :key="slot.name"
+  >
+    <slot :name="slot.name"></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)"
+    :key="slot.name"
+  >
+    <slot :name="slot.name"></slot>
+  </template>
   <div class="flex h-full min-w-0 flex-1 items-center">
     <slot name="menu"></slot>
   </div>
   <div class="flex h-full min-w-0 flex-shrink-0 items-center">
-    <GlobalSearch
-      v-if="preferences.widget.globalSearch"
-      :enable-shortcut-key="globalSearchShortcutKey"
-      :menus="accessStore.accessMenus"
-      class="mr-4"
-    />
-    <ThemeToggle v-if="preferences.widget.themeToggle" class="mr-2" />
-    <LanguageToggle v-if="preferences.widget.languageToggle" class="mr-2" />
-    <VbenFullScreen v-if="preferences.widget.fullscreen" class="mr-2" />
-    <slot v-if="preferences.widget.notification" name="notification"></slot>
-    <slot name="user-dropdown"></slot>
+    <template v-for="slot in rightSlots" :key="slot.name">
+      <slot :name="slot.name">
+        <template v-if="slot.name === 'global-search'">
+          <GlobalSearch
+            :enable-shortcut-key="globalSearchShortcutKey"
+            :menus="accessStore.accessMenus"
+            class="mr-4"
+          />
+        </template>
+        <template v-else-if="slot.name === 'theme-toggle'">
+          <ThemeToggle class="mr-2" />
+        </template>
+        <template v-else-if="slot.name === 'language-toggle'">
+          <LanguageToggle class="mr-2" />
+        </template>
+        <template v-else-if="slot.name === 'fullscreen'">
+          <VbenFullScreen class="mr-2" />
+        </template>
+      </slot>
+    </template>
   </div>
 </template>

+ 10 - 5
packages/effects/layouts/src/basic/layout.vue

@@ -1,7 +1,7 @@
 <script lang="ts" setup>
 import type { MenuRecordRaw } from '@vben/types';
 
-import { computed, watch } from 'vue';
+import { computed, useSlots, watch } from 'vue';
 
 import { useWatermark } from '@vben/hooks';
 import { $t } from '@vben/locales';
@@ -136,11 +136,8 @@ watch(
   () => preferences.app.watermark,
   async (val) => {
     if (val) {
-      // await nextTick();
-
-      updateWatermark({
+      await updateWatermark({
         content: `${preferences.app.name} 用户名: ${userStore.userInfo?.username}`,
-        // parent: contentRef.value,
       });
     }
   },
@@ -148,6 +145,11 @@ watch(
     immediate: true,
   },
 );
+
+const slots = useSlots();
+const headerSlots = computed(() => {
+  return Object.keys(slots).filter((key) => key.startsWith('header-'));
+});
 </script>
 
 <template>
@@ -240,6 +242,9 @@ watch(
         <template #notification>
           <slot name="notification"></slot>
         </template>
+        <template v-for="item in headerSlots" #[item]>
+          <slot :name="item"></slot>
+        </template>
       </LayoutHeader>
     </template>
     <!-- 侧边菜单区域 -->