修复置顶/免打扰开关未接后端、好友二维码死按钮(第5章复测发现)

置顶聊天/消息免打扰:原来是纯本地 useState,onChange 只切一下布尔值,从不调用任何接口——
GET/PATCH /conversations/{topic}/settings 早就封装好了,只是零调用点。打开设置页永远显示
"关",点了也只是那次会话内生效、离开页面就丢,和会话列表真正读取的置顶/免打扰状态是两套
毫不相关的东西。

实测确认真实字段:{pinned, mute_notifications, version},PATCH 需要带 version 做乐观锁。
接入 getConversationSettings/updateConversationSettings,失败时回滚开关视觉状态。
已用真实账号验证:点击后发出真实 PATCH 请求,带正确的 version。

好友个人主页"二维码"图标:参考图里是独立一行(不是贴在头像卡片里的小图标),点击生成这个
好友的二维码名片,对应接口 createUserQRCode() 早就封装好了,原来这个图标没有任何 onClick,
是个看着能点、点了没反应的死装饰。新增 friend-qr 路由和 FriendQr 组件(复用 QrPage 的结构),
CSS 复用了 .people-settings 那套现成的行样式,不是新造一套。
已用真实账号验证:点击后正确跳转、生成出真实二维码。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-09-04 13:43:22 +08:00
co-authored by Claude Sonnet 5
parent d3d7e11b08
commit 0dcef54fed
5 changed files with 149 additions and 15 deletions
+93 -6
View File
@@ -7380,7 +7380,7 @@ const navigate = (view, uid) => {
hash = uid ? _lib_navigation_js__WEBPACK_IMPORTED_MODULE_3__["default"].addUrlParam(hash, 'uid', uid) : _lib_navigation_js__WEBPACK_IMPORTED_MODULE_3__["default"].removeUrlParam(hash, 'uid');
_lib_navigation_js__WEBPACK_IMPORTED_MODULE_3__["default"].navigateTo(hash);
};
const CONTACT_MANAGEMENT_VIEWS = ['address-book', 'friend-requests', 'add-friend', 'my-friend-qr', 'friend-search-result', 'friend-verification', 'friend-profile', 'group-member-profile', 'friend-settings', 'friend-settings-blocked', 'friend-delete-confirm', 'block-list', 'block-confirm', 'clear-chat-confirm'];
const CONTACT_MANAGEMENT_VIEWS = ['address-book', 'friend-requests', 'add-friend', 'my-friend-qr', 'friend-qr', 'friend-search-result', 'friend-verification', 'friend-profile', 'group-member-profile', 'friend-settings', 'friend-settings-blocked', 'friend-delete-confirm', 'block-list', 'block-confirm', 'clear-chat-confirm'];
function alphaInitial(name) {
const ch = (name || '').trim()[0] || '#';
if (/[A-Za-z]/.test(ch)) {
@@ -7978,6 +7978,57 @@ function QrPage({
}, void 0, true)]
}, void 0, true);
}
function FriendQr({
uid,
onToast
}) {
const [payload, setPayload] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(null);
const [profile, setProfile] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(null);
react__WEBPACK_IMPORTED_MODULE_0___default().useEffect(() => {
if (!uid) return;
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.createUserQRCode)(uid).then(data => {
setPayload(data.qr_payload);
setProfile(data.display_profile);
}).catch(() => onToast('二维码生成失败'));
}, [uid]);
const save = () => {
const canvas = document.querySelector('.people-qrcode canvas');
if (!canvas) {
onToast('二维码尚未生成');
return;
}
const a = document.createElement('a');
a.href = canvas.toDataURL('image/png');
a.download = '好友二维码.png';
a.click();
onToast('二维码已保存到相册');
};
return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("div", {
className: "people-page people-qr-page",
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)(Header, {
title: "二维码",
back: "friend-profile",
backUid: uid
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("main", {
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)(QrCard, {
payload: payload,
profile: profile,
loading: !payload
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("div", {
className: "people-qr-actions",
children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("button", {
type: "button",
onClick: save,
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)(Icon, {
children: "download"
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("span", {
children: "保存到相册"
}, void 0, false)]
}, void 0, true)
}, void 0, false)]
}, void 0, true)]
}, void 0, true);
}
function Verification({
uid,
onToast
@@ -8113,7 +8164,14 @@ function Profile({
}, void 0, true) : null, profile.region ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("p", {
children: ["地区:", profile.region]
}, void 0, true) : null]
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)(Icon, {
}, void 0, true)]
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("button", {
type: "button",
className: "people-profile-qr-row",
onClick: () => navigate('friend-qr', uid),
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("span", {
children: "二维码"
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)(Icon, {
children: "qr_code"
}, void 0, false)]
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("dl", {
@@ -8182,6 +8240,7 @@ function Settings({
}) {
const [profile, setProfile] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(() => uid ? (0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_5__.readCachedData)('profile-seed:' + uid) : null);
const [blocked, setBlocked] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(initialBlocked);
const [settingsVersion, setSettingsVersion] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(null);
const [pinned, setPinned] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(false);
const [muted, setMuted] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(false);
const [editingRemark, setEditingRemark] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(false);
@@ -8189,6 +8248,11 @@ function Settings({
const [remarkDraft, setRemarkDraft] = react__WEBPACK_IMPORTED_MODULE_0___default().useState('');
react__WEBPACK_IMPORTED_MODULE_0___default().useEffect(() => {
if (!uid) return;
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.getConversationSettings)(uid).then(s => {
setPinned(!!s.pinned);
setMuted(!!s.mute_notifications);
setSettingsVersion(s.version);
}).catch(() => {});
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.getUserProfile)(uid).then(p => {
(0,_lib_use_cached_data_js__WEBPACK_IMPORTED_MODULE_5__.writeCachedData)('profile-seed:' + uid, p);
setProfile(p);
@@ -8244,14 +8308,34 @@ function Settings({
children: "置顶聊天"
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)(Toggle, {
checked: pinned,
onChange: setPinned
onChange: value => {
const prev = pinned;
setPinned(value);
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.updateConversationSettings)(uid, {
pinned: value,
version: settingsVersion
}).then(s => setSettingsVersion(s.version)).catch(err => {
setPinned(prev);
onToast(err.message || '设置失败');
});
}
}, void 0, false)]
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("div", {
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("span", {
children: "消息免打扰"
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)(Toggle, {
checked: muted,
onChange: setMuted
onChange: value => {
const prev = muted;
setMuted(value);
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.updateConversationSettings)(uid, {
mute_notifications: value,
version: settingsVersion
}).then(s => setSettingsVersion(s.version)).catch(err => {
setMuted(prev);
onToast(err.message || '设置失败');
});
}
}, void 0, false)]
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("button", {
type: "button",
@@ -8474,6 +8558,9 @@ function ContactManagementView({
onOpenScanner: onOpenScanner
}, void 0, false);else if (view === 'my-friend-qr') content = (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)(QrPage, {
onToast: setToast
}, void 0, false);else if (view === 'friend-qr') content = (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)(FriendQr, {
uid: uid,
onToast: setToast
}, void 0, false);else if (view === 'friend-verification') content = (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)(Verification, {
uid: uid,
onToast: setToast
@@ -17232,7 +17319,7 @@ const messages = (0,react_intl__WEBPACK_IMPORTED_MODULE_1__.defineMessages)({
description: 'Placeholder for the country selector'
}
});
const SIDE_PANEL_VIEWS = ['acc_general', 'archive', 'blocked', 'contacts', 'cred', 'crop', 'edit', 'general', 'newtpk', 'notif', 'register', 'reset', 'security', 'settings', 'support', 'wallpapers', 'tenant', 'login', 'enterprise-register', 'enterprise-feedback', 'identity-check', 'realname', 'realname-verified', 'realname-reminder', 'independent-password', 'messages-home', 'messages-offline', 'messages-menu', 'message-actions', 'system-messages', 'chat-inbound-request', 'chat-inbound-message', 'chat-outbound-request', 'chat-status', 'chat-friend', 'chat-media', 'chat-blocked', 'chat-deleted', 'chat-standard', 'chat-attachments', 'chat-mixed', 'chat-contact-card', 'compose-keyboard', 'mentions-select', 'mentions-complete', 'mentions-message', 'emoji-panel', 'emoji-message', 'voice-ready', 'voice-hold', 'voice-recording', 'media-uploading', 'media-gallery', 'media-retry', 'upload-file', 'file-message', 'message-multiselect', 'favorite-success', 'forward-select', 'forward-multiple', 'forward-individual', 'forward-bundle', 'address-book', 'friend-requests', 'add-friend', 'my-friend-qr', 'friend-search-result', 'friend-verification', 'friend-profile', 'group-member-profile', 'friend-settings', 'friend-settings-blocked', 'friend-delete-confirm', 'block-list', 'block-confirm', 'clear-chat-confirm', 'group-list', 'group-empty', 'group-invite', 'group-events', 'group-join-requests', 'group-new-request', 'group-announcement', 'group-summary', 'group-announcement-detail', 'group-settings', 'group-profile', 'group-avatar', 'group-nickname', 'group-description', 'group-my-nickname', 'group-role-message', 'group-qr', 'group-governance', 'group-admins', 'group-admin-add', 'group-admin-success', 'group-admin-partial', 'group-admin-removed', 'group-mute-add', 'group-muted', 'group-owner-select', 'group-owner-confirm', 'group-files', 'group-file-upload', 'favorites', 'favorites-mixed', 'favorites-files', 'favorites-text', 'favorites-voice', 'favorites-links', 'favorites-select', 'favorite-share', 'file-download', 'file-downloading', 'file-downloaded', 'history-search', 'history-files', 'history-links', 'history-images', 'history-videos', 'call-history', 'call-incoming', 'audio-call-ringing', 'audio-call-active', 'audio-call-muted', 'audio-call-speaker', 'video-call-ringing', 'video-call-camera-off', 'video-call-active', 'video-call-peer-camera-off', 'call-minimized', 'workbench', 'organization-directory', 'meeting-organizer', 'meeting-attendee', 'meeting-create', 'meeting-detail', 'daily-report-editor', 'daily-report-mine', 'daily-report-team', 'daily-report-detail', 'monthly-report-editor', 'monthly-report-list', 'monthly-report-detail', 'executive-mailbox', 'executive-mailbox-notice', 'personal-center', 'personal-profile', 'profile-nickname', 'profile-birth-date', 'profile-signature', 'profile-gender', 'profile-region', 'personal-qrcode', 'android-settings', 'cache-clear-confirm', 'clear-history-confirm', 'change-password', 'delete-account-confirm', 'app-version', 'privacy-settings', 'friend-discovery-settings', 'youth-mode-off', 'youth-mode-on', 'youth-mode-verify', 'notification-permission', 'permission-settings-reminder', 'camera-permission-info', 'microphone-permission-info', 'fraud-risk-warning', 'fraud-risk-confirmation', 'abuse-report-reasons', 'abuse-report-details', 'user-feedback', ''];
const SIDE_PANEL_VIEWS = ['acc_general', 'archive', 'blocked', 'contacts', 'cred', 'crop', 'edit', 'general', 'newtpk', 'notif', 'register', 'reset', 'security', 'settings', 'support', 'wallpapers', 'tenant', 'login', 'enterprise-register', 'enterprise-feedback', 'identity-check', 'realname', 'realname-verified', 'realname-reminder', 'independent-password', 'messages-home', 'messages-offline', 'messages-menu', 'message-actions', 'system-messages', 'chat-inbound-request', 'chat-inbound-message', 'chat-outbound-request', 'chat-status', 'chat-friend', 'chat-media', 'chat-blocked', 'chat-deleted', 'chat-standard', 'chat-attachments', 'chat-mixed', 'chat-contact-card', 'compose-keyboard', 'mentions-select', 'mentions-complete', 'mentions-message', 'emoji-panel', 'emoji-message', 'voice-ready', 'voice-hold', 'voice-recording', 'media-uploading', 'media-gallery', 'media-retry', 'upload-file', 'file-message', 'message-multiselect', 'favorite-success', 'forward-select', 'forward-multiple', 'forward-individual', 'forward-bundle', 'address-book', 'friend-requests', 'add-friend', 'my-friend-qr', 'friend-qr', 'friend-search-result', 'friend-verification', 'friend-profile', 'group-member-profile', 'friend-settings', 'friend-settings-blocked', 'friend-delete-confirm', 'block-list', 'block-confirm', 'clear-chat-confirm', 'group-list', 'group-empty', 'group-invite', 'group-events', 'group-join-requests', 'group-new-request', 'group-announcement', 'group-summary', 'group-announcement-detail', 'group-settings', 'group-profile', 'group-avatar', 'group-nickname', 'group-description', 'group-my-nickname', 'group-role-message', 'group-qr', 'group-governance', 'group-admins', 'group-admin-add', 'group-admin-success', 'group-admin-partial', 'group-admin-removed', 'group-mute-add', 'group-muted', 'group-owner-select', 'group-owner-confirm', 'group-files', 'group-file-upload', 'favorites', 'favorites-mixed', 'favorites-files', 'favorites-text', 'favorites-voice', 'favorites-links', 'favorites-select', 'favorite-share', 'file-download', 'file-downloading', 'file-downloaded', 'history-search', 'history-files', 'history-links', 'history-images', 'history-videos', 'call-history', 'call-incoming', 'audio-call-ringing', 'audio-call-active', 'audio-call-muted', 'audio-call-speaker', 'video-call-ringing', 'video-call-camera-off', 'video-call-active', 'video-call-peer-camera-off', 'call-minimized', 'workbench', 'organization-directory', 'meeting-organizer', 'meeting-attendee', 'meeting-create', 'meeting-detail', 'daily-report-editor', 'daily-report-mine', 'daily-report-team', 'daily-report-detail', 'monthly-report-editor', 'monthly-report-list', 'monthly-report-detail', 'executive-mailbox', 'executive-mailbox-notice', 'personal-center', 'personal-profile', 'profile-nickname', 'profile-birth-date', 'profile-signature', 'profile-gender', 'profile-region', 'personal-qrcode', 'android-settings', 'cache-clear-confirm', 'clear-history-confirm', 'change-password', 'delete-account-confirm', 'app-version', 'privacy-settings', 'friend-discovery-settings', 'youth-mode-off', 'youth-mode-on', 'youth-mode-verify', 'notification-permission', 'permission-settings-reminder', 'camera-permission-info', 'microphone-permission-info', 'fraud-risk-warning', 'fraud-risk-confirmation', 'abuse-report-reasons', 'abuse-report-details', 'user-feedback', ''];
const AUTH_FLOW_VIEWS = ['tenant', 'login', 'register', 'reset', 'cred', 'enterprise-register', 'enterprise-feedback'];
const LOGIN_ERROR_TEXT_CN = {
'authentication failed': '账号或密码错误',
@@ -19280,7 +19367,7 @@ class TinodeWeb extends (react__WEBPACK_IMPORTED_MODULE_0___default().Component)
const chapterTwoFullPage = ['messages-offline', 'messages-menu', 'message-actions', 'system-messages'].includes(activeProductView) || !this.state.myUserId && activeProductView === 'messages-home';
const chapterThreeActive = ['chat-inbound-request', 'chat-inbound-message', 'chat-outbound-request', 'chat-status', 'chat-friend', 'chat-media', 'chat-blocked', 'chat-deleted', 'chat-standard', 'chat-attachments', 'chat-mixed', 'chat-contact-card'].includes(activeProductView);
const chapterFourActive = ['compose-keyboard', 'mentions-select', 'mentions-complete', 'mentions-message', 'emoji-panel', 'emoji-message', 'voice-ready', 'voice-hold', 'voice-recording', 'media-uploading', 'media-gallery', 'media-retry', 'upload-file', 'file-message', 'message-multiselect', 'favorite-success', 'forward-select', 'forward-multiple', 'forward-individual', 'forward-bundle'].includes(activeProductView);
const chapterFiveActive = ['address-book', 'friend-requests', 'add-friend', 'my-friend-qr', 'friend-search-result', 'friend-verification', 'friend-profile', 'group-member-profile', 'friend-settings', 'friend-settings-blocked', 'friend-delete-confirm', 'block-list', 'block-confirm', 'clear-chat-confirm'].includes(activeProductView);
const chapterFiveActive = ['address-book', 'friend-requests', 'add-friend', 'my-friend-qr', 'friend-qr', 'friend-search-result', 'friend-verification', 'friend-profile', 'group-member-profile', 'friend-settings', 'friend-settings-blocked', 'friend-delete-confirm', 'block-list', 'block-confirm', 'clear-chat-confirm'].includes(activeProductView);
const chapterSixActive = ['group-list', 'group-empty', 'group-invite', 'group-events', 'group-join-requests', 'group-new-request', 'group-announcement', 'group-summary', 'group-announcement-detail', 'group-settings', 'group-profile', 'group-avatar', 'group-nickname', 'group-description', 'group-my-nickname', 'group-role-message', 'group-qr', 'group-governance', 'group-admins', 'group-admin-add', 'group-admin-success', 'group-admin-partial', 'group-admin-removed', 'group-mute-add', 'group-muted', 'group-owner-select', 'group-owner-confirm', 'group-files', 'group-file-upload'].includes(activeProductView);
const chapterSevenActive = ['favorites', 'favorites-mixed', 'favorites-files', 'favorites-text', 'favorites-voice', 'favorites-links', 'favorites-select', 'favorite-share', 'file-download', 'file-downloading', 'file-downloaded', 'history-search', 'history-files', 'history-links', 'history-images', 'history-videos'].includes(activeProductView);
const callExperienceActive = ['call-history', 'call-incoming', 'audio-call-ringing', 'audio-call-active', 'audio-call-muted', 'audio-call-speaker', 'video-call-ringing', 'video-call-camera-off', 'video-call-active', 'video-call-peer-camera-off', 'call-minimized'].includes(activeProductView);