feat(web): D4 会话未读徽标 — 打开即已读 + 消费 session_read
- SessionMeta.unreadCount(可选,兼容旧后端)+ api/sessions.markSessionRead - lib/sessionRead.ts:会话历史落地后再推游标(best-effort,失败静默,下次 GET /sessions 收敛)+ 帧归属判定(ws.ts 因此留在 500 行内) - store.clearUnread 无变化时返回同一 state,避免多余重渲染;Nav 徽标沿用 Miuix token(mu-radius-pill/mu-err/mu-rise-in) - 逻辑测试 35 → 54 项,含变异校验(改成 clearUnread(activeSessionId) 即 FAIL)
This commit is contained in:
1 parent
e8eccd2bb2
commit
a828edacc5
9 files changed
+235
-11
No files matched your search
@@ -102,6 +102,7 @@ function NavContent({ onNavigate }: { onNavigate: () => void }): React.ReactNode
|
||||
active={s.id === activeSessionId}
|
||||
icon={s.type === "main" ? "chat" : "folder"}
|
||||
label={s.title ?? (s.type === "main" ? "主会话" : "次级会话")}
|
||||
badge={s.unreadCount ?? 0}
|
||||
onClick={() => selectSession(s.id)}
|
||||
/>
|
||||
))}
|
||||
@@ -190,12 +191,15 @@ function NavItem({
|
||||
onClick,
|
||||
active = false,
|
||||
hint,
|
||||
badge = 0,
|
||||
}: {
|
||||
icon: IconName;
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
active?: boolean;
|
||||
hint?: string;
|
||||
/** Unread count (协同 B3/D4); 0 or absent renders no badge. */
|
||||
badge?: number;
|
||||
}): React.ReactNode {
|
||||
return (
|
||||
<button
|
||||
@@ -208,7 +212,16 @@ function NavItem({
|
||||
>
|
||||
<Icon name={icon} size={18} />
|
||||
<span>{label}</span>
|
||||
{hint ? <span className="ml-auto text-[10px] text-[var(--mu-text-lo)]">{hint}</span> : null}
|
||||
{badge > 0 ? (
|
||||
<span
|
||||
className="mu-rise-in ml-auto inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-[var(--mu-radius-pill)] bg-[var(--mu-err)] px-1.5 text-[10px] font-medium leading-none text-white"
|
||||
aria-label={`${badge} 条未读`}
|
||||
>
|
||||
{badge > 99 ? "99+" : badge}
|
||||
</span>
|
||||
) : hint ? (
|
||||
<span className="ml-auto text-[10px] text-[var(--mu-text-lo)]">{hint}</span>
|
||||
) : null}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user