feat: 邮件发送历史记录 v1.9.2
This commit is contained in:
@@ -141,6 +141,10 @@ xian-favor/
|
|||||||
|
|
||||||
## 版本历史
|
## 版本历史
|
||||||
|
|
||||||
|
- v1.9.2 (2026-04-14): 邮件发送历史记录
|
||||||
|
- 详情页面显示邮件发送记录(发送邮箱、时间)
|
||||||
|
- 支持多次发送,显示多条记录
|
||||||
|
- 发送成功/失败状态标记
|
||||||
- v1.9.0 (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)
|
item = db.get_item(item_id)
|
||||||
if not item:
|
if not item:
|
||||||
return jsonify({'success': False, 'error': '条目不存在'}), 404
|
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})
|
return jsonify({'success': True, 'data': item})
|
||||||
|
|
||||||
|
|
||||||
@@ -403,9 +406,14 @@ def send_email():
|
|||||||
server.sendmail(smtp_user, email_addr, msg.as_string())
|
server.sendmail(smtp_user, email_addr, msg.as_string())
|
||||||
server.quit()
|
server.quit()
|
||||||
|
|
||||||
|
# 记录发送日志
|
||||||
|
db.log_email_send(item_id, email_addr, success=True)
|
||||||
|
|
||||||
return jsonify({'success': True, 'message': f'已发送到 {email_addr}'})
|
return jsonify({'success': True, 'message': f'已发送到 {email_addr}'})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
# 记录失败日志
|
||||||
|
db.log_email_send(item_id, email_addr, success=False)
|
||||||
return jsonify({'success': False, 'error': f'发送失败: {str(e)}'}), 500
|
return jsonify({'success': False, 'error': f'发送失败: {str(e)}'}), 500
|
||||||
|
|
||||||
|
|
||||||
@@ -1143,6 +1151,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>`;
|
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;
|
document.getElementById('detailContent').innerHTML = html;
|
||||||
|
|
||||||
new bootstrap.Modal(document.getElementById('detailModal')).show();
|
new bootstrap.Modal(document.getElementById('detailModal')).show();
|
||||||
|
|||||||
@@ -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_type ON items(type)")
|
||||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_items_status ON items(status)")
|
cursor.execute("CREATE INDEX IF NOT EXISTS idx_items_status ON items(status)")
|
||||||
@@ -371,6 +383,31 @@ class Database:
|
|||||||
conn.commit()
|
conn.commit()
|
||||||
return cursor.rowcount > 0
|
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]:
|
def stats(self) -> Dict[str, Any]:
|
||||||
"""获取统计信息"""
|
"""获取统计信息"""
|
||||||
self._ensure_init()
|
self._ensure_init()
|
||||||
|
|||||||
Reference in New Issue
Block a user