feat: 增加网页comment字段

This commit is contained in:
2025-10-26 10:24:07 +08:00
parent 62de9573ac
commit c8dfec6cf4
2 changed files with 127 additions and 26 deletions

View File

@@ -44,6 +44,7 @@ def init_db():
right_filename TEXT NOT NULL, right_filename TEXT NOT NULL,
timestamp REAL NOT NULL, timestamp REAL NOT NULL,
metadata TEXT, metadata TEXT,
comment TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) )
''') ''')
@@ -89,7 +90,7 @@ def get_images_api():
conn = sqlite3.connect(DATABASE_PATH) conn = sqlite3.connect(DATABASE_PATH)
cursor = conn.cursor() cursor = conn.cursor()
# 按时间倒序排列 # 按时间倒序排列
cursor.execute("SELECT id, left_filename, right_filename, timestamp, metadata, created_at FROM images ORDER BY timestamp DESC") cursor.execute("SELECT id, left_filename, right_filename, timestamp, metadata, comment, created_at FROM images ORDER BY timestamp DESC")
rows = cursor.fetchall() rows = cursor.fetchall()
conn.close() conn.close()
@@ -101,7 +102,8 @@ def get_images_api():
"right_filename": row[2], "right_filename": row[2],
"timestamp": row[3], "timestamp": row[3],
"metadata": row[4], "metadata": row[4],
"created_at": row[5] "comment": row[5] or "", # 如果没有comment则显示空字符串
"created_at": row[6]
}) })
return jsonify(images) return jsonify(images)
@@ -194,6 +196,7 @@ def upload_images():
left_file = request.files.get('left_image') left_file = request.files.get('left_image')
right_file = request.files.get('right_image') right_file = request.files.get('right_image')
metadata_str = request.form.get('metadata') # 如果需要处理元数据 metadata_str = request.form.get('metadata') # 如果需要处理元数据
comment = request.form.get('comment', '') # 获取comment字段
if not left_file or not right_file: if not left_file or not right_file:
logger.warning("Received request without required image files.") logger.warning("Received request without required image files.")
@@ -244,9 +247,9 @@ def upload_images():
conn = sqlite3.connect(DATABASE_PATH) conn = sqlite3.connect(DATABASE_PATH)
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute(''' cursor.execute('''
INSERT INTO images (left_filename, right_filename, timestamp, metadata) INSERT INTO images (left_filename, right_filename, timestamp, metadata, comment)
VALUES (?, ?, ?, ?) VALUES (?, ?, ?, ?, ?)
''', (left_filename, right_filename, float(timestamp_str), json.dumps(metadata))) ''', (left_filename, right_filename, float(timestamp_str), json.dumps(metadata), comment))
conn.commit() conn.commit()
image_id = cursor.lastrowid # 获取新插入记录的 ID image_id = cursor.lastrowid # 获取新插入记录的 ID
conn.close() conn.close()
@@ -277,7 +280,25 @@ def upload_images():
logger.error(f"Error processing upload: {e}") logger.error(f"Error processing upload: {e}")
return jsonify({"error": str(e)}), 500 return jsonify({"error": str(e)}), 500
# --- 可选:添加一个简单的状态检查路由 --- @app.route('/api/images/comment', methods=['PUT'])
def update_image_comment():
"""API: 更新图片的comment"""
data = request.json
image_id = data.get('id')
comment = data.get('comment', '')
if not image_id:
return jsonify({"error": "Image ID is required"}), 400
conn = sqlite3.connect(DATABASE_PATH)
cursor = conn.cursor()
# 更新comment字段
cursor.execute("UPDATE images SET comment = ? WHERE id = ?", (comment, image_id))
conn.commit()
conn.close()
return jsonify({"message": f"Comment for image {image_id} updated successfully"})
@app.route('/status') @app.route('/status')
def status(): def status():
with frame_lock: with frame_lock:

View File

