Commit 168178cd authored by 张宏's avatar 张宏

111

parent a7c9b9f7
......@@ -51,6 +51,11 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
AuthLogoutRequestedEvent event,
Emitter<AuthState> emit,
) async {
try {
await _authService.logout();
} catch (_) {
// 即使接口调用失败也继续执行本地登出
}
await _storageService.deleteToken();
await _storageService.deleteUserInfo();
await _storageService.deleteClientId();
......
......@@ -21,11 +21,12 @@ class AlertDashboardBO extends Equatable {
statistics: AlertDashboardStatisticsBO.fromJson(
json['statistics'] as Map<String, dynamic>? ?? {},
),
alerts: (json['alerts'] as List<dynamic>?)
?.map((e) =>
AlertItemBO.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
alerts: _sortedAlerts(
(json['alerts'] as List<dynamic>?)
?.map((e) =>
AlertItemBO.fromJson(e as Map<String, dynamic>))
.toList() ??
[]),
devices: (json['devices'] as List<dynamic>?)
?.map((e) =>
DeviceItemBO.fromJson(e as Map<String, dynamic>))
......@@ -34,6 +35,17 @@ class AlertDashboardBO extends Equatable {
);
}
/// 根据 alertLevelText 排序:置顶 > 高警 > 中警 > 一般
static List<AlertItemBO> _sortedAlerts(List<AlertItemBO> alerts) {
const order = {'置顶': 0, '高警': 1, '中警': 2, '一般': 3};
return List<AlertItemBO>.from(alerts)
..sort((a, b) {
final aOrder = order[a.alertLevelText] ?? 4;
final bOrder = order[b.alertLevelText] ?? 4;
return aOrder.compareTo(bOrder);
});
}
@override
List<Object?> get props => [total, statistics, alerts, devices];
}
......
import 'package:equatable/equatable.dart';
class RoomStatisticsBO extends Equatable {
/// 待响应房间数
final int pendingResponseCount;
/// 入住中房间数
final int occupiedCount;
/// 空闲可清洁房间数
final int vacantCleanableCount;
const RoomStatisticsBO({
this.pendingResponseCount = 0,
this.occupiedCount = 0,
this.vacantCleanableCount = 0,
});
factory RoomStatisticsBO.fromJson(Map<String, dynamic> json) {
return RoomStatisticsBO(
pendingResponseCount: json['pendingResponseCount'] as int? ?? 0,
occupiedCount: json['occupiedCount'] as int? ?? 0,
vacantCleanableCount: json['vacantCleanableCount'] as int? ?? 0,
);
}
@override
List<Object?> get props =>
[pendingResponseCount, occupiedCount, vacantCleanableCount];
}
......@@ -18,4 +18,8 @@ class AuthRepository {
fromJsonT: (data) => UserInfoBO.fromJson(data as Map<String, dynamic>),
);
}
Future<ResponseModel> logout() {
return DioRequest.instance.post('/app/auth/logout');
}
}
import '../../models/bo/room_statistics_bo.dart';
import '../../utils/http/dio_request.dart';
import '../../utils/http/response_model.dart';
class RoomStatisticsRepository {
/// GET /app/room/statistics - 客房服务看板-统计
Future<ResponseModel<RoomStatisticsBO>> getStatistics() {
return DioRequest.instance.get<RoomStatisticsBO>(
'/app/room/statistics',
fromJsonT: (data) =>
RoomStatisticsBO.fromJson(data as Map<String, dynamic>),
);
}
}
......@@ -50,6 +50,13 @@ class AuthService {
return _storageService.getUserInfo();
}
Future<void> logout() async {
final result = await _authRepository.logout();
if (!result.success) {
throw Exception(result.msg);
}
}
Future<bool> isTokenExpired() async {
return _storageService.isTokenExpired();
}
......
import '../models/bo/room_statistics_bo.dart';
import '../repositories/room_statistics_repository.dart';
class RoomStatisticsService {
final RoomStatisticsRepository _repository;
RoomStatisticsService({required RoomStatisticsRepository repository})
: _repository = repository;
Future<RoomStatisticsBO> getStatistics() async {
final result = await _repository.getStatistics();
if (result.success && result.data != null) {
return result.data!;
}
throw Exception(result.msg);
}
}
......@@ -34,7 +34,7 @@ class HomeIndexCubit extends Cubit<HomeIndexState> {
highAlarmCount: dashboard.statistics.highAlertCount,
mediumAlarmCount: dashboard.statistics.mediumAlertCount,
onlineDeviceCount: dashboard.statistics.onlineDeviceCount,
alerts: dashboard.alerts,
alerts: dashboard.alerts.take(5).toList(),
equipmentList: dashboard.devices
.map((d) => _mapDeviceToEquipment(d))
.toList(),
......
......@@ -17,25 +17,44 @@ class ProfileState extends Equatable {
final String username;
final String welcomeText;
final List<MenuItem> menuList;
final bool isLoggingOut;
final String? logoutError;
final bool logoutSuccess;
const ProfileState({
this.username = '',
this.welcomeText = '',
this.menuList = const [],
this.isLoggingOut = false,
this.logoutError,
this.logoutSuccess = false,
});
ProfileState copyWith({
String? username,
String? welcomeText,
List<MenuItem>? menuList,
bool? isLoggingOut,
String? logoutError,
bool? logoutSuccess,
}) {
return ProfileState(
username: username ?? this.username,
welcomeText: welcomeText ?? this.welcomeText,
menuList: menuList ?? this.menuList,
isLoggingOut: isLoggingOut ?? this.isLoggingOut,
logoutError: logoutError ?? this.logoutError,
logoutSuccess: logoutSuccess ?? this.logoutSuccess,
);
}
@override
List<Object?> get props => [username, welcomeText, menuList];
List<Object?> get props => [
username,
welcomeText,
menuList,
isLoggingOut,
logoutError,
logoutSuccess,
];
}
\ No newline at end of file
......@@ -2,6 +2,8 @@ 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/blocs/auth/auth_bloc.dart';
import 'package:smart_hotel_app/blocs/auth/auth_event.dart';
import 'package:smart_hotel_app/views/profile/index/cubit/profile_cubit.dart';
import 'package:smart_hotel_app/views/profile/index/cubit/profile_state.dart';
import 'package:smart_hotel_app/views/profile/index/widget/profile_header_block.dart';
......@@ -15,38 +17,52 @@ class ProfileView extends StatelessWidget {
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => ProfileCubit(),
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: Container(
width: double.infinity,
color: const Color.fromRGBO(242, 243, 245, 1),
child: BlocBuilder<ProfileCubit, ProfileState>(
builder: (context, state) {
return Column(
children: [
ProfileHeaderBlock(
username: state.username,
welcomeText: state.welcomeText,
),
SizedBox(height: 10.h),
Padding(
padding: EdgeInsets.symmetric(horizontal: 28.w),
child: ProfileMenuBlock(menuList: state.menuList),
child: _ProfileContent(),
);
}
}
class _ProfileContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<ProfileCubit, ProfileState>(
builder: (context, state) {
return LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: Container(
width: double.infinity,
color: const Color.fromRGBO(242, 243, 245, 1),
child: Column(
children: [
ProfileHeaderBlock(
username: state.username,
welcomeText: state.welcomeText,
),
SizedBox(height: 10.h),
Padding(
padding: EdgeInsets.symmetric(horizontal: 28.w),
child: ProfileMenuBlock(
menuList: state.menuList,
onLogout: () {
context
.read<AuthBloc>()
.add(const AuthLogoutRequestedEvent());
},
),
],
);
},
),
],
),
),
),
),
);
},
),
);
},
);
},
);
}
}
\ No newline at end of file
}
......@@ -6,8 +6,9 @@ import 'package:smart_hotel_app/views/profile/index/cubit/profile_state.dart';
class ProfileMenuBlock extends StatelessWidget {
final List<MenuItem> menuList;
final VoidCallback? onLogout;
const ProfileMenuBlock({super.key, required this.menuList});
const ProfileMenuBlock({super.key, required this.menuList, this.onLogout});
Widget _buildMenuItem(BuildContext context, MenuItem item) {
return GestureDetector(
......@@ -72,6 +73,9 @@ class ProfileMenuBlock extends StatelessWidget {
case '告警信息':
context.pushRoute(const AbnormalListRoute());
break;
case '退出登录':
onLogout?.call();
break;
default:
break;
}
......
......@@ -3,20 +3,45 @@ import 'package:smart_hotel_app/views/service/index/cubit/service_index_state.da
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';
import 'package:smart_hotel_app/services/room_statistics_service.dart';
import 'package:smart_hotel_app/repositories/room_statistics_repository.dart';
class ServiceIndexCubit extends Cubit<ServiceIndexState> {
final RoomService _roomService;
final RoomStatisticsService _statisticsService;
final Map<String, List<Map<String, dynamic>>> _roomCache = {};
List<FloorAreaBO> _floorAreasCache = [];
ServiceIndexCubit()
: _roomService = RoomService(repository: RoomRepository()),
_statisticsService = RoomStatisticsService(
repository: RoomStatisticsRepository(),
),
super(const ServiceIndexState()) {
_init();
}
Future<void> _init() async {
await loadFloorAndAreas();
await Future.wait([
loadStatistics(),
loadFloorAndAreas(),
]);
}
/// 加载客房服务看板-统计数据(待响应、入住中、空闲可清洁)
Future<void> loadStatistics() async {
emit(state.copyWith(isLoading: true, error: null));
try {
final stats = await _statisticsService.getStatistics();
emit(state.copyWith(
isLoading: false,
pendingCount: stats.pendingResponseCount,
occupiedRooms: stats.occupiedCount,
vacantRooms: stats.vacantCleanableCount,
));
} catch (e) {
emit(state.copyWith(isLoading: false, error: e.toString()));
}
}
/// 加载楼层和区域信息(仅进入页面时调用一次)
......
# 智慧酒店 App 接口对接方案
# 智慧酒店 App 接口对接方案
......@@ -237,10 +237,80 @@ class xxxBO extends Equatable {
| 退出登录 |POST | /app/auth/logout | 已对接 | 退出登录 | |
| 客房服务看板-统计 |GET | /app/room/statistics | 已对接 | 客房服务看板-统计 | |
### 3.2 接口详细定义
#### 非客房-相关楼层区域设备
**GET /app/room/area/devices**
```
求体:
{
"floorId":0,// 楼层
"areaType":'', // 区域
}
应 data:
{
"code": 0,
"msg": "string",
"data": [
{
"deviceName": "string", // 设备名称
"deviceCode": "string", // 设备编码
"runStatus": "string" // 运行状态:normal=正常 warning=预警 fault=故障 offline=离线
}
]
}
```
#### 客房服务看板-统计
**GET /app/room/statistics**
```
求体:
{
}
应 data:
{
"code": 0,
"msg": "string",
"data": {
"pendingResponseCount": 0, // 待响应房间数
"occupiedCount": 0, // 入住中房间数:有人(occupancy_status='1') 或 已入住(sell_status='3')
"vacantCleanableCount": 0 // 空闲可清洁房间数:已退房(checkout_status='1') 且 待清洁(clean_status='0')
}
}
```
#### 退出登录
**POST /app/auth/logout**
```
求体:
{
}
应 data:
{
"code": 0,
"msg": "string",
"data": "string"
}
```
#### 联动规则管理-启用/禁用
**PUT /app/linkage/rules/toggle**
......
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