Commit a8c199a0 authored by 张宏's avatar 张宏

111

parent 93ea6fa5
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
// ============================================================
// 客房状态总览-客房概览统计 BO 模型
// ============================================================
/// 概览统计(顶部统计区域)
class RoomOverviewSummaryBO extends Equatable {
final int occupiedCount;
final int vacantCount;
final int reservedCount;
final int alarmCount;
const RoomOverviewSummaryBO({
this.occupiedCount = 0,
this.vacantCount = 0,
this.reservedCount = 0,
this.alarmCount = 0,
});
factory RoomOverviewSummaryBO.fromJson(Map<String, dynamic> json) {
return RoomOverviewSummaryBO(
occupiedCount: json['occupiedCount'] as int? ?? 0,
vacantCount: json['vacantCount'] as int? ?? 0,
reservedCount: json['reservedCount'] as int? ?? 0,
alarmCount: json['alarmCount'] as int? ?? 0,
);
}
@override
List<Object?> get props => [occupiedCount, vacantCount, reservedCount, alarmCount];
}
/// 楼层Tab列表
class RoomFloorBO extends Equatable {
final int floorId;
final String floorName;
final bool isActive;
const RoomFloorBO({
this.floorId = 0,
this.floorName = '',
this.isActive = false,
});
factory RoomFloorBO.fromJson(Map<String, dynamic> json) {
return RoomFloorBO(
floorId: json['floorId'] as int? ?? 0,
floorName: json['floorName'] as String? ?? '',
isActive: json['isActive'] as bool? ?? false,
);
}
@override
List<Object?> get props => [floorId, floorName, isActive];
}
/// 房间网格(房间卡片网格布局)
class RoomGridBO extends Equatable {
final int roomId;
final String roomNumber;
final String statusText;
final String statusColor;
final bool hasAlarmIcon;
const RoomGridBO({
this.roomId = 0,
this.roomNumber = '',
this.statusText = '',
this.statusColor = '',
this.hasAlarmIcon = false,
});
factory RoomGridBO.fromJson(Map<String, dynamic> json) {
return RoomGridBO(
roomId: json['roomId'] as int? ?? 0,
roomNumber: json['roomNumber'] as String? ?? '',
statusText: json['statusText'] as String? ?? '',
statusColor: json['statusColor'] as String? ?? '',
hasAlarmIcon: json['hasAlarmIcon'] as bool? ?? false,
);
}
@override
List<Object?> get props => [roomId, roomNumber, statusText, statusColor, hasAlarmIcon];
}
/// 房间基础信息(客房概览统计)
class RoomOverviewBasicInfoBO extends Equatable {
final int roomId;
final String roomNumber;
final String roomStatusText;
const RoomOverviewBasicInfoBO({
this.roomId = 0,
this.roomNumber = '',
this.roomStatusText = '',
});
factory RoomOverviewBasicInfoBO.fromJson(Map<String, dynamic> json) {
return RoomOverviewBasicInfoBO(
roomId: json['roomId'] as int? ?? 0,
roomNumber: json['roomNumber'] as String? ?? '',
roomStatusText: json['roomStatusText'] as String? ?? '',
);
}
@override
List<Object?> get props => [roomId, roomNumber, roomStatusText];
}
/// 状态标签
class StatusTagsBO extends Equatable {
final bool isOccupied;
final bool powerSupplyStatus;
final bool wifiNormalStatus;
const StatusTagsBO({
this.isOccupied = false,
this.powerSupplyStatus = false,
this.wifiNormalStatus = false,
});
factory StatusTagsBO.fromJson(Map<String, dynamic> json) {
return StatusTagsBO(
isOccupied: json['isOccupied'] as bool? ?? false,
powerSupplyStatus: json['powerSupplyStatus'] as bool? ?? false,
wifiNormalStatus: json['wifiNormalStatus'] as bool? ?? false,
);
}
@override
List<Object?> get props => [isOccupied, powerSupplyStatus, wifiNormalStatus];
}
/// 房间设备项
class DeviceItemBO extends Equatable {
final int deviceId;
final String deviceName;
final String deviceTypeIcon;
final String powerStatus;
final String powerStatusText;
final String temperatureDisplay;
final String paramDisplayText;
const DeviceItemBO({
this.deviceId = 0,
this.deviceName = '',
this.deviceTypeIcon = '',
this.powerStatus = '',
this.powerStatusText = '',
this.temperatureDisplay = '',
this.paramDisplayText = '',
});
factory DeviceItemBO.fromJson(Map<String, dynamic> json) {
return DeviceItemBO(
deviceId: json['deviceId'] as int? ?? 0,
deviceName: json['deviceName'] as String? ?? '',
deviceTypeIcon: json['deviceTypeIcon'] as String? ?? '',
powerStatus: json['powerStatus'] as String? ?? '',
powerStatusText: json['powerStatusText'] as String? ?? '',
temperatureDisplay: json['temperatureDisplay'] as String? ?? '',
paramDisplayText: json['paramDisplayText'] as String? ?? '',
);
}
@override
List<Object?> get props => [
deviceId,
deviceName,
deviceTypeIcon,
powerStatus,
powerStatusText,
temperatureDisplay,
paramDisplayText,
];
}
/// 房间详情列表项
class RoomDetailListBO extends Equatable {
final RoomOverviewBasicInfoBO roomBasicInfo;
final StatusTagsBO statusTags;
final List<DeviceItemBO> deviceList;
const RoomDetailListBO({
required this.roomBasicInfo,
required this.statusTags,
this.deviceList = const [],
});
factory RoomDetailListBO.fromJson(Map<String, dynamic> json) {
return RoomDetailListBO(
roomBasicInfo: RoomOverviewBasicInfoBO.fromJson(json['roomBasicInfo'] as Map<String, dynamic>? ?? {}),
statusTags: StatusTagsBO.fromJson(json['statusTags'] as Map<String, dynamic>? ?? {}),
deviceList: (json['deviceList'] as List<dynamic>?)
?.map((e) => DeviceItemBO.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
);
}
@override
List<Object?> get props => [roomBasicInfo, statusTags, deviceList];
}
/// 客房概览统计根对象
class RoomOverviewBO extends Equatable {
final RoomOverviewSummaryBO summary;
final List<RoomFloorBO> floorList;
final List<RoomGridBO> roomGrid;
final List<RoomDetailListBO> roomDetailList;
const RoomOverviewBO({
required this.summary,
this.floorList = const [],
this.roomGrid = const [],
this.roomDetailList = const [],
});
factory RoomOverviewBO.fromJson(Map<String, dynamic> json) {
return RoomOverviewBO(
summary: RoomOverviewSummaryBO.fromJson(json['summary'] as Map<String, dynamic>? ?? {}),
floorList: (json['floorList'] as List<dynamic>?)
?.map((e) => RoomFloorBO.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
roomGrid: (json['roomGrid'] as List<dynamic>?)
?.map((e) => RoomGridBO.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
roomDetailList: (json['roomDetailList'] as List<dynamic>?)
?.map((e) => RoomDetailListBO.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
);
}
@override
List<Object?> get props => [summary, floorList, roomGrid, roomDetailList];
}
int _parseInt(dynamic value, {int fallback = 0}) { int _parseInt(dynamic value, {int fallback = 0}) {
if (value == null) return fallback; if (value == null) return fallback;
if (value is int) return value; if (value is int) return value;
......
...@@ -6,6 +6,7 @@ class AlertDashboardRepository { ...@@ -6,6 +6,7 @@ class AlertDashboardRepository {
Future<ResponseModel<AlertDashboardBO>> getDashboard() { Future<ResponseModel<AlertDashboardBO>> getDashboard() {
return DioRequest.instance.get<AlertDashboardBO>( return DioRequest.instance.get<AlertDashboardBO>(
'/app/alert/dashboard', '/app/alert/dashboard',
queryParameters: {'alertStatus': 0,'pageNum':1,'pageSize':5}, // 处理状态:0=待处理 1=处理中 2=已处理 3=已忽略 4=误报关闭
fromJsonT: (data) => fromJsonT: (data) =>
AlertDashboardBO.fromJson(data as Map<String, dynamic>), AlertDashboardBO.fromJson(data as Map<String, dynamic>),
); );
......
...@@ -81,6 +81,15 @@ class RoomRepository { ...@@ -81,6 +81,15 @@ class RoomRepository {
); );
} }
/// 客房状态总览-客房概览统计
Future<ResponseModel<RoomOverviewBO>> getOverview(int? floorId) {
return DioRequest.instance.get<RoomOverviewBO>(
'/app/room/overview',
queryParameters: {'floorId': floorId == 0 ? null : floorId},
fromJsonT: (data) => RoomOverviewBO.fromJson(data as Map<String, dynamic>),
);
}
/// 非客房-相关楼层区域设备 /// 非客房-相关楼层区域设备
Future<ResponseModel<List<AreaDeviceBO>>> getAreaDevices({ Future<ResponseModel<List<AreaDeviceBO>>> getAreaDevices({
required int floorId, required int floorId,
......
...@@ -73,6 +73,15 @@ class RoomService { ...@@ -73,6 +73,15 @@ class RoomService {
} }
} }
/// 客房状态总览-客房概览统计
Future<RoomOverviewBO> getOverview(int? floorId) async {
final result = await _repository.getOverview(floorId);
if (result.success && result.data != null) {
return result.data!;
}
throw Exception(result.msg);
}
/// 非客房-相关楼层区域设备 /// 非客房-相关楼层区域设备
Future<List<AreaDeviceBO>> getAreaDevices({ Future<List<AreaDeviceBO>> getAreaDevices({
required int floorId, required int floorId,
......
...@@ -69,7 +69,7 @@ class _ReportDeviceListBodyState extends State<_ReportDeviceListBody> { ...@@ -69,7 +69,7 @@ class _ReportDeviceListBodyState extends State<_ReportDeviceListBody> {
backgroundColor: Colors.white, backgroundColor: Colors.white,
elevation: 0, elevation: 0,
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.black), icon: const Icon(Icons.arrow_back_ios, color: Colors.black),
onPressed: () { onPressed: () {
context.popRoute(); context.popRoute();
}, },
......
...@@ -22,7 +22,7 @@ class ReportEnergyDetailView extends StatelessWidget { ...@@ -22,7 +22,7 @@ class ReportEnergyDetailView extends StatelessWidget {
backgroundColor: Colors.white, backgroundColor: Colors.white,
elevation: 0, elevation: 0,
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.black87), icon: const Icon(Icons.arrow_back_ios, color: Colors.black87),
onPressed: () { onPressed: () {
context.popRoute(); context.popRoute();
}, },
......
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:smart_hotel_app/repositories/room_repository.dart';
import 'package:smart_hotel_app/services/room_service.dart';
import 'package:smart_hotel_app/views/report/room/cubit/room_report_state.dart'; import 'package:smart_hotel_app/views/report/room/cubit/room_report_state.dart';
class RoomReportCubit extends Cubit<RoomReportState> { class RoomReportCubit extends Cubit<RoomReportState> {
RoomReportCubit() : super(const RoomReportState()) { final RoomService _service;
_initData();
}
void _initData() {
final floors = [
FloorInfo(
floorName: '一楼',
rooms: List.generate(10, (index) => _createRoom(101 + index)),
),
FloorInfo(
floorName: '二楼',
rooms: List.generate(10, (index) => _createRoom(201 + index)),
),
FloorInfo(
floorName: '三楼',
rooms: List.generate(10, (index) => _createRoom(301 + index)),
),
FloorInfo(
floorName: '四楼',
rooms: List.generate(10, (index) => _createRoom(401 + index)),
),
FloorInfo(
floorName: '五楼',
rooms: List.generate(10, (index) => _createRoom(501 + index)),
),
FloorInfo(
floorName: '六楼',
rooms: List.generate(10, (index) => _createRoom(601 + index)),
),
];
final statusStats = [
const StatusStat(
label: '入住',
count: 45,
color: Color.fromRGBO(59, 130, 246, 1),
),
const StatusStat(
label: '空闲',
count: 7,
color: Color.fromRGBO(156, 163, 175, 1),
),
const StatusStat(
label: '预订',
count: 5,
color: Color.fromRGBO(147, 197, 253, 1),
),
const StatusStat(
label: '告警',
count: 3,
color: Color.fromRGBO(251, 191, 36, 1),
),
];
final detailRooms = [ RoomReportCubit({RoomService? service})
RoomInfo( : _service = service ?? RoomService(repository: RoomRepository()),
roomNumber: '302', super(const RoomReportState()) {
status: RoomStatus.occupied, loadData();
statusText: '入住中',
deviceStatus: const RoomDeviceStatus(
hasPerson: true,
powerOn: true,
wifiNormal: true,
acOn: true,
acTemperature: '24',
lightOn: false,
tvOn: true,
curtainOn: false,
),
),
];
emit(state.copyWith(
floors: floors,
statusStats: statusStats,
detailRooms: detailRooms,
));
} }
RoomInfo _createRoom(int number) { Future<void> loadData({int? floorId}) async {
RoomStatus status; emit(state.copyWith(isLoading: true, error: null));
String statusText; try {
final overview = await _service.getOverview(floorId);
if (number == 303) { final activeIndex = overview.floorList.indexWhere((f) => f.isActive);
status = RoomStatus.warning; emit(state.copyWith(
statusText = '告警'; isLoading: false,
} else if (number == 309) { summary: overview.summary,
status = RoomStatus.temperature; floors: overview.floorList,
statusText = '温度'; selectedFloorIndex: activeIndex >= 0 ? activeIndex : 0,
} else if ([302, 304, 308].contains(number)) { roomGrid: overview.roomGrid,
status = RoomStatus.occupied; detailRooms: overview.roomDetailList,
statusText = number == 308 ? '入住·勿扰' : '入住·在房'; ));
} else if (number == 307) { } catch (e) {
status = RoomStatus.reserved; emit(state.copyWith(isLoading: false, error: e.toString()));
statusText = '预定';
} else {
status = RoomStatus.vacant;
statusText = '空闲';
} }
return RoomInfo(
roomNumber: number.toString(),
status: status,
statusText: statusText,
);
} }
void selectFloor(int index) { void selectFloor(int index) {
final floorId = state.floors[index].floorId;
emit(state.copyWith(selectedFloorIndex: index)); emit(state.copyWith(selectedFloorIndex: index));
loadData(floorId: floorId);
} }
} }
\ No newline at end of file
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart'; import 'package:smart_hotel_app/models/bo/room_bo.dart';
enum RoomStatus {
vacant,
occupied,
reserved,
warning,
temperature,
}
class RoomDeviceStatus {
final bool hasPerson;
final bool powerOn;
final bool wifiNormal;
final bool acOn;
final String acTemperature;
final bool lightOn;
final bool tvOn;
final bool curtainOn;
const RoomDeviceStatus({
this.hasPerson = true,
this.powerOn = true,
this.wifiNormal = true,
this.acOn = false,
this.acTemperature = '24',
this.lightOn = false,
this.tvOn = false,
this.curtainOn = false,
});
RoomDeviceStatus copyWith({
bool? hasPerson,
bool? powerOn,
bool? wifiNormal,
bool? acOn,
String? acTemperature,
bool? lightOn,
bool? tvOn,
bool? curtainOn,
}) {
return RoomDeviceStatus(
hasPerson: hasPerson ?? this.hasPerson,
powerOn: powerOn ?? this.powerOn,
wifiNormal: wifiNormal ?? this.wifiNormal,
acOn: acOn ?? this.acOn,
acTemperature: acTemperature ?? this.acTemperature,
lightOn: lightOn ?? this.lightOn,
tvOn: tvOn ?? this.tvOn,
curtainOn: curtainOn ?? this.curtainOn,
);
}
}
class RoomInfo {
final String roomNumber;
final RoomStatus status;
final String statusText;
final RoomDeviceStatus deviceStatus;
const RoomInfo({
required this.roomNumber,
required this.status,
required this.statusText,
this.deviceStatus = const RoomDeviceStatus(),
});
RoomInfo copyWith({
String? roomNumber,
RoomStatus? status,
String? statusText,
RoomDeviceStatus? deviceStatus,
}) {
return RoomInfo(
roomNumber: roomNumber ?? this.roomNumber,
status: status ?? this.status,
statusText: statusText ?? this.statusText,
deviceStatus: deviceStatus ?? this.deviceStatus,
);
}
}
class FloorInfo {
final String floorName;
final List<RoomInfo> rooms;
const FloorInfo({
required this.floorName,
required this.rooms,
});
FloorInfo copyWith({
String? floorName,
List<RoomInfo>? rooms,
}) {
return FloorInfo(
floorName: floorName ?? this.floorName,
rooms: rooms ?? this.rooms,
);
}
}
class StatusStat {
final String label;
final int count;
final Color color;
const StatusStat({
required this.label,
required this.count,
required this.color,
});
}
class RoomReportState extends Equatable { class RoomReportState extends Equatable {
final List<FloorInfo> floors; final bool isLoading;
final String? error;
final RoomOverviewSummaryBO summary;
final List<RoomFloorBO> floors;
final int selectedFloorIndex; final int selectedFloorIndex;
final List<StatusStat> statusStats; final List<RoomGridBO> roomGrid;
final List<RoomInfo> detailRooms; final List<RoomDetailListBO> detailRooms;
const RoomReportState({ const RoomReportState({
this.isLoading = false,
this.error,
this.summary = const RoomOverviewSummaryBO(),
this.floors = const [], this.floors = const [],
this.selectedFloorIndex = 2, this.selectedFloorIndex = 0,
this.statusStats = const [], this.roomGrid = const [],
this.detailRooms = const [], this.detailRooms = const [],
}); });
RoomReportState copyWith({ RoomReportState copyWith({
List<FloorInfo>? floors, bool? isLoading,
String? error,
RoomOverviewSummaryBO? summary,
List<RoomFloorBO>? floors,
int? selectedFloorIndex, int? selectedFloorIndex,
List<StatusStat>? statusStats, List<RoomGridBO>? roomGrid,
List<RoomInfo>? detailRooms, List<RoomDetailListBO>? detailRooms,
bool clearError = false,
}) { }) {
return RoomReportState( return RoomReportState(
isLoading: isLoading ?? this.isLoading,
error: clearError ? null : (error ?? this.error),
summary: summary ?? this.summary,
floors: floors ?? this.floors, floors: floors ?? this.floors,
selectedFloorIndex: selectedFloorIndex ?? this.selectedFloorIndex, selectedFloorIndex: selectedFloorIndex ?? this.selectedFloorIndex,
statusStats: statusStats ?? this.statusStats, roomGrid: roomGrid ?? this.roomGrid,
detailRooms: detailRooms ?? this.detailRooms, detailRooms: detailRooms ?? this.detailRooms,
); );
} }
@override @override
List<Object?> get props => [ List<Object?> get props => [
isLoading,
error,
summary,
floors, floors,
selectedFloorIndex, selectedFloorIndex,
statusStats, roomGrid,
detailRooms, detailRooms,
]; ];
} }
\ No newline at end of file
...@@ -2,6 +2,8 @@ import 'package:auto_route/auto_route.dart'; ...@@ -2,6 +2,8 @@ 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/repositories/room_repository.dart';
import 'package:smart_hotel_app/services/room_service.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_cubit.dart';
import 'package:smart_hotel_app/views/report/room/cubit/room_report_state.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/floor_selector.dart';
...@@ -16,22 +18,72 @@ class ReportRoomDetailView extends StatelessWidget { ...@@ -16,22 +18,72 @@ class ReportRoomDetailView extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (_) => RoomReportCubit(), create: (_) => RoomReportCubit(
service: RoomService(repository: RoomRepository()),
),
child: BlocBuilder<RoomReportCubit, RoomReportState>( child: BlocBuilder<RoomReportCubit, RoomReportState>(
builder: (context, state) { builder: (context, state) {
if (state.isLoading && state.floors.isEmpty) {
return Scaffold(
backgroundColor: const Color.fromRGBO(242, 243, 245, 1),
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios, color: Colors.black),
onPressed: () => context.maybePop(),
),
title: Text(
'客房状态总览',
style: TextStyle(
color: Colors.black,
fontSize: 32.sp,
fontWeight: FontWeight.bold,
),
),
centerTitle: true,
),
body: const Center(child: CircularProgressIndicator()),
);
}
if (state.error != null && state.floors.isEmpty) {
return Scaffold(
backgroundColor: const Color.fromRGBO(242, 243, 245, 1),
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios, color: Colors.black),
onPressed: () => context.maybePop(),
),
title: Text(
'客房状态总览',
style: TextStyle(
color: Colors.black,
fontSize: 32.sp,
fontWeight: FontWeight.bold,
),
),
centerTitle: true,
),
body: Center(child: Text('加载失败: ${state.error}')),
);
}
return Scaffold( return Scaffold(
backgroundColor: const Color.fromRGBO(242, 243, 245, 1), backgroundColor: const Color.fromRGBO(242, 243, 245, 1),
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.white, backgroundColor: Colors.white,
elevation: 0, elevation: 0,
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.black), icon: const Icon(Icons.arrow_back_ios, color: Colors.black),
onPressed: () { onPressed: () {
context.popRoute(); context.maybePop();
}, },
), ),
title: Text( title: Text(
'客房详情', '客房状态总览',
style: TextStyle( style: TextStyle(
color: Colors.black, color: Colors.black,
fontSize: 32.sp, fontSize: 32.sp,
...@@ -66,12 +118,16 @@ class ReportRoomDetailView extends StatelessWidget { ...@@ -66,12 +118,16 @@ class ReportRoomDetailView extends StatelessWidget {
borderRadius: BorderRadius.vertical(bottom: Radius.circular(24.r)), borderRadius: BorderRadius.vertical(bottom: Radius.circular(24.r)),
), ),
padding: EdgeInsets.only(bottom: 28.w), padding: EdgeInsets.only(bottom: 28.w),
child: RoomGrid(rooms: state.floors[state.selectedFloorIndex].rooms), child: RoomGrid(rooms: state.roomGrid),
), ),
SizedBox(height: 10.h), SizedBox(height: 10.h),
if (state.statusStats.isNotEmpty) StatusStats(summary: state.summary),
StatusStats(stats: state.statusStats),
SizedBox(height: 10.h), SizedBox(height: 10.h),
if (state.isLoading)
const Padding(
padding: EdgeInsets.all(16.0),
child: Center(child: CircularProgressIndicator()),
),
...state.detailRooms.map((room) { ...state.detailRooms.map((room) {
return Padding( return Padding(
padding: EdgeInsets.only(bottom: 10.h), padding: EdgeInsets.only(bottom: 10.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/views/report/room/cubit/room_report_state.dart'; import 'package:smart_hotel_app/models/bo/room_bo.dart';
class RoomDetailCard extends StatelessWidget { class RoomDetailCard extends StatelessWidget {
final RoomInfo room; final RoomDetailListBO room;
const RoomDetailCard({super.key, required this.room}); const RoomDetailCard({super.key, required this.room});
...@@ -41,7 +41,7 @@ class RoomDetailCard extends StatelessWidget { ...@@ -41,7 +41,7 @@ class RoomDetailCard extends StatelessWidget {
Row( Row(
children: [ children: [
Text( Text(
room.roomNumber, room.roomBasicInfo.roomNumber,
style: TextStyle( style: TextStyle(
fontSize: 36.sp, fontSize: 36.sp,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
...@@ -50,7 +50,7 @@ class RoomDetailCard extends StatelessWidget { ...@@ -50,7 +50,7 @@ class RoomDetailCard extends StatelessWidget {
), ),
SizedBox(width: 16.w), SizedBox(width: 16.w),
Text( Text(
'入住中', room.roomBasicInfo.roomStatusText,
style: TextStyle( style: TextStyle(
fontSize: 28.sp, fontSize: 28.sp,
color: const Color.fromRGBO(59, 130, 246, 1), color: const Color.fromRGBO(59, 130, 246, 1),
...@@ -63,17 +63,17 @@ class RoomDetailCard extends StatelessWidget { ...@@ -63,17 +63,17 @@ class RoomDetailCard extends StatelessWidget {
children: [ children: [
_StatusTag( _StatusTag(
label: '有人', label: '有人',
isActive: room.deviceStatus.hasPerson, isActive: room.statusTags.isOccupied,
), ),
SizedBox(width: 12.w), SizedBox(width: 12.w),
_StatusTag( _StatusTag(
label: '通电中', label: '通电中',
isActive: room.deviceStatus.powerOn, isActive: room.statusTags.powerSupplyStatus,
), ),
SizedBox(width: 12.w), SizedBox(width: 12.w),
_StatusTag( _StatusTag(
label: 'wifi正常', label: 'wifi正常',
isActive: room.deviceStatus.wifiNormal, isActive: room.statusTags.wifiNormalStatus,
), ),
], ],
), ),
...@@ -93,32 +93,7 @@ class RoomDetailCard extends StatelessWidget { ...@@ -93,32 +93,7 @@ class RoomDetailCard extends StatelessWidget {
crossAxisCount: 2, crossAxisCount: 2,
mainAxisSpacing: 16.h, mainAxisSpacing: 16.h,
crossAxisSpacing: 16.w, crossAxisSpacing: 16.w,
children: [ children: room.deviceList.map((device) => _DeviceControl(device: device)).toList(),
_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,
),
],
), ),
], ],
), ),
...@@ -169,20 +144,28 @@ class _StatusTag extends StatelessWidget { ...@@ -169,20 +144,28 @@ class _StatusTag extends StatelessWidget {
} }
class _DeviceControl extends StatelessWidget { class _DeviceControl extends StatelessWidget {
final IconData icon; final DeviceItemBO device;
final String label;
final String value;
final bool isOn;
const _DeviceControl({ const _DeviceControl({required this.device});
required this.icon,
required this.label, IconData _icon() {
required this.value, switch (device.deviceTypeIcon) {
required this.isOn, case 'ac':
}); return Icons.air_outlined;
case 'light':
return Icons.light_outlined;
case 'tv':
return Icons.tv_outlined;
case 'curtain':
return Icons.curtains_outlined;
default:
return Icons.devices_other;
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isOn = device.powerStatus == '1';
return Container( return Container(
padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 24.h), padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 24.h),
decoration: BoxDecoration( decoration: BoxDecoration(
...@@ -192,13 +175,13 @@ class _DeviceControl extends StatelessWidget { ...@@ -192,13 +175,13 @@ class _DeviceControl extends StatelessWidget {
child: Row( child: Row(
children: [ children: [
Icon( Icon(
icon, _icon(),
color: const Color.fromRGBO(59, 130, 246, 1), color: const Color.fromRGBO(59, 130, 246, 1),
size: 40.sp, size: 40.sp,
), ),
SizedBox(width: 16.w), SizedBox(width: 16.w),
Text( Text(
label, device.deviceName,
style: TextStyle( style: TextStyle(
fontSize: 28.sp, fontSize: 28.sp,
color: const Color.fromRGBO(59, 130, 246, 1), color: const Color.fromRGBO(59, 130, 246, 1),
...@@ -207,7 +190,7 @@ class _DeviceControl extends StatelessWidget { ...@@ -207,7 +190,7 @@ class _DeviceControl extends StatelessWidget {
), ),
const Spacer(), const Spacer(),
Text( Text(
value, device.paramDisplayText.isNotEmpty ? device.paramDisplayText : device.powerStatusText,
style: TextStyle( style: TextStyle(
fontSize: 26.sp, fontSize: 26.sp,
color: isOn color: isOn
......
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/views/report/room/cubit/room_report_state.dart'; import 'package:smart_hotel_app/models/bo/room_bo.dart';
class RoomGrid extends StatelessWidget { class RoomGrid extends StatelessWidget {
final List<RoomInfo> rooms; final List<RoomGridBO> rooms;
const RoomGrid({super.key, required this.rooms}); const RoomGrid({super.key, required this.rooms});
...@@ -18,53 +18,64 @@ class RoomGrid extends StatelessWidget { ...@@ -18,53 +18,64 @@ class RoomGrid extends StatelessWidget {
crossAxisCount: 5, crossAxisCount: 5,
mainAxisSpacing: 20.h, mainAxisSpacing: 20.h,
crossAxisSpacing: 16.w, crossAxisSpacing: 16.w,
children: rooms.map((room) => RoomItem(room: room)).toList(), children: rooms.map((room) => _RoomItem(room: room)).toList(),
), ),
); );
} }
} }
class RoomItem extends StatelessWidget { class _RoomItem extends StatelessWidget {
final RoomInfo room; final RoomGridBO room;
const RoomItem({super.key, required this.room}); const _RoomItem({required this.room});
Color get _bgColor { Color get _bgColor {
switch (room.status) { switch (room.statusColor) {
case RoomStatus.vacant: case 'occupied':
return const Color.fromRGBO(229, 231, 235, 1); return const Color.fromRGBO(219, 234, 254, 1);
case RoomStatus.occupied: case 'alarm':
case RoomStatus.reserved: return const Color.fromRGBO(254, 243, 199, 1);
case 'reserved':
return const Color.fromRGBO(219, 234, 254, 1); return const Color.fromRGBO(219, 234, 254, 1);
case RoomStatus.warning: case 'dnd':
case RoomStatus.temperature:
return const Color.fromRGBO(254, 243, 199, 1); return const Color.fromRGBO(254, 243, 199, 1);
case 'checkout':
case 'idle':
default:
return const Color.fromRGBO(229, 231, 235, 1);
} }
} }
Color get _textColor { Color get _textColor {
switch (room.status) { switch (room.statusColor) {
case RoomStatus.vacant: case 'occupied':
return const Color.fromRGBO(156, 163, 175, 1);
case RoomStatus.occupied:
case RoomStatus.reserved:
return const Color.fromRGBO(59, 130, 246, 1); return const Color.fromRGBO(59, 130, 246, 1);
case RoomStatus.warning: case 'alarm':
case RoomStatus.temperature:
return const Color.fromRGBO(245, 158, 11, 1); return const Color.fromRGBO(245, 158, 11, 1);
case 'reserved':
return const Color.fromRGBO(59, 130, 246, 1);
case 'dnd':
return const Color.fromRGBO(245, 158, 11, 1);
case 'checkout':
case 'idle':
default:
return const Color.fromRGBO(156, 163, 175, 1);
} }
} }
Color? get _borderColor { Color? get _borderColor {
switch (room.status) { switch (room.statusColor) {
case RoomStatus.vacant: case 'idle':
case 'checkout':
return null; return null;
case RoomStatus.occupied: case 'occupied':
case RoomStatus.reserved: case 'reserved':
return const Color.fromRGBO(59, 130, 246, 1); return const Color.fromRGBO(59, 130, 246, 1);
case RoomStatus.warning: case 'alarm':
case RoomStatus.temperature: case 'dnd':
return const Color.fromRGBO(245, 158, 11, 1); return const Color.fromRGBO(245, 158, 11, 1);
default:
return null;
} }
} }
...@@ -91,9 +102,9 @@ class RoomItem extends StatelessWidget { ...@@ -91,9 +102,9 @@ class RoomItem extends StatelessWidget {
), ),
), ),
SizedBox(height: 4.h), SizedBox(height: 4.h),
if (room.status == RoomStatus.warning || room.status == RoomStatus.temperature) if (room.hasAlarmIcon)
Icon(Icons.warning_amber_outlined, color: _textColor, size: 20.sp), Icon(Icons.warning_amber_outlined, color: _textColor, size: 20.sp),
if (room.status != RoomStatus.warning && room.status != RoomStatus.temperature) if (!room.hasAlarmIcon)
Text( Text(
room.statusText, room.statusText,
style: TextStyle( style: TextStyle(
......
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/views/report/room/cubit/room_report_state.dart'; import 'package:smart_hotel_app/models/bo/room_bo.dart';
class StatusStats extends StatelessWidget { class StatusStats extends StatelessWidget {
final List<StatusStat> stats; final RoomOverviewSummaryBO summary;
const StatusStats({super.key, required this.stats}); const StatusStats({super.key, required this.summary});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final items = [
_StatItemData(label: '入住', count: summary.occupiedCount, color: const Color(0xFF4A7CF7)),
_StatItemData(label: '空闲', count: summary.vacantCount, color: const Color(0xFF4CAF50)),
_StatItemData(label: '预定', count: summary.reservedCount, color: const Color(0xFF9C27B0)),
_StatItemData(label: '告警', count: summary.alarmCount, color: const Color(0xFFFF9800)),
];
return Container( return Container(
width: double.infinity, width: double.infinity,
padding: EdgeInsets.all(28.w), padding: EdgeInsets.all(28.w),
...@@ -18,32 +25,39 @@ class StatusStats extends StatelessWidget { ...@@ -18,32 +25,39 @@ class StatusStats extends StatelessWidget {
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: stats.map((stat) => StatItem(stat: stat)).toList(), children: items.map((item) => _StatItem(item: item)).toList(),
), ),
); );
} }
} }
class StatItem extends StatelessWidget { class _StatItemData {
final StatusStat stat; final String label;
final int count;
final Color color;
const _StatItemData({required this.label, required this.count, required this.color});
}
class _StatItem extends StatelessWidget {
final _StatItemData item;
const StatItem({super.key, required this.stat}); const _StatItem({required this.item});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Column(
children: [ children: [
Text( Text(
stat.count.toString(), item.count.toString(),
style: TextStyle( style: TextStyle(
fontSize: 40.sp, fontSize: 40.sp,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: stat.color, color: item.color,
), ),
), ),
SizedBox(height: 4.h), SizedBox(height: 4.h),
Text( Text(
stat.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(156, 163, 175, 1),
......
...@@ -22,7 +22,7 @@ class ReportRuleManagementView extends StatelessWidget { ...@@ -22,7 +22,7 @@ class ReportRuleManagementView extends StatelessWidget {
backgroundColor: Colors.white, backgroundColor: Colors.white,
elevation: 0, elevation: 0,
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.black), icon: const Icon(Icons.arrow_back_ios, color: Colors.black),
onPressed: () { onPressed: () {
context.popRoute(); context.popRoute();
}, },
......
# 智慧酒店 App 接口对接方案 # 智慧酒店 App 接口对接方案
...@@ -241,13 +241,109 @@ class xxxBO extends Equatable { ...@@ -241,13 +241,109 @@ class xxxBO extends Equatable {
| 客房服务看板-统计 |GET | /app/room/statistics | 已对接 | 客房服务看板-统计 | | | 客房服务看板-统计 |GET | /app/room/statistics | 已对接 | 客房服务看板-统计 | |
| 非客房-相关楼层区域设备 |GET | /app/room/area/devices | 未对接 | 非客房-相关楼层区域设备 | | | 非客房-相关楼层区域设备 |GET | /app/room/area/devices | 已对接 | 非客房-相关楼层区域设备 | |
| 客房状态总览-客房概览统计 |GET | /app/room/overview | 已对接 | 客房状态总览-客房概览统计 | |
### 3.2 接口详细定义 ### 3.2 接口详细定义
#### 客房状态总览-客房概览统计
**GET /app/room/overview**
```
求体:
{
"floorId":0
}
应 data:
{
"code": 0,
"msg": "string",
"data": {
"summary": { // 顶部统计区域(页面中间一排数字卡片:45入住 7空闲 5披订 3告警)
"occupiedCount": 0, // 入住数量
"vacantCount": 0, // 空闲数量
"reservedCount": 0, // 预定数量
"alarmCount": 0 // 告警数量
},
"floorList": [ // 楼层Tab列表
{
"floorId": 0,
"floorName": "string",
"isActive": true
}
],
"roomGrid": [ // 房间网格区域(页面主体上半部分的房间卡片网格布局,每格显示房间号+状态文字+颜色背景),按楼层分组展示
{
"roomId": 0,
"roomNumber": "string",
"statusText": "string", // 房间状态文字,显示在房间号下方小字,由 hotel_room.sell_status 和 hotel_room.occupancy_status 综合判断映射:sell_status='0'且occupancy_status='0'→'空闲';sell_status='3'且occupancy_status='1'→'入住·在房';sell_status='4'→'维修/告警'(黄色);sell_status='2'→'预定';dnd_status='1'→'勿扰';checkout_status='1'→'退房'
"statusColor": "string", // 状态颜色类型:idle=灰色(green/默认) occupied=蓝色(blue) alarm=黄色(yellow/orange) reserved=紫色(purple) dnd=橙色(orange) checkout=灰色(gray),根据 statusText 对应,用于渲染格子背景色
"hasAlarmIcon": true // 是否显示告警图标(⚠️):true=显示黄色警告图标 false=不显示
}
],
"roomDetailList": [ // 房间详情列表
{
"roomBasicInfo": { // 房间基础信息区
"roomId": 0,
"roomNumber": "string",
"roomStatusText": "string"
},
"statusTags": { // 状态标签区(一行绿色勾选标签:✓有人 ✓通电中 ✓wif正常)
"isOccupied": true, // 是否已入住
"powerSupplyStatus": true, // 供电状态(true=供电中,false=已断电)
"wifiNormalStatus": true // WiFi是否正常(true=正常,false=异常)
},
"deviceList": [ // 房间设备列表
{
"deviceId": 0,
"deviceName": "string",
"deviceTypeIcon": "string", // 设备图标标识:ac=❄️空调 light=💡照明 tv=📺电视 curtain=🪟窗帘
"powerStatus": "string", // 电源开关状态码:0=关闭 1=开启
"powerStatusText": "string", // 开关状态文字:'开'(绿色)/'关'(灰色),根据 power_status 映射:'1'→'开' '0'→'关'
"temperatureDisplay": "string", // 温度显示文本
"paramDisplayText": "string" // 完整参数显示文本,最终展示值,拼接规则:有温度→'{temperature}°c·{powerStatusText}'(如'24°C·开');无温度→直接显示 powerStatusText(如'开'/'关')
}
]
}
]
}
}
```
#### 客房状态总览
**GET /app/hotel/floor/list**
```
求体:
{
}
应 data:
{
"code": 0,
"msg": "string",
"data": {
"total": 0,
"floors": [
{
"floorId": 0,
"floorName": "string"
}
]
}
}
```
#### 非客房-相关楼层区域设备 #### 非客房-相关楼层区域设备
**GET /app/room/area/devices** **GET /app/room/area/devices**
......
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