6238b86dcb
- 登录/注册(login.html/register.html 1:1):两栏卡片+品牌渐变面板+主题小衣服 pill(onSurface 变体)+记住我(记录并预填最近账号)+原型式 toast 校验; 登录 fidelity 1.4–2.1% 三主题全绿;注册暂不入闸(少 门店编号/兑换券 字段, 已记 CONTRACT,screens.mjs 留存根) - ds 原子补齐:DsToast(.toast 单例)/DsCheck(.check/.agree)/DsButton lg 档/ DsSelect 替换全部旧 DropdownButton/DsField label 在上/DsInput 后缀与密码形态 - 全屏统一:对话框按钮全 DsButton、盒式输入主题钉死(visualDensity.standard、 h38、InputDecorationTheme 渗漏修复)、图标全 lucide、JetBrains Mono 三端同源、 BrandMark 真相源 logo、只读模式写操作全量守卫(WriteGuard+DsToast) - 出入库列表:版式对齐原型(卡片对齐+搜索框居中)、KPI 近30天滚动、结清后 失效财务应收应付表;商品编辑抽屉介绍库改搜索下拉、图片双击全屏预览 - 删除旧 UI 死代码:DataTableCard/FormDialog/PageScaffold/SearchChip/ SelectProductDialog/tabStateProvider - golden/fidelity:stock-in/out 补注册(9%)、login 入册(8%)、goldens 全量重打; 修复 pubspec flutter_web_plugins 非法声明(CI pub get 阻断) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
166 lines
7.1 KiB
Dart
166 lines
7.1 KiB
Dart
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||
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); // ds-ignore: 授权紧迫度专用深色,主题无关(承白字,历史分叉已集中)
|
||
case 'readonly':
|
||
return const Color(
|
||
0xFFB71C1C); // 深红,强调即将彻底锁定 // ds-ignore: 授权紧迫度专用深色,主题无关(承白字,历史分叉已集中)
|
||
case 'grace':
|
||
return const Color(
|
||
0xFFE65100); // 深橙 // ds-ignore: 授权紧迫度专用深色,主题无关(承白字,历史分叉已集中)
|
||
default:
|
||
return Colors.transparent;
|
||
}
|
||
}
|
||
|
||
/// 前景强调色:normal=正常绿,grace=警告橙,readonly/locked=危险红。
|
||
static Color phaseColor(String phase) {
|
||
switch (phase) {
|
||
case 'grace':
|
||
return const Color(
|
||
0xFFE08E00); // ds-ignore: 授权紧迫度专用深色,主题无关(承白字,历史分叉已集中)
|
||
case 'readonly':
|
||
case 'locked':
|
||
return const Color(
|
||
0xFFD14343); // ds-ignore: 授权紧迫度专用深色,主题无关(承白字,历史分叉已集中)
|
||
default:
|
||
return const Color(
|
||
0xFF2E8B57); // ds-ignore: 授权紧迫度专用深色,主题无关(承白字,历史分叉已集中)
|
||
}
|
||
}
|
||
|
||
/// 状态图标:normal=已验证,grace=警告,readonly/locked=错误。
|
||
static IconData phaseIcon(String phase) {
|
||
switch (phase) {
|
||
case 'grace':
|
||
return LucideIcons.triangleAlert;
|
||
case 'readonly':
|
||
case 'locked':
|
||
return LucideIcons.circleAlert;
|
||
default:
|
||
return LucideIcons.shieldCheck;
|
||
}
|
||
}
|
||
|
||
/// 过期降级说明(设置页固定展示),随阈值自动联动。
|
||
static List<String> degradationNotes() => [
|
||
'过期 $graceDays 天内(宽限期):仍可正常使用,请尽快续费',
|
||
'过期 $graceDays–$readonlyDays 天(只读期):仅能查看数据,无法新增或修改',
|
||
'过期满 $readonlyDays 天:系统锁定,无法登录,需续费或激活新授权码后恢复',
|
||
];
|
||
}
|