din/static/app.js
ching 3eaecd18da 重构前端:极简极速交互设计
- 全屏点击即可记录,响应更快
- 长按直接进入输入模式
- 快速标签:工作/学习/运动/休息/创意/其他
- 底部常驻统计,无需切换页面
- 右侧面板显示最近记录
- 成就解锁动画提示
- 优化移动端触摸体验
2026-02-21 06:11:11 +00:00

310 lines
8.7 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 currentRecordId = null;
let lastRecordTime = 0;
let longPressTimer = null;
let isLongPress = false;
const LONG_PRESS_DURATION = 500; // 长按触发时间
// DOM 元素
const triggerArea = document.getElementById('trigger-area');
const dinBtn = document.getElementById('din-btn');
const inputOverlay = document.getElementById('input-overlay');
const quickInput = document.getElementById('quick-input');
const toast = document.getElementById('toast');
const achievementToast = document.getElementById('achievement-toast');
// ========== 核心交互:超快记录 ==========
// 点击/触摸开始
dinBtn.addEventListener('touchstart', handleStart, { passive: false });
dinBtn.addEventListener('mousedown', handleStart);
// 点击/触摸结束
dinBtn.addEventListener('touchend', handleEnd, { passive: false });
dinBtn.addEventListener('mouseup', handleEnd);
dinBtn.addEventListener('mouseleave', cancelLongPress);
// 防止双击缩放
dinBtn.addEventListener('touchmove', (e) => e.preventDefault(), { passive: false });
function handleStart(e) {
e.preventDefault();
isLongPress = false;
// 视觉反馈
dinBtn.classList.add('recording');
// 启动长按计时器
longPressTimer = setTimeout(() => {
isLongPress = true;
dinBtn.classList.remove('recording');
// 长按直接进入输入模式
createRecord(true);
}, LONG_PRESS_DURATION);
}
function handleEnd(e) {
e.preventDefault();
clearTimeout(longPressTimer);
dinBtn.classList.remove('recording');
// 如果不是长按,直接记录
if (!isLongPress) {
createRecord(false);
}
}
function cancelLongPress() {
clearTimeout(longPressTimer);
dinBtn.classList.remove('recording');
}
// ========== 记录逻辑 ==========
async function createRecord(showInput = false) {
// 防抖1秒内不能重复点击
const now = Date.now();
if (now - lastRecordTime < 1000) return;
lastRecordTime = now;
try {
// 立即显示反馈(不等待网络)
showToast('已记录!');
// 后台发送请求
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;
// 更新统计和列表
updateStats();
prependToList(data);
// 检查成就
checkNewAchievements();
// 如果需要输入,显示输入面板
if (showInput) {
showInputPanel(data.created_at);
}
} catch (err) {
console.error('记录失败:', err);
showToast('记录失败', false);
}
}
// ========== 输入面板 ==========
function showInputPanel(timestamp) {
document.getElementById('input-timestamp').textContent = formatTimeOnly(timestamp);
inputOverlay.classList.add('active');
quickInput.value = '';
setTimeout(() => quickInput.focus(), 100);
}
function hideInputPanel() {
inputOverlay.classList.remove('active');
quickInput.blur();
}
// 保存按钮
document.getElementById('btn-save').addEventListener('click', async () => {
if (!currentRecordId) return;
const content = quickInput.value.trim();
if (!content) {
hideInputPanel();
return;
}
try {
await fetch(`${API_BASE}/api/din/${currentRecordId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content })
});
// 更新列表中的内容
updateListItem(currentRecordId, content);
hideInputPanel();
showToast('已保存');
} catch (err) {
showToast('保存失败', false);
}
});
// 跳过按钮
document.getElementById('btn-skip').addEventListener('click', () => {
hideInputPanel();
});
// 点击遮罩关闭
inputOverlay.addEventListener('click', (e) => {
if (e.target === inputOverlay) {
hideInputPanel();
}
});
// 回车保存
quickInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
document.getElementById('btn-save').click();
}
});
// 快速标签
document.querySelectorAll('.tag').forEach(tag => {
tag.addEventListener('click', () => {
const text = tag.dataset.text;
quickInput.value = text;
document.getElementById('btn-save').click();
});
});
// ========== 数据更新 ==========
async function updateStats() {
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-total').textContent = stats.total;
document.getElementById('recent-count').textContent = stats.total;
} catch (err) {
console.error('更新统计失败:', err);
}
}
function prependToList(record) {
const list = document.getElementById('recent-list');
const emptyMsg = list.querySelector('.recent-item .empty');
if (emptyMsg) {
list.innerHTML = '';
}
const item = document.createElement('div');
item.className = 'recent-item';
item.dataset.id = record.id;
item.innerHTML = `
<span class="recent-time">${formatTimeOnly(record.created_at)}</span>
<span class="recent-content empty">(未备注)</span>
`;
list.insertBefore(item, list.firstChild);
// 保持最多10条
while (list.children.length > 10) {
list.removeChild(list.lastChild);
}
}
function updateListItem(id, content) {
const item = document.querySelector(`.recent-item[data-id="${id}"]`);
if (item) {
const contentEl = item.querySelector('.recent-content');
contentEl.textContent = content;
contentEl.classList.remove('empty');
}
}
// ========== 成就检查 ==========
let lastAchievementCount = 0;
async function checkNewAchievements() {
try {
const res = await fetch(`${API_BASE}/api/achievements`);
const data = await res.json();
if (data.unlocked_count > lastAchievementCount) {
// 有新成就解锁
const newAchievements = data.achievements.filter(a => a.unlocked).slice(-1);
if (newAchievements.length > 0) {
showAchievementToast(newAchievements[0]);
}
}
lastAchievementCount = data.unlocked_count;
} catch (err) {
console.error('检查成就失败:', err);
}
}
function showAchievementToast(achievement) {
const icon = achievementToast.querySelector('.achievement-icon');
const text = document.getElementById('achievement-text');
icon.textContent = achievement.icon;
text.textContent = `解锁:${achievement.name}`;
achievementToast.classList.add('show');
setTimeout(() => {
achievementToast.classList.remove('show');
}, 3000);
}
// ========== 辅助函数 ==========
function showToast(message, success = true) {
toast.textContent = message;
toast.style.background = success ? '#ff4757' : '#ff6b6b';
toast.classList.add('show');
setTimeout(() => {
toast.classList.remove('show');
}, 1500);
}
function formatTimeOnly(isoString) {
const date = new Date(isoString);
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
return `${hours}:${minutes}`;
}
// ========== 初始化 ==========
async function init() {
await updateStats();
await loadRecent();
await checkNewAchievements();
}
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');
document.getElementById('recent-count').textContent = records.length;
if (records.length === 0) {
list.innerHTML = '<div class="recent-item"><span class="recent-content empty">点击大按钮开始记录</span></div>';
return;
}
list.innerHTML = records.map(r => `
<div class="recent-item" data-id="${r.id}">
<span class="recent-time">${formatTimeOnly(r.created_at)}</span>
<span class="recent-content ${!r.content ? 'empty' : ''}">${r.content || '(未备注)'}</span>
</div>
`).join('');
} catch (err) {
console.error('加载记录失败:', err);
}
}
// 启动
init();