在这里插入图片描述

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

现场巡检时,运维人员核心诉求是快速定位符合条件的井盖点位,“附近井盖”功能正是为解决这类场景痛点设计:

  • 片区下拉:巡检人员通常按行政区划分责任范围,下拉筛选可快速聚焦当前负责片区,避免跨区域无效查找
  • 风险滑块:高风险井盖(如破损、渗漏)需要优先处理,滑块可精准筛选出高于指定风险阈值的点位
  • 列表展示:核心信息一站式呈现,编号便于台账核对,片区/地址定位物理位置,风险百分比直观判断紧急程度
  • 点击进入详情:复用已有详情页组件,减少重复开发,保证页面交互逻辑统一

这个页面是典型的“筛选+列表”组合,涵盖了 Flutter 状态管理、UI 组件复用、数据过滤等核心知识点,适合作为入门级实战示例。

2. 相关文件一览

功能落地涉及三个核心文件,职责划分清晰:

  • lib/feature_pages.dart:核心页面 NearbyCoversPage 实现,包含UI布局、交互逻辑、筛选逻辑
  • lib/mock_data.dartbuildMockCovers 方法生成模拟数据,避免依赖真实接口即可调试功能
  • lib/models.dartManholeCover 数据模型定义,规范井盖数据结构

3. Mock 数据构造

在开发初期,真实接口未就绪时,Mock 数据是高效验证功能的关键。lib/mock_data.dart 中构造了18条点位数据,分布在5个片区,核心设计思路如下:

List<ManholeCover> buildMockCovers() {
  const districts = ['东城区', '西城区', '南城区', '北城区', '高新区'];
  return List<ManholeCover>.generate(18, (i) {
    final d = districts[i % districts.length];
    final code = 'MH-${(1000 + i).toString()}';
    final risk = ((i * 7) % 100) / 100.0;
  • 片区分配:通过 i % districts.length 实现5个片区循环分配,确保每个片区都有数据,覆盖筛选场景
  • 编号生成:固定前缀 MH- + 递增数字,符合井盖台账的编号规范,便于识别
  • 风险值设计:(i * 7) % 100 / 100.0 让风险值在0~1之间均匀分布,既覆盖低、中、高风险,又能验证滑块筛选的准确性
    final x = ((i * 11) % 100) / 100.0;
    final y = ((i * 17) % 100) / 100.0;
    return ManholeCover(
      id: _uuid.v4(),
      code: code,
      district: d,
      address: '$d ${10 + i}号路口',
      risk: risk,
      x: x,
      y: y,
    );
  });
}
  • 坐标模拟:x/y 坐标通过不同质数(11、17)取模生成,避免坐标重复,模拟真实的地理位置分布
  • 地址拼接:片区+路口编号的格式,符合真实井盖地址的命名习惯,提升数据真实性
  • UUID 生成:_uuid.v4() 生成唯一ID,模拟真实业务中井盖的唯一标识,为后续详情页跳转提供基础

4. 页面主体结构

NearbyCoversPage 作为核心页面,采用 StatefulWidget 实现,因为涉及筛选条件的状态变更:

class _NearbyCoversPageState extends State<NearbyCoversPage> {
  late final List<ManholeCover> _all = buildMockCovers();
  String _district = '全部';
  double _minRisk = 0.0;
}

核心状态设计要点:

  • _all 变量:使用 late final 初始化,保证页面加载时仅构造一次Mock数据,避免重复生成导致性能损耗
  • _district 初始值:默认设为“全部”,确保页面首次加载展示所有片区的井盖,符合用户默认查看全量数据的习惯
  • _minRisk 初始值:默认0.0,即无风险阈值限制,与“全部”片区配合,实现初始状态展示所有点位

5. 片区下拉实现

片区下拉选择是核心筛选控件,需保证选项无重复且交互友好:

final districts = <String>['全部', ...{for (final e in _all) e.district}];
  • 去重处理:通过 Set 集合({for (final e in _all) e.district})自动去重,避免同一片区多次出现在下拉列表
  • 选项排序:“全部”固定在首位,符合用户操作习惯,便于快速重置筛选条件
  • 集合展开:... 展开运算符将Set转为List,适配 DropdownButtonFormField 的数据格式要求
