<%@ page language=“java” import=“java.util.*” pageEncoding=“gbk”%>
<%@ taglib uri=“http://java.sun.com/jsp/jstl/functions” prefix=“fn” %>
<%@ taglib uri=“http://java.sun.com/jsp/jstl/core” prefix=“c”%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+“😕/”+request.getServerName()+“:”+request.getServerPort()+path+“/”;
%>
<!DOCTYPE html> <html> <head> <meta charset="gbk"> <meta name="renderer" content="webkit"> <title>PDF在线盖章系统</title> <meta http-equiv="X-UA-Compatible" content="IE=edge,Chrome=1" /> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <link rel="stylesheet" href="layui/css/layui.css" media="all"> <link rel="stylesheet" type="text/css" href="layuis/css/common_main.css"/> <script type="text/javascript" src="jquery/jquery-1.7.2.min.js"></script> <script src="layui/layui.all.js" charset="gbk"></script> <script type="text/javascript" src="js/pdf/pdf.min.js" charset="gbk"></script> <script type="text/javascript" src="js/pdf/jspdf.umd.min.js" charset="gbk"></script> <script type="text/javascript" src="js/pdf/pdf.worker.min.js" charset="gbk"></script> <style type="text/css"> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { background-color: #f5f7fa; color: #333; line-height: 1.6; padding: 20px; } .container { max-width: 1200px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1); overflow: hidden; } header { background: linear-gradient(135deg, #2c3e50, #4a6491); color: white; padding: 20px; text-align: center; } h1 { font-size: 28px; margin-bottom: 10px; } .description { font-size: 16px; opacity: 0.9; max-width: 600px; margin: 0 auto; } .main-content { display: flex; padding: 20px; gap: 20px; } .left-panel { flex: 0 0 250px; display: flex; flex-direction: column; gap: 20px; } .control-panel { background: #f8f9fa; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); } .button { padding: 12px; border: none; border-radius: 6px; cursor: pointer; font-weight: 600; transition: all 0.3s; width: 100%; margin-bottom: 10px; } .secondary-btn { background: #4a6491; color: white; } .secondary-btn:hover { background: #3a547e; } .secondary-btn2 { background: #27ae60; color: white; } .secondary-btn2:hover { background: #219653; } .secondary-btn3 { background: #e74c3c; color: white; } .secondary-btn3:hover { background: #c0392b; } .stamp-panel { background: #f8f9fa; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); } .stamp-panel h3 { margin-bottom: 15px; color: #2c3e50; border-bottom: 1px solid #ddd; padding-bottom: 10px; } .stamp-item { padding: 12px; background: white; border: 2px dashed #4a6491; border-radius: 6px; text-align: center; cursor: grab; margin-bottom: 10px; transition: all 0.3s; } .stamp-item:hover { background: #e8f0fe; transform: translateY(-2px); } .pdf-viewer { flex: 1; background: #f8f9fa; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); } .viewer-header { padding: 15px; background: #4a6491; color: white; display: flex; justify-content: space-between; align-items: center; } .canvas-container { position: relative; overflow: auto; height: 600px; background: #525659; display: flex; justify-content: center; padding: 20px; } #pdfCanvas { box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); } .loading { text-align: center; padding: 40px; color: #777; font-size: 18px; } .page-controls { display: flex; justify-content: center; padding: 15px; background: #e8e8e8; gap: 15px; } .page-btn { padding: 8px 16px; background: #4a6491; color: white; border: none; border-radius: 4px; cursor: pointer; } .page-info { padding: 8px 16px; background: white; border-radius: 4px; min-width: 100px; text-align: center; } .stamp-indicator { position: absolute; width: 100px; height: 100px; border: 2px dashed #ff0000; border-radius: 50%; background: rgba(255, 0, 0, 0.1); display: flex; align-items: center; justify-content: center; font-weight: bold; color: #ff0000; pointer-events: none; display: none; } footer { text-align: center; padding: 20px; color: #777; font-size: 14px; border-top: 1px solid #eee; } @media (max-width: 768px) { .main-content { flex-direction: column; } .left-panel { flex: 1; width: 100%; } .canvas-container { height: 400px; } } /* 印章样式 */ .signature-stamp { color: #1a73e8; border: 2px solid #1a73e8; } .approval-stamp { color: #0b8043; border: 2px solid #0b8043; } .review-stamp { color: #e67c73; border: 2px solid #e67c73; } .date-stamp { color: #8e24aa; border: 2px solid #8e24aa; } .stamp-on-canvas { position: absolute; width: 100px; height: 100px; border: 2px solid; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; cursor: pointer; z-index: 10; } .zoom-controls { display: flex; justify-content: center; padding: 10px; background: #e8e8e8; gap: 10px; } .zoom-btn { padding: 6px 12px; background: #4a6491; color: white; border: none; border-radius: 4px; cursor: pointer; } </style> </head> <body> <div class="container"> <div class="main-content"> <div class="left-panel"> <div class="control-panel"> <h3>文档操作</h3> <button id="save-btn" class="button secondary-btn">保存签章</button> <button id="export-btn" class="button secondary-btn2">导出文档</button> <button id="reset-btn" class="button secondary-btn3">重置签章</button> </div> <div class="stamp-panel"> <h3>选择印章</h3> <div class="stamp-item signature-stamp" data-type="signature" data-text="张三"> <div>个人签名</div> </div> <div class="stamp-item approval-stamp" data-type="approval" data-text="已批准"> <div>批准印章</div> </div> <div class="stamp-item review-stamp" data-type="review" data-text="已审核"> <div>审核印章</div> </div> <div class="stamp-item date-stamp" data-type="date" data-text="2023-11-08"> <div>日期印章</div> </div> </div> </div> <div class="pdf-viewer"> <div class="viewer-header"> <h3>文档预览</h3> <div id="doc-info">加载中...</div> </div> <div class="canvas-container"> <div class="loading" id="loading-message">正在加载PDF文档...</div> <canvas id="pdfCanvas"></canvas> <div id="stamp-preview" class="stamp-indicator">印章预览</div> </div> <div class="page-controls"> <button id="prev-page" class="page-btn">上一页</button> <div class="page-info">页码: <span id="page-num">0</span> / <span id="page-count">0</span></div> <button id="next-page" class="page-btn">下一页</button> </div> <div class="zoom-controls"> <button id="zoom-out" class="zoom-btn">缩小</button> <button id="zoom-reset" class="zoom-btn">重置缩放</button> <button id="zoom-in" class="zoom-btn">放大</button> </div> </div> </div> </div> <script> // 设置PDF.js worker pdfjsLib.GlobalWorkerOptions.workerSrc = 'js/pdf/pdf.worker.min.js'; // 全局变量 let pdfDoc = null; let pageNum = 1; let pageRendering = false; let pageNumPending = null; let pdfScale = 1.2; let canvas = document.getElementById('pdfCanvas'); let ctx = canvas.getContext('2d'); let currentStamp = null; let stamps = []; // 预设PDF URL - 可以根据需要修改 const DEFAULT_PDF_URL = decodeURI('${pdfurl}'); // 元素引用 const loadingMessage = document.getElementById('loading-message'); const pageNumElement = document.getElementById('page-num'); const pageCountElement = document.getElementById('page-count'); const docInfoElement = document.getElementById('doc-info'); const prevPageButton = document.getElementById('prev-page'); const nextPageButton = document.getElementById('next-page'); const saveButton = document.getElementById('save-btn'); const exportButton = document.getElementById('export-btn'); const resetButton = document.getElementById('reset-btn'); const stampItems = document.querySelectorAll('.stamp-item'); const stampPreview = document.getElementById('stamp-preview'); const zoomInBtn = document.getElementById('zoom-in'); const zoomOutBtn = document.getElementById('zoom-out'); const zoomResetBtn = document.getElementById('zoom-reset'); // 从URL加载PDF function loadPdfFromUrl(url) { loadingMessage.style.display = 'block'; loadingMessage.textContent = '正在加载PDF文档...'; canvas.style.display = 'none'; pdfjsLib.getDocument(url).promise.then(function(pdf) { pdfDoc = pdf; pageCountElement.textContent = pdf.numPages; docInfoElement.textContent = `已加载 - ${pdf.numPages} 页`; loadingMessage.style.display = 'none'; canvas.style.display = 'block'; // 渲染第一页 pageNum = 1; renderPage(pageNum); }).catch(function(error) { loadingMessage.textContent = '加载PDF失败: ' + error.message; console.error('PDF加载错误:', error); // 尝试备用PDF URL setTimeout(() => { loadingMessage.textContent = '尝试备用PDF文档...'; loadPdfFromUrl(DEFAULT_PDF_URL); }, 2000); }); } // 渲染PDF页面 function renderPage(num) { pageRendering = true; pdfDoc.getPage(num).then(function(page) { const viewport = page.getViewport({ scale: pdfScale }); canvas.height = viewport.height; canvas.width = viewport.width; const renderContext = { canvasContext: ctx, viewport: viewport }; const renderTask = page.render(renderContext); renderTask.promise.then(function() { pageRendering = false; pageNumElement.textContent = num; // 如果存在待处理的页面渲染,立即渲染 if (pageNumPending !== null) { renderPage(pageNumPending); pageNumPending = null; } // 绘制当前页的印章 drawStampsForPage(num); }); }); } // 排队渲染页面 function queueRenderPage(num) { if (pageRendering) { pageNumPending = num; } else { renderPage(num); } } // 上一页 function onPrevPage() { if (pageNum <= 1) return; pageNum--; queueRenderPage(pageNum); } // 下一页 function onNextPage() { if (pageNum >= pdfDoc.numPages) return; pageNum++; queueRenderPage(pageNum); } // 绘制当前页的印章 function drawStampsForPage(pageNumber) { // 清除画布并重新渲染PDF页面 ctx.clearRect(0, 0, canvas.width, canvas.height); // 重新渲染PDF页面 pdfDoc.getPage(pageNumber).then(function(page) { const viewport = page.getViewport({ scale: pdfScale }); const renderContext = { canvasContext: ctx, viewport: viewport }; page.render(renderContext).promise.then(function() { // 绘制印章 stamps.filter(stamp => stamp.page === pageNumber).forEach(stamp => { ctx.font = 'bold 20px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; if (stamp.type === 'signature') { ctx.fillStyle = '#1a73e8'; ctx.strokeStyle = '#1a73e8'; } else if (stamp.type === 'approval') { ctx.fillStyle = '#0b8043'; ctx.strokeStyle = '#0b8043'; } else if (stamp.type === 'review') { ctx.fillStyle = '#e67c73'; ctx.strokeStyle = '#e67c73'; } else if (stamp.type === 'date') { ctx.fillStyle = '#8e24aa'; ctx.strokeStyle = '#8e24aa'; } // 绘制圆形印章 ctx.beginPath(); ctx.arc(stamp.x, stamp.y, 50, 0, Math.PI * 2); ctx.stroke(); // 绘制印章文字 ctx.fillText(stamp.text, stamp.x, stamp.y); // 如果是批准或审核印章,添加额外文字 if (stamp.type === 'approval' || stamp.type === 'review') { ctx.font = 'bold 14px Arial'; ctx.fillText('印章', stamp.x, stamp.y + 20); } }); }); }); } // 缩放功能 function zoomIn() { pdfScale += 0.2; queueRenderPage(pageNum); } function zoomOut() { if (pdfScale > 0.5) { pdfScale -= 0.2; queueRenderPage(pageNum); } } function zoomReset() { pdfScale = 1.2; queueRenderPage(pageNum); } // 初始化事件监听 function initEvents() { // 上一页/下一页按钮 prevPageButton.addEventListener('click', onPrevPage); nextPageButton.addEventListener('click', onNextPage); // 保存、导出、重置按钮 saveButton.addEventListener('click', function() { if (stamps.length === 0) { alert('请先添加印章'); return; } alert('保存成功!共添加了 ' + stamps.length + ' 个印章'); }); exportButton.addEventListener('click', function() { if (stamps.length === 0) { alert('请先添加印章'); return; } alert('导出功能已触发(实际应用中会生成带印章的PDF)'); }); resetButton.addEventListener('click', function() { if (stamps.length === 0) { alert('当前没有印章可重置'); return; } if (confirm('确定要重置所有印章吗?')) { stamps = []; renderPage(pageNum); alert('已重置所有印章'); } }); // 缩放按钮 zoomInBtn.addEventListener('click', zoomIn); zoomOutBtn.addEventListener('click', zoomOut); zoomResetBtn.addEventListener('click', zoomReset); // 印章拖放功能 stampItems.forEach(item => { item.addEventListener('dragstart', function(e) { currentStamp = { type: this.dataset.type, text: this.dataset.text || '' }; // 设置拖放预览图像 stampPreview.textContent = this.dataset.text; stampPreview.style.display = 'block'; // 根据印章类型设置样式 if (currentStamp.type === 'signature') { stampPreview.style.borderColor = '#1a73e8'; stampPreview.style.color = '#1a73e8'; stampPreview.style.background = 'rgba(26, 115, 232, 0.1)'; } else if (currentStamp.type === 'approval') { stampPreview.style.borderColor = '#0b8043'; stampPreview.style.color = '#0b8043'; stampPreview.style.background = 'rgba(11, 128, 67, 0.1)'; } else if (currentStamp.type === 'review') { stampPreview.style.borderColor = '#e67c73'; stampPreview.style.color = '#e67c73'; stampPreview.style.background = 'rgba(230, 124, 115, 0.1)'; } else if (currentStamp.type === 'date') { stampPreview.style.borderColor = '#8e24aa'; stampPreview.style.color = '#8e24aa'; stampPreview.style.background = 'rgba(142, 36, 170, 0.1)'; } e.dataTransfer.setDragImage(stampPreview, 50, 50); }); }); // 画布拖放事件 canvas.addEventListener('dragover', function(e) { e.preventDefault(); if (currentStamp) { const rect = canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; stampPreview.style.display = 'block'; stampPreview.style.left = (x - 50) + 'px'; stampPreview.style.top = (y - 50) + 'px'; } }); canvas.addEventListener('dragleave', function() { stampPreview.style.display = 'none'; }); canvas.addEventListener('drop', function(e) { e.preventDefault(); stampPreview.style.display = 'none'; if (currentStamp) { const rect = canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; const stamp = { type: currentStamp.type, text: currentStamp.text, x: x, y: y, page: pageNum }; stamps.push(stamp); drawStampsForPage(pageNum); } }); } // 初始化 window.onload = function() { initEvents(); // 页面加载后自动加载默认PDF loadPdfFromUrl(DEFAULT_PDF_URL); }; </script> </body> </html>这个代码加载到的pdf乱码,页码正常
最新发布