@@ -1,11 +1,19 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>Saved Images List</title> <title>Saved Images List</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.2/socket.io.min.js "></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.2/socket.io.min.js "></script>
<style> <style>
body { font-family: Arial, sans-serif; margin: 20px; } body {
.controls { margin-bottom: 20px; } font-family: Arial, sans-serif;
margin: 20px;
}
.controls {
margin-bottom: 20px;
}
button { button {
padding: 8px 16px; padding: 8px 16px;
margin-right: 10px; margin-right: 10px;
@@ -14,23 +22,59 @@
border: none; border: none;
cursor: pointer; cursor: pointer;
} }
button:hover { background-color: #0056b3; }
button:disabled { background-color: #cccccc; cursor: not-allowed; } button:hover {
table { width: 100%; border-collapse: collapse; margin-top: 20px; } background-color: #0056b3;
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; } }
th { background-color: #f2f2f2; }
.image-preview { width: 100px; height: auto; } button:disabled {
input[type="checkbox"] { transform: scale(1.2); } background-color: #cccccc;
#status { padding: 10px; margin: 10px 0; background-color: #f0f0f0; } cursor: not-allowed;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
th,
td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
}
.image-preview {
width: 100px;
height: auto;
}
input[type="checkbox"] {
transform: scale(1.2);
}
#status {
padding: 10px;
margin: 10px 0;
background-color: #f0f0f0;
}
a { a {
color: #007bff; color: #007bff;
text-decoration: none; text-decoration: none;
} }
a:hover { a:hover {
text-decoration: underline; text-decoration: underline;
} }
</style> </style>
</head> </head>
<body> <body>
<h1>Saved Images</h1> <h1>Saved Images</h1>
<!-- 添加导航链接 --> <!-- 添加导航链接 -->
@@ -41,18 +85,20 @@
<button id="exportBtn" disabled>Export Selected</button> <button id="exportBtn" disabled>Export Selected</button>
<button id="deleteSelectedBtn" disabled>Delete Selected</button> <button id="deleteSelectedBtn" disabled>Delete Selected</button>
</div> </div>
<!-- 修改表格头部 -->
<table id="imagesTable"> <table id="imagesTable">
<thead> <thead>
<tr> <tr>
<th> <th>
<input type="checkbox" id="selectAllCheckbox"> <input type="checkbox" id="selectAllCheckbox">
<label for="selectAllCheckbox" style="display: inline-block; margin-left: 5px; cursor: pointer;">Select All</label> <label for="selectAllCheckbox"
style="display: inline-block; margin-left: 5px; cursor: pointer;">Select All</label>
</th> </th>
<th>ID</th> <th>ID</th>
<th>Left Image</th> <th>Left Image</th>
<th>Right Image</th> <th>Right Image</th>
<th>Timestamp</th> <th>Timestamp</th>
<th>Created At</th> <th>Comment</th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
@@ -82,6 +128,7 @@
} }
// 渲染表格 // 渲染表格
// 修改渲染表格的函数
function renderTable(images) { function renderTable(images) {
const tbody = document.getElementById('imagesTableBody'); const tbody = document.getElementById('imagesTableBody');
tbody.innerHTML = ''; // 清空现有内容 tbody.innerHTML = ''; // 清空现有内容
@@ -97,7 +144,7 @@
leftImg.src = `/static/received/left/${image.left_filename}`; leftImg.src = `/static/received/left/${image.left_filename}`;
leftImg.alt = "Left Image"; leftImg.alt = "Left Image";
leftImg.className = 'image-preview'; leftImg.className = 'image-preview';
leftImg.onerror = function() { this.src = 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="75" viewBox="0 0 100 75"><rect width="100" height="75" fill="%23eee"/><text x="50" y="40" font-family="Arial" font-size="12" fill="%23999" text-anchor="middle">No Image</text></svg>'; }; leftImg.onerror = function () { this.src = 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="75" viewBox="0 0 100 75"><rect width="100" height="75" fill="%23eee"/><text x="50" y="40" font-family="Arial" font-size="12" fill="%23999" text-anchor="middle">No Image</text></svg>'; };
leftCell.appendChild(leftImg); leftCell.appendChild(leftImg);
const rightCell = row.insertCell(3); const rightCell = row.insertCell(3);
@@ -106,15 +153,47 @@
rightImg.src = `/static/received/right/${image.right_filename}`; rightImg.src = `/static/received/right/${image.right_filename}`;
rightImg.alt = "Right Image"; rightImg.alt = "Right Image";
rightImg.className = 'image-preview'; rightImg.className = 'image-preview';
rightImg.onerror = function() { this.src = 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="75" viewBox="0 0 100 75"><rect width="100" height="75" fill="%23eee"/><text x="50" y="40" font-family="Arial" font-size="12" fill="%23999" text-anchor="middle">No Image</text></svg>'; }; rightImg.onerror = function () { this.src = 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="75" viewBox="0 0 100 75"><rect width="100" height="75" fill="%23eee"/><text x="50" y="40" font-family="Arial" font-size="12" fill="%23999" text-anchor="middle">No Image</text></svg>'; };
rightCell.appendChild(rightImg); rightCell.appendChild(rightImg);
row.insertCell(4).textContent = new Date(image.timestamp * 1000).toISOString(); row.insertCell(4).textContent = new Date(image.timestamp * 1000).toISOString();
row.insertCell(5).textContent = image.created_at;
// 添加可编辑的comment单元格
const commentCell = row.insertCell(5);
const commentInput = document.createElement('input');
commentInput.type = 'text';
commentInput.value = image.comment || '';
commentInput.dataset.id = image.id;
commentInput.className = 'comment-input';
commentInput.style.width = '100%';
commentInput.addEventListener('change', function () {
updateComment(image.id, this.value);
});
commentCell.appendChild(commentInput);
row.insertCell(6).innerHTML = `<button onclick="deleteImage(${image.id})">Delete</button>`; row.insertCell(6).innerHTML = `<button onclick="deleteImage(${image.id})">Delete</button>`;
}); });
} }
// 添加更新comment的函数
async function updateComment(id, comment) {
try {
const response = await fetch('/api/images/comment', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: id, comment: comment })
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({ error: response.statusText }));
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
}
document.getElementById('status').textContent = `Comment for image ID ${id} updated.`;
} catch (error) {
console.error('Error updating comment:', error);
document.getElementById('status').textContent = 'Error updating comment: ' + error.message;
}
}
// 删除图片 // 删除图片
async function deleteImage(id) { async function deleteImage(id) {
if (!confirm(`Are you sure you want to delete image ID ${id}?`)) return; if (!confirm(`Are you sure you want to delete image ID ${id}?`)) return;
@@ -165,7 +244,7 @@
document.getElementById('refreshBtn').addEventListener('click', loadImages); document.getElementById('refreshBtn').addEventListener('click', loadImages);
// --- 修改:使用 fetch API 发起导出请求 --- // --- 修改:使用 fetch API 发起导出请求 ---
document.getElementById('exportBtn').addEventListener('click', async function() { document.getElementById('exportBtn').addEventListener('click', async function () {
const selectedIds = getSelectedIds(); const selectedIds = getSelectedIds();
if (selectedIds.length === 0) { if (selectedIds.length === 0) {
alert('Please select at least one image to export.'); alert('Please select at least one image to export.');
@@ -205,7 +284,7 @@
} }
}); });
document.getElementById('deleteSelectedBtn').addEventListener('click', async function() { document.getElementById('deleteSelectedBtn').addEventListener('click', async function () {
const selectedIds = getSelectedIds(); const selectedIds = getSelectedIds();
if (selectedIds.length === 0) { if (selectedIds.length === 0) {
alert('Please select at least one image to delete.'); alert('Please select at least one image to delete.');
@@ -236,7 +315,7 @@
}); });
// 全选复选框事件监听 // 全选复选框事件监听
document.getElementById('selectAllCheckbox').addEventListener('change', function() { document.getElementById('selectAllCheckbox').addEventListener('change', function () {
const isChecked = this.checked; const isChecked = this.checked;
const checkboxes = document.querySelectorAll('.selectCheckbox'); const checkboxes = document.querySelectorAll('.selectCheckbox');
checkboxes.forEach(checkbox => { checkboxes.forEach(checkbox => {
@@ -246,7 +325,7 @@
}); });
// 监听单个复选框变化以更新全选框和按钮状态 // 监听单个复选框变化以更新全选框和按钮状态
document.getElementById('imagesTable').addEventListener('change', function(e) { document.getElementById('imagesTable').addEventListener('change', function (e) {
if (e.target.classList.contains('selectCheckbox')) { if (e.target.classList.contains('selectCheckbox')) {
updateSelectAllCheckbox(); updateSelectAllCheckbox();
} }
@@ -257,4 +336,5 @@
</script> </script>
</body> </body>
</html> </html>