feat: 待办截止时间支持日期+时间

- 截止日期改为日期时间选择器(datetime-local)
- 新增友好显示函数:今天/明天/昨天 + 时间
- 详情页显示完整日期时间格式
- 提醒弹窗显示友好格式
- 后端支持多种日期格式解析
- 只有日期的待办视为当天23:59到期
This commit is contained in:
2026-04-16 11:37:04 +08:00
parent bcb24e474d
commit 1f1528979c
2 changed files with 110 additions and 23 deletions

View File

@@ -718,8 +718,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 +825,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 +1060,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>
@@ -1187,7 +1187,7 @@ function renderItems(items) {
</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();">
@@ -1437,7 +1437,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 +1509,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();
@@ -1592,6 +1606,64 @@ 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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
@@ -2162,7 +2234,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 +2257,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 +2280,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="完成">

View File

@@ -464,21 +464,36 @@ class Database:
item['tags'] = self._get_item_tags(conn, item['id'])
try:
due_date = datetime.strptime(item['due_date'], '%Y-%m-%d')
# 计算距离到期的时间
days_left = (due_date.date() - now.date()).days
due_date_str = item['due_date']
# 支持多种日期格式
if 'T' in due_date_str:
# ISO 格式2026-04-16T14:30
due_date = datetime.strptime(due_date_str[:16], '%Y-%m-%dT%H:%M')
elif len(due_date_str) == 10:
# 只有日期2026-04-16视为当天 23:59:59
due_date = datetime.strptime(due_date_str, '%Y-%m-%d').replace(hour=23, minute=59, second=59)
else:
# 其他格式,尝试解析
due_date = datetime.strptime(due_date_str.split('.')[0], '%Y-%m-%dT%H:%M:%S')
if days_left < 0:
# 计算距离到期的时间
time_left = due_date - now
if time_left.total_seconds() < 0:
# 已过期
item['days_overdue'] = abs(days_left)
days_overdue = abs(int(time_left.total_seconds() / 86400))
item['days_overdue'] = days_overdue
reminders['overdue'].append(item)
elif days_left == 0:
# 今天到期
elif time_left.total_seconds() < 86400: # 24小时内
# 判断是今天还是明天
if due_date.date() == now.date():
reminders['due_today'].append(item)
else:
reminders['due_soon'].append(item)
elif due_date.date() == now.date():
# 今天到期超过24小时的情况比如现在凌晨截止时间是晚上
reminders['due_today'].append(item)
elif days_left == 1:
# 明天到期24小时内
reminders['due_soon'].append(item)
except ValueError:
except (ValueError, AttributeError) as e:
# 日期格式错误,跳过
continue