// stats_page.dart — 统计页(指标卡 + 本周柱状图) import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../pangolin_theme.dart'; import '../state/app_providers.dart'; import '../widgets/app_top_bar.dart'; class StatsPage extends ConsumerWidget { const StatsPage({super.key, required this.isWide}); final bool isWide; static const _vals = [2.1, 3.4, 1.8, 4.6, 5.2, 6.1, 3.0]; @override Widget build(BuildContext context, WidgetRef ref) { final c = context.pangolin; final t = ref.watch(appTextProvider); final maxV = _vals.reduce((a, b) => a > b ? a : b); // desktop 对照 dapp.jsx DStats:padding 32/8、卡 gap 14、柱 height 120·bar×86·宽 34。 final pad = isWide ? 32.0 : 20.0; final metrics = [ (t.trafficMonth, '42.6', 'GB'), (t.avgPing, '29', 'ms'), (t.durMonth, '86.4', 'h'), ]; final body = ListView( padding: EdgeInsets.fromLTRB(pad, isWide ? 8 : 0, pad, 24), children: [ Row(children: [ for (var i = 0; i < metrics.length; i++) Expanded( child: Padding( padding: EdgeInsets.only(right: i < metrics.length - 1 ? 14 : 0), child: _MetricCard(label: metrics[i].$1, value: metrics[i].$2, unit: metrics[i].$3), ), ), ]), const SizedBox(height: 20), Container( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 18), decoration: BoxDecoration( color: c.surface, borderRadius: BorderRadius.circular(PangolinRadius.lg), border: Border.all(color: c.border), boxShadow: PangolinShadow.sm, ), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(t.weekTraffic, style: PangolinText.sm.copyWith(color: c.fg1, fontWeight: FontWeight.w600, fontSize: 13)), const SizedBox(height: 18), SizedBox( // 柱最高 86 + 上下数值/星期标签两行 + 间距 ≈ 134,留余量 140 避免溢出。 height: 140, child: Row(crossAxisAlignment: CrossAxisAlignment.end, children: [ for (var i = 0; i < _vals.length; i++) Expanded( child: Column(mainAxisAlignment: MainAxisAlignment.end, children: [ Text('${_vals[i]}', style: PangolinText.mono.copyWith(fontSize: 10.5, color: c.fg3)), const SizedBox(height: 8), ConstrainedBox( constraints: const BoxConstraints(maxWidth: 34), child: Container( height: 86 * (_vals[i] / maxV), decoration: BoxDecoration( color: c.accent.withOpacity(0.85), borderRadius: const BorderRadius.vertical(top: Radius.circular(6)), ), ), ), const SizedBox(height: 8), Text(t.days7[i], style: PangolinText.caption.copyWith(color: c.fg3)), ]), ), ]), ), ]), ), ], ); if (isWide) return body; return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ AppTopBar(brand: t.brand), PageTitle(title: t.statsTitle), Expanded(child: body), ]); } } class _MetricCard extends StatelessWidget { const _MetricCard({required this.label, required this.value, required this.unit}); final String label, value, unit; @override Widget build(BuildContext context) { final c = context.pangolin; return Container( padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 16), decoration: BoxDecoration( color: c.surface, borderRadius: BorderRadius.circular(PangolinRadius.lg), border: Border.all(color: c.border), boxShadow: PangolinShadow.sm, ), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, overflow: TextOverflow.ellipsis, style: PangolinText.caption.copyWith(color: c.fg3, fontWeight: FontWeight.w600)), const SizedBox(height: 7), Text.rich(TextSpan( text: value, style: PangolinText.mono.copyWith(fontSize: 24, color: c.fg1, fontWeight: FontWeight.w500), children: [TextSpan(text: ' $unit', style: PangolinText.caption.copyWith(color: c.fg3))], )), ]), ); } }