在这里插入图片描述

1. 这个功能解决什么问题

工单详情是巡检人员执行井盖巡检任务的核心入口,其功能设计需贴合一线巡检场景的实际需求:

  • 基础信息呈现:清晰展示工单标题、执行状态、所属片区、关联井盖编码、执行截止时间,让巡检人员快速掌握任务核心信息
  • 执行步骤引导:拆解“到达现场→检查井盖→上传结果”的标准化巡检流程,降低操作门槛
  • 操作功能支撑:提供“转应急”“提交复核”核心操作,适配巡检中突发情况与任务收尾场景
  • 复用性设计:支持从工单列表传参进入(关联具体工单),也可无参独立运行(用于UI调试、功能演示)

这个页面是典型的“详情+操作”组合,适合做 StatelessWidget + 参数传递的最佳实践示例。

2. 相关文件一览

  • lib/feature_pages.dart:核心页面文件,包含WorkOrderDetailPage组件的完整实现
  • lib/mock_data.dart:模拟数据文件,通过buildMockWorkOrders方法生成测试用工单数据

3. 参数传递设计

在Flutter中,StatelessWidget适合纯展示类页面,工单详情页无需内部状态变更,因此选择该组件类型:

class WorkOrderDetailPage extends StatelessWidget {
  final WorkOrder? order;

  const WorkOrderDetailPage({super.key, this.order});
}

关键设计要点:

  • 定义可选参数order,类型为WorkOrder?,允许传参或不传参两种场景
  • 构造方法使用super.key遵循Flutter组件规范,保证组件标识唯一性
  • 无状态设计让页面渲染更高效,符合“展示型页面”的性能优化原则

4. 默认数据兜底

为避免无参场景下页面空白,需为工单数据设置兜底逻辑:

final w = order ?? buildMockWorkOrders().first;

该设计的核心优势:

  • 独立运行能力:页面可脱离工单列表单独调试,无需依赖外部数据传递
  • 开发效率提升:UI调试时无需反复模拟传参,直接查看默认数据渲染效果
  • 体验保障:避免空指针导致的页面崩溃或空白,提升用户体验

5. 基础信息卡片

基础信息采用Card+Padding布局,兼顾美观与信息层级:

Card(
  child: Padding(
    padding: const EdgeInsets.all(12),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(w.title,
            style: Theme.of(context)
                .textTheme
                .titleLarge
                ?.copyWith(fontWeight: FontWeight.w800)),
        const SizedBox(height: 8),
        Text('状态: ${w.status}'),
      ],
    ),
  ),
)

布局与样式设计细节:

  • Card组件提供阴影与圆角,区分不同信息模块,提升视觉层次感
  • CrossAxisAlignment.start让文本左对齐,符合中文阅读习惯
  • titleLarge样式加粗处理,突出工单标题的核心地位
  • SizedBox设置8px间距,避免文本拥挤,提升可读性
  • 补充片区、关联井盖、截止时间等字段时,保持相同的间距与样式规范

6. 执行步骤说明

巡检步骤采用ListTile+Divider组合,清晰展示每一步操作要求:

Card(
  child: Column(
    children: const [
      ListTile(
        leading: Icon(Icons.check_circle_outline),
        title: Text('1. 到达现场'),
        subtitle: Text('定位到点位后开始巡检'),
      ),
      Divider(height: 1),
    ],
  ),
)

交互与视觉设计要点:

  • leading使用勾选图标,强化“步骤清单”的视觉认知
  • Divider分割线高度设为1px,弱化分割线视觉占比,保持页面简洁
  • subtitle补充步骤说明,明确每一步的操作要点(如检查井盖需关注外观、沉降等)
  • 步骤编号前置,符合用户对流程类信息的阅读习惯

7. 操作按钮

底部操作按钮采用Row+Expanded布局,保证按钮适配不同屏幕宽度:

Row(
  children: [
    Expanded(
      child: OutlinedButton(
        onPressed: () {
          ScaffoldMessenger.of(context)
              .showSnackBar(const SnackBar(content: Text('已转为应急(模拟)')));
        },
        child: const Text('转应急'),
      ),
    ),
    const SizedBox(width: 12),
  ],
)

按钮设计规范:

  • OutlinedButton用于次要操作(转应急),FilledButton用于主要操作(提交),通过样式区分操作优先级
  • Expanded让两个按钮平分宽度,保证页面布局的对称性
  • 12px间距避免按钮紧挨,提升点击容错率
  • SnackBar反馈操作结果,让用户明确操作是否生效(模拟接口调用后的提示)

