fix:调整视频缩略图、调整操作记录查询逻辑
This commit is contained in:
parent
335f66a0b5
commit
9fdf8377ee
@ -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<void> 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<void> onClose() async {
|
||||
super.onClose();
|
||||
|
||||
@ -37,8 +37,39 @@ class DoorLockLogPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _DoorLockLogPageState extends State<DoorLockLogPage> with RouteAware {
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
final DoorLockLogLogic logic = Get.put(DoorLockLogLogic());
|
||||
final DoorLockLogState state = Get.find<DoorLockLogLogic>().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<DoorLockLogPage> 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<DoorLockLogPage> 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<DoorLockLogPage> with RouteAware {
|
||||
Text(
|
||||
'${formatTimestampToDateTimeYYYYMMDD(timelineData.operateDate!)}',
|
||||
style: TextStyle(
|
||||
|
||||
fontSize: 20.sp,
|
||||
)),
|
||||
// 使用 SingleChildScrollView 实现横向滚动
|
||||
|
||||
@ -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<VideoThumbnailImage> {
|
||||
final Map<String, String> _thumbnailCache = {}; // 缩略图缓存
|
||||
late Future<String?> _thumbnailFuture; // 用于存储缩略图生成的 Future
|
||||
// ✅ 使用 static 缓存:所有实例共享,避免重复请求
|
||||
static final Map<String, Future<String?>> _pendingThumbnails = {};
|
||||
|
||||
late Future<String?> _thumbnailFuture;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_thumbnailFuture = _generateThumbnail(); // 在 initState 中初始化 Future
|
||||
// ✅ 如果已存在该 URL 的 Future,复用;否则创建并缓存
|
||||
_thumbnailFuture = _pendingThumbnails.putIfAbsent(widget.videoUrl, () {
|
||||
return _generateThumbnail(widget.videoUrl);
|
||||
});
|
||||
}
|
||||
|
||||
// 生成缩略图
|
||||
Future<String?> _generateThumbnail() async {
|
||||
// 生成缩略图(只执行一次 per URL)
|
||||
Future<String?> _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<VideoThumbnailImage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder<String?>(
|
||||
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: <Widget>[
|
||||
RotatedBox(
|
||||
quarterTurns: -1,
|
||||
child: Image.file(
|
||||
File(thumbnailPath), // 显示生成的缩略图
|
||||
File(snapshot.data!),
|
||||
width: 200,
|
||||
height: 200,
|
||||
fit: BoxFit.cover,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user