MediasTab.vue 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. <template>
  2. <div class="q-pa-md">
  3. <div class="row q-col-gutter-md">
  4. <!-- Coluna esquerda: lista -->
  5. <div class="col-12 col-md-5">
  6. <div class="row justify-between items-center q-mb-md">
  7. <span class="text-subtitle1 text-weight-medium">Mídias</span>
  8. <q-btn
  9. icon="add"
  10. color="primary-2"
  11. style="height: 40px; width: 40px; border-radius: 8px"
  12. :disable="!unitId"
  13. @click="openAddDialog"
  14. />
  15. </div>
  16. <div v-if="loading" class="row justify-center q-pa-xl">
  17. <q-spinner color="primary" size="40px" />
  18. </div>
  19. <template v-else>
  20. <div
  21. v-if="medias.length === 0"
  22. class="text-center text-grey-6 q-pa-xl"
  23. >
  24. <q-icon
  25. name="mdi-image-multiple-outline"
  26. size="48px"
  27. color="grey-4"
  28. />
  29. <div class="q-mt-sm">Nenhuma mídia adicionada.</div>
  30. </div>
  31. <q-list v-else separator>
  32. <q-item
  33. v-for="(item, index) in medias"
  34. :key="item.id"
  35. clickable
  36. :active="selectedIndex === index"
  37. active-class="media-item-active"
  38. @click="selectedIndex = index"
  39. >
  40. <q-item-section avatar>
  41. <q-icon
  42. :name="getFileIcon(item.mime_type)"
  43. :color="getFileColor(item.mime_type)"
  44. size="md"
  45. />
  46. </q-item-section>
  47. <q-item-section>
  48. <q-item-label class="ellipsis" style="max-width: 180px">
  49. {{ item.title }}
  50. </q-item-label>
  51. <q-item-label caption>
  52. {{ formatDate(item.created_at) }}
  53. </q-item-label>
  54. </q-item-section>
  55. <q-item-section side />
  56. </q-item>
  57. </q-list>
  58. </template>
  59. </div>
  60. <!-- Coluna direita: pré-visualização -->
  61. <div class="col-12 col-md-7">
  62. <div class="preview-box">
  63. <div
  64. v-if="selectedIndex === null || !medias[selectedIndex]"
  65. class="flex flex-center full-height text-grey-5"
  66. style="min-height: 500px"
  67. >
  68. <div class="column items-center q-gutter-sm">
  69. <q-icon
  70. name="mdi-image-multiple-outline"
  71. size="64px"
  72. color="grey-3"
  73. />
  74. <span>Selecione uma mídia para visualizar</span>
  75. </div>
  76. </div>
  77. <template v-else>
  78. <img
  79. v-if="isImage(medias[selectedIndex].mime_type)"
  80. :src="medias[selectedIndex].file_url"
  81. style="width: 100%; border-radius: 8px; display: block"
  82. />
  83. <video
  84. v-else-if="isVideo(medias[selectedIndex].mime_type)"
  85. :src="medias[selectedIndex].file_url"
  86. controls
  87. style="width: 100%; border-radius: 8px; display: block"
  88. />
  89. <iframe
  90. v-else
  91. :src="medias[selectedIndex].file_url"
  92. style="
  93. width: 100%;
  94. min-height: 500px;
  95. border: none;
  96. border-radius: 8px;
  97. "
  98. />
  99. </template>
  100. </div>
  101. </div>
  102. </div>
  103. </div>
  104. </template>
  105. <script setup>
  106. import { ref, onMounted } from "vue";
  107. import { useQuasar } from "quasar";
  108. import { getMediasByUnit } from "src/api/unit_media";
  109. import AddMediaDialog from "src/pages/unit/components/AddMediaDialog.vue";
  110. const props = defineProps({
  111. unitId: { type: Number, default: null },
  112. });
  113. const $q = useQuasar();
  114. const medias = ref([]);
  115. const selectedIndex = ref(null);
  116. const loading = ref(false);
  117. async function fetchMedias() {
  118. if (!props.unitId) return;
  119. loading.value = true;
  120. try {
  121. medias.value = await getMediasByUnit(props.unitId);
  122. } catch (e) {
  123. console.error(e);
  124. } finally {
  125. loading.value = false;
  126. }
  127. }
  128. function openAddDialog() {
  129. $q.dialog({
  130. component: AddMediaDialog,
  131. componentProps: { unitId: props.unitId },
  132. }).onOk((result) => {
  133. medias.value.unshift(result);
  134. selectedIndex.value = 0;
  135. });
  136. }
  137. function isImage(mimeType) {
  138. return mimeType?.startsWith("image/");
  139. }
  140. function isVideo(mimeType) {
  141. return mimeType?.startsWith("video/");
  142. }
  143. function getFileIcon(mimeType) {
  144. if (isImage(mimeType)) return "mdi-image-outline";
  145. if (isVideo(mimeType)) return "mdi-video-outline";
  146. return "mdi-file-pdf-box";
  147. }
  148. function getFileColor(mimeType) {
  149. if (isImage(mimeType)) return "teal";
  150. if (isVideo(mimeType)) return "blue";
  151. return "negative";
  152. }
  153. function formatDate(dateStr) {
  154. if (!dateStr) return "";
  155. return new Date(dateStr).toLocaleDateString("pt-BR");
  156. }
  157. onMounted(fetchMedias);
  158. </script>
  159. <style scoped>
  160. .preview-box {
  161. border: 1px solid #e0e0e0;
  162. border-radius: 8px;
  163. overflow: hidden;
  164. min-height: 500px;
  165. }
  166. .media-item-active {
  167. background-color: rgba(255, 131, 64, 0.08);
  168. }
  169. .ellipsis {
  170. overflow: hidden;
  171. text-overflow: ellipsis;
  172. white-space: nowrap;
  173. }
  174. </style>