第六轮深度测试:7个并行agent覆盖全部11章,修复21处前端问题

Ch1-2: 用户名注册邀请码错误改为友好文案(原为原始TypeError泄露给用户);
  企业注册页扫描图标补上点击反馈。
Ch3-4: 发名片消息补传typeHint(此前被后端误分类成text,搜索/历史摘要显示成JSON);
  文件卡片按mime大类区分图标颜色(此前所有文件都是同一个灰色通用图标);
  表情面板补上专属退格/发送按钮(浅蓝禁用/深蓝启用两态);订正一处与截图不符的代码注释。
Ch5-6: 群聊设置页"群成员"补上可点击的完整成员列表页(复用MemberPicker字母分组);
  群文件页去掉无依据的硬编码"/10GB"容量上限显示。
Ch7: 收藏点击图片/视频缩略图补上预览;"查找聊天记录-链接"tab补上定位回原消息;
  文件下载页返回入口按来源tab纠正;批量转发成功计数改为按目标会话去重
  (原公式混用"收藏条数"和"目标会话数"两个维度,混选多条收藏转发时可能显示负数);
  收藏/视频tab切换补上URL路由同步。
Ch8: topic.onInfo在通话期间补上事件转发(此前通话中"对方正在输入"和已读回执静默失效);
  主叫等待接听页补上"正在等待对方接受邀请…"文案;语音通话最小化胶囊的还原按钮移出
  拖拽区域(原来嵌在draggable内部,指针捕获可能吞掉点击);修复中等窗宽(641-750px)下
  本地摄像头预览遮挡"切换摄像头"按钮的CSS重叠。
Ch9: 部门树展开时跳过child_count=0的注定空请求;月报字数上限从硬编码1000改为读取
  后端真实的10000;发起会议补上"结束时间需晚于开始时间"的本地校验和更具体的报错;
  日报/月报统计接口补上department_id透传(此前包装函数注释断言后端会忽略,实际已支持)。
Ch10-11: URL风险检测补上g标志(原来一条消息只检查第一个链接,第二个链接哪怕是高危
  也不会被拦截);补上"安全教育"设置入口(原功能完整实现但零入口,需手改hash才能打开);
  修改密码按钮补上busy态反馈;青少年模式PIN页补上"忘记独立密码"入口(后端已支持重置,
  前端未接);"关于我们"补上"联系我们"行。

7个agent同时对全部11章存量后端问题条目逐一复核,结论详见待整理的后端问题清单更新
(Ch1-2发现一条P0级后端问题:验证码固定值"123456",已记录未修复,不在此commit修改
im_be仓库范围内)。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-09-05 03:48:28 +08:00
co-authored by Claude Sonnet 5
parent d42591284f
commit f42b8536e5
19 changed files with 634 additions and 111 deletions
+14 -4
View File
@@ -230,7 +230,7 @@ function DeptNode({dept, depth, myUserId}) {
setLoading(true);
setLoadError(false);
Promise.all([
getOrganizationDepartments(dept.department_id).then(d => d.departments || d.items || []),
dept.child_count ? getOrganizationDepartments(dept.department_id).then(d => d.departments || d.items || []) : Promise.resolve([]),
dept.member_count ? getOrganizationDepartmentMembers(dept.department_id).then(d => d.members || d.items || []) : Promise.resolve([])
]).then(([depts, mems]) => { setChildren(depts); setMembers(mems); })
// Without this, a rejected Promise.all left children/members at null forever -- rendered as
@@ -458,10 +458,14 @@ function MeetingCreate({myUserId, tinode, onToast}) {
}
const startAt = new Date(`${date}T${startTime}:00`).toISOString();
const endAt = new Date(`${date}T${endTime}:00`).toISOString();
// Backend 400s with {details:{field:"end_at"}} for this (real request confirmed), but nothing
// upstream of onToast reads `details` -- catching it locally avoids a round-trip for the single
// most likely input mistake (single shared date, two independent time inputs).
if (endAt <= startAt) { onToast('结束时间需晚于开始时间'); return; }
setSubmitting(true);
createMeeting({title: title.trim(), start_at: startAt, end_at: endAt, location: location.trim(), attendee_user_ids: selected})
.then(_ => { onToast('会议已发起'); navigate('meeting-attendee'); })
.catch(err => onToast(err.message || '发起会议失败'))
.catch(err => onToast((err.details && err.details.field === 'end_at') ? '结束时间需晚于开始时间' : (err.message || '发起会议失败')))
.finally(() => setSubmitting(false));
};
@@ -813,6 +817,11 @@ function MonthlyReportEditor({onToast}) {
const setVersionBoth = v => { versionRef.current = v; setVersion(v); };
const [summary, setSummary] = React.useState(cached ? cached.summary : '');
const [plan, setPlan] = React.useState(cached ? cached.plan : '');
// monthlyReportDetailPayload's real validation.max_length is 10000, not the 1000 this page used to
// hardcode -- confirmed live (a 2000-char draft save was accepted while this UI would have blocked
// it at 1000). Keep 1000 as the pre-load fallback so the textarea has *some* limit before the first
// response comes back, not because it's the real cap.
const [maxLen, setMaxLen] = React.useState(1000);
// A cached seed can go stale the instant something outside this component's lifetime mutates the
// report's version -- concretely, MonthlyReportDetail's "重新编辑" (reopenMonthlyReport) does that,
// then navigates straight here. Live-reproduced: reopen -> land here with the cache still holding
@@ -849,6 +858,7 @@ function MonthlyReportEditor({onToast}) {
const nextSummary = r.work_summary || ''; const nextPlan = r.next_plan || '';
setReportId(r.report_id); setVersionBoth(r.version);
setSummary(nextSummary); setPlan(nextPlan);
if (r.validation && r.validation.max_length) setMaxLen(r.validation.max_length);
writeCachedData(cacheKey, {reportId: r.report_id, version: r.version, summary: nextSummary, plan: nextPlan});
setHydrated(true);
}).catch(err => onToast(err.message || '创建月报失败'));
@@ -879,9 +889,9 @@ function MonthlyReportEditor({onToast}) {
<main>
<section className="daily-report-block-header"><strong>月报汇总</strong></section>
<label className="report-field"><span><b>*</b>本月工作总结</span>
<textarea value={summary} maxLength="1000" placeholder="请输入" onChange={e => setSummary(e.target.value)} /><small>{summary.length}/1000</small></label>
<textarea value={summary} maxLength={maxLen} placeholder="请输入" onChange={e => setSummary(e.target.value)} /><small>{summary.length}/{maxLen}</small></label>
<label className="report-field"><span><b>*</b>下月工作计划</span>
<textarea value={plan} maxLength="1000" placeholder="请输入" onChange={e => setPlan(e.target.value)} /><small>{plan.length}/1000</small></label>
<textarea value={plan} maxLength={maxLen} placeholder="请输入" onChange={e => setPlan(e.target.value)} /><small>{plan.length}/{maxLen}</small></label>
<p className="report-draft-status">{saveError ? '草稿保存失败,请检查网络后重新编辑' : saved ? '草稿已自动保存' : '正在保存草稿…'}</p>
</main>
<footer className="workplace-submit-bar"><button type="button" disabled={submitting || !reportId} onClick={submit}>{submitting ? '提交中…' : '提交'}</button></footer>