diff --git a/client/electron/src/components/MailView.tsx b/client/electron/src/components/MailView.tsx index 0bd3163..50e19bc 100644 --- a/client/electron/src/components/MailView.tsx +++ b/client/electron/src/components/MailView.tsx @@ -17,6 +17,7 @@ import type { Mail } from '../types'; import { MailIcon, ShieldIcon, PersonIcon, BotIcon, ForwardIcon, TreeIcon, TagIcon, GaugeIcon, ChevronRightIcon, ChatBubbleIcon } from './icons'; import { Composer, ComposerChip } from './Composer'; import { useIsNarrow } from '../hooks/useIsNarrow'; +import { useUIStore } from '../stores/uiStore'; import AddressInput from './AddressInput'; import { AttachmentList, AttachmentPicker, type PendingAttachment } from './Attachments'; import ThreadView from './ThreadView'; @@ -44,6 +45,24 @@ export default function MailView() { setThreadOf(null); }, [currentMailID]); + /* + * 打开即已读(2026-09-14 用户:「现在邮件需要完全手动标记是否已读而不支持点进去 + * 自动已读或者回复自动已读」)。 + * + * 两个细节: + * 1. 窄屏下 MailView **可能已经渲染但被列表盖住**(NarrowStack 的覆盖层), + * 那时用户还没看到它 ⇒ 必须等到真正可见(narrowPane === 'detail')才标, + * 否则"滑过去但没看"的邮件也会被标已读。 + * 2. 只对 unread 发请求:已读的再标一次是白跑一趟(而且会让接口日志噪音不断)。 + */ + const narrowPane = useUIStore(s => s.narrowPane); + const visible = !narrow || narrowPane === 'detail'; + const currentStatus = currentMail?.status; + useEffect(() => { + if (!visible || !currentMailID || currentStatus !== 'unread') return; + void markRead(currentMailID); + }, [visible, currentMailID, currentStatus, markRead]); + if (currentSession && currentSessionMails.length > 0) { const last = currentSessionMails[currentSessionMails.length - 1]; // 会话视图的对端是**会话的属性**,不能由「最后一封是谁发的」决定: @@ -862,6 +881,8 @@ function ReplyBar({ const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const fetchInbox = useMailStore(s => s.fetchInbox); + // 回复即已读要用 + const markRead = useMailStore(s => s.markRead); const fetchSent = useMailStore(s => s.fetchSent); const fetchSessions = useSessionStore(s => s.fetchSessions); const fetchContacts = useContactStore(s => s.fetchContacts); @@ -911,6 +932,9 @@ function ReplyBar({ cc: cc.trim(), attachment_ids: attachments.map(a => a.id) }); + // 回复即已读(用户要求):回了信就等于处理过这封了,不必再手动点一次。 + // 放在发送成功之后:发送失败时不该把"我处理过了"记下来。 + void markRead(replyTo.mail_id); setBody(''); setCc(''); setCcOpen(false); diff --git a/client/electron/test/components/Sidebar.test.tsx b/client/electron/test/components/Sidebar.test.tsx index 16bb77b..f1d54c5 100644 --- a/client/electron/test/components/Sidebar.test.tsx +++ b/client/electron/test/components/Sidebar.test.tsx @@ -1,4 +1,4 @@ -import { render, screen, fireEvent } from '@testing-library/react'; +import { render, fireEvent } from '@testing-library/react'; import { beforeEach, describe, expect, it } from 'vitest'; import Sidebar from '../../src/components/Sidebar'; import { useUIStore } from '../../src/stores/uiStore';