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:flutter_bloc/flutter_bloc.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/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> { 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()) { ServiceIndexCubit()
initData(); : _roomService = RoomService(repository: RoomRepository()),
super(const ServiceIndexState()) {
_init();
} }
void initData() { Future<void> _init() async {
emit(state.copyWith( await loadFloorAndAreas();
pendingCount: 2,
occupiedRooms: 45,
vacantRooms: 7,
floors: ['1楼', '2楼', '3楼', '4楼', '5楼', '6楼'],
));
} }
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(
emit(state.copyWith(selectedType: type)); 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 { return {
'1楼': _generateRooms(1, 20), 'number': rs.roomNumber,
'2楼': _generateRooms(2, Random().nextInt(21) + 10), 'status': displayStatus,
'3楼': _generateRooms(3, Random().nextInt(21) + 10), 'type': '客房', // 默认类型,后续可通过楼层areas信息扩展
'4楼': _generateRooms(4, Random().nextInt(21) + 10),
'5楼': _generateRooms(5, Random().nextInt(5) + 10),
'6楼': _generateRooms(6, Random().nextInt(21) + 10),
}; };
} }
bool get _cacheValid { /// 从房间号提取楼层,如 "301" → 3
if (_roomCache == null) return false; int _extractFloor(String roomNumber) {
final firstFloor = _roomCache!['1楼']; if (roomNumber.length >= 2) {
if (firstFloor == null || firstFloor.isEmpty) return false; final match = RegExp(r'^(\d+)').firstMatch(roomNumber);
return firstFloor.first.containsKey('type'); 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) { List<Map<String, dynamic>> getRoomsByFloor(String floor) {
if (!_cacheValid) { return _roomCache[floor] ?? [];
_roomCache = _buildRoomCache();
}
return _roomCache![floor] ?? [];
} }
List<Map<String, dynamic>> _generateRooms(int floor, int count) { void selectFloor(String? floor) {
final statuses = ['空闲', '入住', '待清洁']; emit(state.copyWith(selectedFloor: floor));
final types = ['客房', '大堂', '过道', '会议', '餐厅']; }
final List<Map<String, dynamic>> rooms = [];
void selectType(String? type) {
for (int i = 1; i <= count; i++) { emit(state.copyWith(selectedType: type));
final roomNum = floor * 100 + i; }
final statusIndex = (roomNum % 3);
final typeIndex = (roomNum % types.length); void selectRoom(String? room) {
rooms.add({ emit(state.copyWith(selectedRoom: room));
'number': roomNum.toString(), }
'status': statuses[statusIndex],
'type': types[typeIndex], void setSearchText(String text) {
}); emit(state.copyWith(searchText: text));
}
return rooms;
} }
void powerOnRoom(String roomNumber) { void powerOnRoom(String roomNumber) {
// TODO: 对接 POST /app/room/device/power/on
// ignore: avoid_print // ignore: avoid_print
print('给 $roomNumber 号房送电'); print('给 $roomNumber 号房送电');
} }
void powerOffRoom(String roomNumber) { void powerOffRoom(String roomNumber) {
// TODO: 对接 POST /app/room/device/power/off
// ignore: avoid_print // ignore: avoid_print
print('给 $roomNumber 号房断电'); print('给 $roomNumber 号房断电');
} }
......
...@@ -4,46 +4,60 @@ class ServiceIndexState extends Equatable { ...@@ -4,46 +4,60 @@ class ServiceIndexState extends Equatable {
final int pendingCount; final int pendingCount;
final int occupiedRooms; final int occupiedRooms;
final int vacantRooms; 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? selectedFloor;
final String? selectedType; final String? selectedType;
final String? selectedRoom; final String? selectedRoom;
final String searchText; final String searchText;
final List<Map<String, dynamic>> rooms; final List<Map<String, dynamic>> rooms;
final bool isLoading;
final String? error;
const ServiceIndexState({ const ServiceIndexState({
this.pendingCount = 0, this.pendingCount = 0,
this.occupiedRooms = 0, this.occupiedRooms = 0,
this.vacantRooms = 0, this.vacantRooms = 0,
this.floors = const [], this.floors = const [],
this.areas = const [],
this.selectedFloor, this.selectedFloor,
this.selectedType, this.selectedType,
this.selectedRoom, this.selectedRoom,
this.searchText = '', this.searchText = '',
this.rooms = const [], this.rooms = const [],
this.isLoading = false,
this.error,
}); });
ServiceIndexState copyWith({ ServiceIndexState copyWith({
int? pendingCount, int? pendingCount,
int? occupiedRooms, int? occupiedRooms,
int? vacantRooms, int? vacantRooms,
List<String>? floors, // List<String>? floors,
List<Map<int,String>>? floors,
List<String>? areas,
String? selectedFloor, String? selectedFloor,
String? selectedType, String? selectedType,
String? selectedRoom, String? selectedRoom,
String? searchText, String? searchText,
List<Map<String, dynamic>>? rooms, List<Map<String, dynamic>>? rooms,
bool? isLoading,
String? error,
}) { }) {
return ServiceIndexState( return ServiceIndexState(
pendingCount: pendingCount ?? this.pendingCount, pendingCount: pendingCount ?? this.pendingCount,
occupiedRooms: occupiedRooms ?? this.occupiedRooms, occupiedRooms: occupiedRooms ?? this.occupiedRooms,
vacantRooms: vacantRooms ?? this.vacantRooms, vacantRooms: vacantRooms ?? this.vacantRooms,
floors: floors ?? this.floors, floors: floors ?? this.floors,
areas: areas ?? this.areas,
selectedFloor: selectedFloor ?? this.selectedFloor, selectedFloor: selectedFloor ?? this.selectedFloor,
selectedType: selectedType ?? this.selectedType, selectedType: selectedType ?? this.selectedType,
selectedRoom: selectedRoom ?? this.selectedRoom, selectedRoom: selectedRoom ?? this.selectedRoom,
searchText: searchText ?? this.searchText, searchText: searchText ?? this.searchText,
rooms: rooms ?? this.rooms, rooms: rooms ?? this.rooms,
isLoading: isLoading ?? this.isLoading,
error: error ?? this.error,
); );
} }
...@@ -53,10 +67,13 @@ class ServiceIndexState extends Equatable { ...@@ -53,10 +67,13 @@ class ServiceIndexState extends Equatable {
occupiedRooms, occupiedRooms,
vacantRooms, vacantRooms,
floors, floors,
areas,
selectedFloor, selectedFloor,
selectedType, selectedType,
selectedRoom, selectedRoom,
searchText, searchText,
rooms, rooms,
isLoading,
error,
]; ];
} }
\ No newline at end of file
...@@ -4,6 +4,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart'; ...@@ -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:smart_hotel_app/views/service/index/cubit/service_index_cubit.dart';
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';
class RoomManagementBlock extends StatefulWidget { class RoomManagementBlock extends StatefulWidget {
const RoomManagementBlock({super.key}); const RoomManagementBlock({super.key});
...@@ -24,8 +25,9 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> { ...@@ -24,8 +25,9 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
List<Map<String, dynamic>> allRooms; List<Map<String, dynamic>> allRooms;
if (_selectedFloor == null) { if (_selectedFloor == null) {
allRooms = []; allRooms = [];
for (final floor in cubit.state.floors) { for (final floorMap in (cubit.state.floors ?? [])) {
allRooms.addAll(cubit.getRoomsByFloor(floor)); final floorName = floorMap.values.first;
allRooms.addAll(cubit.getRoomsByFloor(floorName));
} }
} else { } else {
allRooms = cubit.getRoomsByFloor(_selectedFloor!); allRooms = cubit.getRoomsByFloor(_selectedFloor!);
...@@ -50,7 +52,15 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> { ...@@ -50,7 +52,15 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cubit = context.read<ServiceIndexCubit>(); 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, width: double.infinity,
padding: EdgeInsets.all(20.w), padding: EdgeInsets.all(20.w),
decoration: BoxDecoration( decoration: BoxDecoration(
...@@ -91,6 +101,7 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> { ...@@ -91,6 +101,7 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
_buildBottomButtons(cubit), _buildBottomButtons(cubit),
], ],
), ),
),
); );
} }
...@@ -99,22 +110,29 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> { ...@@ -99,22 +110,29 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
return SingleChildScrollView( return SingleChildScrollView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
child: Row( child: Row(
children: floors.map((floor) { children: (floors ?? []).map((floorMap) {
final isSelected = _selectedFloor == floor; final floorName = floorMap.values.first;
final displayFloor = floor.replaceAll('楼', ''); final floorId = floorMap.keys.first;
final isSelected = _selectedFloor == floorName;
final displayFloor = floorName;
return Padding( return Padding(
padding: EdgeInsets.only(right: 24.w), padding: EdgeInsets.only(right: 24.w),
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
setState(() { setState(() {
if (_selectedFloor == floor) { if (_selectedFloor == floorName) {
_selectedFloor = null; _selectedFloor = null;
} else { } else {
_selectedFloor = floor; _selectedFloor = floorName;
} }
_searchController.clear(); _searchController.clear();
_searchText = ''; _searchText = '';
}); });
// 切换楼层时重新请求接口
if (_selectedFloor != null) {
cubit.selectFloor(_selectedFloor);
cubit.loadRooms(floorId: floorId);
}
}, },
child: Column( child: Column(
children: [ children: [
...@@ -150,13 +168,17 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> { ...@@ -150,13 +168,17 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
} }
Widget _buildTypeSelector() { Widget _buildTypeSelector() {
final types = ['客房', '大堂', '过道', '会议', '餐厅']; final cubit = context.read<ServiceIndexCubit>();
final types = cubit.state.areas.isNotEmpty
? cubit.state.areas
: [];
return Row( return Row(
children: types.asMap().entries.map((entry) { children: types.asMap().entries.map((entry) {
final index = entry.key; final index = entry.key;
final type = entry.value; final type = entry.value;
final isSelected = _selectedType == type; final isSelected = _selectedType == type;
return Expanded( return Container(
width: 80.w,
child: Padding( child: Padding(
padding: padding:
EdgeInsets.only(right: index < types.length - 1 ? 10.w : 0), EdgeInsets.only(right: index < types.length - 1 ? 10.w : 0),
......
...@@ -48,6 +48,7 @@ dependencies: ...@@ -48,6 +48,7 @@ dependencies:
flutter_switch: ^0.3.2 flutter_switch: ^0.3.2
fl_chart: ^0.71.0 fl_chart: ^0.71.0
infinite_scroll_pagination: ^5.1.1 infinite_scroll_pagination: ^5.1.1
logger: ^2.7.0
# fl_chart: ^0.71.0 # fl_chart: ^0.71.0
# fl_chart: ^1.1.0 # fl_chart: ^1.1.0
......
# 智慧酒店 App 接口对接方案 # 智慧酒店 App 接口对接方案
...@@ -212,6 +212,7 @@ class xxxBO extends Equatable { ...@@ -212,6 +212,7 @@ class xxxBO extends Equatable {
| 客房服务看板-客房状态总览-楼层 |GET | /app/room/floor/areas | 未对接 | 客房服务看板-客房状态总览-楼层 | | | 客房服务看板-客房状态总览-楼层 |GET | /app/room/floor/areas | 未对接 | 客房服务看板-客房状态总览-楼层 | |
| 客房服务看板-客房状态总览-房间list |GET | /app/room/status | 未对接 | 客房服务看板-客房状态总览-房间list | | | 客房服务看板-客房状态总览-房间list |GET | /app/room/status | 未对接 | 客房服务看板-客房状态总览-房间list | |
| 客房服务看板-送电 |POST | /app/room/device/power/on | 未对接 | 客房服务看板-送电 | | | 客房服务看板-送电 |POST | /app/room/device/power/on | 未对接 | 客房服务看板-送电 | |
| 客房服务看板-断电 |POST | /app/room/device/power/off | 未对接 | 客房服务看板-断电 | | | 客房服务看板-断电 |POST | /app/room/device/power/off | 未对接 | 客房服务看板-断电 | |
...@@ -241,7 +242,7 @@ class xxxBO extends Equatable { ...@@ -241,7 +242,7 @@ class xxxBO extends Equatable {
"msg": "string", "msg": "string",
"data": [ "data": [
{ {
"floorId": 0, "floorId": 0,// 楼层,1,2,3,4,5楼
"areas": [ // 客房,大堂,过道等等 "areas": [ // 客房,大堂,过道等等
"string" "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