626 lines
18 KiB
Markdown
626 lines
18 KiB
Markdown
# 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`
|
||
|
||
```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`
|
||
|
||
```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`
|
||
|
||
```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`
|
||
|
||
```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. 地理围栏功能
|
||
|
||
#### 围栏列表页面
|
||
```dart
|
||
/// 地理围栏列表页面
|
||
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,
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 围栏详情页面
|
||
```dart
|
||
/// 地理围栏详情页面
|
||
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搜索功能
|
||
|
||
```dart
|
||
/// 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状态管理模式
|
||
```dart
|
||
// 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. 性能优化
|
||
- 合理的状态管理避免不必要的重建
|
||
- 地图资源的合理加载和释放
|
||
- 网络请求的优化和缓存
|