加氢站默认选择,演示账号数据

附件默认显示,详情
This commit is contained in:
2025-12-16 11:58:00 +08:00
parent 4ace3c9f27
commit 9ce46a0c7d
9 changed files with 231 additions and 59 deletions

View File

@@ -48,9 +48,9 @@ class AttachmentViewerController extends GetxController {
}
},
);
localFilePath.value = savePath;
} catch (e) {
showErrorToast('PDF文件加载失败请检查网络或文件链接');
print('PDF Download Error: $e');

View File

@@ -40,11 +40,10 @@ class AttachmentViewerPage extends GetView<AttachmentViewerController> {
if (controller.fileType == 'pdf') {
if (controller.localFilePath.isNotEmpty) {
return PDFView(
key: ValueKey(controller.localFilePath.value),
filePath: controller.localFilePath.value,
enableSwipe: true,
swipeHorizontal: false,
autoSpacing: false,
swipeHorizontal: true,
autoSpacing: true,
pageFling: true,
onRender: (pages) {
print("PDF rendered with $pages pages.");

View File

@@ -1,21 +1,77 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:get/get.dart';
import 'package:getx_scaffold/getx_scaffold.dart';
import 'package:path_provider/path_provider.dart';
import 'attachment_viewer_page.dart';
class CertificateViewerController extends GetxController {
class CertificateViewerController extends GetxController with BaseControllerMixin{
late final String title;
late final List<String> attachments;
// --- 新增: 状态管理 ---
/// 用于存储网络PDF的本地路径key是网络urlvalue是本地路径
final RxMap<String, String> localPdfPaths = <String, String>{}.obs;
/// 用于跟踪每个附件的加载状态key是网络url
final RxMap<String, bool> isLoading = <String, bool>{}.obs;
@override
String get builderId => "certificateviewer";
@override
void onInit() {
super.onInit();
// 从 Get.to 的 arguments 中获取标题和附件列表
title = Get.arguments['title'] ?? '证件详情';
attachments = List<String>.from(Get.arguments['attachments'] ?? []);
// --- 新增: 初始化时开始加载所有PDF ---
_loadAllPdfs();
}
/// 导航到通用的附件查看器页面
/// 遍历所有附件如果是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;
}
}
/// 导航到通用的附件查看器页面 (此方法保持不变)
void openAttachment(String url) {
if (url.isEmpty) {
showErrorToast('附件链接无效');
@@ -23,15 +79,17 @@ class CertificateViewerController extends GetxController {
}
Get.to(
() => const AttachmentViewerPage(),
() => const AttachmentViewerPage(),
arguments: {
'url': url,
'url': url,
},
);
}
/// 检查 URL 是否为 PDF,以便在视图中显示不同的图标
/// 检查 URL 是否为 PDF (此方法保持不变)
bool isPdf(String url) {
return url.toLowerCase().endsWith('.pdf');
}
}

View File

@@ -1,51 +1,114 @@
import 'package:flutter/material.dart';
import 'package:flutter_pdfview/flutter_pdfview.dart'; // 引入PDFView
import 'package:get/get.dart';
import 'package:getx_scaffold/common/common.dart';
import 'certificate_viewer_controller.dart';
class CertificateViewerPage extends GetView<CertificateViewerController> {
const CertificateViewerPage({Key? key}) : super(key: key);
const CertificateViewerPage({super.key});
@override
Widget build(BuildContext context) {
Get.put(CertificateViewerController());
return Scaffold(
appBar: AppBar(
title: Text(controller.title),
centerTitle: true,
),
body: ListView.builder(
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
itemCount: controller.attachments.length,
itemBuilder: (context, index) {
final url = controller.attachments[index];
// 从 URL 中提取文件名用于显示
final fileName = url.split('/').last;
return Card(
elevation: 2,
margin: const EdgeInsets.only(bottom: 12),
child: ListTile(
leading: Icon(
controller.isPdf(url)
? Icons.picture_as_pdf_rounded // PDF 图标
: Icons.image_rounded, // 图片图标
color: controller.isPdf(url) ? Colors.red.shade700 : Colors.blue.shade700,
size: 32,
return GetBuilder<CertificateViewerController>(
init: CertificateViewerController(),
id: 'certificateviewer',
builder: (_) {
return Scaffold(
appBar: AppBar(title: Text(controller.title)),
body: Column(
children: [
SizedBox(height: 16,),
Text(
"点击可查看大图",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
title: Text(
fileName,
style: const TextStyle(fontSize: 14),
maxLines: 2,
overflow: TextOverflow.ellipsis,
Expanded(
child: ListView.builder(
padding: const EdgeInsets.all(16.0),
itemCount: controller.attachments.length,
itemBuilder: (context, index) {
final url = controller.attachments[index];
return _buildAttachmentItem(url);
},
),
),
trailing: const Icon(Icons.arrow_forward_ios_rounded, size: 16, color: Colors.grey),
onTap: () => controller.openAttachment(url),
),
);
},
],
),
);
},
);
}
/// 构建单个附件的显示项
Widget _buildAttachmentItem(String url) {
return GestureDetector(
onTap: () {
controller.openAttachment(url);
}, // 点击跳转到详情页
child: Card(
margin: const EdgeInsets.only(bottom: 16.0),
clipBehavior: Clip.antiAlias, // 确保内容不会溢出Card的圆角
elevation: 4,
// 等比缩放展示
child: 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(
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加载中指示器
Widget _buildLoadingIndicator() {
return const SizedBox(height: 200, child: Center(child: CircularProgressIndicator()));
}
// 辅助Widget错误指示器
Widget _buildErrorIndicator() {
return const SizedBox(
height: 200,
child: Center(child: Icon(Icons.error_outline, color: Colors.red, size: 48)),
);
}
}

View File

@@ -809,6 +809,25 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
}
void getSiteList() async {
if(StorageService.to.phone == "13344444444"){
//该账号给stationOptions手动添加一个数据
final testStation = StationModel(
hydrogenId: '1142167389150920704',
name: '羚牛氢能演示加氢站',
address: '上海市嘉定区于田南路111号于田大厦',
price: '35.00', // 价格
siteStatusName: '营运中', // 状态
isSelect: 1, // 默认可选
);
// 使用 assignAll 可以确保列表只包含这个测试数据
stationOptions.assignAll([testStation]);
if (stationOptions.isNotEmpty) {
selectedStationId.value = stationOptions.first.hydrogenId;
}
return;
}
showLoading("加载中");
final originalHeaders = Map<String, dynamic>.from(HttpService.to.dio.options.headers);
try {
@@ -849,6 +868,19 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
} else {
showToast('站点列表已刷新');
}
// 找到第一个可选的站点作为默认值
if (stationOptions.isNotEmpty) {
final firstSelectable = stationOptions.firstWhere(
(station) => station.isSelect == 1,
orElse: () => stationOptions.first, // 降级:如果没有可选的,就用第一个
);
selectedStationId.value = firstSelectable.hydrogenId;
} else {
// 如果列表为空,确保 selectedStationId 也为空
selectedStationId.value = null;
}
} catch (e) {
showToast('数据异常');
}

View File

@@ -437,20 +437,40 @@ class ReservationPage extends GetView<C_ReservationController> {
),
)
.toList(),
value: controller.selectedStationId.value,
value:
// 当前的站点 处理默认
controller.selectedStationId.value ??
(controller.stationOptions.isNotEmpty
? controller.stationOptions.first.hydrogenId
: null),
// 当前选中的是站点ID
onChanged: (value) {
if (value != null) {
controller.selectedStationId.value = value;
}
},
customButton: controller.selectedStationId.value == null
? null // 未选择时,显示默认的 hint
: _buildSelectedStationButton(
controller.stationOptions.firstWhere(
(s) => s.hydrogenId == controller.selectedStationId.value,
),
),
customButton: Obx(() {
// 优先从已选中的 ID 查找
var selectedStation = controller.stationOptions.firstWhereOrNull(
(s) => s.hydrogenId == controller.selectedStationId.value,
);
// 如果找不到已选中的(比如 ID 为空或列表里没有),并且列表不为空,则取第一个作为默认
final stationToShow =
selectedStation ??
(controller.stationOptions.isNotEmpty
? controller.stationOptions.first
: null);
// 如果有要显示的站点,就构建按钮
if (stationToShow != null) {
return _buildSelectedStationButton(stationToShow);
}
// 否则,返回一个空占位符,让 hint 生效
// DropdownButton2 内部会判断,如果 customButton 返回的不是一个有效Widget或根据其内部逻辑就会显示 hint
return const SizedBox.shrink();
}),
buttonStyleData: ButtonStyleData(
height: 40, // 增加高度以容纳两行文字
padding: const EdgeInsets.symmetric(horizontal: 12.0),