在这里插入图片描述

第 06、07 篇我们已经把“车辆列表 + 添加车辆”跑通。
但一个真正可长期使用的工具,不能只有列表。

车辆详情页(Vehicle Detail)承担两个任务:

  • 把“车辆”当作一个实体,给用户一个确认入口。
  • 作为下钻中心,把围绕车辆的功能组织起来。

这一篇我们只做一件事:把 VehicleDetailPage 讲透。
从路由入口、参数传递、页面布局,到复用组件 _KVRow,再到两个关键的下钻按钮。

说明:本文所有代码片段都来自项目文件 lib/app/fillup_app.dart


1. 车辆详情页的定位:不是“看一眼车牌”,而是功能枢纽

很多项目把车辆详情写成:

  • 一张卡片
  • 一个车牌
  • 完事

这样做的问题是:

  • 用户看完没事可做
  • 功能入口散落在各处

在 FillUp 里,我把车辆详情页当作“以车为中心的功能枢纽”。
最直接的两个动作是:

  • 查看该车加油记录
  • 进入维护计划(Feature 占位)

2. 从哪里进入车辆详情:车辆列表项 onTap

车辆详情页的入口在 VehiclesPage 的列表项 InkWell 上。

真实代码摘录:

onTap: () => Get.toNamed('/vehicle/detail', arguments: v),

为什么这里传 arguments: v(而不是只传 id)?

  • 详情页拿到的是完整对象
  • 可以直接展示,不需要二次查库

这种方式对“详情页只展示基础字段”的场景非常划算。


3. 参数兜底:arguments 丢了也不能崩

只要你使用了路由参数传递,就必须考虑一种情况:

  • 某个入口忘了带 arguments
  • 或者你后期改了参数类型

所以 VehicleDetailPage.build 的开头做了兜底。

真实代码:

final vehicle = Get.arguments as Vehicle?;
if (vehicle == null) {
  return const Scaffold(body: Center(child: Text('无车辆数据')));
}

这段代码有两个工程价值:

  • 避免崩溃:宁可显示提示,也不要直接 throw。
  • 便于定位入口问题:看到“无车辆数据”,你就知道是路由参数链路断了。

4. 页面骨架:Scaffold + AppBar + ListView

车辆详情页的 Scaffold 很朴素,但结构清晰。

真实代码摘录:

return Scaffold(
  appBar: AppBar(title: const Text('车辆详情')),
  body: ListView(
    padding: EdgeInsets.all(16.w),
    children: <Widget>[
      Card(
        child: Padding(
          padding: EdgeInsets.all(12.w),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              Text(vehicle.name, style: Theme.of(context).textTheme.titleLarge),
              SizedBox(height: 8.h),
              _KVRow(k: '车牌/备注', v: vehicle.plateNo),
            ],
          ),
        ),
      ),
      SizedBox(height: 12.h),
      FilledButton.tonalIcon(
        onPressed: () {
          Get.toNamed('/fillups', arguments: vehicle);
        },
        icon: const Icon(Icons.local_gas_station),
        label: const Text('查看该车加油记录'),
      ),
      SizedBox(height: 12.h),
      FilledButton.tonalIcon(
        onPressed: () {
          Get.toNamed('/feature/maintenance_plan', arguments: vehicle);
        },
        icon: const Icon(Icons.build),
        label: const Text('维护计划'),
      ),
    ],
  ),
);

我在详情页继续使用 ListView,原因和“添加车辆页”一致:

  • 详情页以后一定会加更多信息
  • 用 ListView 省掉后期改布局的成本

5. 基础信息展示:Card + Padding + Column

车辆详情的第一块内容是一张卡片。
它的目标是:

  • 让用户确认“我正在看哪辆车”
  • 让信息密度足够高,但又不显得拥挤

真实代码摘录:

Card(
  child: Padding(
    padding: EdgeInsets.all(12.w),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        Text(vehicle.name, style: Theme.of(context).textTheme.titleLarge),
        SizedBox(height: 8.h),
        _KVRow(k: '车牌/备注', v: vehicle.plateNo),
      ],
    ),
  ),
),

这里我只放了两行信息:

  • 第一行:vehicle.name,用 titleLarge 强化主标题
  • 第二行:_KVRow 展示“车牌/备注”

为什么不直接 Text(vehicle.plateNo)

  • 因为详情页字段会越来越多
  • 用统一的 key-value 组件更可控

6. _KVRow:把字段展示做成可复用的小组件

_KVRow 不是为了炫技。
它解决的问题是:

  • key 左对齐
  • value 自适应
  • 多行文本不乱

真实实现如下(完整摘录):

class _KVRow extends StatelessWidget {
  final String k;
  final String v;

  const _KVRow({required this.k, required this.v});

  
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    return Padding(
      padding: EdgeInsets.only(bottom: 6.h),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          SizedBox(
            width: 84.w,
            child: Text(
              k,
              style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: cs.onSurfaceVariant),
            ),
          ),
          Expanded(child: Text(v)),
        ],
      ),
    );
  }
}

