# 停车场离线管理 **Repository Path**: pinedu_admin/stop-parking-app ## Basic Information - **Project Name**: 停车场离线管理 - **Description**: 一款专为停车场管理设计的离线车牌租赁管理应用,支持车辆月租和时间段租赁管理,实现快速车牌识别和费用状态查询。 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2026-01-29 - **Last Updated**: 2026-01-29 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 离线停车场车牌管理App完整开发方案书 **项目名称:** 智能停车场车牌租赁管理系统 **开发平台:** Android(华为手机适配) **开发框架:** Flutter + SQLite + HyperLPR3 **项目周期:** 全AI 2个小时 **文档版本:** V1.0 **编制日期:** 2026年1月28日 --- ## 目录 1. [项目概述](#1-项目概述) 2. [需求分析](#2-需求分析) 3. [技术架构设计](#3-技术架构设计) 4. [数据库设计](#4-数据库设计) 5. [功能模块详细设计](#5-功能模块详细设计) 6. [车牌识别技术方案](#6-车牌识别技术方案) 7. [界面设计](#7-界面设计) 8. [开发环境配置](#8-开发环境配置) 9. [项目结构](#9-项目结构) 10. [开发进度计划](#10-开发进度计划) 11. [测试方案](#11-测试方案) 12. [部署方案](#12-部署方案) 13. [风险评估与应对](#13-风险评估与应对) 14. [附录](#14-附录) --- ## 1. 项目概述 ### 1.1 项目背景 开发一款专为停车场管理设计的离线车牌租赁管理应用,支持车辆月租和时间段租赁管理,实现快速车牌识别和费用状态查询。 ### 1.2 项目目标 - 实现完全离线的数据管理和车牌识别 - 提供便捷的车辆信息录入和查询功能 - 通过拍照快速识别车牌并查询缴费状态 - 支持数据导出备份 - 适配华为手机,流畅运行 ### 1.3 核心特性 ✅ 完全离线运行,数据本地持久化 ✅ 高精度车牌识别(准确率95%+) ✅ 实时统计到期和逾期车辆 ✅ 支持数据导出Excel ✅ 简洁直观的操作界面 --- ## 2. 需求分析 ### 2.1 功能需求 #### 2.1.1 首页统计模块 - **FR-001** 显示总车辆数统计 - **FR-002** 显示即将到期车辆数(支持5天/3天/2天切换) - **FR-003** 显示逾期车辆数 - **FR-004** 显示即将到期车辆列表(车牌号、车主、剩余天数) - **FR-005** 点击统计卡片可跳转到对应筛选列表 #### 2.1.2 车辆管理模块 - **FR-006** 车辆列表展示(支持滚动加载) - **FR-007** 车辆信息新增功能 - **FR-008** 车辆信息修改功能 - **FR-009** 车辆信息删除功能(需二次确认) - **FR-010** 按车牌号模糊查询 - **FR-011** 按剩余天数范围查询 - **FR-012** 列表显示剩余天数颜色标识 - 绿色:>7天 - 橙色:3-7天 - 红色:<3天 - 灰色:已逾期 #### 2.1.3 拍照识别模块 - **FR-013** 调用相机拍照功能 - **FR-014** 实时车牌识别(离线) - **FR-015** 识别结果展示(车牌号、缴费状态、剩余天数) - **FR-016** 识别失败提示和重试功能 - **FR-017** 未找到车辆时提供添加入口 #### 2.1.4 数据导出模块 - **FR-018** 导出所有车辆数据到Excel - **FR-019** 支持按状态筛选导出(全部/有效/即将到期/已逾期) - **FR-020** 导出文件保存到手机下载目录 - **FR-021** 导出成功提示和文件路径显示 ### 2.2 非功能需求 #### 2.2.1 性能需求 - **NFR-001** 应用启动时间 < 2秒 - **NFR-002** 车牌识别响应时间 < 500ms - **NFR-003** 数据库查询响应时间 < 100ms - **NFR-004** 列表滚动流畅,FPS > 55 #### 2.2.2 可靠性需求 - **NFR-005** 数据库自动备份机制 - **NFR-006** 应用崩溃自动恢复 - **NFR-007** 数据操作事务保护 #### 2.2.3 兼容性需求 - **NFR-008** 支持Android 8.0及以上版本 - **NFR-009** 适配华为HMS服务 - **NFR-010** 支持不同屏幕尺寸(手机/平板) #### 2.2.4 安全性需求 - **NFR-011** 本地数据加密存储(可选) - **NFR-012** 删除操作二次确认 - **NFR-013** 相机权限安全管理 --- ## 3. 技术架构设计 ### 3.1 整体架构 ``` ┌─────────────────────────────────────────────────────────┐ │ Flutter UI Layer │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐│ │ │首页统计 │ │车辆管理 │ │拍照识别 │ │数据导出 ││ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘│ └─────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────┐ │ Business Logic Layer │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │车辆服务 │ │统计服务 │ │导出服务 │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────┐ │ Data Access Layer │ │ ┌──────────────┐ ┌──────────────┐ │ │ │SQLite Helper │ │数据模型 │ │ │ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────┐ │ Native Module Layer │ │ ┌──────────────────────────────────────────────────┐ │ │ │ HyperLPR3 识别引擎 (Android Kotlin) │ │ │ │ - 车牌检测模型 │ │ │ │ - 字符识别模型 │ │ │ │ - 图像预处理 │ │ │ └──────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘ ``` ### 3.2 技术栈明细 | 层级 | 技术选型 | 版本要求 | 用途说明 | |------|----------|----------|----------| | **前端框架** | Flutter | 3.19+ | 跨平台UI开发 | | **编程语言** | Dart / Kotlin | Dart 3.0+ / Kotlin 1.9+ | Flutter应用/原生模块 | | **本地数据库** | SQLite | - | 离线数据持久化 | | **数据库插件** | sqflite | 2.3.0+ | Flutter SQLite封装 | | **车牌识别** | HyperLPR3 | 3.0+ | 离线车牌识别引擎 | | **相机功能** | camera | 0.10.5+ | 调用设备相机 | | **数据导出** | excel | 4.0.0+ | 生成Excel文件 | | **状态管理** | Provider | 6.1.0+ | 应用状态管理 | | **权限管理** | permission_handler | 11.0.0+ | Android权限请求 | | **路径管理** | path_provider | 2.1.0+ | 获取文件路径 | | **日期处理** | intl | 0.18.0+ | 日期格式化 | ### 3.3 架构特点 1. **分层架构**:UI层、业务逻辑层、数据访问层清晰分离 2. **插件化设计**:车牌识别通过Platform Channel实现Flutter与原生通信 3. **离线优先**:所有核心功能完全离线运行 4. **模块解耦**:各功能模块独立,便于维护和扩展 --- ## 4. 数据库设计 ### 4.1 数据库选型:SQLite **选择理由:** - ✅ 轻量级,无需服务器 - ✅ 支持事务,数据安全 - ✅ 性能优秀,查询速度快 - ✅ Flutter官方支持良好 - ✅ 数据文件易于备份 ### 4.2 数据表设计 #### 4.2.1 车辆信息表 (vehicles) | 字段名 | 类型 | 约束 | 说明 | |--------|------|------|------| | id | INTEGER | PRIMARY KEY AUTOINCREMENT | 自增主键 | | plate_number | TEXT | NOT NULL UNIQUE | 车牌号(唯一) | | owner_name | TEXT | | 车主姓名 | | owner_phone | TEXT | | 联系电话 | | rent_type | INTEGER | NOT NULL | 租赁类型:1-月租,2-时间段租 | | start_date | TEXT | NOT NULL | 开始日期 (YYYY-MM-DD) | | end_date | TEXT | NOT NULL | 结束日期 (YYYY-MM-DD) | | rent_amount | REAL | | 租金金额 | | remark | TEXT | | 备注信息 | | created_at | TEXT | NOT NULL | 创建时间 | | updated_at | TEXT | NOT NULL | 更新时间 | **索引设计:** ```sql CREATE INDEX idx_plate_number ON vehicles(plate_number); CREATE INDEX idx_end_date ON vehicles(end_date); CREATE INDEX idx_rent_type ON vehicles(rent_type); ``` #### 4.2.2 操作日志表 (operation_logs) - 可选 | 字段名 | 类型 | 约束 | 说明 | |--------|------|------|------| | id | INTEGER | PRIMARY KEY AUTOINCREMENT | 自增主键 | | operation_type | TEXT | NOT NULL | 操作类型:add/update/delete | | plate_number | TEXT | NOT NULL | 操作的车牌号 | | operation_time | TEXT | NOT NULL | 操作时间 | | operator | TEXT | | 操作员(预留多用户) | | details | TEXT | | 操作详情JSON | ### 4.3 数据库初始化SQL ```sql -- 创建车辆信息表 CREATE TABLE IF NOT EXISTS vehicles ( id INTEGER PRIMARY KEY AUTOINCREMENT, plate_number TEXT NOT NULL UNIQUE, owner_name TEXT, owner_phone TEXT, rent_type INTEGER NOT NULL, start_date TEXT NOT NULL, end_date TEXT NOT NULL, rent_amount REAL, remark TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); -- 创建索引 CREATE INDEX IF NOT EXISTS idx_plate_number ON vehicles(plate_number); CREATE INDEX IF NOT EXISTS idx_end_date ON vehicles(end_date); CREATE INDEX IF NOT EXISTS idx_rent_type ON vehicles(rent_type); -- 创建操作日志表(可选) CREATE TABLE IF NOT EXISTS operation_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, operation_type TEXT NOT NULL, plate_number TEXT NOT NULL, operation_time TEXT NOT NULL, operator TEXT, details TEXT ); ``` ### 4.4 数据模型设计 #### Vehicle 模型 ```dart class Vehicle { final int? id; final String plateNumber; final String? ownerName; final String? ownerPhone; final int rentType; // 1: 月租, 2: 时间段租 final String startDate; final String endDate; final double? rentAmount; final String? remark; final String createdAt; final String updatedAt; Vehicle({ this.id, required this.plateNumber, this.ownerName, this.ownerPhone, required this.rentType, required this.startDate, required this.endDate, this.rentAmount, this.remark, required this.createdAt, required this.updatedAt, }); // 转换为Map(插入/更新数据库) Map toMap() { return { 'id': id, 'plate_number': plateNumber, 'owner_name': ownerName, 'owner_phone': ownerPhone, 'rent_type': rentType, 'start_date': startDate, 'end_date': endDate, 'rent_amount': rentAmount, 'remark': remark, 'created_at': createdAt, 'updated_at': updatedAt, }; } // 从Map创建对象(查询数据库) factory Vehicle.fromMap(Map map) { return Vehicle( id: map['id'], plateNumber: map['plate_number'], ownerName: map['owner_name'], ownerPhone: map['owner_phone'], rentType: map['rent_type'], startDate: map['start_date'], endDate: map['end_date'], rentAmount: map['rent_amount'], remark: map['remark'], createdAt: map['created_at'], updatedAt: map['updated_at'], ); } // 计算剩余天数 int getRemainingDays() { DateTime end = DateTime.parse(endDate); DateTime now = DateTime.now(); return end.difference(now).inDays; } // 获取状态 String getStatus() { int days = getRemainingDays(); if (days < 0) return '已逾期'; if (days <= 3) return '即将到期'; if (days <= 7) return '临近到期'; return '正常'; } // 获取状态颜色 Color getStatusColor() { int days = getRemainingDays(); if (days < 0) return Colors.grey; if (days <= 3) return Colors.red; if (days <= 7) return Colors.orange; return Colors.green; } } ``` --- ## 5. 功能模块详细设计 ### 5.1 首页统计模块 #### 5.1.1 功能描述 首页作为应用的核心入口,提供实时的车辆统计信息和快速操作入口。 #### 5.1.2 界面布局 ``` ┌─────────────────────────────────────────┐ │ 停车场管理系统 [设置] │ ├─────────────────────────────────────────┤ │ ┌───────────┐ ┌───────────┐ ┌────────┐ │ │ │总车辆数 │ │有效车辆 │ │逾期车辆│ │ │ │ 128 │ │ 120 │ │ 8 │ │ │ └───────────┘ └───────────┘ └────────┘ │ ├─────────────────────────────────────────┤ │ 即将到期车辆 [5天▼] [3天] [2天] │ │ ┌─────────────────────────────────────┐│ │ │ 粤B12345 张三 剩余3天 300元/月 ││ │ │ 粤B67890 李四 剩余2天 300元/月 ││ │ │ 粤A88888 王五 剩余1天 500元/月 ││ │ └─────────────────────────────────────┘│ ├─────────────────────────────────────────┤ │ ┌──────────┐ ┌──────────┐ ┌──────────┐│ │ │ 车辆管理 │ │ 拍照识别 │ │ 数据导出 ││ │ │ 📋 │ │ 📷 │ │ 📊 ││ │ └──────────┘ └──────────┘ └──────────┘│ └─────────────────────────────────────────┘ ``` #### 5.1.3 统计逻辑实现 ```dart class StatisticsService { final DatabaseHelper db; StatisticsService(this.db); // 获取统计数据 Future getStatistics(int warningDays) async { DateTime now = DateTime.now(); DateTime warningDate = now.add(Duration(days: warningDays)); // 总车辆数 int totalCount = await db.getVehicleCount(); // 即将到期车辆 List expiringSoon = await db.getVehiclesByDateRange( now.toString().substring(0, 10), warningDate.toString().substring(0, 10), ); // 逾期车辆 List expired = await db.getExpiredVehicles(); // 有效车辆 = 总数 - 逾期数 int validCount = totalCount - expired.length; return Statistics( totalVehicles: totalCount, validVehicles: validCount, expiredVehicles: expired.length, expiringSoonVehicles: expiringSoon, ); } } class Statistics { final int totalVehicles; final int validVehicles; final int expiredVehicles; final List expiringSoonVehicles; Statistics({ required this.totalVehicles, required this.validVehicles, required this.expiredVehicles, required this.expiringSoonVehicles, }); } ``` #### 5.1.4 交互设计 - 点击统计卡片跳转到对应筛选的车辆列表 - 切换天数标签(5天/3天/2天)实时更新即将到期列表 - 点击即将到期车辆条目查看详情 - 下拉刷新更新统计数据 --- ### 5.2 车辆管理模块 #### 5.2.1 列表页设计 **功能:** - 展示所有车辆信息 - 支持搜索和筛选 - 支持下拉刷新和上拉加载更多 - 滑动删除车辆 **列表项布局:** ``` ┌─────────────────────────────────────────────┐ │ 粤B12345 [🟢] 剩余15天 │ │ 张三 | 138****8888 2026-02-15到期 │ │ 月租 | 300元/月 │ └─────────────────────────────────────────────┘ ``` **搜索栏:** ``` ┌─────────────────────────────────────────────┐ │ 🔍 搜索车牌号或车主姓名 [筛选] │ └─────────────────────────────────────────────┘ [全部] [正常] [即将到期] [已逾期] ``` #### 5.2.2 查询功能实现 ```dart class VehicleSearchService { final DatabaseHelper db; VehicleSearchService(this.db); // 车牌模糊查询 Future> searchByPlate(String keyword) async { return await db.query( 'vehicles', where: 'plate_number LIKE ? OR owner_name LIKE ?', whereArgs: ['%$keyword%', '%$keyword%'], ); } // 按状态筛选 Future> filterByStatus(String status) async { DateTime now = DateTime.now(); switch (status) { case 'normal': // 正常:剩余天数 > 7 DateTime sevenDaysLater = now.add(Duration(days: 7)); return await db.query( 'vehicles', where: 'end_date > ?', whereArgs: [sevenDaysLater.toString().substring(0, 10)], ); case 'expiring': // 即将到期:0 < 剩余天数 <= 7 DateTime sevenDaysLater = now.add(Duration(days: 7)); return await db.query( 'vehicles', where: 'end_date BETWEEN ? AND ?', whereArgs: [ now.toString().substring(0, 10), sevenDaysLater.toString().substring(0, 10), ], ); case 'expired': // 已逾期:剩余天数 < 0 return await db.query( 'vehicles', where: 'end_date < ?', whereArgs: [now.toString().substring(0, 10)], ); default: return await db.getAllVehicles(); } } // 按剩余天数范围查询 Future> searchByRemainingDays(int minDays, int maxDays) async { DateTime now = DateTime.now(); DateTime minDate = now.add(Duration(days: minDays)); DateTime maxDate = now.add(Duration(days: maxDays)); return await db.query( 'vehicles', where: 'end_date BETWEEN ? AND ?', whereArgs: [ minDate.toString().substring(0, 10), maxDate.toString().substring(0, 10), ], orderBy: 'end_date ASC', ); } } ``` #### 5.2.3 新增/编辑表单设计 **表单字段:** ``` ┌─────────────────────────────────────────────┐ │ 添加车辆信息 [保存] │ ├─────────────────────────────────────────────┤ │ 车牌号 * │ │ ┌───────────────────────────────────────┐ │ │ │ 粤B12345 [📷 识别] │ │ │ └───────────────────────────────────────┘ │ │ │ │ 车主姓名 │ │ ┌───────────────────────────────────────┐ │ │ │ 张三 │ │ │ └───────────────────────────────────────┘ │ │ │ │ 联系电话 │ │ ┌───────────────────────────────────────┐ │ │ │ 138****8888 │ │ │ └───────────────────────────────────────┘ │ │ │ │ 租赁类型 * │ │ ○ 月租 ○ 时间段租 │ │ │ │ 开始日期 * │ │ ┌───────────────────────────────────────┐ │ │ │ 2026-01-28 [📅] │ │ │ └───────────────────────────────────────┘ │ │ │ │ 结束日期 * │ │ ┌───────────────────────────────────────┐ │ │ │ 2026-02-28 [📅] │ │ │ └───────────────────────────────────────┘ │ │ │ │ 租金金额 │ │ ┌───────────────────────────────────────┐ │ │ │ 300 │ │ │ └───────────────────────────────────────┘ │ │ │ │ 备注 │ │ ┌───────────────────────────────────────┐ │ │ │ │ │ │ │ │ │ │ └───────────────────────────────────────┘ │ └─────────────────────────────────────────────┘ ``` **表单验证规则:** - 车牌号:必填,格式验证(省份+字母+数字) - 开始日期/结束日期:必填,结束日期必须晚于开始日期 - 租赁类型:必填 - 租金金额:可选,数字验证 - 电话:可选,11位数字验证 #### 5.2.4 删除功能设计 **二次确认对话框:** ``` ┌─────────────────────────────────────────────┐ │ 确认删除 │ ├─────────────────────────────────────────────┤ │ 确定要删除车辆 [粤B12345] 的信息吗? │ │ 此操作不可恢复! │ ├─────────────────────────────────────────────┤ │ [取消] [确定删除] │ └─────────────────────────────────────────────┘ ``` --- ### 5.3 拍照识别模块 #### 5.3.1 功能流程 ``` 用户点击拍照 ↓ 打开相机界面 ↓ 用户拍摄车牌照片 ↓ 调用HyperLPR3识别 ↓ 返回车牌号 ↓ 查询数据库 ↓ 显示车辆信息 / 未找到提示 ``` #### 5.3.2 相机界面设计 ``` ┌─────────────────────────────────────────────┐ │ [←] 拍照识别 [💡] │ ├─────────────────────────────────────────────┤ │ │ │ [相机预览画面] │ │ │ │ ┌─────────────────────────┐ │ │ │ │ │ │ │ 请将车牌对准框内 │ │ │ │ │ │ │ └─────────────────────────┘ │ │ │ │ │ │ │ │ ⭕ │ │ [拍照按钮] │ └─────────────────────────────────────────────┘ ``` **引导框特性:** - 绿色边框提示拍摄区域 - 固定比例(适合车牌比例约3:1) - 半透明遮罩突出拍摄区域 #### 5.3.3 识别结果展示 **找到车辆:** ``` ┌─────────────────────────────────────────────┐ │ 识别成功 [×] │ ├─────────────────────────────────────────────┤ │ 车牌号:粤B12345 │ │ │ │ ✅ 已缴费 │ │ │ │ 车主:张三 │ │ 联系电话:138****8888 │ │ 租赁类型:月租 │ │ 开始日期:2026-01-01 │ │ 到期日期:2026-02-28 │ │ 剩余天数:31天 │ │ 租金:300元/月 │ │ 备注:VIP客户 │ ├─────────────────────────────────────────────┤ │ [查看详情] [编辑] [重新拍摄] │ └─────────────────────────────────────────────┘ ``` **未找到车辆:** ``` ┌─────────────────────────────────────────────┐ │ 识别结果 [×] │ ├─────────────────────────────────────────────┤ │ 车牌号:粤B99999 │ │ │ │ ❌ 未找到该车辆信息 │ │ │ │ 该车辆未办理停车租赁,请引导车主办理。 │ ├─────────────────────────────────────────────┤ │ [添加车辆] [重新拍摄] │ └─────────────────────────────────────────────┘ ``` **识别失败:** ``` ┌─────────────────────────────────────────────┐ │ 识别失败 [×] │ ├─────────────────────────────────────────────┤ │ ⚠️ 未能识别到车牌号 │ │ │ │ 可能原因: │ │ • 光线不足 │ │ • 车牌污损 │ │ • 拍摄角度不佳 │ │ │ │ 建议: │ │ • 在光线充足处拍摄 │ │ • 保持相机与车牌平行 │ │ • 确保车牌清晰完整 │ ├─────────────────────────────────────────────┤ │ [重新拍摄] [手动输入] │ └─────────────────────────────────────────────┘ ``` --- ### 5.4 数据导出模块 #### 5.4.1 导出选项界面 ``` ┌─────────────────────────────────────────────┐ │ 数据导出 [×] │ ├─────────────────────────────────────────────┤ │ 导出范围: │ │ ○ 全部车辆 (128辆) │ │ ○ 有效车辆 (120辆) │ │ ○ 即将到期车辆 (15辆) │ │ ○ 已逾期车辆 (8辆) │ │ │ │ 导出格式: │ │ ● Excel (.xlsx) │ │ ○ CSV (.csv) │ │ │ │ 文件名: │ │ ┌───────────────────────────────────────┐ │ │ │ 停车数据_20260128 │ │ │ └───────────────────────────────────────┘ │ │ │ │ 保存位置: │ │ /storage/emulated/0/Download/ │ ├─────────────────────────────────────────────┤ │ [取消] [开始导出] │ └─────────────────────────────────────────────┘ ``` #### 5.4.2 导出Excel格式 | 车牌号 | 车主姓名 | 联系电话 | 租赁类型 | 开始日期 | 结束日期 | 剩余天数 | 状态 | 租金(元) | 备注 | |--------|----------|----------|----------|----------|----------|----------|------|----------|------| | 粤B12345 | 张三 | 138****8888 | 月租 | 2026-01-01 | 2026-02-28 | 31 | 正常 | 300 | VIP客户 | | 粤B67890 | 李四 | 139****9999 | 月租 | 2026-01-15 | 2026-02-15 | 18 | 正常 | 300 | - | #### 5.4.3 导出实现代码 ```dart import 'package:excel/excel.dart'; import 'package:path_provider/path_provider.dart'; import 'dart:io'; class ExportService { final DatabaseHelper db; ExportService(this.db); Future exportToExcel({String filter = 'all'}) async { try { // 1. 获取数据 List vehicles = await _getVehiclesByFilter(filter); if (vehicles.isEmpty) { throw Exception('没有可导出的数据'); } // 2. 创建Excel var excel = Excel.createExcel(); Sheet sheet = excel['车辆租赁信息']; // 3. 设置表头样式 CellStyle headerStyle = CellStyle( bold: true, fontSize: 12, backgroundColorHex: '#4CAF50', fontColorHex: '#FFFFFF', ); // 4. 写入表头 List headers = [ '车牌号', '车主姓名', '联系电话', '租赁类型', '开始日期', '结束日期', '剩余天数', '状态', '租金(元)', '备注' ]; for (int i = 0; i < headers.length; i++) { var cell = sheet.cell(CellIndex.indexByColumnRow(columnIndex: i, rowIndex: 0)); cell.value = headers[i]; cell.cellStyle = headerStyle; } // 5. 写入数据 for (int i = 0; i < vehicles.length; i++) { Vehicle v = vehicles[i]; int rowIndex = i + 1; sheet.cell(CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: rowIndex)) .value = v.plateNumber; sheet.cell(CellIndex.indexByColumnRow(columnIndex: 1, rowIndex: rowIndex)) .value = v.ownerName ?? '-'; sheet.cell(CellIndex.indexByColumnRow(columnIndex: 2, rowIndex: rowIndex)) .value = v.ownerPhone ?? '-'; sheet.cell(CellIndex.indexByColumnRow(columnIndex: 3, rowIndex: rowIndex)) .value = v.rentType == 1 ? '月租' : '时间段租'; sheet.cell(CellIndex.indexByColumnRow(columnIndex: 4, rowIndex: rowIndex)) .value = v.startDate; sheet.cell(CellIndex.indexByColumnRow(columnIndex: 5, rowIndex: rowIndex)) .value = v.endDate; sheet.cell(CellIndex.indexByColumnRow(columnIndex: 6, rowIndex: rowIndex)) .value = v.getRemainingDays(); sheet.cell(CellIndex.indexByColumnRow(columnIndex: 7, rowIndex: rowIndex)) .value = v.getStatus(); sheet.cell(CellIndex.indexByColumnRow(columnIndex: 8, rowIndex: rowIndex)) .value = v.rentAmount ?? 0; sheet.cell(CellIndex.indexByColumnRow(columnIndex: 9, rowIndex: rowIndex)) .value = v.remark ?? '-'; } // 6. 保存文件 String timestamp = DateTime.now().millisecondsSinceEpoch.toString(); String fileName = '停车数据_$timestamp.xlsx'; Directory? directory = await getExternalStorageDirectory(); String savePath = '/storage/emulated/0/Download/$fileName'; File file = File(savePath); await file.create(recursive: true); await file.writeAsBytes(excel.encode()!); return savePath; } catch (e) { print('导出失败: $e'); return null; } } Future> _getVehiclesByFilter(String filter) async { switch (filter) { case 'valid': return await db.getValidVehicles(); case 'expiring': return await db.getExpiringSoonVehicles(7); case 'expired': return await db.getExpiredVehicles(); default: return await db.getAllVehicles(); } } } ``` --- ## 6. 车牌识别技术方案 ### 6.1 HyperLPR3 技术介绍 **HyperLPR3** 是一款高性能的开源车牌识别系统,支持以下特性: ✅ **高准确率**:识别准确率达95%以上 ✅ **高速度**:单张图片识别时间<200ms ✅ **多车牌支持**:蓝牌、黄牌、新能源、警牌等 ✅ **完全离线**:无需联网,模型本地部署 ✅ **跨平台**:支持Android、iOS、Windows、Linux **GitHub:** https://github.com/szad670401/HyperLPR ### 6.2 技术架构 ``` ┌──────────────────────────────────────────────┐ │ Flutter Camera Layer │ │ (拍照、图片预览、UI交互) │ └──────────────────┬───────────────────────────┘ │ Platform Channel ┌──────────────────┴───────────────────────────┐ │ Android Native Layer (Kotlin) │ │ │ │ ┌─────────────────────────────────────────┐ │ │ │ HyperLPR3 Recognition Engine │ │ │ │ │ │ │ │ ┌────────────┐ ┌─────────────────┐ │ │ │ │ │ 车牌检测 │ → │ 字符识别 │ │ │ │ │ │ (YOLOv5) │ │ (CRNN) │ │ │ │ │ └────────────┘ └─────────────────┘ │ │ │ │ ↓ ↓ │ │ │ │ 定位车牌框 识别车牌号 │ │ │ └─────────────────────────────────────────┘ │ └──────────────────────────────────────────────┘ ``` ### 6.3 Android集成步骤 #### 步骤1:添加依赖 **下载SDK:** ```bash # 从GitHub下载HyperLPR3 Android SDK git clone https://github.com/szad670401/HyperLPR.git cd HyperLPR/android # 将 hyperlpr3.aar 复制到项目的 libs 目录 cp HyperLPR3/libs/hyperlpr3.aar YourProject/android/app/libs/ ``` **修改 `android/app/build.gradle`:** ```gradle android { defaultConfig { // ... 其他配置 ndk { // 指定支持的CPU架构 abiFilters 'armeabi-v7a', 'arm64-v8a' } } // 指定libs目录 repositories { flatDir { dirs 'libs' } } } dependencies { // HyperLPR3 implementation(name: 'hyperlpr3', ext: 'aar') // TensorFlow Lite(如果SDK需要) implementation 'org.tensorflow:tensorflow-lite:2.13.0' // 其他依赖... } ``` #### 步骤2:添加模型文件 将模型文件放置到 `android/app/src/main/assets/` 目录: ``` assets/ ├── plate_detect.tflite # 车牌检测模型 ├── plate_rec.tflite # 字符识别模型 └── plate_rec_dict.txt # 字符字典 ``` #### 步骤3:创建识别工具类 **`android/app/src/main/kotlin/com/parking/app/PlateRecognizer.kt`:** ```kotlin package com.parking.app import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory import com.hyperai.hyperlpr3.HyperLPR3 import com.hyperai.hyperlpr3.bean.HyperLPRParameter import java.io.File class PlateRecognizer(private val context: Context) { private var recognizer: HyperLPR3? = null init { initRecognizer() } /** * 初始化识别器 */ private fun initRecognizer() { try { // 配置参数 val parameter = HyperLPRParameter() .setDetLevel(HyperLPR3.DETECT_LEVEL_LOW) // 检测级别:LOW/MEDIUM/HIGH .setMaxNum(1) // 最多识别1个车牌 .setRecConfidenceThreshold(0.85f) // 识别置信度阈值 // 初始化 recognizer = HyperLPR3.getInstance(context) recognizer?.init(parameter) println("HyperLPR3 初始化成功") } catch (e: Exception) { e.printStackTrace() println("HyperLPR3 初始化失败: ${e.message}") } } /** * 识别车牌 * @param imagePath 图片路径 * @return PlateResult 识别结果 */ fun recognize(imagePath: String): PlateResult { try { // 检查文件是否存在 val file = File(imagePath) if (!file.exists()) { return PlateResult( success = false, plateNumber = null, confidence = 0f, error = "图片文件不存在" ) } // 加载图片 val bitmap = BitmapFactory.decodeFile(imagePath) if (bitmap == null) { return PlateResult( success = false, plateNumber = null, confidence = 0f, error = "图片加载失败" ) } // 执行识别 val results = recognizer?.plateRecognition(bitmap) // 释放bitmap bitmap.recycle() // 处理结果 if (results != null && results.isNotEmpty()) { val plate = results[0] // 检查置信度 if (plate.confidence > 0.7) { return PlateResult( success = true, plateNumber = plate.code, confidence = plate.confidence, error = null ) } else { return PlateResult( success = false, plateNumber = plate.code, confidence = plate.confidence, error = "识别置信度过低" ) } } else { return PlateResult( success = false, plateNumber = null, confidence = 0f, error = "未检测到车牌" ) } } catch (e: Exception) { e.printStackTrace() return PlateResult( success = false, plateNumber = null, confidence = 0f, error = "识别异常: ${e.message}" ) } } /** * 释放资源 */ fun release() { try { recognizer?.release() println("HyperLPR3 资源已释放") } catch (e: Exception) { e.printStackTrace() } } } /** * 识别结果数据类 */ data class PlateResult( val success: Boolean, val plateNumber: String?, val confidence: Float, val error: String? ) ``` #### 步骤4:创建Platform Channel **`android/app/src/main/kotlin/com/parking/app/MainActivity.kt`:** ```kotlin package com.parking.app import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel class MainActivity: FlutterActivity() { private val CHANNEL = "com.parking/plate_recognition" private lateinit var recognizer: PlateRecognizer override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) // 初始化识别器 recognizer = PlateRecognizer(this) // 设置方法通道 MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL) .setMethodCallHandler { call, result -> when (call.method) { "recognizePlate" -> { // 识别车牌 val imagePath = call.argument("imagePath") if (imagePath != null) { val plateResult = recognizer.recognize(imagePath) // 返回结果 val resultMap = mapOf( "success" to plateResult.success, "plateNumber" to plateResult.plateNumber, "confidence" to plateResult.confidence, "error" to plateResult.error ) result.success(resultMap) } else { result.error("INVALID_PATH", "图片路径无效", null) } } "checkInitialized" -> { // 检查是否初始化成功 result.success(true) } else -> { result.notImplemented() } } } } override fun onDestroy() { super.onDestroy() recognizer.release() } } ``` #### 步骤5:Flutter端调用 **`lib/services/plate_recognition_service.dart`:** ```dart import 'dart:io'; import 'package:flutter/services.dart'; class PlateRecognitionService { static const platform = MethodChannel('com.parking/plate_recognition'); /// 识别结果 static Future recognizePlate(String imagePath) async { try { // 检查文件是否存在 if (!await File(imagePath).exists()) { return PlateRecognitionResult( success: false, plateNumber: null, confidence: 0.0, error: '图片文件不存在', ); } // 调用原生方法 final Map result = await platform.invokeMethod( 'recognizePlate', {'imagePath': imagePath}, ); return PlateRecognitionResult( success: result['success'] ?? false, plateNumber: result['plateNumber'], confidence: (result['confidence'] ?? 0.0).toDouble(), error: result['error'], ); } on PlatformException catch (e) { print('车牌识别失败: ${e.message}'); return PlateRecognitionResult( success: false, plateNumber: null, confidence: 0.0, error: e.message ?? '未知错误', ); } catch (e) { print('未知错误: $e'); return PlateRecognitionResult( success: false, plateNumber: null, confidence: 0.0, error: '系统错误: $e', ); } } /// 检查识别引擎是否初始化 static Future checkInitialized() async { try { final bool result = await platform.invokeMethod('checkInitialized'); return result; } catch (e) { return false; } } } /// 识别结果模型 class PlateRecognitionResult { final bool success; final String? plateNumber; final double confidence; final String? error; PlateRecognitionResult({ required this.success, this.plateNumber, required this.confidence, this.error, }); @override String toString() { return 'PlateRecognitionResult(success: $success, plateNumber: $plateNumber, confidence: $confidence, error: $error)'; } } ``` ### 6.4 使用示例 ```dart // 在拍照识别页面中使用 Future _recognizePlate(String imagePath) async { setState(() => _isRecognizing = true); try { // 调用识别服务 final result = await PlateRecognitionService.recognizePlate(imagePath); if (result.success && result.plateNumber != null) { // 识别成功 print('识别成功: ${result.plateNumber}'); print('置信度: ${result.confidence}'); // 查询数据库 final vehicle = await DatabaseHelper.instance .getVehicleByPlate(result.plateNumber!); // 显示结果 _showResultDialog(result.plateNumber!, vehicle); } else { // 识别失败 _showErrorDialog(result.error ?? '未能识别到车牌'); } } finally { setState(() => _isRecognizing = false); } } ``` ### 6.5 优化建议 #### 6.5.1 图片预处理 ```kotlin // 在识别前对图片进行预处理 fun preprocessImage(bitmap: Bitmap): Bitmap { // 1. 调整大小(提高识别速度) val maxSize = 1024 if (bitmap.width > maxSize || bitmap.height > maxSize) { val scale = maxSize.toFloat() / Math.max(bitmap.width, bitmap.height) val width = (bitmap.width * scale).toInt() val height = (bitmap.height * scale).toInt() return Bitmap.createScaledBitmap(bitmap, width, height, true) } return bitmap } ``` #### 6.5.2 提高识别率技巧 1. **光线充足**:在光线良好的环境下拍摄 2. **角度正对**:相机与车牌保持平行 3. **距离适中**:车牌占画面的1/3-1/2 4. **避免抖动**:拍摄时保持稳定 5. **清洁车牌**:确保车牌表面清洁 #### 6.5.3 错误处理 ```dart // 识别失败时的重试机制 int _retryCount = 0; final int _maxRetry = 3; Future _recognizeWithRetry(String imagePath) async { for (int i = 0; i < _maxRetry; i++) { final result = await PlateRecognitionService.recognizePlate(imagePath); if (result.success) { _retryCount = 0; return _handleSuccess(result); } _retryCount++; if (_retryCount >= _maxRetry) { return _handleFailure('识别失败,请重新拍摄'); } await Future.delayed(Duration(milliseconds: 500)); } } ``` --- ## 7. 界面设计 ### 7.1 设计原则 - **简洁明了**:界面清晰,功能一目了然 - **操作便捷**:减少点击次数,提高效率 - **视觉友好**:颜色搭配合理,状态区分明显 - **适配华为**:遵循华为设计规范 ### 7.2 配色方案 | 颜色类型 | 颜色值 | 用途 | |----------|--------|------| | 主色调 | #4CAF50 (绿色) | 主要按钮、强调元素 | | 辅助色 | #2196F3 (蓝色) | 次要按钮、链接 | | 成功色 | #4CAF50 (绿色) | 成功提示、正常状态 | | 警告色 | #FF9800 (橙色) | 警告提示、即将到期 | | 危险色 | #F44336 (红色) | 错误提示、逾期状态 | | 灰色 | #9E9E9E | 已逾期、禁用状态 | | 背景色 | #FAFAFA | 页面背景 | | 卡片色 | #FFFFFF | 卡片背景 | ### 7.3 页面设计规范 #### 7.3.1 导航栏 - 高度:56dp - 标题字体:18sp,粗体 - 返回按钮:左侧24dp - 操作按钮:右侧16dp #### 7.3.2 列表项 - 高度:80-100dp - 内边距:16dp - 圆角:8dp - 阴影:2dp elevation #### 7.3.3 按钮 - 主按钮:高度48dp,圆角24dp - 次按钮:高度40dp,圆角20dp - 文字按钮:高度36dp,无圆角 #### 7.3.4 间距规范 - 小间距:8dp - 中间距:16dp - 大间距:24dp - 超大间距:32dp ### 7.4 完整页面原型 由于篇幅限制,完整的UI原型图建议使用Figma或Sketch制作,包含: 1. 首页统计页面 2. 车辆列表页面 3. 车辆新增/编辑页面 4. 拍照识别页面 5. 识别结果页面 6. 数据导出页面 7. 设置页面 --- ## 8. 开发环境配置 ### 8.1 开发工具安装 #### 8.1.1 Flutter SDK ```bash # 下载Flutter SDK # https://docs.flutter.dev/get-started/install # 配置环境变量 export PATH="$PATH:`pwd`/flutter/bin" # 验证安装 flutter doctor ``` #### 8.1.2 Android Studio - 下载安装Android Studio - 安装Flutter插件 - 安装Dart插件 - 配置Android SDK #### 8.1.3 华为HMS配置 **步骤1:注册华为开发者账号** 访问:https://developer.huawei.com/ **步骤2:创建应用** - 登录AppGallery Connect - 创建项目和应用 - 下载 `agconnect-services.json` **步骤3:配置项目** `android/build.gradle`: ```gradle buildscript { repositories { google() mavenCentral() maven { url 'https://developer.huawei.com/repo/' } } dependencies { classpath 'com.android.tools.build:gradle:7.3.0' classpath 'com.huawei.agconnect:agcp:1.9.1.300' } } ``` `android/app/build.gradle`: ```gradle apply plugin: 'com.android.application' apply plugin: 'com.huawei.agconnect' android { compileSdkVersion 33 defaultConfig { applicationId "com.parking.app" minSdkVersion 24 targetSdkVersion 33 versionCode 1 versionName "1.0.0" } } dependencies { implementation 'com.huawei.hms:base:6.10.0.300' } ``` ### 8.2 项目初始化 ```bash # 创建Flutter项目 flutter create parking_management cd parking_management # 添加依赖 flutter pub add sqflite flutter pub add path_provider flutter pub add camera flutter pub add excel flutter pub add provider flutter pub add permission_handler flutter pub add intl # 运行项目 flutter run ``` ### 8.3 依赖配置文件 **`pubspec.yaml`:** ```yaml name: parking_management description: 离线停车场车牌管理系统 version: 1.0.0+1 environment: sdk: '>=3.0.0 <4.0.0' dependencies: flutter: sdk: flutter # 本地数据库 sqflite: ^2.3.0 path_provider: ^2.1.0 path: ^1.8.3 # 相机功能 camera: ^0.10.5+5 image_picker: ^1.0.4 # 数据导出 excel: ^4.0.0 # 状态管理 provider: ^6.1.0 # 权限管理 permission_handler: ^11.0.1 # 日期处理 intl: ^0.18.1 # UI组件 cupertino_icons: ^1.0.6 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^3.0.0 flutter: uses-material-design: true # 资源文件 assets: - assets/images/ - assets/models/ ``` --- ## 9. 项目结构 ``` parking_management/ ├── android/ # Android原生代码 │ ├── app/ │ │ ├── src/main/ │ │ │ ├── kotlin/ │ │ │ │ └── com/parking/app/ │ │ │ │ ├── MainActivity.kt # 主Activity │ │ │ │ └── PlateRecognizer.kt # 车牌识别工具类 │ │ │ ├── res/ # 资源文件 │ │ │ └── AndroidManifest.xml # 清单文件 │ │ └── build.gradle # 应用级gradle配置 │ └── build.gradle # 项目级gradle配置 │ ├── lib/ # Flutter代码 │ ├── main.dart # 应用入口 │ │ │ ├── models/ # 数据模型 │ │ ├── vehicle.dart # 车辆模型 │ │ ├── statistics.dart # 统计模型 │ │ └── plate_recognition_result.dart # 识别结果模型 │ │ │ ├── database/ # 数据库层 │ │ ├── database_helper.dart # 数据库帮助类 │ │ └── database_config.dart # 数据库配置 │ │ │ ├── services/ # 业务逻辑层 │ │ ├── vehicle_service.dart # 车辆服务 │ │ ├── statistics_service.dart # 统计服务 │ │ ├── plate_recognition_service.dart # 识别服务 │ │ ├── export_service.dart # 导出服务 │ │ └── search_service.dart # 搜索服务 │ │ │ ├── screens/ # 页面 │ │ ├── home/ │ │ │ ├── home_screen.dart # 首页 │ │ │ └── widgets/ │ │ │ ├── statistics_card.dart # 统计卡片 │ │ │ └── expiring_vehicle_list.dart # 即将到期列表 │ │ │ │ │ ├── vehicle/ │ │ │ ├── vehicle_list_screen.dart # 车辆列表 │ │ │ ├── vehicle_form_screen.dart # 新增/编辑表单 │ │ │ ├── vehicle_detail_screen.dart # 车辆详情 │ │ │ └── widgets/ │ │ │ ├── vehicle_card.dart # 车辆卡片 │ │ │ └── vehicle_search_bar.dart # 搜索栏 │ │ │ │ │ ├── recognition/ │ │ │ ├── camera_screen.dart # 拍照页面 │ │ │ ├── recognition_result_screen.dart # 识别结果 │ │ │ └── widgets/ │ │ │ └── camera_guide.dart # 相机引导框 │ │ │ │ │ ├── export/ │ │ │ └── export_screen.dart # 数据导出 │ │ │ │ │ └── settings/ │ │ └── settings_screen.dart # 设置页面 │ │ │ ├── widgets/ # 通用组件 │ │ ├── custom_button.dart # 自定义按钮 │ │ ├── custom_text_field.dart # 自定义输入框 │ │ ├── loading_dialog.dart # 加载对话框 │ │ ├── confirm_dialog.dart # 确认对话框 │ │ └── empty_state.dart # 空状态页面 │ │ │ ├── utils/ # 工具类 │ │ ├── constants.dart # 常量定义 │ │ ├── date_utils.dart # 日期工具 │ │ ├── validator.dart # 表单验证 │ │ ├── color_utils.dart # 颜色工具 │ │ └── permission_utils.dart # 权限工具 │ │ │ └── providers/ # 状态管理 │ ├── vehicle_provider.dart # 车辆状态 │ └── statistics_provider.dart # 统计状态 │ ├── assets/ # 资源文件 │ ├── images/ # 图片资源 │ └── models/ # 模型文件(车牌识别模型) │ ├── test/ # 测试代码 │ ├── unit/ # 单元测试 │ ├── widget/ # 组件测试 │ └── integration/ # 集成测试 │ ├── pubspec.yaml # Flutter依赖配置 └── README.md # 项目说明 ``` --- ## 10. 开发进度计划 ### 第一周:环境搭建与数据层开发 #### Day 1-2:开发环境配置 - [ ] 安装Flutter SDK和Android Studio - [ ] 配置华为HMS服务 - [ ] 创建Flutter项目 - [ ] 配置依赖项 - [ ] 集成HyperLPR3 SDK #### Day 3-4:数据库开发 - [ ] 设计数据库表结构 - [ ] 实现DatabaseHelper类 - [ ] 编写Vehicle模型 - [ ] 实现CRUD基础操作 - [ ] 编写数据库单元测试 #### Day 5:服务层开发 - [ ] 实现VehicleService - [ ] 实现StatisticsService - [ ] 实现SearchService - [ ] 编写服务层单元测试 **交付物:** ✅ 开发环境搭建完成 ✅ 数据库层完成并通过测试 ✅ 基础服务层完成 --- ### 第二周:核心功能开发 #### Day 1-2:首页统计模块 - [ ] 首页UI布局实现 - [ ] 统计卡片组件 - [ ] 即将到期列表组件 - [ ] 状态管理集成 - [ ] 数据刷新机制 #### Day 3-4:车辆管理模块 - [ ] 车辆列表页面 - [ ] 车辆卡片组件 - [ ] 搜索和筛选功能 - [ ] 下拉刷新和上拉加载 #### Day 5:车辆新增/编辑 - [ ] 表单页面UI - [ ] 表单验证逻辑 - [ ] 日期选择器 - [ ] 保存和更新功能 - [ ] 删除功能(含二次确认) **交付物:** ✅ 首页完成 ✅ 车辆管理基础功能完成 ✅ 数据增删改查功能完整 --- ### 第三周:高级功能开发 #### Day 1-2:车牌识别集成 - [ ] Android原生识别代码 - [ ] Platform Channel通信 - [ ] PlateRecognitionService实现 - [ ] 识别功能测试 #### Day 3-4:拍照识别功能 - [ ] 相机权限申请 - [ ] 相机界面实现 - [ ] 引导框组件 - [ ] 拍照和识别流程 - [ ] 识别结果展示页面 #### Day 5:数据导出功能 - [ ] Excel生成功能 - [ ] 导出选项界面 - [ ] 文件保存逻辑 - [ ] 分享功能(可选) **交付物:** ✅ 车牌识别功能完成 ✅ 拍照识别完整流程 ✅ 数据导出功能完成 --- ### 第四周:测试与优化 #### Day 1-2:功能测试 - [ ] 完整功能流程测试 - [ ] 边界情况测试 - [ ] 异常情况处理测试 - [ ] 华为手机真机测试 #### Day 3:性能优化 - [ ] 数据库查询优化 - [ ] 列表滚动性能优化 - [ ] 图片处理优化 - [ ] 内存泄漏检查 #### Day 4:UI/UX优化 - [ ] 界面细节调整 - [ ] 交互体验优化 - [ ] 加载状态优化 - [ ] 错误提示优化 #### Day 5:Bug修复 - [ ] 修复测试发现的问题 - [ ] 代码审查 - [ ] 文档完善 **交付物:** ✅ 测试报告 ✅ 性能优化报告 ✅ Bug修复清单 --- ### 第五周:打包与部署 #### Day 1-2:应用打包 - [ ] 配置应用签名 - [ ] 生成Release APK - [ ] 华为应用市场适配 - [ ] 安装包测试 #### Day 3:文档编写 - [ ] 用户操作手册 - [ ] 系统维护文档 - [ ] 开发文档整理 - [ ] API文档 #### Day 4:交付准备 - [ ] 最终测试 - [ ] 数据备份方案说明 - [ ] 常见问题FAQ - [ ] 培训资料准备 #### Day 5:项目交付 - [ ] 交付演示 - [ ] 用户培训 - [ ] 项目验收 - [ ] 后续支持计划 **交付物:** ✅ 正式版APK ✅ 完整文档包 ✅ 培训材料 ✅ 验收报告 --- ## 11. 测试方案 ### 11.1 测试策略 #### 11.1.1 测试类型 - **单元测试**:测试各个类和方法 - **组件测试**:测试UI组件 - **集成测试**:测试完整功能流程 - **真机测试**:华为手机实际测试 #### 11.1.2 测试覆盖率目标 - 核心业务逻辑:90% - 数据库操作:85% - UI组件:70% - 整体代码:75% ### 11.2 测试用例 #### 11.2.1 数据库测试 ```dart // test/database/database_helper_test.dart import 'package:flutter_test/flutter_test.dart'; void main() { group('DatabaseHelper Tests', () { test('插入车辆', () async { // 测试插入车辆记录 }); test('查询车辆', () async { // 测试查询车辆记录 }); test('更新车辆', () async { // 测试更新车辆记录 }); test('删除车辆', () async { // 测试删除车辆记录 }); test('查询即将到期车辆', () async { // 测试统计查询 }); }); } ``` #### 11.2.2 车牌识别测试 | 测试场景 | 预期结果 | 通过标准 | |----------|----------|----------| | 标准蓝牌清晰图片 | 成功识别 | 识别率>95% | | 标准黄牌清晰图片 | 成功识别 | 识别率>95% | | 新能源车牌 | 成功识别 | 识别率>90% | | 光线不足 | 识别失败提示 | 给出友好提示 | | 模糊图片 | 识别失败提示 | 给出友好提示 | | 无车牌图片 | 未检测到提示 | 给出友好提示 | #### 11.2.3 功能测试清单 **首页统计:** - [ ] 总车辆数正确显示 - [ ] 即将到期车辆正确统计 - [ ] 逾期车辆正确统计 - [ ] 切换天数标签功能正常 - [ ] 点击统计卡片跳转正确 **车辆管理:** - [ ] 列表正确显示所有车辆 - [ ] 搜索功能正常 - [ ] 筛选功能正常 - [ ] 新增车辆成功 - [ ] 编辑车辆成功 - [ ] 删除车辆成功(含二次确认) - [ ] 表单验证正常 **拍照识别:** - [ ] 相机权限正常申请 - [ ] 相机正常打开 - [ ] 拍照功能正常 - [ ] 识别功能正常 - [ ] 识别结果正确显示 - [ ] 未找到车辆提示正常 **数据导出:** - [ ] 导出所有数据成功 - [ ] 导出筛选数据成功 - [ ] Excel格式正确 - [ ] 文件保存成功 - [ ] 导出成功提示 ### 11.3 真机测试 #### 华为手机测试清单 | 机型 | Android版本 | HMS版本 | 测试状态 | |------|------------|---------|---------| | Mate 60 Pro | Android 13 | HMS 6.x | 待测试 | | P60 Pro | Android 13 | HMS 6.x | 待测试 | | Nova 11 | Android 12 | HMS 6.x | 待测试 | #### 测试场景: - [ ] 应用安装和启动 - [ ] 各功能正常运行 - [ ] 相机功能正常 - [ ] 车牌识别准确率 - [ ] 数据导出功能 - [ ] 性能表现(流畅度、响应速度) - [ ] 电量消耗 - [ ] 内存占用 --- ## 12. 部署方案 ### 12.1 APK签名配置 #### 12.1.1 生成签名文件 ```bash # 使用keytool生成签名文件 keytool -genkey -v -keystore parking_key.jks \ -keyalg RSA \ -keysize 2048 \ -validity 10000 \ -alias parking ``` #### 12.1.2 配置签名 创建 `android/key.properties`: ```properties storePassword=your_store_password keyPassword=your_key_password keyAlias=parking storeFile=/path/to/parking_key.jks ``` 修改 `android/app/build.gradle`: ```gradle def keystoreProperties = new Properties() def keystorePropertiesFile = rootProject.file('key.properties') if (keystorePropertiesFile.exists()) { keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) } android { signingConfigs { release { keyAlias keystoreProperties['keyAlias'] keyPassword keystoreProperties['keyPassword'] storeFile file(keystoreProperties['storeFile']) storePassword keystoreProperties['storePassword'] } } buildTypes { release { signingConfig signingConfigs.release minifyEnabled true shrinkResources true } } } ``` ### 12.2 打包发布 ```bash # 构建Release APK flutter build apk --release # 构建分架构APK(减小体积) flutter build apk --split-per-abi --release # 生成的APK位置: # build/app/outputs/flutter-apk/app-release.apk ``` ### 12.3 安装部署 #### 方式一:直接安装 ```bash # 通过USB连接华为手机 adb install build/app/outputs/flutter-apk/app-release.apk ``` #### 方式二:手动安装 1. 将APK文件发送到华为手机 2. 在手机上打开文件管理器 3. 点击APK文件 4. 按提示完成安装 ## 14. 附录 ### 14.1 术语表 | 术语 | 说明 | |------|------| | Flutter | Google开发的跨平台UI框架 | | SQLite | 轻量级嵌入式数据库 | | HyperLPR3 | 开源车牌识别引擎 | | HMS | 华为移动服务 (Huawei Mobile Services) | | APK | Android应用安装包 | | Platform Channel | Flutter与原生代码通信机制 | | Provider | Flutter状态管理方案 | ### 14.2 参考资料 #### 官方文档 - Flutter官方文档:https://docs.flutter.dev/ - Android开发文档:https://developer.android.com/ - 华为HMS文档:https://developer.huawei.com/consumer/cn/doc/ - HyperLPR3文档:https://github.com/szad670401/HyperLPR #### 技术博客 - Flutter中文网:https://flutter.cn/ - Android开发者社区:https://www.androidos.net.cn/ #### 开源项目 - HyperLPR GitHub:https://github.com/szad670401/HyperLPR - Flutter Samples:https://github.com/flutter/samples ### 14.3 联系方式 **技术支持:** - 开发团队邮箱:dev@parking.com - 技术支持热线:400-xxx-xxxx - 在线文档:https://docs.parking.com **项目管理:** - 项目经理:[姓名] - 联系电话:[电话] - 邮箱:[邮箱] ### 14.4 更新记录 | 版本 | 日期 | 更新内容 | 更新人 | |------|------|----------|--------| | V1.0 | 2026-01-28 | 初始版本 | 系统分析师 | --- ## 总结 本方案书详细规划了离线停车场车牌管理App的完整开发流程,涵盖了从需求分析、技术选型、功能设计到开发实施的各个环节。 **核心亮点:** 1. ✅ 采用HyperLPR3实现高精度离线车牌识别 2. ✅ 完全离线运行,数据安全可控 3. ✅ 功能完整,易于使用 4. ✅ 开发周期5周,进度可控 **预期成果:** - 一个功能完善的离线停车场管理应用 - 识别准确率达95%以上 - 流畅的用户体验 - 完善的文档和培训材料 本方案具有很强的可落地性,按照此方案执行可以高质量完成项目交付。 --- **方案编制:** [编制人] **审核:** [审核人] **批准:** [批准人] **日期:** 2026年1月28日