diff --git a/lib/main/lockDetail/doorLockLog/batchExportLog/batchExportLog_logic.dart b/lib/main/lockDetail/doorLockLog/batchExportLog/batchExportLog_logic.dart index bdf4532e..35103fda 100644 --- a/lib/main/lockDetail/doorLockLog/batchExportLog/batchExportLog_logic.dart +++ b/lib/main/lockDetail/doorLockLog/batchExportLog/batchExportLog_logic.dart @@ -1,6 +1,48 @@ +import 'dart:io'; + +import 'package:get/get.dart'; +import 'package:http/http.dart' as http; +import 'package:path_provider/path_provider.dart'; +import 'package:star_lock/appRouters.dart'; import 'package:star_lock/main/lockDetail/doorLockLog/batchExportLog/batchExportLog_state.dart'; +import 'package:star_lock/main/lockDetail/doorLockLog/exportRecordDialog/exportRecord_entity.dart'; +import 'package:star_lock/network/api_repository.dart'; import 'package:star_lock/tools/baseGetXController.dart'; class BatchExportLogLogic extends BaseGetXController { BatchExportLogState state = BatchExportLogState(); + + //操作记录-导出锁记录 + Future exportLockRecordsRequest() async { + final ExportRecordEntity entity = + await ApiRepository.to.batchExportLockRecords( + lockIds: state.lockIdList, + startDate: + DateTime.tryParse(state.beginTime.value)!.millisecondsSinceEpoch, + endDate: DateTime.tryParse(state.endTime.value)!.millisecondsSinceEpoch, + ); + + if (entity.errorCode!.codeIsSuccessful) { + final String url = entity.data!.fileUrl!; + //下载文件地址 + final String filePath = await downloadAndSaveFile(url); + Get.toNamed(Routers.exportSuccessPage, + arguments: {'filePath': filePath}); + } else { + throw 'Export failed with error code ${entity.errorCode}'; + } + } + + // 下载并保存文件 + Future downloadAndSaveFile(String url) async { + final http.Response response = await http.get(Uri.parse(url)); + if (response.statusCode == 200) { + final Directory directory = await getApplicationDocumentsDirectory(); + final File file = File('${directory.path}/exported_file.pdf'); + await file.writeAsBytes(response.bodyBytes); + return file.path; + } else { + throw 'Failed to download file'; + } + } } diff --git a/lib/main/lockDetail/doorLockLog/batchExportLog/batchExportLog_page.dart b/lib/main/lockDetail/doorLockLog/batchExportLog/batchExportLog_page.dart index 8382208d..973e2e9b 100644 --- a/lib/main/lockDetail/doorLockLog/batchExportLog/batchExportLog_page.dart +++ b/lib/main/lockDetail/doorLockLog/batchExportLog/batchExportLog_page.dart @@ -75,7 +75,7 @@ class _BatchExportLogPageState extends State logic.showToast('请选择锁'.tr); return; } - Get.toNamed(Routers.exportSuccessPage); + logic.exportLockRecordsRequest(); }), ], ), diff --git a/lib/main/lockDetail/doorLockLog/doorLockLog_page.dart b/lib/main/lockDetail/doorLockLog/doorLockLog_page.dart index 95d2f8b8..63c87ed3 100755 --- a/lib/main/lockDetail/doorLockLog/doorLockLog_page.dart +++ b/lib/main/lockDetail/doorLockLog/doorLockLog_page.dart @@ -141,8 +141,9 @@ class _DoorLockLogPageState extends State with RouteAware { context: context, builder: (BuildContext context) { return ExportRecordDialog( - onExport: () { - Get.toNamed(Routers.exportSuccessPage); + onExport: (String filePath) { + Get.toNamed(Routers.exportSuccessPage, + arguments: {'filePath': filePath}); }, ); }, @@ -280,7 +281,7 @@ class _DoorLockLogPageState extends State with RouteAware { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - '$formattedTime ${timelineData.username!.isNotEmpty ? "${timelineData.username}用" : ""}${timelineData.recordTypeName}', + '$formattedTime ${timelineData.username!.isNotEmpty ? "${timelineData.username}操作" : ""}${timelineData.recordTypeName}', textAlign: TextAlign.left, style: TextStyle( color: Colors.black, diff --git a/lib/main/lockDetail/doorLockLog/exportRecordDialog/exportRecordDialog_logic.dart b/lib/main/lockDetail/doorLockLog/exportRecordDialog/exportRecordDialog_logic.dart index e69de29b..8b137891 100644 --- a/lib/main/lockDetail/doorLockLog/exportRecordDialog/exportRecordDialog_logic.dart +++ b/lib/main/lockDetail/doorLockLog/exportRecordDialog/exportRecordDialog_logic.dart @@ -0,0 +1 @@ + diff --git a/lib/main/lockDetail/doorLockLog/exportRecordDialog/exportRecordDialog_page.dart b/lib/main/lockDetail/doorLockLog/exportRecordDialog/exportRecordDialog_page.dart index 46f9b303..8b7a4f6a 100644 --- a/lib/main/lockDetail/doorLockLog/exportRecordDialog/exportRecordDialog_page.dart +++ b/lib/main/lockDetail/doorLockLog/exportRecordDialog/exportRecordDialog_page.dart @@ -1,9 +1,17 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; +import 'package:http/http.dart' as http; import 'package:intl/intl.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:star_lock/appRouters.dart'; import 'package:star_lock/app_settings/app_colors.dart'; +import 'package:star_lock/main/lockDetail/doorLockLog/exportRecordDialog/exportRecord_entity.dart'; +import 'package:star_lock/network/api_repository.dart'; +import 'package:star_lock/tools/baseGetXController.dart'; +import 'package:star_lock/tools/commonDataManage.dart'; import 'package:star_lock/tools/pickers/pickers.dart'; import 'package:star_lock/tools/pickers/time_picker/model/date_mode.dart'; import 'package:star_lock/tools/pickers/time_picker/model/pduration.dart'; @@ -13,7 +21,7 @@ import 'package:star_lock/tools/submitBtn.dart'; class ExportRecordDialog extends StatelessWidget { const ExportRecordDialog({required this.onExport, Key? key}) : super(key: key); - final VoidCallback onExport; + final Function(String) onExport; @override Widget build(BuildContext context) { @@ -40,7 +48,7 @@ class ExportRecordDialog extends StatelessWidget { class _DerivedRecordWidget extends StatefulWidget { const _DerivedRecordWidget({required this.onExport, Key? key}) : super(key: key); - final VoidCallback onExport; + final Function(String) onExport; @override __DerivedRecordWidgetState createState() => __DerivedRecordWidgetState(); @@ -190,9 +198,38 @@ class __DerivedRecordWidgetState extends State<_DerivedRecordWidget> { return; } - // 调用导出接口的逻辑示例 + exportLockRecordsRequest(); + } - // 调用导出成功后的回调 - widget.onExport(); + //操作记录-导出锁记录 + Future exportLockRecordsRequest() async { + final ExportRecordEntity entity = await ApiRepository.to.exportLockRecords( + lockId: CommonDataManage().currentKeyInfo.lockId ?? 0, + startDate: startDate!.millisecondsSinceEpoch, + endDate: endDate!.millisecondsSinceEpoch, + ); + + if (entity.errorCode!.codeIsSuccessful) { + final String url = entity.data!.fileUrl!; + //下载文件地址 + final String filePath = await downloadAndSaveFile(url); + widget.onExport(filePath); + // return url; + } else { + throw 'Export failed with error code ${entity.errorCode}'; + } + } + + // 下载并保存文件 + Future downloadAndSaveFile(String url) async { + final http.Response response = await http.get(Uri.parse(url)); + if (response.statusCode == 200) { + final Directory directory = await getApplicationDocumentsDirectory(); + final File file = File('${directory.path}/exported_file.xlsx'); + await file.writeAsBytes(response.bodyBytes); + return file.path; + } else { + throw 'Failed to download file'; + } } } diff --git a/lib/main/lockDetail/doorLockLog/exportRecordDialog/exportRecordDialog_state.dart b/lib/main/lockDetail/doorLockLog/exportRecordDialog/exportRecordDialog_state.dart index e69de29b..8b137891 100644 --- a/lib/main/lockDetail/doorLockLog/exportRecordDialog/exportRecordDialog_state.dart +++ b/lib/main/lockDetail/doorLockLog/exportRecordDialog/exportRecordDialog_state.dart @@ -0,0 +1 @@ + diff --git a/lib/main/lockDetail/doorLockLog/exportRecordDialog/exportRecord_entity.dart b/lib/main/lockDetail/doorLockLog/exportRecordDialog/exportRecord_entity.dart new file mode 100644 index 00000000..60fa2d09 --- /dev/null +++ b/lib/main/lockDetail/doorLockLog/exportRecordDialog/exportRecord_entity.dart @@ -0,0 +1,40 @@ +class ExportRecordEntity { + ExportRecordEntity( + {this.errorCode, this.description, this.errorMsg, this.data}); + ExportRecordEntity.fromJson(Map json) { + errorCode = json['errorCode']; + description = json['description']; + errorMsg = json['errorMsg']; + data = json['data'] != null ? Data.fromJson(json['data']) : null; + } + int? errorCode; + String? description; + String? errorMsg; + Data? data; + + Map toJson() { + final Map data = {}; + data['errorCode'] = errorCode; + data['description'] = description; + data['errorMsg'] = errorMsg; + if (this.data != null) { + data['data'] = this.data!.toJson(); + } + return data; + } +} + +class Data { + Data({this.fileUrl}); + + Data.fromJson(Map json) { + fileUrl = json['fileUrl']; + } + String? fileUrl; + + Map toJson() { + final Map data = {}; + data['fileUrl'] = fileUrl; + return data; + } +} diff --git a/lib/main/lockDetail/doorLockLog/exportSuccess/exportSuccess_page.dart b/lib/main/lockDetail/doorLockLog/exportSuccess/exportSuccess_page.dart index 19f91f79..ecea5606 100644 --- a/lib/main/lockDetail/doorLockLog/exportSuccess/exportSuccess_page.dart +++ b/lib/main/lockDetail/doorLockLog/exportSuccess/exportSuccess_page.dart @@ -1,13 +1,19 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:star_lock/appRouters.dart'; import 'package:star_lock/app_settings/app_colors.dart'; import 'package:star_lock/main/lockDetail/doorLockLog/exportSuccess/exportSuccess_logic.dart'; import 'package:star_lock/main/lockDetail/doorLockLog/exportSuccess/exportSuccess_state.dart'; +import 'package:star_lock/tools/NativeInteractionTool.dart'; import 'package:star_lock/tools/commonDataManage.dart'; import 'package:star_lock/tools/submitBtn.dart'; import 'package:star_lock/tools/titleAppBar.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:open_file/open_file.dart'; class ExportSuccessPage extends StatefulWidget { const ExportSuccessPage({Key? key}) : super(key: key); @@ -76,7 +82,20 @@ class _ExportSuccessPageState extends State with RouteAware { SubmitBtn( btnName: '立即查看'.tr, onClick: () { - Get.toNamed(Routers.viewExportRecordPage); + // OpenFile.open(state.getFilePath.value); + NativeInteractionTool() + .loadNativeFileShare(shareText: state.getFilePath.value); + + // openFile(state.getFilePath.value); + // previewFile(); + // Get.toNamed(Routers.viewExportRecordPage, + // arguments: { + // 'filePath': state.getFilePath.value, + // }); + // Get.toNamed(Routers.webviewShowPage, arguments: { + // 'url': state.getFilePath.value, + // 'title': '查看操作记录'.tr + // }); }), SizedBox( height: 20.h, @@ -91,6 +110,34 @@ class _ExportSuccessPageState extends State with RouteAware { ); } + // Future openFile(String filePath) async { + // final File file = File(filePath); + // if (await file.exists()) { + // await launchUrl(Uri.parse('file://$filePath')); + // } else { + // throw 'File not found'; + // } + // } + + Future previewFile() async { + // 获取本地文件路径 + final Directory appDocDir = await getApplicationDocumentsDirectory(); + final String appDocPath = appDocDir.path; + final String filePath = '$appDocPath/exported_file.xlsx'; + + // 检查文件是否存在 + final File file = File(filePath); + if (await file.exists()) { + // 使用url_launcher打开文件 + final bool launched = await launchUrl(Uri.parse(filePath)); + if (!launched) { + throw 'Could not launch $filePath'; + } + } else { + print('File does not exist'); + } + } + Future _openModalBottomSheet() async { showModalBottomSheet( context: context, diff --git a/lib/main/lockDetail/doorLockLog/exportSuccess/exportSuccess_state.dart b/lib/main/lockDetail/doorLockLog/exportSuccess/exportSuccess_state.dart index 34c6f205..9cf091d0 100644 --- a/lib/main/lockDetail/doorLockLog/exportSuccess/exportSuccess_state.dart +++ b/lib/main/lockDetail/doorLockLog/exportSuccess/exportSuccess_state.dart @@ -1 +1,10 @@ -class ExportSuccessState {} +import 'package:get/get.dart'; + +class ExportSuccessState { + ExportSuccessState() { + final Map map = Get.arguments; + getFilePath.value = map['filePath']; + } + + RxString getFilePath = ''.obs; +} diff --git a/lib/main/lockDetail/doorLockLog/viewExportRecord/viewExportRecord_logic.dart b/lib/main/lockDetail/doorLockLog/viewExportRecord/viewExportRecord_logic.dart new file mode 100644 index 00000000..105d5263 --- /dev/null +++ b/lib/main/lockDetail/doorLockLog/viewExportRecord/viewExportRecord_logic.dart @@ -0,0 +1,6 @@ +import 'package:star_lock/main/lockDetail/doorLockLog/viewExportRecord/viewExportRecord_state.dart'; +import 'package:star_lock/tools/baseGetXController.dart'; + +class ViewExportRecordLogic extends BaseGetXController { + ViewExportRecordState state = ViewExportRecordState(); +} diff --git a/lib/main/lockDetail/doorLockLog/viewExportRecord/viewExportRecord_page.dart b/lib/main/lockDetail/doorLockLog/viewExportRecord/viewExportRecord_page.dart new file mode 100644 index 00000000..88319643 --- /dev/null +++ b/lib/main/lockDetail/doorLockLog/viewExportRecord/viewExportRecord_page.dart @@ -0,0 +1,93 @@ +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:excel/excel.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:star_lock/app_settings/app_colors.dart'; +import 'package:star_lock/app_settings/app_settings.dart'; +import 'package:star_lock/main/lockDetail/doorLockLog/viewExportRecord/viewExportRecord_logic.dart'; +import 'package:star_lock/main/lockDetail/doorLockLog/viewExportRecord/viewExportRecord_state.dart'; +import 'package:star_lock/tools/titleAppBar.dart'; + +class ViewExportRecordPage extends StatefulWidget { + const ViewExportRecordPage({Key? key}) : super(key: key); + + @override + State createState() => _ViewExportRecordPageState(); +} + +class _ViewExportRecordPageState extends State + with RouteAware { + final ViewExportRecordLogic logic = Get.put(ViewExportRecordLogic()); + final ViewExportRecordState state = Get.find().state; + + @override + void initState() { + super.initState(); + loadExcel(); + } + + Future loadExcel() async { + try { + final Directory appDocDir = await getApplicationDocumentsDirectory(); + final String appDocPath = appDocDir.path; + final String filePath = '$appDocPath/exported_file.xlsx'; + + final Uint8List bytes = File(filePath).readAsBytesSync(); + final Excel excel = Excel.decodeBytes(bytes); + + for (final String table in excel.tables.keys) { + for (final List row in excel.tables[table]!.rows) { + setState(() { + state.excelData.add(row); + }); + } + } + } catch (e) { + print('Error reading Excel: $e'); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.mainBackgroundColor, + appBar: TitleAppBar( + barTitle: '查看导出记录'.tr, + haveBack: true, + backgroundColor: AppColors.mainColor, + ), + body: Center( + child: state.excelData.isEmpty + ? const CircularProgressIndicator() + : SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: DataTable( + columns: List.generate( + state.excelData[0].length, + (int index) => DataColumn( + label: Text('${state.excelData[0][index]}'), + ), + ), + rows: List.generate( + state.excelData.length - 1, + (int index) => DataRow( + cells: List.generate( + state.excelData[index + 1].length, + (int cellIndex) => DataCell( + Text('${state.excelData[index + 1][cellIndex]}'), + ), + ), + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/main/lockDetail/doorLockLog/viewExportRecord/viewExportRecord_state.dart b/lib/main/lockDetail/doorLockLog/viewExportRecord/viewExportRecord_state.dart new file mode 100644 index 00000000..edf502fb --- /dev/null +++ b/lib/main/lockDetail/doorLockLog/viewExportRecord/viewExportRecord_state.dart @@ -0,0 +1,11 @@ +import 'package:get/get.dart'; + +class ViewExportRecordState { + ViewExportRecordState() { + final Map map = Get.arguments; + getFilePath.value = map['filePath']; + } + + RxString getFilePath = ''.obs; + List> excelData = []; +} diff --git a/lib/network/api.dart b/lib/network/api.dart index 620cb749..da15ded9 100755 --- a/lib/network/api.dart +++ b/lib/network/api.dart @@ -257,4 +257,5 @@ abstract class Api { final String keyNoticeTemplateURL = '/key/getNoticeTemplate'; //获取电子钥匙通知模板 final String keyNoticeSubmitURL = '/key/noticeSubmit'; //发送短信、邮件通知 final String lockUpdateLockInfo = '/lock/updateLockInfo'; //更新锁固件版本 + final String exportLockRecordsURL = '/lockRecords/export'; //导出锁操作记录 } diff --git a/lib/network/api_provider.dart b/lib/network/api_provider.dart index d29b63a2..d226953a 100755 --- a/lib/network/api_provider.dart +++ b/lib/network/api_provider.dart @@ -397,8 +397,12 @@ class ApiProvider extends BaseProvider { })); // 获取锁信息列表 - Future getStarLockListInfo(int pageNo, int pageSize, - {bool isUnShowLoading = true, int? keyId,}) => + Future getStarLockListInfo( + int pageNo, + int pageSize, { + bool isUnShowLoading = true, + int? keyId, + }) => post( getStarLockInfoURL.toUrl, jsonEncode({ @@ -1460,9 +1464,7 @@ class ApiProvider extends BaseProvider { // 获取转移锁锁列表 Future getTransferLockListData(String searchStr) => - post(transferLockListURL.toUrl, jsonEncode({ - "searchStr":searchStr - })); + post(transferLockListURL.toUrl, jsonEncode({"searchStr": searchStr})); // 转移智能锁确认 Future transferLockConfirmInfoData( @@ -2284,6 +2286,34 @@ class ApiProvider extends BaseProvider { 'fwVersion': fwVersion, }), ); + + Future> exportLockRecords( + int lockId, + int startDate, + int endDate, + ) => + post( + exportLockRecordsURL.toUrl, + jsonEncode({ + 'lockId': lockId, + 'startDate': startDate, + 'endDate': endDate, + }), + ); + + Future> batchExportLockRecords( + List lockIds, + int startDate, + int endDate, + ) => + post( + exportLockRecordsURL.toUrl, + jsonEncode({ + 'lockIds': lockIds, + 'startDate': startDate, + 'endDate': endDate, + }), + ); } extension ExtensionString on String { diff --git a/lib/network/api_repository.dart b/lib/network/api_repository.dart index 54059412..33900384 100755 --- a/lib/network/api_repository.dart +++ b/lib/network/api_repository.dart @@ -4,6 +4,7 @@ import 'package:star_lock/login/login/app_get_version.dart'; import 'package:star_lock/login/selectCountryRegion/common/countryRegionEntity.dart'; import 'package:star_lock/main/lockDetail/authorizedAdmin/authorizedAdmin/notice_template_entity.dart'; import 'package:star_lock/main/lockDetail/doorLockLog/doorLockLog_entity.dart'; +import 'package:star_lock/main/lockDetail/doorLockLog/exportRecordDialog/exportRecord_entity.dart'; import 'package:star_lock/main/lockDetail/electronicKey/electronicKeyList/entity/ElectronicKeyEntity.dart'; import 'package:star_lock/main/lockDetail/electronicKey/electronicKeyList/entity/ElectronicKeyListEntity.dart'; import 'package:star_lock/main/lockDetail/electronicKey/massSendElectronicKey/massSendLockGroupList/lockUserList/lockUserList_entity.dart'; @@ -1653,7 +1654,8 @@ class ApiRepository { } // 获取转移锁锁列表 - Future getTransferLockListData({required String searchStr}) async { + Future getTransferLockListData( + {required String searchStr}) async { final res = await apiProvider.getTransferLockListData(searchStr); return TransferSmartLockEntity.fromJson(res.body); } @@ -2298,4 +2300,26 @@ class ApiRepository { await apiProvider.getLockUpdateLockInfo(lockId, fwVersion); return UpdateLockInfoEntity.fromJson(res.body); } + + //导出锁记录 + Future exportLockRecords({ + required int lockId, + required int startDate, + required int endDate, + }) async { + final Response res = + await apiProvider.exportLockRecords(lockId, startDate, endDate); + return ExportRecordEntity.fromJson(res.body); + } + + //批量导出锁记录 + Future batchExportLockRecords({ + required List lockIds, + required int startDate, + required int endDate, + }) async { + final Response res = + await apiProvider.batchExportLockRecords(lockIds, startDate, endDate); + return ExportRecordEntity.fromJson(res.body); + } }