design+client: 抽 .view-body/PageBody 统一桌面内容页宽度(600/wide 840)——stats·contact 补约束,settings·contact 560→600,九页收口

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P9G7E3wmAYL9KeYCVZVsqu
This commit is contained in:
wangjia
2026-07-13 09:46:57 +08:00
parent b88921a030
commit fa9a5c2d5e
22 changed files with 87 additions and 34 deletions
+3 -2
View File
@@ -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: [
+2 -2
View File
@@ -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),
+3 -2
View File
@@ -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(
+3 -2
View File
@@ -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())),
]),
),
);
+4 -3
View File
@@ -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());
+3 -2
View File
@@ -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<PurchaseScreen> {
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<PurchaseScreen> {
],
);
},
);
));
if (widget.embedded) return body;
return Scaffold(
+2 -2
View File
@@ -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),
+3 -2
View File
@@ -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;
+20
View File
@@ -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,
),
);
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB