第七轮深度测试:修复7个agent发现的12处前端问题,重点是列表分页截断与呼叫/播放状态机
- 通话状态机(Ch8):canStartCall 补齐通话中重复发起的防护,callShouldStart 补齐放弃/失败路径的清理 - 分页截断(Ch5-6):新增 fetchAllPages 辅助函数,修复通讯录/黑名单/好友申请/群成员/管理员/禁言等 11 处列表只读首页导致真实成员被遗漏的问题;群角色判断改用不受分页影响的 getGroup().my_role - 消息与媒体(Ch3-4/Ch7/Ch9):录音超时污染时长、手动重试丢失 head 导致消息被错误分类、批量删除 未取消上传中附件、语音播放器补齐同时只能播一条、workplace-collaboration-view 冗余重试请求 - 独立密码/青少年模式(Ch10-11):设置密码后按调用来源判断是否应自动开启青少年模式,避免关闭流程 中途"忘记密码"被误当成开启;注销账号确认文案补充群主后果说明 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
f42b8536e5
commit
bede45fd7f
@@ -10,7 +10,7 @@ import {
|
||||
updateCurrentGroupAnnouncement, readGroupAnnouncement, getGroupGovernance, updateGroupGovernance, addGroupAdmins,
|
||||
deleteGroupAdmin, muteGroupMembers, getGroupMutes, unmuteGroupMember, transferGroupOwner,
|
||||
getGroupFiles, addGroupFile, deleteGroupFile, uploadLargeFile, getFileUrl, getUserProfile,
|
||||
getGroupMemberProfile
|
||||
getGroupMemberProfile, fetchAllPages
|
||||
} from '../lib/im-api.js';
|
||||
import { readCachedData, writeCachedData, useDebouncedValue } from '../lib/use-cached-data.js';
|
||||
|
||||
@@ -706,8 +706,9 @@ function AdminList({topic, onToast}) {
|
||||
// Fetch the unfiltered member list rather than {role:'admin'} + a separate per-user profile lookup
|
||||
// for the owner -- GET /users/{id}/profile 400s ("malformed request", field "user_id") when {id} is
|
||||
// the caller's own uid, which broke this whenever the viewer is themselves the group's owner.
|
||||
getGroupMembers(topic).then(data => {
|
||||
const items = data.items || [];
|
||||
// Members cap out at 30/page against a 128-member group limit -- an admin sitting past page 1
|
||||
// used to vanish from this list entirely rather than the list just being incomplete.
|
||||
fetchAllPages(cursor => getGroupMembers(topic, {cursor})).then(items => {
|
||||
writeCachedData('group-members:' + topic, items);
|
||||
const d = deriveOwnerAndAdmins(items);
|
||||
setOwner(d.owner);
|
||||
@@ -744,8 +745,7 @@ function GroupMembers({topic, onToast}) {
|
||||
const [loadError, setLoadError] = React.useState(false);
|
||||
React.useEffect(() => {
|
||||
if (!topic) return;
|
||||
getGroupMembers(topic).then(data => {
|
||||
const items = data.items || [];
|
||||
fetchAllPages(cursor => getGroupMembers(topic, {cursor})).then(items => {
|
||||
writeCachedData('group-members:' + topic, items);
|
||||
setMembers(items);
|
||||
}).catch(() => { onToast('加载失败'); setLoadError(true); });
|
||||
@@ -781,8 +781,8 @@ function MemberPicker({type, topic, onToast}) {
|
||||
const [loadError, setLoadError] = React.useState(false);
|
||||
React.useEffect(() => {
|
||||
if (!topic) return;
|
||||
getGroupMembers(topic, mute ? {muteEligible: true} : {eligibleRole: 'admin'})
|
||||
.then(data => setMembers((data.items || []).filter(m => m.eligible !== false)))
|
||||
fetchAllPages(cursor => getGroupMembers(topic, {cursor, ...(mute ? {muteEligible: true} : {eligibleRole: 'admin'})}))
|
||||
.then(items => setMembers(items.filter(m => m.eligible !== false)))
|
||||
.catch(() => { onToast('加载失败'); setLoadError(true); });
|
||||
}, [topic, mute]);
|
||||
const visible = (members || []).filter(m => (m.group_nickname || m.display_name || '').includes(query));
|
||||
@@ -842,15 +842,14 @@ function Muted({topic, onToast}) {
|
||||
const [loadError, setLoadError] = React.useState(false);
|
||||
const load = React.useCallback(() => {
|
||||
if (!topic) return;
|
||||
getGroupMutes(topic).then(data => {
|
||||
const list = data.items || [];
|
||||
fetchAllPages(cursor => getGroupMutes(topic, cursor)).then(list => {
|
||||
writeCachedData('group-muted:' + topic, list);
|
||||
setItems(list);
|
||||
}).catch(() => { onToast('加载失败'); setLoadError(true); });
|
||||
getGroupMembers(topic).then(data => {
|
||||
fetchAllPages(cursor => getGroupMembers(topic, {cursor})).then(items => {
|
||||
const map = {};
|
||||
const avatarMap = {};
|
||||
(data.items || []).forEach(m => {
|
||||
items.forEach(m => {
|
||||
map[m.user_id] = m.group_nickname || m.display_name || m.user_id;
|
||||
if (m.avatar_file_id) avatarMap[m.user_id] = {avatar_file_id: m.avatar_file_id};
|
||||
});
|
||||
@@ -896,7 +895,8 @@ function OwnerSelect({topic, onToast}) {
|
||||
const [loadError, setLoadError] = React.useState(false);
|
||||
React.useEffect(() => {
|
||||
if (!topic) return;
|
||||
getGroupMembers(topic, {eligibleRole: 'owner'}).then(data => setMembers((data.items || []).filter(m => m.eligible !== false)))
|
||||
fetchAllPages(cursor => getGroupMembers(topic, {cursor, eligibleRole: 'owner'}))
|
||||
.then(items => setMembers(items.filter(m => m.eligible !== false)))
|
||||
.catch(() => { onToast('加载失败'); setLoadError(true); });
|
||||
}, [topic]);
|
||||
const visible = (members || []).filter(m => (m.group_nickname || m.display_name || '').includes(query));
|
||||
@@ -1122,11 +1122,13 @@ export default function GroupManagementView({view, hashParams, myUserId, tinode,
|
||||
if (!topic || !myUserId) { setMyRole(null); return; }
|
||||
const key = `group-my-role:${topic}:${myUserId}`;
|
||||
setMyRole(readCachedData(key) || null);
|
||||
getGroupMembers(topic).then(data => {
|
||||
const items = data.items || [];
|
||||
writeCachedData('group-members:' + topic, items);
|
||||
const me = items.find(m => m.user_id === myUserId);
|
||||
const role = me ? me.role : null;
|
||||
// Used to look this up by paginating the full member list and finding myUserId in it (30/page
|
||||
// against a 128-member cap) -- a real owner/admin sitting past page 1 read back as `null`,
|
||||
// silently hiding every governance affordance gated on this value. getGroup's own `my_role`
|
||||
// (already relied on elsewhere, e.g. the disband-group guard above) isn't paginated at all --
|
||||
// it's always exactly "me", so this no longer needs the member list at all.
|
||||
getGroup(topic).then(group => {
|
||||
const role = group ? group.my_role : null;
|
||||
writeCachedData(key, role);
|
||||
setMyRole(role);
|
||||
}).catch(() => {});
|
||||
|
||||
Reference in New Issue
Block a user