fix(nodes): 桌面 Nodes 页统一 600 宽度(原型挂 view-body + Flutter PageBody)+ 双列网格改单列镜像原型

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 10:13:48 +08:00
parent a61641b53b
commit 3d24451835
6 changed files with 23 additions and 21 deletions
+21 -19
View File
@@ -13,6 +13,7 @@ import '../state/nodes_provider.dart';
import '../widgets/app_top_bar.dart';
import '../widgets/country_code.dart';
import '../widgets/pangolin_field.dart';
import '../widgets/page_body.dart';
import '../widgets/pangolin_icons.dart';
import '../widgets/server_tile.dart';
import '../widgets/smart_select_card.dart';
@@ -128,31 +129,32 @@ class _NodesPageState extends ConsumerState<NodesPage> {
]);
if (widget.isWide) {
// 对照 dapp.jsx DServerspadding 4/28、网格 gap 10。智能卡置顶(CLAUDE.md §5 规范,三端一致)。
// 桌面 Nodes 页宽度统一为 view-body(600 居中)、单列 server-row 行列表——
// 镜像原型 design/prototype/screens/ui-desktop.html 的 .nodes-view(真相源)。
// 智能卡置顶(CLAUDE.md §5 规范,三端一致)。
return RefreshIndicator(
color: c.accent,
backgroundColor: c.surface,
onRefresh: _refresh,
child: ListView(
padding: const EdgeInsets.fromLTRB(28, 4, 28, 24),
children: [
smartCard,
const SizedBox(height: 14),
ConstrainedBox(constraints: const BoxConstraints(maxWidth: 440), child: searchRow),
const SizedBox(height: 14),
GridView.count(
crossAxisCount: 2,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 4.4,
child: PageBody(
child: ListView(
padding: const EdgeInsets.fromLTRB(28, 4, 28, 24),
children: [
for (final n in nodes)
_NodeGridTile(node: n, lang: t.lang, active: !smart && n.code == selected, onTap: () => _onTapNode(n)),
smartCard,
const SizedBox(height: 14),
ConstrainedBox(constraints: const BoxConstraints(maxWidth: 440), child: searchRow),
const SizedBox(height: 14),
for (var i = 0; i < nodes.length; i++) ...[
_NodeGridTile(
node: nodes[i],
lang: t.lang,
active: !smart && nodes[i].code == selected,
onTap: () => _onTapNode(nodes[i]),
),
if (i < nodes.length - 1) const SizedBox(height: 10),
],
],
),
],
),
);
}
@@ -226,7 +228,6 @@ class _SearchBox extends StatelessWidget {
}
}
/// 宽屏双列网格中的节点块。
/// 刷新按钮(搜索框右侧):点击刷新节点列表(受 10s 限流);刷新中显示 spinner。
class _RefreshButton extends StatelessWidget {
const _RefreshButton({required this.refreshing, required this.onTap});
@@ -256,6 +257,7 @@ class _RefreshButton extends StatelessWidget {
}
}
/// 宽屏 Nodes 页单列节点行(镜像原型 .server-row);自然高度撑高,不依赖网格 aspect ratio。
class _NodeGridTile extends StatelessWidget {
const _NodeGridTile({required this.node, required this.lang, required this.active, required this.onTap});
final Node node;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 72 KiB

+2 -2
View File
@@ -116,7 +116,7 @@
.rate-chip .u{ font-size:10px; color:var(--fg3); }
/* ---- 节点视图 ---- */
.nodes-view{ padding:var(--space-5) var(--space-6) var(--space-6); }
/* .nodes-view 宽度/内边距由 view-body 原子承担(见 atoms.css),此处不再重复声明 */
.searchbar{ display:flex; align-items:center; gap:9px; background:var(--bg-subtle);
border-radius:var(--radius-md); padding:9px 13px; margin-bottom:var(--space-4); max-width:360px; }
.searchbar svg{ width:17px; height:17px; color:var(--fg3); }
@@ -474,7 +474,7 @@
<!-- 节点视图 -->
<section class="view" data-view="nodes">
<div class="nodes-view">
<div class="view-body nodes-view">
<div class="searchbar"><svg><use href="#i-search"/></svg><input data-i18n-ph="nodes.search" placeholder="搜索加速线路…"></div>
<div class="card card-flush">
<div class="server-row is-smart is-selected" data-node="smart">