Commit 7ba2abc6 authored by 胡曲's avatar 胡曲

fix bugs

parent b57da32b
......@@ -34,11 +34,13 @@ class RoomOverviewSummaryBO extends Equatable {
/// 楼层Tab列表
class RoomFloorBO extends Equatable {
final int floorId;
final int floorNumber;
final String floorName;
final bool isActive;
const RoomFloorBO({
this.floorId = 0,
this.floorNumber = 0,
this.floorName = '',
this.isActive = false,
});
......@@ -46,18 +48,20 @@ class RoomFloorBO extends Equatable {
factory RoomFloorBO.fromJson(Map<String, dynamic> json) {
return RoomFloorBO(
floorId: json['floorId'] as int? ?? 0,
floorNumber: json['floorNumber'] as int? ?? 0,
floorName: json['floorName'] as String? ?? '',
isActive: json['isActive'] as bool? ?? false,
);
}
@override
List<Object?> get props => [floorId, floorName, isActive];
List<Object?> get props => [floorId, floorNumber, floorName, isActive];
}
/// 房间网格(房间卡片网格布局)
class RoomGridBO extends Equatable {
final int roomId;
final int floorNumber;
final String roomNumber;
final String statusText;
final String statusColor;
......@@ -65,6 +69,7 @@ class RoomGridBO extends Equatable {
const RoomGridBO({
this.roomId = 0,
this.floorNumber = 0,
this.roomNumber = '',
this.statusText = '',
this.statusColor = '',
......@@ -78,11 +83,12 @@ class RoomGridBO extends Equatable {
statusText: json['statusText'] as String? ?? '',
statusColor: json['statusColor'] as String? ?? '',
hasAlarmIcon: json['hasAlarmIcon'] as bool? ?? false,
floorNumber: json['floorNumber'] as int? ?? 0,
);
}
@override
List<Object?> get props => [roomId, roomNumber, statusText, statusColor, hasAlarmIcon];
List<Object?> get props => [roomId, roomNumber, statusText, statusColor, hasAlarmIcon, floorNumber];
}
/// 房间基础信息(客房概览统计)
......@@ -90,11 +96,13 @@ class RoomOverviewBasicInfoBO extends Equatable {
final int roomId;
final String roomNumber;
final String roomStatusText;
final int floorNumber;
const RoomOverviewBasicInfoBO({
this.roomId = 0,
this.roomNumber = '',
this.roomStatusText = '',
this.floorNumber = 0,
});
factory RoomOverviewBasicInfoBO.fromJson(Map<String, dynamic> json) {
......@@ -102,11 +110,12 @@ class RoomOverviewBasicInfoBO extends Equatable {
roomId: json['roomId'] as int? ?? 0,
roomNumber: json['roomNumber'] as String? ?? '',
roomStatusText: json['roomStatusText'] as String? ?? '',
floorNumber: json['floorNumber'] as int? ?? 0,
);
}
@override
List<Object?> get props => [roomId, roomNumber, roomStatusText];
List<Object?> get props => [roomId, roomNumber, roomStatusText, floorNumber];
}
/// 状态标签
......@@ -261,11 +270,15 @@ String _parseString(dynamic value, {String fallback = ''}) {
class FloorAreaBO extends Equatable {
final int floorId;
final int floorNumber;
final String floorName;
final List<String> areas;
final List<FloorRoomSimpleBO> rooms;
const FloorAreaBO({
required this.floorId,
required this.floorNumber,
required this.floorName,
required this.areas,
required this.rooms,
});
......@@ -273,6 +286,8 @@ class FloorAreaBO extends Equatable {
factory FloorAreaBO.fromJson(Map<String, dynamic> json) {
return FloorAreaBO(
floorId: _parseInt(json['floorId']),
floorNumber: _parseInt(json['floorNumber']),
floorName: json['floorName'] as String? ?? '',
areas: (json['areas'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
......@@ -286,9 +301,7 @@ class FloorAreaBO extends Equatable {
}
@override
List<Object?> get props => [floorId, areas, rooms];
String get floorName => '${floorId}楼';
List<Object?> get props => [floorId, floorNumber, floorName, areas, rooms];
}
class FloorRoomSimpleBO extends Equatable {
......
......@@ -65,27 +65,31 @@ class RoomRepository {
);
}
/// 客房详情-房间总电源-开
Future<ResponseModel> roomPowerOn({required int roomId}) {
return DioRequest.instance.post(
'/app/room/power/on',
data: {'roomId': roomId},
/// 单设备送电
Future<ResponseModel> powerSingleOn({required int deviceId}) {
return DioRequest.instance.post<Map<String, dynamic>>(
'/app/room/device/power/single/on',
data: {'deviceId': deviceId},
);
}
/// 客房详情-房间总电源-关
Future<ResponseModel> roomPowerOff({required int roomId}) {
return DioRequest.instance.post(
'/app/room/power/off',
data: {'roomId': roomId},
/// 单设备断电
Future<ResponseModel> powerSingleOff({required int deviceId}) {
return DioRequest.instance.post<Map<String, dynamic>>(
'/app/room/device/power/single/off',
data: {'deviceId': deviceId},
);
}
/// 客房状态总览-客房概览统计
Future<ResponseModel<RoomOverviewBO>> getOverview(int? floorId) {
Map<String, dynamic> param = {};
if (floorId != null && floorId != 0) {
param['floorId'] = floorId;
}
return DioRequest.instance.get<RoomOverviewBO>(
'/app/room/overview',
queryParameters: {'floorId': floorId == 0 ? null : floorId},
queryParameters: param,
fromJsonT: (data) => RoomOverviewBO.fromJson(data as Map<String, dynamic>),
);
}
......
......@@ -57,22 +57,6 @@ class RoomService {
throw Exception(result.msg);
}
/// 客房详情-房间总电源-开
Future<void> roomPowerOn({required int roomId}) async {
final result = await _repository.roomPowerOn(roomId: roomId);
if (!result.success) {
throw Exception(result.msg);
}
}
/// 客房详情-房间总电源-关
Future<void> roomPowerOff({required int roomId}) async {
final result = await _repository.roomPowerOff(roomId: roomId);
if (!result.success) {
throw Exception(result.msg);
}
}
/// 客房状态总览-客房概览统计
Future<RoomOverviewBO> getOverview(int? floorId) async {
final result = await _repository.getOverview(floorId);
......@@ -96,4 +80,20 @@ class RoomService {
}
throw Exception(result.msg);
}
/// 单设备送电
Future<void> powerSingleOn({required int deviceId}) async {
final result = await _repository.powerSingleOn(deviceId: deviceId);
if (!result.success) {
throw Exception(result.msg);
}
}
/// 单设备断电
Future<void> powerSingleOff({required int deviceId}) async {
final result = await _repository.powerSingleOff(deviceId: deviceId);
if (!result.success) {
throw Exception(result.msg);
}
}
}
\ No newline at end of file
......@@ -4,7 +4,36 @@ class DealUtils {
/// 去除多余0
static String cleanNumber(String value) {
return value.replaceAll(RegExp(r'\.?0+$'), '');
try {
return value.replaceAll(RegExp(r'\.?0+$'), '');
} catch (e) {
// rethrow;
return value;
}
}
/// 电流,电压
static String formatVoltage(String input) {
if (input.isEmpty) return input;
try {
// 匹配开头的数字部分(包括小数)
RegExp regExp = RegExp(r'^([\d.]+)');
String? numberPart = regExp.firstMatch(input)?.group(1);
String unitPart = input.replaceFirst(RegExp(r'^[\d.]+'), '');
if (numberPart == null) return input;
// 去掉尾随的零和多余的小数点
String formattedNumber = numberPart
.replaceFirst(RegExp(r'\.?0+$'), '') // 去掉末尾的0和可能的小数点
.replaceFirst(RegExp(r'\.$'), ''); // 如果只剩小数点,去掉
return formattedNumber + unitPart;
} catch (e) {
// rethrow;
return input;
}
}
/// 设备图标
......
......@@ -102,7 +102,7 @@ class AbnormalDetailView extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
spacing: 20.h,
children: [
if (alarmStatus != AlarmStatus.processed)
if (alarmStatus != AlarmStatus.processed)//不等于已处理才显示
BlocBuilder<AbnormalCubit, AbnormalState>(
builder: (context, state) {
final isDisabled = state.isHandling || state.isHandled;
......
......@@ -14,8 +14,12 @@ String? alarmStatusToApi(AlarmStatus status) {
return null;
case AlarmStatus.pending:
return '0';
case AlarmStatus.processing:
return '1';
case AlarmStatus.processed:
return '2';
case AlarmStatus.close:
return '3';
case AlarmStatus.falseAlarm:
return '4';
}
......@@ -75,9 +79,46 @@ class AbnormalListCubit extends Cubit<AbnormalListState> {
_pagingController?.refresh();
}
void onAlarmItemTap(AlarmItem item, BuildContext context) {
Future<void> onAlarmItemTap(AlarmItem item, BuildContext context) async {
final alertId = int.tryParse(item.id) ?? 0;
context.pushRoute(AbnormalDetailRoute(
alertId: alertId, alarmStatus: item.status));
final handled = await context.pushRoute(
AbnormalDetailRoute(alertId: alertId, alarmStatus: item.status),
);
// if (handled != null && handled is bool && handled == true) {
// _updateItemStatus(item.id);
// }
}
/// 在分页列表中定位并更新单条记录的状态(不重新请求接口)
void _updateItemStatus(String alertId) {
final state = _pagingController?.value;
if (state == null) return;
final pages = state.pages;
if (pages == null) return;
var found = false;
final newPages = pages.map((page) {
if (found) return page;
final index = page.indexWhere((i) => i.id == alertId);
if (index == -1) return page;
found = true;
final newPage = List<AlarmItem>.from(page);
newPage[index] = AlarmItem(
id: newPage[index].id,
title: newPage[index].title,
deviceInfo: newPage[index].deviceInfo,
alarmTime: newPage[index].alarmTime,
processTime: newPage[index].processTime,
status: AlarmStatus.processed,
level: newPage[index].level,
alertIcon: newPage[index].alertIcon,
);
return newPage;
}).toList();
if (found) {
_pagingController?.value = state.copyWith(pages: newPages);
}
}
}
import 'package:equatable/equatable.dart';
import 'package:smart_hotel_app/models/bo/alert_list_bo.dart';
enum AlarmStatus { all, pending, processed, falseAlarm }
/// 0=待处理 1=处理中 2=已处理 3=已关闭 4=误报关闭
enum AlarmStatus { all, pending, processing, processed, close, falseAlarm }
class AlarmItem extends Equatable {
final String id;
......@@ -37,13 +38,17 @@ class AlarmItem extends Equatable {
);
}
// 0=待处理 1=处理中 2=已确认 3=已关闭 4=误报关闭
// 0=待处理 1=处理中 2=已处理 3=已关闭 4=误报关闭
static AlarmStatus _mapAlertStatus(String alertStatus) {
switch (alertStatus) {
case '0':
return AlarmStatus.pending;
case '1':
return AlarmStatus.processing;
case '2':
return AlarmStatus.processed;
case '3':
return AlarmStatus.close;
case '4':
return AlarmStatus.falseAlarm;
default:
......
......@@ -185,7 +185,7 @@ class _AbnormalListBodyState extends State<_AbnormalListBody> {
itemBuilder: (_, item, __) {
return AlarmListItem(
item: item,
onTap: (i) => cubit.onAlarmItemTap(i, context),
onTap: (i) { cubit.onAlarmItemTap(i, context); },
);
},
),
......
......@@ -170,7 +170,8 @@ class AlarmListItem extends StatelessWidget {
Color? bgColor;
Color? textColor;
String text = '';
String icon = '';
String iconImg = '';
IconData? iconData;
double iconW = 0;
double iconH = 0;
......@@ -179,23 +180,39 @@ class AlarmListItem extends StatelessWidget {
bgColor = const Color.fromRGBO(254, 247, 217, 1);
textColor = const Color.fromRGBO(250, 204, 21, 1);
text = '待处理';
icon = 'lib/assets/icon/info_dcl_bg.png';
iconImg = 'lib/assets/icon/info_dcl_bg.png';
iconW = 4.w;
iconH = 15.h;
break;
case AlarmStatus.processing:
bgColor = const Color.fromRGBO(245, 245, 245, 1);
textColor = const Color.fromRGBO(51, 51, 51, 1);
text = '处理中';
iconData = Icons.check;
iconW = 18.w;
iconH = 11.h;
break;
case AlarmStatus.processed:
bgColor = const Color.fromRGBO(209, 250, 229, 1);
textColor = const Color.fromRGBO(20, 184, 166, 1);
text = '已处理';
icon = 'lib/assets/icon/info_ycl_bg.png';
iconData = Icons.check;
iconW = 18.w;
iconH = 11.h;
break;
case AlarmStatus.close:
bgColor = const Color.fromRGBO(245, 245, 245, 1);
textColor = const Color.fromRGBO(51, 51, 51, 1);
text = '关闭';
iconData = Icons.close;
iconW = 18.w;
iconH = 15.h;
break;
case AlarmStatus.falseAlarm:
bgColor = const Color.fromRGBO(255, 224, 224, 1);
textColor = const Color.fromRGBO(255, 100, 101, 1);
text = '误报';
icon = 'lib/assets/icon/info_wb_bg.png';
iconData = Icons.close;
iconW = 14.w;
iconH = 11.h;
break;
......@@ -218,7 +235,10 @@ class AlarmListItem extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
spacing: 6.w,
children: [
Image.asset(icon, width: iconW, height: iconH),
if (iconData != null)
Icon(iconData, color: textColor, size: 26.w)
else
Image.asset(iconImg, width: iconW, height: iconH),//没有纯感叹号的图标
Text(
text,
style: TextStyle(
......
......@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/models/bo/alert_dashboard_bo.dart';
import 'package:smart_hotel_app/utils/deal_utils.dart';
import 'package:smart_hotel_app/views/home/index/cubit/home_index_cubit.dart';
import 'package:smart_hotel_app/views/home/index/cubit/home_index_state.dart';
import 'package:smart_hotel_app/views/home/index/widget/equipment_list_block.dart';
......@@ -50,42 +51,75 @@ class _HomeViewState extends State<HomeView> with AutoRouteAwareStateMixin<HomeV
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: Container(
width: double.infinity,
color: const Color.fromRGBO(242, 243, 245, 1),
padding: EdgeInsets.symmetric(horizontal: 28.w),
child: BlocBuilder<HomeIndexCubit, HomeIndexState>(
builder: (context, state) {
return Column(
children: [
const TaskHeadBlock(),
SizedBox(height: 20.h),
TaskTotalBlock(
highAlarmCount: state.highAlarmCount,
mediumAlarmCount: state.mediumAlarmCount,
onlineDeviceCount: state.onlineDeviceCount,
),
..._buildAlertBlocks(state.alerts),
SizedBox(height: 20.h),
EquipmentListBlock(
equipmentList: state.equipmentList,
),
SizedBox(height: 20.h),
],
);
},
),
return BlocBuilder<HomeIndexCubit, HomeIndexState>(
builder: (context, state) {
if (state.isLoading) {
return Container(
color: const Color.fromRGBO(242, 243, 245, 1),
child: const Center(child: CircularProgressIndicator(color: Color.fromRGBO(66, 165, 245, 1.0))),
);
}
if (state.error != null) {
return Container(
color: const Color.fromRGBO(242, 243, 245, 1),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'加载失败: ${state.error}',
style: TextStyle(
fontSize: 28.sp,
color: const Color.fromRGBO(100, 116, 139, 1),
),
),
SizedBox(height: 16.h),
ElevatedButton(
onPressed: () =>
context.read<HomeIndexCubit>().reloadDashboard(),
child: const Text('重试'),
),
],
),
),
);
},
}
return LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: Container(
width: double.infinity,
color: const Color.fromRGBO(242, 243, 245, 1),
padding: EdgeInsets.symmetric(horizontal: 28.w),
child: Column(
children: [
const TaskHeadBlock(),
SizedBox(height: 20.h),
TaskTotalBlock(
highAlarmCount: state.highAlarmCount,
mediumAlarmCount: state.mediumAlarmCount,
onlineDeviceCount: state.onlineDeviceCount,
),
..._buildAlertBlocks(state.alerts),
SizedBox(height: 20.h),
EquipmentListBlock(
equipmentList: state.equipmentList,
),
SizedBox(height: 20.h),
],
),
),
),
);
},
);
},
);
}
......@@ -105,8 +139,8 @@ class _HomeViewState extends State<HomeView> with AutoRouteAwareStateMixin<HomeV
temperatureAlarmTitle: alert.alertTitle,
temperatureAlarmLocation: location,
currentTemperature: alert.temperature,
currentVoltage: alert.voltage,
currentCurrent: alert.current,
currentVoltage: DealUtils.formatVoltage(alert.voltage),
currentCurrent: DealUtils.formatVoltage(alert.current),
waitTime: alert.waitingDurationDesc,
alarmLevel: alert.alertLevelText,
),
......@@ -121,7 +155,7 @@ class _HomeViewState extends State<HomeView> with AutoRouteAwareStateMixin<HomeV
alertId: alert.alertId,
voltageAlarmTitle: alert.alertTitle,
voltageAlarmLocation: location,
voltageValue: alert.voltage,
voltageValue: DealUtils.formatVoltage(alert.voltage),
alertContent: alert.alertContent.isNotEmpty ?
'(${alert.alertContent})' : '',
voltageWaitTime: alert.waitingDurationDesc,
......
......@@ -83,6 +83,7 @@ class VoltageOperateBlock extends StatelessWidget {
],
),
),
SizedBox(width: 15.w),
Text(
voltageAlarmLevel,
style: TextStyle(
......
......@@ -31,7 +31,7 @@ class InspectionCubit extends Cubit<InspectionState> {
final roomGroups = rooms.roomGroups;
// 默认选中第一个区域、第一个房间
final defaultAreaIndex = roomGroups.isNotEmpty ? 0 : 0;
const defaultAreaIndex = 0;
final defaultRoomId = roomGroups.isNotEmpty &&
roomGroups[defaultAreaIndex].rooms.isNotEmpty
? roomGroups[defaultAreaIndex].rooms.first.roomId
......@@ -54,8 +54,7 @@ class InspectionCubit extends Cubit<InspectionState> {
}
/// 从顶层 runStatus 汇总计算各 tab 数量
Map<DeviceStatusTab, int> _computeTabCounts(
InspectionRoomRunStatusBO runStatus) {
Map<DeviceStatusTab, int> _computeTabCounts(InspectionRoomRunStatusBO runStatus) {
final total =
runStatus.normal + runStatus.warning + runStatus.offline + runStatus.fault;
return {
......
......@@ -65,7 +65,7 @@ class InspectionDeviceCubit extends Cubit<InspectionDeviceState> {
lastInspectionTime: basicInfo.lastInspectTime,
inspectionHistory: historyList,
inspectionItems: items,
icon: Icons.devices,
icon: Icons.devices_other,
));
} catch (e) {
emit(state.copyWith(isLoading: false, error: e.toString()));
......
......@@ -82,16 +82,16 @@ class DeviceOverviewCard extends StatelessWidget {
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 4.h),
decoration: BoxDecoration(
color: deviceStatus == '1'
? const Color.fromRGBO(225, 245, 238, 1.0)
? const Color.fromRGBO(209, 250, 229, 1.0)
: const Color.fromRGBO(255, 235, 238, 1.0),
borderRadius: BorderRadius.circular(20.r),
borderRadius: BorderRadius.circular(40.r),
),
child: Text(
deviceStatus == '1' ? '在线' : '离线',
style: TextStyle(
fontSize: 24.sp,
color: deviceStatus == '1'
? const Color.fromRGBO(209, 250, 229, 1.0)
? const Color.fromRGBO(20, 184, 166, 1.0)
: const Color.fromRGBO(231, 76, 60, 1.0),
),
),
......
......@@ -82,7 +82,7 @@ class ReportEnergyDetailView extends StatelessWidget {
),
SizedBox(height: 16.h),
ZoneChartBlock(zoneData: state.zoneData),
SizedBox(height: 20.h),
SizedBox(height: 16.h),
],
),
);
......
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/utils/deal_utils.dart';
import 'package:smart_hotel_app/views/report/energy/cubit/energy_state.dart';
class ZoneChartBlock extends StatelessWidget {
......@@ -36,11 +37,13 @@ class ZoneChartBlock extends StatelessWidget {
bottom: index < zoneData.length - 1 ? 20.h : 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 12.h,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
spacing: 8.w,
children: [
Container(
width: 12.w,
......@@ -52,7 +55,6 @@ class ZoneChartBlock extends StatelessWidget {
shape: BoxShape.circle,
),
),
SizedBox(width: 8.w),
Text(
data.name,
style: TextStyle(
......@@ -63,7 +65,8 @@ class ZoneChartBlock extends StatelessWidget {
],
),
Text(
'${data.percentage.toInt()}% (${data.energy.toInt()} kWh)',
'${DealUtils.cleanNumber(data.percentage.toString())}% '
'(${DealUtils.cleanNumber(data.energy.toString())} kWh)',
style: TextStyle(
color: index == 0
? const Color.fromRGBO(59, 130, 246, 1)
......@@ -73,7 +76,6 @@ class ZoneChartBlock extends StatelessWidget {
),
],
),
SizedBox(height: 12.h),
Stack(
children: [
Container(
......
......@@ -12,7 +12,7 @@ class ReportIndexCubit extends Cubit<ReportIndexState> {
ReportIndexCubit({DashboardService? service})
: _service = service ?? DashboardService(repository: DashboardRepository()),
super(const ReportIndexState()) {
loadData();
// loadData();
}
Future<void> loadData() async {
......
......@@ -2,7 +2,6 @@ import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:smart_hotel_app/routes/app_router.dart';
import 'package:smart_hotel_app/routes/app_router.gr.dart';
class DeviceOnlineRate extends StatelessWidget {
......@@ -13,10 +12,13 @@ class DeviceOnlineRate extends StatelessWidget {
@override
Widget build(BuildContext context) {
final devices =
deviceOnlineRate['devices'] as List<Map<String, dynamic>>;
(deviceOnlineRate['devices'] as List<dynamic>?)
?.map((e) => e as Map<String, dynamic>)
.toList() ??
<Map<String, dynamic>>[];
final totalDevices =
devices.fold<int>(0, (sum, device) => sum + (device['count'] as int));
final rate = deviceOnlineRate['rate'] as double;
devices.fold<int>(0, (sum, device) => sum + ((device['count'] as int?) ?? 0));
final rate = deviceOnlineRate['rate'] as double? ?? 0.0;
return GestureDetector(
onTap: () {
......@@ -36,7 +38,7 @@ class DeviceOnlineRate extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
deviceOnlineRate['title'] as String,
(deviceOnlineRate['title'] as String?) ?? '',
style: TextStyle(
fontSize: 28.sp,
fontWeight: FontWeight.normal,
......@@ -93,7 +95,7 @@ class DeviceOnlineRate extends StatelessWidget {
),
),
Text(
deviceOnlineRate['rateText'] as String,
(deviceOnlineRate['rateText'] as String?) ?? '',
style: TextStyle(
fontSize: 24.sp,
fontWeight: FontWeight.bold,
......@@ -113,14 +115,14 @@ class DeviceOnlineRate extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
device['name'] as String,
(device['name'] as String?) ?? '',
style: TextStyle(
fontSize: 22.sp,
color: const Color.fromRGBO(107, 114, 128, 1),
),
),
Text(
'${device['count']} 台',
'${device['count'] ?? 0} 台',
style: TextStyle(
fontSize: 22.sp,
color: const Color.fromRGBO(55, 65, 81, 1),
......
......@@ -11,7 +11,11 @@ class QuickActionsBlock extends StatelessWidget {
@override
Widget build(BuildContext context) {
final actions = quickActions['actions'] as List<Map<String, dynamic>>;
final actions =
(quickActions['actions'] as List<dynamic>?)
?.map((e) => e as Map<String, dynamic>)
.toList() ??
<Map<String, dynamic>>[];
return Container(
width: double.infinity,
......@@ -24,7 +28,7 @@ class QuickActionsBlock extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
quickActions['title'] as String,
(quickActions['title'] as String?) ?? '',
style: TextStyle(
fontSize: 28.sp,
fontWeight: FontWeight.normal,
......@@ -34,7 +38,7 @@ class QuickActionsBlock extends StatelessWidget {
SizedBox(height: 28.h),
Row(
children: actions.map((action) {
final name = action['name'] as String;
final name = (action['name'] as String?) ?? '';
return Padding(
padding: EdgeInsets.only(right: 60.w),
child: GestureDetector(
......@@ -55,7 +59,7 @@ class QuickActionsBlock extends StatelessWidget {
borderRadius: BorderRadius.circular(16.r),
),
child: Icon(
action['icon'] as IconData,
(action['icon'] as IconData?) ?? Icons.apps,
color: const Color.fromRGBO(59, 130, 246, 1),
size: 36.sp,
),
......
......@@ -9,20 +9,25 @@ class ReportMetricsBlock extends StatelessWidget {
@override
Widget build(BuildContext context) {
final powerUsage = reportMetrics['powerUsage'] as Map<String, dynamic>;
final pendingAlerts = reportMetrics['pendingAlerts'] as Map<String, dynamic>;
final powerUsage = reportMetrics.containsKey('powerUsage') &&
reportMetrics['powerUsage'] != null
? Map<String, dynamic>.from(reportMetrics['powerUsage']) : {};
final pendingAlerts = reportMetrics.containsKey('pendingAlerts') &&
reportMetrics['pendingAlerts'] != null
? Map<String, dynamic>.from(reportMetrics['pendingAlerts']) : {};
return Row(
children: [
Expanded(
child: _buildMetricCard(
title: '今日用电',
value: DealUtils.cleanNumber(powerUsage['value'] as String),
unit: powerUsage['unit'] as String,
changeText: powerUsage['changeText'] as String,
value: DealUtils.cleanNumber(powerUsage.containsKey('value') ?
powerUsage['value'] as String : ''),
unit: powerUsage['unit'] as String?,
changeText: powerUsage['changeText'] as String?,
changeColor: const Color.fromRGBO(20, 184, 166, 1),
valueColor: const Color.fromRGBO(73, 149, 234, 1),
progress: powerUsage['progress'] as double,
progress: powerUsage['progress'] as double?,
progressColor: const Color.fromRGBO(73, 149, 234, 1),
),
),
......@@ -30,12 +35,12 @@ class ReportMetricsBlock extends StatelessWidget {
Expanded(
child: _buildMetricCard(
title: '待处理告警',
value: pendingAlerts['value'],
unit: pendingAlerts['unit'] as String,
changeText: pendingAlerts['changeText'] as String,
value: pendingAlerts['value'] as String?,
unit: pendingAlerts['unit'] as String?,
changeText: pendingAlerts['changeText'] as String?,
changeColor: const Color.fromRGBO(250, 204, 21, 1),
valueColor: const Color.fromRGBO(250, 204, 21, 1),
progress: pendingAlerts['progress'] as double,
progress: pendingAlerts['progress'] as double?,
progressColor: const Color.fromRGBO(250, 204, 21, 1),
),
),
......@@ -45,12 +50,12 @@ class ReportMetricsBlock extends StatelessWidget {
Widget _buildMetricCard({
required String title,
required String value,
required String unit,
required String changeText,
required String? value,
required String? unit,
required String? changeText,
required Color changeColor,
required Color valueColor,
required double progress,
required double? progress,
required Color progressColor,
}) {
return Container(
......@@ -73,7 +78,7 @@ class ReportMetricsBlock extends StatelessWidget {
),
),
Text(
changeText,
changeText ?? '',
style: TextStyle(
fontSize: 24.sp,
color: changeColor,
......@@ -86,8 +91,7 @@ class ReportMetricsBlock extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
value,
// cleanNumber(value),
value ?? '',
style: TextStyle(
fontSize: 40.sp,
fontWeight: FontWeight.bold,
......@@ -98,7 +102,7 @@ class ReportMetricsBlock extends StatelessWidget {
Padding(
padding: EdgeInsets.only(bottom: 6.h),
child: Text(
unit,
unit ?? '',
style: TextStyle(
fontSize: 20.sp,
color: const Color.fromRGBO(107, 114, 128, 1),
......
......@@ -12,8 +12,11 @@ class RoomStatusDistribution extends StatelessWidget {
@override
Widget build(BuildContext context) {
final statuses =
roomStatusDistribution['statuses'] as List<Map<String, dynamic>>;
final totalRooms = roomStatusDistribution['totalRooms'] as int;
(roomStatusDistribution['statuses'] as List<dynamic>?)
?.map((e) => e as Map<String, dynamic>)
.toList() ??
<Map<String, dynamic>>[];
final totalRooms = roomStatusDistribution['totalRooms'] as int? ?? 0;
return GestureDetector(
onTap: () {
......@@ -33,7 +36,7 @@ class RoomStatusDistribution extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
roomStatusDistribution['title'] as String,
(roomStatusDistribution['title'] as String?) ?? '',
style: TextStyle(
fontSize: 28.sp,
fontWeight: FontWeight.normal,
......@@ -61,9 +64,9 @@ class RoomStatusDistribution extends StatelessWidget {
),
SizedBox(height: 20.h),
...statuses.map((status) {
final count = status['count'] as int;
final color = status['color'] as Color;
final name = status['name'] as String;
final count = status['count'] as int? ?? 0;
final color = status['color'] as Color? ?? Colors.grey;
final name = status['name'] as String? ?? '';
final percentage = totalRooms > 0 ? count / totalRooms : 0.0;
final percentText = '${(percentage * 100).toInt()}%';
......
......@@ -34,8 +34,8 @@ class WeeklyPowerChart extends StatelessWidget {
double get _bottomInterval {
final len = _spots.length;
if (len <= 1) return 1;
if (len <= 6) return 1;
if (len <= 12) return 2;
if (len <= 7) return 1;
if (len <= 14) return 2;
return (len - 1) / 4;
}
......@@ -71,6 +71,7 @@ class WeeklyPowerChart extends StatelessWidget {
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 20.h,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
......@@ -84,6 +85,7 @@ class WeeklyPowerChart extends StatelessWidget {
),
),
Row(
spacing: 8.w,
children: [
Text(
weeklyPowerUsage['dateRange'] as String? ?? '',
......@@ -92,7 +94,6 @@ class WeeklyPowerChart extends StatelessWidget {
color: const Color.fromRGBO(156, 163, 175, 1),
),
),
SizedBox(width: 8.w),
Icon(
Icons.arrow_forward_ios,
size: 24.sp,
......@@ -102,7 +103,6 @@ class WeeklyPowerChart extends StatelessWidget {
),
],
),
SizedBox(height: 20.h),
Container(
height: 200.h,
padding: EdgeInsets.only(right: 28.w),
......@@ -131,7 +131,7 @@ class WeeklyPowerChart extends StatelessWidget {
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
interval: _bottomInterval,
interval: _bottomInterval,//刻度间隔
getTitlesWidget: (value, meta) {
return Padding(
padding: EdgeInsets.only(top: 8.h),
......@@ -144,7 +144,7 @@ class WeeklyPowerChart extends StatelessWidget {
),
);
},
reservedSize: 28,
reservedSize: 20,//轴标签预留的外部空间
),
),
leftTitles: AxisTitles(
......
......@@ -7,20 +7,27 @@ import 'package:smart_hotel_app/views/report/room/cubit/room_report_state.dart';
class RoomReportCubit extends Cubit<RoomReportState> {
final RoomService _service;
/// 全量数据缓存,切换楼层时直接从内存过滤,不重新请求接口
List<RoomGridBO> _allRoomGrid = [];
List<RoomDetailListBO> _allDetailRooms = [];
RoomReportCubit({RoomService? service})
: _service = service ?? RoomService(repository: RoomRepository()),
super(const RoomReportState()) {
loadData();
}
Future<void> loadData({int floorId = 1}) async {
emit(state.copyWith(isLoading: true, error: null));
Future<void> loadData() async {
emit(state.copyWith(isLoading: true, error: null, clearError: true));
try {
final overview = await _service.getOverview(floorId);
// 不传 floorId,获取全量数据
final overview = await _service.getOverview(null);
final activeIndex = overview.floorList.indexWhere((f) => f.isActive);
final selectedFloorIndex = activeIndex >= 0 ? activeIndex : 0;
// 过滤掉每个房间 deviceList 中名称含"分控"的设备
final filteredDetailRooms = overview.roomDetailList.map((room) {
// 过滤掉每个房间 deviceList 中名称含"分控"的设备,并缓存全量
_allRoomGrid = overview.roomGrid;
_allDetailRooms = overview.roomDetailList.map((room) {
return RoomDetailListBO(
roomBasicInfo: room.roomBasicInfo,
statusTags: room.statusTags,
......@@ -30,13 +37,16 @@ class RoomReportCubit extends Cubit<RoomReportState> {
);
}).toList();
// 按选中楼层的 floorNumber 过滤
final floorNumber = overview.floorList[selectedFloorIndex].floorNumber;
emit(state.copyWith(
isLoading: false,
summary: overview.summary,
floors: overview.floorList,
selectedFloorIndex: activeIndex >= 0 ? activeIndex : 0,
roomGrid: overview.roomGrid,
detailRooms: filteredDetailRooms,
selectedFloorIndex: selectedFloorIndex,
roomGrid: _filterRoomGrid(floorNumber),
detailRooms: _filterDetailRooms(floorNumber),
selectedRoomId: null,
));
} catch (e) {
......@@ -44,16 +54,33 @@ class RoomReportCubit extends Cubit<RoomReportState> {
}
}
/// 切换楼层
void selectFloor(int index) {
if (state.selectedFloorIndex == index) return;
if (index < 0 || index >= state.floors.length) return;
final floorId = state.floors[index].floorId;
emit(state.copyWith(selectedFloorIndex: index, selectedRoomId: null));
loadData(floorId: floorId);
final floorNumber = state.floors[index].floorNumber;
emit(state.copyWith(
selectedFloorIndex: index,
roomGrid: _filterRoomGrid(floorNumber),
detailRooms: _filterDetailRooms(floorNumber),
selectedRoomId: null,
));
}
void selectRoom(int roomId) {
final newId = state.selectedRoomId == roomId ? null : roomId;
emit(state.copyWith(selectedRoomId: newId));
}
List<RoomGridBO> _filterRoomGrid(int floorNumber) {
return _allRoomGrid.where((r) => r.floorNumber == floorNumber).toList();
}
List<RoomDetailListBO> _filterDetailRooms(int floorNumber) {
return _allDetailRooms
.where((r) => r.roomBasicInfo.floorNumber == floorNumber)
.toList();
}
}
......@@ -15,44 +15,44 @@ class FloorSelector extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: EdgeInsets.symmetric(vertical: 20.h),
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.symmetric(horizontal: 28.w, vertical: 20.h),
child: Row(
spacing: 30.w,
children: floorNames.asMap().entries.map((entry) {
final index = entry.key;
final name = entry.value;
final isSelected = index == selectedIndex;
return Expanded(
child: GestureDetector(
onTap: () {
context.read<RoomReportCubit>().selectFloor(index);
},
child: Column(
children: [
Text(
name,
style: TextStyle(
fontSize: 28.sp,
color: isSelected
? const Color.fromRGBO(80, 162, 255, 1)
: const Color.fromRGBO(10, 13, 20, 1),
fontWeight: isSelected ? FontWeight.w500 : FontWeight.normal,
),
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
context.read<RoomReportCubit>().selectFloor(index);
},
child: Column(
spacing: 10.h,
children: [
Text(
name,
style: TextStyle(
fontSize: 28.sp,
color: isSelected
? const Color.fromRGBO(80, 162, 255, 1)
: const Color.fromRGBO(10, 13, 20, 1),
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
),
SizedBox(height: 4.h),
Container(
width: 50.w,
height: 3.h,
decoration: BoxDecoration(
color: isSelected
? const Color.fromRGBO(80, 162, 255, 1)
: Colors.transparent,
borderRadius: BorderRadius.circular(2.r),
),
),
Container(
width: 60.w,
height: 3.h,
decoration: BoxDecoration(
color: isSelected
? const Color.fromRGBO(80, 162, 255, 1)
: Colors.transparent,
borderRadius: BorderRadius.circular(2.r),
),
],
),
),
],
),
);
}).toList(),
......
......@@ -73,10 +73,10 @@ class RoomDetailCard extends StatelessWidget {
label: '通电中',
isActive: room.statusTags.powerSupplyStatus,
),
_StatusTag(
label: 'wifi正常',
isActive: room.statusTags.wifiNormalStatus,
),
// _StatusTag(
// label: 'wifi正常',
// isActive: room.statusTags.wifiNormalStatus,
// ),
],
),
],
......@@ -176,10 +176,7 @@ class _DeviceControl extends StatelessWidget {
),
SizedBox(width: 16.w),
Expanded(
child: Text(
device.deviceName.contains('-')
? device.deviceName.split('-').last
: device.deviceName,
child: Text(device.deviceName,
style: TextStyle(
fontSize: 28.sp,
color: const Color.fromRGBO(59, 130, 246, 1),
......
......@@ -17,72 +17,65 @@ class ServiceIndexCubit extends Cubit<ServiceIndexState> {
repository: RoomStatisticsRepository(),
),
super(const ServiceIndexState()) {
_init();
// _init();
}
Future<void> _init() async {
await Future.wait([
loadStatistics(),
loadFloorAndAreas(),
]);
}
// Future<void> _init() async {
// await Future.wait([
// loadStatistics(),
// loadFloorAndAreas(),
// ]);
// }
Future<void> reloadAll() async {
await Future.wait([
loadStatistics(),
loadFloorAndAreas(),
]);
}
/// 加载客房服务看板-统计数据(待响应、入住中、空闲可清洁)
Future<void> loadStatistics() async {
emit(state.copyWith(isLoading: true, error: null));
try {
final stats = await _statisticsService.getStatistics();
emit(state.copyWith(
isLoading: false,
pendingCount: stats.pendingResponseCount,
occupiedRooms: stats.occupiedCount,
vacantRooms: stats.vacantCleanableCount,
));
await Future.wait([
loadStatistics(),
loadFloorAndAreas(),
]);
emit(state.copyWith(isLoading: false));
} catch (e) {
emit(state.copyWith(isLoading: false, error: e.toString()));
}
}
/// 加载客房服务看板-统计数据(待响应、入住中、空闲可清洁)
Future<void> loadStatistics() async {
final stats = await _statisticsService.getStatistics();
emit(state.copyWith(
pendingCount: stats.pendingResponseCount,
occupiedRooms: stats.occupiedCount,
vacantRooms: stats.vacantCleanableCount,
));
}
/// 加载楼层和区域信息 + 合并房间数据
Future<void> loadFloorAndAreas() async {
emit(state.copyWith(isLoading: true, error: null));
try {
final floorAreas = await _roomService.getFloorAreas();
_floorAreasCache = floorAreas;
final floorAreas = await _roomService.getFloorAreas();
_floorAreasCache = floorAreas;
final floors = floorAreas.map((f) => {f.floorId: f.floorId.toString()}).toList();
final floors = floorAreas.map((f) => {f.floorId: f.floorName}).toList();
emit(state.copyWith(
isLoading: false,
floors: floors,
));
emit(state.copyWith(floors: floors));
// 默认选中第一个楼层,加载其全部房间数据,再默认选中第一个区域和房间
if (floors.isNotEmpty) {
final firstFloor = floors.first;
final firstFloorId = firstFloor.keys.first;
final firstFloorLabel = firstFloor.values.first;
_updateAreasForFloor(firstFloorLabel);
selectFloor(firstFloorLabel);
await _loadAllRooms(floorId: firstFloorId);
_selectFirstTypeAndRoom();
}
} catch (e) {
emit(state.copyWith(isLoading: false, error: e.toString()));
// 默认选中第一个楼层,加载其全部房间数据,再默认选中第一个区域和房间
if (floors.isNotEmpty) {
final firstFloor = floors.first;
final firstFloorId = firstFloor.keys.first;
final firstFloorLabel = firstFloor.values.first;
_updateAreasForFloor(firstFloorLabel);
selectFloor(firstFloorLabel);
await _loadAllRooms(floorId: firstFloorId);
_selectFirstTypeAndRoom();
}
}
/// 根据选中的楼层更新 areas
void _updateAreasForFloor(String floorLabel) {
final matched = _floorAreasCache.where(
(f) => f.floorId.toString() == floorLabel,
(f) => f.floorName == floorLabel,
);
final areas = matched.isNotEmpty ? matched.first.areas : <String>[];
emit(state.copyWith(areas: areas));
......@@ -90,45 +83,40 @@ class ServiceIndexCubit extends Cubit<ServiceIndexState> {
/// 加载某楼层的全部房间状态,与 FloorAreaBO.rooms 合并
Future<void> _loadAllRooms({required int floorId}) async {
emit(state.copyWith(isLoading: true, error: null));
try {
// 获取当前楼层的 FloorAreaBO
final floorArea = _floorAreasCache.cast<FloorAreaBO?>().firstWhere(
(f) => f?.floorId == floorId,
orElse: () => null,
);
if (floorArea == null) {
emit(state.copyWith(isLoading: false, rooms: []));
return;
}
// 拉取全楼层房间状态(不传 areaType)
final roomStatuses = await _roomService.getRoomStatus(floorId: floorId);
// 获取当前楼层的 FloorAreaBO
final floorArea = _floorAreasCache.cast<FloorAreaBO?>().firstWhere(
(f) => f?.floorId == floorId,
orElse: () => null,
);
if (floorArea == null) {
emit(state.copyWith(rooms: []));
return;
}
// 以 roomId 为 key 建立状态映射
final statusMap = <int, RoomStatusBO>{};
for (final rs in roomStatuses) {
statusMap[rs.roomId] = rs;
}
// 拉取全楼层房间状态(不传 areaType)
final roomStatuses = await _roomService.getRoomStatus(floorId: floorId);
// 合并:遍历 floorArea.rooms,匹配状态数据,推断 area
final mergedRooms = <Map<String, dynamic>>[];
for (final floorRoom in floorArea.rooms) {
final status = statusMap[floorRoom.roomId];
final area = _getAreaForRoom(floorRoom.roomName, floorArea.areas);
mergedRooms.add(_buildRoomMap(
roomId: floorRoom.roomId,
roomName: floorRoom.roomName,
areaType: area,
status: status,
));
}
// 以 roomId 为 key 建立状态映射
final roomIdMap = <int, RoomStatusBO>{};
for (final rs in roomStatuses) {
roomIdMap[rs.roomId] = rs;
}
emit(state.copyWith(isLoading: false, rooms: mergedRooms));
} catch (e) {
emit(state.copyWith(isLoading: false, error: e.toString()));
// 合并:遍历 floorArea.rooms,匹配状态数据,推断 area
final mergedRooms = <Map<String, dynamic>>[];
for (final floorRoom in floorArea.rooms) {
final status = roomIdMap[floorRoom.roomId];
final area = _getAreaForRoom(floorRoom.roomName, floorArea.areas);
mergedRooms.add(_buildRoomMap(
roomId: floorRoom.roomId,
roomName: floorRoom.roomName,
areaType: area,
status: status,
));
}
emit(state.copyWith(rooms: mergedRooms));
}
/// 根据 roomName 匹配所属区域类型(最长匹配优先)
......@@ -204,22 +192,18 @@ class ServiceIndexCubit extends Cubit<ServiceIndexState> {
}
}
/// UI 层切换楼层(切换时重置类型/房间/搜索,重新加载数据)
/// UI 层切换楼层(选中后不能取消,切换时重置类型/房间/搜索,重新加载数据)
Future<void> toggleFloor(String floorName, int floorId) async {
final isSameFloor = state.selectedFloor == floorName;
final newFloor = isSameFloor ? null : floorName;
if (state.selectedFloor == floorName) return;
// 计算新楼层的 areas
List<String> areas = state.areas;
if (newFloor != null) {
final matched = _floorAreasCache.where(
(f) => f.floorId.toString() == newFloor,
);
areas = matched.isNotEmpty ? matched.first.areas : <String>[];
}
final matched = _floorAreasCache.where(
(f) => f.floorName == floorName,
);
final areas = matched.isNotEmpty ? matched.first.areas : <String>[];
emit(state.copyWith(
selectedFloor: newFloor,
selectedFloor: floorName,
selectedType: null,
selectedRoom: null,
selectedRoomId: null,
......@@ -227,9 +211,11 @@ class ServiceIndexCubit extends Cubit<ServiceIndexState> {
areas: areas,
));
if (newFloor != null) {
try {
await _loadAllRooms(floorId: floorId);
_selectFirstTypeAndRoom();
} catch (e) {
emit(state.copyWith(error: e.toString()));
}
}
......
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:smart_hotel_app/models/bo/room_bo.dart';
import 'package:smart_hotel_app/repositories/room_repository.dart';
......@@ -21,8 +20,9 @@ class ServiceRoomCubit extends Cubit<ServiceRoomState> {
loadDetail();
}
Future<void> loadDetail() async {
emit(state.copyWith(isLoading: true, error: null));
/// 客房详情接口
Future<void> loadDetail({bool isLoading = true}) async {
emit(state.copyWith(isLoading: isLoading, error: null));
try {
final detail = await _service.getRoomDetail(roomId: _roomId);
emit(_mapDetailToState(detail));
......@@ -31,6 +31,7 @@ class ServiceRoomCubit extends Cubit<ServiceRoomState> {
}
}
/// 数据处理
ServiceRoomState _mapDetailToState(RoomDetailBO detail) {
final basic = detail.roomBasicInfo;
final statusTags = _buildStatusTags(
......@@ -39,13 +40,12 @@ class ServiceRoomCubit extends Cubit<ServiceRoomState> {
wifiOnlineStatus: basic.wifiOnlineStatus,
);
// 房间设备状态
final roomDevices = detail.deviceStatusList
.where((d) => !d.deviceName.contains('分控'))
.map((d) {
return RoomDevice(
name: d.deviceName.contains('-')
? d.deviceName.split('-').last
: d.deviceName,
name: d.deviceName,
icon: DealUtils.deviceIcon(d.deviceTypeIcon),
isOn: d.powerStatus == '1' || d.powerStatus == 'on',
statusText: _buildDeviceStatusText(d),
......@@ -58,14 +58,13 @@ class ServiceRoomCubit extends Cubit<ServiceRoomState> {
iconMap[d.deviceId] = d.deviceTypeIcon;
}
final controlDevices = detail.deviceControl.subDeviceControls
// 分设备控制
final subDeviceControls = detail.deviceControl.subDeviceControls
.where((c) => !c.deviceName.contains('分控'))
.map((c) {
return ControlDevice(
deviceId: c.deviceId,
name: c.deviceName.contains('-')
? c.deviceName.split('-').last
: c.deviceName,
name: c.deviceName,
icon: DealUtils.deviceIcon(iconMap[c.deviceId] ?? ''),
isOn: c.powerOn,
detailText: c.currentParamDisplay.isNotEmpty ? c.currentParamDisplay : null,
......@@ -80,7 +79,7 @@ class ServiceRoomCubit extends Cubit<ServiceRoomState> {
statusTags: statusTags,
roomDevices: roomDevices,
mainPowerOn: detail.deviceControl.roomPowerControl.powerOn,
controlDevices: controlDevices,
controlDevices: subDeviceControls,
);
}
......@@ -129,34 +128,6 @@ class ServiceRoomCubit extends Cubit<ServiceRoomState> {
}
}
/// 房间总电源开关
Future<void> toggleMainPowerForSwitch(bool value) async {
if (state.isPoweringOn || state.isPoweringOff) return;
// final loadingKey = value ? 'isPoweringOn' : 'isPoweringOff';
emit(state.copyWith(
isPoweringOn: value,
isPoweringOff: !value,
));
try {
if (value) {
await _service.roomPowerOn(roomId: _roomId);
} else {
await _service.roomPowerOff(roomId: _roomId);
}
emit(state.copyWith(
isPoweringOn: false,
isPoweringOff: false,
mainPowerOn: value,
));
} catch (e) {
emit(state.copyWith(
isPoweringOn: false,
isPoweringOff: false,
));
rethrow;
}
}
/// 房间总电源开关
Future<void> toggleMainPower(bool value) async {
if (state.isPoweringOn || state.isPoweringOff) return;
......@@ -175,6 +146,7 @@ class ServiceRoomCubit extends Cubit<ServiceRoomState> {
isPoweringOff: false,
mainPowerOn: value,
));
await loadDetail(isLoading: false);
} catch (e) {
emit(state.copyWith(
isPoweringOn: false,
......@@ -188,19 +160,33 @@ class ServiceRoomCubit extends Cubit<ServiceRoomState> {
Future<void> toggleControlDevice(int index, bool value) async {
final devices = state.controlDevices;
if (index < 0 || index >= devices.length) return;
// Note: The API for individual device power control uses deviceId.
// However, we don't have deviceId in ControlDevice model yet.
// For now, just update UI state. The device control card in the view
// navigates to ServiceDeviceDetailRoute for AC control.
final device = devices[index];
// 乐观更新 UI
final oldDevices = devices;
final updatedDevices = List<ControlDevice>.from(devices);
updatedDevices[index] = ControlDevice(
deviceId: updatedDevices[index].deviceId,
name: updatedDevices[index].name,
icon: updatedDevices[index].icon,
deviceId: device.deviceId,
name: device.name,
icon: device.icon,
isOn: value,
detailText: updatedDevices[index].detailText,
deviceTypeName: updatedDevices[index].deviceTypeName,
detailText: device.detailText,
deviceTypeName: device.deviceTypeName,
);
emit(state.copyWith(controlDevices: updatedDevices));
try {
if (value) {
await _service.powerSingleOn(deviceId: device.deviceId);
} else {
await _service.powerSingleOff(deviceId: device.deviceId);
}
await loadDetail(isLoading: false);
} catch (e) {
// 失败回滚
emit(state.copyWith(controlDevices: oldDevices));
rethrow;
}
}
}
......@@ -130,7 +130,7 @@ class ServiceRoomDetailView extends StatelessWidget {
controlDevices: state.controlDevices,
onMainPowerToggle: (value) async {
try {
await cubit.toggleMainPowerForSwitch(value);
await cubit.toggleMainPower(value);
if (context.mounted) {
showToast(value ? '送电成功' : '断电成功');
}
......@@ -143,6 +143,7 @@ class ServiceRoomDetailView extends StatelessWidget {
onDeviceToggle: (index, value) =>
cubit.toggleControlDevice(index, value),
onDeviceTap: (index, device) {
/// item click
if (device.name.contains('空调')) {
context.pushRoute(ServiceDeviceDetailRoute(deviceId: device.deviceId));
}
......
......@@ -32,14 +32,23 @@ class RoomBottomButtons extends StatelessWidget {
borderRadius: BorderRadius.circular(16.r),
),
child: Center(
child: Text(
'送电',
style: TextStyle(
color: Colors.white,
fontSize: 28.sp,
fontWeight: FontWeight.w500,
),
),
child: isPoweringOn
? SizedBox(
width: 32.w,
height: 32.w,
child: CircularProgressIndicator(
strokeWidth: 3.w,
color: const Color.fromRGBO(66, 165, 245, 1.0),
),
)
: Text(
'送电',
style: TextStyle(
color: Colors.white,
fontSize: 28.sp,
fontWeight: FontWeight.w500,
),
),
),
),
),
......@@ -56,14 +65,23 @@ class RoomBottomButtons extends StatelessWidget {
borderRadius: BorderRadius.circular(16.r),
),
child: Center(
child: Text(
'断电',
style: TextStyle(
color: const Color.fromRGBO(100, 116, 139, 1),
fontSize: 28.sp,
fontWeight: FontWeight.w500,
),
),
child: isPoweringOff
? SizedBox(
width: 32.w,
height: 32.w,
child: CircularProgressIndicator(
strokeWidth: 3.w,
color: const Color.fromRGBO(229, 241, 255, 1),
),
)
: Text(
'断电',
style: TextStyle(
color: const Color.fromRGBO(100, 116, 139, 1),
fontSize: 28.sp,
fontWeight: FontWeight.w500,
),
),
),
),
),
......
......@@ -66,8 +66,8 @@ class RoomOverviewCard extends StatelessWidget {
return Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
decoration: BoxDecoration(
color: const Color.fromRGBO(225, 245, 238, 1.0),
borderRadius: BorderRadius.circular(12.r),
color: const Color.fromRGBO(229, 241, 255, 1.0),
borderRadius: BorderRadius.circular(24.r),
),
child: Row(
mainAxisSize: MainAxisSize.min,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment