diff --git a/client/lib/screens/account_page.dart b/client/lib/screens/account_page.dart index d4cbf23..651e211 100644 --- a/client/lib/screens/account_page.dart +++ b/client/lib/screens/account_page.dart @@ -12,6 +12,7 @@ import '../state/navigation_provider.dart'; import '../widgets/account_screens.dart'; import '../widgets/app_top_bar.dart'; import '../widgets/notification_bell.dart'; +import '../widgets/page_body.dart'; import '../widgets/pangolin_icons.dart'; import 'invite_page.dart'; import 'notifications_page.dart'; @@ -50,7 +51,7 @@ class AccountPage extends ConsumerWidget { } } - final body = ListView( + final body = PageBody(child: ListView( padding: EdgeInsets.fromLTRB(pad, isWide ? 6 : 0, pad, 24), children: [ _PlanBanner( @@ -127,7 +128,7 @@ class AccountPage extends ConsumerWidget { ), ]), ], - ); + )); if (isWide) return body; return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/client/lib/screens/contact_page.dart b/client/lib/screens/contact_page.dart index fe80357..6d90840 100644 --- a/client/lib/screens/contact_page.dart +++ b/client/lib/screens/contact_page.dart @@ -10,6 +10,7 @@ import '../pangolin_theme.dart'; import '../services/channel_launch.dart'; import '../services/contact_channels.dart'; import '../state/app_providers.dart'; +import '../widgets/page_body.dart'; import '../widgets/pangolin_icons.dart'; class ContactPage extends ConsumerWidget { @@ -36,8 +37,7 @@ class ContactPage extends ConsumerWidget { return SingleChildScrollView( padding: const EdgeInsets.fromLTRB(32, 16, 32, 24), - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 560), + child: PageBody( child: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text(t.contactIntro, style: PangolinText.sm.copyWith(color: c.fg2)), const SizedBox(height: 16), diff --git a/client/lib/screens/invite_page.dart b/client/lib/screens/invite_page.dart index 02d8255..0b2c0c9 100644 --- a/client/lib/screens/invite_page.dart +++ b/client/lib/screens/invite_page.dart @@ -12,6 +12,7 @@ import '../pangolin_theme.dart'; import '../services/channel_launch.dart'; import '../services/invite_api.dart'; import '../state/invite_provider.dart'; +import '../widgets/page_body.dart'; import '../widgets/pangolin_button.dart'; import '../widgets/pangolin_icons.dart'; import '../widgets/pangolin_toast.dart'; @@ -61,13 +62,13 @@ class InviteScreen extends ConsumerWidget { final c = context.pangolin; final inviteAsync = ref.watch(inviteProvider); - Widget body = inviteAsync.when( + Widget body = PageBody(child: inviteAsync.when( loading: () => const Center(child: CircularProgressIndicator()), error: (_, __) => _Placeholder(t: t), data: (info) => info == null ? _Placeholder(t: t) : _InviteContent(t: t, info: info, onCopy: _copy, onVerifyAndClaim: (ctx) => _verifyAndClaim(ctx, ref)), - ); + )); if (embedded) return body; return Scaffold( diff --git a/client/lib/screens/notifications_page.dart b/client/lib/screens/notifications_page.dart index b6f3941..bc26561 100644 --- a/client/lib/screens/notifications_page.dart +++ b/client/lib/screens/notifications_page.dart @@ -7,6 +7,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../l10n/app_text.dart'; import '../pangolin_theme.dart'; +import '../widgets/page_body.dart'; import '../widgets/pangolin_icons.dart'; import '../widgets/status_pill.dart'; @@ -136,7 +137,7 @@ class NotificationsScreen extends ConsumerWidget { ], ); - if (embedded) return content(); + if (embedded) return PageBody(child: content()); return Scaffold( backgroundColor: c.bg, body: SafeArea( @@ -151,7 +152,7 @@ class NotificationsScreen extends ConsumerWidget { Text(t.notifTitle, style: PangolinText.h3.copyWith(color: c.fg1, fontWeight: FontWeight.w700)), ]), ), - Expanded(child: content()), + Expanded(child: PageBody(child: content())), ]), ), ); diff --git a/client/lib/screens/orders_page.dart b/client/lib/screens/orders_page.dart index 61362b4..137f1b6 100644 --- a/client/lib/screens/orders_page.dart +++ b/client/lib/screens/orders_page.dart @@ -13,6 +13,7 @@ import '../models/payment.dart'; import '../pangolin_theme.dart'; import '../state/navigation_provider.dart'; import '../state/orders_provider.dart'; +import '../widgets/page_body.dart'; import '../widgets/pangolin_button.dart'; import '../widgets/pangolin_icons.dart'; import '../widgets/status_pill.dart'; @@ -67,7 +68,7 @@ class _SubScaffold extends StatelessWidget { @override Widget build(BuildContext context) { final c = context.pangolin; - if (embedded) return child; + if (embedded) return PageBody(child: child); return Scaffold( backgroundColor: c.bg, body: SafeArea( @@ -82,7 +83,7 @@ class _SubScaffold extends StatelessWidget { Text(title, style: PangolinText.h3.copyWith(color: c.fg1, fontWeight: FontWeight.w700)), ]), ), - Expanded(child: child), + Expanded(child: PageBody(child: child)), ]), ), ); @@ -362,7 +363,7 @@ class _OrderDetailView extends ConsumerWidget { ]), ), ), - Expanded(child: body()), + Expanded(child: PageBody(child: body())), ]); } return _SubScaffold(title: t.orderDetailTitle, onBack: onBack, child: body()); diff --git a/client/lib/screens/purchase_page.dart b/client/lib/screens/purchase_page.dart index 7da2ff1..f230c38 100644 --- a/client/lib/screens/purchase_page.dart +++ b/client/lib/screens/purchase_page.dart @@ -9,6 +9,7 @@ import '../l10n/app_text.dart'; import '../models/payment.dart'; import '../pangolin_theme.dart'; import '../state/payment_provider.dart'; +import '../widgets/page_body.dart'; import '../widgets/pangolin_icons.dart'; import '../widgets/pangolin_toast.dart'; import '../widgets/plan_card.dart'; @@ -70,7 +71,7 @@ class _PurchaseScreenState extends ConsumerState { final c = context.pangolin; final catalog = ref.watch(payCatalogProvider); - final body = catalog.when( + final body = PageBody(wide: true, child: catalog.when( loading: () => const Center( child: Padding(padding: EdgeInsets.all(40), child: CircularProgressIndicator())), error: (_, __) => Center( @@ -132,7 +133,7 @@ class _PurchaseScreenState extends ConsumerState { ], ); }, - ); + )); if (widget.embedded) return body; return Scaffold( diff --git a/client/lib/screens/settings_page.dart b/client/lib/screens/settings_page.dart index 5071687..587ce1d 100644 --- a/client/lib/screens/settings_page.dart +++ b/client/lib/screens/settings_page.dart @@ -13,6 +13,7 @@ import '../services/web_launch.dart'; import '../state/app_providers.dart'; import '../state/settings_provider.dart'; import '../state/update_provider.dart'; +import '../widgets/page_body.dart'; import '../widgets/pangolin_icons.dart'; import '../widgets/pangolin_toast.dart'; @@ -54,8 +55,7 @@ class SettingsPage extends ConsumerWidget { return SingleChildScrollView( padding: const EdgeInsets.fromLTRB(32, 16, 32, 24), - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 560), + child: PageBody( child: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // ① 连接(功能开关组;开机自启是桌面端登录项概念,iOS/Android 无此能力,仅桌面显示) _SectionLabel(text: t.settingsGroupConn), diff --git a/client/lib/screens/stats_page.dart b/client/lib/screens/stats_page.dart index da4aa7c..a9df736 100644 --- a/client/lib/screens/stats_page.dart +++ b/client/lib/screens/stats_page.dart @@ -11,6 +11,7 @@ import '../state/account_providers.dart'; import '../state/app_providers.dart'; import '../state/auth_provider.dart'; import '../widgets/app_top_bar.dart'; +import '../widgets/page_body.dart'; import '../widgets/pangolin_icons.dart'; import '../widgets/period_card.dart'; import '../widgets/usage_line_chart.dart'; @@ -69,7 +70,7 @@ class StatsPage extends ConsumerWidget { ref.invalidate(deviceUsageProvider(30)); await ref.read(meProvider.notifier).refresh(); }, - child: ListView( + child: PageBody(child: ListView( padding: EdgeInsets.fromLTRB(isWide ? 32 : 20, isWide ? 8 : 4, isWide ? 32 : 20, 24), children: [ // 顶行:标题(宽屏) + 设备下拉(右上角) @@ -115,7 +116,7 @@ class StatsPage extends ConsumerWidget { ]), ), ], - ), + )), ); if (isWide) return body; diff --git a/client/lib/widgets/page_body.dart b/client/lib/widgets/page_body.dart new file mode 100644 index 0000000..266144c --- /dev/null +++ b/client/lib/widgets/page_body.dart @@ -0,0 +1,20 @@ +// page_body.dart — 桌面内容页统一宽度容器(镜像 design/prototype/atoms.css 的 .view-body)。 +// 默认 600 居中;wide=true 840(多列网格页,如购买页)。手机屏宽 < 600 时约束自然不生效,可无条件包。 +import 'package:flutter/widgets.dart'; + +const double kViewMaxW = 600; // atoms.css .view-body +const double kViewMaxWWide = 840; // atoms.css .view-body.is-wide + +class PageBody extends StatelessWidget { + const PageBody({super.key, required this.child, this.wide = false}); + final Widget child; + final bool wide; + @override + Widget build(BuildContext context) => Center( + child: ConstrainedBox( + constraints: + BoxConstraints(maxWidth: wide ? kViewMaxWWide : kViewMaxW), + child: child, + ), + ); +} diff --git a/client/test/golden/goldens/desktop_account.png b/client/test/golden/goldens/desktop_account.png index 9007cad..81636c6 100644 Binary files a/client/test/golden/goldens/desktop_account.png and b/client/test/golden/goldens/desktop_account.png differ diff --git a/client/test/golden/goldens/desktop_contact.png b/client/test/golden/goldens/desktop_contact.png index ff59b56..c092ee3 100644 Binary files a/client/test/golden/goldens/desktop_contact.png and b/client/test/golden/goldens/desktop_contact.png differ diff --git a/client/test/golden/goldens/desktop_settings.png b/client/test/golden/goldens/desktop_settings.png index 44d3e17..934ae23 100644 Binary files a/client/test/golden/goldens/desktop_settings.png and b/client/test/golden/goldens/desktop_settings.png differ diff --git a/client/test/golden/goldens/desktop_stats.png b/client/test/golden/goldens/desktop_stats.png index 4b24e87..87745f0 100644 Binary files a/client/test/golden/goldens/desktop_stats.png and b/client/test/golden/goldens/desktop_stats.png differ diff --git a/client/test/golden/goldens/tablet_account_dark_zh.png b/client/test/golden/goldens/tablet_account_dark_zh.png index 476d675..0302dba 100644 Binary files a/client/test/golden/goldens/tablet_account_dark_zh.png and b/client/test/golden/goldens/tablet_account_dark_zh.png differ diff --git a/client/test/golden/goldens/tablet_account_light_en.png b/client/test/golden/goldens/tablet_account_light_en.png index f3e936e..7a21f6b 100644 Binary files a/client/test/golden/goldens/tablet_account_light_en.png and b/client/test/golden/goldens/tablet_account_light_en.png differ diff --git a/client/test/golden/goldens/tablet_account_light_zh.png b/client/test/golden/goldens/tablet_account_light_zh.png index 458082e..2adefc4 100644 Binary files a/client/test/golden/goldens/tablet_account_light_zh.png and b/client/test/golden/goldens/tablet_account_light_zh.png differ diff --git a/client/test/golden/goldens/tablet_stats_dark_zh.png b/client/test/golden/goldens/tablet_stats_dark_zh.png index ae5d3a2..3363894 100644 Binary files a/client/test/golden/goldens/tablet_stats_dark_zh.png and b/client/test/golden/goldens/tablet_stats_dark_zh.png differ diff --git a/client/test/golden/goldens/tablet_stats_light_en.png b/client/test/golden/goldens/tablet_stats_light_en.png index e2bdbf0..5891641 100644 Binary files a/client/test/golden/goldens/tablet_stats_light_en.png and b/client/test/golden/goldens/tablet_stats_light_en.png differ diff --git a/client/test/golden/goldens/tablet_stats_light_zh.png b/client/test/golden/goldens/tablet_stats_light_zh.png index 894dc32..e24035f 100644 Binary files a/client/test/golden/goldens/tablet_stats_light_zh.png and b/client/test/golden/goldens/tablet_stats_light_zh.png differ diff --git a/design/prototype/atoms.css b/design/prototype/atoms.css index b9083eb..410a8bd 100644 --- a/design/prototype/atoms.css +++ b/design/prototype/atoms.css @@ -92,6 +92,16 @@ .btn-block { width: 100%; } +/* ============================================================= + VIEW BODY · .view-body (+ .is-wide) + 桌面客户端内容视图统一宽度容器 —— 对齐 Flutter PageBody(client/lib/widgets/page_body.dart)。 + 默认 600px 居中(单栏内容页:账户/设置/联系我们/统计/订单/通知/邀请); + .is-wide 840px(多列网格页:购买套餐)。宽度标量本身无对应 token,字面写死属正常。 + ============================================================= */ +.view-body{ max-width:600px; margin:0 auto; padding:var(--space-5) var(--space-6) var(--space-6); } +.view-body.is-wide{ max-width:840px; } + + /* ============================================================= CARD · .card surface 底 + border + radius-lg + shadow-sm diff --git a/design/prototype/index.html b/design/prototype/index.html index bd9d202..3c78cbe 100644 --- a/design/prototype/index.html +++ b/design/prototype/index.html @@ -189,6 +189,7 @@
组件 Atoms
按钮 .btn 卡片 .card + 内容容器 .view-body 输入 .input 胶囊 .pill / .badge 语言下拉 .langsel @@ -433,6 +434,29 @@ +
+

内容容器 · .view-body

+

桌面客户端内容视图统一宽度容器:默认居中 600px(账户/设置/联系我们/统计/订单/通知/邀请等单栏页),.is-wide 修饰 840px(购买套餐等多列网格页)。padding 固定 space-5 space-6 space-6。对齐 Flutter PageBody(client/lib/widgets/page_body.dart)。

+

默认 · 600px

.view-body
+
+
+
.view-body
+
单栏内容视图
+
max-width:600px · margin:0 auto 居中 · padding 5/6/6
+
+
+
+

宽 · 840px

.view-body.is-wide
+
+
+
.view-body.is-wide
+
多列网格页(购买套餐)
+
max-width:840px · padding 沿用默认 5/6/6
+
+
+
+
+

输入 · .input / .field / .flabel

强描边 + radius-md + focus clay 光环。状态 .is-focused / .is-error / :disabled

diff --git a/design/prototype/screens/ui-desktop.html b/design/prototype/screens/ui-desktop.html index 3ae919c..a11f084 100644 --- a/design/prototype/screens/ui-desktop.html +++ b/design/prototype/screens/ui-desktop.html @@ -125,7 +125,6 @@ .searchbar input::placeholder{ color:var(--fg3); } /* ---- 统计视图 ---- */ - .stats-view{ padding:var(--space-5) var(--space-6) var(--space-6); } /* 顶行:标题左 + 设备筛选下拉右(对齐线上 stats_page.dart 的 _DeviceDropdown) */ .stats-head{ display:flex; align-items:center; justify-content:space-between; margin-bottom:var(--space-4); } .stats-head .sh-t{ font-family:var(--font-display); font-weight:700; font-size:20px; color:var(--fg1); } @@ -155,7 +154,6 @@ .sc-g1{ stop-color:var(--accent); stop-opacity:0; } /* ---- 账户视图 ---- */ - .account-view{ padding:var(--space-5) var(--space-6) var(--space-6); max-width:600px; margin:0 auto; } .account-view .plan-banner{ margin-bottom:var(--space-5); } .st-change{ font-family:var(--font-sans); font-size:13px; font-weight:600; color:var(--accent); } @@ -176,11 +174,9 @@ .dv-menu .menu-item.is-danger:hover{ background:var(--danger-subtle); } /* ---- 设置视图 ---- */ - .settings-view{ padding:var(--space-5) var(--space-6) var(--space-6); max-width:600px; margin:0 auto; } .side .plan-badge{ width:100%; } /* ---- 联系视图 ---- */ - .contact-view{ padding:var(--space-5) var(--space-6) var(--space-6); max-width:none; } .contact-intro{ font-size:13px; color:var(--fg2); line-height:var(--leading-relaxed); margin:0 0 var(--space-4); } .icon-box.is-plain{ background:var(--bg-subtle); color:var(--fg2); } .hours-card{ display:flex; align-items:center; gap:10px; background:var(--bg-subtle); @@ -190,7 +186,7 @@ .hours-card .hc-s{ font-family:var(--font-mono); font-size:12px; color:var(--fg3); margin-top:2px; } /* ---- 购买视图(兑换 & 购买流程,接入主框架内容区) ---- */ - .purchase-view{ padding:var(--space-5) var(--space-6) var(--space-8); max-width:840px; margin:0 auto; } + .purchase-view{ padding:var(--space-5) var(--space-6) var(--space-8); } .pv-back{ display:inline-flex; align-items:center; gap:6px; border:none; background:transparent; color:var(--accent); font-family:var(--font-sans); font-size:13px; font-weight:600; cursor:pointer; padding:6px 12px 6px 8px; margin-bottom:var(--space-4); border-radius:var(--radius-full); @@ -242,7 +238,6 @@ .pv-qr-cap{ font-size:var(--text-sm); font-weight:600; color:var(--fg1); } /* ---- 我的订单视图(列表 → 详情,接入主框架内容区) ---- */ - .orders-view{ padding:var(--space-5) var(--space-6) var(--space-8); max-width:600px; margin:0 auto; } .od-screen{ display:none; } .od-screen.is-active{ display:block; } /* 订单详情 = 一张「收据」卡:顶部渐变条 + 摘要头(金额左/状态右) + 虚线撕裂线 + 明细 */ @@ -346,7 +341,6 @@ background:var(--danger); border:1.5px solid var(--bg); } /* ---- 通知收件箱视图 ---- */ - .notif-view{ padding:var(--space-5) var(--space-6) var(--space-6); max-width:600px; margin:0 auto; } .notif-row{ display:flex; align-items:flex-start; gap:var(--space-3); padding:14px 16px; border-top:1px solid var(--border); } .notif-row:first-child{ border-top:none; } .notif-dot{ flex:none; width:8px; height:8px; margin-top:6px; border-radius:var(--radius-full); background:var(--danger); } @@ -359,7 +353,6 @@ .notif-s{ font-size:12.5px; color:var(--fg3); margin-top:2px; line-height:var(--leading-normal); } /* ---- 邀请好友视图 ---- */ - .invite-view{ padding:var(--space-5) var(--space-6) var(--space-6); max-width:600px; margin:0 auto; } .invite-code-card{ margin-bottom:var(--space-5); display:flex; flex-direction:column; gap:var(--space-3); } .inv-lead{ font-size:13px; color:var(--fg2); line-height:var(--leading-relaxed); } .inv-code-box, .inv-link-box{ display:flex; align-items:center; gap:var(--space-2); @@ -531,7 +524,7 @@
-
+
统计 @@ -583,7 +576,7 @@
-