From 1332f25326b1bbfee44e5843120c24a9207236c5 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Thu, 13 Aug 2026 01:00:43 +0800 Subject: [PATCH] =?UTF-8?q?feat(mail):=20=E5=A4=9A=E9=82=AE=E7=AE=B1?= =?UTF-8?q?=E9=80=89=E6=8B=A9=E7=99=BB=E5=BD=95=E4=B8=8E=E7=94=B3=E8=AF=B7?= =?UTF-8?q?=EF=BC=8C=E5=B1=95=E7=A4=BA=E5=B7=B2=E7=94=B3=E8=AF=B7=E6=95=B0?= =?UTF-8?q?=E9=87=8F=EF=BC=88N/5=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/mail.ts | 13 ++++---- src/views/mail/Mailbox.vue | 63 +++++++++++++++++++++++++++++--------- 2 files changed, 55 insertions(+), 21 deletions(-) diff --git a/src/api/mail.ts b/src/api/mail.ts index 51f4c85..8f795c3 100644 --- a/src/api/mail.ts +++ b/src/api/mail.ts @@ -17,7 +17,7 @@ export interface MailSessionConfig { /** 我的邮箱接口响应 */ export interface MyMailboxResult { - Mailbox: MailboxInfo | null + MailboxList: MailboxInfo[] Session: MailSessionConfig } @@ -66,12 +66,13 @@ export const mailApi = { apply: (data: { MailboxName: string; Password: string; QuotaMB?: number }): Promise<{ MailboxName: string; QuotaMB: number }> => post<{ MailboxName: string; QuotaMB: number }>('/API/Mail/Apply', data), - /** 修改邮箱密码(需先验证当前密码) */ - changePassword: (OldPassword: string, NewPassword: string): Promise => - post('/API/Mail/ChangePassword', { OldPassword, NewPassword }), + /** 修改邮箱密码(需先验证当前密码;MailboxName 可选,多邮箱时指定,缺省取第一个) */ + changePassword: (OldPassword: string, NewPassword: string, MailboxName?: string): Promise => + post('/API/Mail/ChangePassword', { OldPassword, NewPassword, ...(MailboxName ? { MailboxName } : {}) }), - /** 邮箱登录:校验密码并换取会话 token(密码仅此一次传输,后端存内存;401 为密码错误,不触发系统登出) */ - login: (Password: string): Promise => post('/API/Mail/Login', { Password }, { silent: true }), + /** 邮箱登录:校验密码并换取会话 token(密码仅此一次传输,后端存内存;401 为密码错误,不触发系统登出;MailboxName 可选,多邮箱时指定,缺省取第一个) */ + login: (Password: string, MailboxName?: string): Promise => + post('/API/Mail/Login', { Password, ...(MailboxName ? { MailboxName } : {}) }, { silent: true }), /** 邮箱登出:吊销会话 token */ logout: (Token: string): Promise => post('/API/Mail/Logout', { Token }), diff --git a/src/views/mail/Mailbox.vue b/src/views/mail/Mailbox.vue index d47843c..726f859 100644 --- a/src/views/mail/Mailbox.vue +++ b/src/views/mail/Mailbox.vue @@ -26,7 +26,8 @@ import { JmapClient, JmapAuthError, type JmapMailbox, type JmapEmail } from '@/u // === 状态 === const loading = ref(true) -const mailbox = ref(null) +const myMailboxes = ref([]) +const selectedMailbox = ref(null) const session = ref(null) // 申请表单 @@ -71,7 +72,9 @@ const contactSaving = ref(false) const changePwdVisible = ref(false) const changePwdForm = ref({ OldPassword: '', NewPassword: '', ConfirmPassword: '' }) -const myAddress = computed(() => mailbox.value?.MailboxName || '') +const myAddress = computed(() => selectedMailbox.value?.MailboxName || '') +const mailboxCount = computed(() => myMailboxes.value.length) +const atMailboxLimit = computed(() => myMailboxes.value.length >= 5) // === 本地已读覆盖 === // Stalwart v0.11 存在 JMAP keywords 写入已知 bug(GitHub #1139):setSeen 返回成功但已读随机回退未读, @@ -107,7 +110,7 @@ const MAIL_NAME_KEY = 'gerp_mail_name' function saveMailSession(token: string): void { localStorage.setItem(MAIL_TOKEN_KEY, token) - localStorage.setItem(MAIL_NAME_KEY, mailbox.value?.MailboxName || '') + localStorage.setItem(MAIL_NAME_KEY, selectedMailbox.value?.MailboxName || '') } function clearMailSession(): void { @@ -129,10 +132,10 @@ function handleMailSessionExpired(): void { /** 恢复上次邮箱会话:localStorage 中的 token 直接连接 JMAP,失效则自动清除 */ async function restoreSession(): Promise { - if (!mailbox.value || client.value) return + if (!selectedMailbox.value || client.value) return const token = localStorage.getItem(MAIL_TOKEN_KEY) const name = localStorage.getItem(MAIL_NAME_KEY) - if (!token || name !== mailbox.value.MailboxName) return + if (!token || name !== selectedMailbox.value.MailboxName) return restoring.value = true try { const c = new JmapClient(name, token, handleMailSessionExpired) @@ -151,7 +154,11 @@ async function restoreSession(): Promise { async function init(): Promise { try { const result = await mailApi.myMailbox() - mailbox.value = result.Mailbox + myMailboxes.value = result.MailboxList || [] + // 保留当前选中邮箱;无选中或已不在列表则取第一个启用邮箱 + if (!selectedMailbox.value || !myMailboxes.value.some((m) => m.MailboxName === selectedMailbox.value?.MailboxName)) { + selectedMailbox.value = myMailboxes.value.find((m) => m.Status === 1) || myMailboxes.value[0] || null + } session.value = result.Session } catch { // 拦截器已提示 @@ -194,7 +201,7 @@ async function submitApply(): Promise { // === 登录 JMAP === async function submitLogin(): Promise { - if (!mailbox.value || !session.value) return + if (!selectedMailbox.value || !session.value) return if (!loginForm.value.Password) { ElMessage.warning('请输入邮箱密码') return @@ -202,7 +209,7 @@ async function submitLogin(): Promise { connecting.value = true try { // 后端校验密码并签发会话 token(密码仅此一次传输,后端存内存;后续 JMAP 调用只带 token) - const result = await mailApi.login(loginForm.value.Password) + const result = await mailApi.login(loginForm.value.Password, selectedMailbox.value.MailboxName) const c = new JmapClient(result.MailboxName, result.Token, handleMailSessionExpired) await c.discover() client.value = c @@ -617,7 +624,7 @@ async function submitChangePassword(): Promise { return } try { - await mailApi.changePassword(f.OldPassword, f.NewPassword) + await mailApi.changePassword(f.OldPassword, f.NewPassword, selectedMailbox.value?.MailboxName) ElMessage.success('密码修改成功,请重新登录邮箱') changePwdVisible.value = false changePwdForm.value = { OldPassword: '', NewPassword: '', ConfirmPassword: '' } @@ -704,12 +711,12 @@ function isSelected(id: string): boolean {
- + 申请邮箱 -