MainLayout.vue 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. <template>
  2. <q-layout class="main-layout relative" view="hHh lpR fFf">
  3. <q-header
  4. v-if="$q.screen.lt.sm"
  5. class="bg-surface column justify-end"
  6. :style="{
  7. height: `calc(50px + env(safe-area-inset-top))`,
  8. }"
  9. >
  10. </q-header>
  11. <q-page-container>
  12. <q-page class="bg-surface main-layout-page" style="overflow: hidden;">
  13. <q-scroll-area
  14. ref="scrollAreaRef"
  15. class="main-layout-scroll-area"
  16. :content-active-style="{ width: '100%', maxWidth: '100%', overflowX: 'hidden', boxSizing: 'border-box' }"
  17. :content-style="{ width: '100%', maxWidth: '100%', overflowX: 'hidden', boxSizing: 'border-box' }"
  18. :style="scrollAreaHeight"
  19. >
  20. <router-view v-slot="{ Component }">
  21. <Transition mode="out-in">
  22. <component
  23. :is="Component"
  24. :class="{ 'main-layout-view--mobile': $q.screen.lt.sm }"
  25. class="main-layout-view"
  26. />
  27. </Transition>
  28. </router-view>
  29. </q-scroll-area>
  30. </q-page>
  31. </q-page-container>
  32. <q-footer v-if="$q.screen.lt.sm" class="provider-bottom-nav bg-white">
  33. <nav class="provider-bottom-nav-inner">
  34. <router-link
  35. v-for="item in navItems"
  36. :key="item.name"
  37. :to="{ name: item.name }"
  38. :class="{ 'provider-bottom-nav-item--active': isNavItemActive(item) }"
  39. class="provider-bottom-nav-item"
  40. >
  41. <q-icon :name="item.icon" class="provider-bottom-nav-icon" />
  42. <span class="provider-bottom-nav-label font12">
  43. {{ item.label }}
  44. </span>
  45. </router-link>
  46. </nav>
  47. </q-footer>
  48. </q-layout>
  49. </template>
  50. <script setup>
  51. import { computed, useTemplateRef, watch } from "vue";
  52. import { useI18n } from "vue-i18n";
  53. import { useQuasar } from "quasar";
  54. import { useRoute } from "vue-router";
  55. defineOptions({
  56. name: "MainLayout",
  57. });
  58. const $q = useQuasar();
  59. const route = useRoute();
  60. const scrollAreaRef = useTemplateRef("scrollAreaRef");
  61. const { t } = useI18n();
  62. let oldValue = route.path;
  63. const navItems = computed(() => [
  64. { name: "DashboardPage", label: t('nav.home'), icon: "mdi-home-outline" },
  65. { name: "SearchPage", label: t('nav.search'), icon: "mdi-magnify" },
  66. { name: "CalendarPage", label: t('nav.agenda'), icon: "mdi-calendar-blank-outline" },
  67. { name: "ScheduleCartPage", label: t('nav.cart'), icon: "mdi-cart-outline" },
  68. { name: "ProfilePage", label: t('nav.profile'), icon: "mdi-account-circle-outline" },
  69. ]);
  70. const isNavItemActive = (item) => route.name === item.name;
  71. const scrollAreaHeight = computed(() => {
  72. if ($q.screen.lt.sm) {
  73. return 'height: calc(100dvh - 50px - env(safe-area-inset-top) - 80px - env(safe-area-inset-bottom)) !important;';
  74. }
  75. return 'height: 100dvh !important;';
  76. });
  77. watch(
  78. () => route.path,
  79. (value) => {
  80. if (oldValue !== value) {
  81. scrollAreaRef.value?.setScrollPosition("vertical", 0, 0);
  82. scrollAreaRef.value?.setScrollPosition("horizontal", 0, 0);
  83. }
  84. oldValue = value;
  85. }
  86. );
  87. </script>
  88. <style scoped>
  89. .main-layout {
  90. width: 100%;
  91. max-width: 100%;
  92. overflow-x: hidden;
  93. }
  94. .main-layout-page {
  95. width: 100%;
  96. max-width: 100%;
  97. overflow: hidden;
  98. }
  99. .main-layout-scroll-area {
  100. width: 100%;
  101. max-width: 100%;
  102. overflow: hidden;
  103. }
  104. .main-layout-view {
  105. width: 100%;
  106. max-width: 100%;
  107. min-width: 0;
  108. padding: 0 !important;
  109. box-sizing: border-box;
  110. overflow-x: hidden;
  111. }
  112. .main-layout-view--mobile {
  113. padding: 0 !important;
  114. }
  115. .v-enter-active {
  116. opacity: 1;
  117. transition: all 0.15s ease-in;
  118. }
  119. .v-leave-active {
  120. opacity: 1;
  121. transition: all 0.15s ease-out;
  122. }
  123. .v-enter-from,
  124. .v-leave-to {
  125. opacity: 0;
  126. transition: all 0.15s ease-in;
  127. }
  128. .v-leave-to {
  129. transition: all 0.15s ease-out;
  130. }
  131. .provider-bottom-nav {
  132. background: #ffffff;
  133. box-shadow: 0 -12px 30px rgba(38, 27, 52, 0.1);
  134. }
  135. .provider-bottom-nav-inner {
  136. display: grid;
  137. grid-template-columns: repeat(5, minmax(0, 1fr));
  138. align-items: end;
  139. height: 60px;
  140. padding: 0px 0px calc(12px + env(safe-area-inset-bottom));
  141. }
  142. .provider-bottom-nav-item {
  143. display: flex;
  144. flex-direction: column;
  145. align-items: center;
  146. justify-content: flex-end;
  147. gap: 2px;
  148. min-height: 40px;
  149. color: #a1a1a1;
  150. text-decoration: none;
  151. transition: color 0.2s ease;
  152. }
  153. .provider-bottom-nav-item--active {
  154. color: #ff00ea;
  155. }
  156. .provider-bottom-nav-icon {
  157. font-size: 24px;
  158. line-height: 1;
  159. }
  160. .provider-bottom-nav-label {
  161. line-height: 1.1;
  162. letter-spacing: -0.02em;
  163. }
  164. .provider-bottom-nav-item--active .provider-bottom-nav-label {
  165. font-weight: 700;
  166. }
  167. </style>