Files
oneapp_docs/app_car/app_carwatcher.md
2025-09-24 14:08:54 +08:00

18 KiB

App Carwatcher 车辆监控模块

模块概述

app_carwatcher 是 OneApp 车联网生态中的车辆监控模块,负责实时监控车辆状态、位置信息、安全警报等功能。该模块为用户提供全方位的车辆安全监控服务,确保车辆的安全性和用户的使用体验。

基本信息

  • 模块名称: app_carwatcher
  • 版本: 0.1.2
  • 描述: 车辆监控应用模块
  • Flutter 版本: >=2.10.5
  • Dart 版本: >=3.0.0 <4.0.0

功能特性

核心功能

  1. 实时车辆监控

    • 车辆位置实时追踪
    • 车辆状态监控(锁车状态、车窗状态等)
    • 车辆异常报警
  2. 地理围栏管理

    • 围栏设置和编辑
    • 围栏事件监控
    • POI 地点搜索
  3. 历史报告查看

    • 日报详情查看
    • 事件详情展示
    • 事件过滤功能
  4. 地图集成

    • 车辆位置可视化
    • 历史轨迹查看
    • 地理围栏设置

技术架构

目录结构

lib/
├── app_carwatcher.dart           # 模块入口文件
├── src/                          # 源代码目录
│   ├── app_carwatcher_module.dart # 模块定义和依赖注入
│   ├── route_dp.dart             # 路由配置
│   ├── route_export.dart         # 路由导出
│   ├── blocs/                    # 状态管理
│   ├── constants/                # 常量定义
│   ├── pages/                    # 页面组件
│   │   ├── home/                 # 首页
│   │   ├── geo_fence_list/       # 地理围栏列表
│   │   ├── geo_fence_detail/     # 地理围栏详情
│   │   ├── geo_fence_search_poi/ # POI搜索
│   │   ├── daily_report/         # 日报详情
│   │   ├── event_detail/         # 事件详情
│   │   └── event_filter/         # 事件过滤
│   └── utils/                    # 工具类
├── generated/                    # 代码生成文件
└── l10n/                         # 国际化文件

核心实现

1. 模块入口文件

文件: lib/app_carwatcher.dart

/// Carwatcher APP
library app_carwatcher;

export 'src/app_carwatcher_module.dart';
export 'src/route_dp.dart';
export 'src/route_export.dart';

2. 模块定义与路由配置

文件: src/app_carwatcher_module.dart

import 'package:basic_modular/modular.dart';
import 'package:basic_modular_route/basic_modular_route.dart';

import 'route_export.dart';

/// Module负责的页面
class AppCarwatcherModule extends Module with RouteObjProvider {
  @override
  List<ModularRoute> get routes {
    final moduleHome = RouteCenterAPI.routeMetaBy(AppCarwatcherRouteExport.keyModule);
    final carWatcherHomePage = RouteCenterAPI.routeMetaBy(AppCarwatcherRouteExport.keyCarWatcherHomePage);
    final geoFenceListPage = RouteCenterAPI.routeMetaBy(AppCarwatcherRouteExport.keyGeoFenceListPage);
    final geoFenceDetailPage = RouteCenterAPI.routeMetaBy(AppCarwatcherRouteExport.keyGeoFenceDetailPage);
    final searchPoiPage = RouteCenterAPI.routeMetaBy(AppCarwatcherRouteExport.keyGeoFenceSearchPOIPage);
    final dailyReportDetailPage = RouteCenterAPI.routeMetaBy(AppCarwatcherRouteExport.keyDailyReportPage);
    final eventFilterPage = RouteCenterAPI.routeMetaBy(AppCarwatcherRouteExport.keyEventFilterPage);
    final reportDetailPage = RouteCenterAPI.routeMetaBy(AppCarwatcherRouteExport.keyReportDetailPage);

    return [
      ChildRoute<dynamic>(moduleHome.path, child: (_, args) => moduleHome.provider(args).as()),
      ChildRoute<dynamic>(carWatcherHomePage.path, child: (_, args) => carWatcherHomePage.provider(args).as()),
      ChildRoute<dynamic>(geoFenceListPage.path, child: (_, args) => geoFenceListPage.provider(args).as()),
      ChildRoute<dynamic>(geoFenceDetailPage.path, child: (_, args) => geoFenceDetailPage.provider(args).as()),
      ChildRoute<dynamic>(searchPoiPage.path, child: (_, args) => searchPoiPage.provider(args).as()),
      ChildRoute<dynamic>(dailyReportDetailPage.path, child: (_, args) => dailyReportDetailPage.provider(args).as()),
      ChildRoute<dynamic>(eventFilterPage.path, child: (_, args) => eventFilterPage.provider(args).as()),
      ChildRoute<dynamic>(reportDetailPage.path, child: (_, args) => reportDetailPage.provider(args).as()),
    ];
  }
}

