Files
jiu/client/lib/core/config/license_copy.dart
T
wangjia 6238b86dcb feat(client): 登录/注册页照原型重建,ds 真相源组件族统一全部屏
- 登录/注册(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
2026-07-03 09:58:14 +08:00

166 lines
7.1 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 天:系统锁定,无法登录,需续费或激活新授权码后恢复',
];
}