import 'package:lucide_icons_flutter/lucide_icons.dart'; import 'package:flutter/material.dart'; import '../core/theme/context_tokens.dart'; import '../core/theme/app_dims.g.dart'; /// KPI 卡(还原原型 `.kpi`):左上标题、右上图标芯片、下方大数值、可选同比/说明。 /// 色调走 token(禁硬编码 hex),尺寸走 AppDims(禁裸 px)。 /// 原型规格:surface 面 / 1px border / r-lg 圆角 / padding 15·16; /// `.ic` 30×30 r-md;`.v` 24/700 mono;`.d` fs-xs;hover(click) 描边 brand400。 enum KpiTone { info, ok, blue, alert, warn, danger } enum KpiTrend { none, up, down } class KpiCard extends StatelessWidget { final String title; final String value; final IconData icon; final KpiTone tone; /// 同比/说明文案(如「▲ 2.3% 较上月」「需补货 6 项」),null 不显示。 final String? delta; final KpiTrend trend; /// 点击(如清筛选 / 跳转);null 时为静态卡,不显示 hover。 final VoidCallback? onTap; const KpiCard({ super.key, required this.title, required this.value, required this.icon, this.tone = KpiTone.info, this.delta, this.trend = KpiTrend.none, this.onTap, }); @override Widget build(BuildContext context) { final t = context.tokens; final (Color icBg, Color icFg) = switch (tone) { KpiTone.info => (t.infoSoft, t.primary), KpiTone.ok => (t.okSoft, t.success), KpiTone.blue => (t.infoSoft, t.brand400), KpiTone.alert => (t.accentSoft, t.accent), KpiTone.warn => (t.warnBg, t.warn), KpiTone.danger => (t.dangerBg, t.danger), }; final valueColor = switch (tone) { KpiTone.alert => t.accent, KpiTone.danger => t.danger, _ => t.heading, }; final deltaColor = switch (trend) { KpiTrend.up => t.success, KpiTrend.down => t.danger, KpiTrend.none => t.muted, }; final card = Container( padding: const EdgeInsets.symmetric(horizontal: AppDims.sp4, vertical: 15), decoration: BoxDecoration( color: t.surface, border: Border.all(color: t.border), borderRadius: BorderRadius.circular(AppDims.rLg), ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Text(title, style: TextStyle(fontSize: AppDims.fsSm, color: t.muted)), ), Container( width: 30, height: 30, decoration: BoxDecoration( color: icBg, borderRadius: BorderRadius.circular(AppDims.rMd), ), child: Icon(icon, size: 17, color: icFg), ), ], ), const SizedBox(height: 7), Text( value, style: TextStyle( fontSize: 24, fontWeight: FontWeight.w700, color: valueColor, letterSpacing: .3, fontFeatures: const [FontFeature.tabularFigures()], ), ), if (delta != null) ...[ const SizedBox(height: 5), Row( mainAxisSize: MainAxisSize.min, children: [ if (trend != KpiTrend.none) Icon( trend == KpiTrend.up ? LucideIcons.chevronUp : LucideIcons.chevronDown, size: 16, color: deltaColor, ), Text(delta!, style: TextStyle(fontSize: AppDims.fsXs, color: deltaColor)), ], ), ], ], ), ); if (onTap == null) return card; return InkWell( borderRadius: BorderRadius.circular(AppDims.rLg), onTap: onTap, child: card, ); } } /// 状态徽章(还原原型 `.badge`:圆点 + 文字同色 + 软底 pill)。 /// 用于库存「在售/预警/缺货」等派生状态;色与软底全走 token。 class StatusPill extends StatelessWidget { final String label; final Color color; final Color background; const StatusPill( {super.key, required this.label, required this.color, required this.background}); @override Widget build(BuildContext context) { return Container( height: 22, padding: const EdgeInsets.symmetric(horizontal: 9), decoration: BoxDecoration( color: background, borderRadius: BorderRadius.circular(AppDims.rPill), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Container( width: 6, height: 6, decoration: BoxDecoration(color: color, shape: BoxShape.circle), ), const SizedBox(width: 5), Text(label, style: TextStyle( color: color, fontSize: AppDims.fsSm, fontWeight: FontWeight.w600)), ], ), ); } }