|
|
|
|
@@ -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">
|
|
|
|
|
@@ -718,8 +722,8 @@ INDEX_TEMPLATE = '''
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="mt-3">
|
|
|
|
|
<label class="form-label">截止日期</label>
|
|
|
|
|
<input type="date" id="addDueDate" class="form-control">
|
|
|
|
|
<label class="form-label">截止时间</label>
|
|
|
|
|
<input type="datetime-local" id="addDueDate" class="form-control">
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="mb-3">
|
|
|
|
|
@@ -825,8 +829,8 @@ INDEX_TEMPLATE = '''
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="mt-3">
|
|
|
|
|
<label class="form-label">截止日期</label>
|
|
|
|
|
<input type="date" id="editDueDate" class="form-control">
|
|
|
|
|
<label class="form-label">截止时间</label>
|
|
|
|
|
<input type="datetime-local" id="editDueDate" class="form-control">
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="mb-3">
|
|
|
|
|
@@ -1060,8 +1064,8 @@ INDEX_TEMPLATE = '''
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="col">
|
|
|
|
|
<label class="form-label">截止日期</label>
|
|
|
|
|
<input type="date" id="convertDueDate" class="form-control">
|
|
|
|
|
<label class="form-label">截止时间</label>
|
|
|
|
|
<input type="datetime-local" id="convertDueDate" class="form-control">
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
@@ -1104,15 +1108,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,10 +1179,13 @@ 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) : ''}
|
|
|
|
|
${item.type === 'todo' ? `${getStatusLabelShort(item.status)} ${getPriorityLabelShort(item.priority)} ${item.due_date ? '📅' + item.due_date : ''}` : ''}
|
|
|
|
|
${item.type === 'todo' ? `${getStatusLabelShort(item.status)} ${getPriorityLabelShort(item.priority)} ${item.due_date ? '📅' + formatDueDate(item.due_date) : ''}` : ''}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="d-flex align-items-center gap-1 flex-wrap ms-2" onclick="event.stopPropagation();">
|
|
|
|
|
@@ -1270,6 +1268,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;
|
|
|
|
|
@@ -1437,7 +1466,7 @@ async function showDetail(id) {
|
|
|
|
|
html += `<div class="mb-3"><strong>状态:</strong> ${getStatusLabel(item.status)}</div>`;
|
|
|
|
|
html += `<div class="mb-3"><strong>优先级:</strong> ${getPriorityLabel(item.priority)}</div>`;
|
|
|
|
|
if (item.due_date) {
|
|
|
|
|
html += `<div class="mb-3"><strong>截止日期:</strong> ${item.due_date}</div>`;
|
|
|
|
|
html += `<div class="mb-3"><strong>截止时间:</strong> ${formatDueDateFull(item.due_date)}</div>`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -1509,7 +1538,21 @@ async function openEditModal(id) {
|
|
|
|
|
if (type === 'todo') {
|
|
|
|
|
document.getElementById('editStatus').value = item.status;
|
|
|
|
|
document.getElementById('editPriority').value = item.priority;
|
|
|
|
|
document.getElementById('editDueDate').value = item.due_date || '';
|
|
|
|
|
// 处理 datetime-local 格式
|
|
|
|
|
if (item.due_date) {
|
|
|
|
|
// 尝试解析日期时间格式
|
|
|
|
|
let dueDate = item.due_date;
|
|
|
|
|
if (dueDate.includes('T')) {
|
|
|
|
|
// ISO 格式,截取到分钟
|
|
|
|
|
dueDate = dueDate.substring(0, 16);
|
|
|
|
|
} else if (dueDate.length === 10) {
|
|
|
|
|
// 只有日期,添加默认时间 00:00
|
|
|
|
|
dueDate = dueDate + 'T00:00';
|
|
|
|
|
}
|
|
|
|
|
document.getElementById('editDueDate').value = dueDate;
|
|
|
|
|
} else {
|
|
|
|
|
document.getElementById('editDueDate').value = '';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
new bootstrap.Modal(document.getElementById('editModal')).show();
|
|
|
|
|
@@ -1588,10 +1631,79 @@ 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');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 格式化截止时间(友好显示)
|
|
|
|
|
function formatDueDate(dueDate) {
|
|
|
|
|
if (!dueDate) return '';
|
|
|
|
|
|
|
|
|
|
let date;
|
|
|
|
|
if (dueDate.includes('T')) {
|
|
|
|
|
date = new Date(dueDate);
|
|
|
|
|
} else if (dueDate.length === 10) {
|
|
|
|
|
// 只有日期
|
|
|
|
|
date = new Date(dueDate + 'T00:00:00');
|
|
|
|
|
} else {
|
|
|
|
|
date = new Date(dueDate);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 格式化为友好格式
|
|
|
|
|
const now = new Date();
|
|
|
|
|
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
|
|
|
const targetDay = new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
|
|
|
const daysDiff = Math.floor((targetDay - today) / (1000 * 60 * 60 * 24));
|
|
|
|
|
|
|
|
|
|
let datePart;
|
|
|
|
|
if (daysDiff === 0) {
|
|
|
|
|
datePart = '今天';
|
|
|
|
|
} else if (daysDiff === 1) {
|
|
|
|
|
datePart = '明天';
|
|
|
|
|
} else if (daysDiff === -1) {
|
|
|
|
|
datePart = '昨天';
|
|
|
|
|
} else {
|
|
|
|
|
datePart = date.toLocaleDateString('zh-CN', {month: 'short', day: 'numeric'});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const timePart = date.toLocaleTimeString('zh-CN', {hour: '2-digit', minute: '2-digit'});
|
|
|
|
|
|
|
|
|
|
return `${datePart} ${timePart}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 格式化截止时间(完整显示)
|
|
|
|
|
function formatDueDateFull(dueDate) {
|
|
|
|
|
if (!dueDate) return '';
|
|
|
|
|
|
|
|
|
|
let date;
|
|
|
|
|
if (dueDate.includes('T')) {
|
|
|
|
|
date = new Date(dueDate);
|
|
|
|
|
} else if (dueDate.length === 10) {
|
|
|
|
|
date = new Date(dueDate + 'T00:00:00');
|
|
|
|
|
} else {
|
|
|
|
|
date = new Date(dueDate);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return date.toLocaleString('zh-CN', {
|
|
|
|
|
year: 'numeric',
|
|
|
|
|
month: 'long',
|
|
|
|
|
day: 'numeric',
|
|
|
|
|
hour: '2-digit',
|
|
|
|
|
minute: '2-digit'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function escapeHtml(str) {
|
|
|
|
|
if (!str) return '';
|
|
|
|
|
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
|
|
|
@@ -2162,7 +2274,7 @@ function showRemindersModal() {
|
|
|
|
|
html += `<div class="list-group-item list-group-item-danger d-flex justify-content-between align-items-center">
|
|
|
|
|
<div>
|
|
|
|
|
<strong>${item.title || '(无标题)'}</strong>
|
|
|
|
|
<br><small class="text-muted">截止: ${item.due_date} | 已过期 ${item.days_overdue} 天</small>
|
|
|
|
|
<br><small class="text-muted">截止: ${formatDueDateFull(item.due_date)} | 已过期 ${item.days_overdue} 天</small>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<button class="btn btn-sm btn-success" onclick="completeReminder(${item.id})" title="完成">
|
|
|
|
|
@@ -2185,7 +2297,7 @@ function showRemindersModal() {
|
|
|
|
|
html += `<div class="list-group-item list-group-item-warning d-flex justify-content-between align-items-center">
|
|
|
|
|
<div>
|
|
|
|
|
<strong>${item.title || '(无标题)'}</strong>
|
|
|
|
|
<br><small class="text-muted">截止: ${item.due_date}</small>
|
|
|
|
|
<br><small class="text-muted">截止: ${formatDueDateFull(item.due_date)}</small>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<button class="btn btn-sm btn-success" onclick="completeReminder(${item.id})" title="完成">
|
|
|
|
|
@@ -2208,7 +2320,7 @@ function showRemindersModal() {
|
|
|
|
|
html += `<div class="list-group-item list-group-item-info d-flex justify-content-between align-items-center">
|
|
|
|
|
<div>
|
|
|
|
|
<strong>${item.title || '(无标题)'}</strong>
|
|
|
|
|
<br><small class="text-muted">截止: ${item.due_date}</small>
|
|
|
|
|
<br><small class="text-muted">截止: ${formatDueDateFull(item.due_date)}</small>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<button class="btn btn-sm btn-success" onclick="completeReminder(${item.id})" title="完成">
|
|
|
|
|
|