LeftMenuLayout.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. <template>
  2. <q-drawer
  3. v-bind="$attrs"
  4. v-model="leftDrawerOpen"
  5. show-if-above
  6. no-swipe-close
  7. no-swipe-open
  8. :width="250"
  9. :mini-width="64"
  10. :breakpoint="500"
  11. :mini="!$q.screen.lt.md ? miniState : true"
  12. :behavior="'desktop'"
  13. class="detached-container"
  14. >
  15. <div class="column full-height q-pa-sm no-wrap">
  16. <div
  17. v-if="!$q.screen.lt.md"
  18. class="toggle-button-wrapper absolute"
  19. style="top: 50%; right: -32px; z-index: 1"
  20. >
  21. <q-btn
  22. flat
  23. round
  24. size="sm"
  25. padding="8px 8px"
  26. @click="miniState = !miniState"
  27. >
  28. <q-icon
  29. :name="miniState ? 'mdi-chevron-right' : 'mdi-chevron-left'"
  30. />
  31. <q-tooltip
  32. anchor="center right"
  33. self="center left"
  34. :offset="[10, 10]"
  35. >{{ miniState ? $t('navigation.expand_menu') : $t('navigation.collapse_menu') }}</q-tooltip
  36. >
  37. </q-btn>
  38. </div>
  39. <q-list class="column q-mb-md no-wrap" style="border-radius: 6px">
  40. <q-item v-ripple clickable>
  41. <div class="flex">
  42. <q-item-section avatar>
  43. <template #default>
  44. <img
  45. :src="someAvatar()"
  46. alt="avatar"
  47. style="width: 20px; height: 20px; border-radius: 50%"
  48. />
  49. </template>
  50. </q-item-section>
  51. <q-item-section>{{ user_store.user.name }}</q-item-section>
  52. </div>
  53. <q-tooltip
  54. v-if="miniState && !$q.screen.lt.md"
  55. anchor="center right"
  56. self="center left"
  57. :offset="[10, 10]"
  58. >{{ user_store.user.name }}</q-tooltip
  59. >
  60. <q-menu anchor="center right" self="top start">
  61. <q-list class="column no-wrap overflow-hidden">
  62. <q-item
  63. v-ripple
  64. v-close-popup
  65. clickable
  66. :to="{ name: 'ProfilePage' }"
  67. exact
  68. exact-active-class="menu-selected"
  69. >
  70. <div class="flex">
  71. <q-item-section avatar>
  72. <q-icon
  73. name="account_circle"
  74. color="primary"
  75. style="font-size: 18px"
  76. />
  77. </q-item-section>
  78. <q-item-section>{{ $t("navigation.perfil") }}</q-item-section>
  79. </div>
  80. </q-item>
  81. <q-item v-ripple clickable @click="logoutFn">
  82. <div class="flex">
  83. <q-item-section avatar>
  84. <q-icon
  85. name="logout"
  86. color="negative"
  87. style="font-size: 18px"
  88. />
  89. </q-item-section>
  90. <q-item-section>{{ $t("navigation.logout") }}</q-item-section>
  91. </div>
  92. </q-item>
  93. </q-list>
  94. </q-menu>
  95. </q-item>
  96. </q-list>
  97. <q-list class="column no-wrap">
  98. <template v-for="menu in menus" :key="menu.name">
  99. <!-- Single Menu -->
  100. <q-item
  101. v-if="menu.type === 'single'"
  102. v-ripple
  103. clickable
  104. exact-active-class="menu-selected"
  105. exact
  106. active-class="menu-selected"
  107. :to="{ name: menu.name }"
  108. class="q-my-xs"
  109. >
  110. <q-item-section avatar>
  111. <q-icon :name="menu.icon" style="font-size: 18px" />
  112. </q-item-section>
  113. <q-item-section>{{ $t(menu.title) }}</q-item-section>
  114. <q-tooltip
  115. v-if="miniState && !$q.screen.lt.md"
  116. anchor="center right"
  117. self="center left"
  118. :offset="[10, 10]"
  119. >{{ $t(menu.title) }}</q-tooltip
  120. >
  121. </q-item>
  122. <!-- Expansive Menu with children -->
  123. <div v-else>
  124. <template v-if="!miniState">
  125. <q-tooltip
  126. v-if="miniState && !$q.screen.lt.md"
  127. anchor="center right"
  128. self="center left"
  129. :offset="[10, 10]"
  130. >{{ $t(menu.title) }}</q-tooltip
  131. >
  132. <q-expansion-item
  133. v-model="isExpasionItemExpanded"
  134. header-class="menu-item--spaced"
  135. :class="{
  136. 'menu-selected':
  137. childrenAreActive(menu.children) && !isExpasionItemExpanded,
  138. }"
  139. class="menu-item--spaced"
  140. >
  141. <template #header>
  142. <q-item-section avatar>
  143. <q-icon :name="menu.icon" style="font-size: 18px" />
  144. </q-item-section>
  145. <q-item-section>{{ $t(menu.title) }}</q-item-section>
  146. </template>
  147. <div v-for="child in menu.childrens" :key="child.name">
  148. <q-item
  149. v-ripple
  150. clickable
  151. :to="{ name: child.name }"
  152. exact
  153. exact-active-class="menu-selected"
  154. class="menu-item--spaced q-pl-lg"
  155. >
  156. <q-item-section avatar>
  157. <q-icon :name="child.icon" style="font-size: 18px" />
  158. </q-item-section>
  159. <q-item-section>{{ $t(child.title) }}</q-item-section>
  160. <q-tooltip
  161. v-if="miniState && !$q.screen.lt.md"
  162. anchor="center right"
  163. self="center left"
  164. :offset="[10, 10]"
  165. >{{ $t(child.title) }}</q-tooltip
  166. >
  167. </q-item>
  168. </div>
  169. </q-expansion-item>
  170. </template>
  171. <template v-else>
  172. <q-item
  173. v-ripple
  174. clickable
  175. exact
  176. exact-active-class="menu-selected"
  177. class="menu-item--spaced"
  178. >
  179. <q-item-section avatar>
  180. <q-icon :name="menu.icon" style="font-size: 18px" />
  181. </q-item-section>
  182. <q-item-section>{{ $t(menu.title) }}</q-item-section>
  183. <q-tooltip
  184. v-if="miniState && !$q.screen.lt.md"
  185. anchor="center right"
  186. self="center left"
  187. :offset="[10, 10]"
  188. >{{ $t(menu.title) }}</q-tooltip
  189. >
  190. <q-menu anchor="center right" self="top start">
  191. <q-list>
  192. <q-item
  193. v-for="child in menu.childrens"
  194. :key="child.name"
  195. v-ripple
  196. v-close-popup
  197. clickable
  198. :to="{ name: child.name }"
  199. exact
  200. exact-active-class="menu-selected"
  201. >
  202. <q-item-section avatar>
  203. <q-icon :name="child.icon" style="font-size: 18px" />
  204. </q-item-section>
  205. <q-item-section>{{ $t(child.title) }}</q-item-section>
  206. </q-item>
  207. </q-list>
  208. </q-menu>
  209. </q-item>
  210. </template>
  211. </div>
  212. </template>
  213. </q-list>
  214. <q-list class="q-mt-auto">
  215. <q-item v-ripple clickable @click="openUrl('https://softpar.inf.br')">
  216. <div class="flex full-width justify-center">
  217. <q-img
  218. :src="
  219. miniState || $q.screen.lt.md
  220. ? LogoSoftparMini
  221. : $q.dark.isActive
  222. ? LogoSoftparLight
  223. : LogoSoftparDark
  224. "
  225. style="width: 100%; height: 30px; max-width: 114px"
  226. />
  227. </div>
  228. </q-item>
  229. </q-list>
  230. <div class="full-width text-center text-subtitle3">
  231. <span class="text-caption text-weight-light">{{ version }}</span>
  232. </div>
  233. </div>
  234. </q-drawer>
  235. </template>
  236. <script setup>
  237. import { ref, onMounted, watch } from "vue";
  238. import { useAuth } from "src/composables/useAuth";
  239. import { permissionStore } from "src/stores/permission";
  240. import { useRouter, useRoute } from "vue-router";
  241. import { userStore } from "src/stores/user";
  242. import LogoSoftparLight from "src/assets/softpar_logo_light.svg";
  243. import LogoSoftparDark from "src/assets/softpar_logo_dark.svg";
  244. import LogoSoftparMini from "src/assets/softpar_logo_mini.svg";
  245. import { Cookies } from "quasar";
  246. const { logout } = useAuth();
  247. const router = useRouter();
  248. const route = useRoute();
  249. const user_store = userStore();
  250. const version = "0.0.1";
  251. const leftDrawerOpen = ref(true);
  252. const miniState = ref(Cookies.get("miniState") === "true" ?? false);
  253. const childrenAreActive = (children) => {
  254. if (!children) return false;
  255. return children.some((child) => {
  256. return route.path.includes(child.path);
  257. });
  258. };
  259. const someAvatar = () => {
  260. return "https://cdn.quasar.dev/img/avatar4.jpg";
  261. };
  262. const isExpasionItemExpanded = ref(false);
  263. const menus = ref([
  264. {
  265. type: "single",
  266. title: "navigation.dashboard",
  267. name: "HomePage",
  268. icon: "mdi-home-variant-outline",
  269. disable: false,
  270. permission: false,
  271. permissionScope: "dashboard",
  272. },
  273. {
  274. type: "expansive",
  275. title: "navigation.registration",
  276. icon: "mdi-cog-outline",
  277. disable: false,
  278. permission: false,
  279. permissionScope: "config",
  280. childrens: [
  281. {
  282. type: "single",
  283. title: "navigation.users",
  284. name: "UsersPage",
  285. icon: "mdi-account-multiple-outline",
  286. disable: false,
  287. permission: false,
  288. permissionScope: "config.user",
  289. },
  290. ],
  291. },
  292. ]);
  293. const getMenuAccess = () => {
  294. const { getAccess } = permissionStore();
  295. menus.value = menus.value
  296. .map((menu) => {
  297. if (menu.type === "expansive") {
  298. if (getAccess(menu.permissionScope, "menu")) {
  299. menu.permission = true;
  300. }
  301. menu.childrens = menu.childrens.filter((children) => {
  302. children.permission = getAccess(children.permissionScope, "menu");
  303. return children.permission;
  304. });
  305. return menu.childrens.length > 0 ? menu : null;
  306. } else {
  307. menu.permission = getAccess(menu.permissionScope, "menu");
  308. return menu;
  309. }
  310. })
  311. .filter((menu) => menu !== null);
  312. };
  313. const logoutFn = async () => {
  314. await logout();
  315. router.push({ name: "LoginPage" });
  316. };
  317. const openUrl = (url) => {
  318. window.open(url, "_blank");
  319. };
  320. watch(miniState, () => {
  321. Cookies.set("miniState", miniState.value);
  322. });
  323. onMounted(() => {
  324. getMenuAccess();
  325. });
  326. </script>
  327. <style lang="scss" scoped>
  328. @import "/src/css/quasar.variables.scss";
  329. .text-subtitle3 {
  330. font-size: 1.1rem !important;
  331. font-weight: 400 !important;
  332. }
  333. .menu-selected {
  334. background-color: rgba($primary, 0.1);
  335. color: $primary;
  336. }
  337. .toggle-button-wrapper {
  338. transition: transform 0.3s ease;
  339. }
  340. .toggle-button-wrapper:hover {
  341. transform: scale(1.1);
  342. }
  343. .menu-item--spaced {
  344. margin-top: 5px;
  345. margin-bottom: 5px;
  346. }
  347. </style>