Commit 3c72b94e authored by 张宏's avatar 张宏

parent 7e849e9f
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}
settings.ext.flutterSdkPath = flutterSdkPath()
includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
plugins {
id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false
}
}
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.7.2" apply false
}
include ":app"
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:smart_hotel_app/views/home/abnormal_list/cubit/abnormal_list_state.dart';
class AbnormalListCubit extends Cubit<AbnormalListState> {
AbnormalListCubit() : super(AbnormalListState(currentFilter: AlarmStatus.all, alarmList: _getAlarmList()));
static List<AlarmItem> _getAlarmList() {
return [
const AlarmItem(
id: '1',
title: '温度异常告警',
deviceInfo: 'A-301智能空开·客房301',
alarmTime: '2026-5-30 16:32:31',
processTime: '2026-5-30 16:32:31',
status: AlarmStatus.pending,
level: '高警',
urgency: '紧急',
),
const AlarmItem(
id: '2',
title: '温度异常告警',
deviceInfo: 'A-301智能空开·客房301',
alarmTime: '2026-5-30 16:32:31',
processTime: '2026-5-30 16:32:31',
status: AlarmStatus.processed,
level: '高警',
urgency: '紧急',
),
const AlarmItem(
id: '3',
title: '温度异常告警',
deviceInfo: 'A-301智能空开·客房301',
alarmTime: '2026-5-30 16:32:31',
processTime: '2026-5-30 16:32:31',
status: AlarmStatus.falseAlarm,
level: '高警',
urgency: '紧急',
),
];
}
void setFilter(AlarmStatus filter) {
emit(state.copyWith(currentFilter: filter));
}
void onAlarmItemTap(AlarmItem item) {
print('点击告警: ${item.title}');
}
}
enum AlarmStatus { all, pending, processed, falseAlarm }
class AlarmItem {
final String id;
final String title;
final String deviceInfo;
final String alarmTime;
final String processTime;
final AlarmStatus status;
final String level;
final String urgency;
const AlarmItem({
required this.id,
required this.title,
required this.deviceInfo,
required this.alarmTime,
required this.processTime,
required this.status,
required this.level,
required this.urgency,
});
}
class AbnormalListState {
final AlarmStatus currentFilter;
final List<AlarmItem> alarmList;
const AbnormalListState({
required this.currentFilter,
required this.alarmList,
});
AbnormalListState copyWith({
AlarmStatus? currentFilter,
List<AlarmItem>? alarmList,
}) {
return AbnormalListState(
currentFilter: currentFilter ?? this.currentFilter,
alarmList: alarmList ?? this.alarmList,
);
}
List<AlarmItem> get filteredList {
if (currentFilter == AlarmStatus.all) {
return alarmList;
}
return alarmList.where((item) => item.status == currentFilter).toList();
}
}
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/home/abnormal_list/cubit/abnormal_list_cubit.dart';
import 'package:smart_hotel_app/views/home/abnormal_list/cubit/abnormal_list_state.dart';
import 'package:smart_hotel_app/views/home/abnormal_list/widget/alarm_list_item.dart';
import 'package:smart_hotel_app/views/home/abnormal_list/widget/filter_tab_block.dart';
@RoutePage()
class AbnormalListView extends StatelessWidget {
const AbnormalListView({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => AbnormalListCubit(),
child: 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.popRoute();
},
),
title: Text(
'告警信息',
style: TextStyle(
color: const Color.fromRGBO(10, 13, 20, 1),
fontSize: 32.sp,
fontWeight: FontWeight.w200,
),
),
centerTitle: true,
),
body: BlocBuilder<AbnormalListCubit, AbnormalListState>(
builder: (context, state) {
final cubit = context.read<AbnormalListCubit>();
return SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 28.w),
child: Column(
children: [
FilterTabBlock(
currentFilter: state.currentFilter,
onFilterChanged: cubit.setFilter,
),
SizedBox(height: 10.h),
...state.filteredList.map((item) {
return AlarmListItem(
item: item,
onTap: cubit.onAlarmItemTap,
);
}).toList(),
SizedBox(height: 30.h),
],
),
);
},
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/home/abnormal_list/cubit/abnormal_list_state.dart';
class AlarmListItem extends StatelessWidget {
final AlarmItem item;
final Function(AlarmItem) onTap;
const AlarmListItem({
super.key,
required this.item,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => onTap(item),
child: Container(
width: double.infinity,
margin: EdgeInsets.only(top: 20.h),
padding: EdgeInsets.all(25.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25.r),
color: Colors.white,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildIcon(),
SizedBox(width: 20.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildTitleRow(),
SizedBox(height: 15.h),
Text(
item.deviceInfo,
style: TextStyle(
fontSize: 24.sp,
color: const Color.fromRGBO(100, 116, 139, 1),
),
),
SizedBox(height: 10.h),
Text(
'告警时间: ${item.alarmTime}',
style: TextStyle(
fontSize: 24.sp,
color: const Color.fromRGBO(100, 116, 139, 1),
),
),
SizedBox(height: 10.h),
Text(
'处理时间: ${item.processTime}',
style: TextStyle(
fontSize: 24.sp,
color: const Color.fromRGBO(100, 116, 139, 1),
),
),
],
),
),
],
),
),
);
}
Widget _buildIcon() {
return Container(
width: 96.w,
height: 96.w,
decoration: BoxDecoration(
color: const Color.fromRGBO(237, 245, 255, 1),
borderRadius: BorderRadius.circular(16.r),
),
child: Icon(
Icons.thermostat,
size: 48.w,
color: const Color.fromRGBO(73, 149, 234, 1),
),
);
}
Widget _buildTitleRow() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
item.title,
style: TextStyle(
fontSize: 28.sp,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
Row(
children: [
_buildStatusBadge(),
SizedBox(width: 12.w),
_buildLevelBadge(),
SizedBox(width: 8.w),
_buildUrgencyBadge(),
],
),
],
);
}
Widget _buildStatusBadge() {
Color bgColor;
Color textColor;
String text;
IconData? icon;
switch (item.status) {
case AlarmStatus.pending:
bgColor = const Color.fromRGBO(250, 204, 21, 0.15);
textColor = const Color.fromRGBO(250, 204, 21, 1);
text = '待处理';
icon = Icons.error_outline;
break;
case AlarmStatus.processed:
bgColor = const Color.fromRGBO(84, 214, 120, 0.15);
textColor = const Color.fromRGBO(84, 214, 120, 1);
text = '已处理';
icon = Icons.check_circle_outline;
break;
case AlarmStatus.falseAlarm:
bgColor = const Color.fromRGBO(255, 100, 101, 0.15);
textColor = const Color.fromRGBO(255, 100, 101, 1);
text = '误报';
icon = Icons.cancel_outlined;
break;
default:
bgColor = const Color.fromRGBO(100, 116, 139, 0.15);
textColor = const Color.fromRGBO(100, 116, 139, 1);
text = '';
}
return Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
decoration: BoxDecoration(
color: bgColor,
borderRadius: BorderRadius.circular(12.r),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (icon != null) ...[
Icon(icon, size: 20.w, color: textColor),
SizedBox(width: 6.w),
],
Text(
text,
style: TextStyle(
fontSize: 22.sp,
color: textColor,
),
),
],
),
);
}
Widget _buildLevelBadge() {
return Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
decoration: BoxDecoration(
color: const Color.fromRGBO(255, 100, 101, 0.15),
borderRadius: BorderRadius.circular(12.r),
),
child: Text(
item.level,
style: TextStyle(
fontSize: 22.sp,
color: const Color.fromRGBO(255, 100, 101, 1),
),
),
);
}
Widget _buildUrgencyBadge() {
return Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
decoration: BoxDecoration(
color: const Color.fromRGBO(255, 100, 101, 0.15),
borderRadius: BorderRadius.circular(12.r),
),
child: Text(
item.urgency,
style: TextStyle(
fontSize: 22.sp,
color: const Color.fromRGBO(255, 100, 101, 1),
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/home/abnormal_list/cubit/abnormal_list_state.dart';
class FilterTabBlock extends StatelessWidget {
final AlarmStatus currentFilter;
final Function(AlarmStatus) onFilterChanged;
const FilterTabBlock({
super.key,
required this.currentFilter,
required this.onFilterChanged,
});
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
margin: EdgeInsets.only(top: 10.h),
child: Row(
children: [
_buildTab('全部', AlarmStatus.all),
SizedBox(width: 20.w),
_buildTab('待处理', AlarmStatus.pending),
SizedBox(width: 20.w),
_buildTab('已处理', AlarmStatus.processed),
SizedBox(width: 20.w),
_buildTab('误报', AlarmStatus.falseAlarm),
],
),
);
}
Widget _buildTab(String label, AlarmStatus status) {
final isSelected = currentFilter == status;
return Expanded(
child: GestureDetector(
onTap: () => onFilterChanged(status),
child: Container(
height: 72.h,
decoration: BoxDecoration(
color: isSelected ? const Color.fromRGBO(73, 149, 234, 1) : const Color.fromRGBO(100, 116, 139, 0.1),
borderRadius: BorderRadius.circular(16.r),
),
alignment: Alignment.center,
child: Text(
label,
style: TextStyle(
fontSize: 26.sp,
color: isSelected ? Colors.white : const Color.fromRGBO(100, 116, 139, 1),
),
),
),
),
);
}
}
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:smart_hotel_app/views/report/device/cubit/device_list_state.dart';
class DeviceListCubit extends Cubit<DeviceListState> {
DeviceListCubit() : super(const DeviceListState()) {
_initData();
}
void _initData() {
final devices = [
const DeviceInfo(
id: '1',
name: '智能空开 1F-01',
room: '101',
type: DeviceTypeEnum.smartBreaker,
online: true,
status: '正常',
lastUpdate: '10:30',
),
const DeviceInfo(
id: '2',
name: '客控设备 1F-01',
room: '101',
type: DeviceTypeEnum.guestControl,
online: true,
status: '正常',
lastUpdate: '10:32',
),
const DeviceInfo(
id: '3',
name: '网络设备 1F',
room: '一楼',
type: DeviceTypeEnum.network,
online: true,
status: '正常',
lastUpdate: '10:28',
),
const DeviceInfo(
id: '4',
name: '智能空开 2F-01',
room: '201',
type: DeviceTypeEnum.smartBreaker,
online: true,
status: '正常',
lastUpdate: '10:25',
),
const DeviceInfo(
id: '5',
name: '客控设备 2F-01',
room: '201',
type: DeviceTypeEnum.guestControl,
online: false,
status: '离线',
lastUpdate: '09:15',
),
const DeviceInfo(
id: '6',
name: '智能空开 3F-01',
room: '301',
type: DeviceTypeEnum.smartBreaker,
online: true,
status: '正常',
lastUpdate: '10:30',
),
const DeviceInfo(
id: '7',
name: '客控设备 3F-01',
room: '301',
type: DeviceTypeEnum.guestControl,
online: true,
status: '正常',
lastUpdate: '10:30',
),
const DeviceInfo(
id: '8',
name: '网络设备 2F',
room: '二楼',
type: DeviceTypeEnum.network,
online: true,
status: '正常',
lastUpdate: '10:30',
),
];
emit(state.copyWith(devices: devices));
}
void selectType(DeviceTypeEnum type) {
emit(state.copyWith(selectedType: type));
}
}
import 'package:equatable/equatable.dart';
enum DeviceTypeEnum {
all,
smartBreaker,
guestControl,
network,
}
class DeviceInfo {
final String id;
final String name;
final String room;
final DeviceTypeEnum type;
final bool online;
final String status;
final String lastUpdate;
const DeviceInfo({
required this.id,
required this.name,
required this.room,
required this.type,
required this.online,
required this.status,
required this.lastUpdate,
});
DeviceInfo copyWith({
String? id,
String? name,
String? room,
DeviceTypeEnum? type,
bool? online,
String? status,
String? lastUpdate,
}) {
return DeviceInfo(
id: id ?? this.id,
name: name ?? this.name,
room: room ?? this.room,
type: type ?? this.type,
online: online ?? this.online,
status: status ?? this.status,
lastUpdate: lastUpdate ?? this.lastUpdate,
);
}
}
class DeviceListState extends Equatable {
final List<DeviceInfo> devices;
final DeviceTypeEnum selectedType;
const DeviceListState({
this.devices = const [],
this.selectedType = DeviceTypeEnum.all,
});
DeviceListState copyWith({
List<DeviceInfo>? devices,
DeviceTypeEnum? selectedType,
}) {
return DeviceListState(
devices: devices ?? this.devices,
selectedType: selectedType ?? this.selectedType,
);
}
List<DeviceInfo> get filteredDevices {
if (selectedType == DeviceTypeEnum.all) {
return devices;
}
return devices.where((device) => device.type == selectedType).toList();
}
@override
List<Object?> get props => [devices, selectedType];
}
...@@ -2,45 +2,69 @@ import 'package:auto_route/auto_route.dart'; ...@@ -2,45 +2,69 @@ import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/report/room/cubit/room_report_cubit.dart'; import 'package:smart_hotel_app/views/report/device/cubit/device_list_cubit.dart';
import 'package:smart_hotel_app/views/report/device/cubit/device_list_state.dart';
import 'package:smart_hotel_app/views/report/device/widget/device_card.dart';
import 'package:smart_hotel_app/views/report/device/widget/device_filter_tab.dart';
@RoutePage() @RoutePage()
class ReportRoomDetailView extends StatelessWidget { class ReportDeviceListView extends StatelessWidget {
const ReportRoomDetailView({super.key}); const ReportDeviceListView({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (_) => RoomReportCubit(), create: (_) => DeviceListCubit(),
child: Scaffold( child: BlocBuilder<DeviceListCubit, DeviceListState>(
backgroundColor: const Color.fromRGBO(15, 23, 42, 1), builder: (context, state) {
appBar: AppBar( return Scaffold(
backgroundColor: const Color.fromRGBO(15, 23, 42, 1), backgroundColor: const Color.fromRGBO(242, 243, 245, 1),
elevation: 0, appBar: AppBar(
leading: IconButton( backgroundColor: Colors.white,
icon: const Icon(Icons.arrow_back, color: Colors.white), elevation: 0,
onPressed: () { leading: IconButton(
context.popRoute(); icon: const Icon(Icons.arrow_back, color: Colors.black),
}, onPressed: () {
), context.popRoute();
title: Text( },
'能耗分析', ),
style: TextStyle( title: Text(
color: Colors.white, '全部设备',
fontSize: 18.sp, style: TextStyle(
fontWeight: FontWeight.bold, color: Colors.black,
fontSize: 18.sp,
fontWeight: FontWeight.bold,
),
),
centerTitle: true,
), ),
), body: SingleChildScrollView(
centerTitle: true, child: Padding(
), padding: EdgeInsets.symmetric(horizontal: 28.w),
body: SingleChildScrollView( child: Column(
child: Column( children: [
children: [ SizedBox(height: 10.h),
SizedBox(height: 10.h), DeviceFilterTab(
], selectedType: state.selectedType,
), onSelect: (type) {
), context.read<DeviceListCubit>().selectType(type);
},
),
SizedBox(height: 10.h),
...state.filteredDevices.map((device) {
return Padding(
padding: EdgeInsets.only(bottom: 10.h),
child: DeviceCard(device: device),
);
}).toList(),
SizedBox(height: 10.h),
],
),
),
),
);
},
), ),
); );
} }
} }
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/report/device/cubit/device_list_state.dart';
class DeviceCard extends StatelessWidget {
final DeviceInfo device;
const DeviceCard({
super.key,
required this.device,
});
IconData _getIconForType(DeviceTypeEnum type) {
switch (type) {
case DeviceTypeEnum.smartBreaker:
return Icons.power;
case DeviceTypeEnum.guestControl:
return Icons.settings_remote;
case DeviceTypeEnum.network:
return Icons.wifi;
default:
return Icons.devices;
}
}
Color _getIconColor(DeviceTypeEnum type) {
switch (type) {
case DeviceTypeEnum.smartBreaker:
return const Color.fromRGBO(245, 158, 11, 1);
case DeviceTypeEnum.guestControl:
return const Color.fromRGBO(59, 130, 246, 1);
case DeviceTypeEnum.network:
return const Color.fromRGBO(16, 185, 129, 1);
default:
return const Color.fromRGBO(107, 114, 128, 1);
}
}
String _getTypeLabel(DeviceTypeEnum type) {
switch (type) {
case DeviceTypeEnum.smartBreaker:
return '智能空开';
case DeviceTypeEnum.guestControl:
return '客控设备';
case DeviceTypeEnum.network:
return '网络设备';
default:
return '其他';
}
}
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: EdgeInsets.all(28.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.r),
color: Colors.white,
),
child: Row(
children: [
Container(
width: 80.w,
height: 80.h,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16.r),
color: _getIconColor(device.type).withOpacity(0.1),
),
child: Icon(
_getIconForType(device.type),
color: _getIconColor(device.type),
size: 36.sp,
),
),
SizedBox(width: 20.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
device.name,
style: TextStyle(
fontSize: 26.sp,
fontWeight: FontWeight.bold,
color: const Color.fromRGBO(31, 41, 55, 1),
),
),
SizedBox(height: 8.h),
Row(
children: [
Text(
_getTypeLabel(device.type),
style: TextStyle(
fontSize: 20.sp,
color: const Color.fromRGBO(107, 114, 128, 1),
),
),
SizedBox(width: 16.w),
Text(
'房间 ${device.room}',
style: TextStyle(
fontSize: 20.sp,
color: const Color.fromRGBO(107, 114, 128, 1),
),
),
],
),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.r),
color: device.online
? const Color.fromRGBO(220, 252, 231, 1)
: const Color.fromRGBO(254, 226, 226, 1),
),
child: Text(
device.status,
style: TextStyle(
fontSize: 20.sp,
color: device.online
? const Color.fromRGBO(21, 128, 61, 1)
: const Color.fromRGBO(220, 38, 38, 1),
fontWeight: FontWeight.bold,
),
),
),
SizedBox(height: 8.h),
Text(
device.lastUpdate,
style: TextStyle(
fontSize: 18.sp,
color: const Color.fromRGBO(156, 163, 175, 1),
),
),
],
),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/report/device/cubit/device_list_state.dart';
class DeviceFilterTab extends StatelessWidget {
final DeviceTypeEnum selectedType;
final Function(DeviceTypeEnum) onSelect;
const DeviceFilterTab({
super.key,
required this.selectedType,
required this.onSelect,
});
@override
Widget build(BuildContext context) {
final tabs = [
{'type': DeviceTypeEnum.all, 'label': '全部'},
{'type': DeviceTypeEnum.smartBreaker, 'label': '空开'},
{'type': DeviceTypeEnum.guestControl, 'label': '客控'},
{'type': DeviceTypeEnum.network, 'label': '网络'},
];
return Container(
width: double.infinity,
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.r),
color: Colors.white,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: tabs.map((tab) {
final isSelected = selectedType == tab['type'];
return GestureDetector(
onTap: () => onSelect(tab['type'] as DeviceTypeEnum),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 12.h),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16.r),
color: isSelected
? const Color.fromRGBO(59, 130, 246, 1)
: const Color.fromRGBO(243, 244, 246, 1),
),
child: Text(
tab['label'] as String,
style: TextStyle(
fontSize: 24.sp,
color: isSelected ? Colors.white : const Color.fromRGBO(107, 114, 128, 1),
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
),
),
),
);
}).toList(),
),
);
}
}
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/report/room/cubit/room_report_cubit.dart';
import 'package:smart_hotel_app/views/report/room/cubit/room_report_state.dart';
import 'package:smart_hotel_app/views/report/room/widget/floor_selector.dart';
import 'package:smart_hotel_app/views/report/room/widget/room_grid.dart';
import 'package:smart_hotel_app/views/report/room/widget/status_stats.dart';
import 'package:smart_hotel_app/views/report/room/widget/room_detail_card.dart';
@RoutePage()
class ReportRoomDetailView extends StatelessWidget {
const ReportRoomDetailView({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => RoomReportCubit(),
child: BlocBuilder<RoomReportCubit, RoomReportState>(
builder: (context, state) {
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, color: Colors.black),
onPressed: () {
context.popRoute();
},
),
title: Text(
'客房详情',
style: TextStyle(
color: Colors.black,
fontSize: 32.sp,
fontWeight: FontWeight.bold,
),
),
centerTitle: true,
),
body: SingleChildScrollView(
child: Column(
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 28.w),
child: Column(
children: [
SizedBox(height: 10.h),
if (state.floors.isNotEmpty)
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(24.r)),
),
child: FloorSelector(
floorNames: state.floors.map((f) => f.floorName).toList(),
selectedIndex: state.selectedFloorIndex,
),
),
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.floors[state.selectedFloorIndex].rooms),
),
SizedBox(height: 10.h),
if (state.statusStats.isNotEmpty)
StatusStats(stats: state.statusStats),
SizedBox(height: 10.h),
...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
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/report/room/cubit/room_report_cubit.dart';
class FloorSelector extends StatelessWidget {
final List<String> floorNames;
final int selectedIndex;
const FloorSelector({
super.key,
required this.floorNames,
required this.selectedIndex,
});
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.symmetric(horizontal: 28.w, vertical: 16.h),
child: Row(
children: floorNames.asMap().entries.map((entry) {
final index = entry.key;
final name = entry.value;
final isSelected = index == selectedIndex;
return Padding(
padding: EdgeInsets.only(right: 32.w),
child: GestureDetector(
onTap: () {
context.read<RoomReportCubit>().selectFloor(index);
},
child: Column(
children: [
Text(
name,
style: TextStyle(
fontSize: 28.sp,
color: isSelected
? const Color.fromRGBO(59, 130, 246, 1)
: const Color.fromRGBO(156, 163, 175, 1),
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
),
),
SizedBox(height: 4.h),
Container(
width: 40.w,
height: 3.h,
decoration: BoxDecoration(
color: isSelected
? const Color.fromRGBO(59, 130, 246, 1)
: Colors.transparent,
borderRadius: BorderRadius.circular(2.r),
),
),
],
),
),
);
}).toList(),
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/report/room/cubit/room_report_state.dart';
class RoomDetailCard extends StatelessWidget {
final RoomInfo room;
const RoomDetailCard({super.key, required this.room});
@override
Widget build(BuildContext context) {
return 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: [
Row(
children: [
Container(
width: 60.w,
height: 60.h,
decoration: BoxDecoration(
color: const Color.fromRGBO(219, 234, 254, 1),
borderRadius: BorderRadius.circular(12.r),
),
child: Icon(
Icons.meeting_room_outlined,
color: const Color.fromRGBO(59, 130, 246, 1),
size: 32.sp,
),
),
SizedBox(width: 16.w),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
room.roomNumber,
style: TextStyle(
fontSize: 36.sp,
fontWeight: FontWeight.bold,
color: const Color.fromRGBO(31, 41, 55, 1),
),
),
SizedBox(width: 16.w),
Text(
'入住中',
style: TextStyle(
fontSize: 28.sp,
color: const Color.fromRGBO(59, 130, 246, 1),
),
),
],
),
SizedBox(height: 12.h),
Row(
children: [
_StatusTag(
label: '有人',
isActive: room.deviceStatus.hasPerson,
),
SizedBox(width: 12.w),
_StatusTag(
label: '通电中',
isActive: room.deviceStatus.powerOn,
),
SizedBox(width: 12.w),
_StatusTag(
label: 'wifi正常',
isActive: room.deviceStatus.wifiNormal,
),
],
),
],
),
],
),
SizedBox(height: 24.h),
Divider(
height: 1.h,
color: const Color.fromRGBO(229, 231, 235, 1),
),
SizedBox(height: 24.h),
GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 2,
mainAxisSpacing: 16.h,
crossAxisSpacing: 16.w,
children: [
_DeviceControl(
icon: Icons.air_outlined,
label: '空调',
value: '${room.deviceStatus.acTemperature}°C·${room.deviceStatus.acOn ? '开' : '关'}',
isOn: room.deviceStatus.acOn,
),
_DeviceControl(
icon: Icons.light_outlined,
label: '照明',
value: room.deviceStatus.lightOn ? '开' : '关',
isOn: room.deviceStatus.lightOn,
),
_DeviceControl(
icon: Icons.tv_outlined,
label: '电视',
value: room.deviceStatus.tvOn ? '开' : '关',
isOn: room.deviceStatus.tvOn,
),
_DeviceControl(
icon: Icons.curtains_outlined,
label: '窗帘',
value: room.deviceStatus.curtainOn ? '开' : '关',
isOn: room.deviceStatus.curtainOn,
),
],
),
],
),
);
}
}
class _StatusTag extends StatelessWidget {
final String label;
final bool isActive;
const _StatusTag({required this.label, required this.isActive});
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h),
decoration: BoxDecoration(
color: isActive
? const Color.fromRGBO(167, 243, 208, 1)
: const Color.fromRGBO(229, 231, 235, 1),
borderRadius: BorderRadius.circular(12.r),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (isActive)
Icon(
Icons.check,
size: 20.sp,
color: const Color.fromRGBO(16, 185, 129, 1),
),
if (isActive)
SizedBox(width: 4.w),
Text(
label,
style: TextStyle(
fontSize: 22.sp,
color: isActive
? const Color.fromRGBO(16, 185, 129, 1)
: const Color.fromRGBO(156, 163, 175, 1),
),
),
],
),
);
}
}
class _DeviceControl extends StatelessWidget {
final IconData icon;
final String label;
final String value;
final bool isOn;
const _DeviceControl({
required this.icon,
required this.label,
required this.value,
required this.isOn,
});
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 24.h),
decoration: BoxDecoration(
color: const Color.fromRGBO(239, 246, 255, 1),
borderRadius: BorderRadius.circular(24.r),
),
child: Row(
children: [
Icon(
icon,
color: const Color.fromRGBO(59, 130, 246, 1),
size: 40.sp,
),
SizedBox(width: 16.w),
Text(
label,
style: TextStyle(
fontSize: 28.sp,
color: const Color.fromRGBO(59, 130, 246, 1),
fontWeight: FontWeight.w500,
),
),
const Spacer(),
Text(
value,
style: TextStyle(
fontSize: 26.sp,
color: isOn
? const Color.fromRGBO(16, 185, 129, 1)
: const Color.fromRGBO(156, 163, 175, 1),
),
),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/report/room/cubit/room_report_state.dart';
class RoomGrid extends StatelessWidget {
final List<RoomInfo> rooms;
const RoomGrid({super.key, required this.rooms});
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: EdgeInsets.symmetric(horizontal: 28.w),
child: GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 5,
mainAxisSpacing: 20.h,
crossAxisSpacing: 16.w,
children: rooms.map((room) => RoomItem(room: room)).toList(),
),
);
}
}
class RoomItem extends StatelessWidget {
final RoomInfo room;
const RoomItem({super.key, required this.room});
Color get _bgColor {
switch (room.status) {
case RoomStatus.vacant:
return const Color.fromRGBO(229, 231, 235, 1);
case RoomStatus.occupied:
case RoomStatus.reserved:
return const Color.fromRGBO(219, 234, 254, 1);
case RoomStatus.warning:
case RoomStatus.temperature:
return const Color.fromRGBO(254, 243, 199, 1);
}
}
Color get _textColor {
switch (room.status) {
case RoomStatus.vacant:
return const Color.fromRGBO(156, 163, 175, 1);
case RoomStatus.occupied:
case RoomStatus.reserved:
return const Color.fromRGBO(59, 130, 246, 1);
case RoomStatus.warning:
case RoomStatus.temperature:
return const Color.fromRGBO(245, 158, 11, 1);
}
}
Color? get _borderColor {
switch (room.status) {
case RoomStatus.vacant:
return null;
case RoomStatus.occupied:
case RoomStatus.reserved:
return const Color.fromRGBO(59, 130, 246, 1);
case RoomStatus.warning:
case RoomStatus.temperature:
return const Color.fromRGBO(245, 158, 11, 1);
}
}
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(vertical: 12.h, horizontal: 8.w),
decoration: BoxDecoration(
color: _bgColor,
borderRadius: BorderRadius.circular(16.r),
border: _borderColor != null
? Border.all(color: _borderColor!, width: 2.w)
: null,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
room.roomNumber,
style: TextStyle(
fontSize: 28.sp,
fontWeight: FontWeight.bold,
color: _textColor,
),
),
SizedBox(height: 4.h),
if (room.status == RoomStatus.warning || room.status == RoomStatus.temperature)
Icon(Icons.warning_amber_outlined, color: _textColor, size: 20.sp),
if (room.status != RoomStatus.warning && room.status != RoomStatus.temperature)
Text(
room.statusText,
style: TextStyle(
fontSize: 20.sp,
color: _textColor,
),
),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/report/room/cubit/room_report_state.dart';
class StatusStats extends StatelessWidget {
final List<StatusStat> stats;
const StatusStats({super.key, required this.stats});
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: EdgeInsets.all(28.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24.r),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: stats.map((stat) => StatItem(stat: stat)).toList(),
),
);
}
}
class StatItem extends StatelessWidget {
final StatusStat stat;
const StatItem({super.key, required this.stat});
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(
stat.count.toString(),
style: TextStyle(
fontSize: 40.sp,
fontWeight: FontWeight.bold,
color: stat.color,
),
),
SizedBox(height: 4.h),
Text(
stat.label,
style: TextStyle(
fontSize: 24.sp,
color: const Color.fromRGBO(156, 163, 175, 1),
),
),
],
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:smart_hotel_app/views/report/rule/cubit/rule_management_state.dart';
class RuleManagementCubit extends Cubit<RuleManagementState> {
RuleManagementCubit() : super(const RuleManagementState()) {
_initData();
}
void _initData() {
final rules = [
const RuleInfo(
id: '1',
name: '退房自动断电',
description: '酒管系统—酒度状态',
enabled: true,
triggerCondition: '酒管系统—酒度状态',
action: '断电',
icon: Icons.logout_outlined,
),
const RuleInfo(
id: '2',
name: '清洁中请自动开灯',
description: '服务彩忧消洁中调',
enabled: true,
triggerCondition: '服务彩忧消洁中调',
action: '开灯',
icon: Icons.cleaning_services_outlined,
),
const RuleInfo(
id: '3',
name: '温度过高自动保护',
description: '温度·80°C—断电+告管',
enabled: true,
triggerCondition: '温度·80°C',
action: '断电+告管',
icon: Icons.thermostat_outlined,
),
const RuleInfo(
id: '4',
name: '深夜低功事休眠',
description: '23:00-06:00 平&lt;50W',
enabled: false,
triggerCondition: '23:00-06:00 平&lt;50W',
action: '休眠',
icon: Icons.nightlight_outlined,
),
const RuleInfo(
id: '5',
name: '定时全楼巡检提醒',
description: '国日 09:00/15.00',
enabled: true,
triggerCondition: '国日 09:00/15.00',
action: '提醒',
icon: Icons.access_time_outlined,
),
];
emit(state.copyWith(rules: rules));
}
void toggleRule(String id) {
final updatedRules = state.rules.map((rule) {
if (rule.id == id) {
return rule.copyWith(enabled: !rule.enabled);
}
return rule;
}).toList();
emit(state.copyWith(rules: updatedRules));
}
}
import 'package:flutter/material.dart';
import 'package:equatable/equatable.dart';
class RuleInfo {
final String id;
final String name;
final String description;
final bool enabled;
final String triggerCondition;
final String action;
final IconData icon;
const RuleInfo({
required this.id,
required this.name,
required this.description,
required this.enabled,
required this.triggerCondition,
required this.action,
required this.icon,
});
RuleInfo copyWith({
String? id,
String? name,
String? description,
bool? enabled,
String? triggerCondition,
String? action,
IconData? icon,
}) {
return RuleInfo(
id: id ?? this.id,
name: name ?? this.name,
description: description ?? this.description,
enabled: enabled ?? this.enabled,
triggerCondition: triggerCondition ?? this.triggerCondition,
action: action ?? this.action,
icon: icon ?? this.icon,
);
}
}
class RuleManagementState extends Equatable {
final List<RuleInfo> rules;
const RuleManagementState({
this.rules = const [],
});
RuleManagementState copyWith({
List<RuleInfo>? rules,
}) {
return RuleManagementState(
rules: rules ?? this.rules,
);
}
@override
List<Object?> get props => [rules];
}
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/report/rule/cubit/rule_management_cubit.dart';
import 'package:smart_hotel_app/views/report/rule/cubit/rule_management_state.dart';
import 'package:smart_hotel_app/views/report/rule/widget/rule_card.dart';
@RoutePage()
class ReportRuleManagementView extends StatelessWidget {
const ReportRuleManagementView({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => RuleManagementCubit(),
child: BlocBuilder<RuleManagementCubit, RuleManagementState>(
builder: (context, state) {
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, color: Colors.black),
onPressed: () {
context.popRoute();
},
),
title: Text(
'联动规则管理',
style: TextStyle(
color: Colors.black,
fontSize: 32.sp,
fontWeight: FontWeight.bold,
),
),
centerTitle: true,
),
body: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 28.w, vertical: 20.h),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24.r),
),
child: ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: state.rules.length,
separatorBuilder: (context, index) => Divider(
height: 1.h,
color: const Color.fromRGBO(229, 231, 235, 1),
indent: 28.w,
endIndent: 28.w,
),
itemBuilder: (context, index) {
return RuleCard(
rule: state.rules[index],
onToggle: (id) {
context.read<RuleManagementCubit>().toggleRule(id);
},
);
},
),
),
),
),
);
},
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_switch/flutter_switch.dart';
import 'package:smart_hotel_app/views/report/rule/cubit/rule_management_state.dart';
class RuleCard extends StatelessWidget {
final RuleInfo rule;
final Function(String) onToggle;
const RuleCard({
super.key,
required this.rule,
required this.onToggle,
});
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(vertical: 24.h, horizontal: 28.w),
decoration: BoxDecoration(
color: Colors.white,
),
child: Column(
children: [
Row(
children: [
Container(
width: 72.w,
height: 72.h,
decoration: BoxDecoration(
color: const Color.fromRGBO(219, 234, 254, 1),
borderRadius: BorderRadius.circular(12.r),
),
child: Icon(
rule.icon,
size: 36.sp,
color: const Color.fromRGBO(59, 130, 246, 1),
),
),
SizedBox(width: 20.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
rule.name,
style: TextStyle(
fontSize: 32.sp,
fontWeight: FontWeight.bold,
color: const Color.fromRGBO(31, 41, 55, 1),
),
),
SizedBox(height: 8.h),
Text(
rule.description,
style: TextStyle(
fontSize: 26.sp,
color: const Color.fromRGBO(156, 163, 175, 1),
),
),
],
),
),
FlutterSwitch(
width: 88.w,
height: 48.h,
valueFontSize: 20.sp,
toggleSize: 40.h,
value: rule.enabled,
borderRadius: 24.r,
padding: 4.w,
activeColor: const Color.fromRGBO(59, 130, 246, 1),
inactiveColor: const Color.fromRGBO(209, 213, 219, 1),
onToggle: (_) => onToggle(rule.id),
),
],
),
],
),
);
}
}
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