layout.vue 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. <script lang="ts" setup>
  2. import { computed, watch } from 'vue';
  3. import { useWatermark } from '@vben/hooks';
  4. import { $t } from '@vben/locales';
  5. import {
  6. preferences,
  7. updatePreferences,
  8. usePreferences,
  9. } from '@vben/preferences';
  10. import { useCoreAccessStore, useCoreLockStore } from '@vben/stores';
  11. import { MenuRecordRaw } from '@vben/types';
  12. import { mapTree } from '@vben/utils';
  13. import { VbenAdminLayout } from '@vben-core/layout-ui';
  14. import { VbenBackTop, VbenLogo } from '@vben-core/shadcn-ui';
  15. import { Breadcrumb, Preferences } from '../widgets';
  16. import { LayoutContent } from './content';
  17. import { Copyright } from './copyright';
  18. import { LayoutFooter } from './footer';
  19. import { LayoutHeader } from './header';
  20. import {
  21. LayoutExtraMenu,
  22. LayoutMenu,
  23. LayoutMixedMenu,
  24. useExtraMenu,
  25. useMixedMenu,
  26. } from './menu';
  27. import { LayoutTabbar } from './tabbar';
  28. defineOptions({ name: 'BasicLayout' });
  29. const emit = defineEmits<{ clearPreferencesAndLogout: [] }>();
  30. const {
  31. isDark,
  32. isHeaderNav,
  33. isMixedNav,
  34. isMobile,
  35. isSideMixedNav,
  36. layout,
  37. sidebarCollapsed,
  38. } = usePreferences();
  39. const coreAccessStore = useCoreAccessStore();
  40. const { updateWatermark } = useWatermark();
  41. const coreLockStore = useCoreLockStore();
  42. const headerMenuTheme = computed(() => {
  43. return isDark.value ? 'dark' : 'light';
  44. });
  45. const theme = computed(() => {
  46. const dark = isDark.value || preferences.theme.semiDarkMenu;
  47. return dark ? 'dark' : 'light';
  48. });
  49. const logoClass = computed(() => {
  50. const { collapsedShowTitle } = preferences.sidebar;
  51. const classes: string[] = [];
  52. if (collapsedShowTitle && sidebarCollapsed.value && !isMixedNav.value) {
  53. classes.push('mx-auto');
  54. }
  55. if (isSideMixedNav.value) {
  56. classes.push('flex-center');
  57. }
  58. return classes.join(' ');
  59. });
  60. const isMenuRounded = computed(() => {
  61. return preferences.navigation.styleType === 'rounded';
  62. });
  63. const logoCollapsed = computed(() => {
  64. const shouldCollapse = isHeaderNav.value || isMixedNav.value;
  65. if (shouldCollapse) {
  66. return false;
  67. }
  68. const shouldExpandOnMobile = !sidebarCollapsed.value && isMobile.value;
  69. if (shouldExpandOnMobile) {
  70. return false;
  71. }
  72. return sidebarCollapsed.value || isSideMixedNav.value;
  73. });
  74. const showHeaderNav = computed(() => {
  75. return isHeaderNav.value || isMixedNav.value;
  76. });
  77. // 侧边多列菜单
  78. const {
  79. extraActiveMenu,
  80. extraMenus,
  81. handleDefaultSelect,
  82. handleMenuMouseEnter,
  83. handleMixedMenuSelect,
  84. handleSideMouseLeave,
  85. sidebarExtraVisible,
  86. } = useExtraMenu();
  87. const {
  88. handleMenuSelect,
  89. headerActive,
  90. headerMenus,
  91. sidebarActive,
  92. sidebarMenus,
  93. sidebarVisible,
  94. } = useMixedMenu();
  95. function wrapperMenus(menus: MenuRecordRaw[]) {
  96. return mapTree(menus, (item) => {
  97. return { ...item, name: $t(item.name) };
  98. });
  99. }
  100. function toggleSidebar() {
  101. updatePreferences({
  102. sidebar: {
  103. hidden: !preferences.sidebar.hidden,
  104. },
  105. });
  106. }
  107. function clearPreferencesAndLogout() {
  108. emit('clearPreferencesAndLogout');
  109. }
  110. watch(
  111. () => preferences.app.watermark,
  112. async (val) => {
  113. if (val) {
  114. // await nextTick();
  115. updateWatermark({
  116. content: `${preferences.app.name} 用户名: ${coreAccessStore.userInfo?.username}`,
  117. // parent: contentRef.value,
  118. });
  119. }
  120. },
  121. {
  122. immediate: true,
  123. },
  124. );
  125. </script>
  126. <template>
  127. <VbenAdminLayout
  128. v-model:sidebar-extra-visible="sidebarExtraVisible"
  129. :content-compact="preferences.app.contentCompact"
  130. :footer-enable="preferences.footer.enable"
  131. :footer-fixed="preferences.footer.fixed"
  132. :header-hidden="preferences.header.hidden"
  133. :header-mode="preferences.header.mode"
  134. :header-toggle-sidebar-button="preferences.widget.sidebarToggle"
  135. :header-visible="preferences.header.enable"
  136. :is-mobile="preferences.app.isMobile"
  137. :layout="layout"
  138. :sidebar-collapse="preferences.sidebar.collapsed"
  139. :sidebar-collapse-show-title="preferences.sidebar.collapsedShowTitle"
  140. :sidebar-enable="sidebarVisible"
  141. :sidebar-expand-on-hover="preferences.sidebar.expandOnHover"
  142. :sidebar-extra-collapse="preferences.sidebar.extraCollapse"
  143. :sidebar-hidden="preferences.sidebar.hidden"
  144. :sidebar-semi-dark="preferences.theme.semiDarkMenu"
  145. :sidebar-theme="theme"
  146. :sidebar-width="preferences.sidebar.width"
  147. :tabbar-enable="preferences.tabbar.enable"
  148. :tabbar-height="preferences.tabbar.height"
  149. @side-mouse-leave="handleSideMouseLeave"
  150. @toggle-sidebar="toggleSidebar"
  151. @update:sidebar-collapse="
  152. (value: boolean) => updatePreferences({ sidebar: { collapsed: value } })
  153. "
  154. @update:sidebar-enable="
  155. (value: boolean) => updatePreferences({ sidebar: { enable: value } })
  156. "
  157. @update:sidebar-expand-on-hover="
  158. (value: boolean) =>
  159. updatePreferences({ sidebar: { expandOnHover: value } })
  160. "
  161. @update:sidebar-extra-collapse="
  162. (value: boolean) =>
  163. updatePreferences({ sidebar: { extraCollapse: value } })
  164. "
  165. >
  166. <template v-if="preferences.app.enablePreferences" #preferences>
  167. <Preferences @clear-preferences-and-logout="clearPreferencesAndLogout" />
  168. </template>
  169. <template #floating-groups>
  170. <VbenBackTop />
  171. </template>
  172. <!-- logo -->
  173. <template #logo>
  174. <VbenLogo
  175. v-if="preferences.logo.enable"
  176. :class="logoClass"
  177. :collapsed="logoCollapsed"
  178. :src="preferences.logo.source"
  179. :text="preferences.app.name"
  180. :theme="showHeaderNav ? headerMenuTheme : theme"
  181. />
  182. </template>
  183. <!-- 头部区域 -->
  184. <template #header>
  185. <LayoutHeader :theme="theme">
  186. <template
  187. v-if="!showHeaderNav && preferences.breadcrumb.enable"
  188. #breadcrumb
  189. >
  190. <Breadcrumb
  191. :hide-when-only-one="preferences.breadcrumb.hideOnlyOne"
  192. :show-home="preferences.breadcrumb.showHome"
  193. :show-icon="preferences.breadcrumb.showIcon"
  194. :type="preferences.breadcrumb.styleType"
  195. />
  196. </template>
  197. <template v-if="showHeaderNav" #menu>
  198. <LayoutMenu
  199. :default-active="headerActive"
  200. :menus="wrapperMenus(headerMenus)"
  201. :rounded="isMenuRounded"
  202. :theme="headerMenuTheme"
  203. class="w-full"
  204. mode="horizontal"
  205. @select="handleMenuSelect"
  206. />
  207. </template>
  208. <template #user-dropdown>
  209. <slot name="user-dropdown"></slot>
  210. </template>
  211. <template #notification>
  212. <slot name="notification"></slot>
  213. </template>
  214. </LayoutHeader>
  215. </template>
  216. <!-- 侧边菜单区域 -->
  217. <template #menu>
  218. <LayoutMenu
  219. :accordion="preferences.navigation.accordion"
  220. :collapse="preferences.sidebar.collapsed"
  221. :collapse-show-title="preferences.sidebar.collapsedShowTitle"
  222. :default-active="sidebarActive"
  223. :menus="wrapperMenus(sidebarMenus)"
  224. :rounded="isMenuRounded"
  225. :theme="theme"
  226. mode="vertical"
  227. @select="handleMenuSelect"
  228. />
  229. </template>
  230. <template #mixed-menu>
  231. <!-- :collapse="!preferences.sidebar.collapsedShowTitle" -->
  232. <LayoutMixedMenu
  233. :active-path="extraActiveMenu"
  234. :menus="wrapperMenus(headerMenus)"
  235. :rounded="isMenuRounded"
  236. :theme="theme"
  237. @default-select="handleDefaultSelect"
  238. @enter="handleMenuMouseEnter"
  239. @select="handleMixedMenuSelect"
  240. />
  241. </template>
  242. <!-- 侧边额外区域 -->
  243. <template #side-extra>
  244. <LayoutExtraMenu
  245. :accordion="preferences.navigation.accordion"
  246. :collapse="preferences.sidebar.extraCollapse"
  247. :menus="wrapperMenus(extraMenus)"
  248. :rounded="isMenuRounded"
  249. :theme="theme"
  250. />
  251. </template>
  252. <template #side-extra-title>
  253. <VbenLogo
  254. v-if="preferences.logo.enable"
  255. :text="preferences.app.name"
  256. :theme="theme"
  257. />
  258. </template>
  259. <template #tabbar>
  260. <LayoutTabbar
  261. v-if="preferences.tabbar.enable"
  262. :show-icon="preferences.tabbar.showIcon"
  263. :theme="theme"
  264. />
  265. </template>
  266. <!-- 主体内容 -->
  267. <template #content>
  268. <LayoutContent />
  269. </template>
  270. <!-- 页脚 -->
  271. <template v-if="preferences.footer.enable" #footer>
  272. <LayoutFooter>
  273. <Copyright
  274. v-if="preferences.copyright.enable"
  275. v-bind="preferences.copyright"
  276. />
  277. </LayoutFooter>
  278. </template>
  279. <template #extra>
  280. <slot name="extra"></slot>
  281. <Transition v-if="preferences.widget.lockScreen" name="slide-up">
  282. <slot v-if="coreLockStore.isLockScreen" name="lock-screen"></slot>
  283. </Transition>
  284. </template>
  285. </VbenAdminLayout>
  286. </template>