3. 路由导出管理

文件: src/route_export.dart

import 'package:basic_modular/modular.dart';
import 'package:clr_carwatcher/clr_carwatcher.dart';
import 'package:ui_mapview/ui_mapview.dart';

import 'app_carwatcher_module.dart';
import 'pages/daily_report/daily_report_detail_page.dart';
import 'pages/event_detail/report_event_detail_page.dart';
import 'pages/event_filter/event_filter_page.dart';
import 'pages/geo_fence_detail/geo_fence_detail_page.dart';
import 'pages/geo_fence_list/geo_fence_list_page.dart';
import 'pages/geo_fence_search_poi/geo_fence_search_poi_page.dart';
import 'pages/home/car_watcher_home_page.dart';

/// app_carwatcher的路由管理
class AppCarwatcherRouteExport implements RouteExporter {
  // 路由常量定义
  static const String keyModule = 'app_carWatcher';
  static const String keyCarWatcherHomePage = 'app_carWatcher.carWatcher_home';
  static const String keyGeoFenceListPage = 'app_carWatcher.geoFenceList';
  static const String keyGeoFenceDetailPage = 'app_carWatcher.geoFenceDetail';
  static const String keyGeoFenceSearchPOIPage = 'app_carWatcher.poiSearch';
  static const String keyDailyReportPage = 'app_carWatcher.dailyReport';
  static const String keyEventFilterPage = 'app_carWatcher.eventFilter';
  static const String keyReportDetailPage = 'app_carWatcher.reportDetail';

  @override
  List<RouteMeta> exportRoutes() {
    final r0 = RouteMeta(keyModule, '/app_carWatcher', (args) => AppCarwatcherModule(), null);
    
    final r1 = RouteMeta(keyCarWatcherHomePage, '/carWatcher_home', 
        (args) => CarWatcherHomePage(args.data['vin'] as String), r0);
    
    final r2 = RouteMeta(keyGeoFenceListPage, '/geoFenceList', 
        (args) => GeoFenceListPage(args.data['vin'] as String), r0);
    
    final r3 = RouteMeta(keyGeoFenceDetailPage, '/geoFenceDetail', 
        (args) => GeoFenceDetailPage(
          args.data['vin'] as String,
          args.data['fenceId'] as String?,
          args.data['fenceOnCount'] as int,
        ), r0);
    
    final r4 = RouteMeta(keyGeoFenceSearchPOIPage, '/poiSearch', 
        (args) => GeoFenceSearchPOIPage(
          args.data['province'] as String?,
          args.data['city'] as String?,
          args.data['cityCode'] as String?,
          args.data['fenceLatLng'] as LatLng?,
        ), r0);
    
    final r5 = RouteMeta(keyDailyReportPage, '/dailyReport', 
        (args) => DailyReportDetailPage(
          args.data['vin'] as String,
          args.data['reportId'] as String,
        ), r0);
    
    final r6 = RouteMeta(keyEventFilterPage, '/eventFilter', 
        (args) => EventFilterPage(args.data['eventTypes'] as List<EEventType>?), r0);
    
    final r7 = RouteMeta(keyReportDetailPage, '/reportDetail', 
        (args) => ReportEventDetailPage(
          args.data['vin'] as String,
          args.data['eventId'] as String,
        ), r0);

    return [r0, r1, r2, r3, r4, r5, r6, r7];
  }
}

核心页面功能

1. 车辆监控首页

文件: pages/home/car_watcher_home_page.dart

import 'package:app_consent/app_consent.dart';
import 'package:basic_intl/intl.dart';
import 'package:basic_logger/basic_logger.dart';
import 'package:basic_modular/modular.dart';
import 'package:flutter/material.dart';
import 'package:ui_basic/pull_to_refresh.dart';
import 'package:ui_basic/screen_util.dart';
import 'package:ui_basic/ui_basic.dart';

/// CarWatcher首页
class CarWatcherHomePage extends StatefulWidget with RouteObjProvider {
  const CarWatcherHomePage(this.vin, {Key? key}) : super(key: key);

  /// 车辆vin码
  final String vin;

  @override
  State<StatefulWidget> createState() => _CarWatcherHomeState();
}

class _CarWatcherHomeState extends State<CarWatcherHomePage> {
  final CarWatcherHomeBloc _bloc = CarWatcherHomeBloc();
  late List<BannerBean> images = [];