8. 完整页面代码(核心片段)

整合上述模块,形成工单详情页核心代码结构:

class WorkOrderDetailPage extends StatelessWidget {
  final WorkOrder? order;

  const WorkOrderDetailPage({super.key, this.order});

  
  Widget build(BuildContext context) {
    final w = order ?? buildMockWorkOrders().first;
    return Scaffold(
      appBar: AppBar(title: const Text('工单详情')),
      body: ListView(
        padding: const EdgeInsets.all(12),
        children: [
          // 基础信息卡片
          // 执行步骤卡片
          // 操作按钮
        ],
      ),
    );
  }
}

页面结构设计逻辑:

  • Scaffold作为页面根布局,提供AppBar与body基础结构
  • ListView作为body容器,适配内容超出屏幕时的滚动需求
  • 12px全局内边距,避免内容紧贴屏幕边缘,提升视觉舒适度
  • 数据兜底逻辑在build方法内执行,保证每次构建都能获取有效数据

9. 工单详情数据模型

为规范工单数据管理,定义结构化的数据模型:

class WorkOrderDetail {
  final String id;
  final String title;
  final String description;
  final WorkOrderStatus status;
  final String district;
  
  const WorkOrderDetail({
    required this.id,
    required this.title,
    required this.description,
    required this.status,
    required this.district,
  });
}

数据模型设计原则:

  • 核心字段使用required修饰,保证数据完整性
  • 枚举类型(如WorkOrderStatus)替代字符串,避免状态值不规范
  • 可选字段(如completedAt、assignedTo)设为可空类型,适配不同工单状态
  • 容器类型字段(如attachments、steps)设置默认空数组,避免空指针

工单步骤子模型设计,细化每一步的执行信息:

class WorkOrderStep {
  final String id;
  final String title;
  final String description;
  final WorkOrderStepStatus status;

  const WorkOrderStep({
    required this.id,
    required this.title,
    required this.description,
    required this.status,
  });
}

步骤模型设计要点:

  • 独立的步骤ID,支持精准更新单个步骤状态
  • 步骤状态枚举(pending/inProgress/completed/failed),覆盖全流程状态
  • 完成时间、执行人等字段设为可空,仅在步骤完成后赋值

工单位置模型,适配地图定位场景:

class WorkOrderLocation {
  final double latitude;
  final double longitude;
  final String address;

  const WorkOrderLocation({
    required this.latitude,
    required this.longitude,
    required this.address,
  });
}

位置模型设计细节:

  • 经纬度使用double类型,保证定位精度
  • 地址字段存储详细位置信息,适配巡检人员的线下定位需求
  • 可选的description字段,补充点位特征描述(如“交叉口东南角”)

10. 工单详情状态管理

使用Provider实现工单数据的状态管理,解耦UI与数据:

class WorkOrderDetailProvider extends ChangeNotifier {
  WorkOrderDetail? _workOrder;
  bool _loading = false;
  String? _error;

  WorkOrderDetail? get workOrder => _workOrder;
  bool get loading => _loading;
  String? get error => _error;
}

状态管理核心设计:

  • 私有变量存储核心状态(工单数据、加载状态、错误信息)
  • 公开getter方法,避免外部直接修改状态,保证数据安全性
  • ChangeNotifier混入,支持状态变更时通知UI刷新

加载工单详情的核心方法,模拟异步请求流程:

Future<void> loadWorkOrderDetail(String workOrderId) async {
  _loading = true;
  _error = null;
  notifyListeners();

  try {
    await Future.delayed(const Duration(seconds: 1));
    _workOrder = _generateMockWorkOrderDetail(workOrderId);
    _loading = false;
    notifyListeners();
  } catch (e) {
    _error = e.toString();
    _loading = false;
    notifyListeners();
  }
}

异步方法设计规范:

  • 开始请求时设置loading为true,清空错误信息,通知UI显示加载状态
  • Future.delayed模拟网络请求延迟,贴合真实场景
  • try-catch捕获异常,设置错误信息,保证页面稳定性
  • 每次状态变更后调用notifyListeners,触发UI刷新

更新工单状态的方法,处理状态变更逻辑:

Future<void> updateWorkOrderStatus(WorkOrderStatus newStatus) async {
  if (_workOrder == null || _updating) return;

  _updating = true;
  notifyListeners();

  try {
    await Future.delayed(const Duration(milliseconds: 500));
    _workOrder = _workOrder!.copyWith(status: newStatus);
    _updating = false;
    notifyListeners();
  } catch (e) {
    _error = e.toString();
    _updating = false;
    notifyListeners();
  }
}

