feat(email): 新增 Outlook 收件箱功能

- 后端:GET /api/email-services/{id}/inbox 端点,通过 IMAPNewProvider 读取收件箱
- 前端:收件箱列表模态框(支持仅未读筛选、刷新)
- 前端:邮件正文弹窗
- Outlook 操作列新增「收件箱」按钮
This commit is contained in:
cnlimiter
2026-03-21 02:37:35 +08:00
parent 14ea1fd983
commit 4ae99659f2
3 changed files with 235 additions and 25 deletions

View File

@@ -72,6 +72,25 @@ const elements = {
editOutlookForm: document.getElementById('edit-outlook-form'),
closeEditOutlookModal: document.getElementById('close-edit-outlook-modal'),
cancelEditOutlook: document.getElementById('cancel-edit-outlook'),
// 收件箱模态框
inboxModal: document.getElementById('inbox-modal'),
closeInboxModal: document.getElementById('close-inbox-modal'),
inboxRefreshBtn: document.getElementById('inbox-refresh-btn'),
inboxOnlyUnseen: document.getElementById('inbox-only-unseen'),
inboxLoading: document.getElementById('inbox-loading'),
inboxTable: document.getElementById('inbox-table'),
inboxTbody: document.getElementById('inbox-tbody'),
inboxEmpty: document.getElementById('inbox-empty'),
inboxModalEmail: document.getElementById('inbox-modal-email'),
// 邮件正文模态框
emailDetailModal: document.getElementById('email-detail-modal'),
closeEmailDetailModal: document.getElementById('close-email-detail-modal'),
emailDetailSubject: document.getElementById('email-detail-subject'),
emailDetailSender: document.getElementById('email-detail-sender'),
emailDetailDate: document.getElementById('email-detail-date'),
emailDetailBody: document.getElementById('email-detail-body'),
};
const CUSTOM_SUBTYPE_LABELS = {
@@ -164,6 +183,12 @@ function initEventListeners() {
document.addEventListener('click', () => {
document.querySelectorAll('.dropdown-menu.active').forEach(m => m.classList.remove('active'));
});
// 收件箱模态框事件
elements.closeInboxModal.addEventListener('click', () => elements.inboxModal.classList.remove('active'));
elements.closeEmailDetailModal.addEventListener('click', () => elements.emailDetailModal.classList.remove('active'));
elements.inboxRefreshBtn.addEventListener('click', () => loadInbox(currentInboxServiceId, true));
elements.inboxOnlyUnseen.addEventListener('change', () => loadInbox(currentInboxServiceId));
}
function toggleEmailMoreMenu(btn) {
@@ -247,6 +272,7 @@ async function loadOutlookServices() {
<td>${format.date(service.last_used)}</td>
<td>
<div style="display:flex;gap:4px;align-items:center;white-space:nowrap;">
<button class="btn btn-secondary btn-sm" onclick="openInboxModal(${service.id}, '${escapeHtml(service.config?.email || service.name)}')">收件箱</button>
<button class="btn btn-secondary btn-sm" onclick="editOutlookService(${service.id})">编辑</button>
<div class="dropdown" style="position:relative;">
<button class="btn btn-secondary btn-sm" onclick="event.stopPropagation();toggleEmailMoreMenu(this)">更多</button>
@@ -787,3 +813,57 @@ async function handleEditOutlook(e) {
toast.error('更新失败: ' + error.message);
}
}
// ============== 收件箱 ==============
let currentInboxServiceId = null;
async function openInboxModal(serviceId, email) {
currentInboxServiceId = serviceId;
elements.inboxModalEmail.textContent = email;
elements.inboxOnlyUnseen.checked = false;
elements.inboxModal.classList.add('active');
await loadInbox(serviceId);
}
async function loadInbox(serviceId) {
if (!serviceId) return;
const onlyUnseen = elements.inboxOnlyUnseen.checked;
elements.inboxLoading.style.display = 'block';
elements.inboxTable.style.display = 'none';
elements.inboxEmpty.style.display = 'none';
elements.inboxEmpty.textContent = '暂无邮件';
try {
const params = new URLSearchParams({ count: 50, only_unseen: onlyUnseen });
const data = await api.get(`/email-services/${serviceId}/inbox?${params}`);
const emails = data.emails || [];
elements.inboxLoading.style.display = 'none';
if (emails.length === 0) {
elements.inboxEmpty.style.display = 'block';
return;
}
elements.inboxTbody.innerHTML = emails.map(m => {
const dataAttr = escapeHtml(JSON.stringify(m));
return `<tr style="cursor:pointer;" onclick="showEmailDetail(JSON.parse(this.dataset.mail))" data-mail="${dataAttr}">
<td style="text-align:center;">${m.is_read ? '' : '<span style="color:var(--primary);font-size:10px;">●</span>'}</td>
<td style="max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${escapeHtml(m.subject)}">${escapeHtml(m.subject) || '(无主题)'}</td>
<td style="max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${escapeHtml(m.sender)}">${escapeHtml(m.sender)}</td>
<td>${format.date(m.received_at)}</td>
</tr>`;
}).join('');
elements.inboxTable.style.display = 'table';
} catch (e) {
elements.inboxLoading.style.display = 'none';
elements.inboxEmpty.style.display = 'block';
elements.inboxEmpty.textContent = '加载失败:' + (e.message || '未知错误');
console.error('加载收件箱失败:', e);
}
}
function showEmailDetail(mail) {
elements.emailDetailSubject.textContent = mail.subject || '(无主题)';
elements.emailDetailSender.textContent = mail.sender || '';
elements.emailDetailDate.textContent = format.date(mail.received_at);
elements.emailDetailBody.textContent = mail.body || mail.body_preview || '(无正文)';
elements.emailDetailModal.classList.add('active');
}