  @override
  void initState() {
    super.initState();
    
    // 初始化banner数据
    images.add(BannerBean(
      imageUrl: 'packages/app_carwatcher/assets/images/icon_banner1.png',
      title: '车辆监控横幅1',
    ));
    
    images.add(BannerBean(
      imageUrl: 'packages/app_carwatcher/assets/images/icon_banner2.png', 
      title: '车辆监控横幅2',
    ));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('车辆监控')),
      body: RefreshIndicator(
        onRefresh: () async {
          _bloc.add(RefreshDataEvent());
        },
        child: SingleChildScrollView(
          child: Column(
            children: [
              // Banner轮播
              HomeBannerWidget(images: images),
              
              // 功能入口区域
              _buildFunctionEntries(),
              
              // 车辆状态信息
              _buildVehicleStatus(),
              
              // 最近事件列表
              _buildRecentEvents(),
            ],
          ),
        ),
      ),
    );
  }
  
  Widget _buildFunctionEntries() {
    return Container(
      padding: EdgeInsets.all(16),
      child: GridView.count(
        shrinkWrap: true,
        physics: NeverScrollableScrollPhysics(),
        crossAxisCount: 2,
        children: [
          _buildFunctionCard(
            icon: Icons.location_on,
            title: '地理围栏',
            onTap: () => _navigateToGeoFenceList(),
          ),
          _buildFunctionCard(
            icon: Icons.event_note,
            title: '历史报告',
            onTap: () => _navigateToDailyReport(),
          ),
        ],
      ),
    );
  }
  
  Widget _buildFunctionCard({
    required IconData icon,
    required String title,
    required VoidCallback onTap,
  }) {
    return Card(
      child: InkWell(
        onTap: onTap,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(icon, size: 48, color: Colors.blue),
            SizedBox(height: 8),
            Text(title, style: TextStyle(fontSize: 16)),
          ],
        ),
      ),
    );
  }
  
  void _navigateToGeoFenceList() {
    Modular.to.pushNamed('/app_carWatcher/geoFenceList', arguments: {'vin': widget.vin});
  }
  
  void _navigateToDailyReport() {
    Modular.to.pushNamed('/app_carWatcher/dailyReport', arguments: {'vin': widget.vin});
  }
}

2. 地理围栏功能

围栏列表页面

/// 地理围栏列表页面
class GeoFenceListPage extends StatefulWidget {
  const GeoFenceListPage(this.vin, {Key? key}) : super(key: key);
  
  final String vin;
  
  @override
  State<GeoFenceListPage> createState() => _GeoFenceListPageState();
}

class _GeoFenceListPageState extends State<GeoFenceListPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('地理围栏'),
        actions: [
          IconButton(
            icon: Icon(Icons.add),
            onPressed: () => _navigateToCreateFence(),
          ),
        ],
      ),
      body: ListView.builder(
        itemCount: fenceList.length,
        itemBuilder: (context, index) {
          final fence = fenceList[index];
          return ListTile(
            title: Text(fence.name),
            subtitle: Text(fence.address),
            trailing: Switch(
              value: fence.isEnabled,
              onChanged: (value) => _toggleFence(fence, value),
            ),
            onTap: () => _navigateToFenceDetail(fence),
          );
        },
      ),
    );
  }
  
  void _navigateToCreateFence() {
    Modular.to.pushNamed('/app_carWatcher/geoFenceDetail', arguments: {
      'vin': widget.vin,
      'fenceId': null,
      'fenceOnCount': 0,
    });
  }
}

围栏详情页面

/// 地理围栏详情页面
class GeoFenceDetailPage extends StatefulWidget {
  const GeoFenceDetailPage(this.vin, this.fenceId, this.fenceOnCount, {Key? key}) 
      : super(key: key);
  
  final String vin;
  final String? fenceId;
  final int fenceOnCount;
  
  @override
  State<GeoFenceDetailPage> createState() => _GeoFenceDetailPageState();
}