我在这里最看重两点:

  1. SizedBox(width: 84.w) 固定 key 的宽度
  2. Expanded(child: Text(v)) 让 value 自动换行

当你后续在车辆详情里加更多字段时,只需要重复:

  • _KVRow(k: 'xxx', v: 'yyy')

而不需要手写对齐逻辑。


7. 下钻按钮 1:查看该车加油记录

车辆详情页的第一个下钻动作是“查看该车加油记录”。
它是用户最常用的路径之一。

真实代码摘录:

FilledButton.tonalIcon(
  onPressed: () {
    Get.toNamed('/fillups', arguments: vehicle);
  },
  icon: const Icon(Icons.local_gas_station),
  label: const Text('查看该车加油记录'),
),

这里依然是 传对象 的策略:

  • arguments: vehicle

后续 FillUpsPage 里可以根据 vehicles.activeVehicle 或 arguments 决定展示逻辑。
在这个项目里,记录页更多依赖全局 controller 的 activeVehicle。
但把 vehicle 传过去能让未来扩展更灵活。


8. 下钻按钮 2:维护计划(Feature 占位)

第二个按钮是“维护计划”,它代表一种架构预留:

  • 详情页不应该只负责展示
  • 它也应该提供“下一步能做什么”的入口

真实代码摘录:

FilledButton.tonalIcon(
  onPressed: () {
    Get.toNamed('/feature/maintenance_plan', arguments: vehicle);
  },
  icon: const Icon(Icons.build),
  label: const Text('维护计划'),
),

注意这个路由风格:

  • /feature/<id>

这和项目里的 Feature Registry 一致。
占位按钮的价值是:

  • 先把信息架构确定
  • 后续把占位页替换成真实实现即可

9. Spacing 是一种语言:为什么我用 12.h/16.w 这种“可复用节奏”

你会看到详情页里有几处固定的间距:

padding: EdgeInsets.all(16.w),
padding: EdgeInsets.all(12.w),
SizedBox(height: 12.h),

它们的价值不是“看起来整齐”,而是让整页形成稳定节奏:

  • 页面级 padding:16
  • 卡片内部 padding:12
  • 区块间距:12

当你以后要加第三个入口按钮或加更多信息行,只要沿用这套节奏,页面不会突然变得拥挤。


10. 车辆详情为什么仍然要“少”:把复杂度留给下一层

车辆详情页里我只展示了:

  • 车辆名称
  • 车牌/备注

并提供两个强相关入口。

原因是:

  • 车辆详情页的第一职责是“确认 + 下钻”
  • 过度堆字段会让用户不知道下一步干嘛

更复杂的信息(例如:该车总花费、总油量、段数、平均油耗)
更适合放在统计页,或者在“查看该车加油记录”的列表页顶部以卡片形式呈现。


11. 未来扩展:车辆详情页怎么加“编辑车辆”

当前项目没有实现车辆编辑。
但如果你要加,我建议你沿用我们在加油编辑页里用过的思路:

  • 通过 Get.arguments 把 Vehicle 传进编辑页
  • 预填 controller 文本
  • 保存时保持 id 不变
  • controller 继续用 addOrUpdate(DB 层 upsert)

这样你不需要在数据层额外加 update。


12. 车辆详情与记录页的联动:从详情跳转后,记录页如何处理“未选择车辆”

车辆详情里我们跳转到了 /fillups
但在实际使用中,记录页可能遇到两种空态:

  • 没有选择车辆
  • 选择了车辆但没有记录

FillUpsPageObx 里做了显式处理(真实代码摘录):

final v = vehicles.activeVehicle;
final list = v == null ? <FillUpEntry>[] : fillups.displayEntries(v.id);
if (v == null) {
  return const _EmptyState(
    title: '未选择车辆',
    subtitle: '先去“车辆管理”添加/选择车辆',
    icon: Icons.directions_car,
  );
}
if (list.isEmpty) {
  return const _EmptyState(
    title: '暂无加油记录',
    subtitle: '点击右下角 + 新增第一条记录(或取消筛选)',
    icon: Icons.local_gas_station,
  );
}

这一段逻辑对“车辆详情 -> 记录页”的体验很关键:

  • 如果用户还没设置 activeVehicle,记录页会引导回车辆管理
  • 如果用户已有车辆但没数据,记录页会引导去新增第一条记录

换句话说:详情页负责把用户带到下一层能力;下一层页面负责把用户继续往前推。


小结

车辆详情页要做得“短而硬”。
它不是信息堆砌,而是把路径组织清楚:

  • 从车辆列表进详情(带 Vehicle 参数)
  • 兜底避免崩溃
  • 卡片展示基础信息
  • _KVRow 做字段对齐
  • 两个下钻按钮把用户带到下一层能力

下一篇我们会继续做第 09 篇:统计概览实现,把“围绕车辆的数字”真正展示出来。


文章底部添加社区引导:https://openharmonycrossplatform.csdn.net

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