Commit 66f73066 authored by 张宏's avatar 张宏

3

parent 56619d0f
import 'package:equatable/equatable.dart';
int _parseInt(dynamic value, {int fallback = 0}) {
if (value == null) return fallback;
if (value is int) return value;
if (value is double) return value.toInt();
if (value is String) {
final parsed = int.tryParse(value.trim());
return parsed ?? fallback;
}
return fallback;
}
String _parseString(dynamic value, {String fallback = ''}) {
if (value == null) return fallback;
if (value is String) return value;
return value.toString();
}
// ==================== 客房服务看板-客房状态总览-楼层 ====================
class FloorAreaBO extends Equatable {
final int floorId;
final List<String> areas;
final List<FloorRoomSimpleBO> rooms;
const FloorAreaBO({
required this.floorId,
required this.areas,
required this.rooms,
});
factory FloorAreaBO.fromJson(Map<String, dynamic> json) {
return FloorAreaBO(
floorId: _parseInt(json['floorId']),
areas: (json['areas'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
[],
rooms: (json['rooms'] as List<dynamic>?)
?.map((e) =>
FloorRoomSimpleBO.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
);
}
@override
List<Object?> get props => [floorId, areas, rooms];
String get floorName => '${floorId}楼';
}
class FloorRoomSimpleBO extends Equatable {
final int roomId;
final String roomName;
const FloorRoomSimpleBO({
required this.roomId,
required this.roomName,
});
factory FloorRoomSimpleBO.fromJson(Map<String, dynamic> json) {
return FloorRoomSimpleBO(
roomId: _parseInt(json['roomId']),
roomName: json['roomName'] as String? ?? '',
);
}
@override
List<Object?> get props => [roomId, roomName];
}
// ==================== 客房服务看板-客房状态总览-房间list ====================
class RoomStatusBO extends Equatable {
final int roomId;
final String roomNumber;
final String occupancyStatus;
final String roomStatus;
final String cleanStatus;
final String devicePowerStatus;
const RoomStatusBO({
required this.roomId,
required this.roomNumber,
required this.occupancyStatus,
required this.roomStatus,
required this.cleanStatus,
required this.devicePowerStatus,
});
factory RoomStatusBO.fromJson(Map<String, dynamic> json) {
return RoomStatusBO(
roomId: _parseInt(json['roomId']),
roomNumber: json['roomNumber'] as String? ?? '',
occupancyStatus: _parseString(json['occupancyStatus']),
roomStatus: _parseString(json['roomStatus']),
cleanStatus: _parseString(json['cleanStatus']),
devicePowerStatus: _parseString(json['devicePowerStatus']),
);
}
@override
List<Object?> get props =>
[roomId, roomNumber, occupancyStatus, roomStatus, cleanStatus, devicePowerStatus];
}
\ No newline at end of file
import '../utils/http/response_model.dart';
import '../utils/http/dio_request.dart';
import '../models/bo/room_bo.dart';
class RoomRepository {
/// 客房服务看板-客房状态总览-楼层
Future<ResponseModel<List<FloorAreaBO>>> getFloorAreas() {
return DioRequest.instance.get<List<FloorAreaBO>>(
'/app/room/floor/areas',
fromJsonT: (data) {
final list = data as List<dynamic>;
return list
.map((e) => FloorAreaBO.fromJson(e as Map<String, dynamic>))
.toList();
},
);
}
/// 客房服务看板-客房状态总览-房间list
Future<ResponseModel<List<RoomStatusBO>>> getRoomStatus({
String? roomId,
int? floorId,
String? areaType,
}) {
final queryParameters = <String, dynamic>{};
if (roomId != null) queryParameters['roomId'] = roomId;
if (floorId != null) queryParameters['floorId'] = floorId.toString();
if (areaType != null) queryParameters['areaType'] = areaType;
return DioRequest.instance.get<List<RoomStatusBO>>(
'/app/room/status',
queryParameters: queryParameters,
fromJsonT: (data) {
final list = data as List<dynamic>;
return list
.map((e) => RoomStatusBO.fromJson(e as Map<String, dynamic>))
.toList();
},
);
}
}
\ No newline at end of file
import '../models/bo/room_bo.dart';
import '../repositories/room_repository.dart';
class RoomService {
final RoomRepository _repository;
RoomService({required RoomRepository repository}) : _repository = repository;
/// 获取楼层及区域信息
Future<List<FloorAreaBO>> getFloorAreas() async {
final result = await _repository.getFloorAreas();
if (result.success && result.data != null) {
return result.data!;
}
throw Exception(result.msg);
}
/// 获取房间状态列表
Future<List<RoomStatusBO>> getRoomStatus({
String? roomId,
int? floorId,
String? areaType,
}) async {
final result = await _repository.getRoomStatus(
roomId: roomId,
floorId: floorId,
areaType: areaType,
);
if (result.success && result.data != null) {
return result.data!;
}
throw Exception(result.msg);
}
}
\ No newline at end of file
import 'dart:math';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:smart_hotel_app/views/service/index/cubit/service_index_state.dart';
import 'package:smart_hotel_app/services/room_service.dart';
import 'package:smart_hotel_app/repositories/room_repository.dart';
import 'package:smart_hotel_app/models/bo/room_bo.dart';
class ServiceIndexCubit extends Cubit<ServiceIndexState> {
Map<String, List<Map<String, dynamic>>>? _roomCache;
final RoomService _roomService;
final Map<String, List<Map<String, dynamic>>> _roomCache = {};
ServiceIndexCubit() : super(const ServiceIndexState()) {
initData();
ServiceIndexCubit()
: _roomService = RoomService(repository: RoomRepository()),
super(const ServiceIndexState()) {
_init();
}
void initData() {
emit(state.copyWith(
pendingCount: 2,
occupiedRooms: 45,
vacantRooms: 7,
floors: ['1楼', '2楼', '3楼', '4楼', '5楼', '6楼'],
));
Future<void> _init() async {
await loadFloorAndAreas();
}
void selectFloor(String? floor) {
emit(state.copyWith(selectedFloor: floor));
}
/// 加载楼层和区域信息
Future<void> loadFloorAndAreas() async {
emit(state.copyWith(isLoading: true, error: null));
try {
final floorAreas = await _roomService.getFloorAreas();
final floors = floorAreas.map((f) => {f.floorId: f.floorId.toString()}).toList();
final areas = <String>{};
for (final f in floorAreas) {
areas.addAll(f.areas);
}
void selectType(String? type) {
emit(state.copyWith(selectedType: type));
}
emit(state.copyWith(
isLoading: false,
floors: floors,
areas: areas.toList(),
));
void selectRoom(String? room) {
emit(state.copyWith(selectedRoom: room));
// 默认选中第一个楼层,加载其房间数据
if (floors.isNotEmpty) {
final firstFloor = floors.first;
final firstFloorId = firstFloor.keys.first;
final firstFloorLabel = firstFloor.values.first;
selectFloor(firstFloorLabel);
await loadRooms(floorId: firstFloorId);
}
} catch (e) {
emit(state.copyWith(isLoading: false, error: e.toString()));
}
}
void setSearchText(String text) {
emit(state.copyWith(searchText: text));
/// 加载房间状态列表
Future<void> loadRooms({
int? floorId,
String? areaType,
}) async {
emit(state.copyWith(isLoading: true, error: null));
try {
final roomStatuses = await _roomService.getRoomStatus(
floorId: floorId,
areaType: areaType,
);
// 如果指定了楼层,直接缓存到对应楼层key下
if (floorId != null) {
final floorKey = floorId.toString();
// 更新该楼层的房间数据到 _roomCache
final updatedAllRooms = <Map<String, dynamic>>[];
for (final rs in roomStatuses) {
updatedAllRooms.add(_statusBoToRoomMap(rs));
}
_roomCache[floorKey] = updatedAllRooms;
} else {
// 未指定楼层,按房间号提取楼层归类(兜底)
_roomCache.clear();
for (final floorMap in (state.floors ?? [])) {
_roomCache[floorMap.values.first] = [];
}
for (final rs in roomStatuses) {
final floorNum = _extractFloor(rs.roomNumber);
final floorKey = floorNum.toString();
_roomCache.putIfAbsent(floorKey, () => []);
_roomCache[floorKey]!.add(_statusBoToRoomMap(rs));
}
}
// 展平所有房间用于全量展示(可被页面直接使用)
final allRooms = <Map<String, dynamic>>[];
for (final floorMap in (state.floors ?? [])) {
final floorLabel = floorMap.values.first;
allRooms.addAll(_roomCache[floorLabel] ?? []);
}
emit(state.copyWith(isLoading: false, rooms: allRooms));
} catch (e) {
emit(state.copyWith(isLoading: false, error: e.toString()));
}
}
Map<String, List<Map<String, dynamic>>> _buildRoomCache() {
/// 将 RoomStatusBO 转为页面所需的 Map
Map<String, dynamic> _statusBoToRoomMap(RoomStatusBO rs) {
// 状态映射:cleanStatus "2" → 待清洁,occupancyStatus "1" → 入住,其余为 空闲
String displayStatus;
if (rs.cleanStatus == '2') {
displayStatus = '待清洁';
} else if (rs.occupancyStatus == '1') {
displayStatus = '入住';
} else {
displayStatus = '空闲';
}
return {
'1楼': _generateRooms(1, 20),
'2楼': _generateRooms(2, Random().nextInt(21) + 10),
'3楼': _generateRooms(3, Random().nextInt(21) + 10),
'4楼': _generateRooms(4, Random().nextInt(21) + 10),
'5楼': _generateRooms(5, Random().nextInt(5) + 10),
'6楼': _generateRooms(6, Random().nextInt(21) + 10),
'number': rs.roomNumber,
'status': displayStatus,
'type': '客房', // 默认类型,后续可通过楼层areas信息扩展
};
}
bool get _cacheValid {
if (_roomCache == null) return false;
final firstFloor = _roomCache!['1楼'];
if (firstFloor == null || firstFloor.isEmpty) return false;
return firstFloor.first.containsKey('type');
/// 从房间号提取楼层,如 "301" → 3
int _extractFloor(String roomNumber) {
if (roomNumber.length >= 2) {
final match = RegExp(r'^(\d+)').firstMatch(roomNumber);
if (match != null) {
final num = int.tryParse(match.group(1) ?? '');
if (num != null) {
if (num > 100) {
return num ~/ 100;
}
return num;
}
}
}
return 1;
}
List<Map<String, dynamic>> getRoomsByFloor(String floor) {
if (!_cacheValid) {
_roomCache = _buildRoomCache();
}
return _roomCache![floor] ?? [];
return _roomCache[floor] ?? [];
}
List<Map<String, dynamic>> _generateRooms(int floor, int count) {
final statuses = ['空闲', '入住', '待清洁'];
final types = ['客房', '大堂', '过道', '会议', '餐厅'];
final List<Map<String, dynamic>> rooms = [];
for (int i = 1; i <= count; i++) {
final roomNum = floor * 100 + i;
final statusIndex = (roomNum % 3);
final typeIndex = (roomNum % types.length);
rooms.add({
'number': roomNum.toString(),
'status': statuses[statusIndex],
'type': types[typeIndex],
});
}
return rooms;
void selectFloor(String? floor) {
emit(state.copyWith(selectedFloor: floor));
}
void selectType(String? type) {
emit(state.copyWith(selectedType: type));
}
void selectRoom(String? room) {
emit(state.copyWith(selectedRoom: room));
}
void setSearchText(String text) {
emit(state.copyWith(searchText: text));
}
void powerOnRoom(String roomNumber) {
// TODO: 对接 POST /app/room/device/power/on
// ignore: avoid_print
print('给 $roomNumber 号房送电');
}
void powerOffRoom(String roomNumber) {
// TODO: 对接 POST /app/room/device/power/off
// ignore: avoid_print
print('给 $roomNumber 号房断电');
}
......
......@@ -4,46 +4,60 @@ class ServiceIndexState extends Equatable {
final int pendingCount;
final int occupiedRooms;
final int vacantRooms;
final List<String> floors;
// final List<String> floors;
final List<Map<int,String>>? floors;
final List<String> areas;
final String? selectedFloor;
final String? selectedType;
final String? selectedRoom;
final String searchText;
final List<Map<String, dynamic>> rooms;
final bool isLoading;
final String? error;
const ServiceIndexState({
this.pendingCount = 0,
this.occupiedRooms = 0,
this.vacantRooms = 0,
this.floors = const [],
this.areas = const [],
this.selectedFloor,
this.selectedType,
this.selectedRoom,
this.searchText = '',
this.rooms = const [],
this.isLoading = false,
this.error,
});
ServiceIndexState copyWith({
int? pendingCount,
int? occupiedRooms,
int? vacantRooms,
List<String>? floors,
// List<String>? floors,
List<Map<int,String>>? floors,
List<String>? areas,
String? selectedFloor,
String? selectedType,
String? selectedRoom,
String? searchText,
List<Map<String, dynamic>>? rooms,
bool? isLoading,
String? error,
}) {
return ServiceIndexState(
pendingCount: pendingCount ?? this.pendingCount,
occupiedRooms: occupiedRooms ?? this.occupiedRooms,
vacantRooms: vacantRooms ?? this.vacantRooms,
floors: floors ?? this.floors,
areas: areas ?? this.areas,
selectedFloor: selectedFloor ?? this.selectedFloor,
selectedType: selectedType ?? this.selectedType,
selectedRoom: selectedRoom ?? this.selectedRoom,
searchText: searchText ?? this.searchText,
rooms: rooms ?? this.rooms,
isLoading: isLoading ?? this.isLoading,
error: error ?? this.error,
);
}
......@@ -53,10 +67,13 @@ class ServiceIndexState extends Equatable {
occupiedRooms,
vacantRooms,
floors,
areas,
selectedFloor,
selectedType,
selectedRoom,
searchText,
rooms,
isLoading,
error,
];
}
\ No newline at end of file
......@@ -4,6 +4,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/service/index/cubit/service_index_cubit.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:smart_hotel_app/routes/app_router.gr.dart';
import 'package:smart_hotel_app/views/service/index/cubit/service_index_state.dart';
class RoomManagementBlock extends StatefulWidget {
const RoomManagementBlock({super.key});
......@@ -24,8 +25,9 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
List<Map<String, dynamic>> allRooms;
if (_selectedFloor == null) {
allRooms = [];
for (final floor in cubit.state.floors) {
allRooms.addAll(cubit.getRoomsByFloor(floor));
for (final floorMap in (cubit.state.floors ?? [])) {
final floorName = floorMap.values.first;
allRooms.addAll(cubit.getRoomsByFloor(floorName));
}
} else {
allRooms = cubit.getRoomsByFloor(_selectedFloor!);
......@@ -50,7 +52,15 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
@override
Widget build(BuildContext context) {
final cubit = context.read<ServiceIndexCubit>();
return Container(
return BlocListener<ServiceIndexCubit, ServiceIndexState>(
listener: (context, state) {
if (_selectedFloor != state.selectedFloor) {
setState(() {
_selectedFloor = state.selectedFloor;
});
}
},
child: Container(
width: double.infinity,
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
......@@ -91,6 +101,7 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
_buildBottomButtons(cubit),
],
),
),
);
}
......@@ -99,22 +110,29 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: floors.map((floor) {
final isSelected = _selectedFloor == floor;
final displayFloor = floor.replaceAll('楼', '');
children: (floors ?? []).map((floorMap) {
final floorName = floorMap.values.first;
final floorId = floorMap.keys.first;
final isSelected = _selectedFloor == floorName;
final displayFloor = floorName;
return Padding(
padding: EdgeInsets.only(right: 24.w),
child: GestureDetector(
onTap: () {
setState(() {
if (_selectedFloor == floor) {
if (_selectedFloor == floorName) {
_selectedFloor = null;
} else {
_selectedFloor = floor;
_selectedFloor = floorName;
}
_searchController.clear();
_searchText = '';
});
// 切换楼层时重新请求接口
if (_selectedFloor != null) {
cubit.selectFloor(_selectedFloor);
cubit.loadRooms(floorId: floorId);
}
},
child: Column(
children: [
......@@ -150,13 +168,17 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
}
Widget _buildTypeSelector() {
final types = ['客房', '大堂', '过道', '会议', '餐厅'];
final cubit = context.read<ServiceIndexCubit>();
final types = cubit.state.areas.isNotEmpty
? cubit.state.areas
: [];
return Row(
children: types.asMap().entries.map((entry) {
final index = entry.key;
final type = entry.value;
final isSelected = _selectedType == type;
return Expanded(
return Container(
width: 80.w,
child: Padding(
padding:
EdgeInsets.only(right: index < types.length - 1 ? 10.w : 0),
......
......@@ -48,6 +48,7 @@ dependencies:
flutter_switch: ^0.3.2
fl_chart: ^0.71.0
infinite_scroll_pagination: ^5.1.1
logger: ^2.7.0
# fl_chart: ^0.71.0
# fl_chart: ^1.1.0
......
# 智慧酒店 App 接口对接方案
# 智慧酒店 App 接口对接方案
......@@ -212,6 +212,7 @@ class xxxBO extends Equatable {
| 客房服务看板-客房状态总览-楼层 |GET | /app/room/floor/areas | 未对接 | 客房服务看板-客房状态总览-楼层 | |
| 客房服务看板-客房状态总览-房间list |GET | /app/room/status | 未对接 | 客房服务看板-客房状态总览-房间list | |
| 客房服务看板-送电 |POST | /app/room/device/power/on | 未对接 | 客房服务看板-送电 | |
| 客房服务看板-断电 |POST | /app/room/device/power/off | 未对接 | 客房服务看板-断电 | |
......@@ -241,7 +242,7 @@ class xxxBO extends Equatable {
"msg": "string",
"data": [
{
"floorId": 0,
"floorId": 0,// 楼层,1,2,3,4,5楼
"areas": [ // 客房,大堂,过道等等
"string"
],
......
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