Compare commits

...

8 Commits

Author SHA1 Message Date
31f2d8b428 fix: 搜索功能修复 - debounce函数定义顺序
- debounce函数在使用前必须先定义
- 之前定义在代码末尾,导致搜索事件绑定失败
- 现移到代码开头,确保正确执行
2026-04-16 13:40:44 +08:00
5d6dd10dfa docs: 更新版本历史 v2.3.1 2026-04-16 12:20:51 +08:00
d0f7b07260 fix: 日期放到标题后面,不增加卡片高度
- 日期紧跟标题后面,同一行显示
- 格式更紧凑:04-16 11:09 →04-16 12:00
- 字体10px,透明度0.5,不影响阅读
2026-04-16 12:20:32 +08:00
0be768ca8e docs: 更新版本历史 v2.3.0 2026-04-16 12:17:24 +08:00
68ecb16303 feat: 卡片显示创建和更新日期
- 每个收藏卡片底部显示日期
- 格式:04-16 11:09(月-日 时:分)
- 有更新时显示:创建 → 更新
- 字体更小更淡,不影响卡片高度
2026-04-16 12:16:54 +08:00
82d928f497 docs: 更新版本历史 v2.2.0 2026-04-16 11:45:57 +08:00
c99eca35f0 feat: 快捷添加按钮,一键选择类型
- 顶部按钮栏分离为4个快捷添加按钮
- 点击直接进入对应类型的添加弹窗
- 弹窗标题显示类型图标和名称
- 不再需要下拉选择类型
2026-04-16 11:45:25 +08:00
47b195ed1c docs: 更新版本历史 v2.1.0 2026-04-16 11:37:49 +08:00
2 changed files with 97 additions and 35 deletions

View File

@@ -141,6 +141,25 @@ xian-favor/
## 版本历史
- **v2.3.1** (2026-04-16): 日期放到标题后面,不增加卡片高度
- 日期紧跟标题,同一行显示
- 格式紧凑:`04-16 11:09→04-16 12:00`
- **v2.3.0** (2026-04-16): 卡片显示创建和更新日期
- 每个收藏卡片底部显示日期
- 格式04-16 11:09月-日 时:分)
- 有更新时显示:创建 → 更新
- 字体更小更淡,不影响卡片高度
- **v2.2.0** (2026-04-16): 快捷添加按钮,一键选择类型
- 顶部按钮栏分离为4个快捷添加按钮文本、链接、待办、专栏
- 点击直接进入对应类型的添加弹窗
- 弹窗标题显示类型图标和名称
- 不再需要下拉选择类型,操作更快捷
- **v2.1.0** (2026-04-16): 待办截止时间支持日期+时间
- 截止日期改为日期时间选择器
- 列表显示友好格式:今天 18:30、明天 09:00 等
- 详情页显示完整格式2026年4月16日 18:30
- 后端支持多种日期格式解析
- 只有日期的待办视为当天 23:59 到期
- **v2.0.1** (2026-04-16): 转换弹窗优化
- 内容预览保留换行格式,提高可读性
- 转换方式默认改为复制创建

View File

