feat: 增加监视页面
This commit is contained in:
387
templates/view.html
Normal file
387
templates/view.html
Normal file
@@ -0,0 +1,387 @@
|
||||
<!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>
|
||||
Reference in New Issue
Block a user