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
@@ -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 DServers:padding 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;
|
||||
|
||||
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 72 KiB |