mirror of
https://github.com/yiGmMk/wxocr.git
synced 2025-12-24 13:38:02 +08:00
441 lines
14 KiB
HTML
441 lines
14 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>微信OCR文字识别工具</title>
|
|
<style>
|
|
:root {
|
|
--primary-color: #07c160;
|
|
--secondary-color: #576b95;
|
|
}
|
|
|
|
body {
|
|
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
background: #f5f5f5;
|
|
color: #333;
|
|
}
|
|
|
|
.container {
|
|
background: white;
|
|
border-radius: 12px;
|
|
padding: 30px;
|
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
h1 {
|
|
color: var(--primary-color);
|
|
text-align: center;
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
.upload-section {
|
|
border: 2px dashed #ddd;
|
|
border-radius: 8px;
|
|
padding: 30px;
|
|
text-align: center;
|
|
margin: 20px 0;
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.upload-section:hover {
|
|
border-color: var(--primary-color);
|
|
background: #f8fff9;
|
|
}
|
|
|
|
.preview-image {
|
|
max-width: 100%;
|
|
max-height: 400px;
|
|
margin: 20px 0;
|
|
border-radius: 8px;
|
|
display: none;
|
|
}
|
|
|
|
.input-group {
|
|
margin: 20px 0;
|
|
}
|
|
|
|
input[type="file"],
|
|
input[type="text"] {
|
|
width: 100%;
|
|
padding: 12px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 6px;
|
|
margin: 10px 0;
|
|
}
|
|
|
|
button {
|
|
background: var(--primary-color);
|
|
color: white;
|
|
border: none;
|
|
padding: 12px 30px;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
font-size: 16px;
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
button:hover {
|
|
opacity: 0.9;
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.result-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin-top: 20px;
|
|
display: none;
|
|
}
|
|
|
|
.result-table th,
|
|
.result-table td {
|
|
padding: 12px;
|
|
border: 1px solid #eee;
|
|
text-align: left;
|
|
}
|
|
|
|
.result-table th {
|
|
background: var(--secondary-color);
|
|
color: white;
|
|
}
|
|
|
|
.loading {
|
|
display: none;
|
|
text-align: center;
|
|
color: var(--primary-color);
|
|
margin: 20px 0;
|
|
}
|
|
|
|
.error {
|
|
color: #ff4d4f;
|
|
margin: 10px 0;
|
|
display: none;
|
|
}
|
|
|
|
.image-container {
|
|
position: relative;
|
|
margin: 20px 0;
|
|
display: inline-block;
|
|
}
|
|
|
|
.text-box {
|
|
position: absolute;
|
|
border: 2px solid var(--primary-color);
|
|
background-color: rgba(7, 193, 96, 0.1);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.text-tooltip {
|
|
position: absolute;
|
|
background: white;
|
|
border: 1px solid #ddd;
|
|
padding: 8px;
|
|
border-radius: 4px;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
z-index: 100;
|
|
display: none;
|
|
max-width: 300px;
|
|
word-break: break-word;
|
|
}
|
|
|
|
.confidence {
|
|
color: var(--primary-color);
|
|
font-weight: bold;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div class="container">
|
|
<h1>微信OCR文字识别工具</h1>
|
|
|
|
<!-- 上传区域 -->
|
|
<div class="upload-section">
|
|
<div class="input-group">
|
|
<input type="file" id="fileInput" accept="image/*">
|
|
<p>或拖拽图片到此区域</p>
|
|
<input type="text" id="urlInput" placeholder="输入图片URL地址">
|
|
</div>
|
|
<button onclick="processImage()">开始识别</button>
|
|
</div>
|
|
|
|
<!-- 图片预览 -->
|
|
<div class="image-container" id="imageContainer">
|
|
<img id="preview" class="preview-image">
|
|
<!-- 文本框将在这里动态添加 -->
|
|
</div>
|
|
|
|
<!-- 加载状态 -->
|
|
<div class="loading" id="loading">识别中,请稍候...</div>
|
|
|
|
<!-- 错误提示 -->
|
|
<div class="error" id="error"></div>
|
|
|
|
<!-- 结果显示 -->
|
|
<table class="result-table" id="resultTable">
|
|
<thead>
|
|
<tr>
|
|
<th>文本内容</th>
|
|
<th>置信度</th>
|
|
<th>位置信息 (左, 上, 右, 下)</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="resultBody"></tbody>
|
|
</table>
|
|
|
|
<!-- 使用说明 -->
|
|
<h2>API接口说明</h2>
|
|
<h3>请求方式</h3>
|
|
<pre>POST /ocr</pre>
|
|
|
|
<h3>请求示例</h3>
|
|
<pre>
|
|
{
|
|
"image": "BASE64_ENCODED_IMAGE_DATA"
|
|
}</pre>
|
|
|
|
<h3>返回示例</h3>
|
|
<pre id="responseSample"></pre>
|
|
</div>
|
|
|
|
<script>
|
|
// 默认的API地址
|
|
const API_ENDPOINT = window.location.origin + '/ocr';
|
|
|
|
// 初始化拖放功能
|
|
const uploadSection = document.querySelector('.upload-section');
|
|
uploadSection.addEventListener('dragover', (e) => {
|
|
e.preventDefault();
|
|
uploadSection.style.backgroundColor = '#f0fff0';
|
|
});
|
|
|
|
uploadSection.addEventListener('drop', (e) => {
|
|
e.preventDefault();
|
|
uploadSection.style.backgroundColor = '';
|
|
const file = e.dataTransfer.files[0];
|
|
handleFile(file);
|
|
});
|
|
|
|
// 处理文件选择
|
|
document.getElementById('fileInput').addEventListener('change', function (e) {
|
|
handleFile(e.target.files[0]);
|
|
});
|
|
|
|
// 处理文件上传
|
|
async function handleFile(file) {
|
|
if (!file) return;
|
|
if (!file.type.startsWith('image/')) {
|
|
showError('请上传图片文件');
|
|
return;
|
|
}
|
|
|
|
// 显示预览图片
|
|
const reader = new FileReader();
|
|
reader.onload = (e) => {
|
|
document.getElementById('preview').src = e.target.result;
|
|
document.getElementById('preview').style.display = 'block';
|
|
|
|
// 清除之前的文本框
|
|
const imageContainer = document.getElementById('imageContainer');
|
|
const existingBoxes = imageContainer.querySelectorAll('.text-box, .text-tooltip');
|
|
existingBoxes.forEach(box => box.remove());
|
|
};
|
|
reader.readAsDataURL(file);
|
|
}
|
|
|
|
// 开始处理图像
|
|
async function processImage() {
|
|
const file = document.getElementById('fileInput').files[0];
|
|
const url = document.getElementById('urlInput').value;
|
|
let base64Data = '';
|
|
|
|
try {
|
|
showLoading();
|
|
clearError();
|
|
|
|
if (file) {
|
|
base64Data = await fileToBase64(file);
|
|
} else if (url) {
|
|
base64Data = await urlToBase64(url);
|
|
} else {
|
|
showError('请选择图片或输入图片URL');
|
|
return;
|
|
}
|
|
|
|
// 发送请求
|
|
const response = await fetch(API_ENDPOINT, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ image: base64Data })
|
|
});
|
|
|
|
const data = await response.json();
|
|
handleResponse(data);
|
|
} catch (error) {
|
|
showError(`请求失败:${error.message}`);
|
|
} finally {
|
|
hideLoading();
|
|
}
|
|
}
|
|
|
|
// 处理响应数据
|
|
function handleResponse(data) {
|
|
// 处理新的响应结构
|
|
const resultData = data.result || data;
|
|
|
|
if (resultData.errcode !== 0) {
|
|
showError(`识别失败,错误码:${resultData.errcode}`);
|
|
return;
|
|
}
|
|
|
|
// 显示结果表格
|
|
const tbody = document.getElementById('resultBody');
|
|
tbody.innerHTML = '';
|
|
resultData.ocr_response.forEach(item => {
|
|
const row = document.createElement('tr');
|
|
row.innerHTML = `
|
|
<td>${item.text}</td>
|
|
<td>${(item.rate * 100).toFixed(2)}%</td>
|
|
<td>(${item.left.toFixed(1)}, ${item.top.toFixed(1)},
|
|
${item.right.toFixed(1)}, ${item.bottom.toFixed(1)})</td>
|
|
`;
|
|
tbody.appendChild(row);
|
|
});
|
|
document.getElementById('resultTable').style.display = 'table';
|
|
|
|
// 在图片上绘制识别区域
|
|
drawTextBoxes(resultData.ocr_response, resultData.width, resultData.height);
|
|
}
|
|
|
|
// 在图片上绘制文本框
|
|
function drawTextBoxes(ocrResults, originalWidth, originalHeight) {
|
|
const imageContainer = document.getElementById('imageContainer');
|
|
const preview = document.getElementById('preview');
|
|
|
|
// 清除之前的文本框
|
|
const existingBoxes = imageContainer.querySelectorAll('.text-box, .text-tooltip');
|
|
existingBoxes.forEach(box => box.remove());
|
|
|
|
// 获取图片的实际显示尺寸和位置
|
|
const imgRect = preview.getBoundingClientRect();
|
|
const containerRect = imageContainer.getBoundingClientRect();
|
|
|
|
// 计算图片相对于容器的偏移
|
|
const offsetX = imgRect.left - containerRect.left;
|
|
const offsetY = imgRect.top - containerRect.top;
|
|
|
|
// 计算缩放比例
|
|
const scaleX = imgRect.width / originalWidth;
|
|
const scaleY = imgRect.height / originalHeight;
|
|
|
|
// 为每个识别结果创建文本框
|
|
ocrResults.forEach((item, index) => {
|
|
// 创建文本框
|
|
const textBox = document.createElement('div');
|
|
textBox.className = 'text-box';
|
|
|
|
// 精确定位文本框,考虑图片在容器中的偏移
|
|
const left = item.left * scaleX + offsetX;
|
|
const top = item.top * scaleY + offsetY;
|
|
const width = (item.right - item.left) * scaleX;
|
|
const height = (item.bottom - item.top) * scaleY;
|
|
|
|
// 设置文本框位置和大小
|
|
textBox.style.left = `${left}px`;
|
|
textBox.style.top = `${top}px`;
|
|
textBox.style.width = `${width}px`;
|
|
textBox.style.height = `${height}px`;
|
|
textBox.dataset.index = index;
|
|
|
|
// 创建提示框
|
|
const tooltip = document.createElement('div');
|
|
tooltip.className = 'text-tooltip';
|
|
tooltip.innerHTML = `
|
|
<div>${item.text}</div>
|
|
<div class="confidence">置信度: ${(item.rate * 100).toFixed(2)}%</div>
|
|
`;
|
|
|
|
// 添加鼠标事件
|
|
textBox.addEventListener('mouseenter', function (e) {
|
|
tooltip.style.left = `${e.pageX - imageContainer.offsetLeft + 10}px`;
|
|
tooltip.style.top = `${e.pageY - imageContainer.offsetTop + 10}px`;
|
|
tooltip.style.display = 'block';
|
|
});
|
|
|
|
textBox.addEventListener('mousemove', function (e) {
|
|
tooltip.style.left = `${e.pageX - imageContainer.offsetLeft + 10}px`;
|
|
tooltip.style.top = `${e.pageY - imageContainer.offsetTop + 10}px`;
|
|
});
|
|
|
|
textBox.addEventListener('mouseleave', function () {
|
|
tooltip.style.display = 'none';
|
|
});
|
|
|
|
imageContainer.appendChild(textBox);
|
|
imageContainer.appendChild(tooltip);
|
|
});
|
|
}
|
|
|
|
// 工具函数
|
|
function fileToBase64(file) {
|
|
return new Promise((resolve, reject) => {
|
|
const reader = new FileReader();
|
|
reader.onload = () => resolve(reader.result);
|
|
reader.onerror = error => reject(error);
|
|
reader.readAsDataURL(file);
|
|
});
|
|
}
|
|
|
|
async function urlToBase64(url) {
|
|
const response = await fetch(url);
|
|
const blob = await response.blob();
|
|
return fileToBase64(blob);
|
|
}
|
|
|
|
function showLoading() {
|
|
document.getElementById('loading').style.display = 'block';
|
|
}
|
|
|
|
function hideLoading() {
|
|
document.getElementById('loading').style.display = 'none';
|
|
}
|
|
|
|
function showError(message) {
|
|
const errorDiv = document.getElementById('error');
|
|
errorDiv.textContent = message;
|
|
errorDiv.style.display = 'block';
|
|
}
|
|
|
|
function clearError() {
|
|
document.getElementById('error').style.display = 'none';
|
|
}
|
|
|
|
// 初始化示例响应显示
|
|
document.getElementById('responseSample').textContent = JSON.stringify({
|
|
"result": {
|
|
"errcode": 0,
|
|
"height": 258,
|
|
"imgpath": "temp/0cfbda36-a05d-4cba-9a72-cec6833d305d.png",
|
|
"ocr_response": [
|
|
{
|
|
"bottom": 41.64999771118164,
|
|
"left": 33.6875,
|
|
"rate": 0.9951504468917847,
|
|
"right": 164.76248168945312,
|
|
"text": "API接口说明",
|
|
"top": 18.98750114440918
|
|
}
|
|
],
|
|
"width": 392
|
|
}
|
|
}, null, 2);
|
|
</script>
|
|
</body>
|
|
|
|
</html> |