DropdownButtonFormField<String>(
  value: _district,
  decoration: const InputDecoration(
    border: OutlineInputBorder(),
    labelText: '片区',
  ),
  • 样式设计:OutlineInputBorder 边框样式,与Flutter默认表单样式统一,提升UI一致性
  • 选中值绑定:value: _district 实现选中状态与页面状态双向绑定,用户选择后状态实时同步
  • 标签提示:labelText: '片区' 明确控件用途,降低用户理解成本
  items: districts
      .map((e) => DropdownMenuItem(value: e, child: Text(e)))
      .toList(growable: false),
  onChanged: (v) => setState(() => _district = v ?? '全部'),
)
  • 性能优化:toList(growable: false) 创建不可变列表,避免列表被意外修改,同时减少内存占用
  • 空值兜底:v ?? '全部' 处理用户可能的空选择(如下拉框异常),保证状态不会出现null值
  • 状态更新:setState 触发页面重建,筛选条件变更后列表实时刷新

6. 风险滑块实现

风险滑块用于筛选高于指定阈值的井盖,兼顾交互性和可视化:

Text('最小风险: ${(100 * _minRisk).round()}%'),
Slider(
  value: _minRisk,
  onChanged: (v) => setState(() => _minRisk = v),
),

核心设计细节:

  • 数值转换:_minRisk 是0~1的小数,乘以100并取整后展示为百分比,符合用户对“风险值”的认知习惯
  • 实时交互:onChanged 回调直接更新 _minRisk 状态,滑块拖动过程中列表实时过滤,反馈即时
  • 无额外配置:使用Slider默认的0~1取值范围,与风险值的存储格式一致,无需额外的数值映射

7. 列表过滤逻辑

过滤逻辑是页面的核心业务逻辑,需保证条件判断准确且性能高效:

final list = _all.where((e) {
  final okDistrict = _district == '全部' || e.district == _district;
  final okRisk = e.risk >= _minRisk;
  return okDistrict && okRisk;
}).toList(growable: false);

过滤规则拆解:

  • 片区判断:_district == '全部' 时跳过片区筛选,否则仅匹配指定片区的井盖,逻辑简洁且覆盖所有场景
  • 风险判断:e.risk >= _minRisk 严格匹配“高于等于阈值”的条件,符合用户“只看高风险”的核心诉求
  • 组合条件:&& 逻辑与保证只有同时满足片区和风险条件的井盖才会被展示
  • 性能优化:toList(growable: false) 生成不可变列表,避免后续操作修改过滤结果

8. 列表项 UI

列表项需清晰展示核心信息,同时保证视觉层次和交互性:

Card(
  margin: const EdgeInsets.fromLTRB(12, 6, 12, 6),
  child: ListTile(
    title: Text(c.code),
    subtitle: Text('${c.district} · ${c.address}'),

UI设计要点:

  • 容器选择:Card 组件包裹列表项,增加阴影和圆角,提升视觉层次感,区分不同井盖条目
  • 边距控制:EdgeInsets.fromLTRB(12, 6, 12, 6) 上下6px、左右12px的边距,避免条目过于紧凑或松散
  • 标题设计:title 展示井盖编号,是核心标识;subtitle 拼接片区和地址,用 · 分隔,信息层级清晰
    trailing: CircularPercentIndicator(
      radius: 18,
      lineWidth: 4,
      percent: c.risk.clamp(0.0, 1.0),
      center: Text('${(c.risk * 100).round()}%'),
      progressColor: _riskColor(c.risk),
    ),
  • 风险可视化:CircularPercentIndicator 圆形进度条直观展示风险百分比,比纯文字更易识别
  • 数值安全:c.risk.clamp(0.0, 1.0) 限制风险值范围,避免异常值导致进度条展示错误
  • 颜色映射:_riskColor 函数根据风险值返回不同颜色,红/橙/绿分别对应高/中/低风险,视觉提示更明显
    onTap: () => Navigator.of(context).push(
      MaterialPageRoute(builder: (_) => PointDetailPage(cover: c)),
    ),
  ),
)
  • 详情跳转:onTap 回调触发页面跳转,将当前井盖对象传入详情页,实现数据传递
  • 路由选择:MaterialPageRoute 是Flutter默认路由,保证跳转动画和交互符合平台规范

9. 风险颜色映射

风险颜色映射函数统一维护阈值与颜色的对应关系,便于后续维护:

