fix(client): 只读模式下允许激活兑换券(自救出口)

授权到期进入 readonly/locked 相位后,「兑换续期」按钮被全局
WriteGuard 误锁(置灰/隐藏),用户无法激活新授权码自救。后端
/license/* 本就豁免 LicenseGuard,去掉该按钮外层 WriteGuard 包裹;
role=readonly 仍由后端 ReadOnly() 403 兜底。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bupi8Kdqkfx2N5acFsHTx5
This commit is contained in:
wangjia
2026-08-25 19:11:26 +08:00
parent 22047b725c
commit 8fb81cc571
@@ -18,7 +18,6 @@ import '../../core/utils/clock.dart';
import '../../providers/license_provider.dart';
import '../../widgets/ds/ds_atoms.dart';
import '../../widgets/ds/ds_toast.dart';
import '../../widgets/write_guard.dart';
import 'settings_card.dart';
class LicensePanel extends ConsumerStatefulWidget {
@@ -123,11 +122,12 @@ class _LicensePanelState extends ConsumerState<LicensePanel> {
),
),
const SizedBox(width: 10),
WriteGuard(
child: DsButton(_redeeming ? '兑换中…' : '兑换续期',
variant: DsBtnVariant.primary,
onPressed: _redeeming ? null : _redeem),
),
// 激活/兑换是过期后的「自救出口」:后端 /license/* 豁免 LicenseGuard
// 故此按钮不受授权相位(readonly/locked)只读锁约束,绝不能包 WriteGuard
// role=readonly 用户由后端 ReadOnly() 403 + toast 兜底。
DsButton(_redeeming ? '兑换中…' : '兑换续期',
variant: DsBtnVariant.primary,
onPressed: _redeeming ? null : _redeem),
],
),
),