Compare commits

...

3 Commits

Author SHA1 Message Date
51c76ebd24 feat: 新增草稿箱自动保存功能
- 编辑时自动保存到 localStorage(每5秒或输入后2秒)
- 打开添加弹框时检查是否有草稿并提示恢复
- 成功添加后清除草稿
- 弹框标题显示“已自动保存”指示器
2026-04-19 17:31:08 +08:00
facf39e778 fix: 修复回收站显示问题
- 修改提示文字:回收站数据可随时恢复,无30天限制
- 清空分页组件:避免显示错误的分页
2026-04-19 17:09:58 +08:00
51cecf1f4e fix: 修复回收站点击事件被侧边栏通用处理器捕获的问题
- 只为有 data-filter 属性的链接添加过滤事件处理器
- 回收站链接使用 onclick 内联事件,不会被通用处理器干扰
- 从回收站返回时正确重置 trashView 状态
2026-04-19 17:07:48 +08:00

View File

@@ -848,6 +848,7 @@ INDEX_TEMPLATE = '''
<h5 class="modal-title">
<span id="addModalIcon"></span>
<span id="addModalTitle">添加条目</span>
<span id="draftIndicator" class="badge bg-secondary ms-2" style="display:none;">已自动保存</span>
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
@@ -1362,9 +1363,15 @@ document.addEventListener('DOMContentLoaded', async () => {
});
// 侧边栏过滤
document.querySelectorAll('.sidebar a').forEach(a => {
document.querySelectorAll('.sidebar a[data-filter]').forEach(a => {
a.addEventListener('click', (e) => {
e.preventDefault();
// 如果在回收站视图,先退出
if (trashView) {
trashView = false;
}
document.querySelectorAll('.sidebar a').forEach(x => x.classList.remove('active'));
a.classList.add('active');
@@ -1525,6 +1532,129 @@ function changeSort() {
// ============ 添加功能 ============
// 快捷添加按钮
// ============ 草稿箱 ============
const DRAFT_KEY = 'xian_favor_draft';
let draftTimer = null;
// 保存草稿到 localStorage
function saveDraft() {
const type = document.getElementById('addType')?.value || 'text';
const draft = {
type,
title: document.getElementById('addTitle')?.value || '',
content: document.getElementById('addContent')?.value || '',
url: document.getElementById('addUrl')?.value || '',
source: document.getElementById('addSource')?.value || '',
status: document.getElementById('addStatus')?.value || 'pending',
priority: document.getElementById('addPriority')?.value || 'medium',
due_date: document.getElementById('addDueDate')?.value || '',
note: document.getElementById('addNote')?.value || '',
tags: document.getElementById('addTags')?.value || '',
is_starred: document.getElementById('addStarred')?.checked || false,
saved_at: new Date().toISOString()
};
// 只在有内容时保存
if (draft.title || draft.content || draft.url || draft.note) {
localStorage.setItem(DRAFT_KEY, JSON.stringify(draft));
showDraftIndicator();
}
}
// 加载草稿
function loadDraft() {
const draft = localStorage.getItem(DRAFT_KEY);
if (!draft) return null;
return JSON.parse(draft);
}
// 清除草稿
function clearDraft() {
localStorage.removeItem(DRAFT_KEY);
hideDraftIndicator();
}
// 显示草稿指示器
function showDraftIndicator() {
const indicator = document.getElementById('draftIndicator');
if (indicator) indicator.style.display = 'inline';
}
// 隐藏草稿指示器
function hideDraftIndicator() {
const indicator = document.getElementById('draftIndicator');
if (indicator) indicator.style.display = 'none';
}
// 检查是否有草稿并提示恢复
function checkAndRestoreDraft(type) {
const draft = loadDraft();
if (!draft) return false;
// 草稿类型匹配才提示
if (draft.type === type && (draft.title || draft.content || draft.url || draft.note)) {
const savedTime = new Date(draft.saved_at).toLocaleString('zh-CN');
if (confirm(`发现未保存的草稿(${savedTime}),是否恢复?`)) {
restoreDraftToForm(draft);
return true;
} else {
clearDraft(); // 用户选择不恢复,清除草稿
}
}
return false;
}
// 将草稿恢复到表单
function restoreDraftToForm(draft) {
document.getElementById('addTitle').value = draft.title || '';
if (draft.type === 'text') {
document.getElementById('addContent').value = draft.content || '';
}
if (['link', 'column'].includes(draft.type)) {
document.getElementById('addUrl').value = draft.url || '';
}
if (draft.type === 'column') {
document.getElementById('addSource').value = draft.source || '';
}
if (draft.type === 'todo') {
document.getElementById('addStatus').value = draft.status || 'pending';
document.getElementById('addPriority').value = draft.priority || 'medium';
document.getElementById('addDueDate').value = draft.due_date || '';
}
document.getElementById('addNote').value = draft.note || '';
document.getElementById('addTags').value = draft.tags || '';
document.getElementById('addStarred').checked = draft.is_starred || false;
showDraftIndicator();
}
// 启动自动保存
function startAutoSave() {
// 每5秒自动保存一次
draftTimer = setInterval(saveDraft, 5000);
// 监听输入事件立即保存(带延迟)
const form = document.getElementById('addForm');
if (form) {
form.addEventListener('input', () => {
clearTimeout(draftTimer);
draftTimer = setTimeout(saveDraft, 2000); // 输入后2秒保存
// 重新启动定时保存
clearInterval(draftTimer);
draftTimer = setInterval(saveDraft, 5000);
});
}
}
// 停止自动保存
function stopAutoSave() {
if (draftTimer) {
clearInterval(draftTimer);
clearTimeout(draftTimer);
draftTimer = null;
}
}
function showAddModal(type) {
// 设置类型
document.getElementById('addType').value = type;
@@ -1549,8 +1679,19 @@ function showAddModal(type) {
// 清空表单
document.getElementById('addForm').reset();
// 检查是否有草稿
const restored = checkAndRestoreDraft(type);
// 打开弹窗
new bootstrap.Modal(document.getElementById('addModal')).show();
// 启动自动保存
startAutoSave();
// 弹框关闭时停止自动保存
document.getElementById('addModal').addEventListener('hidden.bs.modal', () => {
stopAutoSave();
}, { once: true });
}
// 添加条目
@@ -1579,6 +1720,8 @@ async function addItem() {
if (res.ok) {
bootstrap.Modal.getInstance(document.getElementById('addModal')).hide();
document.getElementById('addForm').reset();
clearDraft(); // 成功添加后清除草稿
stopAutoSave(); // 停止自动保存
refreshData();
}
}
@@ -2334,13 +2477,17 @@ async function loadTrash() {
function renderTrash(items, total) {
const container = document.getElementById('itemList');
const paginationContainer = document.getElementById('pagination');
// 清空分页
paginationContainer.innerHTML = '';
// 显示回收站标题和操作按钮
let header = `
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
<h5><i class="bi bi-trash"></i> 回收站 (${total} 条数据)</h5>
<small class="text-muted">删除的数据可在30天内恢复</small>
<small class="text-muted">回收站数据可随时恢复或彻底删除</small>
</div>
<div>
<button class="btn btn-outline-secondary" onclick="hideTrash()">