invoice / index.html
bep40's picture
Add 2 files
bfbccdc verified
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Ứng dụng PDF Viewer với Thumbnail & Slideshow</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.4.120/pdf.min.js"></script>
<style>
.highlight {
background-color: yellow;
font-weight: bold;
}
.thumbnail {
position: relative;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.thumbnail:hover {
transform: scale(1.05);
box-shadow: 0 0 12px rgba(0, 0, 0, 0.2);
}
.thumbnail img {
width: 100%;
height: auto;
border-radius: 8px;
}
.thumbnail .title {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: rgba(0,0,0,0.6);
color: white;
text-align: center;
padding: 4px 0;
font-size: 14px;
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
}
#pdfViewer canvas {
display: block;
margin: 0 auto;
max-width: 100%;
height: auto;
border-radius: 8px;
}
.text-layer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 10;
font-size: 16px;
line-height: 1.5;
}
</style>
</head>
<body class="bg-gray-100 text-gray-800 font-sans min-h-screen flex flex-col items-center">
<header class="w-full bg-gradient-to-r from-indigo-600 to-purple-700 text-white py-5 shadow-md">
<h1 class="text-3xl font-bold text-center">Ứng dụng PDF Viewer với Thumbnail & Tìm kiếm</h1>
<p class="text-center mt-1 text-sm">Xem, trình chiếu và tìm kiếm văn bản trong file PDF</p>
</header>
<main class="max-w-7xl w-full mx-auto p-6 mt-6 flex flex-col gap-6">
<!-- Danh sách PDF dạng thumbnail -->
<div class="border border-gray-300 rounded-lg p-4 bg-gray-50">
<h2 class="text-lg font-semibold mb-3">Chọn tài liệu PDF:</h2>
<div id="pdfGrid" class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4"></div>
</div>
<!-- Khung xem PDF -->
<div class="flex flex-col items-center">
<div id="pdfViewer" class="relative border border-gray-300 rounded-lg p-4 bg-gray-100 min-h-96 w-full max-w-4xl">
<p class="text-center text-gray-400 mt-20">Chọn một file PDF từ danh sách để xem</p>
</div>
<!-- Điều hướng trang -->
<div class="flex justify-between items-center w-full max-w-4xl mt-4">
<button onclick="prevPage()" id="prevBtn" class="bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600 transition disabled:opacity-50 disabled:cursor-not-allowed">⬅️ Trang trước</button>
<span id="pageInfo" class="text-lg font-semibold">Trang: 1 / 1</span>
<button onclick="nextPage()" id="nextBtn" class="bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600 transition disabled:opacity-50 disabled:cursor-not-allowed">Trang sau ➡️</button>
</div>
<!-- Slideshow controls -->
<div class="flex gap-4 items-center mt-4">
<button onclick="startSlideshow()" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 transition">▶️ Bắt đầu Slideshow</button>
<button onclick="stopSlideshow()" class="bg-red-500 text-white px-4 py-2 rounded hover:bg-red-600 transition">⏹️ Dừng Slideshow</button>
<span>Chuyển trang mỗi:</span>
<select id="intervalSelect" class="border rounded px-3 py-1">
<option value="2000">2 giây</option>
<option value="3000" selected>3 giây</option>
<option value="5000">5 giây</option>
</select>
</div>
<!-- Tìm kiếm nội dung -->
<div class="flex gap-2 items-center mt-6 w-full max-w-4xl">
<input type="text" id="searchText" placeholder="Nhập từ khóa tìm kiếm..." class="border p-2 rounded flex-grow focus:ring-2 focus:ring-blue-400 outline-none">
<button onclick="searchText()" class="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600 transition">🔍 Tìm kiếm</button>
</div>
</div>
</main>
<footer class="mt-10 text-gray-500 text-center pb-4 text-sm">
&copy; 2025 - Ứng dụng xem PDF với trang bìa, slideshow và tìm kiếm
</footer>
<script>
const pdfViewer = document.getElementById('pdfViewer');
const pageInfo = document.getElementById('pageInfo');
const prevBtn = document.getElementById('prevBtn');
const nextBtn = document.getElementById('nextBtn');
const pdfGrid = document.getElementById('pdfGrid');
const searchTextEl = document.getElementById('searchText');
const intervalSelect = document.getElementById('intervalSelect');
let pdfDocs = [];
let currentDocIndex = -1;
let currentPage = 1;
let totalPages = 0;
let currentHighlight = '';
let slideshowInterval = null;
// Danh sách file PDF mẫu
const pdfFiles = [
{ name: 'Tài liệu 1', url: 'files/document1.pdf', thumb: 'files/thumb1.jpg' },
{ name: 'Tài liệu 2', url: 'files/document2.pdf', thumb: 'files/thumb2.jpg' },
{ name: 'Tài liệu 3', url: 'files/document3.pdf', thumb: 'files/thumb3.jpg' },
{ name: 'Tài liệu 4', url: 'files/document4.pdf', thumb: 'files/thumb4.jpg' },
{ name: 'Tài liệu 5', url: 'files/document5.pdf', thumb: 'files/thumb5.jpg' }
];
// Tạo thumbnail và danh sách PDF
function updatePdfGrid() {
pdfGrid.innerHTML = '';
pdfFiles.forEach((file, index) => {
const div = document.createElement('div');
div.className = 'thumbnail';
div.innerHTML = `
<img src="${file.thumb}" alt="${file.name}" class="w-full h-auto object-cover">
<div class="title">${file.name}</div>
`;
div.onclick = () => {
currentDocIndex = index;
currentPage = 1;
currentHighlight = '';
loadPdf(index);
};
pdfGrid.appendChild(div);
});
}
// Load file PDF từ URL
function loadPdf(index) {
const file = pdfFiles[index];
pdfjsLib.getDocument(file.url).promise.then(pdf => {
pdfDocs[index] = pdf;
totalPages = pdf.numPages;
pageInfo.textContent = `Trang: 1 / ${totalPages}`;
renderPage(currentPage);
enableNavigationButtons();
});
}
// Render trang PDF
function renderPage(pageNumber) {
if (currentDocIndex === -1) {
pdfViewer.innerHTML = '<p class="text-center text-gray-400 mt-20">Vui lòng chọn một file PDF từ danh sách.</p>';
return;
}
const pdf = pdfDocs[currentDocIndex];
if (!pdf) {
pdfViewer.innerHTML = '<p class="text-center text-gray-400 mt-20">Đang tải tài liệu...</p>';
return;
}
pdf.getPage(pageNumber).then(page => {
pdfViewer.innerHTML = '';
const viewport = page.getViewport({ scale: 1.5 });
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
const renderContext = {
canvasContext: context,
viewport: viewport
};
pdfViewer.appendChild(canvas);
page.render(renderContext).promise.then(() => {
if (currentHighlight.trim() !== '') {
renderTextLayer(page, currentHighlight);
}
});
});
}
// Vẽ lớp văn bản để highlight
function renderTextLayer(page, highlightText) {
const viewport = page.getViewport({ scale: 1.5 });
const textLayer = document.createElement('div');
textLayer.className = 'text-layer';
pdfViewer.appendChild(textLayer);
page.getTextContent().then(textContent => {
textLayer.innerHTML = '';
textContent.items.forEach(item => {
const x = item.transform[4];
const y = viewport.height - item.transform[5];
const span = document.createElement('span');
span.style.position = 'absolute';
span.style.left = `${x}px`;
span.style.top = `${y}px`;
span.style.fontSize = `${viewport.scale * 14}px`;
span.style.pointerEvents = 'auto';
span.innerHTML = item.str.replace(new RegExp(`(${highlightText})`, 'gi'), '<span class="highlight">$1</span>');
textLayer.appendChild(span);
});
});
}
// Điều hướng trang
function nextPage() {
if (currentDocIndex === -1) return;
if (currentPage < totalPages) {
currentPage++;
pageInfo.textContent = `Trang: ${currentPage} / ${totalPages}`;
renderPage(currentPage);
}
enableNavigationButtons();
}
function prevPage() {
if (currentDocIndex === -1) return;
if (currentPage > 1) {
currentPage--;
pageInfo.textContent = `Trang: ${currentPage} / ${totalPages}`;
renderPage(currentPage);
}
enableNavigationButtons();
}
function enableNavigationButtons() {
prevBtn.disabled = currentPage <= 1;
nextBtn.disabled = currentPage >= totalPages;
}
// Tìm kiếm văn bản trong PDF
function searchText() {
const query = searchTextEl.value.trim();
if (!query) return alert("Vui lòng nhập từ khóa để tìm kiếm.");
if (currentDocIndex === -1) return alert("Vui lòng chọn một file PDF từ danh sách.");
const pdf = pdfDocs[currentDocIndex];
let found = false;
for (let i = 1; i <= pdf.numPages; i++) {
pdf.getPage(i).then(page => {
page.getTextContent().then(textContent => {
const text = textContent.items.map(item => item.str).join(' ').toLowerCase();
if (text.includes(query.toLowerCase())) {
currentPage = i;
pageInfo.textContent = `Trang: ${currentPage} / ${totalPages}`;
currentHighlight = query;
renderPage(currentPage);
found = true;
}
});
});
}
setTimeout(() => {
if (!found) alert(`Không tìm thấy từ khóa "${query}" trong tài liệu này.`);
}, 1500);
}
// Chức năng slideshow
function startSlideshow() {
if (currentDocIndex === -1 || totalPages <= 1) return;
stopSlideshow(); // Dừng nếu đang chạy
const interval = parseInt(intervalSelect.value);
slideshowInterval = setInterval(() => {
if (currentPage < totalPages) {
currentPage++;
pageInfo.textContent = `Trang: ${currentPage} / ${totalPages}`;
renderPage(currentPage);
} else {
stopSlideshow();
}
}, interval);
}
function stopSlideshow() {
if (slideshowInterval) clearInterval(slideshowInterval);
}
// Khởi động ứng dụng
updatePdfGrid();
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-qwensite.hf.space/logo.svg" alt="qwensite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-qwensite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >QwenSite</a> - 🧬 <a href="https://enzostvs-qwensite.hf.space?remix=bep40/invoice" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>