Commit 7566c742 authored by huqu's avatar huqu

fix bugs

parent 59e6cf59
...@@ -56,5 +56,7 @@ class AppRouter extends $AppRouter { ...@@ -56,5 +56,7 @@ class AppRouter extends $AppRouter {
AutoRoute(page: ReportDeviceListRoute.page), AutoRoute(page: ReportDeviceListRoute.page),
// InspectionTopologyRoute / InspectionTopologyView(巡检拓扑页) // InspectionTopologyRoute / InspectionTopologyView(巡检拓扑页)
AutoRoute(page: InspectionTopologyRoute.page), AutoRoute(page: InspectionTopologyRoute.page),
// AboutRoute / AboutView(关于页)
AutoRoute(page: AboutRoute.page),
]; ];
} }
This diff is collapsed.
class DealUtils {
/// 去除多余0
static String cleanNumber(String value) {
return value.replaceAll(RegExp(r'\.?0+$'), '');
}
}
\ No newline at end of file
...@@ -99,4 +99,8 @@ class DeviceCubit extends Cubit<DeviceState> { ...@@ -99,4 +99,8 @@ class DeviceCubit extends Cubit<DeviceState> {
void clearOperationMessage() { void clearOperationMessage() {
emit(state.copyWith(operationMessage: null)); emit(state.copyWith(operationMessage: null));
} }
void selectChartTab(int index) {
emit(state.copyWith(selectedChartTabIndex: index));
}
} }
\ No newline at end of file
...@@ -53,6 +53,7 @@ class DeviceState extends Equatable { ...@@ -53,6 +53,7 @@ class DeviceState extends Equatable {
final bool isPoweringOn; final bool isPoweringOn;
final bool isPoweringOff; final bool isPoweringOff;
final String? operationMessage; final String? operationMessage;
final int selectedChartTabIndex;
const DeviceState({ const DeviceState({
this.isLoading = false, this.isLoading = false,
...@@ -61,6 +62,7 @@ class DeviceState extends Equatable { ...@@ -61,6 +62,7 @@ class DeviceState extends Equatable {
this.isPoweringOn = false, this.isPoweringOn = false,
this.isPoweringOff = false, this.isPoweringOff = false,
this.operationMessage, this.operationMessage,
this.selectedChartTabIndex = 0,
}); });
DeviceState copyWith({ DeviceState copyWith({
...@@ -70,6 +72,7 @@ class DeviceState extends Equatable { ...@@ -70,6 +72,7 @@ class DeviceState extends Equatable {
bool? isPoweringOn, bool? isPoweringOn,
bool? isPoweringOff, bool? isPoweringOff,
Object? operationMessage = _unset, Object? operationMessage = _unset,
int? selectedChartTabIndex,
}) { }) {
return DeviceState( return DeviceState(
isLoading: isLoading ?? this.isLoading, isLoading: isLoading ?? this.isLoading,
...@@ -82,6 +85,8 @@ class DeviceState extends Equatable { ...@@ -82,6 +85,8 @@ class DeviceState extends Equatable {
operationMessage: identical(operationMessage, _unset) operationMessage: identical(operationMessage, _unset)
? this.operationMessage ? this.operationMessage
: operationMessage as String?, : operationMessage as String?,
selectedChartTabIndex:
selectedChartTabIndex ?? this.selectedChartTabIndex,
); );
} }
...@@ -93,5 +98,6 @@ class DeviceState extends Equatable { ...@@ -93,5 +98,6 @@ class DeviceState extends Equatable {
isPoweringOn, isPoweringOn,
isPoweringOff, isPoweringOff,
operationMessage, operationMessage,
selectedChartTabIndex,
]; ];
} }
\ No newline at end of file
...@@ -78,7 +78,11 @@ class DeviceDetailView extends StatelessWidget { ...@@ -78,7 +78,11 @@ class DeviceDetailView extends StatelessWidget {
SizedBox(height: 10.h), SizedBox(height: 10.h),
DeviceParmsBlock(deviceInfo: deviceInfo), DeviceParmsBlock(deviceInfo: deviceInfo),
SizedBox(height: 10.h), SizedBox(height: 10.h),
PowerCharBlock(deviceInfo: deviceInfo), PowerCharBlock(
deviceInfo: deviceInfo,
selectedTabIndex: state.selectedChartTabIndex,
onTabChanged: (index) => cubit.selectChartTab(index),
),
SizedBox(height: 10.h), SizedBox(height: 10.h),
_buildBottomButtons(context, cubit, state), _buildBottomButtons(context, cubit, state),
SizedBox(height: 30.h), SizedBox(height: 30.h),
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/utils/deal_utils.dart';
import 'package:smart_hotel_app/views/home/device/cubit/device_state.dart'; import 'package:smart_hotel_app/views/home/device/cubit/device_state.dart';
class DeviceParmsBlock extends StatelessWidget { class DeviceParmsBlock extends StatelessWidget {
...@@ -57,15 +58,15 @@ class DeviceParmsBlock extends StatelessWidget { ...@@ -57,15 +58,15 @@ class DeviceParmsBlock extends StatelessWidget {
); );
} }
String _formatValue(String raw) { // String _formatValue(String raw) {
final d = double.tryParse(raw); // final d = double.tryParse(raw);
if (d == null) return raw; // if (d == null) return raw;
// 去掉末尾多余的 0,整数不显示小数点 // // 去掉末尾多余的 0,整数不显示小数点
final formatted = d.toStringAsFixed(2); // final formatted = d.toStringAsFixed(2);
final parts = formatted.split('.'); // final parts = formatted.split('.');
if (parts.length == 2 && parts[1] == '00') return parts[0]; // if (parts.length == 2 && parts[1] == '00') return parts[0];
return formatted.replaceAll(RegExp(r'0+$'), '').replaceAll(RegExp(r'\.$'), ''); // return formatted.replaceAll(RegExp(r'0+$'), '').replaceAll(RegExp(r'\.$'), '');
} // }
Widget _buildParamCard(String value, String label, String unit, {bool isGreen = false}) { Widget _buildParamCard(String value, String label, String unit, {bool isGreen = false}) {
return Container( return Container(
...@@ -81,7 +82,7 @@ class DeviceParmsBlock extends StatelessWidget { ...@@ -81,7 +82,7 @@ class DeviceParmsBlock extends StatelessWidget {
TextSpan( TextSpan(
children: [ children: [
TextSpan( TextSpan(
text: _formatValue(value), text: DealUtils.cleanNumber(value),
style: TextStyle( style: TextStyle(
fontSize: 40.sp, fontSize: 40.sp,
color: isGreen ? const Color.fromRGBO(20, 184, 166, 1) color: isGreen ? const Color.fromRGBO(20, 184, 166, 1)
......
...@@ -4,17 +4,17 @@ import 'package:flutter_screenutil/flutter_screenutil.dart'; ...@@ -4,17 +4,17 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/fl_chart.dart';
import 'package:smart_hotel_app/views/home/device/cubit/device_state.dart'; import 'package:smart_hotel_app/views/home/device/cubit/device_state.dart';
class PowerCharBlock extends StatefulWidget { class PowerCharBlock extends StatelessWidget {
final DeviceInfo deviceInfo; final DeviceInfo deviceInfo;
final int selectedTabIndex;
final ValueChanged<int> onTabChanged;
const PowerCharBlock({super.key, required this.deviceInfo}); const PowerCharBlock({
super.key,
@override required this.deviceInfo,
State<PowerCharBlock> createState() => _PowerCharBlockState(); required this.selectedTabIndex,
} required this.onTabChanged,
});
class _PowerCharBlockState extends State<PowerCharBlock> {
int _selectedIndex = 0;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
...@@ -61,17 +61,17 @@ class _PowerCharBlockState extends State<PowerCharBlock> { ...@@ -61,17 +61,17 @@ class _PowerCharBlockState extends State<PowerCharBlock> {
SizedBox( SizedBox(
height: 250.h, height: 250.h,
child: BarChart( child: BarChart(
BarChartData( BarChartData(
alignment: BarChartAlignment.spaceBetween, alignment: BarChartAlignment.spaceBetween,
maxY: _calcMaxY(_selectedIndex), maxY: _calcMaxY(selectedTabIndex),
minY: 0, minY: 0,
groupsSpace: 10, groupsSpace: 10,
barTouchData: BarTouchData(enabled: false), barTouchData: BarTouchData(enabled: false),
gridData: FlGridData( gridData: FlGridData(
show: true, show: true,
drawHorizontalLine: true, drawHorizontalLine: true,
drawVerticalLine: false, drawVerticalLine: false,
horizontalInterval: _calcInterval(_selectedIndex), horizontalInterval: _calcInterval(selectedTabIndex),
getDrawingHorizontalLine: (value) { getDrawingHorizontalLine: (value) {
return const FlLine( return const FlLine(
color: Color.fromRGBO(209, 213, 219, 1), color: Color.fromRGBO(209, 213, 219, 1),
...@@ -82,12 +82,14 @@ class _PowerCharBlockState extends State<PowerCharBlock> { ...@@ -82,12 +82,14 @@ class _PowerCharBlockState extends State<PowerCharBlock> {
), ),
titlesData: FlTitlesData( titlesData: FlTitlesData(
show: true, show: true,
rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), rightTitles:
topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), const AxisTitles(sideTitles: SideTitles(showTitles: false)),
topTitles:
const AxisTitles(sideTitles: SideTitles(showTitles: false)),
leftTitles: AxisTitles( leftTitles: AxisTitles(
sideTitles: SideTitles( sideTitles: SideTitles(
showTitles: true, showTitles: true,
interval: _calcInterval(_selectedIndex), interval: _calcInterval(selectedTabIndex),
getTitlesWidget: (value, meta) { getTitlesWidget: (value, meta) {
return Text( return Text(
'${value.toInt()}', '${value.toInt()}',
...@@ -103,15 +105,20 @@ class _PowerCharBlockState extends State<PowerCharBlock> { ...@@ -103,15 +105,20 @@ class _PowerCharBlockState extends State<PowerCharBlock> {
bottomTitles: AxisTitles( bottomTitles: AxisTitles(
sideTitles: SideTitles( sideTitles: SideTitles(
showTitles: true, showTitles: true,
interval: _calcBottomInterval(), interval: 1,
getTitlesWidget: (value, meta) { getTitlesWidget: (value, meta) {
final i = value.toInt(); final i = value.toInt();
final labels = widget.deviceInfo.timeLabels; final labels = deviceInfo.timeLabels;
final label = i >= 0 && i < labels.length ? labels[i] : ''; if (i < 0 || i >= labels.length) {
return const SizedBox.shrink();
}
if (!_visibleLabelIndices.contains(i)) {
return const SizedBox.shrink();
}
return Padding( return Padding(
padding: EdgeInsets.only(top: 8.h), padding: EdgeInsets.only(top: 8.h),
child: Text( child: Text(
label, labels[i],
style: TextStyle( style: TextStyle(
color: const Color.fromRGBO(156, 163, 175, 1), color: const Color.fromRGBO(156, 163, 175, 1),
fontSize: 20.sp, fontSize: 20.sp,
...@@ -124,7 +131,7 @@ class _PowerCharBlockState extends State<PowerCharBlock> { ...@@ -124,7 +131,7 @@ class _PowerCharBlockState extends State<PowerCharBlock> {
), ),
), ),
borderData: FlBorderData(show: false), borderData: FlBorderData(show: false),
barGroups: _buildBarGroups(_selectedIndex), barGroups: _buildBarGroups(),
), ),
), ),
), ),
...@@ -134,81 +141,99 @@ class _PowerCharBlockState extends State<PowerCharBlock> { ...@@ -134,81 +141,99 @@ class _PowerCharBlockState extends State<PowerCharBlock> {
} }
Widget _buildTab(String title, int index) { Widget _buildTab(String title, int index) {
final isSelected = _selectedIndex == index; final isSelected = selectedTabIndex == index;
return GestureDetector( return GestureDetector(
onTap: () { onTap: () => onTabChanged(index),
setState(() {
_selectedIndex = index;
});
},
child: Container( child: Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h), padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.r), borderRadius: BorderRadius.circular(8.r),
color: isSelected ? const Color.fromRGBO(219, 234, 254, 1) : Colors.transparent, color: isSelected
? const Color.fromRGBO(219, 234, 254, 1)
: Colors.transparent,
), ),
child: Text( child: Text(
title, title,
style: TextStyle( style: TextStyle(
fontSize: 24.sp, fontSize: 24.sp,
color: isSelected ? const Color.fromRGBO(73, 149, 234, 1) : color: isSelected
const Color.fromRGBO(100, 116, 139, 1), ? const Color.fromRGBO(73, 149, 234, 1)
: const Color.fromRGBO(100, 116, 139, 1),
), ),
), ),
), ),
); );
} }
List<BarChartGroupData> _buildBarGroups(int index) { List<BarChartGroupData> _buildBarGroups() {
final data = index == 0 ? widget.deviceInfo.powerData : widget.deviceInfo.tempData; final data = selectedTabIndex == 0
final Color mainColor = index == 0 ? deviceInfo.powerData
? const Color.fromRGBO(59, 130, 246, 1) : deviceInfo.tempData;
final Color mainColor = selectedTabIndex == 0
? const Color.fromRGBO(80, 162, 255, 1)
: const Color.fromRGBO(251, 191, 36, 1); : const Color.fromRGBO(251, 191, 36, 1);
return List.generate( return List.generate(data.length, (i) {
data.length, if (data[i] == 0) return BarChartGroupData(x: i);
(i) {
if (data[i] == 0) return BarChartGroupData(x: i); return BarChartGroupData(
return BarChartGroupData( x: i,
x: i, barRods: [
barRods: [ BarChartRodData(
BarChartRodData( toY: data[i].toDouble(),
toY: data[i].toDouble(), color: mainColor,
color: mainColor, width: 16.w,
width: 22.w, borderRadius: BorderRadius.vertical(top: Radius.circular(8.r)),
borderRadius: BorderRadius.vertical(top: Radius.circular(8.r)), ),
), ],
], );
); }).where((group) => group.barRods.isNotEmpty).toList();
},
).where((group) => group.barRods.isNotEmpty).toList();
} }
double _calcMaxY(int index) { double _calcMaxY(int index) {
final data = index == 0 ? widget.deviceInfo.powerData : widget.deviceInfo.tempData; final data = index == 0 ? deviceInfo.powerData : deviceInfo.tempData;
if (data.isEmpty) return 100; if (data.isEmpty) return 100;
final max = data.reduce((a, b) => a > b ? a : b).toDouble(); final max = data.reduce((a, b) => a > b ? a : b).toDouble();
if (max == 0) return 100; if (max == 0) return 100;
// 向上取整到最近的 nice number,并留 20% 余量
final raw = max * 1.2; final raw = max * 1.2;
final magnitude = pow(10, (log(raw) / ln10).floor()).toDouble(); final magnitude = pow(10, (log(raw) / ln10).floor()).toDouble();
final normalized = raw / magnitude; final normalized = raw / magnitude;
final nice = normalized <= 1 ? 1 final nice =
: normalized <= 2 ? 2 normalized <= 1 ? 1 :
: normalized <= 5 ? 5 normalized <= 2 ? 2 :
: 10; normalized <= 5 ? 5 : 10;
return nice * magnitude; return nice * magnitude;
} }
Set<int> get _visibleLabelIndices {
final length = deviceInfo.timeLabels.length;
if (length <= 10) {
return Set.from(List.generate(length, (i) => i));
}
final isOdd = length % 2 == 1;
if (isOdd) {
// 基数:7 个标签,首尾都显示,中间 5 个均分 6 段
final step = (length - 1) / 6;
final indices = <int>{};
for (int i = 0; i <= 6; i++) {
indices.add((step * i).round());
}
return indices;
} else {
// 偶数:6 个标签,显示首和倒数第二个,中间 4 个均分 5 段
final step = (length - 2) / 5;
final indices = <int>{};
for (int i = 0; i <= 5; i++) {
indices.add((step * i).round());
}
return indices;
}
}
double _calcInterval(int index) { double _calcInterval(int index) {
return _calcMaxY(index) / 5; return _calcMaxY(index) / 5;
} }
double _calcBottomInterval() {
final length = widget.deviceInfo.timeLabels.length;
if (length <= 6) return 1;
if (length <= 12) return 2;
if (length <= 24) return 3;
return (length / 6).ceilToDouble();
}
} }
\ No newline at end of file
...@@ -5,6 +5,7 @@ import 'package:smart_hotel_app/repositories/alert_dashboard_repository.dart'; ...@@ -5,6 +5,7 @@ import 'package:smart_hotel_app/repositories/alert_dashboard_repository.dart';
import 'package:smart_hotel_app/repositories/alert_detail_repository.dart'; import 'package:smart_hotel_app/repositories/alert_detail_repository.dart';
import 'package:smart_hotel_app/services/alert_dashboard_service.dart'; import 'package:smart_hotel_app/services/alert_dashboard_service.dart';
import 'package:smart_hotel_app/services/alert_detail_service.dart'; import 'package:smart_hotel_app/services/alert_detail_service.dart';
import 'package:smart_hotel_app/utils/deal_utils.dart';
import 'package:smart_hotel_app/views/home/index/cubit/home_index_state.dart'; import 'package:smart_hotel_app/views/home/index/cubit/home_index_state.dart';
class HomeIndexCubit extends Cubit<HomeIndexState> { class HomeIndexCubit extends Cubit<HomeIndexState> {
...@@ -73,8 +74,9 @@ class HomeIndexCubit extends Cubit<HomeIndexState> { ...@@ -73,8 +74,9 @@ class HomeIndexCubit extends Cubit<HomeIndexState> {
icon: _mapDeviceIcon(d.deviceTypeIcon), icon: _mapDeviceIcon(d.deviceTypeIcon),
title: d.deviceName, title: d.deviceName,
subtitle: _buildDeviceSubtitle(d), subtitle: _buildDeviceSubtitle(d),
status: d.onlineStatus == '1' ? d.runStatusText : '离线', // status: d.onlineStatus == '1' ? d.runStatusText : '离线',
statusColor: _mapStatusColor(d.onlineStatus, d.runStatus), status: d.runStatusText,
statusColor: _mapStatusColor(d.runStatus),
); );
} }
...@@ -94,28 +96,26 @@ class HomeIndexCubit extends Cubit<HomeIndexState> { ...@@ -94,28 +96,26 @@ class HomeIndexCubit extends Cubit<HomeIndexState> {
} }
String _buildDeviceSubtitle(DeviceItemBO d) { String _buildDeviceSubtitle(DeviceItemBO d) {
final parts = <String>[]; return [
if (d.roomName.isNotEmpty) parts.add(d.roomName); if (d.roomName.isNotEmpty) d.roomName,
if (d.temperature.isNotEmpty) parts.add('${d.temperature}°C'); if (d.temperature.isNotEmpty) '${DealUtils.cleanNumber(d.temperature)}°C',
if (d.voltage.isNotEmpty) parts.add('${d.voltage}V'); if (d.voltage.isNotEmpty) '${DealUtils.cleanNumber(d.voltage)}V',
if (d.power.isNotEmpty) parts.add('${d.power}kW'); if (d.power.isNotEmpty) '${DealUtils.cleanNumber(d.power)}kW',
if (d.current.isNotEmpty) parts.add('${d.current}A'); if (d.current.isNotEmpty) '${DealUtils.cleanNumber(d.current)}A',
return parts.isNotEmpty ? parts.join('·') : ''; ].join('·');
} }
Color _mapStatusColor(String onlineStatus, String runStatus) { Color _mapStatusColor(String runStatus) {
if (onlineStatus == '0') {
return const Color.fromRGBO(102, 102, 102, 1.0);
}
switch (runStatus) { switch (runStatus) {
case 'alert': case 'normal':
case 'alarm': return const Color.fromRGBO(20, 184, 166, 1.0);
return const Color.fromRGBO(255, 77, 79, 1.0);
case 'warning': case 'warning':
case '注意': return const Color.fromRGBO(250, 204, 21, 1.0);
return const Color.fromRGBO(255, 193, 7, 1.0); case 'fault':
return const Color.fromRGBO(255, 77, 79, 1.0);
case 'offline':
default: default:
return const Color.fromRGBO(26, 188, 156, 1.0); return const Color.fromRGBO(102, 102, 102, 1.0);
} }
} }
} }
...@@ -20,26 +20,30 @@ class HomeView extends StatefulWidget { ...@@ -20,26 +20,30 @@ class HomeView extends StatefulWidget {
} }
class _HomeViewState extends State<HomeView> with AutoRouteAwareStateMixin<HomeView> { class _HomeViewState extends State<HomeView> with AutoRouteAwareStateMixin<HomeView> {
TabsRouter? _tabsRouter;
@override @override
void didChangeDependencies() { void didChangeDependencies() {
super.didChangeDependencies(); super.didChangeDependencies();
// Subscribe to tab router events // 缓存引用,避免在 dispose 中通过 context 查找(此时树已不稳定)
final tabsRouter = AutoTabsRouter.of(context); final newRouter = AutoTabsRouter.of(context);
tabsRouter.addListener(_onTabChange); if (newRouter != _tabsRouter) {
_tabsRouter?.removeListener(_onTabChange);
_tabsRouter = newRouter;
_tabsRouter?.addListener(_onTabChange);
}
} }
@override @override
void dispose() { void dispose() {
final tabsRouter = AutoTabsRouter.of(context); _tabsRouter?.removeListener(_onTabChange);
tabsRouter.removeListener(_onTabChange);
super.dispose(); super.dispose();
} }
void _onTabChange() { void _onTabChange() {
final tabsRouter = AutoTabsRouter.of(context); if (!mounted) return;
// Check if this tab is now active
if (tabsRouter.activeIndex == 0) { if (_tabsRouter?.activeIndex == 0) {
// Reload data when tab is activated
context.read<HomeIndexCubit>().reloadDashboard(); context.read<HomeIndexCubit>().reloadDashboard();
} }
} }
...@@ -88,7 +92,10 @@ class _HomeViewState extends State<HomeView> with AutoRouteAwareStateMixin<HomeV ...@@ -88,7 +92,10 @@ class _HomeViewState extends State<HomeView> with AutoRouteAwareStateMixin<HomeV
List<Widget> _buildAlertBlocks(List<AlertItemBO> alerts) { List<Widget> _buildAlertBlocks(List<AlertItemBO> alerts) {
final blocks = <Widget>[]; final blocks = <Widget>[];
for (final alert in alerts) { for (final alert in alerts) {
final location = '${alert.roomName}·${alert.deviceName}'; final location = [alert.roomName, alert.deviceName]
.where((s) => s.isNotEmpty)
.join('·');
switch (alert.alertCategory) { switch (alert.alertCategory) {
case 'temperature': case 'temperature':
blocks.addAll([ blocks.addAll([
......
...@@ -107,7 +107,7 @@ class TemperatureBlock extends StatelessWidget { ...@@ -107,7 +107,7 @@ class TemperatureBlock extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(
currentTemperature, currentTemperature.isNotEmpty ? currentTemperature : '--℃',
style: TextStyle( style: TextStyle(
fontSize: 40.sp, fontSize: 40.sp,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
...@@ -130,7 +130,7 @@ class TemperatureBlock extends StatelessWidget { ...@@ -130,7 +130,7 @@ class TemperatureBlock extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(
currentVoltage, currentVoltage.isNotEmpty ? currentVoltage : '--V',
style: TextStyle( style: TextStyle(
fontSize: 40.sp, fontSize: 40.sp,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
...@@ -153,7 +153,7 @@ class TemperatureBlock extends StatelessWidget { ...@@ -153,7 +153,7 @@ class TemperatureBlock extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(
currentCurrent, currentCurrent.isNotEmpty ? currentCurrent : '--A',
style: TextStyle( style: TextStyle(
fontSize: 40.sp, fontSize: 40.sp,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
......
...@@ -177,7 +177,6 @@ class VoltageOperateBlock extends StatelessWidget { ...@@ -177,7 +177,6 @@ class VoltageOperateBlock extends StatelessWidget {
borderRadius: BorderRadius.circular(16.r), borderRadius: BorderRadius.circular(16.r),
), ),
child: Container( child: Container(
padding: EdgeInsets.all(24.w),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
borderRadius: BorderRadius.circular(16.r), borderRadius: BorderRadius.circular(16.r),
...@@ -186,33 +185,42 @@ class VoltageOperateBlock extends StatelessWidget { ...@@ -186,33 +185,42 @@ class VoltageOperateBlock extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
Text(
voltageAlarmTitle,
style: TextStyle(
fontSize: 30.sp,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
GestureDetector( GestureDetector(
onTap: () => Navigator.of(dialogContext).pop(), onTap: () => Navigator.of(dialogContext).pop(),
child: Icon( child: Container(
Icons.close, margin: EdgeInsets.only(top: 20.h, right: 20.h),
color: const Color.fromRGBO(100, 116, 139, 1.0), padding: EdgeInsets.symmetric(horizontal: 2.w, vertical: 2.h),
size: 32.sp, decoration: BoxDecoration(
), shape: BoxShape.circle,
border: Border.all(
width: 2.w,
color: const Color.fromRGBO(100, 116, 139, 1.0),
),
),
child: Icon(
Icons.close,
color: const Color.fromRGBO(100, 116, 139, 1.0),
size: 32.sp,
),
)
), ),
], ],
), ),
SizedBox(height: 16.h), Text(
voltageAlarmTitle,
style: TextStyle(
fontSize: 30.sp,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
SizedBox(height: 20.h),
Container( Container(
width: double.infinity, width: double.infinity,
padding: EdgeInsets.symmetric( margin: EdgeInsets.symmetric(horizontal: 40.w),
horizontal: 20.w, padding: EdgeInsets.symmetric(horizontal: 30.w, vertical: 30.h),
vertical: 20.h,
),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color.fromRGBO(242, 243, 245, 1), color: const Color.fromRGBO(242, 243, 245, 1),
borderRadius: BorderRadius.circular(12.r), borderRadius: BorderRadius.circular(12.r),
...@@ -230,7 +238,7 @@ class VoltageOperateBlock extends StatelessWidget { ...@@ -230,7 +238,7 @@ class VoltageOperateBlock extends StatelessWidget {
), ),
SizedBox(height: 12.h), SizedBox(height: 12.h),
Text( Text(
'1.检查输入电压是否正常;\n2.确认设备是否正常运行;\n3.若异常,请联系电工。', '1.检查设备是否通风良好;\n2.确认设备负载是否正常;\n3.若异常,请联系电工。',
style: TextStyle( style: TextStyle(
fontSize: 26.sp, fontSize: 26.sp,
color: const Color.fromRGBO(100, 116, 139, 1.0), color: const Color.fromRGBO(100, 116, 139, 1.0),
...@@ -240,8 +248,8 @@ class VoltageOperateBlock extends StatelessWidget { ...@@ -240,8 +248,8 @@ class VoltageOperateBlock extends StatelessWidget {
], ],
), ),
), ),
SizedBox(height: 20.h),
GestureDetector( GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () async { onTap: () async {
Navigator.of(dialogContext).pop(); Navigator.of(dialogContext).pop();
try { try {
...@@ -256,19 +264,13 @@ class VoltageOperateBlock extends StatelessWidget { ...@@ -256,19 +264,13 @@ class VoltageOperateBlock extends StatelessWidget {
} }
}, },
child: Container( child: Container(
width: double.infinity, padding: EdgeInsets.symmetric(horizontal: 30.w, vertical: 30.h),
height: 72.h, child: Text(
decoration: BoxDecoration( '确认',
color: const Color.fromRGBO(66, 165, 245, 1.0), style: TextStyle(
borderRadius: BorderRadius.circular(12.r), fontSize: 28.sp,
), color: const Color.fromRGBO(73, 149, 234, 1.0),
child: Center( fontWeight: FontWeight.bold,
child: Text(
'确认',
style: TextStyle(
fontSize: 28.sp,
color: Colors.white,
),
), ),
), ),
), ),
......
...@@ -133,7 +133,7 @@ class DeviceOverviewCard extends StatelessWidget { ...@@ -133,7 +133,7 @@ class DeviceOverviewCard extends StatelessWidget {
), ),
), ),
Text( Text(
value, value.isNotEmpty ? value : '--',
style: TextStyle( style: TextStyle(
fontSize: 28.sp, fontSize: 28.sp,
color: const Color.fromRGBO(100, 116, 139, 1.0), color: const Color.fromRGBO(100, 116, 139, 1.0),
......
...@@ -37,33 +37,28 @@ class InspectionTopologyCubit extends Cubit<InspectionTopologyState> { ...@@ -37,33 +37,28 @@ class InspectionTopologyCubit extends Cubit<InspectionTopologyState> {
void _emitFromTopology(DeviceTopologyBO topology) { void _emitFromTopology(DeviceTopologyBO topology) {
final info = topology.deviceInfo; final info = topology.deviceInfo;
final nodes = topology.treeNodes; final treeNodes = topology.treeNodes;
final rootNode = _buildTopologyTree(nodes); if (treeNodes.isEmpty) {
emit(InspectionTopologyState(
deviceName: info.deviceName,
deviceModel: '${info.deviceCode}·${info.deviceLocation}',
));
return;
}
final rootNode = _buildTopologyTree(treeNodes);
emit(InspectionTopologyState( emit(InspectionTopologyState(
deviceName: info.deviceName, deviceName: info.deviceName,
deviceModel: '${info.deviceCode}·${info.deviceLocation}', deviceModel: '${info.deviceCode}·${info.deviceLocation}',
topologyNodes: [rootNode], topologyNodes: [rootNode],
deviceCount: _countAllNodes(rootNode), deviceCount: _countAllNodes(rootNode),
connectionCount: _countConnections(rootNode), connectionCount: _countConnections(rootNode),
// statusText:
)); ));
} }
/// 根据 gatewayId 将扁平节点列表构建为树结构,并自动计算布局位置 /// 根据 gatewayId 将扁平节点列表构建为树结构,并自动计算布局位置
TopologyNode _buildTopologyTree(List<TreeNodeBO> nodes) { TopologyNode _buildTopologyTree(List<TreeNodeBO> nodes) {
if (nodes.isEmpty) {
return const TopologyNode(
id: '0',
name: '未知设备',
status: 'offline',
children: [],
x: 0.5,
y: 0.08,
);
}
// 构建子节点映射表:parent deviceId -> [child TreeNodeBO] // 构建子节点映射表:parent deviceId -> [child TreeNodeBO]
final childrenMap = <int, List<TreeNodeBO>>{}; final childrenMap = <int, List<TreeNodeBO>>{};
final nodeMap = <int, TreeNodeBO>{}; final nodeMap = <int, TreeNodeBO>{};
......
...@@ -55,7 +55,7 @@ class InspectionTopologyPage extends StatelessWidget { ...@@ -55,7 +55,7 @@ class InspectionTopologyPage extends StatelessWidget {
builder: (context, state) { builder: (context, state) {
if (state.isLoading) { if (state.isLoading) {
return const Center( return const Center(
child: CircularProgressIndicator(color: const Color.fromRGBO(66, 165, 245, 1.0)), child: CircularProgressIndicator(color: Color.fromRGBO(66, 165, 245, 1.0)),
); );
} }
...@@ -124,7 +124,7 @@ class InspectionTopologyPage extends StatelessWidget { ...@@ -124,7 +124,7 @@ class InspectionTopologyPage extends StatelessWidget {
if (state.selectedNode != null) if (state.selectedNode != null)
DeviceDetailDialog( DeviceDetailDialog(
node: state.selectedNode!, node: state.selectedNode!,
curentState: state, currentState: state,
onClose: () => onClose: () =>
context.read<InspectionTopologyCubit>().selectNode(null), context.read<InspectionTopologyCubit>().selectNode(null),
), ),
......
...@@ -6,13 +6,13 @@ import '../cubit/inspection_topology_state.dart'; ...@@ -6,13 +6,13 @@ import '../cubit/inspection_topology_state.dart';
class DeviceDetailDialog extends StatelessWidget { class DeviceDetailDialog extends StatelessWidget {
final TopologyNode node; final TopologyNode node;
final InspectionTopologyState curentState; final InspectionTopologyState currentState;
final VoidCallback onClose; final VoidCallback onClose;
const DeviceDetailDialog({ const DeviceDetailDialog({
super.key, super.key,
required this.node, required this.node,
required this.curentState, required this.currentState,
required this.onClose, required this.onClose,
}); });
...@@ -26,7 +26,7 @@ class DeviceDetailDialog extends StatelessWidget { ...@@ -26,7 +26,7 @@ class DeviceDetailDialog extends StatelessWidget {
child: GestureDetector( child: GestureDetector(
onTap: onClose, onTap: onClose,
child: Container( child: Container(
color: Colors.black.withOpacity(0.5), color: Colors.black.withValues(alpha: 0.5),
), ),
), ),
), ),
...@@ -91,7 +91,7 @@ class DeviceDetailDialog extends StatelessWidget { ...@@ -91,7 +91,7 @@ class DeviceDetailDialog extends StatelessWidget {
), ),
SizedBox(height: 12.h), SizedBox(height: 12.h),
Text( Text(
curentState.deviceModel, currentState.deviceModel,
style: TextStyle( style: TextStyle(
fontSize: 24.sp, fontSize: 24.sp,
color: const Color.fromRGBO(148, 163, 184, 1), color: const Color.fromRGBO(148, 163, 184, 1),
......
import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:smart_hotel_app/blocs/auth/auth_bloc.dart'; import 'package:smart_hotel_app/blocs/auth/auth_bloc.dart';
import 'package:smart_hotel_app/blocs/auth/auth_event.dart'; import 'package:smart_hotel_app/blocs/auth/auth_event.dart';
...@@ -8,6 +10,7 @@ import 'package:smart_hotel_app/views/login/cubit/login_state.dart'; ...@@ -8,6 +10,7 @@ import 'package:smart_hotel_app/views/login/cubit/login_state.dart';
class LoginCubit extends Cubit<LoginState> { class LoginCubit extends Cubit<LoginState> {
final AuthBloc authBloc; final AuthBloc authBloc;
final StorageService storageService; final StorageService storageService;
late final StreamSubscription _authSubscription;
LoginCubit({ LoginCubit({
required this.authBloc, required this.authBloc,
...@@ -17,18 +20,32 @@ class LoginCubit extends Cubit<LoginState> { ...@@ -17,18 +20,32 @@ class LoginCubit extends Cubit<LoginState> {
} }
void _listenAuthState() { void _listenAuthState() {
authBloc.stream.listen((authState) { _authSubscription = authBloc.stream.listen(
if (authState is AuthFailure) { _onAuthStateChanged,
emit(state.copyWith(isLoading: false, error: authState.error)); onError: _onAuthError,
} else if (authState is AuthLoading) { );
emit(state.copyWith(isLoading: true, error: null)); }
} else if (authState is AuthSuccess) {
emit(state.copyWith(isLoading: false, error: null)); void _onAuthStateChanged(AuthState authState) {
} // 检查 Cubit 是否已关闭
}); if (isClosed) return;
if (authState is AuthFailure) {
emit(state.copyWith(isLoading: false, error: authState.error));
} else if (authState is AuthLoading) {
emit(state.copyWith(isLoading: true, error: null));
} else if (authState is AuthSuccess) {
emit(state.copyWith(isLoading: false, error: null));
}
}
void _onAuthError(error) {
if (isClosed) return;
emit(state.copyWith(isLoading: false, error: error.toString()));
} }
void toggleRememberPassword(bool value) { void toggleRememberPassword(bool value) {
if (isClosed) return;
emit(state.copyWith(rememberPassword: value)); emit(state.copyWith(rememberPassword: value));
} }
...@@ -53,6 +70,8 @@ class LoginCubit extends Cubit<LoginState> { ...@@ -53,6 +70,8 @@ class LoginCubit extends Cubit<LoginState> {
} }
Future<void> loadRememberCredentials() async { Future<void> loadRememberCredentials() async {
if (isClosed) return;
final credentials = await storageService.getRememberCredentials(); final credentials = await storageService.getRememberCredentials();
if (credentials['username'] != null) { if (credentials['username'] != null) {
emit(state.copyWith( emit(state.copyWith(
...@@ -66,4 +85,11 @@ class LoginCubit extends Cubit<LoginState> { ...@@ -66,4 +85,11 @@ class LoginCubit extends Cubit<LoginState> {
void forgotPwd() { void forgotPwd() {
// TODO: 跳转到忘记密码页面 // TODO: 跳转到忘记密码页面
} }
@override
Future<void> close() {
_authSubscription.cancel();
return super.close();
}
} }
\ No newline at end of file
...@@ -17,7 +17,7 @@ class BtnLogin extends StatelessWidget { ...@@ -17,7 +17,7 @@ class BtnLogin extends StatelessWidget {
width: double.infinity, width: double.infinity,
height: 104.h, height: 104.h,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Color.fromRGBO(73, 149, 234, 1), color: const Color.fromRGBO(73, 149, 234, 1),
borderRadius: BorderRadius.circular(40.w), borderRadius: BorderRadius.circular(40.w),
), ),
child: ElevatedButton( child: ElevatedButton(
......
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
@RoutePage()
class AboutView extends StatelessWidget {
const AboutView({super.key});
Widget _buildRichInfo() {
TextStyle highlightStyle = TextStyle(
fontSize: 30.sp,
color: const Color.fromRGBO(15, 23, 42, 1),
fontWeight: FontWeight.w600,
);
return RichText(
text: TextSpan(
style: TextStyle(
fontSize: 30.sp,
color: const Color.fromRGBO(100, 116, 139, 1),
),
children: [
TextSpan(text: '湖北洲语科技有限公司', style: highlightStyle),
const TextSpan(text: '成立于2020年06月30日,'
'注册地位于武汉市汉阳区龙阳大道98号武汉惠誉大厦6层(1)商号'),
TextSpan(text: '601-603', style: highlightStyle),
const TextSpan(text: ',''法定代表人为'),
TextSpan(text: '严高波', style: highlightStyle),
const TextSpan(text: '。经营范围包括光电、信息、网络技术开发、技术咨询、'
'技术服务、技术转让;电子产品、通讯设施(不含无线发射装置及卫星地面接收装置)、'
'光机电一体化设备技术研发、批发、零售;数据处理及存储服务;计算机软硬件技术开发、'
'技术咨询、技术服务;计算机系统集成;商务信息咨询(不含商务调查);'
'企业管理咨询;市场营销策划;策划创意服务;展示展览服务;会议会展服务;'
'文化艺术交流活动策划;货物或技术进出口(国家禁止或涉及行政审批的货物和技术进出口除外);'
'对农业项目的投资;对生态旅游项目的投资;安防设备、电子产品安装;'
'网络工程、弱电工程、通信工程、楼宇智能化工程、道路工程、园林绿化工程、建筑工程施工;'
'为建筑工程提供劳务服务(涉及许可经营项目,应取得相关部门许可后方可经营)。'),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color.fromRGBO(242, 243, 245, 1),
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios,
color: Color.fromRGBO(100, 116, 139, 1)),
onPressed: () => context.maybePop(),
),
title: Text(
'关于',
style: TextStyle(
color: const Color.fromRGBO(15, 23, 42, 1),
fontSize: 32.sp,
fontWeight: FontWeight.w600,
),
),
centerTitle: true,
),
body: SingleChildScrollView(
padding: EdgeInsets.all(28.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: double.infinity,
padding: EdgeInsets.all(28.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24.r),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'公司简介',
style: TextStyle(
fontSize: 30.sp,
fontWeight: FontWeight.w600,
color: const Color.fromRGBO(15, 23, 42, 1),
),
),
SizedBox(height: 20.h),
_buildRichInfo(),
],
),
),
],
),
),
);
}
}
\ No newline at end of file
...@@ -28,7 +28,7 @@ class ProfileCubit extends Cubit<ProfileState> { ...@@ -28,7 +28,7 @@ class ProfileCubit extends Cubit<ProfileState> {
const MenuItem(icon: 'lib/assets/icon/help_bg.png', title: '帮助'), const MenuItem(icon: 'lib/assets/icon/help_bg.png', title: '帮助'),
const MenuItem(icon: 'lib/assets/icon/about_bg.png', title: '关于'), const MenuItem(icon: 'lib/assets/icon/about_bg.png', title: '关于'),
const MenuItem(icon: 'lib/assets/icon/exit_login_bg.png', title: '退出登录'), const MenuItem(icon: 'lib/assets/icon/exit_login_bg.png', title: '退出登录'),
const MenuItem(icon: 'lib/assets/icon/logout_account_bg.png', title: '注销账号'), // const MenuItem(icon: 'lib/assets/icon/logout_account_bg.png', title: '注销账号'),
], ],
)); ));
} }
......
...@@ -55,8 +55,7 @@ class _ProfileContent extends StatelessWidget { ...@@ -55,8 +55,7 @@ class _ProfileContent extends StatelessWidget {
child: ProfileMenuBlock( child: ProfileMenuBlock(
menuList: state.menuList, menuList: state.menuList,
onLogout: () { onLogout: () {
context.read<AuthBloc>() _showHandleDialog(context);
.add(const AuthLogoutRequestedEvent());
}, },
), ),
), ),
...@@ -70,4 +69,100 @@ class _ProfileContent extends StatelessWidget { ...@@ -70,4 +69,100 @@ class _ProfileContent extends StatelessWidget {
}, },
); );
} }
void _showHandleDialog(BuildContext context) {
showDialog(
context: context,
barrierDismissible: true,
builder: (dialogContext) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24.r),
),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 28.w, vertical: 28.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24.r),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'温馨提示',
style: TextStyle(
fontSize: 34.sp,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
SizedBox(height: 30.h),
Text(
'确定退出登录吗?',
style: TextStyle(
fontSize: 32.sp,
color: Colors.black,
),
),
SizedBox(height: 50.h),
Row(
spacing: 28.w,
children: [
Expanded(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
Navigator.of(dialogContext).pop();
},
child: Container(
height: 80.h,
alignment: Alignment.center,
decoration: BoxDecoration(
color: const Color.fromRGBO(229, 241, 255, 1),
borderRadius: BorderRadius.circular(16.r),
),
child: Text(
'取消',
style: TextStyle(
fontSize: 32.sp,
color: const Color.fromRGBO(100, 116, 139, 1),
),
),
),
)
),
Expanded(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
Navigator.of(dialogContext).pop();
context.read<AuthBloc>().add(const AuthLogoutRequestedEvent());
},
child: Container(
height: 80.h,
alignment: Alignment.center,
decoration: BoxDecoration(
color: const Color.fromRGBO(73, 149, 234, 1),
borderRadius: BorderRadius.circular(16.r),
),
child: Text(
'确定',
style: TextStyle(
fontSize: 32.sp,
color: Colors.white,
),
),
),
)
),
],
),
],
),
),
);
},
);
}
} }
...@@ -73,6 +73,11 @@ class ProfileMenuBlock extends StatelessWidget { ...@@ -73,6 +73,11 @@ class ProfileMenuBlock extends StatelessWidget {
case '告警信息': case '告警信息':
context.pushRoute(const AbnormalListRoute()); context.pushRoute(const AbnormalListRoute());
break; break;
case '帮助':
break;
case '关于':
context.pushRoute(const AboutRoute());
break;
case '退出登录': case '退出登录':
onLogout?.call(); onLogout?.call();
break; break;
......
...@@ -20,26 +20,29 @@ class ReportIndexView extends StatefulWidget { ...@@ -20,26 +20,29 @@ class ReportIndexView extends StatefulWidget {
} }
class _ReportIndexViewState extends State<ReportIndexView> with AutoRouteAwareStateMixin<ReportIndexView> { class _ReportIndexViewState extends State<ReportIndexView> with AutoRouteAwareStateMixin<ReportIndexView> {
TabsRouter? _tabsRouter;
@override @override
void didChangeDependencies() { void didChangeDependencies() {
super.didChangeDependencies(); super.didChangeDependencies();
// Subscribe to tab router events final newRouter = AutoTabsRouter.of(context);
final tabsRouter = AutoTabsRouter.of(context); if (newRouter != _tabsRouter) {
tabsRouter.addListener(_onTabChange); _tabsRouter?.removeListener(_onTabChange);
_tabsRouter = newRouter;
_tabsRouter?.addListener(_onTabChange);
}
} }
@override @override
void dispose() { void dispose() {
final tabsRouter = AutoTabsRouter.of(context); _tabsRouter?.removeListener(_onTabChange);
tabsRouter.removeListener(_onTabChange);
super.dispose(); super.dispose();
} }
void _onTabChange() { void _onTabChange() {
final tabsRouter = AutoTabsRouter.of(context); if (!mounted) return;
// Check if this tab is now active (index 2)
if (tabsRouter.activeIndex == 2) { if (_tabsRouter?.activeIndex == 2) {
// Reload data when tab is activated
context.read<ReportIndexCubit>().reloadData(); context.read<ReportIndexCubit>().reloadData();
} }
} }
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/utils/deal_utils.dart';
class ReportMetricsBlock extends StatelessWidget { class ReportMetricsBlock extends StatelessWidget {
final Map<String, dynamic> reportMetrics; final Map<String, dynamic> reportMetrics;
...@@ -16,7 +17,7 @@ class ReportMetricsBlock extends StatelessWidget { ...@@ -16,7 +17,7 @@ class ReportMetricsBlock extends StatelessWidget {
Expanded( Expanded(
child: _buildMetricCard( child: _buildMetricCard(
title: '今日用电', title: '今日用电',
value: powerUsage['value'], value: DealUtils.cleanNumber(powerUsage['value'] as String),
unit: powerUsage['unit'] as String, unit: powerUsage['unit'] as String,
changeText: powerUsage['changeText'] as String, changeText: powerUsage['changeText'] as String,
changeColor: const Color.fromRGBO(20, 184, 166, 1), changeColor: const Color.fromRGBO(20, 184, 166, 1),
...@@ -86,6 +87,7 @@ class ReportMetricsBlock extends StatelessWidget { ...@@ -86,6 +87,7 @@ class ReportMetricsBlock extends StatelessWidget {
children: [ children: [
Text( Text(
value, value,
// cleanNumber(value),
style: TextStyle( style: TextStyle(
fontSize: 40.sp, fontSize: 40.sp,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
......
...@@ -31,6 +31,8 @@ class RoomReportCubit extends Cubit<RoomReportState> { ...@@ -31,6 +31,8 @@ class RoomReportCubit extends Cubit<RoomReportState> {
} }
void selectFloor(int index) { void selectFloor(int index) {
if (state.selectedFloorIndex == index) return;
final floorId = state.floors[index].floorId; final floorId = state.floors[index].floorId;
emit(state.copyWith(selectedFloorIndex: index)); emit(state.copyWith(selectedFloorIndex: index));
loadData(floorId: floorId); loadData(floorId: floorId);
......
...@@ -23,54 +23,6 @@ class ReportRoomDetailView extends StatelessWidget { ...@@ -23,54 +23,6 @@ class ReportRoomDetailView extends StatelessWidget {
), ),
child: BlocBuilder<RoomReportCubit, RoomReportState>( child: BlocBuilder<RoomReportCubit, RoomReportState>(
builder: (context, state) { builder: (context, state) {
if (state.isLoading && state.floors.isEmpty) {
return Scaffold(
backgroundColor: const Color.fromRGBO(242, 243, 245, 1),
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios, color: Colors.black),
onPressed: () => context.maybePop(),
),
title: Text(
'客房状态总览',
style: TextStyle(
color: Colors.black,
fontSize: 32.sp,
fontWeight: FontWeight.bold,
),
),
centerTitle: true,
),
body: const Center(child: CircularProgressIndicator(color: Color.fromRGBO(66, 165, 245, 1.0))),
);
}
if (state.error != null && state.floors.isEmpty) {
return Scaffold(
backgroundColor: const Color.fromRGBO(242, 243, 245, 1),
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios, color: Colors.black),
onPressed: () => context.maybePop(),
),
title: Text(
'客房状态总览',
style: TextStyle(
color: Colors.black,
fontSize: 32.sp,
fontWeight: FontWeight.bold,
),
),
centerTitle: true,
),
body: Center(child: Text('加载失败: ${state.error}')),
);
}
return Scaffold( return Scaffold(
backgroundColor: const Color.fromRGBO(242, 243, 245, 1), backgroundColor: const Color.fromRGBO(242, 243, 245, 1),
appBar: AppBar( appBar: AppBar(
...@@ -78,9 +30,7 @@ class ReportRoomDetailView extends StatelessWidget { ...@@ -78,9 +30,7 @@ class ReportRoomDetailView extends StatelessWidget {
elevation: 0, elevation: 0,
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back_ios, color: Colors.black), icon: const Icon(Icons.arrow_back_ios, color: Colors.black),
onPressed: () { onPressed: () => context.maybePop(),
context.maybePop();
},
), ),
title: Text( title: Text(
'客房状态总览', '客房状态总览',
...@@ -92,58 +42,71 @@ class ReportRoomDetailView extends StatelessWidget { ...@@ -92,58 +42,71 @@ class ReportRoomDetailView extends StatelessWidget {
), ),
centerTitle: true, centerTitle: true,
), ),
body: SingleChildScrollView( body: _buildBody(context, state),
child: Column( );
children: [ },
Padding( ),
padding: EdgeInsets.symmetric(horizontal: 28.w), );
child: Column( }
children: [
SizedBox(height: 10.h), Widget _buildBody(BuildContext context, RoomReportState state) {
if (state.floors.isNotEmpty) if (state.isLoading && state.floors.isEmpty) {
Container( return const Center(child: CircularProgressIndicator(color: Color.fromRGBO(66, 165, 245, 1)));
decoration: BoxDecoration( }
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(24.r)), if (state.error != null && state.floors.isEmpty) {
), return Center(child: Text('加载失败: ${state.error}'));
child: FloorSelector( }
floorNames: state.floors.map((f) => f.floorName).toList(),
selectedIndex: state.selectedFloorIndex, return SingleChildScrollView(
), child: Column(
), children: [
if (state.floors.isNotEmpty && state.selectedFloorIndex < state.floors.length) Padding(
Container( padding: EdgeInsets.symmetric(horizontal: 28.w),
decoration: BoxDecoration( child: Column(
color: Colors.white, children: [
borderRadius: BorderRadius.vertical(bottom: Radius.circular(24.r)), SizedBox(height: 10.h),
), if (state.floors.isNotEmpty)
padding: EdgeInsets.only(bottom: 28.w), Container(
child: RoomGrid(rooms: state.roomGrid), decoration: BoxDecoration(
), color: Colors.white,
SizedBox(height: 10.h), borderRadius: BorderRadius.vertical(top: Radius.circular(24.r)),
StatusStats(summary: state.summary), ),
SizedBox(height: 10.h), child: FloorSelector(
if (state.isLoading) floorNames: state.floors.map((f) => f.floorName).toList(),
const Padding( selectedIndex: state.selectedFloorIndex,
padding: EdgeInsets.all(16.0),
child: Center(child: CircularProgressIndicator(color: const Color.fromRGBO(66, 165, 245, 1.0))),
),
...state.detailRooms.map((room) {
return Padding(
padding: EdgeInsets.only(bottom: 10.h),
child: RoomDetailCard(room: room),
);
}).toList(),
SizedBox(height: 10.h),
],
), ),
), ),
], if (state.floors.isNotEmpty && state.selectedFloorIndex < state.floors.length)
), Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(bottom: Radius.circular(24.r)),
),
padding: EdgeInsets.only(bottom: 28.w),
child: RoomGrid(rooms: state.roomGrid),
),
SizedBox(height: 10.h),
StatusStats(summary: state.summary),
SizedBox(height: 10.h),
if (state.isLoading)
const Padding(
padding: EdgeInsets.all(16.0),
child: Center(child: CircularProgressIndicator(color: Color.fromRGBO(66, 165, 245, 1.0))),
),
...state.detailRooms.map((room) {
return Padding(
padding: EdgeInsets.only(bottom: 10.h),
child: RoomDetailCard(room: room),
);
}).toList(),
SizedBox(height: 10.h),
],
), ),
); ),
}, ],
), ),
); );
} }
} }
\ No newline at end of file
...@@ -16,7 +16,7 @@ class RoomGrid extends StatelessWidget { ...@@ -16,7 +16,7 @@ class RoomGrid extends StatelessWidget {
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 5, crossAxisCount: 5,
mainAxisSpacing: 20.h, mainAxisSpacing: 16.h,
crossAxisSpacing: 16.w, crossAxisSpacing: 16.w,
children: rooms.map((room) => _RoomItem(room: room)).toList(), children: rooms.map((room) => _RoomItem(room: room)).toList(),
), ),
...@@ -31,49 +31,38 @@ class _RoomItem extends StatelessWidget { ...@@ -31,49 +31,38 @@ class _RoomItem extends StatelessWidget {
Color get _bgColor { Color get _bgColor {
switch (room.statusColor) { switch (room.statusColor) {
case 'occupied': case 'blue':
return const Color.fromRGBO(219, 234, 254, 1); return const Color.fromRGBO(229, 241, 255, 1);
case 'alarm': case 'yellow':
return const Color.fromRGBO(254, 243, 199, 1); return const Color.fromRGBO(255, 251, 237, 1);
case 'reserved': case 'default':
return const Color.fromRGBO(219, 234, 254, 1); case 'gray':
case 'dnd':
return const Color.fromRGBO(254, 243, 199, 1);
case 'checkout':
case 'idle':
default: default:
return const Color.fromRGBO(229, 231, 235, 1); return const Color.fromRGBO(238, 238, 238, 1);
} }
} }
Color get _textColor { Color get _textColor {
switch (room.statusColor) { switch (room.statusColor) {
case 'occupied': case 'blue':
return const Color.fromRGBO(59, 130, 246, 1); return const Color.fromRGBO(74, 149, 234, 1);
case 'alarm': case 'yellow':
return const Color.fromRGBO(245, 158, 11, 1); return const Color.fromRGBO(250, 204, 21, 1);
case 'reserved': case 'default':
return const Color.fromRGBO(59, 130, 246, 1); case 'gray':
case 'dnd':
return const Color.fromRGBO(245, 158, 11, 1);
case 'checkout':
case 'idle':
default: default:
return const Color.fromRGBO(156, 163, 175, 1); return const Color.fromRGBO(100, 116, 139, 1);
} }
} }
Color? get _borderColor { Color? get _borderColor {
switch (room.statusColor) { switch (room.statusColor) {
case 'idle': case 'blue':
case 'checkout': return const Color.fromRGBO(73, 149, 234, 1);
return null; case 'yellow':
case 'occupied': return const Color.fromRGBO(250, 204, 21, 1);
case 'reserved': case 'default':
return const Color.fromRGBO(59, 130, 246, 1); case 'gray':
case 'alarm':
case 'dnd':
return const Color.fromRGBO(245, 158, 11, 1);
default: default:
return null; return null;
} }
...@@ -81,44 +70,56 @@ class _RoomItem extends StatelessWidget { ...@@ -81,44 +70,56 @@ class _RoomItem extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return GestureDetector(
padding: EdgeInsets.symmetric(vertical: 12.h, horizontal: 8.w), behavior: HitTestBehavior.opaque,
decoration: BoxDecoration( onTap: () {
color: _bgColor,
borderRadius: BorderRadius.circular(16.r), },
border: _borderColor != null child: Container(
? Border.all(color: _borderColor!, width: 2.w) padding: EdgeInsets.symmetric(vertical: 12.h),
: null, decoration: BoxDecoration(
), color: _bgColor,
child: Column( borderRadius: BorderRadius.circular(16.r),
mainAxisAlignment: MainAxisAlignment.center, // border: _borderColor != null
children: [ // ? Border.all(color: _borderColor!, width: 1.w)
Text( // : null,
room.roomNumber, ),
style: TextStyle( child: Column(
fontSize: 28.sp, mainAxisAlignment: MainAxisAlignment.center,
fontWeight: FontWeight.w500, spacing: 4.h,
color: _textColor, children: [
Text(
room.roomNumber,
style: TextStyle(
fontSize: 28.sp,
fontWeight: FontWeight.w500,
color: _textColor,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
), ),
maxLines: 1, Row(
overflow: TextOverflow.ellipsis, mainAxisAlignment: MainAxisAlignment.center,
), children: [
SizedBox(height: 4.h), if (room.hasAlarmIcon)
Row( Icon(Icons.warning_amber_outlined, color: _textColor, size: 24.sp),
mainAxisAlignment: MainAxisAlignment.center, Flexible(
children: [ fit: FlexFit.loose, //让文本只占实际需要的宽度
if (room.hasAlarmIcon) child: Text(
Icon(Icons.warning_amber_outlined, color: _textColor, size: 24.sp), room.statusText,
Text( style: TextStyle(
room.statusText, fontSize: 24.sp,
style: TextStyle( color: _textColor,
fontSize: 24.sp, ),
color: _textColor, textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
), ),
), ],
], ),
), ],
], ),
), ),
); );
} }
......
...@@ -49,7 +49,7 @@ class _StatItem extends StatelessWidget { ...@@ -49,7 +49,7 @@ class _StatItem extends StatelessWidget {
child: Container( child: Container(
padding: EdgeInsets.symmetric(vertical: 12.h, horizontal: 8.w), padding: EdgeInsets.symmetric(vertical: 12.h, horizontal: 8.w),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color.fromRGBO(229, 231, 235, 1), color: const Color.fromRGBO(238, 238, 238, 1),
borderRadius: BorderRadius.circular(24.r), borderRadius: BorderRadius.circular(24.r),
), ),
child: Column( child: Column(
...@@ -67,7 +67,7 @@ class _StatItem extends StatelessWidget { ...@@ -67,7 +67,7 @@ class _StatItem extends StatelessWidget {
item.label, item.label,
style: TextStyle( style: TextStyle(
fontSize: 24.sp, fontSize: 24.sp,
color: const Color.fromRGBO(156, 163, 175, 1), color: const Color.fromRGBO(100, 116, 139, 1),
), ),
), ),
], ],
......
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:smart_hotel_app/models/bo/room_bo.dart';
class ServiceIndexState extends Equatable { class ServiceIndexState extends Equatable {
/// copyWith sentinel: 区分「未传参」和「显式传 null」 /// copyWith sentinel: 区分「未传参」和「显式传 null」
...@@ -16,9 +15,7 @@ class ServiceIndexState extends Equatable { ...@@ -16,9 +15,7 @@ class ServiceIndexState extends Equatable {
final int? selectedRoomId; final int? selectedRoomId;
final String searchText; final String searchText;
final List<Map<String, dynamic>> rooms; final List<Map<String, dynamic>> rooms;
final List<AreaDeviceBO> areaDevices;
final bool isLoading; final bool isLoading;
final bool isLoadingAreaDevices;
final String? error; final String? error;
final bool isPoweringOn; final bool isPoweringOn;
final bool isPoweringOff; final bool isPoweringOff;
...@@ -35,9 +32,7 @@ class ServiceIndexState extends Equatable { ...@@ -35,9 +32,7 @@ class ServiceIndexState extends Equatable {
this.selectedRoomId, this.selectedRoomId,
this.searchText = '', this.searchText = '',
this.rooms = const [], this.rooms = const [],
this.areaDevices = const [],
this.isLoading = false, this.isLoading = false,
this.isLoadingAreaDevices = false,
this.error, this.error,
this.isPoweringOn = false, this.isPoweringOn = false,
this.isPoweringOff = false, this.isPoweringOff = false,
...@@ -55,9 +50,7 @@ class ServiceIndexState extends Equatable { ...@@ -55,9 +50,7 @@ class ServiceIndexState extends Equatable {
Object? selectedRoomId = _nothing, Object? selectedRoomId = _nothing,
String? searchText, String? searchText,
List<Map<String, dynamic>>? rooms, List<Map<String, dynamic>>? rooms,
List<AreaDeviceBO>? areaDevices,
bool? isLoading, bool? isLoading,
bool? isLoadingAreaDevices,
Object? error = _nothing, Object? error = _nothing,
bool? isPoweringOn, bool? isPoweringOn,
bool? isPoweringOff, bool? isPoweringOff,
...@@ -74,9 +67,7 @@ class ServiceIndexState extends Equatable { ...@@ -74,9 +67,7 @@ class ServiceIndexState extends Equatable {
selectedRoomId: identical(selectedRoomId, _nothing) ? this.selectedRoomId : selectedRoomId as int?, selectedRoomId: identical(selectedRoomId, _nothing) ? this.selectedRoomId : selectedRoomId as int?,
searchText: searchText ?? this.searchText, searchText: searchText ?? this.searchText,
rooms: rooms ?? this.rooms, rooms: rooms ?? this.rooms,
areaDevices: areaDevices ?? this.areaDevices,
isLoading: isLoading ?? this.isLoading, isLoading: isLoading ?? this.isLoading,
isLoadingAreaDevices: isLoadingAreaDevices ?? this.isLoadingAreaDevices,
error: identical(error, _nothing) ? this.error : error as String?, error: identical(error, _nothing) ? this.error : error as String?,
isPoweringOn: isPoweringOn ?? this.isPoweringOn, isPoweringOn: isPoweringOn ?? this.isPoweringOn,
isPoweringOff: isPoweringOff ?? this.isPoweringOff, isPoweringOff: isPoweringOff ?? this.isPoweringOff,
...@@ -96,9 +87,7 @@ class ServiceIndexState extends Equatable { ...@@ -96,9 +87,7 @@ class ServiceIndexState extends Equatable {
selectedRoomId, selectedRoomId,
searchText, searchText,
rooms, rooms,
areaDevices,
isLoading, isLoading,
isLoadingAreaDevices,
error, error,
isPoweringOn, isPoweringOn,
isPoweringOff, isPoweringOff,
......
...@@ -17,26 +17,29 @@ class ServiceIndexView extends StatefulWidget { ...@@ -17,26 +17,29 @@ class ServiceIndexView extends StatefulWidget {
} }
class _ServiceIndexViewState extends State<ServiceIndexView> with AutoRouteAwareStateMixin<ServiceIndexView> { class _ServiceIndexViewState extends State<ServiceIndexView> with AutoRouteAwareStateMixin<ServiceIndexView> {
TabsRouter? _tabsRouter;
@override @override
void didChangeDependencies() { void didChangeDependencies() {
super.didChangeDependencies(); super.didChangeDependencies();
// Subscribe to tab router events final newRouter = AutoTabsRouter.of(context);
final tabsRouter = AutoTabsRouter.of(context); if (newRouter != _tabsRouter) {
tabsRouter.addListener(_onTabChange); _tabsRouter?.removeListener(_onTabChange);
_tabsRouter = newRouter;
_tabsRouter?.addListener(_onTabChange);
}
} }
@override @override
void dispose() { void dispose() {
final tabsRouter = AutoTabsRouter.of(context); _tabsRouter?.removeListener(_onTabChange);
tabsRouter.removeListener(_onTabChange);
super.dispose(); super.dispose();
} }
void _onTabChange() { void _onTabChange() {
final tabsRouter = AutoTabsRouter.of(context); if (!mounted) return;
// Check if this tab is now active (index 1)
if (tabsRouter.activeIndex == 1) { if (_tabsRouter?.activeIndex == 1) {
// Reload data when tab is activated
context.read<ServiceIndexCubit>().reloadAll(); context.read<ServiceIndexCubit>().reloadAll();
} }
} }
......
...@@ -7,7 +7,6 @@ import 'package:smart_hotel_app/views/service/index/cubit/service_index_cubit.da ...@@ -7,7 +7,6 @@ import 'package:smart_hotel_app/views/service/index/cubit/service_index_cubit.da
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:smart_hotel_app/routes/app_router.gr.dart'; import 'package:smart_hotel_app/routes/app_router.gr.dart';
import 'package:smart_hotel_app/views/service/index/cubit/service_index_state.dart'; import 'package:smart_hotel_app/views/service/index/cubit/service_index_state.dart';
import 'package:smart_hotel_app/views/service/index/widget/area_device_list.dart';
import 'package:smart_hotel_app/utils/toast_utils.dart'; import 'package:smart_hotel_app/utils/toast_utils.dart';
class RoomManagementBlock extends StatelessWidget { class RoomManagementBlock extends StatelessWidget {
...@@ -39,28 +38,19 @@ class RoomManagementBlock extends StatelessWidget { ...@@ -39,28 +38,19 @@ class RoomManagementBlock extends StatelessWidget {
SizedBox(height: 24.h), SizedBox(height: 24.h),
_buildTypeSelector(state, cubit), _buildTypeSelector(state, cubit),
SizedBox(height: 24.h), SizedBox(height: 24.h),
if (state.selectedType == null || state.selectedType == '客房') ...[ _buildSearchBox(state, cubit),
_buildSearchBox(state, cubit), SizedBox(height: 32.h),
SizedBox(height: 32.h), _buildLine(),
_buildLine(), SizedBox(height: 24.h),
SizedBox(height: 24.h), _buildRoomGrid(state, cubit),
_buildRoomGrid(state, cubit), SizedBox(height: 24.h),
SizedBox(height: 24.h), if (state.selectedRoom != null) ...[
if (state.selectedRoom != null) ...[
_buildLine(),
SizedBox(height: 24.h),
_buildRoomDetail(state, cubit),
SizedBox(height: 10.h),
],
_buildBottomButtons(state, cubit, context),
] else ...[
_buildLine(), _buildLine(),
SizedBox(height: 24.h), SizedBox(height: 24.h),
AreaDeviceList( _buildRoomDetail(state, cubit),
devices: state.areaDevices, SizedBox(height: 10.h),
isLoading: state.isLoadingAreaDevices,
),
], ],
_buildBottomButtons(state, cubit, context),
], ],
), ),
); );
...@@ -78,8 +68,7 @@ class RoomManagementBlock extends StatelessWidget { ...@@ -78,8 +68,7 @@ class RoomManagementBlock extends StatelessWidget {
floorName == '3' ? '三' : floorName == '3' ? '三' :
floorName == '4' ? '四' : floorName == '4' ? '四' :
floorName == '5' ? '五' : floorName == '5' ? '五' :
floorName == '6' ? '六' : floorName == '6' ? '六' : '';
floorName == '7' ? '七' : '';
final floorId = floorMap.keys.first; final floorId = floorMap.keys.first;
final isSelected = state.selectedFloor == floorName; final isSelected = state.selectedFloor == floorName;
return Expanded( return Expanded(
...@@ -241,7 +230,10 @@ class RoomManagementBlock extends StatelessWidget { ...@@ -241,7 +230,10 @@ class RoomManagementBlock extends StatelessWidget {
const crossAxisCount = 12; const crossAxisCount = 12;
final floorPart = state.selectedFloor?.replaceAll('楼', '') ?? ''; final floorPart = state.selectedFloor?.replaceAll('楼', '') ?? '';
final filterLabel = floorPart.isNotEmpty ? '客房$floorPart楼·左右滑动选择' : '左右滑动选择'; final areaLabel = state.selectedType ?? '全部';
final filterLabel = floorPart.isNotEmpty
? '$areaLabel·$floorPart楼·左右滑动选择'
: '$areaLabel·左右滑动选择';
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
...@@ -386,6 +378,8 @@ class RoomManagementBlock extends StatelessWidget { ...@@ -386,6 +378,8 @@ class RoomManagementBlock extends StatelessWidget {
width: 24.sp, width: 24.sp,
height: 30.sp, height: 30.sp,
); );
// return Icon(Icons.cleaning_services_rounded, size: 26.w,
// color: const Color.fromRGBO(255, 138, 139, 1));
} else { } else {
return Text( return Text(
' ', ' ',
...@@ -405,7 +399,6 @@ class RoomManagementBlock extends StatelessWidget { ...@@ -405,7 +399,6 @@ class RoomManagementBlock extends StatelessWidget {
Widget _buildBottomButtons(ServiceIndexState state, ServiceIndexCubit cubit, BuildContext context) { Widget _buildBottomButtons(ServiceIndexState state, ServiceIndexCubit cubit, BuildContext context) {
final isRoomSelected = state.selectedRoom != null; final isRoomSelected = state.selectedRoom != null;
final isProcessing = state.isPoweringOn || state.isPoweringOff;
return Row( return Row(
spacing: 16.w, spacing: 16.w,
children: [ children: [
...@@ -421,7 +414,7 @@ class RoomManagementBlock extends StatelessWidget { ...@@ -421,7 +414,7 @@ class RoomManagementBlock extends StatelessWidget {
), ),
elevation: 0, elevation: 0,
), ),
onPressed: isRoomSelected && !isProcessing onPressed: isRoomSelected && !state.isPoweringOn
? () async { ? () async {
try { try {
await cubit.powerOnRoom(state.selectedRoom!); await cubit.powerOnRoom(state.selectedRoom!);
...@@ -453,14 +446,14 @@ class RoomManagementBlock extends StatelessWidget { ...@@ -453,14 +446,14 @@ class RoomManagementBlock extends StatelessWidget {
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.r), borderRadius: BorderRadius.circular(16.r),
side: BorderSide( // side: BorderSide(
color: const Color.fromRGBO(73, 149, 234, 1), // color: const Color.fromRGBO(73, 149, 234, 1),
width: 1.w, // width: 1.w,
), // ),
), ),
elevation: 0, elevation: 0,
), ),
onPressed: isRoomSelected && !isProcessing onPressed: isRoomSelected && !state.isPoweringOff
? () async { ? () async {
try { try {
await cubit.powerOffRoom(state.selectedRoom!); await cubit.powerOffRoom(state.selectedRoom!);
...@@ -515,6 +508,7 @@ class RoomManagementBlock extends StatelessWidget { ...@@ -515,6 +508,7 @@ class RoomManagementBlock extends StatelessWidget {
], ],
); );
} }
} }
......
...@@ -150,36 +150,55 @@ class ServiceRoomCubit extends Cubit<ServiceRoomState> { ...@@ -150,36 +150,55 @@ class ServiceRoomCubit extends Cubit<ServiceRoomState> {
/// 房间总电源开关 /// 房间总电源开关
Future<void> toggleMainPowerForSwitch(bool value) async { Future<void> toggleMainPowerForSwitch(bool value) async {
if (state.isTogglingPower) return; if (state.isPoweringOn || state.isPoweringOff) return;
emit(state.copyWith(isTogglingPower: true)); final loadingKey = value ? 'isPoweringOn' : 'isPoweringOff';
emit(state.copyWith(
isPoweringOn: value,
isPoweringOff: !value,
));
try { try {
if (value) { if (value) {
await _service.roomPowerOn(roomId: _roomId); await _service.roomPowerOn(roomId: _roomId);
} else { } else {
await _service.roomPowerOff(roomId: _roomId); await _service.roomPowerOff(roomId: _roomId);
} }
emit(state.copyWith(isTogglingPower: false, mainPowerOn: value)); emit(state.copyWith(
isPoweringOn: false,
isPoweringOff: false,
mainPowerOn: value,
));
} catch (e) { } catch (e) {
emit(state.copyWith(isTogglingPower: false)); emit(state.copyWith(
// Re-throw so the view can show error snackbar isPoweringOn: false,
isPoweringOff: false,
));
rethrow; rethrow;
} }
} }
/// 房间总电源开关 /// 房间总电源开关
Future<void> toggleMainPower(bool value) async { Future<void> toggleMainPower(bool value) async {
if (state.isTogglingPower) return; if (state.isPoweringOn || state.isPoweringOff) return;
emit(state.copyWith(isTogglingPower: true)); emit(state.copyWith(
isPoweringOn: value,
isPoweringOff: !value,
));
try { try {
if (value) { if (value) {
await _service.powerOn(roomId: _roomId); await _service.powerOn(roomId: _roomId);
} else { } else {
await _service.powerOff(roomId: _roomId); await _service.powerOff(roomId: _roomId);
} }
emit(state.copyWith(isTogglingPower: false, mainPowerOn: value)); emit(state.copyWith(
isPoweringOn: false,
isPoweringOff: false,
mainPowerOn: value,
));
} catch (e) { } catch (e) {
emit(state.copyWith(isTogglingPower: false)); emit(state.copyWith(
// Re-throw so the view can show error snackbar isPoweringOn: false,
isPoweringOff: false,
));
rethrow; rethrow;
} }
} }
......
...@@ -45,7 +45,8 @@ class ServiceRoomState extends Equatable { ...@@ -45,7 +45,8 @@ class ServiceRoomState extends Equatable {
final List<RoomDevice> roomDevices; final List<RoomDevice> roomDevices;
final bool mainPowerOn; final bool mainPowerOn;
final List<ControlDevice> controlDevices; final List<ControlDevice> controlDevices;
final bool isTogglingPower; final bool isPoweringOn;
final bool isPoweringOff;
const ServiceRoomState({ const ServiceRoomState({
this.isLoading = false, this.isLoading = false,
...@@ -57,7 +58,8 @@ class ServiceRoomState extends Equatable { ...@@ -57,7 +58,8 @@ class ServiceRoomState extends Equatable {
this.roomDevices = const [], this.roomDevices = const [],
this.mainPowerOn = false, this.mainPowerOn = false,
this.controlDevices = const [], this.controlDevices = const [],
this.isTogglingPower = false, this.isPoweringOn = false,
this.isPoweringOff = false,
}); });
ServiceRoomState copyWith({ ServiceRoomState copyWith({
...@@ -70,7 +72,8 @@ class ServiceRoomState extends Equatable { ...@@ -70,7 +72,8 @@ class ServiceRoomState extends Equatable {
List<RoomDevice>? roomDevices, List<RoomDevice>? roomDevices,
bool? mainPowerOn, bool? mainPowerOn,
List<ControlDevice>? controlDevices, List<ControlDevice>? controlDevices,
bool? isTogglingPower, bool? isPoweringOn,
bool? isPoweringOff,
}) { }) {
return ServiceRoomState( return ServiceRoomState(
isLoading: isLoading ?? this.isLoading, isLoading: isLoading ?? this.isLoading,
...@@ -82,7 +85,8 @@ class ServiceRoomState extends Equatable { ...@@ -82,7 +85,8 @@ class ServiceRoomState extends Equatable {
roomDevices: roomDevices ?? this.roomDevices, roomDevices: roomDevices ?? this.roomDevices,
mainPowerOn: mainPowerOn ?? this.mainPowerOn, mainPowerOn: mainPowerOn ?? this.mainPowerOn,
controlDevices: controlDevices ?? this.controlDevices, controlDevices: controlDevices ?? this.controlDevices,
isTogglingPower: isTogglingPower ?? this.isTogglingPower, isPoweringOn: isPoweringOn ?? this.isPoweringOn,
isPoweringOff: isPoweringOff ?? this.isPoweringOff,
); );
} }
...@@ -97,6 +101,7 @@ class ServiceRoomState extends Equatable { ...@@ -97,6 +101,7 @@ class ServiceRoomState extends Equatable {
roomDevices, roomDevices,
mainPowerOn, mainPowerOn,
controlDevices, controlDevices,
isTogglingPower, isPoweringOn,
isPoweringOff,
]; ];
} }
...@@ -124,7 +124,8 @@ class ServiceRoomDetailView extends StatelessWidget { ...@@ -124,7 +124,8 @@ class ServiceRoomDetailView extends StatelessWidget {
), ),
SizedBox(height: 20.h), SizedBox(height: 20.h),
RoomBottomButtons( RoomBottomButtons(
isProcessing: state.isTogglingPower, isPoweringOn: state.isPoweringOn,
isPoweringOff: state.isPoweringOff,
onPowerOn: () async { onPowerOn: () async {
try { try {
await cubit.toggleMainPower(true); await cubit.toggleMainPower(true);
......
...@@ -4,15 +4,15 @@ import 'package:flutter_screenutil/flutter_screenutil.dart'; ...@@ -4,15 +4,15 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
class RoomBottomButtons extends StatelessWidget { class RoomBottomButtons extends StatelessWidget {
final VoidCallback? onPowerOn; final VoidCallback? onPowerOn;
final VoidCallback? onPowerOff; final VoidCallback? onPowerOff;
// final VoidCallback? onDeviceControl; final bool isPoweringOn;
final bool isProcessing; final bool isPoweringOff;
const RoomBottomButtons({ const RoomBottomButtons({
super.key, super.key,
this.onPowerOn, this.onPowerOn,
this.onPowerOff, this.onPowerOff,
// this.onDeviceControl, this.isPoweringOn = false,
this.isProcessing = false, this.isPoweringOff = false,
}); });
@override @override
...@@ -22,11 +22,11 @@ class RoomBottomButtons extends StatelessWidget { ...@@ -22,11 +22,11 @@ class RoomBottomButtons extends StatelessWidget {
children: [ children: [
Expanded( Expanded(
child: GestureDetector( child: GestureDetector(
onTap: (onPowerOn != null && !isProcessing) ? onPowerOn : null, onTap: (onPowerOn != null && !isPoweringOn) ? onPowerOn : null,
child: Container( child: Container(
height: 70.h, height: 70.h,
decoration: BoxDecoration( decoration: BoxDecoration(
color: (onPowerOn != null && !isProcessing) color: (onPowerOn != null && !isPoweringOn)
? const Color.fromRGBO(73, 149, 234, 1) ? const Color.fromRGBO(73, 149, 234, 1)
: const Color.fromRGBO(204, 204, 204, 1), : const Color.fromRGBO(204, 204, 204, 1),
borderRadius: BorderRadius.circular(16.r), borderRadius: BorderRadius.circular(16.r),
...@@ -46,26 +46,20 @@ class RoomBottomButtons extends StatelessWidget { ...@@ -46,26 +46,20 @@ class RoomBottomButtons extends StatelessWidget {
), ),
Expanded( Expanded(
child: GestureDetector( child: GestureDetector(
onTap: (onPowerOff != null && !isProcessing) ? onPowerOff : null, onTap: (onPowerOff != null && !isPoweringOff) ? onPowerOff : null,
child: Container( child: Container(
height: 70.h, height: 70.h,
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color.fromRGBO(229, 241, 255, 1), color: (onPowerOff != null && !isPoweringOff)
? const Color.fromRGBO(229, 241, 255, 1)
: const Color.fromRGBO(204, 204, 204, 1),
borderRadius: BorderRadius.circular(16.r), borderRadius: BorderRadius.circular(16.r),
border: Border.all(
color: (onPowerOff != null && !isProcessing)
? const Color.fromRGBO(73, 149, 234, 1)
: const Color.fromRGBO(204, 204, 204, 1),
width: 1.w,
),
), ),
child: Center( child: Center(
child: Text( child: Text(
'断电', '断电',
style: TextStyle( style: TextStyle(
color: (onPowerOff != null && !isProcessing) color: const Color.fromRGBO(100, 116, 139, 1),
? const Color.fromRGBO(66, 165, 245, 1.0)
: const Color.fromRGBO(100, 116, 139, 1),
fontSize: 28.sp, fontSize: 28.sp,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
...@@ -74,29 +68,7 @@ class RoomBottomButtons extends StatelessWidget { ...@@ -74,29 +68,7 @@ class RoomBottomButtons extends StatelessWidget {
), ),
), ),
), ),
// Expanded(
// child: GestureDetector(
// onTap: onDeviceControl,
// child: Container(
// height: 70.h,
// decoration: BoxDecoration(
// color: const Color.fromRGBO(100, 116, 139, 0.2),
// borderRadius: BorderRadius.circular(16.r),
// ),
// child: Center(
// child: Text(
// '设备控制',
// style: TextStyle(
// color: const Color.fromRGBO(100, 116, 139, 1.0),
// fontSize: 28.sp,
// fontWeight: FontWeight.w500,
// ),
// ),
// ),
// ),
// ),
// ),
], ],
); );
} }
} }
\ No newline at end of file
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