From 4773ee5f64b633cd51201900d26d3b3d9974ae32 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 6 Sep 2026 16:30:06 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D"=E8=A7=A3=E6=95=A3=E7=BE=A4?= =?UTF-8?q?=E7=BB=84"=E4=BB=8D=E7=94=A8=E5=8E=9F=E7=94=9F=20window.confirm?= =?UTF-8?q?()=EF=BC=8C=E6=98=AF=E6=9C=AC=E6=96=87=E4=BB=B6=E5=94=AF?= =?UTF-8?q?=E4=B8=80=E6=9C=AA=E7=BB=9F=E4=B8=80=E7=9A=84=E5=8D=B1=E9=99=A9?= =?UTF-8?q?=E6=93=8D=E4=BD=9C=E7=A1=AE=E8=AE=A4=E5=BC=B9=E5=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 现象:本工程已经在"退出当前登录"那次修复(46a98782)里明确认定原生 window.confirm() 和其余确认弹层不一致、且在部分内嵌 WebView/kiosk 环境下会被直接禁用(同步对话框调用 无提示地恒返回 false,操作被静默拦下且用户毫无感知)。全仓库复查后发现"解散群组"是 group-management-view.jsx 里唯一还在用这个模式的危险操作——同样不可逆、后果同样重大, 却没有跟上那次统一。 修复:改成两段式状态(先点"解散群组"进入确认态,再点确认态里的"确定"才真正调用 delTopic),复用本文件 OwnerSelect 早就在用的同款弹层视觉语言 (.group-sheet-backdrop/.group-confirm-sheet),不再依赖浏览器原生对话框。 验证:真实浏览器操作,用真实账号在真实群里走了"点解散→点取消"(弹层消失、群未受 影响、群列表可正常继续访问)和"点解散→点确定"(原生 WS {del hard:true} 成功执行, 200 ok,正确跳转回群列表且群已从列表消失)两条路径。 顺带记录:content-library-view.jsx 的收藏批量删除也还在用裸 window.confirm(), 是同一类问题,但属于第七章范围,本轮不动,留给对应章节的测试处理。 Co-Authored-By: Claude Sonnet 5 --- src/views/group-management-view.jsx | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/views/group-management-view.jsx b/src/views/group-management-view.jsx index c62984b8..2a78f083 100644 --- a/src/views/group-management-view.jsx +++ b/src/views/group-management-view.jsx @@ -649,6 +649,7 @@ function Governance({topic, myRole, tinode, onToast}) { // and returns, so this is one of the highest-traffic pages in the file. const [gov, setGov] = React.useState(() => (topic && readCachedData('group-governance:' + topic)) || null); const [dissolving, setDissolving] = React.useState(false); + const [confirmingDissolve, setConfirmingDissolve] = React.useState(false); const [loadError, setLoadError] = React.useState(false); React.useEffect(() => { if (!topic) return; @@ -664,18 +665,27 @@ function Governance({topic, myRole, tinode, onToast}) { updateGroupGovernance(topic, {[key]: value, version: gov ? gov.version : 0}).then(data => { writeCachedData('group-governance:' + topic, data); setGov(data); }) .catch(err => onToast(err.message || '设置失败')); }; - const dissolve = () => { + // 原来是裸 window.confirm()——本工程已经在"退出登录"那次修复里明确认定这类原生弹窗和 + // 其余确认弹层不一致(personal-settings-view.jsx 的 LogoutConfirm/ConfirmSheet 那条注释: + // "a native confirm() is also environment-dependent: some embedded WebViews/kiosk contexts + // disable synchronous dialogs outright...silently preventing [the action] with no error + // shown")。解散群组同样不可逆、后果同样重大,却是本文件里唯一还在用 window.confirm() 的 + // 危险操作——本文件自己在 OwnerSelect 里已经有现成的同款弹层视觉语言(.group-sheet-backdrop + // /.group-confirm-sheet),改成两段式状态复用它,不再依赖浏览器原生对话框。 + const requestDissolve = () => { // Backend already refuses to actually destroy the group for a non-owner (the same native // {del hard:true} call it silently downgrades into "remove me from this group" instead) -- // but that response is indistinguishable from a real success, so without this gate a member // who somehow lands on this page got told "群聊已解散" when they'd really just left it. if (myRole !== 'owner') return; - if (!window.confirm('解散后群聊将无法恢复,确定要解散该群聊吗?')) return; + setConfirmingDissolve(true); + }; + const dissolve = () => { const t = tinode ? tinode.getTopic(topic) : null; - if (!t) return; + if (!t) { setConfirmingDissolve(false); return; } setDissolving(true); t.delTopic(true).then(() => { onToast('群聊已解散'); navigate('group-list'); }) - .catch(err => onToast(err.message || '解散失败')).finally(() => setDissolving(false)); + .catch(err => { onToast(err.message || '解散失败'); setConfirmingDissolve(false); }).finally(() => setDissolving(false)); }; if (!gov) return

{loadError ? '群组管理加载失败' : '加载中…'}

; return
@@ -694,8 +704,14 @@ function Governance({topic, myRole, tinode, onToast}) { {label}{desc} setField(key, v)} />
)} - + + {confirmingDissolve ?
+

解散群组

+

解散后群聊将无法恢复,确定要解散该群聊吗?

+ + +
: null} ; }