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) {
...@@ -63,7 +63,7 @@ class _PowerCharBlockState extends State<PowerCharBlock> { ...@@ -63,7 +63,7 @@ class _PowerCharBlockState extends State<PowerCharBlock> {
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),
...@@ -71,7 +71,7 @@ class _PowerCharBlockState extends State<PowerCharBlock> { ...@@ -71,7 +71,7 @@ class _PowerCharBlockState extends State<PowerCharBlock> {
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,
(i) {
if (data[i] == 0) return BarChartGroupData(x: 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: 22.w, width: 16.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: Container(
margin: EdgeInsets.only(top: 20.h, right: 20.h),
padding: EdgeInsets.symmetric(horizontal: 2.w, vertical: 2.h),
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
width: 2.w,
color: const Color.fromRGBO(100, 116, 139, 1.0),
),
),
child: Icon( child: Icon(
Icons.close, Icons.close,
color: const Color.fromRGBO(100, 116, 139, 1.0), color: const Color.fromRGBO(100, 116, 139, 1.0),
size: 32.sp, 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,
decoration: BoxDecoration(
color: const Color.fromRGBO(66, 165, 245, 1.0),
borderRadius: BorderRadius.circular(12.r),
),
child: Center(
child: Text( child: Text(
'确认', '确认',
style: TextStyle( style: TextStyle(
fontSize: 28.sp, fontSize: 28.sp,
color: Colors.white, color: const Color.fromRGBO(73, 149, 234, 1.0),
), fontWeight: FontWeight.bold,
), ),
), ),
), ),
......
...@@ -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,7 +20,16 @@ class LoginCubit extends Cubit<LoginState> { ...@@ -17,7 +20,16 @@ class LoginCubit extends Cubit<LoginState> {
} }
void _listenAuthState() { void _listenAuthState() {
authBloc.stream.listen((authState) { _authSubscription = authBloc.stream.listen(
_onAuthStateChanged,
onError: _onAuthError,
);
}
void _onAuthStateChanged(AuthState authState) {
// 检查 Cubit 是否已关闭
if (isClosed) return;
if (authState is AuthFailure) { if (authState is AuthFailure) {
emit(state.copyWith(isLoading: false, error: authState.error)); emit(state.copyWith(isLoading: false, error: authState.error));
} else if (authState is AuthLoading) { } else if (authState is AuthLoading) {
...@@ -25,10 +37,15 @@ class LoginCubit extends Cubit<LoginState> { ...@@ -25,10 +37,15 @@ class LoginCubit extends Cubit<LoginState> {
} else if (authState is AuthSuccess) { } else if (authState is AuthSuccess) {
emit(state.copyWith(isLoading: false, error: null)); 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,7 +23,6 @@ class ReportRoomDetailView extends StatelessWidget { ...@@ -23,7 +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( return Scaffold(
backgroundColor: const Color.fromRGBO(242, 243, 245, 1), backgroundColor: const Color.fromRGBO(242, 243, 245, 1),
appBar: AppBar( appBar: AppBar(
...@@ -43,56 +42,23 @@ class ReportRoomDetailView extends StatelessWidget { ...@@ -43,56 +42,23 @@ class ReportRoomDetailView extends StatelessWidget {
), ),
centerTitle: true, centerTitle: true,
), ),
body: const Center(child: CircularProgressIndicator(color: Color.fromRGBO(66, 165, 245, 1.0))), body: _buildBody(context, state),
);
},
),
); );
} }
Widget _buildBody(BuildContext context, RoomReportState state) {
if (state.isLoading && state.floors.isEmpty) {
return const Center(child: CircularProgressIndicator(color: Color.fromRGBO(66, 165, 245, 1)));
}
if (state.error != null && state.floors.isEmpty) { if (state.error != null && state.floors.isEmpty) {
return Scaffold( return Center(child: Text('加载失败: ${state.error}'));
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 SingleChildScrollView(
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: SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
Padding( Padding(
...@@ -126,7 +92,7 @@ class ReportRoomDetailView extends StatelessWidget { ...@@ -126,7 +92,7 @@ class ReportRoomDetailView extends StatelessWidget {
if (state.isLoading) if (state.isLoading)
const Padding( const Padding(
padding: EdgeInsets.all(16.0), padding: EdgeInsets.all(16.0),
child: Center(child: CircularProgressIndicator(color: const Color.fromRGBO(66, 165, 245, 1.0))), child: Center(child: CircularProgressIndicator(color: Color.fromRGBO(66, 165, 245, 1.0))),
), ),
...state.detailRooms.map((room) { ...state.detailRooms.map((room) {
return Padding( return Padding(
...@@ -140,10 +106,7 @@ class ReportRoomDetailView extends StatelessWidget { ...@@ -140,10 +106,7 @@ class ReportRoomDetailView extends StatelessWidget {
), ),
], ],
), ),
),
);
},
),
); );
} }
} }
\ 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,17 +70,23 @@ class _RoomItem extends StatelessWidget { ...@@ -81,17 +70,23 @@ 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,
onTap: () {
},
child: Container(
padding: EdgeInsets.symmetric(vertical: 12.h),
decoration: BoxDecoration( decoration: BoxDecoration(
color: _bgColor, color: _bgColor,
borderRadius: BorderRadius.circular(16.r), borderRadius: BorderRadius.circular(16.r),
border: _borderColor != null // border: _borderColor != null
? Border.all(color: _borderColor!, width: 2.w) // ? Border.all(color: _borderColor!, width: 1.w)
: null, // : null,
), ),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
spacing: 4.h,
children: [ children: [
Text( Text(
room.roomNumber, room.roomNumber,
...@@ -103,23 +98,29 @@ class _RoomItem extends StatelessWidget { ...@@ -103,23 +98,29 @@ class _RoomItem extends StatelessWidget {
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
SizedBox(height: 4.h),
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
if (room.hasAlarmIcon) if (room.hasAlarmIcon)
Icon(Icons.warning_amber_outlined, color: _textColor, size: 24.sp), Icon(Icons.warning_amber_outlined, color: _textColor, size: 24.sp),
Text( Flexible(
fit: FlexFit.loose, //让文本只占实际需要的宽度
child: Text(
room.statusText, room.statusText,
style: TextStyle( style: TextStyle(
fontSize: 24.sp, fontSize: 24.sp,
color: _textColor, 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,7 +38,6 @@ class RoomManagementBlock extends StatelessWidget { ...@@ -39,7 +38,6 @@ 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(),
...@@ -53,14 +51,6 @@ class RoomManagementBlock extends StatelessWidget { ...@@ -53,14 +51,6 @@ class RoomManagementBlock extends StatelessWidget {
SizedBox(height: 10.h), SizedBox(height: 10.h),
], ],
_buildBottomButtons(state, cubit, context), _buildBottomButtons(state, cubit, context),
] else ...[
_buildLine(),
SizedBox(height: 24.h),
AreaDeviceList(
devices: state.areaDevices,
isLoading: state.isLoadingAreaDevices,
),
],
], ],
), ),
); );
...@@ -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)
borderRadius: BorderRadius.circular(16.r), ? const Color.fromRGBO(229, 241, 255, 1)
border: Border.all(
color: (onPowerOff != null && !isProcessing)
? const Color.fromRGBO(73, 149, 234, 1)
: const Color.fromRGBO(204, 204, 204, 1), : const Color.fromRGBO(204, 204, 204, 1),
width: 1.w, borderRadius: BorderRadius.circular(16.r),
),
), ),
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,28 +68,6 @@ class RoomBottomButtons extends StatelessWidget { ...@@ -74,28 +68,6 @@ 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,
// ),
// ),
// ),
// ),
// ),
// ),
], ],
); );
} }
......
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