@@ -539,11 +539,11 @@ INDEX_TEMPLATE = '''
.content { padding: 20px; }
.card { margin-bottom: 8px; transition: transform 0.2s; }
.card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.15); }
.card-body { padding: 10px 15px; }
.card-body { padding: 8px 12px; }
.tag { margin-right: 5px; }
.item-card { font-size: 14px; }
.item-card h6 { font-size: 14px; margin-bottom: 4px; }
.item-card p { margin-bottom: 4px; }
.item-card h6 { font-size: 14px; margin-bottom: 2px; }
.item-card p { margin-bottom: 2px; }
.item-card .text-muted.small { font-size: 12px; }
.type-text { border-left: 4px solid #17a2b8; }
.type-link { border-left: 4px solid #28a745; }
@@ -609,12 +609,21 @@ INDEX_TEMPLATE = '''
<button class="btn btn-outline-info me-2" onclick="showAIAddModal()" title="AI自动添加">
<i class="bi bi-robot"></i> AI添加
</button>
<button class="btn btn-outline-secondary me-1" onclick="showAddModal('text')" title="添加文本">
<i class="bi bi-file-text"></i>
</button>
<button class="btn btn-outline-success me-1" onclick="showAddModal('link')" title="添加链接">
<i class="bi bi-link-45deg"></i>
</button>
<button class="btn btn-outline-warning me-1" onclick="showAddModal('todo')" title="添加待办">
<i class="bi bi-check2-square"></i>
</button>
<button class="btn btn-outline-primary me-1" onclick="showAddModal('column')" title="添加专栏">
<i class="bi bi-newspaper"></i>
</button>
<button class="btn btn-outline-success me-2" onclick="exportData()" title="导出JSON">
<i class="bi bi-download"></i> 导出
</button>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addModal">
<i class="bi bi-plus-lg"></i> 添加
</button>
</div>
<!-- 统计卡片 -->
@@ -667,20 +676,15 @@ INDEX_TEMPLATE = '''
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">添加条目</h5>
<h5 class="modal-title">
<span id="addModalIcon"></span>
<span id="addModalTitle">添加条目</span>
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="addForm">
<div class="mb-3">
<label class="form-label">类型</label>
<select id="addType" class="form-select">
<option value="text">📝 文本</option>
<option value="link">🔗 链接</option>
<option value="column">📰 专栏</option>
<option value="todo">✅ 待办</option>
</select>
</div>
<input type="hidden" id="addType" value="text">
<div class="mb-3">
<label class="form-label">标题</label>
<input type="text" id="addTitle" class="form-control" placeholder="可选">
@@ -691,11 +695,11 @@ INDEX_TEMPLATE = '''
</div>
<div class="mb-3" id="urlGroup" style="display:none;">
<label class="form-label">URL</label>
<input type="url" id="addUrl" class="form-control">
<input type="url" id="addUrl" class="form-control" placeholder="https://...">
</div>
<div class="mb-3" id="sourceGroup" style="display:none;">
<label class="form-label">来源</label>
<input type="text" id="addSource" class="form-control">
<input type="text" id="addSource" class="form-control" placeholder="专栏来源">
</div>
<div class="mb-3" id="todoFields" style="display:none;">
<div class="row">
@@ -1086,6 +1090,17 @@ INDEX_TEMPLATE = '''
<script>
const API_BASE = '/api';
let currentFilter = { type: '', status: '' };
let currentPage = 1;
const pageSize = 20;
// 工具函数 - 必须先定义
function debounce(fn, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
// 初始化
document.addEventListener('DOMContentLoaded', async () => {
@@ -1104,15 +1119,6 @@ document.addEventListener('DOMContentLoaded', async () => {
// 标签搜索实时过滤
document.getElementById('tagSearch')?.addEventListener('input', debounce(loadTagManagerList, 300));
// 类型切换时显示/隐藏字段
document.getElementById('addType').addEventListener('change', (e) => {
const type = e.target.value;
document.getElementById('contentGroup').style.display = type === 'text' ? 'block' : 'none';
document.getElementById('urlGroup').style.display = ['link', 'column'].includes(type) ? 'block' : 'none';
document.getElementById('sourceGroup').style.display = type === 'column' ? 'block' : 'none';
document.getElementById('todoFields').style.display = type === 'todo' ? 'block' : 'none';
});
// 编辑时类型切换
document.getElementById('editType').addEventListener('change', (e) => {
updateEditFieldsByType(e.target.value);
@@ -1184,6 +1190,9 @@ function renderItems(items) {
<h6 class="card-title text-truncate mb-1 ${item.type === 'todo' && item.status === 'completed' ? 'text-muted' : ''}">
${getTypeIcon(item.type)} ${item.title || truncate(item.content || item.url, 30)}
${item.type === 'todo' && item.status === 'completed' ? '' : ''}
<span style="font-size:10px; opacity:0.5; margin-left:8px;">
${formatShortDate(item.created_at)}${item.updated_at && item.updated_at !== item.created_at ? '' + formatShortDate(item.updated_at) : ''}
</span>
</h6>
<p class="card-text text-muted small mb-0 text-truncate" style="font-size:12px;">
${item.url ? truncate(item.url, 50) : item.content ? truncate(item.content, 50) : item.note ? truncate(item.note, 50) : ''}
@@ -1270,6 +1279,37 @@ async function refreshData() {
loadItems(currentPage);
}
// ============ 添加功能 ============
// 快捷添加按钮
function showAddModal(type) {
// 设置类型
document.getElementById('addType').value = type;
// 设置弹窗标题和图标
const typeInfo = {
text: { icon: '📝', title: '添加文本' },
link: { icon: '🔗', title: '添加链接' },
column: { icon: '📰', title: '添加专栏' },
todo: { icon: '', title: '添加待办' }
};
document.getElementById('addModalIcon').textContent = typeInfo[type].icon;
document.getElementById('addModalTitle').textContent = typeInfo[type].title;
// 显示/隐藏对应字段
document.getElementById('contentGroup').style.display = type === 'text' ? 'block' : 'none';
document.getElementById('urlGroup').style.display = ['link', 'column'].includes(type) ? 'block' : 'none';
document.getElementById('sourceGroup').style.display = type === 'column' ? 'block' : 'none';
document.getElementById('todoFields').style.display = type === 'todo' ? 'block' : 'none';
// 清空表单
document.getElementById('addForm').reset();
// 打开弹窗
new bootstrap.Modal(document.getElementById('addModal')).show();
}
// 添加条目
async function addItem() {
const type = document.getElementById('addType').value;
@@ -1602,6 +1642,17 @@ function truncate(str, len) {
return str && str.length > len ? str.substring(0, len) + '...' : str || '';
}
// 简短日期格式(用于卡片显示)
function formatShortDate(dateStr) {
if (!dateStr) return '';
const date = new Date(dateStr);
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hour = String(date.getHours()).padStart(2, '0');
const min = String(date.getMinutes()).padStart(2, '0');
return `${month}-${day} ${hour}:${min}`;
}
function formatDate(dateStr) {
return new Date(dateStr).toLocaleString('zh-CN');
}
@@ -2156,14 +2207,6 @@ async function sendItemEmail() {
}
}
function debounce(fn, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
// ============ 提醒功能 ============
let reminderData = null;