|
|
|
@ -348,35 +348,157 @@ |
|
|
|
} |
|
|
|
|
|
|
|
#signatureCanvas { |
|
|
|
border: 1px solid #ced4da; |
|
|
|
border-radius: 4px; |
|
|
|
background-color: #f8f9fa; |
|
|
|
border: 2px solid #ced4da; |
|
|
|
border-radius: 8px; |
|
|
|
background-color: white; |
|
|
|
cursor: crosshair; |
|
|
|
width: 100%; |
|
|
|
height: 200px; |
|
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); |
|
|
|
} |
|
|
|
|
|
|
|
.signature-actions { |
|
|
|
margin-top: 8px; |
|
|
|
text-align: right; |
|
|
|
margin-top: 12px; |
|
|
|
display: flex; |
|
|
|
justify-content: space-between; |
|
|
|
align-items: center; |
|
|
|
} |
|
|
|
|
|
|
|
.signature-actions button { |
|
|
|
padding: 6px 12px; |
|
|
|
padding: 8px 16px; |
|
|
|
background-color: #6c757d; |
|
|
|
color: white; |
|
|
|
border: none; |
|
|
|
border-radius: 4px; |
|
|
|
font-size: 14px; |
|
|
|
font-weight: 500; |
|
|
|
cursor: pointer; |
|
|
|
transition: background-color 0.3s ease; |
|
|
|
} |
|
|
|
|
|
|
|
.signature-actions button:hover { |
|
|
|
background-color: #5a6268; |
|
|
|
} |
|
|
|
|
|
|
|
.signature-actions .sign-hint { |
|
|
|
font-size: 14px; |
|
|
|
color: #666; |
|
|
|
} |
|
|
|
|
|
|
|
/* 签名显示样式 */ |
|
|
|
.signature-display img { |
|
|
|
max-width: 200px; |
|
|
|
max-height: 100px; |
|
|
|
max-width: 250px; |
|
|
|
max-height: 120px; |
|
|
|
border: 1px solid #e0e0e0; |
|
|
|
border-radius: 4px; |
|
|
|
padding: 8px; |
|
|
|
background-color: white; |
|
|
|
} |
|
|
|
|
|
|
|
/* 签名弹窗样式 */ |
|
|
|
.signature-modal { |
|
|
|
display: none; |
|
|
|
position: fixed; |
|
|
|
top: 0; |
|
|
|
left: 0; |
|
|
|
width: 100%; |
|
|
|
height: 100%; |
|
|
|
background-color: rgba(0, 0, 0, 0.8); |
|
|
|
z-index: 2000; |
|
|
|
align-items: center; |
|
|
|
justify-content: center; |
|
|
|
padding: 20px; |
|
|
|
} |
|
|
|
|
|
|
|
.signature-modal.show { |
|
|
|
display: flex; |
|
|
|
} |
|
|
|
|
|
|
|
.signature-modal-content { |
|
|
|
background-color: white; |
|
|
|
border-radius: 12px; |
|
|
|
padding: 24px; |
|
|
|
width: 90%; |
|
|
|
max-width: 500px; |
|
|
|
max-height: 80vh; |
|
|
|
overflow: hidden; |
|
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2); |
|
|
|
} |
|
|
|
|
|
|
|
.signature-modal-header { |
|
|
|
display: flex; |
|
|
|
justify-content: space-between; |
|
|
|
align-items: center; |
|
|
|
margin-bottom: 20px; |
|
|
|
} |
|
|
|
|
|
|
|
.signature-modal-header h3 { |
|
|
|
font-size: 18px; |
|
|
|
font-weight: 600; |
|
|
|
color: #333; |
|
|
|
margin: 0; |
|
|
|
} |
|
|
|
|
|
|
|
.signature-modal-close { |
|
|
|
background: none; |
|
|
|
border: none; |
|
|
|
font-size: 24px; |
|
|
|
color: #666; |
|
|
|
cursor: pointer; |
|
|
|
} |
|
|
|
|
|
|
|
#signatureModalCanvas { |
|
|
|
border: 2px solid #ced4da; |
|
|
|
border-radius: 8px; |
|
|
|
background-color: white; |
|
|
|
cursor: crosshair; |
|
|
|
width: 100%; |
|
|
|
height: 300px; |
|
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); |
|
|
|
} |
|
|
|
|
|
|
|
.signature-modal-actions { |
|
|
|
margin-top: 20px; |
|
|
|
display: flex; |
|
|
|
gap: 12px; |
|
|
|
} |
|
|
|
|
|
|
|
.signature-modal-actions button { |
|
|
|
flex: 1; |
|
|
|
padding: 12px; |
|
|
|
border: none; |
|
|
|
border-radius: 4px; |
|
|
|
font-size: 16px; |
|
|
|
font-weight: 500; |
|
|
|
cursor: pointer; |
|
|
|
transition: background-color 0.3s ease; |
|
|
|
} |
|
|
|
|
|
|
|
.signature-modal-actions .cancel-btn { |
|
|
|
background-color: #6c757d; |
|
|
|
color: white; |
|
|
|
} |
|
|
|
|
|
|
|
.signature-modal-actions .clear-btn { |
|
|
|
background-color: #ffc107; |
|
|
|
color: #212529; |
|
|
|
} |
|
|
|
|
|
|
|
.signature-modal-actions .confirm-btn { |
|
|
|
background-color: #28a745; |
|
|
|
color: white; |
|
|
|
} |
|
|
|
|
|
|
|
.signature-modal-actions .cancel-btn:hover { |
|
|
|
background-color: #5a6268; |
|
|
|
} |
|
|
|
|
|
|
|
.signature-modal-actions .clear-btn:hover { |
|
|
|
background-color: #e0a800; |
|
|
|
} |
|
|
|
|
|
|
|
.signature-modal-actions .confirm-btn:hover { |
|
|
|
background-color: #218838; |
|
|
|
} |
|
|
|
|
|
|
|
/* 响应式设计 */ |
|
|
|
@ -455,13 +577,30 @@ |
|
|
|
<div class="form-group"> |
|
|
|
<label>手写签名:</label> |
|
|
|
<div class="signature-container"> |
|
|
|
<canvas id="signatureCanvas" width="350" height="200"></canvas> |
|
|
|
<canvas id="signatureCanvas" width="350" height="200" onclick="openSignatureModal()"></canvas> |
|
|
|
<div class="signature-actions"> |
|
|
|
<span class="sign-hint">点击签名区域开始签名</span> |
|
|
|
<button type="button" onclick="clearSignature()">清除</button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<input type="hidden" id="signature" name="signature"> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 签名弹窗 --> |
|
|
|
<div class="signature-modal" id="signatureModal"> |
|
|
|
<div class="signature-modal-content"> |
|
|
|
<div class="signature-modal-header"> |
|
|
|
<h3>手写签名</h3> |
|
|
|
<button type="button" class="signature-modal-close" onclick="closeSignatureModal()">×</button> |
|
|
|
</div> |
|
|
|
<canvas id="signatureModalCanvas" width="400" height="300"></canvas> |
|
|
|
<div class="signature-modal-actions"> |
|
|
|
<button type="button" class="cancel-btn" onclick="closeSignatureModal()">取消</button> |
|
|
|
<button type="button" class="clear-btn" onclick="clearModalSignature()">清除</button> |
|
|
|
<button type="button" class="confirm-btn" onclick="confirmSignature()">确认</button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="form-actions" style="margin-top: 20px;"> |
|
|
|
<button type="submit" form="certificateForm" class="submit-btn">提交信息</button> |
|
|
|
</div> |
|
|
|
@ -481,6 +620,8 @@ |
|
|
|
<script> |
|
|
|
// 签名画布相关变量 |
|
|
|
let canvas, ctx, isDrawing = false; |
|
|
|
// 弹窗签名画布相关变量 |
|
|
|
let modalCanvas, modalCtx, modalIsDrawing = false; |
|
|
|
|
|
|
|
// 获取或创建会话标识 |
|
|
|
function getSessionId() { |
|
|
|
@ -616,6 +757,11 @@ |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
`; |
|
|
|
|
|
|
|
// 显示二维码 |
|
|
|
const sessionId = getSessionId(); |
|
|
|
const viewUrl = `http://8.137.125.67:3008/view.html?sessionId=${encodeURIComponent(sessionId)}`; |
|
|
|
displayQRCodeOnPage(viewUrl); |
|
|
|
} |
|
|
|
|
|
|
|
container.innerHTML = html; |
|
|
|
@ -625,28 +771,189 @@ |
|
|
|
|
|
|
|
// 初始化签名画布 |
|
|
|
function initSignatureCanvas() { |
|
|
|
// 初始化主画布 |
|
|
|
canvas = document.getElementById('signatureCanvas'); |
|
|
|
if (!canvas) return; |
|
|
|
if (canvas) { |
|
|
|
ctx = canvas.getContext('2d'); |
|
|
|
ctx.strokeStyle = '#000'; |
|
|
|
ctx.lineWidth = 2; |
|
|
|
ctx.lineCap = 'round'; |
|
|
|
ctx.lineJoin = 'round'; |
|
|
|
|
|
|
|
// 清除画布 |
|
|
|
clearSignature(); |
|
|
|
} |
|
|
|
|
|
|
|
ctx = canvas.getContext('2d'); |
|
|
|
ctx.strokeStyle = '#000'; |
|
|
|
ctx.lineWidth = 2; |
|
|
|
ctx.lineCap = 'round'; |
|
|
|
ctx.lineJoin = 'round'; |
|
|
|
// 初始化弹窗画布 |
|
|
|
initModalSignatureCanvas(); |
|
|
|
} |
|
|
|
|
|
|
|
// 初始化弹窗签名画布 |
|
|
|
function initModalSignatureCanvas() { |
|
|
|
modalCanvas = document.getElementById('signatureModalCanvas'); |
|
|
|
if (!modalCanvas) return; |
|
|
|
|
|
|
|
modalCtx = modalCanvas.getContext('2d'); |
|
|
|
modalCtx.strokeStyle = '#000'; |
|
|
|
modalCtx.lineWidth = 3; // 弹窗中使用更粗的线条 |
|
|
|
modalCtx.lineCap = 'round'; |
|
|
|
modalCtx.lineJoin = 'round'; |
|
|
|
|
|
|
|
// 清除画布 |
|
|
|
clearSignature(); |
|
|
|
clearModalSignature(); |
|
|
|
|
|
|
|
// 添加鼠标事件 |
|
|
|
canvas.addEventListener('mousedown', startDrawing); |
|
|
|
canvas.addEventListener('mousemove', draw); |
|
|
|
canvas.addEventListener('mouseup', stopDrawing); |
|
|
|
canvas.addEventListener('mouseout', stopDrawing); |
|
|
|
modalCanvas.addEventListener('mousedown', startModalDrawing); |
|
|
|
modalCanvas.addEventListener('mousemove', drawModal); |
|
|
|
modalCanvas.addEventListener('mouseup', stopModalDrawing); |
|
|
|
modalCanvas.addEventListener('mouseout', stopModalDrawing); |
|
|
|
|
|
|
|
// 添加触摸事件(支持移动设备) |
|
|
|
canvas.addEventListener('touchstart', startDrawingTouch); |
|
|
|
canvas.addEventListener('touchmove', drawTouch); |
|
|
|
canvas.addEventListener('touchend', stopDrawing); |
|
|
|
modalCanvas.addEventListener('touchstart', startModalDrawingTouch); |
|
|
|
modalCanvas.addEventListener('touchmove', drawModalTouch); |
|
|
|
modalCanvas.addEventListener('touchend', stopModalDrawing); |
|
|
|
} |
|
|
|
|
|
|
|
// 打开签名弹窗 |
|
|
|
function openSignatureModal() { |
|
|
|
const modal = document.getElementById('signatureModal'); |
|
|
|
if (modal) { |
|
|
|
modal.classList.add('show'); |
|
|
|
// 清除弹窗画布 |
|
|
|
clearModalSignature(); |
|
|
|
// 添加点击外部区域关闭弹窗的事件 |
|
|
|
setTimeout(() => { |
|
|
|
modal.addEventListener('click', handleModalClick); |
|
|
|
}, 100); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 处理弹窗点击事件 |
|
|
|
function handleModalClick(e) { |
|
|
|
const modal = document.getElementById('signatureModal'); |
|
|
|
const modalContent = document.querySelector('.signature-modal-content'); |
|
|
|
// 如果点击的是弹窗外部区域,关闭弹窗 |
|
|
|
if (e.target === modal) { |
|
|
|
closeSignatureModal(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 关闭签名弹窗 |
|
|
|
function closeSignatureModal() { |
|
|
|
const modal = document.getElementById('signatureModal'); |
|
|
|
if (modal) { |
|
|
|
// 移除点击事件监听器 |
|
|
|
modal.removeEventListener('click', handleModalClick); |
|
|
|
modal.classList.remove('show'); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 开始弹窗签名 |
|
|
|
function startModalDrawing(e) { |
|
|
|
modalIsDrawing = true; |
|
|
|
const rect = modalCanvas.getBoundingClientRect(); |
|
|
|
const x = e.clientX - rect.left; |
|
|
|
const y = e.clientY - rect.top; |
|
|
|
modalCtx.beginPath(); |
|
|
|
modalCtx.moveTo(x, y); |
|
|
|
} |
|
|
|
|
|
|
|
// 弹窗签名 |
|
|
|
function drawModal(e) { |
|
|
|
if (!modalIsDrawing) return; |
|
|
|
const rect = modalCanvas.getBoundingClientRect(); |
|
|
|
const x = e.clientX - rect.left; |
|
|
|
const y = e.clientY - rect.top; |
|
|
|
modalCtx.lineTo(x, y); |
|
|
|
modalCtx.stroke(); |
|
|
|
} |
|
|
|
|
|
|
|
// 停止弹窗签名 |
|
|
|
function stopModalDrawing() { |
|
|
|
modalIsDrawing = false; |
|
|
|
} |
|
|
|
|
|
|
|
// 触摸事件处理(弹窗) |
|
|
|
function startModalDrawingTouch(e) { |
|
|
|
e.preventDefault(); |
|
|
|
const touch = e.touches[0]; |
|
|
|
const mouseEvent = new MouseEvent('mousedown', { |
|
|
|
clientX: touch.clientX, |
|
|
|
clientY: touch.clientY |
|
|
|
}); |
|
|
|
modalCanvas.dispatchEvent(mouseEvent); |
|
|
|
} |
|
|
|
|
|
|
|
function drawModalTouch(e) { |
|
|
|
e.preventDefault(); |
|
|
|
const touch = e.touches[0]; |
|
|
|
const mouseEvent = new MouseEvent('mousemove', { |
|
|
|
clientX: touch.clientX, |
|
|
|
clientY: touch.clientY |
|
|
|
}); |
|
|
|
modalCanvas.dispatchEvent(mouseEvent); |
|
|
|
} |
|
|
|
|
|
|
|
// 清除弹窗签名 |
|
|
|
function clearModalSignature() { |
|
|
|
if (!modalCanvas || !modalCtx) return; |
|
|
|
modalCtx.clearRect(0, 0, modalCanvas.width, modalCanvas.height); |
|
|
|
// 填充背景色 |
|
|
|
modalCtx.fillStyle = '#f8f9fa'; |
|
|
|
modalCtx.fillRect(0, 0, modalCanvas.width, modalCanvas.height); |
|
|
|
} |
|
|
|
|
|
|
|
// 确认签名 |
|
|
|
function confirmSignature() { |
|
|
|
if (!modalCanvas) return; |
|
|
|
|
|
|
|
// 检查画布是否有内容 |
|
|
|
const imageData = modalCtx.getImageData(0, 0, modalCanvas.width, modalCanvas.height); |
|
|
|
const data = imageData.data; |
|
|
|
let hasContent = false; |
|
|
|
|
|
|
|
// 遍历像素数据,检查是否有非背景色的像素 |
|
|
|
for (let i = 0; i < data.length; i += 4) { |
|
|
|
const r = data[i]; |
|
|
|
const g = data[i + 1]; |
|
|
|
const b = data[i + 2]; |
|
|
|
const a = data[i + 3]; |
|
|
|
|
|
|
|
// 检查是否不是透明像素且不是背景色(#f8f9fa) |
|
|
|
if (a > 0 && !(r === 248 && g === 249 && b === 250)) { |
|
|
|
hasContent = true; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 只有当画布有内容时才保存签名 |
|
|
|
if (hasContent) { |
|
|
|
// 将弹窗画布内容复制到主画布 |
|
|
|
if (canvas && ctx) { |
|
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height); |
|
|
|
ctx.fillStyle = '#f8f9fa'; |
|
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height); |
|
|
|
|
|
|
|
// 调整比例,确保签名在主画布中合适显示 |
|
|
|
const scale = Math.min(canvas.width / modalCanvas.width, canvas.height / modalCanvas.height); |
|
|
|
const offsetX = (canvas.width - modalCanvas.width * scale) / 2; |
|
|
|
const offsetY = (canvas.height - modalCanvas.height * scale) / 2; |
|
|
|
|
|
|
|
ctx.save(); |
|
|
|
ctx.translate(offsetX, offsetY); |
|
|
|
ctx.scale(scale, scale); |
|
|
|
ctx.drawImage(modalCanvas, 0, 0); |
|
|
|
ctx.restore(); |
|
|
|
} |
|
|
|
|
|
|
|
// 保存签名到隐藏字段 |
|
|
|
saveSignature(); |
|
|
|
|
|
|
|
// 关闭弹窗 |
|
|
|
closeSignatureModal(); |
|
|
|
} else { |
|
|
|
alert('请先进行签名'); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
function startDrawing(e) { |
|
|
|
@ -781,8 +1088,8 @@ |
|
|
|
// 生成查看页面的URL,包含会话ID |
|
|
|
const viewUrl = `http://8.137.125.67:3008/view.html?sessionId=${encodeURIComponent(sessionId)}`; |
|
|
|
|
|
|
|
// 生成二维码 |
|
|
|
generateQRCode(viewUrl); |
|
|
|
// 生成二维码并在页面下方显示 |
|
|
|
displayQRCodeOnPage(viewUrl); |
|
|
|
} else { |
|
|
|
alert('没有找到可导出的信息'); |
|
|
|
} |
|
|
|
@ -793,6 +1100,81 @@ |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
// 在页面下方显示二维码 |
|
|
|
function displayQRCodeOnPage(url) { |
|
|
|
// 检查是否已有二维码容器,如有则先移除 |
|
|
|
const existingContainer = document.getElementById('qrCodeDisplay'); |
|
|
|
if (existingContainer) { |
|
|
|
existingContainer.remove(); |
|
|
|
} |
|
|
|
|
|
|
|
// 创建二维码容器 |
|
|
|
const qrContainer = document.createElement('div'); |
|
|
|
qrContainer.id = 'qrCodeDisplay'; |
|
|
|
qrContainer.style.cssText = ` |
|
|
|
margin: 20px 16px; |
|
|
|
padding: 16px; |
|
|
|
background-color: white; |
|
|
|
border-radius: 8px; |
|
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
|
|
|
text-align: center; |
|
|
|
`; |
|
|
|
|
|
|
|
// 创建二维码标题 |
|
|
|
const qrTitle = document.createElement('h3'); |
|
|
|
qrTitle.style.cssText = ` |
|
|
|
color: #28a745; |
|
|
|
margin-bottom: 15px; |
|
|
|
font-size: 18px; |
|
|
|
`; |
|
|
|
qrTitle.innerHTML = '📋 合格证二维码'; |
|
|
|
|
|
|
|
// 创建二维码图片 |
|
|
|
const qrImage = document.createElement('img'); |
|
|
|
qrImage.id = 'qrCodeImage'; |
|
|
|
qrImage.style.cssText = ` |
|
|
|
max-width: 250px; |
|
|
|
max-height: 250px; |
|
|
|
margin-bottom: 15px; |
|
|
|
background-color: white; |
|
|
|
padding: 10px; |
|
|
|
border-radius: 8px; |
|
|
|
border: 1px solid #e0e0e0; |
|
|
|
`; |
|
|
|
|
|
|
|
// 创建提示文字 |
|
|
|
const qrText = document.createElement('div'); |
|
|
|
qrText.style.cssText = ` |
|
|
|
color: #666; |
|
|
|
font-size: 14px; |
|
|
|
text-align: center; |
|
|
|
line-height: 1.5; |
|
|
|
`; |
|
|
|
qrText.innerHTML = '<p style="margin-bottom: 8px;">扫描二维码查看完整信息</p><p>长按保存二维码</p>'; |
|
|
|
|
|
|
|
// 使用Google Charts API生成二维码 |
|
|
|
const qrCodeUrl = `https://api.qrserver.com/v1/create-qr-code/?size=250x250&data=${encodeURIComponent(url)}`; |
|
|
|
qrImage.src = qrCodeUrl; |
|
|
|
|
|
|
|
// 组装容器 |
|
|
|
qrContainer.appendChild(qrTitle); |
|
|
|
qrContainer.appendChild(qrImage); |
|
|
|
qrContainer.appendChild(qrText); |
|
|
|
|
|
|
|
// 找到合适的位置插入二维码容器 |
|
|
|
// 在support元素之前插入 |
|
|
|
const supportElement = document.querySelector('.support'); |
|
|
|
if (supportElement) { |
|
|
|
supportElement.parentNode.insertBefore(qrContainer, supportElement); |
|
|
|
} else { |
|
|
|
// 如果没有support元素,就在container末尾插入 |
|
|
|
const container = document.querySelector('.container'); |
|
|
|
if (container) { |
|
|
|
container.appendChild(qrContainer); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 重置提交状态 |
|
|
|
function resetSubmissionStatus() { |
|
|
|
const sessionId = getSessionId(); |
|
|
|
@ -1014,6 +1396,11 @@ |
|
|
|
sessionStorage.setItem('certificateSubmitted_' + sessionId, 'true'); |
|
|
|
displayCertificate(data.certificate); |
|
|
|
alert('提交成功!合格证信息已保存到数据库。'); |
|
|
|
|
|
|
|
// 提交成功后自动生成二维码 |
|
|
|
setTimeout(() => { |
|
|
|
exportCertificate(); |
|
|
|
}, 1000); |
|
|
|
} else { |
|
|
|
// 无签名,标记为部分完成,保持可编辑状态 |
|
|
|
sessionStorage.setItem('certificatePartiallyCompleted_' + sessionId, 'true'); |
|
|
|
|