class _GeoFenceDetailPageState extends State<GeoFenceDetailPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.fenceId == null ? '新建围栏' : '编辑围栏'),
        actions: [
          TextButton(
            onPressed: _saveFence,
            child: Text('保存'),
          ),
        ],
      ),
      body: Column(
        children: [
          // 地图显示区域
          Expanded(
            flex: 2,
            child: Container(
              color: Colors.grey[300],
              child: Center(child: Text('地图显示区域')),
            ),
          ),
          
          // 设置区域
          Expanded(
            flex: 1,
            child: Padding(
              padding: EdgeInsets.all(16),
              child: Column(
                children: [
                  TextField(
                    decoration: InputDecoration(
                      labelText: '围栏名称',
                      border: OutlineInputBorder(),
                    ),
                  ),
                  SizedBox(height: 16),
                  Row(
                    children: [
                      Text('半径(米): '),
                      Expanded(
                        child: Slider(
                          min: 100,
                          max: 5000,
                          value: 500,
                          onChanged: (value) {},
                        ),
                      ),
                      Text('500'),
                    ],
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
  
  void _saveFence() {
    // 保存围栏逻辑
    Navigator.of(context).pop();
  }
}

3. POI搜索功能

/// POI搜索页面
class GeoFenceSearchPOIPage extends StatefulWidget {
  const GeoFenceSearchPOIPage(
    this.province,
    this.city,
    this.cityCode,
    this.fenceLatLng, {
    Key? key,
  }) : super(key: key);
  
  final String? province;
  final String? city;
  final String? cityCode;
  final LatLng? fenceLatLng;
  
  @override
  State<GeoFenceSearchPOIPage> createState() => _GeoFenceSearchPOIPageState();
}

class _GeoFenceSearchPOIPageState extends State<GeoFenceSearchPOIPage> {
  final TextEditingController _searchController = TextEditingController();
  List<POIItem> _searchResults = [];
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: TextField(
          controller: _searchController,
          decoration: InputDecoration(
            hintText: '搜索地点',
            border: InputBorder.none,
            suffixIcon: IconButton(
              icon: Icon(Icons.search),
              onPressed: _performSearch,
            ),
          ),
          onSubmitted: (_) => _performSearch(),
        ),
      ),
      body: ListView.builder(
        itemCount: _searchResults.length,
        itemBuilder: (context, index) {
          final poi = _searchResults[index];
          return ListTile(
            title: Text(poi.name),
            subtitle: Text(poi.address),
            onTap: () => _selectPOI(poi),
          );
        },
      ),
    );
  }
  
  void _performSearch() {
    // 执行POI搜索
    setState(() {
      _searchResults = [
        // 模拟搜索结果
        POIItem(name: '示例地点1', address: '示例地址1'),
        POIItem(name: '示例地点2', address: '示例地址2'),
      ];
    });
  }
  
  void _selectPOI(POIItem poi) {
    Navigator.of(context).pop(poi);
  }
}

class POIItem {
  final String name;
  final String address;
  
  POIItem({required this.name, required this.address});
}

状态管理

BLoC状态管理模式

// CarWatcher首页状态管理
class CarWatcherHomeBloc extends Bloc<CarWatcherHomeEvent, CarWatcherHomeState> {
  CarWatcherHomeBloc() : super(CarWatcherHomeInitial()) {
    on<LoadDataEvent>(_onLoadData);
    on<RefreshDataEvent>(_onRefreshData);
  }
  
  Future<void> _onLoadData(LoadDataEvent event, Emitter<CarWatcherHomeState> emit) async {
    emit(CarWatcherHomeLoading());
    
    try {
      // 加载车辆数据
      final vehicleData = await loadVehicleData(event.vin);
      emit(CarWatcherHomeLoaded(vehicleData));
    } catch (e) {
      emit(CarWatcherHomeError(e.toString()));
    }
  }
  
  Future<void> _onRefreshData(RefreshDataEvent event, Emitter<CarWatcherHomeState> emit) async {
    // 刷新数据逻辑
  }
}

// 事件定义
abstract class CarWatcherHomeEvent {}

class LoadDataEvent extends CarWatcherHomeEvent {
  final String vin;
  LoadDataEvent(this.vin);
}

class RefreshDataEvent extends CarWatcherHomeEvent {}

// 状态定义
abstract class CarWatcherHomeState {}

class CarWatcherHomeInitial extends CarWatcherHomeState {}

class CarWatcherHomeLoading extends CarWatcherHomeState {}

class CarWatcherHomeLoaded extends CarWatcherHomeState {
  final VehicleData data;
  CarWatcherHomeLoaded(this.data);
}

class CarWatcherHomeError extends CarWatcherHomeState {
  final String message;
  CarWatcherHomeError(this.message);
}

依赖管理

核心依赖

  • clr_carwatcher: 车辆监控服务SDK
  • ui_mapview: 地图视图组件
  • app_consent: 用户授权管理

框架依赖

  • basic_modular: 模块化框架
  • basic_modular_route: 路由管理
  • basic_intl: 国际化支持
  • basic_logger: 日志服务

UI 组件依赖

  • ui_basic: 基础UI组件
  • flutter/material: Material Design组件

最佳实践

1. 代码组织

  • 按功能模块组织页面和组件
  • 统一的路由管理和导航
  • 清晰的状态管理模式

2. 用户体验

  • 响应式设计适配不同屏幕
  • 友好的加载和错误提示
  • 流畅的页面转场

3. 性能优化

  • 合理的状态管理避免不必要的重建
  • 地图资源的合理加载和释放
  • 网络请求的优化和缓存