import 'package:flutter/material.dart'; import '../../models/license.dart'; /// 授权阶段文案 + 阈值的集中配置。 /// /// 所有授权相关提示(顶部横幅、到期弹窗、设置页状态、底部状态栏、过期说明) /// 统一在此维护,组件层不再硬编码文案与天数阈值。 /// /// 阈值须与后端 `backend/internal/middleware/license_guard.go` 保持一致: /// 宽限期 0–[graceDays] 天(仍可写)→ 只读 [graceDays]–[readonlyDays] 天 → 锁定 [readonlyDays] 天+。 class LicenseCopy { LicenseCopy._(); /// 宽限期上限(天):过期 ≤ 此值仍可正常写入。 static const int graceDays = 7; /// 只读期上限(天):过期 ≤ 此值进入只读;超过则锁定登录。 static const int readonlyDays = 15; /// 顶部横幅文案(仅 grace/readonly/locked 显示;normal 返回空串)。 static String banner(LicenseInfo lic) { final d = lic.daysExpired; switch (lic.phase) { case 'grace': return '授权已过期 $d 天,请及时续费,否则将影响您的正常使用'; case 'readonly': return '授权已过期 $d 天,已进入只读模式,暂时无法新增或修改数据;' '过期满 $readonlyDays 天后将彻底锁定,请尽快续费'; case 'locked': return '授权已过期 $d 天并已锁定,所有功能已停用,请立即续费或激活新授权码'; default: return ''; } } /// 设置页「授权状态」行文案。 static String statusText(LicenseInfo lic) { switch (lic.phase) { case 'grace': return '宽限期 · 已过期 ${lic.daysExpired} 天'; case 'readonly': return '只读模式 · 已过期 ${lic.daysExpired} 天'; case 'locked': return '已锁定 · 已过期 ${lic.daysExpired} 天'; default: return lic.expiresAt == null ? '正常(永久授权)' : '正常(剩余 ${lic.daysRemaining ?? 0} 天)'; } } /// 底部状态栏紧凑文案。[expiryDate] 为已格式化的到期日(yyyy-MM-dd)。 static String statusBar(LicenseInfo lic, String expiryDate) { switch (lic.phase) { case 'grace': return '授权宽限期 · 已过期 ${lic.daysExpired} 天'; case 'readonly': return '只读模式 · 已过期 ${lic.daysExpired} 天'; case 'locked': return '已锁定 · 已过期 ${lic.daysExpired} 天'; default: return lic.expiresAt == null ? '永久授权' : '授权正常 · 到期 $expiryDate'; } } /// 到期提醒弹窗的(标题, 正文)。 static (String title, String body) dialog(LicenseInfo lic) { final d = lic.daysExpired; switch (lic.phase) { case 'locked': return ( '授权已锁定', '您的授权已过期 $d 天(超过 $readonlyDays 天),所有功能已停用。\n' '请前往「设置 → 授权」激活新的授权码,或联系客服续费。', ); case 'readonly': return ( '授权已过期 · 只读模式', '您的授权已过期 $d 天,系统进入只读模式,无法执行任何写操作。\n' '过期满 $readonlyDays 天后将彻底锁定登录,请尽快续费。', ); default: // grace return ( '授权已过期', '您的授权已过期 $d 天,目前仍可正常使用(宽限期 $graceDays 天)。\n' '过期超过 $graceDays 天将进入只读模式,请尽快续费以免影响使用。', ); } } /// 只读账号尝试写操作被后端拒绝(403 code=READONLY_USER)时的提示。 static const String readonlyUserToast = '当前为只读账号,仅可查看数据,无法新增或修改'; /// 写按钮被授权过期禁用时,点击弹出的提示文案。 static String writeBlockedToast(LicenseInfo lic) { switch (lic.phase) { case 'locked': return '授权已过期 ${lic.daysExpired} 天并已锁定,无法操作,请前往「设置 → 授权」续费或激活'; default: // readonly return '授权已过期 ${lic.daysExpired} 天,已进入只读模式,续费后可继续操作(设置 → 授权)'; } } // ---- 阶段视觉(颜色 / 图标)集中映射 ---- // 顶部横幅、底部状态条、设置页状态三处此前各自硬编码 phase→色,readonly 取色还分叉 // (横幅 0xFFB71C1C vs 状态条 0xFFEF5350)。这里按「角色」给出单一来源: // - [bannerColor]:顶部横幅**实底背景**(白字承其上),仅 grace/readonly/locked 显示。 // - [phaseColor] :前景强调色(底部状态条图标+文字、设置页状态文字共用一套)。 // - [phaseIcon] :状态图标(状态条用)。 /// 顶部横幅实底背景色(深色调,承载白字)。normal 不显示横幅,返回透明。 static Color bannerColor(String phase) { switch (phase) { case 'locked': return const Color(0xFFD14343); case 'readonly': return const Color(0xFFB71C1C); // 深红,强调即将彻底锁定 case 'grace': return const Color(0xFFE65100); // 深橙 default: return Colors.transparent; } } /// 前景强调色:normal=正常绿,grace=警告橙,readonly/locked=危险红。 static Color phaseColor(String phase) { switch (phase) { case 'grace': return const Color(0xFFE08E00); case 'readonly': case 'locked': return const Color(0xFFD14343); default: return const Color(0xFF2E8B57); } } /// 状态图标:normal=已验证,grace=警告,readonly/locked=错误。 static IconData phaseIcon(String phase) { switch (phase) { case 'grace': return Icons.warning_amber_rounded; case 'readonly': case 'locked': return Icons.error_outline; default: return Icons.verified_user_outlined; } } /// 过期降级说明(设置页固定展示),随阈值自动联动。 static List degradationNotes() => [ '过期 $graceDays 天内(宽限期):仍可正常使用,请尽快续费', '过期 $graceDays–$readonlyDays 天(只读期):仅能查看数据,无法新增或修改', '过期满 $readonlyDays 天:系统锁定,无法登录,需续费或激活新授权码后恢复', ]; }