Color _riskColor(double risk) {
  if (risk >= 0.7) return Colors.red;
  if (risk >= 0.3) return Colors.orange;
  return Colors.green;
}

设计思路:

  • 阈值划分:0.7和0.3作为分界点,将风险分为高(≥0.7)、中(0.3~0.7)、低(<0.3)三档,符合运维场景的风险分级标准
  • 颜色选择:红色(高风险)、橙色(中风险)、绿色(低风险)是行业通用的风险警示色,用户认知成本低
  • 维护性:所有风险颜色的逻辑集中在该函数,后续调整阈值或颜色仅需修改此处,无需逐个修改列表项

10. 完整页面代码

class NearbyCoversPage extends StatefulWidget {
  const NearbyCoversPage({super.key});

  
  State<NearbyCoversPage> createState() => _NearbyCoversPageState();
}
  • 页面基础结构:StatefulWidget 是实现状态变更的基础,构造方法添加 super.key 符合Flutter最佳实践
  • 状态类关联:createState 方法返回自定义状态类,实现页面逻辑与UI分离
class _NearbyCoversPageState extends State<NearbyCoversPage> {
  late final List<ManholeCover> _all = buildMockCovers();
  String _district = '全部';
  double _minRisk = 0.0;

  
  Widget build(BuildContext context) {
    final districts = <String>['全部', ...{for (final e in _all) e.district}];
  • 状态初始化:在状态类中初始化Mock数据和筛选条件,保证页面加载时状态就绪
  • 下拉选项生成:在 build 方法中生成片区选项,确保每次状态变更后选项列表实时更新
    final list = _all.where((e) {
      final okDistrict = _district == '全部' || e.district == _district;
      final okRisk = e.risk >= _minRisk;
      return okDistrict && okRisk;
    }).toList(growable: false);

    return Scaffold(
      appBar: AppBar(title: const Text('附近井盖')),
  • 过滤逻辑执行:在 build 方法中执行过滤,每次 setState 都会重新计算筛选结果
  • 页面骨架:Scaffold 作为页面根布局,AppBar 设置页面标题,符合Material Design规范
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(12),
            child: Row(
              children: [
                Expanded(
                  child: DropdownButtonFormField<String>(
                    value: _district,
                    decoration: const InputDecoration(
                      border: OutlineInputBorder(),
                      labelText: '片区',
                    ),
  • 布局结构:Column 分为筛选区和列表区,Padding 控制筛选区与页面边缘的间距
  • 行布局:Row 包裹片区下拉和风险滑块,实现横向排列,充分利用页面宽度
  • 占比控制:Expanded 让下拉框占满剩余宽度,保证控件布局均衡
                    items: districts
                        .map((e) => DropdownMenuItem(value: e, child: Text(e)))
                        .toList(growable: false),
                    onChanged: (v) => setState(() => _district = v ?? '全部'),
                  ),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text('最小风险: ${(100 * _minRisk).round()}%'),
                      Slider(
                        value: _minRisk,
                        onChanged: (v) => setState(() => _minRisk = v),
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
  • 间距控制:SizedBox(width: 12) 分隔下拉框和滑块,避免控件重叠,提升UI美观度
  • 滑块布局:Column 让滑块和数值文本垂直排列,CrossAxisAlignment.start 左对齐,符合阅读习惯
          Expanded(
            child: ListView.builder(
              itemCount: list.length,
              itemBuilder: (context, i) {
                final c = list[i];
                return Card(
                  margin: const EdgeInsets.fromLTRB(12, 6, 12, 6),
                  child: ListTile(
                    title: Text(c.code),
                    subtitle: Text('${c.district} · ${c.address}'),
  • 列表区布局:Expanded 让列表占满剩余高度,避免列表高度不足导致滚动异常
  • 列表构建:ListView.builder 按需构建列表项,提升大数据量下的性能
  • 数据绑定:itemBuilder 中获取过滤后的井盖对象,绑定到列表项UI
                    trailing: CircularPercentIndicator(
                      radius: 18,
                      lineWidth: 4,
                      percent: c.risk.clamp(0.0, 1.0),
                      center: Text('${(c.risk * 100).round()}%'),
                      progressColor: _riskColor(c.risk),
                    ),
                    onTap: () => Navigator.of(context).push(
                      MaterialPageRoute(builder: (_) => PointDetailPage(cover: c)),
                    ),
                  ),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}
  • 进度条配置:圆形进度条的半径、线宽等参数经过调试,保证在列表项中展示比例协调
  • 详情页跳转:点击列表项时传递井盖对象,实现详情页的数据渲染
  • 布局闭合:完整的嵌套布局保证页面结构合法,无布局溢出问题

11. 附近井盖数据模型

为了更好地管理附近井盖数据,我们需要定义一个完整的数据模型,覆盖运维场景的所有核心信息:

class NearbyManhole {
  final String id;
  final String code;
  final String name;
  final String district;
  final String address;
  final double latitude;
  final double longitude;

模型设计原则:

  • 核心标识:id(唯一ID)、code(井盖编号)、name(井盖名称),满足台账管理的基础需求
  • 位置信息:district(片区)、address(详细地址)、latitude/longitude(经纬度),覆盖定位场景
  • 不可变设计:所有字段使用 final,保证数据不可随意修改,符合Flutter状态管理的最佳实践
  final double riskLevel;
  final ManholeStatus status;
  final ManholeType type;
  final double distance;
  final DateTime createdAt;
  • 风险与状态:riskLevel(风险等级)、status(井盖状态)、type(井盖类型),满足筛选和分类需求
  • 距离信息:distance(与当前位置距离),是“附近井盖”功能的核心字段
  • 时间信息:createdAt(创建时间),记录井盖建档时间,便于追溯
  final DateTime? lastInspectionAt;
  final String? material;
  final String? manufacturer;
  final Map<String, dynamic> properties;

  const NearbyManhole({
    required this.id,
    required this.code,
    required this.name,
  • 可选字段:lastInspectionAt(最后巡检时间)、material(材质)、manufacturer(生产厂家),使用可空类型适配非必填场景
  • 扩展字段:properties 为Map类型,存储深度、直径、重量等个性化属性,提升模型扩展性
  • 构造方法:required 关键字标记必填字段,强制调用者传入核心数据,避免空值异常
    required this.district,
    required this.address,
    required this.latitude,
    required this.longitude,
    required this.riskLevel,
    required this.status,
    required this.type,
    required this.distance,
    required this.createdAt,
    this.lastInspectionAt,
    this.material,
    this.manufacturer,
    this.properties = const {},
  });
}
  • 构造方法兜底:properties 默认值为空Map,避免调用时未传入导致null值
  • 字段完整性:覆盖井盖全生命周期的核心信息,无需频繁修改模型即可适配多数运维场景
class NearbyFilter {
  final String district;
  final double minRisk;
  final double maxDistance;
  final List<ManholeStatus> statusFilter;

过滤器模型设计:

  • 基础筛选:district(片区)、minRisk(最小风险)、maxDistance(最大距离),覆盖核心筛选维度
  • 多维度筛选:statusFilter(状态筛选)支持多选,满足复杂的筛选需求
  final List<ManholeType> typeFilter;
  final String searchKeyword;
  final SortType sortBy;
  final bool ascending;

  const NearbyFilter({
    this.district = '全部',
    this.minRisk = 0.0,
    this.maxDistance = 5.0,
  • 扩展筛选:typeFilter(类型筛选)、searchKeyword(关键词搜索),覆盖精细化筛选场景
  • 排序配置:sortBy(排序维度)、ascending(升序/降序),满足列表排序需求
  • 默认值设计:所有字段设置合理默认值,避免筛选时出现空值,降低使用成本
    this.statusFilter = const [],
    this.typeFilter = const [],
    this.searchKeyword = '',
    this.sortBy = SortType.distance,
    this.ascending = true,
  });
}
  • 空列表默认值:statusFilter/typeFilter 默认空列表,代表不筛选状态/类型
  • 排序默认值:默认按距离升序排列,符合“附近井盖”按距离由近到远展示的核心诉求
class NearbyStats {
  final int totalCount;
  final int highRiskCount;
  final int maintenanceCount;
  final double averageRisk;
  final double maxDistance;
  final Map<String, int> districtDistribution;

  const NearbyStats({
    required this.totalCount,
    required this.highRiskCount,
    required this.maintenanceCount,
    required this.averageRisk,
    required this.maxDistance,
    required this.districtDistribution,
  });
}

统计模型设计:

  • 核心统计:totalCount(总数)、highRiskCount(高风险数)、maintenanceCount(待维护数),满足运维统计需求
  • 均值与极值:averageRisk(平均风险)、maxDistance(最大距离),提供数据概览
  • 分布统计:districtDistribution(片区分布),展示各片区井盖数量,便于区域管理
enum ManholeStatus {
  normal,
  damaged,
  maintenance,
  replaced,
  abandoned,
}

enum ManholeType {
  standard,
  heavy,
  composite,
  smart,
  decorative,
}

enum SortType {
  distance,
  risk,
  code,
  district,
  lastInspection,
}

枚举设计:

  • 状态枚举:覆盖井盖全生命周期状态,从正常使用到废弃,满足状态筛选需求
  • 类型枚举:包含标准、重型、复合材料等常见井盖类型,适配不同场景的井盖分类
  • 排序枚举:支持距离、风险、编号、片区、最后巡检时间排序,覆盖用户核心排序需求

12. 附近井盖状态管理

使用 Provider 管理附近井盖数据,实现状态共享和统一管理:

class NearbyManholeProvider extends ChangeNotifier {
  List<NearbyManhole> _nearbyManholes = [];
  List<NearbyManhole> _filteredManholes = [];
  NearbyFilter _filter = const NearbyFilter();
  NearbyStats? _stats;
  bool _loading = false;

状态管理设计:

  • 数据存储:_nearbyManholes(原始数据)、_filteredManholes(筛选后数据),分离原始数据和展示数据
  • 筛选配置:_filter 存储当前筛选条件,保证筛选逻辑可追溯
  • 辅助状态:_stats(统计数据)、_loading(加载状态)、_error(错误信息),覆盖数据加载全流程
  String? _error;
  double _currentLat = 39.9042;
  double _currentLng = 116.4074;

  List<NearbyManhole> get nearbyManholes => _nearbyManholes;
  List<NearbyManhole> get filteredManholes => _filteredManholes;
  NearbyFilter get filter => _filter;
  NearbyStats? get stats => _stats;
  bool get loading => _loading;
  String? get error => _error;
  • 位置信息:_currentLat/_currentLng 默认设为北京坐标,模拟用户当前位置,便于距离计算
  • 只读访问:所有状态通过getter方法暴露,避免外部直接修改状态,保证状态管理的可控性
  Future<void> loadNearbyManholes() async {
    _loading = true;
    _error = null;
    notifyListeners();

    try {
      await Future.delayed(const Duration(seconds: 1));
      
      _nearbyManholes = _generateMockNearbyManholes();
      _applyFilter();
      _calculateStats();

数据加载逻辑:

  • 加载状态:开始加载时设置 _loading = true,清空错误信息,通知监听者更新UI
  • 模拟延迟:Future.delayed 模拟网络请求延迟,贴近真实接口调用场景
  • 数据流程:生成Mock数据 → 应用筛选条件 → 计算统计数据,流程清晰且符合真实业务逻辑
      
      _loading = false;
      notifyListeners();
    } catch (e) {
      _error = e.toString();
      _loading = false;
      notifyListeners();
    }
  }
  • 异常处理:捕获加载过程中的异常,设置错误信息并更新加载状态,避免页面崩溃
  • 状态通知:关键节点调用 notifyListeners,保证UI实时响应状态变更
  void updateFilter(NearbyFilter filter) {
    _filter = filter;
    _applyFilter();
    notifyListeners();
  }

  void updateLocation(double lat, double lng) {
    _currentLat = lat;
    _currentLng = lng;
    _recalculateDistances();
    _applyFilter();
    notifyListeners();
  }

状态更新方法:

  • 筛选更新:updateFilter 接收新筛选条件,应用筛选并通知UI更新
  • 位置更新:updateLocation 接收新坐标,重新计算距离后再筛选,保证距离相关的筛选和排序准确
  void _applyFilter() {
    _filteredManholes = _nearbyManholes.where((manhole) {
      if (_filter.district != '全部' && manhole.district != _filter.district) {
        return false;
      }
      if (manhole.riskLevel < _filter.minRisk) {
        return false;
      }
      if (manhole.distance > _filter.maxDistance) {
        return false;
      }

筛选逻辑实现:

  • 片区筛选:非“全部”片区时,仅匹配指定片区的井盖
  • 风险筛选:过滤掉风险等级低于最小阈值的井盖
  • 距离筛选:过滤掉距离超过最大阈值的井盖,保证只展示“附近”的井盖
      if (_filter.statusFilter.isNotEmpty && !_filter.statusFilter.contains(manhole.status)) {
        return false;
      }
      if (_filter.typeFilter.isNotEmpty && !_filter.typeFilter.contains(manhole.type)) {
        return false;
      }
      if (_filter.searchKeyword.isNotEmpty && 
          !manhole.code.toLowerCase().contains(_filter.searchKeyword.toLowerCase()) &&
          !manhole.address.toLowerCase().contains(_filter.searchKeyword.toLowerCase())) {
        return false;
      }
      return true;
    }).toList();
    
    _sortFilteredManholes();
  }
  • 状态/类型筛选:筛选条件非空时,仅匹配包含的状态/类型,空列表时不筛选
  • 关键词搜索:忽略大小写,匹配井盖编号或地址,提升搜索的易用性
  • 排序执行:筛选完成后执行排序,保证列表展示顺序符合用户要求
  void _sortFilteredManholes() {
    switch (_filter.sortBy) {
      case SortType.distance:
        _filteredManholes.sort((a, b) => _filter.ascending 
            ? a.distance.compareTo(b.distance)
            : b.distance.compareTo(a.distance));
        break;
      case SortType.risk:
        _filteredManholes.sort((a, b) => _filter.ascending
            ? a.riskLevel.compareTo(b.riskLevel)
            : b.riskLevel.compareTo(a.riskLevel));
        break;

排序逻辑实现:

  • 距离排序:按距离升序/降序排列,满足“由近到远”或“由远到近”的查看需求
  • 风险排序:按风险等级升序/降序排列,便于优先处理高风险井盖
      case SortType.code:
        _filteredManholes.sort((a, b) => _filter.ascending
            ? a.code.compareTo(b.code)
            : b.code.compareTo(a.code));
        break;
      case SortType.district:
        _filteredManholes.sort((a, b) => _filter.ascending
            ? a.district.compareTo(b.district)
            : b.district.compareTo(a.district));
        break;
  • 编号排序:按井盖编号字母序排列,便于台账式查找
  • 片区排序:按片区名称字母序排列,便于按区域归类查看
      case SortType.lastInspection:
        _filteredManholes.sort((a, b) {
          final aTime = a.lastInspectionAt ?? DateTime(1970);
          final bTime = b.lastInspectionAt ?? DateTime(1970);
          return _filter.ascending
              ? aTime.compareTo(bTime)
              : bTime.compareTo(aTime);
        });
        break;
    }
  }
  • 巡检时间排序:处理空值(默认1970年),避免排序异常,满足按巡检时间筛选的需求
  List<NearbyManhole> _generateMockNearbyManholes() {
    const districts = ['东城区', '西城区', '南城区', '北城区', '高新区'];
    const materials = ['铸铁', '复合材料', '水泥', '不锈钢'];
    const manufacturers = ['厂家A', '厂家B', '厂家C', '厂家D'];
    
    return List<NearbyManhole>.generate(25, (index) {
      final district = districts[index % districts.length];
      final lat = _currentLat + (index % 20 - 10) * 0.001;
      final lng = _currentLng + (index % 20 - 10) * 0.001;

Mock数据生成逻辑:

  • 基础数据:定义片区、材质、厂家等基础数据列表,保证数据多样性
  • 数量控制:生成25条数据,覆盖足够的筛选和排序场景
  • 坐标生成:基于当前位置偏移,模拟真实的附近井盖地理位置分布
      final distance = _calculateDistance(_currentLat, _currentLng, lat, lng);
      
      return NearbyManhole(
        id: 'MANHOLE_${index.toString().padLeft(3, '0')}',
        code: 'MH-${(1000 + index).toString()}',
        name: '${district}井盖${index + 1}',
        district: district,
        address: '${district}${index + 1}号路口',
  • 距离计算:调用 _calculateDistance 方法,基于经纬度计算真实的球面距离
  • ID生成:固定前缀+补零数字,保证ID格式统一且唯一
  • 名称/地址:按片区+序号的格式生成,符合真实井盖的命名习惯
        latitude: lat,
        longitude: lng,
        riskLevel: 0.1 + (index % 90) / 100.0,
        status: ManholeStatus.values[index % ManholeStatus.values.length],
        type: ManholeType.values[index % ManholeType.values.length],
        distance: distance,
  • 风险值生成:0.1~1.0的范围,覆盖低、中、高风险,避免全为0或1的极端情况
  • 状态/类型分配:循环分配枚举值,保证各状态/类型都有数据,便于测试筛选功能
        createdAt: DateTime.now().subtract(Duration(days: index % 365)),
        lastInspectionAt: DateTime.now().subtract(Duration(days: index % 30)),
        material: materials[index % materials.length],
        manufacturer: manufacturers[index % manufacturers.length],
        properties: {
          'depth': 2.0 + (index % 10) * 0.1,
          'diameter': 0.6 + (index % 5) * 0.05,
          'weight': 40 + (index % 80),
        },
      );
    });
  }
  • 时间生成:创建时间和巡检时间按天偏移,模拟真实的时间分布
  • 扩展属性:深度、直径、重量等属性按固定范围偏移,符合真实井盖的参数范围
  double _calculateDistance(double lat1, double lng1, double lat2, double lng2) {
    const double earthRadius = 6371000; // 地球半径(米)
    final double dLat = _toRadians(lat2 - lat1);
    final double dLng = _toRadians(lng2 - lng1);
    final double a = sin(dLat / 2) * sin(dLat / 2) +
        cos(_toRadians(lat1)) * cos(_toRadians(lat2)) *
        sin(dLng / 2) * sin(dLng / 2);

距离计算逻辑:

  • 球面距离公式:使用Haversine公式计算两点间的球面距离,符合真实的地理距离计算规则
  • 单位转换:地球半径以米为单位,后续转换为公里,符合“附近井盖”的距离展示习惯
  • 弧度转换:经纬度差值转换为弧度,保证三角函数计算的准确性
    final double c = 2 * atan2(sqrt(a), sqrt(1 - a));
    return earthRadius * c / 1000; // 转换为公里
  }

  double _toRadians(double degrees) {
    return degrees * pi / 180;
  }
  • 公式收尾:完成Haversine公式计算,转换为公里单位,便于用户理解
  • 弧度转换方法:抽离为独立方法,提升代码复用性和可读性
  void _recalculateDistances() {
    for (final manhole in _nearbyManholes) {
      final distance = _calculateDistance(
        _currentLat,
        _currentLng,
        manhole.latitude,
        manhole.longitude,
      );
      // 更新距离(这里需要使用可变对象或重新创建对象)
    }
  }

距离重计算逻辑:

  • 遍历所有井盖:用户位置变更时,重新计算每个井盖的距离
  • 备注说明:提示需要使用可变对象或重建对象,因为当前模型字段为final,保证代码的可维护性
  void _calculateStats() {
    final totalCount = _filteredManholes.length;
    final highRiskCount = _filteredManholes.where((m) => m.riskLevel >= 0.7).length;
    final maintenanceCount = _filteredManholes.where((m) => m.status == ManholeStatus.maintenance).length;
    
    final averageRisk = totalCount > 0
        ? _filteredManholes.map((m) => m.riskLevel).reduce((a, b) => a + b) / totalCount
        : 0.0;

统计数据计算:

  • 基础统计:总数、高风险数、待维护数,直接通过过滤和计数实现
  • 平均风险:处理空列表情况,避免除以0异常,保证统计数据的有效性
    final maxDistance = _filteredManholes.isEmpty
        ? 0.0
        : _filteredManholes.map((m) => m.distance).reduce((a, b) => a > b ? a : b);
    
    final districtDistribution = <String, int>{};
    for (final manhole in _filteredManholes) {
      districtDistribution[manhole.district] = (districtDistribution[manhole.district] ?? 0) + 1;
    }
  • 极值计算:最大距离通过reduce方法获取,处理空列表情况
  • 片区分布:遍历筛选后的数据,统计各片区井盖数量,生成分布Map
    _stats = NearbyStats(
      totalCount: totalCount,
      highRiskCount: highRiskCount,
      maintenanceCount: maintenanceCount,
      averageRisk: averageRisk,
      maxDistance: maxDistance,
      districtDistribution: districtDistribution,
    );
  }
}
  • 统计对象生成:将计算结果封装为 NearbyStats 对象,便于UI层统一获取和展示统计数据

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

Logo

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

更多推荐