365 lines
11 KiB
JavaScript
365 lines
11 KiB
JavaScript
// 视觉记录系统前端逻辑
|
||
|
||
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); |