Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6b775c99f9 | |||
| 061bfa3b55 | |||
| 2eccc11369 | |||
| d874577458 | |||
| bc59fb39db | |||
| fba2c1290d |
10
README.md
10
README.md
@@ -141,6 +141,16 @@ xian-favor/
|
||||
|
||||
## 版本历史
|
||||
|
||||
- v1.10.0 (2026-04-16): 网页端待办到期提醒功能
|
||||
- 新增提醒 API `/api/reminders`
|
||||
- 获取已过期、今天到期、即将到期的待办
|
||||
- 前端顶部提醒栏显示提醒数量和概要
|
||||
- 弹窗详情支持快速完成待办
|
||||
- 每5分钟自动刷新提醒
|
||||
- v1.9.2 (2026-04-14): 邮件发送历史记录
|
||||
- 详情页面显示邮件发送记录(发送邮箱、时间)
|
||||
- 支持多次发送,显示多条记录
|
||||
- 发送成功/失败状态标记
|
||||
- v1.9.0 (2026-04-14): 发送邮件功能 + 邮箱管理
|
||||
- 每个收藏卡片添加"发送邮件"按钮
|
||||
- 选择已有邮箱或输入新邮箱发送
|
||||
|
||||
BIN
xian_favor/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
xian_favor/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
xian_favor/__pycache__/api.cpython-310.pyc
Normal file
BIN
xian_favor/__pycache__/api.cpython-310.pyc
Normal file
Binary file not shown.
BIN
xian_favor/__pycache__/cli.cpython-310.pyc
Normal file
BIN
xian_favor/__pycache__/cli.cpython-310.pyc
Normal file
Binary file not shown.
BIN
xian_favor/__pycache__/config.cpython-310.pyc
Normal file
BIN
xian_favor/__pycache__/config.cpython-310.pyc
Normal file
Binary file not shown.
BIN
xian_favor/__pycache__/db.cpython-310.pyc
Normal file
BIN
xian_favor/__pycache__/db.cpython-310.pyc
Normal file
Binary file not shown.
@@ -65,6 +65,9 @@ def get_item(item_id):
|
||||
item = db.get_item(item_id)
|
||||
if not item:
|
||||
return jsonify({'success': False, 'error': '条目不存在'}), 404
|
||||
# 获取邮件发送历史
|
||||
email_logs = db.get_email_logs(item_id)
|
||||
item['email_logs'] = email_logs
|
||||
return jsonify({'success': True, 'data': item})
|
||||
|
||||
|
||||
@@ -109,6 +112,28 @@ def complete_item(item_id):
|
||||
return jsonify({'success': True, 'data': item})
|
||||
|
||||
|
||||
@app.route('/api/items/<int:item_id>/reopen', methods=['POST'])
|
||||
def reopen_item(item_id):
|
||||
"""重新打开待办"""
|
||||
item = db.get_item(item_id)
|
||||
if not item:
|
||||
return jsonify({'success': False, 'error': '条目不存在'}), 404
|
||||
|
||||
if item['type'] != 'todo':
|
||||
return jsonify({'success': False, 'error': '不是待办事项'}), 400
|
||||
|
||||
# 获取请求中的目标状态,默认为 pending
|
||||
data = request.get_json() or {}
|
||||
new_status = data.get('status', 'pending')
|
||||
|
||||
if new_status not in ['pending', 'in_progress']:
|
||||
return jsonify({'success': False, 'error': '无效状态'}), 400
|
||||
|
||||
db.update_item(item_id, status=new_status)
|
||||
item = db.get_item(item_id)
|
||||
return jsonify({'success': True, 'data': item})
|
||||
|
||||
|
||||
@app.route('/api/tags', methods=['GET'])
|
||||
def list_tags():
|
||||
"""列出标签"""
|
||||
@@ -164,6 +189,13 @@ def get_stats():
|
||||
return jsonify({'success': True, 'data': stats})
|
||||
|
||||
|
||||
@app.route('/api/reminders', methods=['GET'])
|
||||
def get_reminders():
|
||||
"""获取提醒信息"""
|
||||
reminders = db.get_reminders()
|
||||
return jsonify({'success': True, 'data': reminders})
|
||||
|
||||
|
||||
@app.route('/api/ai-process', methods=['POST'])
|
||||
def ai_process():
|
||||
"""AI处理文本"""
|
||||
@@ -379,8 +411,9 @@ def send_email():
|
||||
import smtplib
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from datetime import datetime
|
||||
|
||||
# SMTP配置
|
||||
# SMTP配置:端口587无SSL
|
||||
smtp_host = 'mail.tphai.com'
|
||||
smtp_port = 587
|
||||
smtp_user = 'favor@tphai.com'
|
||||
@@ -390,22 +423,26 @@ def send_email():
|
||||
msg['From'] = smtp_user
|
||||
msg['To'] = email_addr
|
||||
msg['Subject'] = subject
|
||||
msg['Date'] = datetime.now().strftime('%a, %d %b %Y %H:%M:%S +0800')
|
||||
msg['Reply-To'] = email_addr
|
||||
|
||||
msg.attach(MIMEText(body, 'plain', 'utf-8'))
|
||||
|
||||
# 端口587无SSL,尝试STARTTLS,失败则用普通连接
|
||||
# 直接连接,无SSL
|
||||
server = smtplib.SMTP(smtp_host, smtp_port)
|
||||
try:
|
||||
server.starttls()
|
||||
except smtplib.SMTPNotSupportedError:
|
||||
pass # 服务器不支持TLS,继续用普通连接
|
||||
server.ehlo()
|
||||
server.login(smtp_user, smtp_pass)
|
||||
server.sendmail(smtp_user, email_addr, msg.as_string())
|
||||
server.quit()
|
||||
|
||||
# 记录发送日志
|
||||
db.log_email_send(item_id, email_addr, success=True)
|
||||
|
||||
return jsonify({'success': True, 'message': f'已发送到 {email_addr}'})
|
||||
|
||||
except Exception as e:
|
||||
# 记录失败日志
|
||||
db.log_email_send(item_id, email_addr, success=False)
|
||||
return jsonify({'success': False, 'error': f'发送失败: {str(e)}'}), 500
|
||||
|
||||
|
||||
@@ -482,6 +519,16 @@ INDEX_TEMPLATE = '''
|
||||
|
||||
<!-- 主内容 -->
|
||||
<div class="col-md-10 content">
|
||||
<!-- 提醒栏 -->
|
||||
<div id="reminderBar" class="alert alert-warning alert-dismissible fade show mb-3" style="display:none;" role="alert">
|
||||
<i class="bi bi-bell-fill"></i>
|
||||
<span id="reminderText">有待办事项需要关注</span>
|
||||
<button type="button" class="btn btn-sm btn-warning ms-2" onclick="showRemindersModal()">
|
||||
<i class="bi bi-eye"></i> 查看详情
|
||||
</button>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" onclick="dismissReminderBar()"></button>
|
||||
</div>
|
||||
|
||||
<!-- 顶部操作栏 -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div class="d-flex gap-2">
|
||||
@@ -873,6 +920,26 @@ INDEX_TEMPLATE = '''
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 提醒详情模态框 -->
|
||||
<div class="modal fade" id="remindersModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-warning text-dark">
|
||||
<h5 class="modal-title"><i class="bi bi-bell-fill"></i> 待办提醒</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="remindersContent">
|
||||
<!-- 动态填充 -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
const API_BASE = '/api';
|
||||
@@ -883,6 +950,10 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
await loadStats(); // 先加载统计,确保总数可用
|
||||
loadItems();
|
||||
loadTags();
|
||||
loadReminders(); // 加载提醒
|
||||
|
||||
// 定时刷新提醒(每5分钟)
|
||||
setInterval(loadReminders, 5 * 60 * 1000);
|
||||
|
||||
// 标签输入自动提示
|
||||
document.getElementById('addTags').addEventListener('input', showTagSuggestions);
|
||||
@@ -968,8 +1039,9 @@ function renderItems(items) {
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div style="flex: 1; min-width: 0;">
|
||||
<h6 class="card-title text-truncate mb-1">
|
||||
<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' ? ' ✓' : ''}
|
||||
</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) : ''}
|
||||
@@ -981,6 +1053,7 @@ function renderItems(items) {
|
||||
<button class="btn btn-sm btn-outline-info py-0 px-1" onclick="showSendEmailModal(${item.id})" title="发送邮件"><i class="bi bi-envelope" style="font-size:11px;"></i></button>
|
||||
<button class="btn btn-sm btn-outline-primary py-0 px-1" onclick="openEditModal(${item.id})" title="编辑"><i class="bi bi-pencil" style="font-size:11px;"></i></button>
|
||||
${item.type === 'todo' && item.status !== 'completed' ? `<button class="btn btn-sm btn-outline-success py-0 px-1" onclick="completeItem(${item.id})" title="完成"><i class="bi bi-check-lg" style="font-size:11px;"></i></button>` : ''}
|
||||
${item.type === 'todo' && item.status === 'completed' ? `<button class="btn btn-sm btn-outline-warning py-0 px-1" onclick="reopenItem(${item.id})" title="重新打开"><i class="bi bi-arrow-counterclockwise" style="font-size:11px;"></i></button>` : ''}
|
||||
<button class="btn btn-sm btn-outline-danger py-0 px-1" onclick="deleteItem(${item.id})" title="删除"><i class="bi bi-trash" style="font-size:11px;"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1089,6 +1162,20 @@ async function completeItem(id) {
|
||||
refreshData();
|
||||
}
|
||||
|
||||
// 重新打开待办
|
||||
async function reopenItem(id) {
|
||||
const res = await fetch(`${API_BASE}/items/${id}/reopen`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ status: 'pending' })
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
refreshData();
|
||||
loadReminders(); // 刷新提醒
|
||||
}
|
||||
}
|
||||
|
||||
// 删除条目
|
||||
async function deleteItem(id) {
|
||||
if (!confirm('确认删除?')) return;
|
||||
@@ -1143,6 +1230,20 @@ async function showDetail(id) {
|
||||
|
||||
html += `<div class="text-muted small"><strong>创建时间:</strong> ${formatDate(item.created_at)}<br><strong>更新时间:</strong> ${formatDate(item.updated_at)}</div>`;
|
||||
|
||||
// 邮件发送历史
|
||||
if (item.email_logs && item.email_logs.length > 0) {
|
||||
html += `<hr><div class="mb-3"><strong>📧 邮件发送记录:</strong></div>`;
|
||||
html += `<div class="border rounded p-2 bg-light">`;
|
||||
item.email_logs.forEach(log => {
|
||||
const statusIcon = log.success ? '✅' : '❌';
|
||||
html += `<div class="d-flex justify-content-between align-items-center py-1 border-bottom">
|
||||
<span>${statusIcon} 发送到: <strong>${log.email}</strong></span>
|
||||
<span class="text-muted small">${formatDate(log.sent_at)}</span>
|
||||
</div>`;
|
||||
});
|
||||
html += `</div>`;
|
||||
}
|
||||
|
||||
document.getElementById('detailContent').innerHTML = html;
|
||||
|
||||
new bootstrap.Modal(document.getElementById('detailModal')).show();
|
||||
@@ -1763,6 +1864,165 @@ function debounce(fn, delay) {
|
||||
timer = setTimeout(() => fn.apply(this, args), delay);
|
||||
};
|
||||
}
|
||||
|
||||
// ============ 提醒功能 ============
|
||||
|
||||
let reminderData = null;
|
||||
let reminderDismissed = false;
|
||||
|
||||
async function loadReminders() {
|
||||
const res = await fetch(`${API_BASE}/reminders`);
|
||||
const data = await res.json();
|
||||
|
||||
if (data.success) {
|
||||
reminderData = data.data;
|
||||
updateReminderBar();
|
||||
}
|
||||
}
|
||||
|
||||
function updateReminderBar() {
|
||||
if (!reminderData || reminderDismissed) return;
|
||||
|
||||
const total = reminderData.total;
|
||||
if (total === 0) {
|
||||
document.getElementById('reminderBar').style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示提醒栏
|
||||
document.getElementById('reminderBar').style.display = 'block';
|
||||
|
||||
// 构建提醒文本
|
||||
let text = '';
|
||||
if (reminderData.overdue.length > 0) {
|
||||
text += `<span class="text-danger fw-bold">${reminderData.overdue.length}个已过期</span> `;
|
||||
}
|
||||
if (reminderData.due_today.length > 0) {
|
||||
text += `<span class="text-warning fw-bold">${reminderData.due_today.length}个今天到期</span> `;
|
||||
}
|
||||
if (reminderData.due_soon.length > 0) {
|
||||
text += `<span>${reminderData.due_soon.length}个即将到期</span>`;
|
||||
}
|
||||
|
||||
document.getElementById('reminderText').innerHTML = text;
|
||||
|
||||
// 更新提醒角标样式
|
||||
const bar = document.getElementById('reminderBar');
|
||||
if (reminderData.overdue.length > 0) {
|
||||
bar.className = 'alert alert-danger alert-dismissible fade show mb-3';
|
||||
} else if (reminderData.due_today.length > 0) {
|
||||
bar.className = 'alert alert-warning alert-dismissible fade show mb-3';
|
||||
} else {
|
||||
bar.className = 'alert alert-info alert-dismissible fade show mb-3';
|
||||
}
|
||||
}
|
||||
|
||||
function dismissReminderBar() {
|
||||
reminderDismissed = true;
|
||||
document.getElementById('reminderBar').style.display = 'none';
|
||||
}
|
||||
|
||||
function showRemindersModal() {
|
||||
if (!reminderData) return;
|
||||
|
||||
let html = '';
|
||||
|
||||
// 已过期
|
||||
if (reminderData.overdue.length > 0) {
|
||||
html += `<div class="mb-3"><h6 class="text-danger"><i class="bi bi-exclamation-circle"></i> 已过期 (${reminderData.overdue.length})</h6>`;
|
||||
html += `<div class="list-group">`;
|
||||
reminderData.overdue.forEach(item => {
|
||||
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>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-sm btn-success" onclick="completeReminder(${item.id})" title="完成">
|
||||
<i class="bi bi-check"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-primary" onclick="openEditModal(${item.id}); bootstrap.Modal.getInstance(document.getElementById('remindersModal')).hide();" title="编辑">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>`;
|
||||
});
|
||||
html += `</div></div>`;
|
||||
}
|
||||
|
||||
// 今天到期
|
||||
if (reminderData.due_today.length > 0) {
|
||||
html += `<div class="mb-3"><h6 class="text-warning"><i class="bi bi-clock-fill"></i> 今天到期 (${reminderData.due_today.length})</h6>`;
|
||||
html += `<div class="list-group">`;
|
||||
reminderData.due_today.forEach(item => {
|
||||
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>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-sm btn-success" onclick="completeReminder(${item.id})" title="完成">
|
||||
<i class="bi bi-check"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-primary" onclick="openEditModal(${item.id}); bootstrap.Modal.getInstance(document.getElementById('remindersModal')).hide();" title="编辑">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>`;
|
||||
});
|
||||
html += `</div></div>`;
|
||||
}
|
||||
|
||||
// 即将到期
|
||||
if (reminderData.due_soon.length > 0) {
|
||||
html += `<div class="mb-3"><h6 class="text-info"><i class="bi bi-hourglass-split"></i> 即将到期 (${reminderData.due_soon.length})</h6>`;
|
||||
html += `<div class="list-group">`;
|
||||
reminderData.due_soon.forEach(item => {
|
||||
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>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-sm btn-success" onclick="completeReminder(${item.id})" title="完成">
|
||||
<i class="bi bi-check"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-primary" onclick="openEditModal(${item.id}); bootstrap.Modal.getInstance(document.getElementById('remindersModal')).hide();" title="编辑">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>`;
|
||||
});
|
||||
html += `</div></div>`;
|
||||
}
|
||||
|
||||
if (!html) {
|
||||
html = '<div class="text-center text-muted py-3">暂无待办提醒</div>';
|
||||
}
|
||||
|
||||
document.getElementById('remindersContent').innerHTML = html;
|
||||
new bootstrap.Modal(document.getElementById('remindersModal')).show();
|
||||
}
|
||||
|
||||
async function completeReminder(id) {
|
||||
await fetch(`${API_BASE}/items/${id}/done`, { method: 'POST' });
|
||||
|
||||
// 刷新数据
|
||||
await loadReminders();
|
||||
refreshData();
|
||||
|
||||
// 如果弹窗打开,更新显示
|
||||
const modalEl = document.getElementById('remindersModal');
|
||||
const modal = bootstrap.Modal.getInstance(modalEl);
|
||||
if (modal) {
|
||||
// 检查是否还有提醒
|
||||
if (reminderData && reminderData.total > 0) {
|
||||
showRemindersModal();
|
||||
} else {
|
||||
modal.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -90,6 +90,18 @@ class Database:
|
||||
)
|
||||
""")
|
||||
|
||||
# 邮件发送记录表
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS email_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
item_id INTEGER NOT NULL,
|
||||
email TEXT NOT NULL,
|
||||
sent_at TEXT NOT NULL,
|
||||
success INTEGER DEFAULT 1,
|
||||
FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE
|
||||
)
|
||||
""")
|
||||
|
||||
# 创建索引
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_items_type ON items(type)")
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_items_status ON items(status)")
|
||||
@@ -371,6 +383,31 @@ class Database:
|
||||
conn.commit()
|
||||
return cursor.rowcount > 0
|
||||
|
||||
# ============ 邮件日志 操作 ============
|
||||
|
||||
def log_email_send(self, item_id: int, email: str, success: bool = True) -> int:
|
||||
"""记录邮件发送"""
|
||||
now = datetime.now().isoformat()
|
||||
with self.get_conn() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
INSERT INTO email_logs (item_id, email, sent_at, success)
|
||||
VALUES (?, ?, ?, ?)
|
||||
""", (item_id, email, now, 1 if success else 0))
|
||||
conn.commit()
|
||||
return cursor.lastrowid
|
||||
|
||||
def get_email_logs(self, item_id: int) -> List[Dict[str, Any]]:
|
||||
"""获取收藏的邮件发送记录"""
|
||||
with self.get_conn() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
SELECT * FROM email_logs
|
||||
WHERE item_id = ?
|
||||
ORDER BY sent_at DESC
|
||||
""", (item_id,))
|
||||
return [dict(row) for row in cursor.fetchall()]
|
||||
|
||||
def stats(self) -> Dict[str, Any]:
|
||||
"""获取统计信息"""
|
||||
self._ensure_init()
|
||||
@@ -396,6 +433,59 @@ class Database:
|
||||
stats['tags'] = cursor.fetchone()['count']
|
||||
|
||||
return stats
|
||||
|
||||
# ============ 提醒相关 ============
|
||||
|
||||
def get_reminders(self) -> Dict[str, Any]:
|
||||
"""获取提醒信息:即将到期和已过期的待办"""
|
||||
self._ensure_init()
|
||||
with self.get_conn() as conn:
|
||||
cursor = conn.cursor()
|
||||
now = datetime.now()
|
||||
|
||||
reminders = {
|
||||
'overdue': [], # 已过期
|
||||
'due_today': [], # 今天到期
|
||||
'due_soon': [] # 24小时内到期(不含今天)
|
||||
}
|
||||
|
||||
# 查询未完成的待办(有截止日期的)
|
||||
cursor.execute("""
|
||||
SELECT * FROM items
|
||||
WHERE type = 'todo'
|
||||
AND status != 'completed'
|
||||
AND due_date IS NOT NULL
|
||||
AND due_date != ''
|
||||
ORDER BY due_date ASC
|
||||
""")
|
||||
|
||||
for row in cursor.fetchall():
|
||||
item = dict(row)
|
||||
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
|
||||
|
||||
if days_left < 0:
|
||||
# 已过期
|
||||
item['days_overdue'] = abs(days_left)
|
||||
reminders['overdue'].append(item)
|
||||
elif days_left == 0:
|
||||
# 今天到期
|
||||
reminders['due_today'].append(item)
|
||||
elif days_left == 1:
|
||||
# 明天到期(24小时内)
|
||||
reminders['due_soon'].append(item)
|
||||
except ValueError:
|
||||
# 日期格式错误,跳过
|
||||
continue
|
||||
|
||||
# 统计总数
|
||||
reminders['total'] = len(reminders['overdue']) + len(reminders['due_today']) + len(reminders['due_soon'])
|
||||
|
||||
return reminders
|
||||
|
||||
|
||||
# 全局数据库实例
|
||||
|
||||
Reference in New Issue
Block a user