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

111

parent a7c9b9f7
...@@ -51,6 +51,11 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> { ...@@ -51,6 +51,11 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
AuthLogoutRequestedEvent event, AuthLogoutRequestedEvent event,
Emitter<AuthState> emit, Emitter<AuthState> emit,
) async { ) async {
try {
await _authService.logout();
} catch (_) {
// 即使接口调用失败也继续执行本地登出
}
await _storageService.deleteToken(); await _storageService.deleteToken();
await _storageService.deleteUserInfo(); await _storageService.deleteUserInfo();
await _storageService.deleteClientId(); await _storageService.deleteClientId();
......
...@@ -21,11 +21,12 @@ class AlertDashboardBO extends Equatable { ...@@ -21,11 +21,12 @@ class AlertDashboardBO extends Equatable {
statistics: AlertDashboardStatisticsBO.fromJson( statistics: AlertDashboardStatisticsBO.fromJson(
json['statistics'] as Map<String, dynamic>? ?? {}, json['statistics'] as Map<String, dynamic>? ?? {},
), ),
alerts: (json['alerts'] as List<dynamic>?) alerts: _sortedAlerts(
?.map((e) => (json['alerts'] as List<dynamic>?)
AlertItemBO.fromJson(e as Map<String, dynamic>)) ?.map((e) =>
.toList() ?? AlertItemBO.fromJson(e as Map<String, dynamic>))
[], .toList() ??
[]),
devices: (json['devices'] as List<dynamic>?) devices: (json['devices'] as List<dynamic>?)
?.map((e) => ?.map((e) =>
DeviceItemBO.fromJson(e as Map<String, dynamic>)) DeviceItemBO.fromJson(e as Map<String, dynamic>))
...@@ -34,6 +35,17 @@ class AlertDashboardBO extends Equatable { ...@@ -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 @override
List<Object?> get props => [total, statistics, alerts, devices]; 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 { ...@@ -18,4 +18,8 @@ class AuthRepository {
fromJsonT: (data) => UserInfoBO.fromJson(data as Map<String, dynamic>), 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 { ...@@ -50,6 +50,13 @@ class AuthService {
return _storageService.getUserInfo(); return _storageService.getUserInfo();
} }
Future<void> logout() async {
final result = await _authRepository.logout();
if (!result.success) {
throw Exception(result.msg);
}
}
Future<bool> isTokenExpired() async { Future<bool> isTokenExpired() async {
return _storageService.isTokenExpired(); 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> { ...@@ -34,7 +34,7 @@ class HomeIndexCubit extends Cubit<HomeIndexState> {
highAlarmCount: dashboard.statistics.highAlertCount, highAlarmCount: dashboard.statistics.highAlertCount,
mediumAlarmCount: dashboard.statistics.mediumAlertCount, mediumAlarmCount: dashboard.statistics.mediumAlertCount,
onlineDeviceCount: dashboard.statistics.onlineDeviceCount, onlineDeviceCount: dashboard.statistics.onlineDeviceCount,
alerts: dashboard.alerts, alerts: dashboard.alerts.take(5).toList(),
equipmentList: dashboard.devices equipmentList: dashboard.devices
.map((d) => _mapDeviceToEquipment(d)) .map((d) => _mapDeviceToEquipment(d))
.toList(), .toList(),
......
...@@ -17,25 +17,44 @@ class ProfileState extends Equatable { ...@@ -17,25 +17,44 @@ class ProfileState extends Equatable {
final String username; final String username;
final String welcomeText; final String welcomeText;
final List<MenuItem> menuList; final List<MenuItem> menuList;
final bool isLoggingOut;
final String? logoutError;
final bool logoutSuccess;
const ProfileState({ const ProfileState({
this.username = '', this.username = '',
this.welcomeText = '', this.welcomeText = '',
this.menuList = const [], this.menuList = const [],
this.isLoggingOut = false,
this.logoutError,
this.logoutSuccess = false,
}); });
ProfileState copyWith({ ProfileState copyWith({
String? username, String? username,
String? welcomeText, String? welcomeText,
List<MenuItem>? menuList, List<MenuItem>? menuList,
bool? isLoggingOut,
String? logoutError,
bool? logoutSuccess,
}) { }) {
return ProfileState( return ProfileState(
username: username ?? this.username, username: username ?? this.username,
welcomeText: welcomeText ?? this.welcomeText, welcomeText: welcomeText ?? this.welcomeText,
menuList: menuList ?? this.menuList, menuList: menuList ?? this.menuList,
isLoggingOut: isLoggingOut ?? this.isLoggingOut,
logoutError: logoutError ?? this.logoutError,
logoutSuccess: logoutSuccess ?? this.logoutSuccess,
); );
} }
@override @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'; ...@@ -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/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_cubit.dart';
import 'package:smart_hotel_app/views/profile/index/cubit/profile_state.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'; import 'package:smart_hotel_app/views/profile/index/widget/profile_header_block.dart';
...@@ -15,38 +17,52 @@ class ProfileView extends StatelessWidget { ...@@ -15,38 +17,52 @@ class ProfileView extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (_) => ProfileCubit(), create: (_) => ProfileCubit(),
child: LayoutBuilder( child: _ProfileContent(),
builder: (context, constraints) { );
return SingleChildScrollView( }
child: ConstrainedBox( }
constraints: BoxConstraints(
minHeight: constraints.maxHeight, class _ProfileContent extends StatelessWidget {
), @override
child: Container( Widget build(BuildContext context) {
width: double.infinity, return BlocBuilder<ProfileCubit, ProfileState>(
color: const Color.fromRGBO(242, 243, 245, 1), builder: (context, state) {
child: BlocBuilder<ProfileCubit, ProfileState>( return LayoutBuilder(
builder: (context, state) { builder: (context, constraints) {
return Column( return SingleChildScrollView(
children: [ child: ConstrainedBox(
ProfileHeaderBlock( constraints: BoxConstraints(
username: state.username, minHeight: constraints.maxHeight,
welcomeText: state.welcomeText, ),
), child: Container(
SizedBox(height: 10.h), width: double.infinity,
Padding( color: const Color.fromRGBO(242, 243, 245, 1),
padding: EdgeInsets.symmetric(horizontal: 28.w), child: Column(
child: ProfileMenuBlock(menuList: state.menuList), 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'; ...@@ -6,8 +6,9 @@ import 'package:smart_hotel_app/views/profile/index/cubit/profile_state.dart';
class ProfileMenuBlock extends StatelessWidget { class ProfileMenuBlock extends StatelessWidget {
final List<MenuItem> menuList; 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) { Widget _buildMenuItem(BuildContext context, MenuItem item) {
return GestureDetector( return GestureDetector(
...@@ -72,6 +73,9 @@ class ProfileMenuBlock extends StatelessWidget { ...@@ -72,6 +73,9 @@ class ProfileMenuBlock extends StatelessWidget {
case '告警信息': case '告警信息':
context.pushRoute(const AbnormalListRoute()); context.pushRoute(const AbnormalListRoute());
break; break;
case '退出登录':
onLogout?.call();
break;
default: default:
break; break;
} }
......
...@@ -3,20 +3,45 @@ import 'package:smart_hotel_app/views/service/index/cubit/service_index_state.da ...@@ -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/services/room_service.dart';
import 'package:smart_hotel_app/repositories/room_repository.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/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> { class ServiceIndexCubit extends Cubit<ServiceIndexState> {
final RoomService _roomService; final RoomService _roomService;
final RoomStatisticsService _statisticsService;
final Map<String, List<Map<String, dynamic>>> _roomCache = {}; final Map<String, List<Map<String, dynamic>>> _roomCache = {};
List<FloorAreaBO> _floorAreasCache = []; List<FloorAreaBO> _floorAreasCache = [];
ServiceIndexCubit() ServiceIndexCubit()
: _roomService = RoomService(repository: RoomRepository()), : _roomService = RoomService(repository: RoomRepository()),
_statisticsService = RoomStatisticsService(
repository: RoomStatisticsRepository(),
),
super(const ServiceIndexState()) { super(const ServiceIndexState()) {
_init(); _init();
} }
Future<void> _init() async { 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 { ...@@ -237,10 +237,80 @@ class xxxBO extends Equatable {
| 退出登录 |POST | /app/auth/logout | 已对接 | 退出登录 | |
| 客房服务看板-统计 |GET | /app/room/statistics | 已对接 | 客房服务看板-统计 | |
### 3.2 接口详细定义 ### 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** **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