修复"退出当前登录"仍用原生 window.confirm(),与其余三个确认弹层不一致

现象:清除缓存/清除聊天记录/注销账号三处早前已经统一改成组件内 ConfirmSheet 弹层
(本文件其它三处组件自己的注释也在讲这次统一),唯独"退出当前登录"还留着最原始的
window.confirm()。本轮测试发现这在当前浏览器自动化环境下会被判定为"环境不支持同步
弹窗"而直接返回 false(控制台可见"Page dialog suppressed"警告),退出登录点了完全
没反应,且没有任何错误提示——这不只是测试环境的偶然状况:不少内嵌 WebView/嵌入式
容器场景本身就会禁用 confirm()/alert() 这类阻塞式原生对话框,真实用户在这些环境下
点"退出登录"会遇到一模一样的静默无效。

修法:新增 LogoutConfirm 组件,复用同一个 ConfirmSheet(图标 logout,标题"退出登录"),
"退出当前登录"按钮改成 setSheet('logout') 打开这个弹层,与 CacheClear/
ClearHistoryConfirm/DeleteAccountConfirm 完全同构,不再依赖浏览器原生对话框。

验证:npm run build:dev 后在浏览器实测,点击"退出当前登录"正确弹出本地弹层(标题/
文案/确认取消按钮与设计一致,logout 图标正常渲染为图形而非文字),点"确认"后真实
退出登录、回到企业码入口页;点"取消"正确关闭弹层且不登出。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-09-06 13:19:45 +08:00
co-authored by Claude Sonnet 5
parent eaafabe94f
commit 46a98782fd
+17 -7
View File
@@ -297,7 +297,7 @@ function SettingsList({onToast, onLogout, tinode, chatList, onDeleteAccount}) {
// 但因为底下真的没有任何东西在渲染,视觉上跟直接跳转到一个空白新页面没有区别。改成本组件自己的
// 本地 state,弹层作为兄弟节点叠在列表上方,列表本身不卸载——group-management-view.jsx 的
// OwnerSelect(选择新群主)已经是这个正确模式,这里照抄,不是发明新写法。
const [sheet, setSheet] = React.useState(null); // null | 'cache' | 'history' | 'delete'
const [sheet, setSheet] = React.useState(null); // null | 'cache' | 'history' | 'delete' | 'logout'
React.useEffect(() => {
getYouthMode().then(d => { writeCachedData('youth-mode', d); setYouthSettings(d); }).catch(() => {});
getRealnameStatus().then(d => { writeCachedData('me-realname', d); setRealname(d); }).catch(() => {});
@@ -305,11 +305,6 @@ function SettingsList({onToast, onLogout, tinode, chatList, onDeleteAccount}) {
const youthEnabled = youthSettings ? youthSettings.enabled : null;
const realnameStatus = realname ? realname.status : null;
const realnameLabel = {verified: '已认证', pending: '审核中', rejected: '未通过'}[realnameStatus] || '未认证';
const logout = () => {
if (window.confirm('确定要退出当前登录吗?')) {
onLogout ? onLogout() : onToast('当前环境不支持退出登录');
}
};
return <div className="personal-page"><Header title="设置" /><main className="android-settings-list">
<section>
<MenuRow icon="delete_sweep" title="清除缓存" onClick={() => setSheet('cache')} />
@@ -329,11 +324,12 @@ function SettingsList({onToast, onLogout, tinode, chatList, onDeleteAccount}) {
<MenuRow icon="shield" title="安全教育" route="fraud-risk-warning" />
</section>
<section><MenuRow icon="no_accounts" title="注销账号" onClick={() => setSheet('delete')} danger /></section>
<button type="button" className="personal-logout" onClick={logout}>退出当前登录</button>
<button type="button" className="personal-logout" onClick={() => setSheet('logout')}>退出当前登录</button>
</main>
{sheet === 'cache' ? <CacheClear onToast={onToast} onClose={() => setSheet(null)} /> : null}
{sheet === 'history' ? <ClearHistoryConfirm onToast={onToast} tinode={tinode} chatList={chatList} onClose={() => setSheet(null)} /> : null}
{sheet === 'delete' ? <DeleteAccountConfirm onToast={onToast} onDeleteAccount={onDeleteAccount} onClose={() => setSheet(null)} /> : null}
{sheet === 'logout' ? <LogoutConfirm onToast={onToast} onLogout={onLogout} onClose={() => setSheet(null)} /> : null}
</div>;
}
@@ -346,6 +342,20 @@ function ConfirmSheet({icon, title, body, confirmLabel = '确认', confirmDanger
</section></div>;
}
// Was a bare `window.confirm()` -- the only one of these four confirmation flows not using this
// same ConfirmSheet pattern, an inconsistency with cache/history/delete-account below (their own
// comments describe deliberately moving away from route-per-confirmation dialogs to this in-place
// sheet). A native confirm() is also environment-dependent: some embedded WebViews/kiosk contexts
// disable synchronous dialogs outright (confirm() then returns false unconditionally, silently
// preventing logout with no error shown), which this sheet-based approach doesn't depend on.
function LogoutConfirm({onLogout, onToast, onClose}) {
const confirm = () => {
onClose();
onLogout ? onLogout() : onToast('当前环境不支持退出登录');
};
return <ConfirmSheet icon="logout" title="退出登录" body="确定要退出当前登录吗?" onConfirm={confirm} onCancel={onClose} />;
}
// No backend endpoint clears cache/history -- both are pure client-side operations (confirmed:
// server/main.go's ~74 routes have nothing for either). Cache clearing empties Cache Storage/IDB;
// clearing all history calls the native Tinode delMessagesAll(false) per open conversation, same