// API 基础地址 const API_BASE = ''; // 离线队列(用于后台同步) const DB_NAME = 'din-offline'; const DB_VERSION = 1; let db = null; // 初始化 IndexedDB function initDB() { return new Promise((resolve, reject) => { const request = indexedDB.open(DB_NAME, DB_VERSION); request.onerror = () => reject(request.error); request.onsuccess = () => { db = request.result; resolve(db); }; request.onupgradeneeded = (e) => { const db = e.target.result; if (!db.objectStoreNames.contains('pending')) { db.createObjectStore('pending', { keyPath: 'id', autoIncrement: true }); } }; }); } // 添加到离线队列 async function addToQueue(data) { if (!db) await initDB(); return new Promise((resolve, reject) => { const tx = db.transaction('pending', 'readwrite'); const store = tx.objectStore('pending'); const request = store.add({ data, timestamp: Date.now() }); request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); } // 注册后台同步 async function registerSync() { if ('serviceWorker' in navigator && 'sync' in ServiceWorkerRegistration.prototype) { const reg = await navigator.serviceWorker.ready; await reg.sync.register('sync-din'); } } // DOM 元素 const dinBtn = document.getElementById('din-btn'); const inputSection = document.getElementById('input-section'); const dinContent = document.getElementById('din-content'); const saveBtn = document.getElementById('save-btn'); const skipBtn = document.getElementById('skip-btn'); const toast = document.getElementById('toast'); // 状态 let currentRecordId = null; // 初始化 async function init() { await initDB(); await loadStats(); await loadRecent(); await loadAchievements(); // 检查网络状态 window.addEventListener('online', () => { showToast('已连接到网络', true); syncPendingData(); }); window.addEventListener('offline', () => { showToast('进入离线模式', false); }); } // 同步离线数据 async function syncPendingData() { if (!db || !navigator.onLine) return; return new Promise((resolve, reject) => { const tx = db.transaction('pending', 'readonly'); const store = tx.objectStore('pending'); const request = store.getAll(); request.onsuccess = async () => { const pending = request.result; if (pending.length === 0) { resolve(); return; } for (const item of pending) { try { await fetch(`${API_BASE}/api/din`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(item.data) }); // 删除已同步的 const delTx = db.transaction('pending', 'readwrite'); const delStore = delTx.objectStore('pending'); await delStore.delete(item.id); } catch (err) { console.error('Sync failed for item:', item.id); } } await loadStats(); await loadRecent(); resolve(); }; request.onerror = () => reject(request.error); }); } // 加载统计数据 async function loadStats() { try { const res = await fetch(`${API_BASE}/api/stats`); const stats = await res.json(); document.getElementById('stat-today').textContent = stats.today; document.getElementById('stat-week').textContent = stats.week; document.getElementById('stat-month').textContent = stats.month; document.getElementById('stat-total').textContent = stats.total; // 增长率 updateGrowth('growth-today', stats.day_growth); updateGrowth('growth-week', stats.week_growth); updateGrowth('growth-month', stats.month_growth); } catch (err) { console.error('加载统计失败:', err); } } function updateGrowth(id, value) { const el = document.getElementById(id); if (value > 0) { el.textContent = `+${value}%`; el.classList.remove('negative'); } else if (value < 0) { el.textContent = `${value}%`; el.classList.add('negative'); } else { el.textContent = '-'; } } // 加载最近记录 async function loadRecent() { try { const res = await fetch(`${API_BASE}/api/din?limit=10`); const records = await res.json(); const list = document.getElementById('recent-list'); if (records.length === 0) { list.innerHTML = '
还没有记录,点击上方按钮开始!
'; return; } list.innerHTML = records.map(r => `
${formatTime(r.created_at)}
${r.content || '(无描述)'}
`).join(''); } catch (err) { console.error('加载记录失败:', err); } } // 加载成就 async function loadAchievements() { try { const res = await fetch(`${API_BASE}/api/achievements`); const data = await res.json(); document.getElementById('achievement-progress').textContent = `(${data.unlocked_count}/${data.total_count})`; const list = document.getElementById('achievement-list'); list.innerHTML = data.achievements.map(a => `
${a.icon}
${a.name}
${a.desc}
`).join(''); } catch (err) { console.error('加载成就失败:', err); } } // 点击大按钮 dinBtn.addEventListener('click', async () => { dinBtn.classList.add('recording'); dinBtn.disabled = true; const content = ''; // 先创建空记录 try { // 检查网络状态 if (!navigator.onLine) { // 离线模式:存入队列 await addToQueue({ content }); await registerSync(); showToast('已保存,将在联网时同步'); // 显示输入区域(离线编辑) currentRecordId = 'offline_' + Date.now(); inputSection.classList.remove('hidden'); dinContent.value = ''; dinContent.focus(); return; } const res = await fetch(`${API_BASE}/api/din`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content }) }); const data = await res.json(); currentRecordId = data.id; // 显示输入区域 inputSection.classList.remove('hidden'); dinContent.value = ''; dinContent.focus(); // 更新统计 await loadStats(); } catch (err) { console.error('创建记录失败:', err); showToast('记录失败,请重试', false); } finally { dinBtn.classList.remove('recording'); dinBtn.disabled = false; } }); // 保存内容 saveBtn.addEventListener('click', async () => { if (!currentRecordId) return; const content = dinContent.value.trim(); try { await fetch(`${API_BASE}/api/din/${currentRecordId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content }) }); inputSection.classList.add('hidden'); currentRecordId = null; await loadRecent(); await loadAchievements(); showToast('记录成功!'); } catch (err) { console.error('保存失败:', err); showToast('保存失败', false); } }); // 跳过 skipBtn.addEventListener('click', async () => { inputSection.classList.add('hidden'); currentRecordId = null; await loadRecent(); await loadAchievements(); showToast('记录成功!'); }); // 回车保存 dinContent.addEventListener('keypress', (e) => { if (e.key === 'Enter') { saveBtn.click(); } }); // 点击外部自动跳过(不丢失记录) document.addEventListener('click', (e) => { // 输入框隐藏时不处理 if (inputSection.classList.contains('hidden')) return; // 点击输入框内部不处理 if (inputSection.contains(e.target)) return; // 点击大按钮时不处理(刚点击过) if (dinBtn.contains(e.target)) return; // 点击其他地方:自动跳过 skipBtn.click(); }); // 编辑记录 async function editRecord(id, currentContent) { const newContent = prompt('修改内容:', currentContent); if (newContent === null) return; // 取消 try { await fetch(`${API_BASE}/api/din/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content: newContent.trim() }) }); await loadRecent(); showToast('修改成功!'); } catch (err) { console.error('修改失败:', err); showToast('修改失败', false); } } // 删除记录 async function deleteRecord(id) { if (!confirm('确定删除这条记录?')) return; try { await fetch(`${API_BASE}/api/din/${id}`, { method: 'DELETE' }); await loadRecent(); await loadStats(); await loadAchievements(); showToast('删除成功'); } catch (err) { console.error('删除失败:', err); showToast('删除失败', false); } } // 显示提示 function showToast(text, success = true) { toast.querySelector('.toast-text').textContent = text; toast.querySelector('.toast-icon').textContent = success ? '✓' : '✗'; toast.style.background = success ? 'rgba(74,222,128,0.9)' : 'rgba(248,113,113,0.9)'; toast.classList.remove('hidden'); setTimeout(() => { toast.classList.add('hidden'); }, 2000); } // 格式化时间 function formatTime(isoString) { const date = new Date(isoString); const now = new Date(); const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); const recordDate = new Date(date.getFullYear(), date.getMonth(), date.getDate()); const hours = date.getHours().toString().padStart(2, '0'); const minutes = date.getMinutes().toString().padStart(2, '0'); if (recordDate.getTime() === today.getTime()) { return `${hours}:${minutes}`; } const yesterday = new Date(today); yesterday.setDate(yesterday.getDate() - 1); if (recordDate.getTime() === yesterday.getTime()) { return `昨天 ${hours}:${minutes}`; } return `${date.getMonth() + 1}/${date.getDate()}`; } // HTML 转义 function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // 启动 init();