const API = ''; // ===== STATE ===== let currentId = null; let lastClick = 0; let pressTimer = null; let isLongPress = false; const DEBOUNCE = 200; // 200ms 防抖 const LONG_PRESS = 600; // 600ms 长按 // ===== DOM ===== const dinBtn = document.getElementById('din-btn'); const floatInput = document.getElementById('float-input'); const floatField = document.getElementById('float-field'); const overlay = document.getElementById('overlay'); const toast = document.getElementById('toast'); const confirmOverlay = document.getElementById('confirm-overlay'); const confirmOk = document.getElementById('confirm-ok'); const confirmCancel = document.getElementById('confirm-cancel'); // ===== CUSTOM CONFIRM DIALOG ===== function showConfirm(message, title = '确认删除', icon = '🗑️') { return new Promise((resolve) => { document.querySelector('.confirm-title').textContent = title; document.querySelector('.confirm-message').textContent = message; document.querySelector('.confirm-icon').textContent = icon; confirmOverlay.classList.add('active'); const onOk = () => { confirmOverlay.classList.remove('active'); cleanup(); resolve(true); }; const onCancel = () => { confirmOverlay.classList.remove('active'); cleanup(); resolve(false); }; const cleanup = () => { confirmOk.removeEventListener('click', onOk); confirmCancel.removeEventListener('click', onCancel); confirmOverlay.removeEventListener('click', onOverlayClick); }; const onOverlayClick = (e) => { if (e.target === confirmOverlay) { onCancel(); } }; confirmOk.addEventListener('click', onOk); confirmCancel.addEventListener('click', onCancel); confirmOverlay.addEventListener('click', onOverlayClick); }); } // ===== BUTTON INTERACTION ===== dinBtn.addEventListener('touchstart', onPress, { passive: false }); dinBtn.addEventListener('mousedown', onPress); dinBtn.addEventListener('touchend', onRelease, { passive: false }); dinBtn.addEventListener('mouseup', onRelease); dinBtn.addEventListener('mouseleave', onCancel); function onPress(e) { e.preventDefault(); if (Date.now() - lastClick < DEBOUNCE) return; isLongPress = false; dinBtn.classList.add('recording'); pressTimer = setTimeout(() => { isLongPress = true; dinBtn.classList.remove('recording'); record(true); // 长按 = 记录 + 输入 }, LONG_PRESS); } function onRelease(e) { e.preventDefault(); clearTimeout(pressTimer); dinBtn.classList.remove('recording'); if (!isLongPress) { lastClick = Date.now(); record(false); // 短按 = 仅记录 } } function onCancel() { clearTimeout(pressTimer); dinBtn.classList.remove('recording'); } // ===== RECORD ===== async function record(openInput = false) { showToast('已记录!'); try { const res = await fetch(`${API}/api/din`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content: '' }) }); const data = await res.json(); currentId = data.id; // 并行更新 UI Promise.all([ refreshStats(), addChip(data), checkAchievements() ]); if (openInput) { openFloatInput(data.created_at, false); // false = 新建模式 } } catch (err) { console.error('记录失败:', err); showToast('记录失败', false); } } // ===== FLOAT INPUT ===== function openFloatInput(timestamp, isEdit = false) { document.getElementById('float-time').textContent = formatTime(timestamp); document.querySelector('.float-title').textContent = isEdit ? '编辑记录' : '添加备注'; floatField.value = ''; floatInput.classList.add('active'); overlay.classList.add('active'); setTimeout(() => floatField.focus(), 50); } function closeFloatInput() { floatInput.classList.remove('active'); overlay.classList.remove('active'); floatField.blur(); } // 保存 document.getElementById('btn-save').addEventListener('click', async () => { const content = floatField.value.trim(); if (!content || !currentId) { closeFloatInput(); return; } await fetch(`${API}/api/din/${currentId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content }) }); updateChip(currentId, content); closeFloatInput(); showToast('已保存'); }); // 删除 document.getElementById('btn-delete').addEventListener('click', async () => { if (!currentId) { console.log('No currentId to delete'); closeFloatInput(); return; } const confirmed = await showConfirm('确定要删除这条记录吗?此操作不可恢复。'); if (!confirmed) return; try { const res = await fetch(`${API}/api/din/${currentId}`, { method: 'DELETE' }); if (!res.ok) { throw new Error('Delete failed'); } // 移除 chip - 使用字符串比较 const chip = document.querySelector(`.recent-chip[data-id="${currentId}"]`); console.log('Looking for chip with id:', currentId, 'found:', chip); if (chip) chip.remove(); await refreshStats(); closeFloatInput(); showToast('已删除'); currentId = null; // 清除当前ID } catch (err) { console.error('Delete error:', err); showToast('删除失败', false); } }); // 跳过 document.getElementById('btn-skip').addEventListener('click', closeFloatInput); // 点击遮罩关闭 overlay.addEventListener('click', closeFloatInput); // 回车保存 floatField.addEventListener('keypress', e => { if (e.key === 'Enter') document.getElementById('btn-save').click(); }); // 快捷标签 document.querySelectorAll('.tag').forEach(tag => { tag.addEventListener('click', () => { floatField.value = tag.dataset.text; document.getElementById('btn-save').click(); }); }); // ===== CHIPS ===== function addChip(record) { const container = document.getElementById('recent-chips'); const empty = container.querySelector('.empty'); if (empty) empty.remove(); const chip = document.createElement('div'); chip.className = 'recent-chip'; chip.dataset.id = record.id; chip.innerHTML = ` ${formatTime(record.created_at)} 新记录 `; chip.addEventListener('click', () => { currentId = String(record.id); // 确保是字符串 console.log('Chip clicked, currentId set to:', currentId); openFloatInput(record.created_at, true); // true = 编辑模式 }); container.insertBefore(chip, container.firstChild); // 保持最多 10 个 while (container.children.length > 10) { container.removeChild(container.lastChild); } } function updateChip(id, content) { const chip = document.querySelector(`.recent-chip[data-id="${id}"]`); if (chip) { chip.querySelector('span:last-child').textContent = content; } } // ===== STATS ===== async function refreshStats() { try { const res = await fetch(`${API}/api/stats`); const s = await res.json(); document.getElementById('stat-today').textContent = s.today; document.getElementById('stat-week').textContent = s.week; document.getElementById('stat-month').textContent = s.month; document.getElementById('stat-total').textContent = s.total; // 增长率 updateGrowth('growth-today', s.day_growth); updateGrowth('growth-week', s.week_growth); updateGrowth('growth-month', s.month_growth); } catch (err) { console.error(err); } } function updateGrowth(id, value) { const el = document.getElementById(id); if (!el) return; if (value > 0) { el.textContent = `+${value}%`; el.classList.remove('negative'); } else if (value < 0) { el.textContent = `${value}%`; el.classList.add('negative'); } else { el.textContent = '-'; } } // ===== ACHIEVEMENTS ===== async function checkAchievements() { try { const res = await fetch(`${API}/api/achievements`); const data = await res.json(); document.getElementById('ach-progress').textContent = `${data.unlocked_count}/${data.total_count}`; const list = document.getElementById('achievements-list'); list.innerHTML = data.achievements.map(a => `