Files
vision-record/web/static/app.js

365 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 视觉记录系统前端逻辑
const API_BASE = '';
let currentImageId = null;
// ============== 初始化 ==============
document.addEventListener('DOMContentLoaded', () => {
refreshAll();
setInterval(refreshAll, 5000); // 每5秒刷新
});
function refreshAll() {
loadStatus();
loadImages();
loadEvents();
loadStats();
}
// ============== 状态相关 ==============
async function loadStatus() {
try {
const res = await fetch(`${API_BASE}/api/status`);
const data = await res.json();
updateStatusBar(data.scheduler);
updateStats(data.stats);
// 更新按钮状态
const startBtn = document.getElementById('start-btn');
const stopBtn = document.getElementById('stop-btn');
if (data.scheduler.running) {
startBtn.disabled = true;
stopBtn.disabled = false;
document.getElementById('scheduler-status').innerHTML =
`状态: <span style="color: #4CAF50;">✓ 运行中</span>`;
} else {
startBtn.disabled = false;
stopBtn.disabled = true;
document.getElementById('scheduler-status').innerHTML =
`状态: <span style="color: #f44336;">⏹ 已停止</span>`;
}
document.getElementById('capture-count').textContent =
`拍照次数: ${data.scheduler.capture_count}`;
// 更新输入框
document.getElementById('interval-input').value = data.scheduler.interval;
document.getElementById('auto-analyze').checked = data.scheduler.auto_analyze;
} catch (e) {
console.error('加载状态失败:', e);
}
}
function updateStatusBar(status) {
// 已在 loadStatus 中处理
}
function updateStats(stats) {
document.getElementById('total-images').textContent = stats.total_images;
document.getElementById('analyzed-images').textContent = stats.analyzed_images;
document.getElementById('total-events').textContent = stats.total_events;
// 更新事件类型筛选
const eventFilter = document.getElementById('event-filter');
const currentValue = eventFilter.value;
// 清空并重建选项
eventFilter.innerHTML = '<option value="all">全部类型</option>';
stats.event_types.forEach(item => {
const opt = document.createElement('option');
opt.value = item.type;
opt.textContent = `${item.type} (${item.count})`;
eventFilter.appendChild(opt);
});
eventFilter.value = currentValue;
}
// ============== 控制相关 ==============
async function startScheduler() {
const interval = parseInt(document.getElementById('interval-input').value);
const autoAnalyze = document.getElementById('auto-analyze').checked;
try {
const res = await fetch(`${API_BASE}/api/scheduler/start?interval=${interval}&auto_analyze=${autoAnalyze}`, {
method: 'POST'
});
const data = await res.json();
if (data.success) {
alert('启动成功!');
refreshAll();
} else {
alert('启动失败: ' + data.error);
}
} catch (e) {
alert('请求失败: ' + e.message);
}
}
async function stopScheduler() {
try {
const res = await fetch(`${API_BASE}/api/scheduler/stop`, {
method: 'POST'
});
const data = await res.json();
if (data.success) {
alert('已停止!');
refreshAll();
}
} catch (e) {
alert('请求失败: ' + e.message);
}
}
async function setInterval() {
const interval = parseInt(document.getElementById('interval-input').value);
if (interval < 10) {
alert('间隔不能小于10秒');
return;
}
try {
const res = await fetch(`${API_BASE}/api/scheduler/interval?interval=${interval}`, {
method: 'POST'
});
const data = await res.json();
if (data.success) {
alert('间隔已更新为 ' + interval + ' 秒');
refreshAll();
}
} catch (e) {
alert('请求失败: ' + e.message);
}
}
async function captureNow() {
try {
const res = await fetch(`${API_BASE}/api/capture`, {
method: 'POST'
});
const data = await res.json();
if (data.success) {
alert('拍照成功图片ID: ' + data.image_id);
refreshAll();
} else {
alert('拍照失败: ' + data.error);
}
} catch (e) {
alert('请求失败: ' + e.message);
}
}
async function analyzeUnanalyzed() {
try {
const res = await fetch(`${API_BASE}/api/analyze/unanalyzed`, {
method: 'POST'
});
const data = await res.json();
alert(`分析了 ${data.results.length} 张图片`);
refreshAll();
} catch (e) {
alert('请求失败: ' + e.message);
}
}
// ============== 图片相关 ==============
async function loadImages() {
const filter = document.getElementById('image-filter').value;
const analyzedOnly = filter === 'analyzed';
try {
const res = await fetch(`${API_BASE}/api/images?limit=50&analyzed_only=${analyzedOnly}`);
const data = await res.json();
renderImages(data.images);
} catch (e) {
console.error('加载图片失败:', e);
}
}
function renderImages(images) {
const grid = document.getElementById('images-grid');
grid.innerHTML = '';
if (images.length === 0) {
grid.innerHTML = '<p style="color: #888;">暂无图片</p>';
return;
}
images.forEach(img => {
const card = document.createElement('div');
card.className = 'image-card';
card.onclick = () => openImageModal(img.id);
const imgEl = document.createElement('img');
imgEl.src = `/images/${img.path.split('/').pop()}`;
imgEl.alt = `图片 ${img.id}`;
const info = document.createElement('div');
info.className = 'image-card-info';
const time = new Date(img.timestamp).toLocaleString('zh-CN');
info.innerHTML = `
<div class="time">${time}</div>
<div class="${img.analyzed ? 'analyzed' : 'unanalyzed'}">
${img.analyzed ? '✓ 已分析' : '○ 未分析'}
</div>
`;
card.appendChild(imgEl);
card.appendChild(info);
grid.appendChild(card);
});
}
async function openImageModal(imageId) {
currentImageId = imageId;
try {
const res = await fetch(`${API_BASE}/api/images/${imageId}`);
const data = await res.json();
const modal = document.getElementById('image-modal');
const modalImg = document.getElementById('modal-image');
const modalTitle = document.getElementById('modal-title');
const modalTime = document.getElementById('modal-time');
const modalEvents = document.getElementById('modal-events');
modalImg.src = `/images/${data.path.split('/').pop()}`;
modalTitle.textContent = `图片 #${data.id}`;
modalTime.textContent = `时间: ${new Date(data.timestamp).toLocaleString('zh-CN')}`;
modalEvents.innerHTML = '';
if (data.events && data.events.length > 0) {
data.events.forEach(event => {
const eventDiv = document.createElement('div');
eventDiv.className = 'modal-event';
eventDiv.innerHTML = `
<strong>${event.event_type}</strong>
<span class="event-confidence">${event.confidence}</span>
<div>${event.description}</div>
`;
modalEvents.appendChild(eventDiv);
});
} else {
modalEvents.innerHTML = '<p style="color: #888;">暂无事件记录</p>';
}
modal.classList.add('active');
} catch (e) {
console.error('加载图片详情失败:', e);
}
}
function closeModal() {
document.getElementById('image-modal').classList.remove('active');
currentImageId = null;
}
async function analyzeCurrentImage() {
if (!currentImageId) return;
try {
const res = await fetch(`${API_BASE}/api/analyze/${currentImageId}`, {
method: 'POST'
});
const data = await res.json();
if (data.success) {
alert('分析完成!');
openImageModal(currentImageId); // 刷新详情
refreshAll();
} else {
alert('分析失败: ' + data.error);
}
} catch (e) {
alert('请求失败: ' + e.message);
}
}
async function deleteCurrentImage() {
if (!currentImageId) return;
if (!confirm('确定删除此图片?')) return;
try {
const res = await fetch(`${API_BASE}/api/images/${currentImageId}`, {
method: 'DELETE'
});
const data = await res.json();
if (data.success) {
closeModal();
refreshAll();
}
} catch (e) {
alert('请求失败: ' + e.message);
}
}
// ============== 事件相关 ==============
async function loadEvents() {
const filter = document.getElementById('event-filter').value;
const eventType = filter === 'all' ? '' : filter;
try {
let url = `${API_BASE}/api/events?limit=50`;
if (eventType) {
url += `&event_type=${encodeURIComponent(eventType)}`;
}
const res = await fetch(url);
const data = await res.json();
renderEvents(data.events);
} catch (e) {
console.error('加载事件失败:', e);
}
}
function renderEvents(events) {
const list = document.getElementById('events-list');
list.innerHTML = '';
if (events.length === 0) {
list.innerHTML = '<p style="color: #888;">暂无事件</p>';
return;
}
events.forEach(event => {
const card = document.createElement('div');
card.className = `event-card type-${event.event_type}`;
const time = new Date(event.timestamp).toLocaleString('zh-CN');
card.innerHTML = `
<div class="event-header">
<span class="event-type">${event.event_type}</span>
<span class="event-time">${time}</span>
</div>
<div class="event-desc">${event.description}</div>
<div class="event-confidence">${event.confidence}</div>
`;
card.onclick = () => openImageModal(event.image_id);
list.appendChild(card);
});
}
// 筛选变化监听
document.getElementById('image-filter').addEventListener('change', loadImages);
document.getElementById('event-filter').addEventListener('change', loadEvents);