修复全页面走查发现的问题(第四批:精准查找、隐私锁定、搜索防抖)

精准查找搜不到手机号(第5章 5.5)
- 一直写死 type=exact_account,而该分支只比对 profile.public_account / u.username / u.id
  (tenant_adapter.go:9170-9172),**不匹配 u.phone**。参考图 5.5 里输的恰恰是手机号,
  这个场景必然搜不到。改为按输入形态分流:11 位 1 开头走 type=phone。
- phone 分支服务端不做 E.164 归一化(只有模糊的 account 分支才调 normalizeUserPhone),
  库里存的是 +86 开头,所以裸号码由前端补区号——注册流程本来也是这么拼的。
- 已用真实后端验证请求正确路由为 q=%2B8613800001188&type=phone。

隐私设置忽略 editable(第10章 10.12/10.13)
- 后端 userPrivacyPayload 返回 editable{add_me_methods, profile_visibility,
  last_seen_visibility, read_receipts} 表达"企业强制策略锁死了这一项",前端完全没读。
  被锁的开关照样能点,点了 400 再回滚,用户只看到开关自己弹回去。现在禁用并给出说明文案,
  同时补 .personal-switch:disabled 的视觉反馈(否则看不出为什么点不动)。

搜索防抖
- 新增共享的 useDebouncedValue,用在三处真正发服务端请求的搜索:收藏、会话内检索、
  群列表、群文件。原来 query 直接进 effect 依赖,每敲一个字符发一次请求;群列表更糟,
  reload 对三个分类各发一次,一个字符就是 3 个请求。而且返回顺序没有保证,后发的短前缀
  请求可能后到、把正确结果覆盖掉。
- 实测:群列表输入 4 个字,请求数从 12 降到 3。
- FavoriteShare 那处搜索是本地过滤 chatList、不发请求,没有动。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-09-03 18:56:41 +08:00
co-authored by Claude Opus 5
parent c9b8125013
commit 35d3e1cf6b
8 changed files with 144 additions and 39 deletions
+10 -5
View File
@@ -11,7 +11,7 @@ import {
deleteGroupAdmin, muteGroupMembers, getGroupMutes, unmuteGroupMember, transferGroupOwner,
getGroupFiles, addGroupFile, deleteGroupFile, uploadLargeFile, getFileUrl, getUserProfile
} from '../lib/im-api.js';
import { readCachedData, writeCachedData } from '../lib/use-cached-data.js';
import { readCachedData, writeCachedData, useDebouncedValue } from '../lib/use-cached-data.js';
// Carries the group `topic` (and anything else already in the hash) across navigation, the same way
// contact-management-view.jsx threads `uid` -- these pages all need to know *which* group.
@@ -163,7 +163,10 @@ function GroupList({tinode, onToast}) {
const [lists, setLists] = React.useState(() => readCachedData('groups') || {owned: null, managed: null, joined: null});
const [loadErrors, setLoadErrors] = React.useState({owned: false, managed: false, joined: false});
const [category, setCategory] = React.useState('owned');
const [query, setQuery] = React.useState('');
// 这里比别处更值得防抖:reload 对 owned/managed/joined 三个分类各发一次请求,
// 每敲一个字符就是 3 个 getGroups。
const [queryInput, setQueryInput] = React.useState('');
const query = useDebouncedValue(queryInput, 300);
const [creating, setCreating] = React.useState(false);
const [name, setName] = React.useState('');
const [submitting, setSubmitting] = React.useState(false);
@@ -190,7 +193,7 @@ function GroupList({tinode, onToast}) {
};
return <div className="group-page">
<Header title="群聊" back="address-book" right="创建" onRight={() => setCreating(true)} />
<Search value={query} onChange={setQuery} placeholder="搜索群聊" />
<Search value={queryInput} onChange={setQueryInput} placeholder="搜索群聊" />
<nav className="group-tabs">
{[['owned', '我创建的'], ['managed', '我管理的'], ['joined', '我加入的']].map(([key, label]) =>
<button type="button" key={key} className={category === key ? 'active' : ''} onClick={() => setCategory(key)}>
@@ -847,7 +850,9 @@ const FILE_TYPE_META = {
};
function GroupFiles({topic, tinode, onToast}) {
const [query, setQuery] = React.useState('');
// 群文件搜索走服务端 getGroupFiles({query}),同样防抖。
const [queryInput, setQueryInput] = React.useState('');
const query = useDebouncedValue(queryInput, 300);
// Only the unfiltered (query === '') result is cached -- same rule GroupList already follows, so a
// plain revisit never shows a stale search result under a fresh empty query.
const [files, setFiles] = React.useState(() => (topic && readCachedData('group-files:' + topic)) || null);
@@ -902,7 +907,7 @@ function GroupFiles({topic, tinode, onToast}) {
<button type="button" onClick={() => pick('image')}><Icon>image</Icon>上传照片</button>
<button type="button" onClick={() => pick('video')}><Icon>play_circle_outline</Icon>上传视频</button>
</div> : null}
<Search value={query} onChange={setQuery} placeholder="搜索文件名或者上传者" />
<Search value={queryInput} onChange={setQueryInput} placeholder="搜索文件名或者上传者" />
<p className="group-inline-hint">共{(files || []).length}个文件(已使用{gb(usedBytes)}GB/10GB)</p>
<main className="group-file-list">
{uploading ? <article className="uploading">