@fge-bo-vue/pdf-viewer
v1.0.2
Published
Componente Vue 3 para visualización de PDFs con soporte para HTTP Range Requests (Formerly pdf-fge-vue3)
Downloads
45
Readme
@fge-bo-vue/pdf-viewer
📦 Formerly:
pdf-fge-vue3(deprecated)
Componente Vue 3 para visualización de PDFs con soporte para HTTP Range Requests, caché inteligente y múltiples estrategias de carga.
🔄 Migrating from pdf-fge-vue3
If you're migrating from the old package:
# Uninstall old package
npm uninstall pdf-fge-vue3
# Install new package
npm install @fge-bo-vue/pdf-vieweryour imports:
import { PdfViewer, PdfViewerRange } from '@fge-bo-vue/pdf-viewer';
import '@fge-bo-vue/pdf-viewer/styles.css';✅ No breaking changes - All APIs remain the same!
🎯 Características
- ✅ HTTP Range Requests: Descarga solo las partes necesarias del PDF
- ✅ 3 Estrategias de Carga: on-demand, progressive, complete
- ✅ Caché Inteligente: Persiste entre recargas (F5) con expiración configurable
- ✅ Pre-validación de URLs: Captura errores HTTP (403, 404, etc.) antes de cargar
- ✅ Arquitectura Modular: Composables y componentes reutilizables
- ✅ TypeScript: Tipado completo
- ✅ Optimización de Ancho de Banda: Modo on-demand carga solo páginas visibles
📦 Instalación
npm install @fge-bo-vue/pdf-viewer
# o
yarn add @fge-bo-vue/pdf-viewer⚠️ Importación de Estilos (OBLIGATORIO)
IMPORTANTE: Los estilos CSS son obligatorios para el correcto funcionamiento del componente. Debes importarlos en tu aplicación:
import '@fge-bo-vue/pdf-viewer/styles.css';📋 Componentes Disponibles
La librería incluye dos componentes principales:
1. PdfViewerRange (Recomendado)
- Soporta HTTP Range Requests
- 3 estrategias de carga:
on-demand,progressive,complete - Caché inteligente con expiración configurable
- Ideal para archivos grandes o conexiones lentas
2. PdfViewer (Clásico)
- Carga completa del PDF desde URL, File o Base64
- Soporte para integración con AGETIC
- Marcas de agua configurables
- Ideal para archivos pequeños o cuando no hay soporte de Range Requests
¿Cuál usar?
| Característica | PdfViewerRange | PdfViewer | |----------------|----------------|-----------| | Range Requests | ✅ Sí | ❌ No | | Caché inteligente | ✅ Sí | ❌ No | | Integración AGETIC | ❌ No | ✅ Sí | | Marcas de agua | ❌ No | ✅ Sí | | Base64 support | ❌ No | ✅ Sí | | File objects | ❌ No | ✅ Sí | | Optimizado para | Archivos grandes | Archivos pequeños | | Ancho de banda | Mínimo | Completo |
Recomendación:
- Usa PdfViewerRange si trabajas con archivos grandes (>5MB) y tu backend soporta Range Requests
- Usa PdfViewer si necesitas integración con AGETIC, marcas de agua, o trabajas con archivos pequeños
🚀 Uso de PdfViewerRange
Importación con defineAsyncComponent (Recomendado)
<script setup lang="ts">
import { defineAsyncComponent, ref } from 'vue';
import '@fge-bo-vue/pdf-viewer/styles.css'; // ⚠️ OBLIGATORIO
const PdfViewerRange = defineAsyncComponent(() =>
import('@fge-bo-vue/pdf-viewer').then((module) => module.PdfViewerRange)
);
const pdfUrl = ref('https://api.example.com/files/document.pdf');
const headers = ref({ Authorization: 'Bearer token123' });
</script>
<template>
<PdfViewerRange
:range-url="pdfUrl"
:http-headers="headers"
load-strategy="on-demand"
:cache-enabled="true"
:cache-expiration-minutes="5"
/>
</template>Importación Directa
<script setup lang="ts">
import { ref } from 'vue';
import { PdfViewerRange } from '@fge-bo-vue/pdf-viewer';
import '@fge-bo-vue/pdf-viewer/styles.css'; // ⚠️ OBLIGATORIO
const pdfUrl = ref('https://api.example.com/files/document.pdf');
</script>
<template>
<PdfViewerRange :range-url="pdfUrl" />
</template>Ejemplo Completo con PdfViewerRange
<script setup lang="ts">
import { ref } from 'vue';
import { PdfViewerRange } from '@fge-bo-vue/pdf-viewer';
import '@fge-bo-vue/pdf-viewer/styles.css'; // ⚠️ OBLIGATORIO
const pdfUrl = ref('https://api.example.com/files/document.pdf?token=abc123');
const headers = ref({
Authorization: 'Bearer token123',
});
const handleError = (message: string) => {
console.error('Error al cargar PDF:', message);
};
const handleLoaded = (pages: number) => {
console.log(`PDF cargado con ${pages} páginas`);
};
</script>
<template>
<PdfViewerRange
:range-url="pdfUrl"
:http-headers="headers"
:load-strategy="'on-demand'"
:cache-enabled="true"
:cache-expiration-minutes="5"
:download-name="'documento.pdf'"
:show-progress="true"
@error="handleError"
@loaded="handleLoaded"
/>
</template>Props de PdfViewerRange
| Prop | Tipo | Default | Descripción |
|------|------|---------|-------------|
| rangeUrl | string | requerido | URL del endpoint que soporta Range Requests |
| httpHeaders | Record<string, string> | {} | Headers HTTP adicionales (ej: Authorization) |
| loadStrategy | 'on-demand' \| 'progressive' \| 'complete' | 'on-demand' | Estrategia de carga del PDF |
| cacheEnabled | boolean | true | Habilitar caché del PDF |
| cacheExpirationMinutes | number | 1 | Tiempo de expiración del caché en minutos |
| rangeChunkSize | number | 65536 | Tamaño del chunk en bytes (64KB) |
| downloadName | string | 'documento.pdf' | Nombre del archivo al descargar |
| showProgress | boolean | true | Mostrar barra de progreso |
| pageScale | PageScale | 'auto' | Escala inicial de la página |
| pageNumber | number | 1 | Número de página inicial |
| toolbar | Partial<Toolbar> | Ver abajo | Configuración de la barra de herramientas |
Toolbar por Defecto
{
search: true,
paginator: true,
zoom: true,
fullscreen: true,
download: true,
print: true,
}📊 Estrategias de Carga
1. On-Demand (Recomendado para archivos grandes)
Carga solo las páginas visibles cuando el usuario navega a ellas.
Ventajas:
- Mínimo ancho de banda
- Carga inicial rápida (~100KB-1MB de metadatos)
- Ideal para documentos grandes (>10MB)
Uso:
<PdfViewerRange
:range-url="url"
:load-strategy="'on-demand'"
/>2. Progressive (Balance)
Precarga páginas cercanas anticipadamente mientras el usuario navega.
Ventajas:
- Balance entre velocidad y ancho de banda
- Experiencia de usuario fluida
- Bueno para documentos medianos (5-10MB)
Uso:
<PdfViewerRange
:range-url="url"
:load-strategy="'progressive'"
/>3. Complete (Máximo rendimiento local)
Descarga todo el PDF de una vez.
Ventajas:
- Navegación instantánea después de cargar
- Sin esperas al cambiar de página
- Ideal para documentos pequeños (<5MB)
Uso:
<PdfViewerRange
:range-url="url"
:load-strategy="'complete'"
/>🎨 Uso de PdfViewer
El componente PdfViewer es ideal para cuando:
- No necesitas HTTP Range Requests
- Trabajas con archivos pequeños
- Necesitas integración con AGETIC
- Requieres marcas de agua
- El PDF viene de File, Base64 o URL simple
Importación con defineAsyncComponent (Recomendado)
<script setup lang="ts">
import { defineAsyncComponent, ref } from 'vue';
import '@fge-bo-vue/pdf-viewer/styles.css'; // ⚠️ OBLIGATORIO
const PdfViewer = defineAsyncComponent(() =>
import('@fge-bo-vue/pdf-viewer').then((module) => module.PdfViewer)
);
const pdfFile = ref({
src: 'https://api.example.com/files/document.pdf',
downloadName: 'documento.pdf',
});
</script>
<template>
<PdfViewer
:file="pdfFile"
:token="'your-token'"
@error="(msg) => console.error(msg)"
/>
</template>Importación Directa
<script setup lang="ts">
import { ref } from 'vue';
import { PdfViewer } from '@fge-bo-vue/pdf-viewer';
import '@fge-bo-vue/pdf-viewer/styles.css'; // ⚠️ OBLIGATORIO
const pdfFile = ref({
src: { url: 'https://api.example.com/files/document.pdf' },
downloadName: 'documento.pdf',
});
</script>
<template>
<PdfViewer :file="pdfFile" :token="'your-token'" />
</template>Ejemplo Completo con PdfViewer
<script setup lang="ts">
import { ref } from 'vue';
import { PdfViewer } from '@fge-bo-vue/pdf-viewer';
import '@fge-bo-vue/pdf-viewer/styles.css'; // ⚠️ OBLIGATORIO
const pdfFile = ref({
src: {
url: 'https://api.example.com/files/document.pdf',
httpHeaders: {
authorization: 'Bearer token123',
},
},
downloadName: 'documento.pdf',
msFileId: 'file-id-123', // Para integración con AGETIC
});
const token = ref('your-session-token');
const handleError = (message: string) => {
alert(`Error: ${message}`);
};
const handleFileApproved = (data: any) => {
console.log('Archivo aprobado:', data);
};
</script>
<template>
<PdfViewer
:file="pdfFile"
:token="token"
:toolbar="{
search: true,
paginator: true,
zoom: true,
fullscreen: true,
download: true,
print: true,
agetic: {
enableSign: true
}
}"
@error="handleError"
@fileApprovedCD="handleFileApproved"
/>
</template>Props de PdfViewer
| Prop | Tipo | Default | Descripción |
|------|------|---------|-------------|
| file | FileSrcPdf | undefined | Objeto con la fuente del PDF |
| token | string | requerido | Token de sesión |
| pageScale | PageScale | 'auto' | Escala inicial de la página |
| pageNumber | number | 1 | Número de página inicial |
| toolbar | Partial<Toolbar> | Ver abajo | Configuración de la barra de herramientas |
| tokenAgetic | string | undefined | Token para integración AGETIC |
| modeAgetic | MODE_AGETIC | MODE_AGETIC.DEV | Modo de AGETIC (DEV/PROD) |
| verifyFgeEmployee | boolean | false | Verificar empleado FGE |
| watermark | WatermarkType | undefined | Configuración de marca de agua |
| isFetchV2 | boolean | false | Usar API v2 para obtener PDF |
| isBase64 | boolean | false | El PDF viene en formato Base64 |
| v2ParamsType | V2ParamsType | undefined | Parámetros para API v2 |
Tipo FileSrcPdf
interface FileSrcPdf {
src: PDFSrc; // URL string, File object, or { url: string, httpHeaders?: {} }
downloadName?: string;
msFileId?: string; // Para integración AGETIC
}Toolbar de PdfViewer
{
search: true,
paginator: true,
zoom: true,
fullscreen: true,
download: true,
print: true,
agetic: {
enableSign: boolean; // Habilitar firma digital AGETIC
}
}Eventos de PdfViewer
interface PdfViewerEvents {
error: (message: string) => void;
fileApprovedCD: (data: ApprobationData) => void; // Cuando se aprueba con firma AGETIC
}💾 Sistema de Caché (PdfViewerRange)
- ✅ Persiste entre recargas (F5) y navegación
- ✅ Usa Cache Storage API + localStorage
- ✅ Expiración configurable por tiempo
- ✅ Limpieza automática de caché antiguo
Configuración de Caché
<PdfViewerRange
:cache-enabled="true"
:cache-expiration-minutes="5"
/>Limpiar Caché Manualmente
<script setup>
const pdfViewer = ref();
// Limpiar caché del PDF actual
pdfViewer.value?.clearPdfCache();
// Limpiar todo el caché de PDFs
pdfViewer.value?.clearAllPdfCache();
</script>
<template>
<PdfViewerRange ref="pdfViewer" :range-url="url" />
</template>🔒 Manejo de Errores HTTP
El componente captura y muestra errores HTTP del backend automáticamente:
- 403 Forbidden: URL firmada expirada, sin permisos
- 404 Not Found: Documento no encontrado
- 401 Unauthorized: Sesión expirada
Ejemplo de Respuesta del Backend
{
"error": true,
"message": "URL firmada expirada.",
"response": null,
"status": 403
}El componente mostrará una pantalla de error elegante con el mensaje del backend.
🎨 Métodos Expuestos
Ambos componentes exponen métodos para controlar el visor programáticamente.
Métodos Comunes (PdfViewer y PdfViewerRange)
interface CommonMethods {
setZoom(zoom: PageScale | number): void;
setPage(page: number): void;
rotatePDF(): void;
downloadClick(): void;
printPDF(): void;
search(query?: string, findPrevious?: boolean): void;
}Métodos Exclusivos de PdfViewerRange
interface PdfViewerRangeMethods extends CommonMethods {
clearPdfCache(): Promise<void>; // Limpiar caché del PDF actual
clearAllPdfCache(): Promise<void>; // Limpiar todo el caché
}Uso de Métodos
<script setup>
import { ref } from 'vue';
import { PdfViewerRange, PdfViewer } from '@fge-bo-vue/pdf-viewer';
import '@fge-bo-vue/pdf-viewer/styles.css'; // ⚠️ OBLIGATORIO
// Funciona con ambos componentes
const pdfViewerRange = ref();
const pdfViewer = ref();
// Métodos comunes (disponibles en ambos)
const zoomIn = () => {
pdfViewerRange.value?.setZoom(1.5);
pdfViewer.value?.setZoom(1.5);
};
const goToPage = (page: number) => {
pdfViewerRange.value?.setPage(page);
pdfViewer.value?.setPage(page);
};
const rotate = () => {
pdfViewerRange.value?.rotatePDF();
pdfViewer.value?.rotatePDF();
};
const searchText = () => {
pdfViewerRange.value?.search('texto a buscar');
pdfViewer.value?.search('texto a buscar');
};
// Métodos exclusivos de PdfViewerRange
const clearCache = () => {
pdfViewerRange.value?.clearPdfCache();
};
const clearAllCache = () => {
pdfViewerRange.value?.clearAllPdfCache();
};
</script>
<template>
<!-- Con PdfViewerRange -->
<PdfViewerRange ref="pdfViewerRange" :range-url="url" />
<!-- Con PdfViewer -->
<PdfViewer ref="pdfViewer" :file="file" :token="token" />
<button @click="zoomIn">Zoom In</button>
<button @click="goToPage(5)">Ir a página 5</button>
<button @click="rotate">Rotar</button>
<button @click="searchText">Buscar</button>
<button @click="clearCache">Limpiar caché (solo Range)</button>
</template>📡 Eventos
Eventos de PdfViewerRange
interface PdfViewerRangeEvents {
error: (message: string) => void;
loaded: (pages: number) => void;
progress: (loaded: number, total: number) => void;
}Ejemplo:
<template>
<PdfViewerRange
:range-url="url"
@error="(msg) => console.error('Error:', msg)"
@loaded="(pages) => console.log('Cargado:', pages, 'páginas')"
@progress="(loaded, total) => console.log('Progreso:', loaded, '/', total)"
/>
</template>Eventos de PdfViewer
interface PdfViewerEvents {
error: (message: string) => void;
fileApprovedCD: (data: ApprobationData) => void; // Solo cuando se usa AGETIC
}Ejemplo:
<template>
<PdfViewer
:file="file"
:token="token"
@error="(msg) => alert(`Error: ${msg}`)"
@fileApprovedCD="(data) => console.log('Archivo aprobado:', data)"
/>
</template>🏗️ Arquitectura Modular
Estructura de Archivos
src/
├── components/
│ ├── PdfViewerRange.vue # Componente con Range Requests
│ ├── PdfViewer.vue # Componente clásico con AGETIC
│ ├── AgeticSidebar.vue # Integración con firma AGETIC
│ ├── PdfErrorDisplay.vue # Pantalla de errores HTTP
│ ├── PdfLoadingProgress.vue # Indicador de progreso
│ ├── LoadingComponent.vue # Componente de carga
│ ├── ValidationItem.vue # Item de validación
│ └── PDFToolbar.vue # Barra de herramientas
├── composables/
│ ├── usePdfCache.ts # Manejo de caché
│ ├── usePdfLoader.ts # Carga de PDFs con Range Requests
│ ├── usePdfValidation.ts # Pre-validación de URLs
│ ├── usePdfViewer.ts # Lógica del visor PDF
│ ├── usePdfSearch.ts # Búsqueda en PDF
│ └── usePdfActions.ts # Acciones (descargar, imprimir, etc.)
├── hooks/
│ └── useAgetic.ts # Hook para integración AGETIC
└── types/
└── index.ts # Tipos TypeScriptComposables
usePdfCache (PdfViewerRange)
const { getCachedPdf, cachePdf, clearCache, clearAllCache } = usePdfCache();Funciones:
getCachedPdf(url, expirationMinutes): Buscar PDF en cachécachePdf(url, data, strategy): Guardar PDF en cachéclearCache(url): Limpiar caché de un PDFclearAllCache(): Limpiar todo el caché
usePdfLoader
const { isLoading, loadProgress, loadPdf, getPdfData } = usePdfLoader();Funciones:
loadPdf(options, onProgress): Cargar PDF con Range RequestsgetPdfData(pdfDoc): Obtener datos completos del PDF
usePdfValidation
const { validateUrl } = usePdfValidation();Funciones:
validateUrl(url, headers): Pre-validar URL antes de cargar
🔧 Requisitos del Backend
El backend debe soportar HTTP Range Requests (RFC 7233):
Headers Requeridos
Accept-Ranges: bytes
Content-Range: bytes 0-1023/1234567
Content-Length: 1024Respuestas HTTP
- 206 Partial Content: Para requests con Range header
- 416 Range Not Satisfiable: Si el rango solicitado es inválido
- 200 OK: Para requests sin Range header (fallback)
Ejemplo de Implementación NestJS
Ver el backend de ejemplo en: /ms-files-v2/src/modules/file/file-range.service.ts
🐛 Troubleshooting
Los estilos no se aplican correctamente
Solución: Verifica que hayas importado los estilos:
import '@fge-bo-vue/pdf-viewer/styles.css';Esta importación es obligatoria y debe estar en el componente donde uses PdfViewer o PdfViewerRange.
El PDF no carga en modo on-demand (PdfViewerRange)
Verifica en DevTools → Network:
- Los requests deben tener header
Range: bytes=X-Y - Las respuestas deben ser
206 Partial Content - Backend debe enviar header
Accept-Ranges: bytes
El caché no funciona (PdfViewerRange)
- Verifica que
cacheEnabled={true} - Revisa la consola del navegador
- El caché solo funciona en HTTPS o localhost
- Solo aplica para
PdfViewerRange, no paraPdfViewer
El componente no se importa correctamente
Si usas defineAsyncComponent:
import '@fge-bo-vue/pdf-viewer/styles.css';
const PdfViewerRange = defineAsyncComponent(() =>
import('@fge-bo-vue/pdf-viewer').then((module) => module.PdfViewerRange)
);Si usas importación directa:
import { PdfViewerRange, PdfViewer } from '@fge-bo-vue/pdf-viewer';
import '@fge-bo-vue/pdf-viewer/styles.css';Errores CORS (PdfViewerRange)
El backend debe permitir estos headers:
Access-Control-Allow-Headers: Range
Access-Control-Expose-Headers: Content-Range, Accept-Ranges📄 Licencia
UNLICENSED - FGE Internal Use Only
