MainLayout.vue 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. <template>
  2. <q-layout class="main-layout relative" view="hHh lpR fFf">
  3. <!-- <LeftMenuLayout v-if="!$q.screen.lt.sm" />
  4. <LeftMenuLayoutMobile v-else v-model="leftDrawerOpen" /> -->
  5. <q-header
  6. v-if="$q.screen.lt.sm"
  7. class="bg-surface column justify-end"
  8. :style="{
  9. height: `calc(50px + env(safe-area-inset-top))`,
  10. }"
  11. >
  12. <!-- <q-toolbar
  13. class="flex justify-between bg-primary"
  14. style="border-radius: 0 0 6px 6px !important"
  15. > -->
  16. <!-- <q-btn dense flat @click="toggleLeftDrawer">
  17. <q-icon name="menu" :color="$q.dark.isActive ? 'white' : 'black'" />
  18. </q-btn> -->
  19. <!-- <q-btn dense flat>
  20. <img
  21. :src="someAvatar()"
  22. alt="avatar"
  23. style="width: 20px; height: 20px; border-radius: 50%"
  24. />
  25. <q-menu anchor="center right" self="top start">
  26. <q-list class="column no-wrap overflow-hidden">
  27. <q-item
  28. v-ripple
  29. v-close-popup
  30. clickable
  31. :to="{ name: 'ProfilePage' }"
  32. exact
  33. exact-active-class="menu-selected"
  34. >
  35. <div class="flex">
  36. <q-item-section avatar>
  37. <q-icon
  38. name="account_circle"
  39. color="primary"
  40. style="font-size: 18px"
  41. />
  42. </q-item-section>
  43. <q-item-section>{{
  44. $t("user.profile.singular")
  45. }}</q-item-section>
  46. </div>
  47. </q-item>
  48. <q-item v-ripple clickable @click="logoutFn">
  49. <div class="flex">
  50. <q-item-section avatar>
  51. <q-icon
  52. name="logout"
  53. color="negative"
  54. style="font-size: 18px"
  55. />
  56. </q-item-section>
  57. <q-item-section>{{ $t("auth.logout") }}</q-item-section>
  58. </div>
  59. </q-item>
  60. </q-list>
  61. </q-menu>
  62. </q-btn> -->
  63. <!-- </q-toolbar> -->
  64. </q-header>
  65. <q-page-container>
  66. <q-page class="bg-surface">
  67. <q-scroll-area
  68. ref="scrollAreaRef"
  69. :style="scrollAreaStyle"
  70. >
  71. <router-view v-slot="{ Component }">
  72. <Transition mode="out-in">
  73. <component
  74. :is="Component"
  75. class="main-layout__view"
  76. :class="{ 'main-layout__view--mobile': $q.screen.lt.sm }"
  77. />
  78. </Transition>
  79. </router-view>
  80. </q-scroll-area>
  81. </q-page>
  82. </q-page-container>
  83. <q-footer v-if="$q.screen.lt.sm" class="provider-bottom-nav bg-white">
  84. <nav class="provider-bottom-nav__inner">
  85. <router-link
  86. v-for="item in navItems"
  87. :key="item.name"
  88. :to="{ name: item.name }"
  89. class="provider-bottom-nav__item"
  90. :class="{ 'provider-bottom-nav__item--active': isNavItemActive(item) }"
  91. >
  92. <q-icon :name="item.icon" class="provider-bottom-nav__icon" />
  93. <span class="provider-bottom-nav__label">{{ item.label }}</span>
  94. </router-link>
  95. </nav>
  96. </q-footer>
  97. </q-layout>
  98. </template>
  99. <script setup>
  100. import { computed, useTemplateRef, watch } from "vue";
  101. import { useQuasar } from "quasar";
  102. import { useRoute } from "vue-router";
  103. // import { useAuth } from "src/composables/useAuth";
  104. // import { useRouter } from "vue-router";
  105. // import LeftMenuLayout from "src/components/layout/LeftMenuLayout.vue";
  106. // import LeftMenuLayoutMobile from "src/components/layout/LeftMenuLayoutMobile.vue";
  107. defineOptions({
  108. name: "MainLayout",
  109. });
  110. // const { logout } = useAuth();
  111. const $q = useQuasar();
  112. const route = useRoute();
  113. // const leftDrawerOpen = ref(false);
  114. const scrollAreaRef = useTemplateRef("scrollAreaRef");
  115. // const router = useRouter();
  116. const MOBILE_HEADER_HEIGHT = 68;
  117. const MOBILE_BOTTOM_NAV_HEIGHT = 102;
  118. let oldValue = route.path;
  119. const navItems = [
  120. {
  121. name: "DashboardPage",
  122. label: "Início",
  123. icon: "mdi-home-outline",
  124. },
  125. {
  126. name: "SearchPage",
  127. label: "Busca",
  128. icon: "mdi-magnify",
  129. },
  130. {
  131. name: "AgendaPage",
  132. label: "Agenda",
  133. icon: "mdi-calendar-blank-outline",
  134. },
  135. {
  136. name: "ProfilePage",
  137. label: "Perfil",
  138. icon: "mdi-account-circle-outline",
  139. },
  140. ];
  141. const scrollAreaStyle = computed(() => {
  142. if ($q.screen.lt.sm) {
  143. return `height: calc(100dvh - ${MOBILE_HEADER_HEIGHT}px - env(safe-area-inset-top) - ${MOBILE_BOTTOM_NAV_HEIGHT}px - env(safe-area-inset-bottom)) !important;`;
  144. }
  145. return "height: calc(100dvh - env(safe-area-inset-top)) !important;";
  146. });
  147. const isNavItemActive = (item) => route.name === item.name;
  148. // const someAvatar = () => {
  149. // let random = Math.floor(Math.random() * 5) + 1;
  150. // return "https://cdn.quasar.dev/img/avatar" + random + ".jpg";
  151. // };
  152. // const logoutFn = async () => {
  153. // await logout();
  154. // router.push({ name: "LoginPage" });
  155. // };
  156. // const toggleLeftDrawer = () => {
  157. // leftDrawerOpen.value = !leftDrawerOpen.value;
  158. // };
  159. watch(
  160. () => route.path,
  161. (value) => {
  162. if (oldValue !== value) {
  163. scrollAreaRef.value?.setScrollPosition("vertical", 0, 0);
  164. scrollAreaRef.value?.setScrollPosition("horizontal", 0, 0);
  165. }
  166. oldValue = value;
  167. }
  168. );
  169. </script>
  170. <style scoped>
  171. .main-layout__view {
  172. padding: 20px !important;
  173. padding-right: 10px !important;
  174. }
  175. .main-layout__view--mobile {
  176. padding-left: 10px !important;
  177. padding-bottom: 18px !important;
  178. }
  179. .v-enter-active {
  180. opacity: 1;
  181. transition: all 0.15s ease-in;
  182. }
  183. .v-leave-active {
  184. opacity: 1;
  185. transition: all 0.15s ease-out;
  186. }
  187. .v-enter-from,
  188. .v-leave-to {
  189. opacity: 0;
  190. transition: all 0.15s ease-in;
  191. }
  192. .v-leave-to {
  193. transition: all 0.15s ease-out;
  194. }
  195. .provider-bottom-nav {
  196. background: #ffffff;
  197. box-shadow: 0 -12px 30px rgba(38, 27, 52, 0.1);
  198. }
  199. .provider-bottom-nav__inner {
  200. display: grid;
  201. grid-template-columns: repeat(4, minmax(0, 1fr));
  202. align-items: end;
  203. min-height: 56px;
  204. padding: 12px 10px calc(12px + env(safe-area-inset-bottom));
  205. }
  206. .provider-bottom-nav__item {
  207. display: flex;
  208. flex-direction: column;
  209. align-items: center;
  210. justify-content: flex-end;
  211. gap: 8px;
  212. min-height: 56px;
  213. color: #a1a1a1;
  214. text-decoration: none;
  215. transition: color 0.2s ease;
  216. }
  217. .provider-bottom-nav__item--active {
  218. color: #ff00ea;
  219. }
  220. .provider-bottom-nav__icon {
  221. font-size: 30px;
  222. line-height: 1;
  223. }
  224. .provider-bottom-nav__label {
  225. font-size: 12px;
  226. font-weight: 400;
  227. line-height: 1.1;
  228. letter-spacing: -0.02em;
  229. }
  230. .provider-bottom-nav__item--active .provider-bottom-nav__label {
  231. font-weight: 700;
  232. }
  233. </style>