Merge branch 'dev_feature' into dev
ui调整 # Conflicts: # ln_jq_app/lib/pages/b_page/reservation/controller.dart # ln_jq_app/lib/pages/b_page/site/controller.dart # ln_jq_app/lib/pages/b_page/site/view.dart # ln_jq_app/lib/pages/c_page/mine/view.dart # ln_jq_app/lib/pages/c_page/reservation/controller.dart # ln_jq_app/lib/pages/c_page/reservation/view.dart # ln_jq_app/lib/pages/login/view.dart
This commit is contained in:
@@ -12,12 +12,12 @@ class AttachmentViewerPage extends GetView<AttachmentViewerController> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Get.put(AttachmentViewerController());
|
||||
final fileName = controller.url.split('/').last;
|
||||
// final fileName = controller.url.split('/').last;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
fileName,
|
||||
"证件详情",
|
||||
style: const TextStyle(fontSize: 16),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import 'attachment_viewer_page.dart';
|
||||
|
||||
class CertificateViewerController extends GetxController with BaseControllerMixin{
|
||||
class CertificateViewerController extends GetxController with BaseControllerMixin {
|
||||
late final String title;
|
||||
late final List<String> attachments;
|
||||
|
||||
@@ -78,18 +78,11 @@ class CertificateViewerController extends GetxController with BaseControllerMixi
|
||||
return;
|
||||
}
|
||||
|
||||
Get.to(
|
||||
() => const AttachmentViewerPage(),
|
||||
arguments: {
|
||||
'url': url,
|
||||
},
|
||||
);
|
||||
Get.to(() => const AttachmentViewerPage(), arguments: {'url': url});
|
||||
}
|
||||
|
||||
/// 检查 URL 是否为 PDF (此方法保持不变)
|
||||
bool isPdf(String url) {
|
||||
return url.toLowerCase().endsWith('.pdf');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@ import 'package:get/get.dart';
|
||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||
import 'package:ln_jq_app/common/model/base_model.dart';
|
||||
import 'package:ln_jq_app/common/model/vehicle_info.dart';
|
||||
import 'package:ln_jq_app/pages/c_page/car_info/attachment_viewer_page.dart';
|
||||
import 'package:ln_jq_app/pages/qr_code/view.dart';
|
||||
import 'package:ln_jq_app/storage_service.dart';
|
||||
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'certificate_viewer_page.dart';
|
||||
import 'dart:io';
|
||||
|
||||
class CarInfoController extends GetxController with BaseControllerMixin {
|
||||
@override
|
||||
@@ -22,11 +24,37 @@ class CarInfoController extends GetxController with BaseControllerMixin {
|
||||
final RxList<String> operationAttachments = <String>[].obs;
|
||||
final RxList<String> hydrogenationAttachments = <String>[].obs;
|
||||
final RxList<String> registerAttachments = <String>[].obs;
|
||||
String color = "";
|
||||
String hydrogenCapacity = "";
|
||||
String rentFromCompany = "";
|
||||
String address = "";
|
||||
bool isNotice = false;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
getUserBindCarInfo();
|
||||
_msgNotice();
|
||||
}
|
||||
|
||||
Future<void> _msgNotice() async {
|
||||
final Map<String, dynamic> requestData = {
|
||||
'appFlag': 1,
|
||||
'isRead': 1,
|
||||
'pageNum': 1,
|
||||
'pageSize': 5,
|
||||
};
|
||||
final response = await HttpService.to.get(
|
||||
'appointment/unread_notice/page',
|
||||
params: requestData,
|
||||
);
|
||||
if (response != null) {
|
||||
final result = BaseModel.fromJson(response.data);
|
||||
if (result.code == 0 && result.data != null) {
|
||||
String total = result.data["total"].toString();
|
||||
isNotice = int.parse(total) > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -96,6 +124,21 @@ class CarInfoController extends GetxController with BaseControllerMixin {
|
||||
parseAttachments(data['hydrogenationAttachment']),
|
||||
);
|
||||
registerAttachments.assignAll(parseAttachments(data['registerAttachment']));
|
||||
|
||||
// 初始化时开始加载所有PDF
|
||||
attachments = [
|
||||
...drivingAttachments,
|
||||
...operationAttachments,
|
||||
...hydrogenationAttachments,
|
||||
...registerAttachments,
|
||||
];
|
||||
|
||||
color = data['color'].toString();
|
||||
hydrogenCapacity = data['hydrogenCapacity'].toString();
|
||||
rentFromCompany = data['rentFromCompany'].toString();
|
||||
address = data['address'].toString();
|
||||
|
||||
loadAllPdfs();
|
||||
}
|
||||
}
|
||||
updateUi();
|
||||
@@ -117,4 +160,69 @@ class CarInfoController extends GetxController with BaseControllerMixin {
|
||||
arguments: {'title': title, 'attachments': attachments},
|
||||
);
|
||||
}
|
||||
|
||||
/// 导航到通用的附件查看器页面
|
||||
void openAttachment(String url) {
|
||||
if (url.isEmpty) {
|
||||
showErrorToast('附件链接无效');
|
||||
return;
|
||||
}
|
||||
|
||||
Get.to(() => const AttachmentViewerPage(), arguments: {'url': url});
|
||||
}
|
||||
|
||||
/// 检查 URL 是否为 PDF
|
||||
bool isPdf(String url) {
|
||||
return url.toLowerCase().endsWith('.pdf');
|
||||
}
|
||||
|
||||
List<String> attachments = [];
|
||||
|
||||
// --- 新增: 状态管理 ---
|
||||
/// 用于存储网络PDF的本地路径,key是网络url,value是本地路径
|
||||
final RxMap<String, String> localPdfPaths = <String, String>{}.obs;
|
||||
|
||||
/// 用于跟踪每个附件的加载状态,key是网络url
|
||||
final RxMap<String, bool> isLoading = <String, bool>{}.obs;
|
||||
|
||||
/// 遍历所有附件,如果是PDF则进行下载
|
||||
void loadAllPdfs() {
|
||||
for (var url in attachments) {
|
||||
if (isPdf(url)) {
|
||||
_downloadPdf(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 下载单个PDF文件
|
||||
Future<void> _downloadPdf(String url) async {
|
||||
if (url.isEmpty) return;
|
||||
|
||||
// 开始加载
|
||||
isLoading[url] = true;
|
||||
|
||||
try {
|
||||
final dio = Dio();
|
||||
final Directory tempDir = await getTemporaryDirectory();
|
||||
final String savePath = '${tempDir.path}/${url.split('/').last}';
|
||||
|
||||
// 检查文件是否已存在,避免重复下载
|
||||
if (await File(savePath).exists()) {
|
||||
localPdfPaths[url] = savePath;
|
||||
isLoading[url] = false;
|
||||
return;
|
||||
}
|
||||
|
||||
await dio.download(url, savePath);
|
||||
|
||||
// 下载成功后,更新本地路径
|
||||
localPdfPaths[url] = savePath;
|
||||
} catch (e) {
|
||||
print('PDF download error for $url: $e');
|
||||
// 出错时也可以更新状态,以便UI显示错误提示
|
||||
} finally {
|
||||
// 结束加载
|
||||
isLoading[url] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_pdfview/flutter_pdfview.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||
import 'package:ln_jq_app/common/styles/theme.dart';
|
||||
import 'package:ln_jq_app/pages/qr_code/view.dart';
|
||||
import 'package:ln_jq_app/common/login_util.dart';
|
||||
import 'package:ln_jq_app/pages/c_page/message/view.dart';
|
||||
import 'package:ln_jq_app/storage_service.dart';
|
||||
import 'package:photo_view/photo_view.dart';
|
||||
|
||||
import '../../../common/styles/theme.dart';
|
||||
import 'controller.dart';
|
||||
|
||||
class CarInfoPage extends GetView<CarInfoController> {
|
||||
@@ -16,22 +20,26 @@ class CarInfoPage extends GetView<CarInfoController> {
|
||||
id: 'car_info',
|
||||
builder: (_) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey[100],
|
||||
backgroundColor: const Color.fromRGBO(240, 244, 247, 0.4),
|
||||
body: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_buildDriverInfoCard(),
|
||||
const SizedBox(height: 5),
|
||||
_buildCarBindingCard(),
|
||||
const SizedBox(height: 5),
|
||||
_buildCertificatesCard(),
|
||||
const SizedBox(height: 5),
|
||||
_buildTipsCard(),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildUserInfoCard(),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 20.w, right: 20.w),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
_buildCarInfoCard(),
|
||||
_buildCertificatesCard(context),
|
||||
const SizedBox(height: 12),
|
||||
_buildSafetyReminderCard(),
|
||||
SizedBox(height: 95.h),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -39,74 +47,124 @@ class CarInfoPage extends GetView<CarInfoController> {
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建顶部的司机信息卡片
|
||||
Widget _buildDriverInfoCard() {
|
||||
Widget _buildUserInfoCard() {
|
||||
return Card(
|
||||
elevation: 2,
|
||||
elevation: 1,
|
||||
color: Colors.white,
|
||||
margin: EdgeInsets.zero,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(20),
|
||||
bottomRight: Radius.circular(20),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
padding: EdgeInsets.only(left: 20.w, right: 20.w, bottom: 16, top: 50),
|
||||
child: Row(
|
||||
children: [
|
||||
const CircleAvatar(
|
||||
radius: 20,
|
||||
backgroundColor: Colors.blue,
|
||||
child: Icon(Icons.person, color: Colors.white, size: 34),
|
||||
Stack(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 25,
|
||||
backgroundColor: Colors.white,
|
||||
child: LoginUtil.getAssImg('ic_user_logo@2x'),
|
||||
),
|
||||
Positioned(
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: SizedBox(
|
||||
height: 16.h,
|
||||
width: 16.w,
|
||||
child: LoginUtil.getAssImg('ic_logo@2x'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
SizedBox(width: 8.w),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"${StorageService.to.name}",
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
"${StorageService.to.name}",
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8.w),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color.fromRGBO(236, 255, 234, 1),
|
||||
border: Border.all(color: const Color(0xFFB7E19F)),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.eco, size: 12, color: Color(0xFF52C41A)),
|
||||
SizedBox(width: 4),
|
||||
Text(
|
||||
"绿色先锋",
|
||||
style: TextStyle(
|
||||
color: Color(0xFF52C41A),
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
"${StorageService.to.phone}",
|
||||
style: TextStyle(color: Colors.grey, fontSize: 11),
|
||||
"羚牛ID:${StorageService.to.phone}",
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 11),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue[50],
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.blue, width: 0.5),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Get.to(() => const MessagePage());
|
||||
},
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor: Colors.grey[100],
|
||||
padding: const EdgeInsets.all(8),
|
||||
),
|
||||
child: const Row(
|
||||
children: [
|
||||
Icon(Icons.shield_outlined, color: Colors.blue, size: 14),
|
||||
SizedBox(width: 4),
|
||||
Text(
|
||||
'已认证',
|
||||
style: TextStyle(
|
||||
color: Colors.blue,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
icon: Badge(
|
||||
smallSize: 8,
|
||||
backgroundColor: controller.isNotice
|
||||
? Colors.red
|
||||
: Colors.transparent,
|
||||
child: const Icon(
|
||||
Icons.notifications_outlined,
|
||||
color: Colors.black87,
|
||||
size: 30,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider(height: 1, indent: 16, endIndent: 16),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||
padding: EdgeInsets.only(left: 20.w, right: 20.w, bottom: 20),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
_buildStatItem('156', '服务天数'),
|
||||
_buildStatItem('4.9', '评分'),
|
||||
_buildStatItem('98%', '准时率'),
|
||||
_buildModernStatItem('本月里程数', 'Accumulated', '2,852km', ''),
|
||||
const SizedBox(width: 8),
|
||||
_buildModernStatItem('总里程', 'Refuel Count', "2.5W km", ''),
|
||||
const SizedBox(width: 8),
|
||||
_buildModernStatItem('服务评分', 'Driver rating', "4.9分", ''),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -115,207 +173,379 @@ class CarInfoPage extends GetView<CarInfoController> {
|
||||
);
|
||||
}
|
||||
|
||||
// 司机信息卡片中的小统计项
|
||||
Widget _buildStatItem(String value, String label) {
|
||||
return Column(
|
||||
children: [
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.blue,
|
||||
),
|
||||
Widget _buildModernStatItem(String title, String subtitle, String value, String unit) {
|
||||
return Expanded(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF8F9FA),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(label, style: const TextStyle(color: Colors.grey, fontSize: 12)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建车辆绑定信息卡片
|
||||
Widget _buildCarBindingCard() {
|
||||
return Card(
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
_buildInfoRow('车牌号: ${controller.plateNumber}', '扫码绑定'),
|
||||
const SizedBox(height: 12),
|
||||
_buildInfoRow('车架号:', '${controller.vin}'),
|
||||
const SizedBox(height: 12),
|
||||
_buildInfoRow('车辆型号:', '${controller.modelName}'),
|
||||
const SizedBox(height: 12),
|
||||
_buildInfoRow('车辆品牌:', '${controller.brandName}'),
|
||||
],
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Icon(Icons.propane_rounded, size: 50, color: Colors.blue.withOpacity(0.5)),
|
||||
Text(subtitle, style: const TextStyle(fontSize: 9, color: Colors.grey)),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||
textBaseline: TextBaseline.alphabetic,
|
||||
children: [
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
Text(unit, style: const TextStyle(fontSize: 10, color: Colors.black54)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 车辆绑定卡片中的信息行
|
||||
Widget _buildInfoRow(String label, String value) {
|
||||
bool isButton = value == '扫码绑定';
|
||||
Widget _buildCarInfoCard() {
|
||||
return Card(
|
||||
elevation: 2,
|
||||
color: Colors.white,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildDetailRow('车牌号', controller.plateNumber, isPlate: true),
|
||||
const SizedBox(height: 11),
|
||||
_buildDetailRow('车架号', controller.vin),
|
||||
const SizedBox(height: 11),
|
||||
_buildDetailRow('车辆型号', controller.modelName),
|
||||
const SizedBox(height: 11),
|
||||
_buildDetailRow('车辆品牌', controller.brandName),
|
||||
const SizedBox(height: 10),
|
||||
_buildH2LevelProgress(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDetailRow(String label, String value, {bool isPlate = false}) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(label, style: const TextStyle(fontSize: 13)),
|
||||
const SizedBox(width: 8),
|
||||
isButton
|
||||
? GestureDetector(
|
||||
onTap: () async {
|
||||
controller.doQrCode();
|
||||
},
|
||||
Text(label, style: const TextStyle(fontSize: 13, color: Colors.grey)),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (isPlate)
|
||||
GestureDetector(
|
||||
onTap: () => controller.doQrCode(),
|
||||
child: Container(
|
||||
margin: EdgeInsetsGeometry.only(left: 10.w),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5),
|
||||
margin: const EdgeInsets.only(right: 10),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.blue.shade300, width: 1),
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: Colors.blue.withOpacity(0.05),
|
||||
border: Border.all(color: const Color.fromRGBO(71, 174, 208, 1)),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: const Color.fromRGBO(235, 250, 255, 1),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min, // Keep the row compact
|
||||
children: [
|
||||
Icon(
|
||||
StorageService.to.hasVehicleInfo ? Icons.repeat : Icons.search,
|
||||
size: 13,
|
||||
color: Colors.blue,
|
||||
const Icon(
|
||||
Icons.sync,
|
||||
size: 12,
|
||||
color: Color.fromRGBO(71, 174, 208, 1),
|
||||
),
|
||||
const SizedBox(width: 3),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
StorageService.to.hasVehicleInfo ? "换车牌" : value,
|
||||
StorageService.to.hasVehicleInfo ? "换车牌" : "扫码绑定",
|
||||
style: const TextStyle(
|
||||
color: Colors.blue,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color.fromRGBO(71, 174, 208, 1),
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
value,
|
||||
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
|
||||
),
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// 3. 构建车辆证件卡片
|
||||
Widget _buildCertificatesCard() {
|
||||
return Card(
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildCertificateRow(
|
||||
icon: Icons.credit_card_rounded,
|
||||
title: '行驶证',
|
||||
attachments: controller.drivingAttachments,
|
||||
),
|
||||
const Divider(),
|
||||
_buildCertificateRow(
|
||||
icon: Icons.article_rounded,
|
||||
title: '营运证',
|
||||
attachments: controller.operationAttachments,
|
||||
),
|
||||
const Divider(),
|
||||
_buildCertificateRow(
|
||||
icon: Icons.propane_tank_rounded,
|
||||
title: '加氢证',
|
||||
attachments: controller.hydrogenationAttachments,
|
||||
),
|
||||
const Divider(),
|
||||
_buildCertificateRow(
|
||||
icon: Icons.app_registration_rounded,
|
||||
title: '登记证',
|
||||
attachments: controller.registerAttachments,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 证件展示
|
||||
Widget _buildCertificateRow({
|
||||
required IconData icon,
|
||||
required String title,
|
||||
required List<String> attachments,
|
||||
}) {
|
||||
return ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
leading: CircleAvatar(
|
||||
radius: 24,
|
||||
backgroundColor: Colors.blue.withOpacity(0.1),
|
||||
child: Icon(icon, color: Colors.blue, size: 28),
|
||||
),
|
||||
title: Text(
|
||||
title,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
|
||||
),
|
||||
// 使用 Obx 响应式地显示附件数量
|
||||
subtitle: Obx(
|
||||
() => Text(
|
||||
'共 ${attachments.length} 个附件',
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 12),
|
||||
),
|
||||
),
|
||||
trailing: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[200],
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Icon(Icons.find_in_page_outlined, color: AppTheme.themeColor),
|
||||
),
|
||||
// 更新 onTap 逻辑
|
||||
onTap: () => controller.navigateToCertificateViewer(title, attachments),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTipsCard() {
|
||||
return Card(
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildTipItem(Icons.info_outline, '请确保车辆证件齐全有效'),
|
||||
const SizedBox(height: 10),
|
||||
_buildTipItem(Icons.rule, '定期检查车辆状态和证件有效期'),
|
||||
const SizedBox(height: 10),
|
||||
_buildTipItem(Icons.headset_mic_outlined, '如有疑问请联系客服: 400-021-1773'),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 提示信息卡片中的列表项
|
||||
Widget _buildTipItem(IconData icon, String text) {
|
||||
return Row(
|
||||
Widget _buildH2LevelProgress() {
|
||||
return Column(
|
||||
children: [
|
||||
Icon(icon, color: Colors.blue, size: 20),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(text, style: const TextStyle(fontSize: 12, color: Colors.black54)),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: const LinearProgressIndicator(
|
||||
value: 0.75,
|
||||
minHeight: 8,
|
||||
backgroundColor: Color(0xFFF0F2F5),
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Color.fromRGBO(16, 185, 129, 1)),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text("H2 Level", style: TextStyle(fontSize: 11, color: Colors.grey)),
|
||||
Text(
|
||||
"75%",
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: Color.fromRGBO(16, 185, 129, 1),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// 3. 构建车辆证件卡片 (重构为 TabView 样式)
|
||||
Widget _buildCertificatesCard(BuildContext context) {
|
||||
return DefaultTabController(
|
||||
length: 4,
|
||||
child: Column(
|
||||
children: [
|
||||
TabBar(
|
||||
isScrollable: false,
|
||||
indicatorColor: Color.fromRGBO(16, 185, 129, 1),
|
||||
labelColor: Color.fromRGBO(16, 185, 129, 1),
|
||||
unselectedLabelColor: Colors.grey,
|
||||
labelStyle: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
|
||||
indicatorSize: TabBarIndicatorSize.label,
|
||||
tabs: const [
|
||||
Tab(text: '行驶证'),
|
||||
Tab(text: '营运证'),
|
||||
Tab(text: '加氢证'),
|
||||
Tab(text: '登记证'),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 9),
|
||||
SizedBox(
|
||||
height: 336.h, // 给定一个高度,或者使用别的方式布局
|
||||
child: TabBarView(
|
||||
children: [
|
||||
_buildCertificateContent('行驶证', controller.drivingAttachments),
|
||||
_buildCertificateContent('营运证', controller.operationAttachments),
|
||||
_buildCertificateContent('加氢资格证', controller.hydrogenationAttachments),
|
||||
_buildCertificateContent('登记证', controller.registerAttachments),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建单个证件的展示内容
|
||||
Widget _buildCertificateContent(String title, RxList<String> attachments) {
|
||||
return Obx(() {
|
||||
return Card(
|
||||
elevation: 0,
|
||||
color: Colors.white,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: attachments.isEmpty
|
||||
? const Center(child: Text('暂无相关证件信息'))
|
||||
: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
//证件文字
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
_buildCertDetailItem('所属公司', controller.rentFromCompany, isFull: true),
|
||||
_buildCertDetailItem('运营城市', controller.address),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
_buildCertDetailItem(
|
||||
'车辆颜色',
|
||||
controller.color,
|
||||
valueColor: const Color(0xFF52C41A),
|
||||
),
|
||||
_buildCertDetailItem('氢瓶容量', controller.hydrogenCapacity),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// 附件预览部分
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
controller.navigateToCertificateViewer(title, attachments);
|
||||
},
|
||||
child: Container(
|
||||
height: 184.h,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: Color.fromRGBO(226, 232, 240, 1)),
|
||||
color: Color.fromRGBO(248, 250, 252, 1),
|
||||
),
|
||||
child: Center(child: _buildAttachmentPreview(attachments[0])),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildCertDetailItem(
|
||||
String label,
|
||||
String value, {
|
||||
Color? valueColor,
|
||||
bool isFull = false,
|
||||
}) {
|
||||
return Container(
|
||||
width: isFull ? null : 140,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(label, style: const TextStyle(fontSize: 12, color: Colors.grey)),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
value,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: valueColor ?? Colors.black87,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 附件预览组件 (智能判断 PDF 或图片)
|
||||
Widget _buildAttachmentPreview(String url) {
|
||||
return AspectRatio(
|
||||
aspectRatio: 4 / 3,
|
||||
child: controller.isPdf(url)
|
||||
? Obx(() {
|
||||
final bool loading = controller.isLoading[url] ?? true;
|
||||
final String? localPath = controller.localPdfPaths[url];
|
||||
|
||||
if (loading) {
|
||||
return _buildLoadingIndicator();
|
||||
} else if (localPath != null && localPath.isNotEmpty) {
|
||||
return IgnorePointer(
|
||||
ignoring: true, // 设置为 true 来忽略所有指针事件
|
||||
child: PDFView(
|
||||
backgroundColor: Color.fromRGBO(248, 250, 252, 1),
|
||||
fitEachPage: true,
|
||||
filePath: localPath,
|
||||
fitPolicy: FitPolicy.WIDTH,
|
||||
// 适配宽度
|
||||
enableSwipe: false,
|
||||
swipeHorizontal: false,
|
||||
autoSpacing: false,
|
||||
pageFling: false,
|
||||
preventLinkNavigation: true, // 顺便禁用PDF内部链接的跳转
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// PDF加载失败
|
||||
return _buildErrorIndicator();
|
||||
}
|
||||
})
|
||||
: Image.network(
|
||||
url,
|
||||
fit: BoxFit.contain,
|
||||
// 图片加载时显示loading
|
||||
loadingBuilder: (context, child, loadingProgress) {
|
||||
if (loadingProgress == null) return child;
|
||||
return _buildLoadingIndicator();
|
||||
},
|
||||
// 图片加载失败时显示错误
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return _buildErrorIndicator();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoadingIndicator() {
|
||||
return const SizedBox(height: 200, child: Center(child: CircularProgressIndicator()));
|
||||
}
|
||||
|
||||
Widget _buildErrorIndicator() {
|
||||
return const SizedBox(
|
||||
height: 200,
|
||||
child: Center(child: Icon(Icons.error_outline, color: Colors.red, size: 48)),
|
||||
);
|
||||
}
|
||||
|
||||
/// 安全提醒卡片
|
||||
Widget _buildSafetyReminderCard() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color.fromRGBO(242, 249, 248, 1),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.info_outline, color: AppTheme.themeColor, size: 24),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
"安全提醒",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color.fromRGBO(1, 113, 55, 1),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const Text(
|
||||
"请确保车辆证件齐全有效,定期检查车辆状态和证件有效期,以确保运输作业合规安全。",
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Color.fromRGBO(1, 113, 55, 0.8),
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
"如有疑问请联系客服:400-021-1773",
|
||||
style: TextStyle(fontSize: 13, color: Color.fromRGBO(1, 113, 55, 0.8)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user