加氢站默认选择,演示账号数据
附件默认显示,详情
This commit is contained in:
@@ -48,9 +48,9 @@ class AttachmentViewerController extends GetxController {
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
localFilePath.value = savePath;
|
||||
|
||||
|
||||
} catch (e) {
|
||||
showErrorToast('PDF文件加载失败,请检查网络或文件链接');
|
||||
print('PDF Download Error: $e');
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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是网络url,value是本地路径
|
||||
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');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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('数据异常');
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user