修复会议详情页显示裸user_id的问题(第9章复测发现)

创建/详情/更新/取消/我的回应这5个会议接口的 organizer.display_name/participants[].display_name
实测恒等于裸 user_id,只有列表接口 GET /meetings 会真的解析姓名。之前的注释误以为"响应已带
姓名解析"对所有会议接口都成立(可能是当时只测过列表接口就下了结论),这次复测用真实创建的
会议逐个接口验证,证伪了这个假设。

MeetingDetail 接入已有的 useUserNames(日报/月报详情页已经在用同一套机制),发起人和参会人
姓名解析不出来时不再显示裸 uid。同时更正了 MeetingCard 上那条误导性注释,说清楚它只对列表
接口成立。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-09-04 13:31:55 +08:00
co-authored by Claude Sonnet 5
parent 1d51e53a46
commit d3d7e11b08
3 changed files with 27 additions and 9 deletions
+17 -6
View File
@@ -257,10 +257,10 @@ function durationLabel(startAt, endAt) {
const RESPONSE_LABEL = {accepted: '已接受', declined: '已拒绝', tentative: '待定'}; const RESPONSE_LABEL = {accepted: '已接受', declined: '已拒绝', tentative: '待定'};
// Meeting responses already come back with names resolved server-side (organizer.display_name, // 这条只对 GET /meetings(列表)成立——实测确认只有列表接口会解析真实姓名,MeetingCard 只在
// participant.display_name/participant_names[]) -- confirmed against a real created meeting's // 列表页使用,不需要额外查询。**创建/详情/更新/取消/我的回应这5个接口不会**(display_name
// response payload, so no separate getUserProfile lookups are needed here (unlike org-tree/report // 恒等于裸 user_id,第一次记录这条时误以为"响应已带姓名解析"对所有会议接口都成立,被这次复测
// authors, which only ever return bare ids). // 用真实创建的会议证伪)——MeetingDetail 复用的是这5个接口之一,接了 useUserNames 兜底。
function MeetingCard({meeting, myUserId, onRespond, onToast}) { function MeetingCard({meeting, myUserId, onRespond, onToast}) {
const organizerName = (meeting.organizer && meeting.organizer.display_name) || meeting.organizer_display_name || meeting.organizer_id; const organizerName = (meeting.organizer && meeting.organizer.display_name) || meeting.organizer_display_name || meeting.organizer_id;
const attendeeNames = (meeting.participants || []).map(p => p.display_name || p.user_id).join('、'); const attendeeNames = (meeting.participants || []).map(p => p.display_name || p.user_id).join('、');
@@ -331,6 +331,17 @@ function MeetingDetail({hashParams, onToast}) {
const myUserId = me && me.user_id; const myUserId = me && me.user_id;
const [busy, setBusy] = React.useState(false); const [busy, setBusy] = React.useState(false);
const [loadError, setLoadError] = React.useState(false); const [loadError, setLoadError] = React.useState(false);
// 详情/创建/更新/取消/我的回应这5个接口的 organizer.display_name / participants[].display_name
// 实测就是裸 user_id 本身(只有列表接口 GET /meetings 真的解析姓名)——上面 MeetingCard 那段
// "响应已带姓名解析、不需要额外查询"的注释只对列表成立,本页面复用同一份 useUserNames 兜底
// (日报/月报详情页已经在用同一套机制)。
const participantIds = (meeting && meeting.participants || []).map(p => p.user_id);
const nameIds = meeting ? [meeting.organizer_id, ...participantIds] : [];
const resolvedNames = useUserNames(nameIds, myUserId);
const nameFor = (id, fallback) => {
if (fallback && fallback !== id) return fallback;
return (id && resolvedNames[id]) || fallback || id;
};
const load = () => meetingId && getMeeting(meetingId).then(m => { writeCachedData('meeting:' + meetingId, m); setMeeting(m); }).catch(err => { onToast(err.message || '加载失败'); setLoadError(true); }); const load = () => meetingId && getMeeting(meetingId).then(m => { writeCachedData('meeting:' + meetingId, m); setMeeting(m); }).catch(err => { onToast(err.message || '加载失败'); setLoadError(true); });
React.useEffect(() => { React.useEffect(() => {
@@ -372,14 +383,14 @@ function MeetingDetail({hashParams, onToast}) {
<header><span><small>{formatMeetingDate(new Date(meeting.start_at))} {formatTime(new Date(meeting.start_at))}</small> <header><span><small>{formatMeetingDate(new Date(meeting.start_at))} {formatTime(new Date(meeting.start_at))}</small>
<strong>会议主题:{meeting.title}</strong></span></header> <strong>会议主题:{meeting.title}</strong></span></header>
<dl> <dl>
<div><dt>发起人</dt><dd>{(meeting.organizer && meeting.organizer.display_name) || meeting.organizer_display_name || meeting.organizer_id}</dd></div> <div><dt>发起人</dt><dd>{nameFor(meeting.organizer_id, (meeting.organizer && meeting.organizer.display_name) || meeting.organizer_display_name)}</dd></div>
<div><dt>时间</dt><dd>{formatMeetingDate(new Date(meeting.start_at))} {formatTime(new Date(meeting.start_at))} - {formatTime(new Date(meeting.end_at))}</dd></div> <div><dt>时间</dt><dd>{formatMeetingDate(new Date(meeting.start_at))} {formatTime(new Date(meeting.start_at))} - {formatTime(new Date(meeting.end_at))}</dd></div>
{meeting.location ? <div><dt>地点</dt><dd>{meeting.location}</dd></div> : null} {meeting.location ? <div><dt>地点</dt><dd>{meeting.location}</dd></div> : null}
<div><dt>会议时间</dt><dd>{durationLabel(meeting.start_at, meeting.end_at)}</dd></div> <div><dt>会议时间</dt><dd>{durationLabel(meeting.start_at, meeting.end_at)}</dd></div>
</dl> </dl>
<ul className="meeting-participant-list"> <ul className="meeting-participant-list">
{(meeting.participants || []).map(p => <li key={p.user_id}> {(meeting.participants || []).map(p => <li key={p.user_id}>
<span>{p.display_name || p.user_id}</span> <span>{nameFor(p.user_id, p.display_name)}</span>
<em className={p.response}>{RESPONSE_LABEL[p.response] || '待确认'}</em> <em className={p.response}>{RESPONSE_LABEL[p.response] || '待确认'}</em>
</li>)} </li>)}
</ul> </ul>
+9 -2
View File
@@ -20349,6 +20349,13 @@ function MeetingDetail({
const myUserId = me && me.user_id; const myUserId = me && me.user_id;
const [busy, setBusy] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(false); const [busy, setBusy] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(false);
const [loadError, setLoadError] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(false); const [loadError, setLoadError] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(false);
const participantIds = (meeting && meeting.participants || []).map(p => p.user_id);
const nameIds = meeting ? [meeting.organizer_id, ...participantIds] : [];
const resolvedNames = useUserNames(nameIds, myUserId);
const nameFor = (id, fallback) => {
if (fallback && fallback !== id) return fallback;
return id && resolvedNames[id] || fallback || id;
};
const load = () => meetingId && (0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_3__.getMeeting)(meetingId).then(m => { const load = () => meetingId && (0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_3__.getMeeting)(meetingId).then(m => {
(0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_4__.writeCachedData)('meeting:' + meetingId, m); (0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_4__.writeCachedData)('meeting:' + meetingId, m);
setMeeting(m); setMeeting(m);
@@ -20422,7 +20429,7 @@ function MeetingDetail({
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_5__.jsxDEV)("dt", { children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_5__.jsxDEV)("dt", {
children: "发起人" children: "发起人"
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_5__.jsxDEV)("dd", { }, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_5__.jsxDEV)("dd", {
children: meeting.organizer && meeting.organizer.display_name || meeting.organizer_display_name || meeting.organizer_id children: nameFor(meeting.organizer_id, meeting.organizer && meeting.organizer.display_name || meeting.organizer_display_name)
}, void 0, false)] }, void 0, false)]
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_5__.jsxDEV)("div", { }, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_5__.jsxDEV)("div", {
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_5__.jsxDEV)("dt", { children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_5__.jsxDEV)("dt", {
@@ -20447,7 +20454,7 @@ function MeetingDetail({
className: "meeting-participant-list", className: "meeting-participant-list",
children: (meeting.participants || []).map(p => (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_5__.jsxDEV)("li", { children: (meeting.participants || []).map(p => (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_5__.jsxDEV)("li", {
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_5__.jsxDEV)("span", { children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_5__.jsxDEV)("span", {
children: p.display_name || p.user_id children: nameFor(p.user_id, p.display_name)
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_5__.jsxDEV)("em", { }, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_5__.jsxDEV)("em", {
className: p.response, className: p.response,
children: RESPONSE_LABEL[p.response] || '待确认' children: RESPONSE_LABEL[p.response] || '待确认'
+1 -1
View File
File diff suppressed because one or more lines are too long