修复收藏/聊天记录检索的请求竞态、下载页按钮占一半宽度、两个类名零CSS覆盖
- Favorites/History 组件的 load() 原来没有过期响应保护:切换类型/输入搜索词都会触发新请求, 但如果一个更早发出的、范围更宽的请求(比如"全部")比后发的窄范围搜索请求更晚返回,会无条件 覆盖掉已经正确显示的搜索结果——实测复现过"搜索框显示查询词,列表却是全量未过滤结果"。改成 每次调用 load() 时认领一个自增计数器,响应到达时如果计数器已经被更新的调用推进过,直接丢弃 这个过期响应。History组件(按topic/tab/query切换)是同一个模式,一并修复。 - `.library-download-actions`(下载页loading/done两态的按钮容器)是2列等宽grid,但两个状态 永远只渲染1个按钮——按钮固定占左侧50%宽度,右侧约一半容器宽度是死空白。改成单列。 - `.library-empty-hint`(收藏/聊天记录/下载页的加载中提示文字)和`.library-load-more` ("加载更多"按钮)在base.css里完全没有对应规则,退化成浏览器默认段落间距/按钮外观—— 补了基础样式,跟第八轮修复的.library-link-text/.library-record-header是同一类遗漏。 均已重新构建并实测验证(下载按钮宽度、空状态文字样式、加载更多按钮样式均用真实DOM测量确认; 请求竞态修复通过真实收藏页搜索/清空往返验证无回归,竞态本身的精确时序难以在测试环境里稳定 复现,但修复采用的是标准、成熟的自增计数器丢弃过期响应模式)。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
38b43936ce
commit
ee2cf6f965
+28
-1
@@ -10902,6 +10902,30 @@ div.toast.show {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
/* Favorites/History/DownloadPage's loading/error placeholder text and Favorites/History's "加载
|
||||
更多" pagination button -- both had zero rules anywhere in this file, falling back to a bare
|
||||
<p>'s default margins and a plain <button>'s default outline respectively. */
|
||||
.library-empty-hint {
|
||||
padding: 1.5rem 1rem;
|
||||
color: #9299a2;
|
||||
font-size: 0.85rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.library-load-more {
|
||||
display: block;
|
||||
width: min(100%, 52rem);
|
||||
margin: 0 auto;
|
||||
padding: 0.8rem;
|
||||
border: 1px solid #eceef1;
|
||||
border-radius: 0.4rem;
|
||||
box-shadow: none;
|
||||
color: #567;
|
||||
background: #fff;
|
||||
font-size: 0.85rem;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.library-favorite-item {
|
||||
display: flex;
|
||||
gap: 0.7rem;
|
||||
@@ -11482,9 +11506,12 @@ div.toast.show {
|
||||
color: #9299a2;
|
||||
}
|
||||
|
||||
/* DownloadPage's loading ("取消") and done ("用其他应用打开") states each render exactly one button
|
||||
here -- never two side by side -- so the 2nd 1fr column sat permanently empty, pinning the
|
||||
button to the left half with the right half dead space. */
|
||||
.library-download-actions {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0.8rem;
|
||||
}
|
||||
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -220,8 +220,16 @@ function Favorites({view, tinode, onToast}) {
|
||||
const [hasMore, setHasMore] = React.useState(false);
|
||||
const [fileUrls, setFileUrls] = React.useState({});
|
||||
const [sourceNames, setSourceNames] = React.useState({});
|
||||
// An in-flight request isn't guaranteed to resolve before a newer one its own type/query change
|
||||
// triggered -- without this, a slow full-list request landing after a fast, narrower search
|
||||
// response overwrites the correct filtered results with the stale unfiltered ones (confirmed
|
||||
// live: search box showing a query, list showing all items). Each load() call claims the counter
|
||||
// for itself; a response only applies if nothing newer has been requested since.
|
||||
const requestIdRef = React.useRef(0);
|
||||
const load = React.useCallback((append) => {
|
||||
const requestId = ++requestIdRef.current;
|
||||
getFavorites({type, query, cursor: append ? cursor : ''}).then(data => {
|
||||
if (requestIdRef.current !== requestId) return;
|
||||
setItems(prev => {
|
||||
const list = append ? (prev || []).concat(data.items || []) : (data.items || []);
|
||||
if (!query) writeCachedData('favorites:' + type, list);
|
||||
@@ -523,12 +531,18 @@ function History({view, hashParams, myUserId, onToast}) {
|
||||
const [hasMore, setHasMore] = React.useState(false);
|
||||
const [mediaUrls, setMediaUrls] = React.useState({});
|
||||
const [senderNames, setSenderNames] = React.useState({});
|
||||
// Same stale-response guard as Favorites' load() above and for the same reason -- a slower
|
||||
// request for a since-abandoned topic/tab/query can still resolve after a newer one and
|
||||
// overwrite its correct results.
|
||||
const requestIdRef = React.useRef(0);
|
||||
const load = React.useCallback((append) => {
|
||||
if (!topic) return;
|
||||
const requestId = ++requestIdRef.current;
|
||||
const type = HISTORY_TYPE[view];
|
||||
const call = view === 'history-search' ? searchTopicMessages(topic, {query, type, cursor: append ? cursor : ''}) :
|
||||
getTopicMedia(topic, type, append ? cursor : '');
|
||||
call.then(data => {
|
||||
if (requestIdRef.current !== requestId) return;
|
||||
setItems(prev => {
|
||||
const list = append ? (prev || []).concat(data.items || []) : (data.items || []);
|
||||
if (!query && cacheKey) writeCachedData(cacheKey, list);
|
||||
|
||||
@@ -9263,12 +9263,15 @@ function Favorites({
|
||||
const [hasMore, setHasMore] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(false);
|
||||
const [fileUrls, setFileUrls] = react__WEBPACK_IMPORTED_MODULE_0___default().useState({});
|
||||
const [sourceNames, setSourceNames] = react__WEBPACK_IMPORTED_MODULE_0___default().useState({});
|
||||
const requestIdRef = react__WEBPACK_IMPORTED_MODULE_0___default().useRef(0);
|
||||
const load = react__WEBPACK_IMPORTED_MODULE_0___default().useCallback(append => {
|
||||
const requestId = ++requestIdRef.current;
|
||||
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_5__.getFavorites)({
|
||||
type,
|
||||
query,
|
||||
cursor: append ? cursor : ''
|
||||
}).then(data => {
|
||||
if (requestIdRef.current !== requestId) return;
|
||||
setItems(prev => {
|
||||
const list = append ? (prev || []).concat(data.items || []) : data.items || [];
|
||||
if (!query) (0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_6__.writeCachedData)('favorites:' + type, list);
|
||||
@@ -9762,8 +9765,10 @@ function History({
|
||||
const [hasMore, setHasMore] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(false);
|
||||
const [mediaUrls, setMediaUrls] = react__WEBPACK_IMPORTED_MODULE_0___default().useState({});
|
||||
const [senderNames, setSenderNames] = react__WEBPACK_IMPORTED_MODULE_0___default().useState({});
|
||||
const requestIdRef = react__WEBPACK_IMPORTED_MODULE_0___default().useRef(0);
|
||||
const load = react__WEBPACK_IMPORTED_MODULE_0___default().useCallback(append => {
|
||||
if (!topic) return;
|
||||
const requestId = ++requestIdRef.current;
|
||||
const type = HISTORY_TYPE[view];
|
||||
const call = view === 'history-search' ? (0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_5__.searchTopicMessages)(topic, {
|
||||
query,
|
||||
@@ -9771,6 +9776,7 @@ function History({
|
||||
cursor: append ? cursor : ''
|
||||
}) : (0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_5__.getTopicMedia)(topic, type, append ? cursor : '');
|
||||
call.then(data => {
|
||||
if (requestIdRef.current !== requestId) return;
|
||||
setItems(prev => {
|
||||
const list = append ? (prev || []).concat(data.items || []) : data.items || [];
|
||||
if (!query && cacheKey) (0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_6__.writeCachedData)(cacheKey, list);
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user