修复"解散群组"仍用原生 window.confirm(),是本文件唯一未统一的危险操作确认弹层
现象:本工程已经在"退出当前登录"那次修复(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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
30e6d5e300
commit
4773ee5f64
@@ -649,6 +649,7 @@ function Governance({topic, myRole, tinode, onToast}) {
|
|||||||
// and returns, so this is one of the highest-traffic pages in the file.
|
// 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 [gov, setGov] = React.useState(() => (topic && readCachedData('group-governance:' + topic)) || null);
|
||||||
const [dissolving, setDissolving] = React.useState(false);
|
const [dissolving, setDissolving] = React.useState(false);
|
||||||
|
const [confirmingDissolve, setConfirmingDissolve] = React.useState(false);
|
||||||
const [loadError, setLoadError] = React.useState(false);
|
const [loadError, setLoadError] = React.useState(false);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!topic) return;
|
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); })
|
updateGroupGovernance(topic, {[key]: value, version: gov ? gov.version : 0}).then(data => { writeCachedData('group-governance:' + topic, data); setGov(data); })
|
||||||
.catch(err => onToast(err.message || '设置失败'));
|
.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
|
// 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) --
|
// {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
|
// 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.
|
// who somehow lands on this page got told "群聊已解散" when they'd really just left it.
|
||||||
if (myRole !== 'owner') return;
|
if (myRole !== 'owner') return;
|
||||||
if (!window.confirm('解散后群聊将无法恢复,确定要解散该群聊吗?')) return;
|
setConfirmingDissolve(true);
|
||||||
|
};
|
||||||
|
const dissolve = () => {
|
||||||
const t = tinode ? tinode.getTopic(topic) : null;
|
const t = tinode ? tinode.getTopic(topic) : null;
|
||||||
if (!t) return;
|
if (!t) { setConfirmingDissolve(false); return; }
|
||||||
setDissolving(true);
|
setDissolving(true);
|
||||||
t.delTopic(true).then(() => { onToast('群聊已解散'); navigate('group-list'); })
|
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 <div className="group-page"><Header title="群组管理" back="group-settings" backTopic={topic} /><main className="group-governance"><p className="group-inline-empty">{loadError ? '群组管理加载失败' : '加载中…'}</p></main></div>;
|
if (!gov) return <div className="group-page"><Header title="群组管理" back="group-settings" backTopic={topic} /><main className="group-governance"><p className="group-inline-empty">{loadError ? '群组管理加载失败' : '加载中…'}</p></main></div>;
|
||||||
return <div className="group-page">
|
return <div className="group-page">
|
||||||
@@ -694,8 +704,14 @@ function Governance({topic, myRole, tinode, onToast}) {
|
|||||||
<span><strong>{label}</strong><small>{desc}</small></span>
|
<span><strong>{label}</strong><small>{desc}</small></span>
|
||||||
<Toggle checked={!!gov[key]} disabled={gov[key + '_editable'] === false} onChange={v => setField(key, v)} />
|
<Toggle checked={!!gov[key]} disabled={gov[key + '_editable'] === false} onChange={v => setField(key, v)} />
|
||||||
</div>)}</section>
|
</div>)}</section>
|
||||||
<button type="button" className="group-danger" disabled={dissolving || myRole !== 'owner'} onClick={dissolve}>解散群组</button>
|
<button type="button" className="group-danger" disabled={dissolving || myRole !== 'owner'} onClick={requestDissolve}>解散群组</button>
|
||||||
</main>
|
</main>
|
||||||
|
{confirmingDissolve ? <div className="group-sheet-backdrop"><section className="group-confirm-sheet">
|
||||||
|
<h2>解散群组</h2>
|
||||||
|
<p>解散后群聊将无法恢复,确定要解散该群聊吗?</p>
|
||||||
|
<button type="button" className="danger" disabled={dissolving} onClick={dissolve}>{dissolving ? '处理中…' : '确定'}</button>
|
||||||
|
<button type="button" disabled={dissolving} onClick={() => setConfirmingDissolve(false)}>取消</button>
|
||||||
|
</section></div> : null}
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user