状态更新设计要点:

  • 前置判断避免重复请求(_updating为true时直接返回)
  • 500ms延迟模拟接口响应,避免操作反馈过快
  • copyWith方法更新状态,保证不可变数据规范
  • 异常处理保证更新失败时恢复状态,提示错误信息

完成工单步骤的方法,精准更新单个步骤:

Future<void> completeStep(String stepId, Map<String, dynamic> results) async {
  if (_workOrder == null || _updating) return;

  _updating = true;
  notifyListeners();

  try {
    final updatedSteps = _workOrder!.steps.map((step) {
      if (step.id == stepId) {
        return step.copyWith(status: WorkOrderStepStatus.completed);
      }
      return step;
    }).toList();
    _workOrder = _workOrder!.copyWith(steps: updatedSteps);
    _updating = false;
    notifyListeners();
  } catch (e) {
    _error = e.toString();
    _updating = false;
    notifyListeners();
  }
}

步骤更新核心逻辑:

  • map遍历步骤列表,匹配目标步骤ID后更新状态
  • copyWith方法保证原数据不被修改,符合Flutter不可变数据设计理念
  • 支持传入results参数,存储步骤执行结果(如检查记录、照片链接)

模拟数据生成方法,用于开发调试:

WorkOrderDetail _generateMockWorkOrderDetail(String workOrderId) {
  return WorkOrderDetail(
    id: workOrderId,
    title: '井盖巡检工单 #$workOrderId',
    status: WorkOrderStatus.inProgress,
    district: '东城区',
    coverCode: 'MH-1001',
  );
}

模拟数据设计要点:

  • 动态生成工单标题(拼接ID),区分不同测试工单
  • 默认状态设为“执行中”,贴合巡检场景的常见状态
  • 固定片区与井盖编码,保证测试数据的一致性

11. 高级工单详情组件

构建功能更丰富的高级详情组件,提升用户体验:

class AdvancedWorkOrderDetailWidget extends StatelessWidget {
  final WorkOrderDetail workOrder;
  final Function(WorkOrderStatus) onStatusUpdate;

  const AdvancedWorkOrderDetailWidget({
    super.key,
    required this.workOrder,
    required this.onStatusUpdate,
  });
}

高级组件设计原则:

  • 接收工单数据与回调函数,实现UI与逻辑解耦
  • 无状态设计,状态变更通过父组件回调处理
  • 泛化的回调参数(WorkOrderStatus),支持扩展更多状态操作

组件构建方法,拆分多个子模块:


Widget build(BuildContext context) {
  return SingleChildScrollView(
    padding: const EdgeInsets.all(16),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        _buildHeader(context),
        const SizedBox(height: 24),
        _buildBasicInfo(context),
      ],
    ),
  );
}

布局设计要点:

  • SingleChildScrollView适配长内容滚动,避免内容溢出
  • 16px全局内边距,比基础版更大的间距,提升高级组件的视觉舒适度
  • 24px模块间距,区分不同信息板块,增强页面呼吸感
  • CrossAxisAlignment.start保证所有模块左对齐,统一视觉风格

头部模块设计,突出核心信息:

Widget _buildHeader(BuildContext context) {
  return Card(
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Row(
        children: [
          CircleAvatar(
            backgroundColor: _getStatusColor(workOrder.status).withOpacity(0.1),
            child: Icon(_getStatusIcon(workOrder.status)),
          ),
          const SizedBox(width: 12),
          Expanded(child: Text(workOrder.title)),
        ],
      ),
    ),
  );
}

头部设计细节:

  • CircleAvatar承载状态图标,通过颜色编码区分不同状态
  • 状态图标与标题的12px间距,保证视觉平衡
  • Expanded包裹标题,避免长标题溢出
  • Card组件提升头部模块的视觉层级,突出核心信息

状态颜色映射方法,统一视觉规范:

Color _getStatusColor(WorkOrderStatus status) {
  switch (status) {
    case WorkOrderStatus.pending:
      return Colors.grey;
    case WorkOrderStatus.inProgress:
      return Colors.orange;
    case WorkOrderStatus.completed:
      return Colors.green;
    default:
      return Colors.grey;
  }
}

颜色设计规范:

  • 待处理(pending):灰色,代表未开始
  • 执行中(inProgress):橙色,代表进行中
  • 已完成(completed):绿色,代表成功完成
  • 统一的颜色映射方法,保证全页面状态颜色一致

基本信息模块,标准化展示字段:

