Files
pangolin/client/lib/widgets/period_card.dart
wangjia 01ebafc05e feat(client/stats): 统计页重设计 — 月/周/日周期卡 + 设备下拉 + 两周折线 + 修刷新
布局重做(对照确认稿):
- 3 张周期卡(本月/本周/今日),每张 = ↓下行 · ↑上行 · 使用时长(PeriodCard)
- 右上设备下拉(_DeviceDropdown,先只挂「全部」;每设备归因打通后填设备名,见 TODO #9/#10)
- 柱状图 → 折线图:最近两周(14 天)按天流量(UsageLineChart,CustomPainter 零依赖)

数据源统一 + 修刷新(根治「本月流量冻住」):
- 月/周/日 + 折线全从单一源 usageProvider(30) 聚合(取最后 N 天点求和,末点即今日,
  避开 UTC/本地日期坑);不再 me.weekly + usage 两条管线
- usageProvider/deviceUsageProvider 改 autoDispose(进页重取)+ 下拉刷新(invalidate)
  + 刷新连带 meProvider.refresh。后端每 60s 更新,无需更勤

UI 全走 PangolinColors/Text/Spacing token 单源、零硬编码;中英 l10n key 补齐。
widget 数值断言测试(刀1)按新结构重写并通过;统计/连接页 golden 重生成
(非 CI 闸;components/auth 基线未动)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 17:43:14 +08:00

69 lines
2.4 KiB
Dart

// period_card.dart — 周期统计卡(本月/本周/今日):左侧周期名 + 横排 3 指标。
// 沿用 MetricCard 的 surface+border+shadow 与 mono 数值观感,3 张竖排成统计页主体。
import 'package:flutter/material.dart';
import '../pangolin_theme.dart';
/// 一个指标:数值 + 单位 + 标签(下行/上行/使用时长)。
class StatMetric {
const StatMetric(this.value, this.unit, this.label);
final String value, unit, label;
}
class PeriodCard extends StatelessWidget {
const PeriodCard({super.key, required this.period, required this.metrics});
/// 周期名(本月/本周/今日)。
final String period;
final List<StatMetric> metrics;
@override
Widget build(BuildContext context) {
final c = context.pangolin;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
decoration: BoxDecoration(
color: c.surface,
borderRadius: BorderRadius.circular(PangolinRadius.lg),
border: Border.all(color: c.border),
boxShadow: PangolinShadow.sm,
),
child: Row(children: [
SizedBox(
width: 52,
child: Text(period,
style: PangolinText.sm.copyWith(color: c.fg1, fontWeight: FontWeight.w600, fontSize: 14)),
),
for (var i = 0; i < metrics.length; i++)
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 4),
decoration: i == 0
? null
: BoxDecoration(border: Border(left: BorderSide(color: c.border))),
child: Column(children: [
Text.rich(
TextSpan(
text: metrics[i].value,
style: PangolinText.mono.copyWith(fontSize: 19, color: c.fg1, fontWeight: FontWeight.w500),
children: [
TextSpan(
text: ' ${metrics[i].unit}',
style: PangolinText.caption.copyWith(color: c.fg3, fontSize: 11),
),
],
),
textAlign: TextAlign.center,
),
const SizedBox(height: 4),
Text(metrics[i].label,
textAlign: TextAlign.center,
style: PangolinText.caption.copyWith(color: c.fg2, fontSize: 11)),
]),
),
),
]),
);
}
}