第五轮深度测试:管理员撤回、所有人@提醒、失效附件UI,并核对现存后端问题清单
- messages-view.jsx: 拖拽发送/回复菜单在编辑态下的旁路修复;群主/管理员可撤回他人消息 (对齐后端 authorizeIMMessageMutation 的 allowAdmin);新增"所有人"@提醒(head.mention_all)。 - chat-message.jsx: system_action 头类型消息按 system_notice 同款渲染(此前显示"内容无效")。 - content-library-view.jsx: 语音/链接/图文收藏与聊天记录卡片补上 removed(已失效)态UI, 之前只有 FileCard 处理了这个状态。 - call-panel.jsx: handleVideoOfferMsg(5步.then链,用reject sentinel跳过后续步骤)、 handleToggleSpeakerClick 补组件卸载守卫,修完此前遗漏的两处。 - group-management-view.jsx: 治理开关(Governance.setField)和群主转让(OwnerSelect) 补上乐观锁 version 参数(此前从不传,服务端 version<=0 时会跳过冲突检测);群文件搜索框 文案承诺"搜索文件名或者上传者"但服务端q参数只匹配文件名,改为拉取全量列表本地按文件名+ 已解析的上传者昵称一起过滤;群公告详情页补上从未被调用过的已读上报(readGroupAnnouncement)。 以上均用真实账号在浏览器/直接请求验证:管理员撤回、system_action渲染、mention_all、 文件按上传者搜索、公告已读上报(200)、治理开关stale-version探测确认409冲突检测生效。 7个并行测试agent对全部11章做了第二轮回归+现存后端问题清单逐条复核,结论待整理进 docs/IM-后端问题清单*.md(全部现存条目复核后均建议保留,多条证据强度从推断升级为已验证)。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
4a30cfa6b3
commit
d42591284f
+71
-18
@@ -9032,13 +9032,24 @@ function FileCard({
|
||||
}
|
||||
function VoiceCard({
|
||||
att,
|
||||
head
|
||||
head,
|
||||
removed
|
||||
}) {
|
||||
const [src, setSrc] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(null);
|
||||
react__WEBPACK_IMPORTED_MODULE_0___default().useEffect(() => {
|
||||
resolveAttachmentUrl(att).then(setSrc);
|
||||
}, [att]);
|
||||
if (!removed) resolveAttachmentUrl(att).then(setSrc);
|
||||
}, [att, removed]);
|
||||
const duration = att && att.duration || head && head.duration || 0;
|
||||
if (removed) {
|
||||
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)("div", {
|
||||
className: "library-voice-card removed",
|
||||
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)(Icon, {
|
||||
children: "block"
|
||||
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)("em", {
|
||||
children: "语音已失效"
|
||||
}, void 0, false)]
|
||||
}, void 0, true);
|
||||
}
|
||||
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)("div", {
|
||||
className: "library-voice-card",
|
||||
children: src ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)(_widgets_audio_player_jsx__WEBPACK_IMPORTED_MODULE_2__["default"], {
|
||||
@@ -9053,8 +9064,19 @@ function VoiceCard({
|
||||
}
|
||||
function LinkText({
|
||||
url,
|
||||
onOpen
|
||||
onOpen,
|
||||
removed
|
||||
}) {
|
||||
if (removed) {
|
||||
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)("div", {
|
||||
className: "library-link-text removed",
|
||||
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)(Icon, {
|
||||
children: "link_off"
|
||||
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)("em", {
|
||||
children: "链接已失效"
|
||||
}, void 0, false)]
|
||||
}, void 0, true);
|
||||
}
|
||||
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)("button", {
|
||||
type: "button",
|
||||
className: "library-link-text",
|
||||
@@ -9070,8 +9092,19 @@ function MediaThumb({
|
||||
url,
|
||||
video,
|
||||
duration,
|
||||
onOpen
|
||||
onOpen,
|
||||
removed
|
||||
}) {
|
||||
if (removed) {
|
||||
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)("div", {
|
||||
className: "library-media-thumb removed",
|
||||
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)(Icon, {
|
||||
children: video ? 'videocam_off' : 'broken_image'
|
||||
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)("em", {
|
||||
children: video ? '视频已失效' : '图片已失效'
|
||||
}, void 0, false)]
|
||||
}, void 0, true);
|
||||
}
|
||||
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)("button", {
|
||||
type: "button",
|
||||
className: "library-media-thumb",
|
||||
@@ -9122,14 +9155,17 @@ function FavoriteItem({
|
||||
onOpen: onOpen
|
||||
}, void 0, false) : null, item.type === 'voice' ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)(VoiceCard, {
|
||||
att: att,
|
||||
head: item.snapshot && item.snapshot.head
|
||||
head: item.snapshot && item.snapshot.head,
|
||||
removed: item.availability === 'removed'
|
||||
}, void 0, false) : null, item.type === 'link' ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)(LinkText, {
|
||||
url: item.snapshot && item.snapshot.content && item.snapshot.content.url || plainTextOf(item.snapshot && item.snapshot.content, item.snapshot && item.snapshot.text),
|
||||
onOpen: onOpen
|
||||
onOpen: onOpen,
|
||||
removed: item.availability === 'removed'
|
||||
}, void 0, false) : null, item.type === 'image' || item.type === 'video' ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)(MediaThumb, {
|
||||
url: item._thumbUrl,
|
||||
video: item.type === 'video',
|
||||
onOpen: onOpen
|
||||
onOpen: onOpen,
|
||||
removed: item.availability === 'removed'
|
||||
}, void 0, false) : null, (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)("footer", {
|
||||
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)("span", {
|
||||
children: ["来自 ", source]
|
||||
@@ -9802,7 +9838,8 @@ function History({
|
||||
fmt: item.fmt,
|
||||
ent: item.ent
|
||||
} : undefined, item.text),
|
||||
onOpen: openLink
|
||||
onOpen: openLink,
|
||||
removed: item.availability === 'removed'
|
||||
}, void 0, false)]
|
||||
}, item.seq, true))
|
||||
}, void 0, false) : (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)("section", {
|
||||
@@ -9811,7 +9848,8 @@ function History({
|
||||
url: mediaUrls[item.seq],
|
||||
video: view === 'history-videos',
|
||||
duration: item.duration,
|
||||
onOpen: () => jump(item.seq)
|
||||
onOpen: () => jump(item.seq),
|
||||
removed: item.availability === 'removed'
|
||||
}, item.seq, false))
|
||||
}, void 0, false), hasMore ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_8__.jsxDEV)("button", {
|
||||
type: "button",
|
||||
@@ -11257,6 +11295,13 @@ function Announcement({
|
||||
onToast('群公告已发布');
|
||||
}).catch(err => onToast(err.message || '发布失败')).finally(() => setSaving(false));
|
||||
};
|
||||
const readReportedFor = react__WEBPACK_IMPORTED_MODULE_0___default().useRef(null);
|
||||
react__WEBPACK_IMPORTED_MODULE_0___default().useEffect(() => {
|
||||
const id = data && data.announcement_id;
|
||||
if (!id || !topic || readReportedFor.current === id) return;
|
||||
readReportedFor.current = id;
|
||||
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.readGroupAnnouncement)(topic, id).catch(() => {});
|
||||
}, [topic, data]);
|
||||
if (!data) return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("div", {
|
||||
className: "group-page",
|
||||
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)(Header, {
|
||||
@@ -11801,7 +11846,8 @@ function Governance({
|
||||
}, [topic]);
|
||||
const setField = (key, value) => {
|
||||
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.updateGroupGovernance)(topic, {
|
||||
[key]: value
|
||||
[key]: value,
|
||||
version: gov ? gov.version : 0
|
||||
}).then(data => {
|
||||
(0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_5__.writeCachedData)('group-governance:' + topic, data);
|
||||
setGov(data);
|
||||
@@ -12214,7 +12260,7 @@ function OwnerSelect({
|
||||
}
|
||||
if (busy || !target) return;
|
||||
setBusy(true);
|
||||
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.transferGroupOwner)(topic, target.user_id).then(() => {
|
||||
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.getGroup)(topic).then(group => (0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.transferGroupOwner)(topic, target.user_id, group.version)).then(() => {
|
||||
onToast('群主已转让');
|
||||
navigate('group-governance', topic);
|
||||
}).catch(err => onToast(err.message || '转让失败')).finally(() => setBusy(false));
|
||||
@@ -12334,17 +12380,15 @@ function GroupFiles({
|
||||
};
|
||||
const load = react__WEBPACK_IMPORTED_MODULE_0___default().useCallback(() => {
|
||||
if (!topic) return;
|
||||
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.getGroupFiles)(topic, {
|
||||
query
|
||||
}).then(data => {
|
||||
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.getGroupFiles)(topic, {}).then(data => {
|
||||
const items = data.items || [];
|
||||
if (!query) (0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_5__.writeCachedData)('group-files:' + topic, items);
|
||||
(0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_5__.writeCachedData)('group-files:' + topic, items);
|
||||
setFiles(items);
|
||||
}).catch(() => {
|
||||
onToast('加载失败');
|
||||
setLoadError(true);
|
||||
});
|
||||
}, [topic, query]);
|
||||
}, [topic]);
|
||||
react__WEBPACK_IMPORTED_MODULE_0___default().useEffect(() => {
|
||||
load();
|
||||
}, [load]);
|
||||
@@ -12358,6 +12402,12 @@ function GroupFiles({
|
||||
setUploaderNames(map);
|
||||
}).catch(() => {});
|
||||
}, [topic]);
|
||||
const visibleFiles = react__WEBPACK_IMPORTED_MODULE_0___default().useMemo(() => {
|
||||
const all = files || [];
|
||||
const q = query.trim().toLowerCase();
|
||||
if (!q) return all;
|
||||
return all.filter(f => (f.file_name || '').toLowerCase().includes(q) || (uploaderNames[f.uploader_id] || '').toLowerCase().includes(q));
|
||||
}, [files, query, uploaderNames]);
|
||||
const usedBytes = (files || []).reduce((sum, f) => sum + (f.file_size || 0), 0);
|
||||
const pick = kind => {
|
||||
setMenuOpen(false);
|
||||
@@ -12479,7 +12529,10 @@ function GroupFiles({
|
||||
}, void 0, true) : null, files === null ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("p", {
|
||||
className: "group-inline-empty",
|
||||
children: loadError ? '文件列表加载失败' : '加载中…'
|
||||
}, void 0, false) : files.map(file => {
|
||||
}, void 0, false) : visibleFiles.length === 0 && query.trim() ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("p", {
|
||||
className: "group-inline-empty",
|
||||
children: "未找到匹配的文件"
|
||||
}, void 0, false) : visibleFiles.map(file => {
|
||||
const [icon, color] = FILE_TYPE_META[file.file_type] || FILE_TYPE_META.file;
|
||||
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("article", {
|
||||
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("span", {
|
||||
|
||||
Reference in New Issue
Block a user