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

用户实机测试反馈:系统消息列表里每条通知底部都是同一个"前往安全设置"按钮,点进去跟通知内容毫不
相关;正文还直接显示 "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>)}
+45 -4
View File
@@ -12479,7 +12479,11 @@ __webpack_require__.r(__webpack_exports__);
const navigate = view => _lib_navigation_js__WEBPACK_IMPORTED_MODULE_2__["default"].navigateTo(_lib_navigation_js__WEBPACK_IMPORTED_MODULE_2__["default"].setUrlSidePanel(window.location.hash, view));
const navigate = (view, topic) => {
let hash = _lib_navigation_js__WEBPACK_IMPORTED_MODULE_2__["default"].setUrlSidePanel(window.location.hash, view);
hash = topic ? _lib_navigation_js__WEBPACK_IMPORTED_MODULE_2__["default"].addUrlParam(hash, 'topic', topic) : _lib_navigation_js__WEBPACK_IMPORTED_MODULE_2__["default"].removeUrlParam(hash, 'topic');
_lib_navigation_js__WEBPACK_IMPORTED_MODULE_2__["default"].navigateTo(hash);
};
function frequentContacts(chatList) {
if (!chatList || chatList.length === 0) return [];
return chatList.filter(item => item.topic && tinode_sdk__WEBPACK_IMPORTED_MODULE_1__.Tinode.isP2PTopicName(item.topic)).slice().sort((a, b) => new Date(b.touched || 0).getTime() - new Date(a.touched || 0).getTime()).slice(0, 8).map(item => ({
@@ -12955,6 +12959,23 @@ function MessageActions() {
}, void 0, false)]
}, void 0, true);
}
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: '对方拒绝了你的好友申请'
};
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
}) {
@@ -13024,7 +13045,7 @@ function SystemMessages({
children: "新"
}, void 0, false) : null]
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("p", {
children: item.summary
children: systemNotificationText(item)
}, void 0, false), item.action && item.action.device ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("dl", {
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("div", {
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("dt", {
@@ -13039,7 +13060,27 @@ function SystemMessages({
children: item.action.ip
}, void 0, false)]
}, void 0, true) : null]
}, void 0, true) : null, (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("button", {
}, void 0, true) : null, item.action && typeof item.action.topic === 'string' && item.action.topic.startsWith('grp') ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("button", {
type: "button",
className: "system-security-link",
onClick: e => {
e.stopPropagation();
navigate('group-settings', item.action.topic);
},
children: ["查看群聊 ", (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)(Icon, {
children: "chevron_right"
}, void 0, false)]
}, void 0, true) : item.action && item.action.type === 'friend_request' ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("button", {
type: "button",
className: "system-security-link",
onClick: e => {
e.stopPropagation();
navigate('friend-requests');
},
children: ["查看新朋友 ", (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)(Icon, {
children: "chevron_right"
}, void 0, false)]
}, void 0, true) : item.action && (item.action.device || item.action.ip) ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("button", {
type: "button",
className: "system-security-link",
onClick: e => {
@@ -13049,7 +13090,7 @@ function SystemMessages({
children: ["前往安全设置 ", (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)(Icon, {
children: "chevron_right"
}, void 0, false)]
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("footer", {
}, void 0, true) : null, (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("footer", {
children: ["保持警惕", (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("br", {}, void 0, false), "即时通讯安全团队"]
}, void 0, true)]
}, void 0, true)]
+1 -1
View File
File diff suppressed because one or more lines are too long