header.vue 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. <script lang="ts" setup>
  2. import { computed, useSlots } from 'vue';
  3. import { useRefresh } from '@vben/hooks';
  4. import { RotateCw } from '@vben/icons';
  5. import { preferences, usePreferences } from '@vben/preferences';
  6. import { useAccessStore } from '@vben/stores';
  7. import { VbenFullScreen, VbenIconButton } from '@vben-core/shadcn-ui';
  8. import {
  9. GlobalSearch,
  10. LanguageToggle,
  11. PreferencesButton,
  12. ThemeToggle,
  13. TimezoneButton,
  14. } from '../../widgets';
  15. interface Props {
  16. /**
  17. * Logo 主题
  18. */
  19. theme?: string;
  20. }
  21. defineOptions({
  22. name: 'LayoutHeader',
  23. });
  24. withDefaults(defineProps<Props>(), {
  25. theme: 'light',
  26. });
  27. const emit = defineEmits<{ clearPreferencesAndLogout: [] }>();
  28. const REFERENCE_VALUE = 50;
  29. const accessStore = useAccessStore();
  30. const { globalSearchShortcutKey, preferencesButtonPosition } = usePreferences();
  31. const slots = useSlots();
  32. const { refresh } = useRefresh();
  33. const rightSlots = computed(() => {
  34. const list = [{ index: REFERENCE_VALUE + 100, name: 'user-dropdown' }];
  35. if (preferences.widget.globalSearch) {
  36. list.push({
  37. index: REFERENCE_VALUE,
  38. name: 'global-search',
  39. });
  40. }
  41. if (preferencesButtonPosition.value.header) {
  42. list.push({
  43. index: REFERENCE_VALUE + 10,
  44. name: 'preferences',
  45. });
  46. }
  47. if (preferences.widget.themeToggle) {
  48. list.push({
  49. index: REFERENCE_VALUE + 20,
  50. name: 'theme-toggle',
  51. });
  52. }
  53. if (preferences.widget.languageToggle) {
  54. list.push({
  55. index: REFERENCE_VALUE + 30,
  56. name: 'language-toggle',
  57. });
  58. }
  59. if (preferences.widget.timezone) {
  60. list.push({
  61. index: REFERENCE_VALUE + 40,
  62. name: 'timezone',
  63. });
  64. }
  65. if (preferences.widget.fullscreen) {
  66. list.push({
  67. index: REFERENCE_VALUE + 50,
  68. name: 'fullscreen',
  69. });
  70. }
  71. if (preferences.widget.notification) {
  72. list.push({
  73. index: REFERENCE_VALUE + 60,
  74. name: 'notification',
  75. });
  76. }
  77. Object.keys(slots).forEach((key) => {
  78. const name = key.split('-');
  79. if (key.startsWith('header-right')) {
  80. list.push({ index: Number(name[2]), name: key });
  81. }
  82. });
  83. return list.sort((a, b) => a.index - b.index);
  84. });
  85. const leftSlots = computed(() => {
  86. const list: Array<{ index: number; name: string }> = [];
  87. if (preferences.widget.refresh) {
  88. list.push({
  89. index: 0,
  90. name: 'refresh',
  91. });
  92. }
  93. Object.keys(slots).forEach((key) => {
  94. const name = key.split('-');
  95. if (key.startsWith('header-left')) {
  96. list.push({ index: Number(name[2]), name: key });
  97. }
  98. });
  99. return list.sort((a, b) => a.index - b.index);
  100. });
  101. function clearPreferencesAndLogout() {
  102. emit('clearPreferencesAndLogout');
  103. }
  104. </script>
  105. <template>
  106. <template
  107. v-for="slot in leftSlots.filter((item) => item.index < REFERENCE_VALUE)"
  108. :key="slot.name"
  109. >
  110. <slot :name="slot.name">
  111. <template v-if="slot.name === 'refresh'">
  112. <VbenIconButton class="my-0 mr-1 rounded-md" @click="refresh">
  113. <RotateCw class="size-4" />
  114. </VbenIconButton>
  115. </template>
  116. </slot>
  117. </template>
  118. <div class="flex-center hidden lg:block">
  119. <slot name="breadcrumb"></slot>
  120. </div>
  121. <template
  122. v-for="slot in leftSlots.filter((item) => item.index > REFERENCE_VALUE)"
  123. :key="slot.name"
  124. >
  125. <slot :name="slot.name"></slot>
  126. </template>
  127. <div
  128. :class="`menu-align-${preferences.header.menuAlign}`"
  129. class="flex h-full min-w-0 flex-1 items-center"
  130. >
  131. <slot name="menu"></slot>
  132. </div>
  133. <div class="flex h-full min-w-0 flex-shrink-0 items-center">
  134. <template v-for="slot in rightSlots" :key="slot.name">
  135. <slot :name="slot.name">
  136. <template v-if="slot.name === 'global-search'">
  137. <GlobalSearch
  138. :enable-shortcut-key="globalSearchShortcutKey"
  139. :menus="accessStore.accessMenus"
  140. class="mr-1 sm:mr-4"
  141. />
  142. </template>
  143. <template v-else-if="slot.name === 'preferences'">
  144. <PreferencesButton
  145. class="mr-1"
  146. @clear-preferences-and-logout="clearPreferencesAndLogout"
  147. />
  148. </template>
  149. <template v-else-if="slot.name === 'theme-toggle'">
  150. <ThemeToggle class="mr-1 mt-[2px]" />
  151. </template>
  152. <template v-else-if="slot.name === 'language-toggle'">
  153. <LanguageToggle class="mr-1" />
  154. </template>
  155. <template v-else-if="slot.name === 'fullscreen'">
  156. <VbenFullScreen class="mr-1" />
  157. </template>
  158. <template v-else-if="slot.name === 'timezone'">
  159. <TimezoneButton class="mr-1 mt-[2px]" />
  160. </template>
  161. </slot>
  162. </template>
  163. </div>
  164. </template>
  165. <style lang="scss" scoped>
  166. .menu-align-start {
  167. --menu-align: start;
  168. }
  169. .menu-align-center {
  170. --menu-align: center;
  171. }
  172. .menu-align-end {
  173. --menu-align: end;
  174. }
  175. </style>