修复收藏/系统消息/通话记录批次的代码审查问题

- 通话结果"已拒绝"文案按视角区分(拒绝方看到"已拒绝"而非"对方已拒绝")
- 通话记录页补充移动端返回头部与加载失败重试态
- 挂断时按是否已接通传递正确的挂断原因(cancelled)
- 系统消息"全部标为已读"翻页加循环护栏,防止游标卡死;按钮可见性改用真实未读总数
- 收藏批量删除/转发在部分失败时保留失败项以便重试,而非无条件退出多选模式
- 收藏卡片边框/分类文字/侧栏图标颜色token修正
This commit is contained in:
Your Name
2026-09-16 12:54:40 +08:00
parent 97e8d7144f
commit 29a1f1c2fd
5 changed files with 67 additions and 14 deletions
+18 -7
View File
@@ -193,7 +193,7 @@ function FavoriteCard({ item, selectMode, selected, onToggle }: { item: Favorite
{body}
{selectMode ? <SelectionCheckbox selected={selected} className="absolute -right-1 -top-1" /> : null}
</div>
<div className="flex items-center justify-between border-t border-border-list-item pt-2.5 text-xs text-text-tertiary-3">
<div className="flex items-center justify-between border-t border-border-row pt-2.5 text-xs text-text-tertiary-3">
<SourceLine source={item.source} />
<span className="flex-none">{formatEventTimestamp(new Date(item.collected_at))}</span>
</div>
@@ -271,8 +271,16 @@ export function FavoritesLayout() {
},
onSuccess: (data) => {
invalidateAll();
exitSelectMode();
if (data?.failed?.length) setError(`${data.failed.length} 项删除失败,请重试`);
// Exiting select mode unconditionally used to clear `selected` even on a PARTIAL failure --
// the error toast named a count ("N项删除失败") but gave no way to act on it, so retrying
// meant re-selecting from scratch by memory. Only actually-failed ids stay selected instead,
// primed for an immediate retry.
if (data?.failed?.length) {
setSelected(new Set(data.failed.map((f) => f.favorite_id)));
setError(`${data.failed.length} 项删除失败,请重试`);
} else {
exitSelectMode();
}
},
onError: (err) => setError(err instanceof Error ? err.message : '删除失败'),
});
@@ -284,10 +292,13 @@ export function FavoritesLayout() {
forwardFavorites([...selected], targetTopics, comment),
onSuccess: (data) => {
setForwarding(false);
exitSelectMode();
// Same `failed[]` gap as deleteMutation above (favorites.ts:101) -- forwarding to 3 targets can
// succeed for 2 and fail for 1, and silently reporting "success" either way hides that.
// succeed for 2 and fail for 1, and silently reporting "success" either way (including
// clearing the favorites selection) hid that AND left nothing to retry from. The picker
// itself still closes (which of several targets failed isn't tracked here) but the source
// favorites stay selected so re-opening "转发" immediately can retry against the same set.
if (data.failed?.length) setError(`${data.failed.length} 项转发失败,请重试`);
else exitSelectMode();
},
onError: (err) => setError(err instanceof Error ? err.message : '转发失败'),
});
@@ -335,7 +346,7 @@ export function FavoritesLayout() {
type="button"
onClick={() => switchCategory(c.key)}
className={`flex-none whitespace-nowrap rounded-2xl px-3.5 py-1.5 text-[13px] ${
category === c.key ? 'bg-accent text-white' : 'bg-[#f1f2f4] text-text-body'
category === c.key ? 'bg-accent text-white' : 'bg-[#f1f2f4] text-text-tertiary-1'
}`}
>
{c.label}
@@ -379,7 +390,7 @@ export function FavoritesLayout() {
category === c.key ? 'bg-accent-selected-bg text-accent' : 'text-text-body hover:bg-[#f6f8fb]'
}`}
>
<Icon name={c.icon} className="text-[18px]" />
<Icon name={c.icon} className={`text-[18px] ${category === c.key ? '' : 'text-text-tertiary-4'}`} />
<span className="flex-1 text-left">{c.label}</span>
</button>
))}