| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161 |
- <template>
- <div class="default-media-picker" :class="rootClass">
- <div v-if="labelOutside && label" class="default-media-picker__label">
- {{ label }}<span v-if="required" class="text-negative"> *</span>
- </div>
- <q-file
- v-model="model"
- v-bind="$attrs"
- :accept="accept"
- :class="{ 'required-field': required }"
- :dense="dense"
- :error="!!error"
- :error-message="errorMessage"
- :label="labelOutside ? undefined : label"
- :placeholder="placeholder"
- :rules="rules"
- lazy-rules="ondemand"
- outlined
- @update:model-value="onFileChange"
- >
- <template #prepend>
- <slot name="prepend">
- <q-icon name="attach_file" />
- </slot>
- </template>
- </q-file>
- <div
- v-if="showPreview"
- class="default-media-picker__preview"
- :style="{ height: `${previewHeight}px` }"
- >
- <img
- v-if="previewUrl && isImage"
- alt="Pré-visualização do arquivo"
- class="default-media-picker__image"
- :src="previewUrl"
- />
- <video
- v-else-if="previewUrl && isVideo"
- class="default-media-picker__video"
- controls
- :src="previewUrl"
- />
- <q-icon
- v-else-if="model"
- color="grey-6"
- name="mdi-file-check-outline"
- size="48px"
- />
- <span v-else class="text-caption text-grey-6">
- Nenhum arquivo selecionado
- </span>
- </div>
- </div>
- </template>
- <script setup>
- import { computed, onUnmounted, ref, watch, useAttrs } from "vue";
- defineOptions({ inheritAttrs: false });
- const {
- accept,
- dense,
- label,
- labelOutside,
- placeholder,
- previewHeight,
- rules,
- showPreview,
- } = defineProps({
- accept: { type: String, default: "image/*,video/*,.pdf" },
- dense: { type: Boolean, default: false },
- label: { type: String, default: "Arquivo" },
- labelOutside: { type: Boolean, default: true },
- placeholder: { type: String, default: "" },
- previewHeight: { type: Number, default: 200 },
- rules: { type: Array, default: () => [] },
- showPreview: { type: Boolean, default: false },
- });
- const attrs = useAttrs();
- const model = defineModel({ type: [File, null], default: null });
- const error = defineModel("error", {
- type: [String, Object, Array, Boolean, null],
- default: null,
- });
- const previewUrl = defineModel("previewUrl", { type: String, default: null });
- const localObjectUrl = ref(null);
- const errorMessage = computed(() =>
- typeof error.value === "string" ? error.value : "",
- );
- const isImage = computed(
- () => model.value?.type?.startsWith("image/") ?? false,
- );
- const isVideo = computed(
- () => model.value?.type?.startsWith("video/") ?? false,
- );
- const required = computed(() => rules.some((rule) => rule?.$id === "required"));
- const rootClass = computed(() => attrs.class);
- const revokeObjectUrl = () => {
- if (localObjectUrl.value) {
- URL.revokeObjectURL(localObjectUrl.value);
- localObjectUrl.value = null;
- }
- }
- const onFileChange = (file) => {
- revokeObjectUrl();
- if (file instanceof File && (isImage.value || isVideo.value)) {
- localObjectUrl.value = URL.createObjectURL(file);
- previewUrl.value = localObjectUrl.value;
- } else {
- previewUrl.value = null;
- }
- }
- watch(model, (file) => onFileChange(file));
- onUnmounted(() => {
- revokeObjectUrl();
- });
- </script>
- <style scoped>
- .default-media-picker__preview {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 100%;
- margin-top: 8px;
- overflow: hidden;
- border: 1px solid #e0e0e0;
- border-radius: 4px;
- background: #fafafa;
- }
- .default-media-picker__image,
- .default-media-picker__video {
- width: 100%;
- height: 100%;
- object-fit: contain;
- }
- </style>
|