From 9fdf8377eec4b3eb75c7862fed2dca81c662f41a Mon Sep 17 00:00:00 2001 From: liyi Date: Tue, 19 Aug 2025 10:16:20 +0800 Subject: [PATCH] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E7=BC=A9=E7=95=A5=E5=9B=BE=E3=80=81=E8=B0=83=E6=95=B4=E6=93=8D?= =?UTF-8?q?=E4=BD=9C=E8=AE=B0=E5=BD=95=E6=9F=A5=E8=AF=A2=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../doorLockLog/doorLockLog_logic.dart | 32 +++++- .../doorLockLog/doorLockLog_page.dart | 99 ++++++++++++++----- .../widget/video_thumbnail_image.dart | 62 +++++------- 3 files changed, 130 insertions(+), 63 deletions(-) diff --git a/lib/main/lockDetail/doorLockLog/doorLockLog_logic.dart b/lib/main/lockDetail/doorLockLog/doorLockLog_logic.dart index f667fd50..e7240743 100755 --- a/lib/main/lockDetail/doorLockLog/doorLockLog_logic.dart +++ b/lib/main/lockDetail/doorLockLog/doorLockLog_logic.dart @@ -236,7 +236,7 @@ class DoorLockLogLogic extends BaseGetXController { lockId: state.keyInfos.value.lockId!, lockEventType: state.dropdownValue.value, pageNo: pageNo, - pageSize: 100, + pageSize: 1000, startDate: state.startDate.value, endDate: state.endDate.value); if (entity.errorCode!.codeIsSuccessful) { @@ -361,6 +361,7 @@ class DoorLockLogLogic extends BaseGetXController { @override Future onInit() async { + _setWeekRange(); super.onInit(); // 获取是否是演示模式 演示模式不获取接口 @@ -373,6 +374,35 @@ class DoorLockLogLogic extends BaseGetXController { } } + void _setWeekRange() { + final now = DateTime.now(); + + // 计算当前日期是星期几(1=周一,7=周日) + int weekday = now.weekday; // 1-7 + + // 计算距离本周一有多少天(向后推) + // 周一: 0天, 周二: 1天, ..., 周日: 6天 + int daysToSubtract = weekday - 1; // 减去1,因为周一就是基准 + + // 当前周的周一 00:00:00.000 + DateTime startOfWeek = DateTime(now.year, now.month, now.day) + .subtract(Duration(days: daysToSubtract)); + + // 当前周的周日 23:59:59.999 + DateTime endOfWeek = startOfWeek + .add(Duration(days: 6)) // 加6天到周日 + .add(Duration(hours: 23, minutes: 59, seconds: 59, milliseconds: 999)); + + // 更新响应式变量 + state.startDate.value = startOfWeek.millisecondsSinceEpoch; + state.endDate.value = endOfWeek.millisecondsSinceEpoch; + } + + // 可选:提供一个方法来刷新周范围(比如切换周) + void refreshWeek() { + _setWeekRange(); + } + @override Future onClose() async { super.onClose(); diff --git a/lib/main/lockDetail/doorLockLog/doorLockLog_page.dart b/lib/main/lockDetail/doorLockLog/doorLockLog_page.dart index fbdb1aac..bc70d611 100755 --- a/lib/main/lockDetail/doorLockLog/doorLockLog_page.dart +++ b/lib/main/lockDetail/doorLockLog/doorLockLog_page.dart @@ -37,8 +37,39 @@ class DoorLockLogPage extends StatefulWidget { } class _DoorLockLogPageState extends State with RouteAware { + final ScrollController _scrollController = ScrollController(); final DoorLockLogLogic logic = Get.put(DoorLockLogLogic()); final DoorLockLogState state = Get.find().state; + bool _isAtBottom = false; + + @override + void initState() { + super.initState(); + _scrollController.addListener(_onScroll); + } + + void _onScroll() { + final max = _scrollController.position.maxScrollExtent; + final current = _scrollController.position.pixels; + + AppLog.log('current:${current}'); + // 判断是否接近底部(例如 5 像素内) + if (current >= max - 5) { + if (!_isAtBottom) { + setState(() { + _isAtBottom = true; + }); + print('✅ 已滑动到 timelines 列表底部!'); + // 可以在这里触发加载更多、发送事件等 + } + } else { + if (_isAtBottom) { + setState(() { + _isAtBottom = false; + }); + } + } + } @override Widget build(BuildContext context) { @@ -104,6 +135,27 @@ class _DoorLockLogPageState extends State with RouteAware { Expanded(child: timeLineView()) ], ), + floatingActionButton: Visibility( + visible: _isAtBottom, + child: FloatingActionButton( + onPressed: () { + _scrollController.animateTo( + 0.0, + duration: const Duration(milliseconds: 300), + curve: Curves.easeOut, + ); + }, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(48.w), + ), + backgroundColor: AppColors.mainColor, + child: Icon( + Icons.arrow_upward, + color: Colors.white, + size: 48.w, + ), + ), + ), ); } @@ -193,39 +245,33 @@ class _DoorLockLogPageState extends State with RouteAware { borderRadius: BorderRadius.circular(16.w), ), child: Obx( - () => EasyRefreshTool( - onRefresh: () async { - logic.mockNetworkDataRequest(isRefresh: true); - }, - onLoad: () async { - logic.mockNetworkDataRequest(isRefresh: false); - }, - child: state.lockLogItemList.isNotEmpty - ? Timeline.tileBuilder( - builder: _timelineBuilderWidget(), - theme: TimelineThemeData( - nodePosition: 0.04, //居左侧距离 - connectorTheme: const ConnectorThemeData( - thickness: 1.0, - color: AppColors.greyLineColor, - indent: 0.5, - ), - indicatorTheme: const IndicatorThemeData( - size: 8.0, - color: AppColors.greyLineColor, - position: 0.4, - ), + () => state.lockLogItemList.isNotEmpty + ? Timeline.tileBuilder( + controller: _scrollController, + builder: _timelineBuilderWidget(), + theme: TimelineThemeData( + nodePosition: 0.04, //居左侧距离 + connectorTheme: const ConnectorThemeData( + thickness: 1.0, + color: AppColors.greyLineColor, + indent: 0.5, ), - ) - : NoData(), - ), + indicatorTheme: const IndicatorThemeData( + size: 8.0, + color: AppColors.greyLineColor, + position: 0.4, + ), + ), + ) + : NoData(), ), ); } String formatTimestampToDateTimeYYYYMMDD(int timestampMs) { DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(timestampMs); - DateFormat formatter = DateFormat('MM${'月'.tr}dd${'日'.tr}'); // 格式:2025-08-18 14:30 + DateFormat formatter = + DateFormat('MM${'月'.tr}dd${'日'.tr}'); // 格式:2025-08-18 14:30 return formatter.format(dateTime); } @@ -377,7 +423,6 @@ class _DoorLockLogPageState extends State with RouteAware { Text( '${formatTimestampToDateTimeYYYYMMDD(timelineData.operateDate!)}', style: TextStyle( - fontSize: 20.sp, )), // 使用 SingleChildScrollView 实现横向滚动 diff --git a/lib/main/lockDetail/videoLog/widget/video_thumbnail_image.dart b/lib/main/lockDetail/videoLog/widget/video_thumbnail_image.dart index 4f119569..04ffebb8 100644 --- a/lib/main/lockDetail/videoLog/widget/video_thumbnail_image.dart +++ b/lib/main/lockDetail/videoLog/widget/video_thumbnail_image.dart @@ -1,53 +1,47 @@ -import 'dart:io'; // 导入 dart:io 以使用 File 类 +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:path_provider/path_provider.dart'; // 导入 path_provider -import 'package:video_thumbnail/video_thumbnail.dart'; // 导入 video_thumbnail +import 'package:path_provider/path_provider.dart'; +import 'package:video_thumbnail/video_thumbnail.dart'; class VideoThumbnailImage extends StatefulWidget { final String videoUrl; - VideoThumbnailImage({required this.videoUrl}); + const VideoThumbnailImage({Key? key, required this.videoUrl}) + : super(key: key); @override _VideoThumbnailState createState() => _VideoThumbnailState(); } class _VideoThumbnailState extends State { - final Map _thumbnailCache = {}; // 缩略图缓存 - late Future _thumbnailFuture; // 用于存储缩略图生成的 Future + // ✅ 使用 static 缓存:所有实例共享,避免重复请求 + static final Map> _pendingThumbnails = {}; + + late Future _thumbnailFuture; @override void initState() { super.initState(); - _thumbnailFuture = _generateThumbnail(); // 在 initState 中初始化 Future + // ✅ 如果已存在该 URL 的 Future,复用;否则创建并缓存 + _thumbnailFuture = _pendingThumbnails.putIfAbsent(widget.videoUrl, () { + return _generateThumbnail(widget.videoUrl); + }); } - // 生成缩略图 - Future _generateThumbnail() async { + // 生成缩略图(只执行一次 per URL) + Future _generateThumbnail(String url) async { try { - // 检查缓存中是否已有缩略图 - if (_thumbnailCache.containsKey(widget.videoUrl)) { - return _thumbnailCache[widget.videoUrl]; - } - - // 获取临时目录路径 final tempDir = await getTemporaryDirectory(); - final thumbnailPath = await VideoThumbnail.thumbnailFile( - video: widget.videoUrl, - // 视频 URL + final thumbnail = await VideoThumbnail.thumbnailFile( + video: url, thumbnailPath: tempDir.path, - // 缩略图保存路径 imageFormat: ImageFormat.JPEG, - // 缩略图格式 maxHeight: 200, - // 缩略图最大高度 - quality: 100, // 缩略图质量 (0-100) + quality: 100, ); - - // 更新缓存 - _thumbnailCache[widget.videoUrl] = thumbnailPath!; - return thumbnailPath; + return thumbnail; } catch (e) { print('Failed to generate thumbnail: $e'); return null; @@ -57,27 +51,25 @@ class _VideoThumbnailState extends State { @override Widget build(BuildContext context) { return FutureBuilder( - future: _thumbnailFuture, // 生成缩略图的 Future + future: _thumbnailFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { - // 加载中显示转圈 return Center(child: CircularProgressIndicator()); } else if (snapshot.hasError || !snapshot.hasData) { - // 加载失败或没有数据时显示提示 - return Image.asset( - 'images/icon_unHaveData.png', // 错误图片路径 - fit: BoxFit.cover, + return Center( + child: Image.asset( + 'images/icon_unHaveData.png', + fit: BoxFit.cover, + ), ); } else { - // 加载成功,显示缩略图 - final thumbnailPath = snapshot.data!; return Stack( alignment: Alignment.center, children: [ RotatedBox( quarterTurns: -1, child: Image.file( - File(thumbnailPath), // 显示生成的缩略图 + File(snapshot.data!), width: 200, height: 200, fit: BoxFit.cover,