Files
pangolin/client/lib/screens/stats_page.dart
T
wangjia 22e4eb1ead feat(client): desktop 其他页精还原 + 整页 golden 闸
- stats_page 对照 dapp.jsx DStats 精对齐: padding 32/8、指标卡 padding 16/18·
  gap 14·值 mono 24、柱状图卡 padding 20/18·标题 13、柱 height 140(容 bar 86)·宽 34
- nodes_page 对照 DServers 微调: padding top 4、网格 gap 10(智能卡置顶保留, 符 §5)
- 新增 test/golden/desktop_pages_golden_test.dart: 用 macOS 平台覆盖 + 920×600 surface
  让 formFactor=desktop, 截 节点/统计/账户 三页整页 golden 作回归闸(连接页含旋转动画
  由组件级 golden 覆盖, 不在此截)

测试: flutter test 79 通过(+3 desktop 整页 golden), analyze 0 error

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 12:02:24 +08:00

119 lines
4.6 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.
// 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 DStatspadding 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))],
)),
]),
);
}
}