mirror of
https://origin.cursor.com/mrdevmx/panels.git
synced 2026-10-09 20:43:17 +00:00
146 lines
3.8 KiB
Vue
146 lines
3.8 KiB
Vue
<template>
|
|
<Dialog
|
|
v-model:visible="visible"
|
|
modal
|
|
:header="title"
|
|
class="document-preview-dialog"
|
|
:style="{ width: 'min(920px, 96vw)' }"
|
|
:content-style="{ padding: '0' }"
|
|
@hide="onHide"
|
|
>
|
|
<div class="document-preview-body">
|
|
<div v-if="loading" class="document-preview-state">
|
|
<ProgressSpinner stroke-width="4" />
|
|
<span class="muted">Cargando vista previa…</span>
|
|
</div>
|
|
<div v-else-if="error" class="document-preview-state">
|
|
<i class="pi pi-exclamation-triangle" />
|
|
<span>{{ error }}</span>
|
|
</div>
|
|
<img v-else-if="kind === 'image' && url" :src="url" :alt="title" class="document-preview-image" />
|
|
<iframe v-else-if="kind === 'pdf' && url" :src="url" class="document-preview-frame" title="Vista previa PDF" />
|
|
<div v-else class="document-preview-state">
|
|
<i class="pi pi-file" />
|
|
<span>Este archivo no tiene vista previa en el panel.</span>
|
|
<Button label="Descargar" icon="pi pi-download" outlined size="small" @click="downloadFile" />
|
|
</div>
|
|
</div>
|
|
<template #footer>
|
|
<Button label="Descargar" icon="pi pi-download" text :disabled="!url" @click="downloadFile" />
|
|
<Button label="Cerrar" severity="secondary" @click="visible = false" />
|
|
</template>
|
|
</Dialog>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
const props = defineProps<{
|
|
modelValue: boolean;
|
|
path: string;
|
|
name: string;
|
|
mime?: string | null;
|
|
}>();
|
|
const emit = defineEmits<{ "update:modelValue": [value: boolean] }>();
|
|
|
|
const { api } = useApi();
|
|
const visible = computed({
|
|
get: () => props.modelValue,
|
|
set: (value) => emit("update:modelValue", value),
|
|
});
|
|
const title = computed(() => props.name || "Documento");
|
|
const loading = ref(false);
|
|
const error = ref("");
|
|
const url = ref("");
|
|
const blobRef = ref<Blob | null>(null);
|
|
|
|
const kind = computed<"image" | "pdf" | "other">(() => {
|
|
const mime = (props.mime || blobRef.value?.type || "").toLowerCase();
|
|
const name = props.name.toLowerCase();
|
|
if (mime.startsWith("image/") || /\.(jpe?g|png|gif|webp|bmp)$/i.test(name)) return "image";
|
|
if (mime.includes("pdf") || name.endsWith(".pdf")) return "pdf";
|
|
return "other";
|
|
});
|
|
|
|
async function loadPreview() {
|
|
revoke();
|
|
error.value = "";
|
|
blobRef.value = null;
|
|
if (!props.path) return;
|
|
loading.value = true;
|
|
try {
|
|
const blob = await api<Blob>(props.path);
|
|
blobRef.value = blob;
|
|
url.value = URL.createObjectURL(blob);
|
|
} catch (err: unknown) {
|
|
error.value = err instanceof Error ? err.message : "No se pudo abrir el documento";
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
}
|
|
|
|
function downloadFile() {
|
|
if (!url.value) return;
|
|
const a = document.createElement("a");
|
|
a.href = url.value;
|
|
a.download = props.name || "documento";
|
|
a.rel = "noopener";
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
a.remove();
|
|
}
|
|
|
|
function revoke() {
|
|
if (url.value) URL.revokeObjectURL(url.value);
|
|
url.value = "";
|
|
}
|
|
|
|
function onHide() {
|
|
revoke();
|
|
blobRef.value = null;
|
|
error.value = "";
|
|
}
|
|
|
|
watch(
|
|
() => [props.modelValue, props.path] as const,
|
|
([open]) => {
|
|
if (open) loadPreview();
|
|
else onHide();
|
|
},
|
|
);
|
|
</script>
|
|
|
|
<style scoped>
|
|
.document-preview-body {
|
|
min-height: 360px;
|
|
max-height: min(78vh, 760px);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: #f4f6f8;
|
|
}
|
|
.document-preview-state {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 28px 20px;
|
|
color: #5d6e84;
|
|
text-align: center;
|
|
}
|
|
.document-preview-state .pi {
|
|
font-size: 28px;
|
|
color: #8a96a8;
|
|
}
|
|
.document-preview-image {
|
|
display: block;
|
|
max-width: 100%;
|
|
max-height: min(78vh, 760px);
|
|
object-fit: contain;
|
|
background: #fff;
|
|
}
|
|
.document-preview-frame {
|
|
width: 100%;
|
|
height: min(78vh, 760px);
|
|
border: 0;
|
|
background: #fff;
|
|
}
|
|
</style>
|