鸿蒙版Flutter快递查询助手
一个使用Flutter开发的鸿蒙原生应用,提供便捷的快递查询服务,支持物流跟踪、短信通知和电话联系等功能。本应用专为鸿蒙系统优化,同时保持对其他主流平台的兼容性,为用户提供统一、高效的快递查询体验。
开发背景
随着电子商务的快速发展,快递服务已成为人们日常生活中不可或缺的一部分。然而,用户经常需要在多个平台之间切换来查询不同快递公司的物流信息,体验不够便捷。
本项目旨在解决这一痛点,通过整合多家快递公司的查询接口,提供统一的查询入口,让用户能够便捷地追踪任何快递包裹的物流状态。同时,项目特别针对鸿蒙系统进行了原生适配,为鸿蒙用户提供流畅的使用体验。
作为一个开源项目,我们希望能够帮助更多的开发者了解Flutter与鸿蒙系统的结合开发,推动跨平台应用在鸿蒙生态中的发展。
功能特点
- 快递查询:支持多家快递公司查询(顺丰、圆通、中通等)
- 智能识别:自动识别快递公司,无需手动选择
- 物流跟踪:实时跟踪物流信息,及时了解包裹状态
- 时间轴展示:美观的时间轴展示物流进度和详细信息
- 历史记录:保存查询历史,方便再次查询
- 短信通知:支持将物流信息通过短信发送给他人
- 一键拨号:提供客服电话一键拨打功能
- 鸿蒙适配:针对鸿蒙系统进行了原生适配,提供流畅体验
多平台支持
本项目基于Flutter跨平台框架开发,支持多种操作系统和设备:
- 鸿蒙OS:针对华为设备进行了原生适配
- Android:支持Android 5.0及以上版本
- iOS:支持iOS 11.0及以上版本
- Web:可部署为网页应用,支持主流浏览器
- macOS:支持macOS 10.14及以上版本
- Windows:支持Windows 10及以上版本
- Linux:支持Ubuntu 18.04及以上版本
效果
鸿蒙
macos
技术栈
- Flutter框架
- 鸿蒙OS适配
- HTTP网络请求
- 响应式UI设计
- 状态管理
- 本地存储
- 短信发送集成
- 电话拨打功能
核心功能实现
短信发送功能
应用集成了短信发送功能,可以将快递物流信息通过短信分享给他人:
dart
// 短信发送功能实现
Future<void> sendSms(BuildContext context, {String? trackingInfo}) async {
String defaultNumber = '17752170152';
String message;
if (trackingInfo != null && trackingInfo.isNotEmpty) {
// 发送物流信息
message = '您的快递最新状态: $trackingInfo';
} else {
// 发送应用介绍
message = '快递查询助手是一款便捷的快递查询工具...';
}
// 显示对话框让用户输入电话号码
await showSmsDialog(context, defaultNumber, message);
}
电话拨打功能
应用提供了一键拨打客服电话的功能:
csharp
// 电话拨打功能实现
Future<void> _callNumber(BuildContext context) async {
const String number = '17752170152';
try {
// 使用FlutterPhoneDirectCaller拨号
bool? result = await FlutterPhoneDirectCaller.callNumber(number);
if (result != true) {
// 如果直接拨号失败,显示提示
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('无法拨打电话,请手动拨打: 17752170152')),
);
}
}
} catch (e) {
// 错误处理
}
}
## 安装方式
### 通用安装
```bash
# 克隆仓库
git clone git@gitcode.com:nutpi/flutter_express_tracker.git
# 进入项目目录
cd flutter_express_tracker
# 安装依赖
flutter pub get
# 运行应用
flutter run
bash
# 运行Web版本
flutter run -d chrome
# 运行macOS版本
flutter run -d macos
# 运行Linux版本
flutter run -d linux
# 运行Windows版本
flutter run -d windows
# 构建Web版本
flutter build web
# 构建macOS应用
flutter build macos
# 构建Linux应用
flutter build linux
# 构建Windows应用
flutter build windows
贡献指南
欢迎提交问题和功能请求,或直接提交代码贡献。请确保遵循项目的代码风格和提交规范。
版权信息
MIT License
作者:坚果派 公众号:nutpi 官网: www.nutpi.net/
参考
历史记录管理
应用实现了本地历史记录管理功能,方便用户查看和管理已查询的快递信息:
ini
// 历史记录管理实现
class TrackingHistoryService {
final _prefs = SharedPreferences.getInstance();
static const _historyKey = 'tracking_history';
// 保存查询记录
Future<void> saveTrackingHistory(TrackingHistory history) async {
final prefs = await _prefs;
List<String> histories = prefs.getStringList(_historyKey) ?? [];
histories.insert(0, jsonEncode(history.toJson()));
await prefs.setStringList(_historyKey, histories);
}
// 获取历史记录
Future<List<TrackingHistory>> getTrackingHistories() async {
final prefs = await _prefs;
List<String> histories = prefs.getStringList(_historyKey) ?? [];
return histories
.map((e) => TrackingHistory.fromJson(jsonDecode(e)))
.toList();
}
}
性能优化
为确保应用的流畅运行,我们采取了以下优化措施:
- 懒加载:页面组件采用懒加载方式,减少初始加载时间
- 缓存管理:实现智能缓存机制,减少网络请求
- 状态管理:使用Provider进行高效的状态管理
- 图片优化:实现图片懒加载和压缩
- 网络优化:请求合并和数据预加载
项目架构
项目采用清晰的分层架构设计:
bash
lib/
├── main.dart # 应用入口
├── models/ # 数据模型
│ ├── tracking_model.dart
│ └── tracking_history_model.dart
├── pages/ # 页面组件
│ ├── tracking_page.dart
│ └── history_page.dart
├── services/ # 业务逻辑
│ ├── tracking_service.dart
│ └── history_service.dart
└── utils/ # 工具类
└── http_util.dart
依赖管理
项目使用了以下主要依赖:
yaml
dependencies:
# 网络请求
http: ^1.1.0
# 状态管理
provider: ^6.1.2
# 本地存储
shared_preferences: ^2.2.2
# UI组件
flutter_animate: ^4.5.0
fl_chart: ^0.66.2
鸿蒙适配
为支持鸿蒙系统,我们进行了以下适配:
-
原生API适配:
- 短信发送接口适配
- 电话拨打功能适配
- 本地存储接口适配
- 触摸事件和手势处理适配
-
UI适配:
- 遵循鸿蒙设计规范
- 支持鸿蒙手势操作(包括点击、长按、拖拽等)
- 适配不同屏幕尺寸和分辨率
- 兼容鸿蒙系统的视图层次结构
-
性能优化:
- 针对鸿蒙系统进行性能调优
- 优化启动速度和运行内存
- 减少不必要的视图重绘和布局计算
使用指南
快递查询
- 在首页输入快递单号
- 系统自动识别快递公司
- 点击查询按钮获取物流信息
- 查看物流详情和时间轴展示
历史记录
- 点击底部导航栏的"历史"标签
- 查看已查询的快递记录
- 点击记录可重新查询
- 左滑删除不需要的记录
分享功能
- 在物流详情页点击分享按钮
- 选择短信分享方式
- 输入接收方手机号
- 确认发送物流信息
常见问题
-
为什么无法识别快递公司?
- 确保输入的快递单号正确
- 部分新增快递公司可能暂不支持自动识别
-
如何处理查询失败?
- 检查网络连接
- 确认快递单号是否正确
- 尝试手动选择快递公司
-
短信发送失败怎么办?
- 检查是否授予应用短信权限
- 确认手机号格式正确
- 检查短信服务是否可用
-
收到"设备不支持发送短信"错误怎么办?
-
这是因为某些设备(如模拟器、平板电脑或某些电脑版本)不支持发送短信功能
-
错误信息"PlatformException(device_not_capable)"表示当前设备硬件不支持短信功能
-
解决方案:
- 尝试使用其他分享方式,如复制文本或使用其他通讯应用
- 在支持短信功能的移动设备上使用该应用
- 如果在鸿蒙设备上遇到此问题,请确保已授予相应权限并使用最新版本的系统
-
更新日志
v1.0.0 (2025-03-08)
- 首次发布
- 支持多家快递公司查询
- 实现短信通知功能
- 支持历史记录管理
v1.0.1 (2025-03-09)
- 优化查询速度
- 修复已知问题
- 改进UI交互体验
- 增加更多快递公司支持
详细技术实现
应用架构
本项目采用了清晰的MVVM(Model-View-ViewModel)架构模式,有效分离了UI层、业务逻辑层和数据层,提高了代码的可维护性和可测试性。
数据层(Model)
数据层负责处理应用的数据逻辑,包括数据获取、存储和处理:
dart
// 快递跟踪模型示例
class TrackingModel {
final String trackingNumber;
final String courierCompany;
final List<TrackingDetail> details;
final String status;
final DateTime lastUpdated;
TrackingModel({
required this.trackingNumber,
required this.courierCompany,
required this.details,
required this.status,
required this.lastUpdated,
});
factory TrackingModel.fromJson(Map<String, dynamic> json) {
// JSON解析逻辑
return TrackingModel(
trackingNumber: json['tracking_number'],
courierCompany: json['courier_company'],
details: (json['details'] as List)
.map((e) => TrackingDetail.fromJson(e))
.toList(),
status: json['status'],
lastUpdated: DateTime.parse(json['last_updated']),
);
}
}
视图模型层(ViewModel)
视图模型层连接视图和模型,处理业务逻辑并提供数据给UI:
ini
class TrackingViewModel extends ChangeNotifier {
final TrackingService _trackingService = TrackingService();
final TrackingHistoryService _historyService = TrackingHistoryService();
TrackingModel? _trackingInfo;
bool _isLoading = false;
String? _error;
// Getters
TrackingModel? get trackingInfo => _trackingInfo;
bool get isLoading => _isLoading;
String? get error => _error;
// 查询快递信息
Future<void> queryTracking(String trackingNumber) async {
_isLoading = true;
_error = null;
notifyListeners();
try {
_trackingInfo = await _trackingService.getTrackingInfo(trackingNumber);
// 保存到历史记录
if (_trackingInfo != null) {
await _historyService.saveTrackingHistory(TrackingHistory(
id: const Uuid().v4(),
trackingNumber: trackingNumber,
courierCompany: _trackingInfo!.courierCompany,
status: _trackingInfo!.status,
queryTime: DateTime.now(),
));
}
} catch (e) {
_error = e.toString();
} finally {
_isLoading = false;
notifyListeners();
}
}
}
视图层(View)
视图层负责UI展示和用户交互:
less
class TrackingPage extends StatelessWidget {
final TextEditingController _trackingController = TextEditingController();
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => TrackingViewModel(),
child: Consumer<TrackingViewModel>(
builder: (context, viewModel, _) {
return Scaffold(
appBar: AppBar(title: const Text('快递查询')),
body: Column(
children: [
// 输入框和查询按钮
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
Expanded(
child: TextField(
controller: _trackingController,
decoration: const InputDecoration(
hintText: '请输入快递单号',
border: OutlineInputBorder(),
),
),
),
const SizedBox(width: 16),
ElevatedButton(
onPressed: viewModel.isLoading
? null
: () => viewModel.queryTracking(_trackingController.text),
child: const Text('查询'),
),
],
),
),
// 加载指示器
if (viewModel.isLoading)
const CircularProgressIndicator(),
// 错误信息
if (viewModel.error != null)
Text(
viewModel.error!,
style: const TextStyle(color: Colors.red),
),
// 查询结果
if (viewModel.trackingInfo != null)
Expanded(
child: TrackingDetailWidget(
trackingInfo: viewModel.trackingInfo!,
),
),
],
),
);
},
),
);
}
}
网络请求实现
应用使用HTTP包处理网络请求,实现了请求重试、超时处理和错误处理机制:
dart
class TrackingService {
final String _baseUrl = 'https://api.example.com/tracking';
final http.Client _client = http.Client();
Future<TrackingModel> getTrackingInfo(String trackingNumber) async {
try {
final response = await _client.get(
Uri.parse('$_baseUrl?number=$trackingNumber'),
headers: {'Content-Type': 'application/json'},
).timeout(const Duration(seconds: 10));
if (response.statusCode == 200) {
return TrackingModel.fromJson(jsonDecode(response.body));
} else {
throw Exception('查询失败: ${response.statusCode}');
}
} on TimeoutException {
throw Exception('网络请求超时,请检查网络连接');
} on SocketException {
throw Exception('网络连接失败,请检查网络设置');
} catch (e) {
throw Exception('查询出错: $e');
}
}
}
本地存储实现
应用使用SharedPreferences进行本地数据存储,实现了数据的持久化:
dart
class ConfigService {
final _prefs = SharedPreferences.getInstance();
static const _themeKey = 'app_theme';
static const _languageKey = 'app_language';
// 保存主题设置
Future<void> saveThemeMode(ThemeMode mode) async {
final prefs = await _prefs;
await prefs.setInt(_themeKey, mode.index);
}
// 获取主题设置
Future<ThemeMode> getThemeMode() async {
final prefs = await _prefs;
final index = prefs.getInt(_themeKey) ?? ThemeMode.system.index;
return ThemeMode.values[index];
}
// 保存语言设置
Future<void> saveLanguage(String languageCode) async {
final prefs = await _prefs;
await prefs.setString(_languageKey, languageCode);
}
// 获取语言设置
Future<String> getLanguage() async {
final prefs = await _prefs;
return prefs.getString(_languageKey) ?? 'zh';
}
}
高级功能详解
智能识别快递公司
应用实现了基于单号规则的快递公司智能识别算法:
python
String? identifyCourierCompany(String trackingNumber) {
// 顺丰快递:以SF开头,后接12位数字
if (RegExp(r'^SF\d{12}$').hasMatch(trackingNumber)) {
return '顺丰快递';
}
// 中通快递:以ZTO开头或纯数字(通常为12位)
if (trackingNumber.startsWith('ZTO') ||
RegExp(r'^\d{12}$').hasMatch(trackingNumber)) {
return '中通快递';
}
// 圆通快递:以YT开头,后接10位数字
if (RegExp(r'^YT\d{10}$').hasMatch(trackingNumber)) {
return '圆通快递';
}
// 更多快递公司识别规则...
// 无法识别
return null;
}
物流时间轴实现
应用使用自定义Widget实现了美观的物流时间轴展示:
less
class TrackingTimeline extends StatelessWidget {
final List<TrackingDetail> details;
const TrackingTimeline({Key? key, required this.details}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: details.length,
itemBuilder: (context, index) {
final detail = details[index];
final isFirst = index == 0;
final isLast = index == details.length - 1;
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 时间轴线和节点
Column(
children: [
// 上半部分线条
if (!isFirst)
Container(
width: 2,
height: 20,
color: Colors.grey,
),
// 节点
Container(
width: 12,
height: 12,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: isFirst ? Colors.green : Colors.grey,
),
),
// 下半部分线条
if (!isLast)
Container(
width: 2,
height: 20,
color: Colors.grey,
),
],
),
// 物流详情
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
detail.time,
style: TextStyle(
color: isFirst ? Colors.green : Colors.grey,
fontWeight: isFirst ? FontWeight.bold : FontWeight.normal,
),
),
const SizedBox(height: 4),
Text(detail.description),
],
),
),
),
],
);
},
);
}
}
鸿蒙系统适配详解
鸿蒙应用配置
在ohos/AppScope/app.json5
中配置应用信息:
bash
{
"app": {
"bundleName": "com.nutpi.express_tracker",
"vendor": "nutpi",
"versionCode": 1000000,
"versionName": "1.0.0",
"icon": "$media:app_icon",
"label": "快递查询助手"
}
}
鸿蒙权限配置
在ohos/entry/src/main/module.json5
中配置所需权限:
json
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
},
{
"name": "ohos.permission.SEND_MESSAGES"
},
{
"name": "ohos.permission.CALL_PHONE"
}
]
}
}
鸿蒙特有API适配
针对鸿蒙系统的特有功能进行适配:
dart
Future<void> sendSmsOnHarmonyOS(String phoneNumber, String message) async {
try {
// 使用鸿蒙特有的短信发送API
await FlutterSms.sendSMS(
message: message,
recipients: [phoneNumber],
sendDirect: true,
);
} catch (e) {
// 处理鸿蒙系统特有的异常
if (e.toString().contains('harmony_specific_error')) {
// 特殊处理逻辑
}
rethrow;
}
}