修复收藏页多选模式下类型状态丢失、跨tab选择静默累加两个问题
根因1:'favorites-select' 是全部类型 tab 共用的同一个路由,本身不带类型信息,Favorites 组件原来进入多选模式时把 type 硬编码回退成 'file',不管进入前实际停在哪个 tab。实测在 "文本"tab 点"多选",列表和头部计数会静默换成"文件"tab 的数据,用户看不出发生了什么, 勾选/删除会作用在自己根本没看到的文件项上——测试过程中因此真的误删了一条文件收藏。 修复:进入多选前把当前 type 作为 hash 参数带上,多选页/分享页的返回箭头、"取消"、发送后 跳转都改用该参数换算的 tab,不再硬编码 'favorites-files'。 根因2:切换类型 tab 只更新 type,没有清空 selected——在"文件"tab 选中项目后切到"图片"tab, 头部仍显示旧的"已选择 N 项",实际操作的是已经不在当前列表里的旧类型收藏,删除/转发没有任何 可见提示。修复:切 tab 时一并清空 selected。 验证:真实浏览器逐步复现两个问题后确认修复生效——在"文本"tab进入多选保留文本列表、 切"图片"tab选择清零、"取消"正确回到多选前所在的图片tab;多选删除功能本身回归正常。
This commit is contained in:
@@ -266,10 +266,17 @@ function FavoriteItem({item, onOpen, selecting, selected, onSelect, query, onToa
|
||||
const TYPE_ROUTE = {all: 'favorites-mixed', file: 'favorites-files', text: 'favorites-text', voice: 'favorites-voice', link: 'favorites-links', image: 'favorites-image', video: 'favorites-video'};
|
||||
const TYPE_LABELS = [['all', '全部'], ['image', '图片'], ['video', '视频'], ['file', '文件'], ['voice', '语音'], ['text', '文本'], ['link', '链接']];
|
||||
|
||||
function Favorites({view, tinode, onToast}) {
|
||||
function Favorites({view, hashParams, tinode, onToast}) {
|
||||
const initialType = view === 'favorites-files' ? 'file' : view === 'favorites-text' ? 'text' : view === 'favorites-voice' ? 'voice' : view === 'favorites-links' ? 'link' : view === 'favorites-image' ? 'image' : view === 'favorites-video' ? 'video' : 'all';
|
||||
const selecting = view === 'favorites-select';
|
||||
const [type, setType] = React.useState(selecting ? 'file' : initialType);
|
||||
// 'favorites-select' 是所有类型 tab 共用的同一个路由,本身不带任何类型信息——原来不管进入
|
||||
// 多选前停在哪个 tab,一律硬编码回退成 'file'。实测复现:在"文本"tab 点"多选",列表和头部
|
||||
// 的"已选择 N 项"计数都静默换成了"文件"tab 的数据(tab 高亮也跟着跳到"文件"),用户在视觉上
|
||||
// 完全看不出发生了什么,勾选/删除会作用在自己压根没看到、以为还是文本收藏的文件项上——测试
|
||||
// 过程中真的因为这个把一条本不该删的文件收藏删掉了。现在"多选"按钮把当前 type 作为 hash 参数
|
||||
// 带过去,这里读回来,保留进入多选前用户实际所在的 tab;只有直接深链到 'favorites-select'
|
||||
// 且没带 type 参数时才落回原来的 'file' 默认值。
|
||||
const [type, setType] = React.useState(selecting ? ((hashParams && hashParams.type) || 'file') : initialType);
|
||||
// 输入值与实际发请求用的值分开:原来 query 直接进 load 的依赖数组,输入 6 个字的关键词就打
|
||||
// 6 次接口,而且返回顺序没有保证——后发的短前缀请求可能后到、把正确结果覆盖掉。
|
||||
const [queryInput, setQueryInput] = React.useState('');
|
||||
@@ -359,11 +366,19 @@ function Favorites({view, tinode, onToast}) {
|
||||
};
|
||||
const list = items || [];
|
||||
return <div className="library-page">
|
||||
<Header title={selecting ? `已选择 ${selected.length} 项` : '我的收藏'} back={selecting ? 'favorites-files' : 'messages-home'}
|
||||
right={selecting ? '取消' : '多选'} onRight={() => navigate(selecting ? 'favorites-files' : 'favorites-select')} />
|
||||
{/* "返回"/"取消"都要回到多选前那个类型 tab,而不是硬编码的"文件"——TYPE_ROUTE 查不到时
|
||||
(比如 type 还是初始的 'all') 才退回 'favorites-files',跟原来的兜底保持一致。 */}
|
||||
<Header title={selecting ? `已选择 ${selected.length} 项` : '我的收藏'} back={selecting ? (TYPE_ROUTE[type] || 'favorites-files') : 'messages-home'}
|
||||
right={selecting ? '取消' : '多选'} onRight={() => navigate(selecting ? (TYPE_ROUTE[type] || 'favorites-files') : 'favorites-select', selecting ? undefined : {type})} />
|
||||
<Search value={queryInput} onChange={setQueryInput} placeholder="搜索收藏内容" />
|
||||
<nav className="library-type-tabs">{TYPE_LABELS.map(([id, label]) => <button type="button" key={id} className={type === id ? 'active' : ''}
|
||||
onClick={() => { setType(id); if (TYPE_ROUTE[id] && !selecting) navigateReplace(TYPE_ROUTE[id]); }}>{label}</button>)}</nav>
|
||||
// 多选模式下切 tab 之前,selected 存的是"收藏 id 的集合",不带类型信息——原来只切 type、
|
||||
// 不清 selected,导致在"文件"tab 选中 2 项后切到"图片"tab,头部仍显示"已选择 2 项"、点
|
||||
// 删除/转发会真的作用在两个已经从当前列表消失、用户此刻根本看不到也无法勾选/取消的文件上,
|
||||
// 且没有任何提示告诉用户这两项还算在内。多选场景下天然只该管"当前这个列表里勾了什么",
|
||||
// 跨 tab 静默累加选择集不是这里的预期行为(对照参考图"收藏—文件(多选)"也只画了单一
|
||||
// 类型列表内的勾选,没有跨 tab 累加的设计)。
|
||||
onClick={() => { setType(id); setSelected([]); if (TYPE_ROUTE[id] && !selecting) navigateReplace(TYPE_ROUTE[id]); }}>{label}</button>)}</nav>
|
||||
<main className="library-favorite-list">
|
||||
{items === null ? <p className="library-empty-hint">加载中…</p> :
|
||||
list.length ? <>{list.map(item => <FavoriteItem key={item.favorite_id} item={{...item, _thumbUrl: fileUrls[item.favorite_id],
|
||||
@@ -410,7 +425,9 @@ function FavoriteShare({hashParams, myUserId, chatList, onToast}) {
|
||||
return forTarget.length > 0 && forTarget.every(r => r.status === 'sent');
|
||||
}).length;
|
||||
onToast(successTargets === selected.length ? `已分享给 ${selected.length} 个会话` : `${successTargets}/${selected.length} 个会话分享成功`);
|
||||
window.setTimeout(() => navigate('favorites-files'), 700);
|
||||
// 发完之后回到分享前那个类型 tab,而不是硬编码的"文件"——preview 是这批收藏里第一条的详情,
|
||||
// 拿它的 type 就够找回对应 tab;preview 还没加载完时退回原来的 'favorites-files' 兜底。
|
||||
window.setTimeout(() => navigate(TYPE_ROUTE[preview && preview.type] || 'favorites-files'), 700);
|
||||
}).catch(err => onToast(err.message || '分享失败')).finally(() => setSending(false));
|
||||
};
|
||||
const previewAtt = preview && firstAttachment(preview.snapshot && preview.snapshot.content);
|
||||
@@ -418,7 +435,10 @@ function FavoriteShare({hashParams, myUserId, chatList, onToast}) {
|
||||
(!query || topicDisplayName(c.name, c.public, c.name || '').toLowerCase().includes(query.toLowerCase())));
|
||||
const nameOf = uid => { const t = contacts.find(c => c.name === uid); return (t && topicDisplayName(t.name, t.public, uid)) || uid; };
|
||||
return <div className="library-page">
|
||||
<Header title="分享" back="favorites-select" />
|
||||
{/* 同上一条注释——"favorites-select" 不带类型信息,点这里的返回箭头原来会静默把多选列表
|
||||
换成硬编码的"文件"tab。preview.type 是这批收藏第一条的真实类型,带回去能让返回箭头落回
|
||||
分享前实际所在的 tab。 */}
|
||||
<Header title="分享" back="favorites-select" backParams={preview ? {type: preview.type} : undefined} />
|
||||
{!confirming ? <>
|
||||
<Search value={query} onChange={setQuery} placeholder="搜索联系人或群聊" />
|
||||
<p className="library-section-title">最近聊天</p>
|
||||
@@ -718,7 +738,7 @@ function History({view, hashParams, myUserId, onToast}) {
|
||||
|
||||
export default function ContentLibraryView({view, hashParams, tinode, myUserId, onTopicSelected, chatList}) {
|
||||
const [toast, setToast] = React.useState(''); let content;
|
||||
if (view.startsWith('favorites')) content = <Favorites view={view} tinode={tinode} onToast={setToast} />;
|
||||
if (view.startsWith('favorites')) content = <Favorites view={view} hashParams={hashParams} tinode={tinode} onToast={setToast} />;
|
||||
else if (view === 'favorite-share') content = <FavoriteShare hashParams={hashParams} myUserId={myUserId} chatList={chatList} onToast={setToast} />;
|
||||
else if (view.startsWith('file-download')) content = <DownloadPage hashParams={hashParams} tinode={tinode} onToast={setToast} />;
|
||||
else content = <History view={view} hashParams={hashParams} myUserId={myUserId} onToast={setToast} />;
|
||||
|
||||
Reference in New Issue
Block a user