MainLayout.vue 4.4 KB

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