系统消息:按通知自身内容决定入口和文案,不再一律显示"前往安全设置"

用户实机测试反馈:系统消息列表里每条通知底部都是同一个"前往安全设置"按钮,点进去跟通知内容毫不
相关;正文还直接显示 "group.settings.updated" 这种原始事件码。查证后两个问题性质不同:

入口按钮(前端问题,已修):原来无条件对所有通知渲染同一个按钮、固定跳 security 页。改为按通知
自己的 action 字段决定——action.topic 是 grp 开头的进对应群聊设置页,好友申请类(action.type
为 friend_request)进"新朋友"页,带 device/ip 的安全类才进安全设置页,都不匹配就不显示按钮。
注意 action.topic 不能直接当群用:好友申请通知的 action 里带的是 p2p 会话 topic
(db/mysql/tenant_adapter.go:8643),所以判断加了 grp 前缀检查。跳转目标都核对过在
tinode-web.jsx 的路由白名单里(friend-requests / group-settings),没有凭空造路由名。

正文文案(后端问题,前端只做兜底):后端多处把机器码直接当 summary 写进通知——群事件统一写
summary=event.EventType(im_group_events.go:42-44,7 种群事件还共用同一个 title"群聊动态"),
好友申请处理结果写 summary=status(im_http.go:5956 等,值就是 "accepted"/"rejected")。前端加
了一层兜底:summary 命中"纯小写字母数字点下划线"这种机器码特征时,按已知码翻译成中文,翻译不了
就退回通用文案;后端以后补上真正的文案会自动优先使用,不会被兜底覆盖。这条已记入后端问题清单。

实机验证:两条群通知正文由 "group.settings.updated" 变为"群设置有更新"、按钮变为"查看群聊"且
点击后正确进入 grpXDkK5cbXoK0 的群聊设置页;两条好友通知正文由 "accepted" 变为"对方已通过你的
好友申请",不再显示无关的安全设置按钮。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-09-03 17:32:36 +08:00
co-authored by Claude Opus 5
parent 3bdb0bf40f
commit 8225848493
3 changed files with 93 additions and 8 deletions
+47 -3
View File
@@ -10,7 +10,14 @@ import { topicDisplayName } from '../lib/utils.js';
// Must go through setUrlSidePanel (not a bare navigateTo(view), which replaces the whole hash) so
// switching panels from "messages-home" doesn't silently drop path[1] -- the currently open
// conversation topic, which coexists with this panel in the split view once a chat is selected.
const navigate = view => HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash, view));
// `topic` 可选:系统消息里的群事件通知需要带着群 topic 跳进对应群聊设置页,写法与
// group-management-view.jsx 的同名辅助函数保持一致(不带 topic 时显式清掉,避免把上一个页面
// 残留的 topic 参数带进不相关的页面)。
const navigate = (view, topic) => {
let hash = HashNavigation.setUrlSidePanel(window.location.hash, view);
hash = topic ? HashNavigation.addUrlParam(hash, 'topic', topic) : HashNavigation.removeUrlParam(hash, 'topic');
HashNavigation.navigateTo(hash);
};
// Was a hardcoded FREQUENT_CONTACTS mock (5 fake names, always clicking through to the same
// direct-message-view.jsx demo regardless of which avatar was tapped -- confirmed independently by
@@ -281,6 +288,33 @@ function MessageActions() {
</div>;
}
// 系统通知的 summary 在多处后端代码里被直接填成机器码而不是人话:群事件统一写
// summary=event.EventType(server/im_group_events.go:42-44,所有群事件共用 title="群聊动态"),
// 好友申请处理结果写 summary=status(server/im_http.go:5956 与 db/mysql/tenant_adapter.go:8643,8700,
// 值就是 "accepted"/"rejected")。已记入后端问题清单,在后端给出可读文案之前前端按已知码兜底翻译。
const NOTIFICATION_TEXT = {
'group.member.joined': '有成员加入了群聊',
'group.member.left': '有成员退出了群聊',
'group.member.updated': '群成员资料有更新',
'group.mute.updated': '群禁言设置有变更',
'group.owner.transferred': '群主已转让',
'group.role.updated': '群成员角色有变更',
'group.settings.updated': '群设置有更新',
accepted: '对方已通过你的好友申请',
rejected: '对方拒绝了你的好友申请'
};
// 机器码判定:纯 ASCII 且只由小写字母/数字/点/下划线/连字符组成("accepted"、
// "group.settings.updated" 这类),正常中文文案不会命中。
const MACHINE_CODE = /^[a-z0-9._-]+$/;
function systemNotificationText(item) {
const summary = (item.summary || '').trim();
// 后端给了真正的文案就直接用;只有当它是机器码时才走兜底,避免把未来后端补上的正常文案盖掉。
if (summary && !MACHINE_CODE.test(summary)) return summary;
return NOTIFICATION_TEXT[summary] || NOTIFICATION_TEXT[item.event_type] || '您有一条新的系统通知。';
}
function SystemMessages({authenticated}) {
const [items, setItems] = React.useState([]);
const [loading, setLoading] = React.useState(true);
@@ -313,9 +347,19 @@ function SystemMessages({authenticated}) {
<time>{new Date(item.created_at).toLocaleString('zh-CN')}</time>
<div className="system-notification-card">
<header><h2>{item.title}</h2>{!item.read_at ? <span>新</span> : null}</header>
<p>{item.summary}</p>
<p>{systemNotificationText(item)}</p>
{item.action && item.action.device ? <dl><div><dt>设备:</dt><dd>{item.action.device}</dd></div>{item.action.ip ? <div><dt>IP地址:</dt><dd>{item.action.ip}</dd></div> : null}</dl> : null}
<button type="button" className="system-security-link" onClick={e => { e.stopPropagation(); navigate(authenticated ? 'security' : 'identity-check'); }}>前往安全设置 <Icon>chevron_right</Icon></button>
{/* 入口按通知自身的 action 决定,不再对所有通知一律显示"前往安全设置"。注意 action.topic
不能直接当成群:好友申请类通知的 action 里带的是 p2p 会话 topic
(db/mysql/tenant_adapter.go:8643),只有 grp 前缀才是真的群。都不匹配时不显示按钮,
避免把人导到跟这条通知无关的页面。 */}
{item.action && typeof item.action.topic === 'string' && item.action.topic.startsWith('grp') ?
<button type="button" className="system-security-link" onClick={e => { e.stopPropagation(); navigate('group-settings', item.action.topic); }}>查看群聊 <Icon>chevron_right</Icon></button> :
item.action && item.action.type === 'friend_request' ?
<button type="button" className="system-security-link" onClick={e => { e.stopPropagation(); navigate('friend-requests'); }}>查看新朋友 <Icon>chevron_right</Icon></button> :
item.action && (item.action.device || item.action.ip) ?
<button type="button" className="system-security-link" onClick={e => { e.stopPropagation(); navigate(authenticated ? 'security' : 'identity-check'); }}>前往安全设置 <Icon>chevron_right</Icon></button> :
null}
<footer>保持警惕<br />即时通讯安全团队</footer>
</div>
</article>)}