Files
m20_core_web/templates/view.html
2025-10-28 11:03:34 +08:00

387 lines
16 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Capture View</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
}
.controls {
margin-bottom: 20px;
}
button {
padding: 10px 15px;
margin-right: 10px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
th,
td {
border: 1px solid #ddd;
padding: 8px;
text-align: center; /* 居中对齐 */
}
th {
background-color: #f2f2f2;
}
.image-preview {
width: 150px; /* 调整预览图宽度 */
height: auto;
display: block;
margin: 0 auto; /* 图片居中 */
}
input[type="checkbox"] {
transform: scale(1.2);
}
#status {
padding: 10px;
margin: 10px 0;
background-color: #f0f0f0;
}
a {
color: #007bff;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
/* 样式化自动刷新开关 */
.toggle-switch {
position: relative;
display: inline-block;
width: 50px;
height: 24px;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .4s;
border-radius: 24px;
}
.slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 3px;
bottom: 3px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
input:checked + .slider {
background-color: #2196F3;
}
input:checked + .slider:before {
transform: translateX(26px);
}
</style>
</head>
<body>
<h1>Capture View</h1>
<div id="status">Loading images...</div>
<div class="controls">
<button id="refreshBtn">Refresh List</button>
<button id="exportBtn" disabled>Export Selected</button>
<button id="exportAllBtn">Export All</button> <!-- 新增导出全部按钮 -->
<!-- 自动刷新开关 -->
<label class="toggle-switch">
<input type="checkbox" id="autoRefreshToggle">
<span class="slider"></span>
</label>
<span>Auto Refresh (3s)</span>
</div>
<table id="imagesTable">
<thead>
<tr>
<th>
<input type="checkbox" id="selectAllCheckbox">
<label for="selectAllCheckbox" style="display: inline-block; margin-left: 5px; cursor: pointer;">Select All</label>
</th>
<th>Left Marked Image</th>
<th>Right Marked Image</th>
</tr>
</thead>
<tbody id="imagesTableBody">
<!-- 动态加载行 -->
</tbody>
</table>
<script>
let allImages = []; // 存储所有图片数据
let autoRefreshInterval = null; // 存储自动刷新定时器 ID
let isAutoRefreshEnabled = false; // 标记自动刷新是否启用
// 加载图片列表
async function loadImages() {
document.getElementById('status').textContent = 'Loading images...';
try {
const response = await fetch('/api/images');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
allImages = await response.json();
renderTable(allImages);
document.getElementById('status').textContent = `Loaded ${allImages.length} image pairs.`;
updateSelectAllCheckbox();
} catch (error) {
console.error('Error loading images:', error);
document.getElementById('status').textContent = 'Error loading images: ' + error.message;
}
}
// 渲染表格
function renderTable(images) {
const tbody = document.getElementById('imagesTableBody');
tbody.innerHTML = ''; // 清空现有内容
images.forEach(image => {
const row = tbody.insertRow();
// Checkbox 列
const checkboxCell = row.insertCell(0);
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'selectCheckbox';
checkbox.dataset.id = image.id;
checkboxCell.appendChild(checkbox);
// Left Marked Image 列
const leftMarkedCell = row.insertCell(1);
if (image.left_marked_filename) {
const img = document.createElement('img');
img.src = `/static/received/left_marked/${image.left_marked_filename}`;
img.alt = `Left Marked Image ID ${image.id}`;
img.className = 'image-preview';
img.onerror = function () {
this.src = 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="150" height="112" viewBox="0 0 150 112"><rect width="150" height="112" fill="%23eee"/><text x="75" y="56" font-family="Arial" font-size="14" fill="%23999" text-anchor="middle" dominant-baseline="middle">No Image</text></svg>';
};
leftMarkedCell.appendChild(img);
} else {
// 如果没有标记图片,显示原图或占位符
// 优先显示原图
if (image.left_filename) {
const img = document.createElement('img');
img.src = `/static/received/left/${image.left_filename}`;
img.alt = `Left Original Image ID ${image.id}`;
img.className = 'image-preview';
img.onerror = function () {
this.src = 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="150" height="112" viewBox="0 0 150 112"><rect width="150" height="112" fill="%23eee"/><text x="75" y="56" font-family="Arial" font-size="14" fill="%23999" text-anchor="middle" dominant-baseline="middle">No Image</text></svg>';
};
leftMarkedCell.appendChild(img);
} else {
leftMarkedCell.textContent = "N/A";
}
}
// Right Marked Image 列
const rightMarkedCell = row.insertCell(2);
if (image.right_marked_filename) {
const img = document.createElement('img');
img.src = `/static/received/right_marked/${image.right_marked_filename}`;
img.alt = `Right Marked Image ID ${image.id}`;
img.className = 'image-preview';
img.onerror = function () {
this.src = 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="150" height="112" viewBox="0 0 150 112"><rect width="150" height="112" fill="%23eee"/><text x="75" y="56" font-family="Arial" font-size="14" fill="%23999" text-anchor="middle" dominant-baseline="middle">No Image</text></svg>';
};
rightMarkedCell.appendChild(img);
} else {
// 如果没有标记图片,显示原图或占位符
if (image.right_filename) {
const img = document.createElement('img');
img.src = `/static/received/right/${image.right_filename}`;
img.alt = `Right Original Image ID ${image.id}`;
img.className = 'image-preview';
img.onerror = function () {
this.src = 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="150" height="112" viewBox="0 0 150 112"><rect width="150" height="112" fill="%23eee"/><text x="75" y="56" font-family="Arial" font-size="14" fill="%23999" text-anchor="middle" dominant-baseline="middle">No Image</text></svg>';
};
rightMarkedCell.appendChild(img);
} else {
rightMarkedCell.textContent = "N/A";
}
}
});
}
// 更新“全选”复选框状态
function updateSelectAllCheckbox() {
const checkboxes = document.querySelectorAll('.selectCheckbox');
const allSelected = checkboxes.length > 0 && checkboxes.length === document.querySelectorAll('.selectCheckbox:checked').length;
document.getElementById('selectAllCheckbox').checked = allSelected;
updateExportDeleteButtons(); // 导出按钮依赖选中状态
}
// 更新导出按钮状态
function updateExportDeleteButtons() {
const selectedCount = getSelectedIds().length;
document.getElementById('exportBtn').disabled = selectedCount === 0;
// 导出全部按钮始终启用
}
// 获取选中图片的 ID 列表
function getSelectedIds() {
const checkboxes = document.querySelectorAll('.selectCheckbox:checked');
return Array.from(checkboxes).map(cb => parseInt(cb.dataset.id));
}
// 切换自动刷新
function toggleAutoRefresh() {
const toggle = document.getElementById('autoRefreshToggle');
isAutoRefreshEnabled = toggle.checked;
if (isAutoRefreshEnabled) {
// 启用自动刷新
if (!autoRefreshInterval) { // 防止重复设置
autoRefreshInterval = setInterval(loadImages, 3000);
console.log("Auto-refresh started.");
}
} else {
// 禁用自动刷新
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
autoRefreshInterval = null;
console.log("Auto-refresh stopped.");
}
}
}
// 刷新按钮事件
document.getElementById('refreshBtn').addEventListener('click', () => {
loadImages();
});
// 全选复选框事件
document.getElementById('selectAllCheckbox').addEventListener('change', function () {
const isChecked = this.checked;
const checkboxes = document.querySelectorAll('.selectCheckbox');
checkboxes.forEach(checkbox => {
checkbox.checked = isChecked;
});
updateExportDeleteButtons();
});
// 表格行内复选框事件(用于更新全选按钮和导出按钮)
document.getElementById('imagesTable').addEventListener('change', function (e) {
if (e.target.classList.contains('selectCheckbox')) {
updateSelectAllCheckbox();
}
});
// 自动刷新开关事件
document.getElementById('autoRefreshToggle').addEventListener('change', toggleAutoRefresh);
// 导出按钮事件 (导出选中)
document.getElementById('exportBtn').addEventListener('click', async function () {
const selectedIds = getSelectedIds();
if (selectedIds.length === 0) {
alert('Please select at least one image pair to export.');
return;
}
try {
document.getElementById('status').textContent = 'Preparing export...';
const response = await fetch('/api/images/export', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ids: selectedIds })
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({ error: response.statusText }));
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
}
const exportTime = new Date().toLocaleString();
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = 'Selected' + exportTime + '.zip';
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
document.getElementById('status').textContent = `Exported ${selectedIds.length} selected image pairs.`;
} catch (error) {
console.error('Error exporting selected images:', error);
document.getElementById('status').textContent = 'Error exporting selected images: ' + error.message;
}
});
// 导出全部按钮事件
document.getElementById('exportAllBtn').addEventListener('click', async function () {
if (allImages.length === 0) {
alert('No images available to export.');
return;
}
// 获取所有图片的 ID
const allIds = allImages.map(image => image.id);
try {
document.getElementById('status').textContent = 'Preparing export...';
const response = await fetch('/api/images/export', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ids: allIds }) // 发送所有 ID
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({ error: response.statusText }));
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
}
const exportTime = new Date().toISOString().replace(/[:.]/g, '-');
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = 'All_' + exportTime + '.zip';
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
document.getElementById('status').textContent = `Exported all ${allIds.length} image pairs.`;
} catch (error) {
console.error('Error exporting all images:', error);
document.getElementById('status').textContent = 'Error exporting all images: ' + error.message;
}
});
// 页面加载时获取图片
loadImages();
</script>
</body>
</html>