Widget _buildBasicInfo(BuildContext context) {
  return Card(
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        children: [
          _buildInfoRow('片区', workOrder.district),
          _buildInfoRow('关联井盖', workOrder.coverCode),
        ],
      ),
    ),
  );
}

信息行设计要点:

  • 统一的_infoRow方法,保证所有字段展示样式一致
  • Card组件包裹,区分基础信息与其他模块
  • 16px内边距,保证文本与容器边缘的距离

信息行通用组件,标准化字段展示:

Widget _buildInfoRow(String label, String value) {
  return Padding(
    padding: const EdgeInsets.only(bottom: 12),
    child: Row(
      children: [
        SizedBox(width: 80, child: Text(label)),
        const SizedBox(width: 16),
        Expanded(child: Text(value)),
      ],
    ),
  );
}

信息行布局规范:

  • 标签固定80px宽度,保证所有标签对齐
  • 16px标签与值的间距,避免拥挤
  • Expanded包裹值文本,适配长文本换行
  • 12px底部间距,区分不同字段行

位置信息模块,适配地图展示:

Widget _buildLocationInfo(BuildContext context) {
  if (workOrder.location == null) return const SizedBox.shrink();

  return Card(
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        children: [
          _buildInfoRow('地址', workOrder.location!.address),
          _buildInfoRow('坐标', '${workOrder.location!.latitude}, ${workOrder.location!.longitude}'),
        ],
      ),
    ),
  );
}

位置模块设计要点:

  • 前置判断位置是否为空,避免空指针
  • 地址与坐标分开展示,满足不同场景的查看需求
  • 地图占位容器(200px高度),预留地图集成接口

步骤列表模块,可视化展示执行进度:

Widget _buildSteps(BuildContext context) {
  return Card(
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        children: workOrder.steps.map((step) {
          return _buildStepItem(context, step);
        }).toList(),
      ),
    ),
  );
}

步骤列表设计逻辑:

  • map遍历步骤数据,生成单个步骤项
  • Card组件包裹,区分步骤模块与其他信息
  • 16px内边距,保证步骤项与容器边缘的距离

单个步骤项设计,展示状态与操作:

Widget _buildStepItem(BuildContext context, WorkOrderStep step) {
  return Container(
    margin: const EdgeInsets.only(bottom: 16),
    child: Row(
      children: [
        CircleAvatar(
          radius: 16,
          backgroundColor: _getStepStatusColor(step.status),
          child: Text(step.id.substring(5)),
        ),
        const SizedBox(width: 12),
        Expanded(child: Text(step.title)),
      ],
    ),
  );
}

步骤项设计细节:

  • 圆形头像展示步骤编号,通过背景色区分状态
  • 16px头像半径,保证视觉突出且不占过多空间
  • 12px头像与标题间距,提升视觉平衡
  • 16px底部外边距,区分不同步骤项

操作按钮模块,适配状态变更:

Widget _buildActions(BuildContext context) {
  return Card(
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Row(
        children: [
          Expanded(
            child: OutlinedButton(
              onPressed: () => _showEmergencyDialog(context),
              child: const Text('转应急'),
            ),
          ),
          const SizedBox(width: 12),
        ],
      ),
    ),
  );
}

操作按钮设计要点:

  • 按钮宽度平分,保证布局对称
  • 点击事件触发对话框,避免误操作
  • OutlinedButton/FilledButton区分操作优先级
  • 12px按钮间距,提升点击容错率

对话框组件,确认关键操作:

void _showEmergencyDialog(BuildContext context) {
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('转为应急工单'),
      content: const Text('确定要将此工单转为应急工单吗?'),
      actions: [
        TextButton(onPressed: () => Navigator.pop(context), child: const Text('取消')),
        TextButton(onPressed: () => onStatusUpdate(WorkOrderStatus.assigned), child: const Text('确定')),
      ],
    ),
  );
}

对话框设计规范:

  • 明确的标题与内容,告知用户操作后果
  • 取消/确定按钮对称布局,符合用户操作习惯
  • 确定按钮触发状态更新回调,完成业务逻辑

12. 小结

工单详情的实现展现了 Flutter 开发的几个重要原则:

数据模型:复杂的工单详情结构和步骤管理

状态管理:使用 Provider 管理详情状态和更新

用户体验:加载状态、确认对话框、步骤追踪

组件化设计:可复用的详情组件和步骤项目

交互设计:状态更新、步骤完成、附件管理

这样的设计不仅满足了工单详情展示的基本需求,还为后续的功能扩展(如实时协作、多媒体支持、智能推荐等)提供了良好的基础架构。


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