Commit e4085511 authored by 张宏's avatar 张宏

parent 77fb6258
import 'package:equatable/equatable.dart';
class AlertListBO extends Equatable {
final int total;
final AlertListTabCountBO tabCount;
final List<AlertListItemBO> rows;
const AlertListBO({
required this.total,
required this.tabCount,
required this.rows,
});
factory AlertListBO.fromJson(Map<String, dynamic> json) {
return AlertListBO(
total: json['total'] as int? ?? 0,
tabCount: AlertListTabCountBO.fromJson(
json['tabCount'] as Map<String, dynamic>? ?? {},
),
rows: (json['rows'] as List<dynamic>?)
?.map(
(e) => AlertListItemBO.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
);
}
@override
List<Object?> get props => [total, tabCount, rows];
}
class AlertListTabCountBO extends Equatable {
final int all;
final int pending;
final int processed;
final int falseAlarm;
const AlertListTabCountBO({
required this.all,
required this.pending,
required this.processed,
required this.falseAlarm,
});
factory AlertListTabCountBO.fromJson(Map<String, dynamic> json) {
return AlertListTabCountBO(
all: json['all'] as int? ?? 0,
pending: json['pending'] as int? ?? 0,
processed: json['processed'] as int? ?? 0,
falseAlarm: json['falseAlarm'] as int? ?? 0,
);
}
@override
List<Object?> get props => [all, pending, processed, falseAlarm];
}
class AlertListItemBO extends Equatable {
final int alertId;
final String alertTitle;
final String alertIcon;
final String deviceLocationText;
final String alertTime;
final String handleTime;
final String alertLevel;
final String alertStatus;
const AlertListItemBO({
required this.alertId,
required this.alertTitle,
required this.alertIcon,
required this.deviceLocationText,
required this.alertTime,
required this.handleTime,
required this.alertLevel,
required this.alertStatus,
});
factory AlertListItemBO.fromJson(Map<String, dynamic> json) {
return AlertListItemBO(
alertId: json['alertId'] as int? ?? 0,
alertTitle: json['alertTitle'] as String? ?? '',
alertIcon: json['alertIcon'] as String? ?? '',
deviceLocationText: json['deviceLocationText'] as String? ?? '',
alertTime: json['alertTime'] as String? ?? '',
handleTime: json['handleTime'] as String? ?? '',
alertLevel: json['alertLevel'] as String? ?? '',
alertStatus: json['alertStatus'] as String? ?? '',
);
}
@override
List<Object?> get props => [
alertId,
alertTitle,
alertIcon,
deviceLocationText,
alertTime,
handleTime,
alertLevel,
alertStatus,
];
}
\ No newline at end of file
import 'package:equatable/equatable.dart';
class InspectionRoomBO extends Equatable {
final int roomId;
final String roomNumber;
final InspectionRoomRunStatusBO runStatus;
const InspectionRoomBO({
required this.roomId,
required this.roomNumber,
required this.runStatus,
});
factory InspectionRoomBO.fromJson(Map<String, dynamic> json) {
return InspectionRoomBO(
roomId: json['roomId'] as int? ?? 0,
roomNumber: json['roomNumber'] as String? ?? '',
runStatus: InspectionRoomRunStatusBO.fromJson(
json['runStatus'] as Map<String, dynamic>? ?? {},
),
);
}
@override
List<Object?> get props => [roomId, roomNumber, runStatus];
}
class InspectionRoomRunStatusBO extends Equatable {
final int normal;
final int warning;
final int offline;
final int fault;
const InspectionRoomRunStatusBO({
required this.normal,
required this.warning,
required this.offline,
required this.fault,
});
factory InspectionRoomRunStatusBO.fromJson(Map<String, dynamic> json) {
return InspectionRoomRunStatusBO(
normal: json['normal'] as int? ?? 0,
warning: json['warning'] as int? ?? 0,
offline: json['offline'] as int? ?? 0,
fault: json['fault'] as int? ?? 0,
);
}
@override
List<Object?> get props => [normal, warning, offline, fault];
}
\ No newline at end of file
import '../utils/http/response_model.dart';
import '../utils/http/dio_request.dart';
import '../models/bo/alert_list_bo.dart';
class AlertListRepository {
/// 获取告警列表
Future<ResponseModel<AlertListBO>> getList() {
return DioRequest.instance.get<AlertListBO>(
'/app/alert/list',
fromJsonT: (data) =>
AlertListBO.fromJson(data as Map<String, dynamic>),
);
}
}
\ No newline at end of file
import '../utils/http/response_model.dart';
import '../utils/http/dio_request.dart';
import '../models/bo/inspection_room_bo.dart';
class InspectionRoomRepository {
/// 获取设备巡检房间列表
Future<ResponseModel<List<InspectionRoomBO>>> getRooms() {
return DioRequest.instance.get<List<InspectionRoomBO>>(
'/app/device/inspection/rooms',
fromJsonT: (data) {
final list = data as List<dynamic>;
return list
.map((e) => InspectionRoomBO.fromJson(e as Map<String, dynamic>))
.toList();
},
);
}
}
\ No newline at end of file
import '../models/bo/alert_list_bo.dart';
import '../repositories/alert_list_repository.dart';
class AlertListService {
final AlertListRepository _repository;
AlertListService({required AlertListRepository repository})
: _repository = repository;
Future<AlertListBO> getList() async {
final result = await _repository.getList();
if (result.success && result.data != null) {
return result.data!;
}
throw Exception(result.msg);
}
}
\ No newline at end of file
import '../models/bo/inspection_room_bo.dart';
import '../repositories/inspection_room_repository.dart';
class InspectionRoomService {
final InspectionRoomRepository _repository;
InspectionRoomService({required InspectionRoomRepository repository})
: _repository = repository;
Future<List<InspectionRoomBO>> getRooms() async {
final result = await _repository.getRooms();
if (result.success && result.data != null) {
return result.data!;
}
throw Exception(result.msg);
}
}
\ No newline at end of file
import 'package:auto_route/auto_route.dart';
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/alert_list_repository.dart';
import 'package:smart_hotel_app/routes/app_router.gr.dart';
import 'package:smart_hotel_app/services/alert_list_service.dart';
import 'package:smart_hotel_app/views/home/abnormal_list/cubit/abnormal_list_state.dart'; import 'package:smart_hotel_app/views/home/abnormal_list/cubit/abnormal_list_state.dart';
class AbnormalListCubit extends Cubit<AbnormalListState> { class AbnormalListCubit extends Cubit<AbnormalListState> {
AbnormalListCubit() : super(AbnormalListState(currentFilter: AlarmStatus.all, alarmList: _getAlarmList())); final AlertListService _service;
static List<AlarmItem> _getAlarmList() { AbnormalListCubit()
return [ : _service = AlertListService(
const AlarmItem( repository: AlertListRepository(),
id: '1', ),
title: '温度异常告警', super(const AbnormalListState()) {
deviceInfo: 'A-301智能空开·客房301', loadList();
alarmTime: '2026-5-30 16:32:31', }
processTime: '2026-5-30 16:32:31',
status: AlarmStatus.pending, Future<void> loadList() async {
level: '高警', emit(const AbnormalListState(isLoading: true));
urgency: '紧急', try {
), final alertList = await _service.getList();
const AlarmItem( emit(AbnormalListState(
id: '2', alarmList:
title: '温度异常告警', alertList.rows.map((e) => AlarmItem.fromAlertListItemBO(e)).toList(),
deviceInfo: 'A-301智能空开·客房301', allCount: alertList.tabCount.all,
alarmTime: '2026-5-30 16:32:31', pendingCount: alertList.tabCount.pending,
processTime: '2026-5-30 16:32:31', processedCount: alertList.tabCount.processed,
status: AlarmStatus.processed, falseAlarmCount: alertList.tabCount.falseAlarm,
level: '高警', ));
urgency: '紧急', } catch (e) {
), emit(AbnormalListState(error: e.toString()));
const AlarmItem( }
id: '3',
title: '温度异常告警',
deviceInfo: 'A-301智能空开·客房301',
alarmTime: '2026-5-30 16:32:31',
processTime: '2026-5-30 16:32:31',
status: AlarmStatus.falseAlarm,
level: '高警',
urgency: '紧急',
),
];
} }
void setFilter(AlarmStatus filter) { void setFilter(AlarmStatus filter) {
emit(state.copyWith(currentFilter: filter)); emit(state.copyWith(currentFilter: filter));
} }
void onAlarmItemTap(AlarmItem item) { void onAlarmItemTap(AlarmItem item, BuildContext context) {
print('点击告警: ${item.title}'); final alertId = int.tryParse(item.id) ?? 0;
context.pushRoute(AbnormalDetailRoute(alertId: alertId));
} }
} }
\ No newline at end of file
import 'package:equatable/equatable.dart';
import 'package:smart_hotel_app/models/bo/alert_list_bo.dart';
enum AlarmStatus { all, pending, processed, falseAlarm } enum AlarmStatus { all, pending, processed, falseAlarm }
class AlarmItem { class AlarmItem extends Equatable {
final String id; final String id;
final String title; final String title;
final String deviceInfo; final String deviceInfo;
...@@ -20,24 +23,80 @@ class AlarmItem { ...@@ -20,24 +23,80 @@ class AlarmItem {
required this.level, required this.level,
required this.urgency, required this.urgency,
}); });
factory AlarmItem.fromAlertListItemBO(AlertListItemBO item) {
return AlarmItem(
id: item.alertId.toString(),
title: item.alertTitle,
deviceInfo: item.deviceLocationText,
alarmTime: item.alertTime,
processTime: item.handleTime.isEmpty ? '--' : item.handleTime,
status: _mapAlertStatus(item.alertStatus),
level: item.alertLevel,
urgency: '',
);
}
static AlarmStatus _mapAlertStatus(String status) {
switch (status) {
case '0':
case '1':
return AlarmStatus.pending;
case '2':
case '3':
return AlarmStatus.processed;
case '4':
return AlarmStatus.falseAlarm;
default:
return AlarmStatus.all;
}
}
@override
List<Object?> get props =>
[id, title, deviceInfo, alarmTime, processTime, status, level, urgency];
} }
class AbnormalListState { class AbnormalListState extends Equatable {
final bool isLoading;
final String? error;
final AlarmStatus currentFilter; final AlarmStatus currentFilter;
final List<AlarmItem> alarmList; final List<AlarmItem> alarmList;
final int allCount;
final int pendingCount;
final int processedCount;
final int falseAlarmCount;
const AbnormalListState({ const AbnormalListState({
required this.currentFilter, this.isLoading = false,
required this.alarmList, this.error,
this.currentFilter = AlarmStatus.all,
this.alarmList = const [],
this.allCount = 0,
this.pendingCount = 0,
this.processedCount = 0,
this.falseAlarmCount = 0,
}); });
AbnormalListState copyWith({ AbnormalListState copyWith({
bool? isLoading,
String? error,
AlarmStatus? currentFilter, AlarmStatus? currentFilter,
List<AlarmItem>? alarmList, List<AlarmItem>? alarmList,
int? allCount,
int? pendingCount,
int? processedCount,
int? falseAlarmCount,
}) { }) {
return AbnormalListState( return AbnormalListState(
isLoading: isLoading ?? this.isLoading,
error: error,
currentFilter: currentFilter ?? this.currentFilter, currentFilter: currentFilter ?? this.currentFilter,
alarmList: alarmList ?? this.alarmList, alarmList: alarmList ?? this.alarmList,
allCount: allCount ?? this.allCount,
pendingCount: pendingCount ?? this.pendingCount,
processedCount: processedCount ?? this.processedCount,
falseAlarmCount: falseAlarmCount ?? this.falseAlarmCount,
); );
} }
...@@ -47,4 +106,16 @@ class AbnormalListState { ...@@ -47,4 +106,16 @@ class AbnormalListState {
} }
return alarmList.where((item) => item.status == currentFilter).toList(); return alarmList.where((item) => item.status == currentFilter).toList();
} }
}
@override
List<Object?> get props => [
isLoading,
error,
currentFilter,
alarmList,
allCount,
pendingCount,
processedCount,
falseAlarmCount,
];
}
\ No newline at end of file
...@@ -21,7 +21,8 @@ class AbnormalListView extends StatelessWidget { ...@@ -21,7 +21,8 @@ class AbnormalListView extends StatelessWidget {
backgroundColor: Colors.white, backgroundColor: Colors.white,
elevation: 0, elevation: 0,
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back_ios, color: Color.fromRGBO(100, 116, 139, 1)), icon: const Icon(Icons.arrow_back_ios,
color: Color.fromRGBO(100, 116, 139, 1)),
onPressed: () { onPressed: () {
context.popRoute(); context.popRoute();
}, },
...@@ -39,6 +40,37 @@ class AbnormalListView extends StatelessWidget { ...@@ -39,6 +40,37 @@ class AbnormalListView extends StatelessWidget {
body: BlocBuilder<AbnormalListCubit, AbnormalListState>( body: BlocBuilder<AbnormalListCubit, AbnormalListState>(
builder: (context, state) { builder: (context, state) {
final cubit = context.read<AbnormalListCubit>(); final cubit = context.read<AbnormalListCubit>();
if (state.isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (state.error != null) {
return Center(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 28.w),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'加载失败: ${state.error}',
style: TextStyle(
fontSize: 28.sp,
color: const Color.fromRGBO(255, 100, 101, 1),
),
textAlign: TextAlign.center,
),
SizedBox(height: 24.h),
ElevatedButton(
onPressed: () => cubit.loadList(),
child: Text('重新加载', style: TextStyle(fontSize: 28.sp)),
),
],
),
),
);
}
return SingleChildScrollView( return SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 28.w), padding: EdgeInsets.symmetric(horizontal: 28.w),
child: Column( child: Column(
...@@ -46,14 +78,31 @@ class AbnormalListView extends StatelessWidget { ...@@ -46,14 +78,31 @@ class AbnormalListView extends StatelessWidget {
FilterTabBlock( FilterTabBlock(
currentFilter: state.currentFilter, currentFilter: state.currentFilter,
onFilterChanged: cubit.setFilter, onFilterChanged: cubit.setFilter,
allCount: state.allCount,
pendingCount: state.pendingCount,
processedCount: state.processedCount,
falseAlarmCount: state.falseAlarmCount,
), ),
SizedBox(height: 10.h), SizedBox(height: 10.h),
...state.filteredList.map((item) { if (state.filteredList.isEmpty)
return AlarmListItem( Padding(
item: item, padding: EdgeInsets.only(top: 60.h),
onTap: cubit.onAlarmItemTap, child: Text(
); '暂无数据',
}).toList(), style: TextStyle(
fontSize: 28.sp,
color: const Color.fromRGBO(100, 116, 139, 1),
),
),
)
else
...state.filteredList.map((item) {
return AlarmListItem(
item: item,
onTap: (i) =>
cubit.onAlarmItemTap(i, context),
);
}).toList(),
SizedBox(height: 30.h), SizedBox(height: 30.h),
], ],
), ),
...@@ -63,4 +112,4 @@ class AbnormalListView extends StatelessWidget { ...@@ -63,4 +112,4 @@ class AbnormalListView extends StatelessWidget {
), ),
); );
} }
} }
\ No newline at end of file
...@@ -5,11 +5,19 @@ import 'package:smart_hotel_app/views/home/abnormal_list/cubit/abnormal_list_sta ...@@ -5,11 +5,19 @@ import 'package:smart_hotel_app/views/home/abnormal_list/cubit/abnormal_list_sta
class FilterTabBlock extends StatelessWidget { class FilterTabBlock extends StatelessWidget {
final AlarmStatus currentFilter; final AlarmStatus currentFilter;
final Function(AlarmStatus) onFilterChanged; final Function(AlarmStatus) onFilterChanged;
final int allCount;
final int pendingCount;
final int processedCount;
final int falseAlarmCount;
const FilterTabBlock({ const FilterTabBlock({
super.key, super.key,
required this.currentFilter, required this.currentFilter,
required this.onFilterChanged, required this.onFilterChanged,
this.allCount = 0,
this.pendingCount = 0,
this.processedCount = 0,
this.falseAlarmCount = 0,
}); });
@override @override
...@@ -19,19 +27,19 @@ class FilterTabBlock extends StatelessWidget { ...@@ -19,19 +27,19 @@ class FilterTabBlock extends StatelessWidget {
margin: EdgeInsets.only(top: 10.h), margin: EdgeInsets.only(top: 10.h),
child: Row( child: Row(
children: [ children: [
_buildTab('全部', AlarmStatus.all), _buildTab('全部', AlarmStatus.all, allCount),
SizedBox(width: 20.w), SizedBox(width: 20.w),
_buildTab('待处理', AlarmStatus.pending), _buildTab('待处理', AlarmStatus.pending, pendingCount),
SizedBox(width: 20.w), SizedBox(width: 20.w),
_buildTab('已处理', AlarmStatus.processed), _buildTab('已处理', AlarmStatus.processed, processedCount),
SizedBox(width: 20.w), SizedBox(width: 20.w),
_buildTab('误报', AlarmStatus.falseAlarm), _buildTab('误报', AlarmStatus.falseAlarm, falseAlarmCount),
], ],
), ),
); );
} }
Widget _buildTab(String label, AlarmStatus status) { Widget _buildTab(String label, AlarmStatus status, int count) {
final isSelected = currentFilter == status; final isSelected = currentFilter == status;
return Expanded( return Expanded(
child: GestureDetector( child: GestureDetector(
...@@ -39,19 +47,40 @@ class FilterTabBlock extends StatelessWidget { ...@@ -39,19 +47,40 @@ class FilterTabBlock extends StatelessWidget {
child: Container( child: Container(
height: 72.h, height: 72.h,
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected ? const Color.fromRGBO(73, 149, 234, 1) : const Color.fromRGBO(100, 116, 139, 0.1), color: isSelected
? const Color.fromRGBO(73, 149, 234, 1)
: const Color.fromRGBO(100, 116, 139, 0.1),
borderRadius: BorderRadius.circular(16.r), borderRadius: BorderRadius.circular(16.r),
), ),
alignment: Alignment.center, alignment: Alignment.center,
child: Text( child: Row(
label, mainAxisAlignment: MainAxisAlignment.center,
style: TextStyle( children: [
fontSize: 26.sp, Text(
color: isSelected ? Colors.white : const Color.fromRGBO(100, 116, 139, 1), label,
), style: TextStyle(
fontSize: 26.sp,
color: isSelected
? Colors.white
: const Color.fromRGBO(100, 116, 139, 1),
),
),
if (count > 0) ...[
SizedBox(width: 6.w),
Text(
'$count',
style: TextStyle(
fontSize: 22.sp,
color: isSelected
? Colors.white
: const Color.fromRGBO(100, 116, 139, 0.8),
),
),
],
],
), ),
), ),
), ),
); );
} }
} }
\ No newline at end of file
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:smart_hotel_app/models/bo/inspection_room_bo.dart';
import 'package:smart_hotel_app/repositories/inspection_room_repository.dart';
import 'package:smart_hotel_app/services/inspection_room_service.dart';
import 'package:smart_hotel_app/views/home/inspection/cubit/inspection_state.dart'; import 'package:smart_hotel_app/views/home/inspection/cubit/inspection_state.dart';
class InspectionCubit extends Cubit<InspectionState> { class InspectionCubit extends Cubit<InspectionState> {
static const List<String> allRooms = [ final InspectionRoomService _service;
'101',
'102',
'103',
'104',
'105',
'106',
];
static final List<InspectionDevice> allDevices = [ static const List<InspectionDevice> allDevices = [
InspectionDevice( InspectionDevice(
id: '1', id: '1',
icon: Icons.wifi, icon: Icons.wifi,
...@@ -21,7 +17,7 @@ class InspectionCubit extends Cubit<InspectionState> { ...@@ -21,7 +17,7 @@ class InspectionCubit extends Cubit<InspectionState> {
location: '101电视柜', location: '101电视柜',
status: '正常', status: '正常',
time: '2024-01-15 14:30:00', time: '2024-01-15 14:30:00',
statusColor: const Color.fromRGBO(26, 188, 156, 1.0), statusColor: Color.fromRGBO(26, 188, 156, 1.0),
), ),
InspectionDevice( InspectionDevice(
id: '2', id: '2',
...@@ -31,7 +27,7 @@ class InspectionCubit extends Cubit<InspectionState> { ...@@ -31,7 +27,7 @@ class InspectionCubit extends Cubit<InspectionState> {
location: '101角落', location: '101角落',
status: '正常', status: '正常',
time: '2024-01-15 14:25:00', time: '2024-01-15 14:25:00',
statusColor: const Color.fromRGBO(26, 188, 156, 1.0), statusColor: Color.fromRGBO(26, 188, 156, 1.0),
), ),
InspectionDevice( InspectionDevice(
id: '3', id: '3',
...@@ -41,7 +37,7 @@ class InspectionCubit extends Cubit<InspectionState> { ...@@ -41,7 +37,7 @@ class InspectionCubit extends Cubit<InspectionState> {
location: '102门口', location: '102门口',
status: '告警', status: '告警',
time: '2024-01-15 10:00:00', time: '2024-01-15 10:00:00',
statusColor: const Color.fromRGBO(255, 77, 79, 1.0), statusColor: Color.fromRGBO(255, 77, 79, 1.0),
), ),
InspectionDevice( InspectionDevice(
id: '4', id: '4',
...@@ -51,7 +47,7 @@ class InspectionCubit extends Cubit<InspectionState> { ...@@ -51,7 +47,7 @@ class InspectionCubit extends Cubit<InspectionState> {
location: '102天花板', location: '102天花板',
status: '正常', status: '正常',
time: '2024-01-15 14:25:00', time: '2024-01-15 14:25:00',
statusColor: const Color.fromRGBO(26, 188, 156, 1.0), statusColor: Color.fromRGBO(26, 188, 156, 1.0),
), ),
InspectionDevice( InspectionDevice(
id: '5', id: '5',
...@@ -61,7 +57,7 @@ class InspectionCubit extends Cubit<InspectionState> { ...@@ -61,7 +57,7 @@ class InspectionCubit extends Cubit<InspectionState> {
location: '103角落', location: '103角落',
status: '正常', status: '正常',
time: '2024-01-15 14:25:00', time: '2024-01-15 14:25:00',
statusColor: const Color.fromRGBO(26, 188, 156, 1.0), statusColor: Color.fromRGBO(26, 188, 156, 1.0),
), ),
InspectionDevice( InspectionDevice(
id: '6', id: '6',
...@@ -71,7 +67,7 @@ class InspectionCubit extends Cubit<InspectionState> { ...@@ -71,7 +67,7 @@ class InspectionCubit extends Cubit<InspectionState> {
location: '103', location: '103',
status: '开启', status: '开启',
time: '2024-01-15 14:20:00', time: '2024-01-15 14:20:00',
statusColor: const Color.fromRGBO(26, 188, 156, 1.0), statusColor: Color.fromRGBO(26, 188, 156, 1.0),
), ),
InspectionDevice( InspectionDevice(
id: '7', id: '7',
...@@ -81,7 +77,7 @@ class InspectionCubit extends Cubit<InspectionState> { ...@@ -81,7 +77,7 @@ class InspectionCubit extends Cubit<InspectionState> {
location: '104门', location: '104门',
status: '离线', status: '离线',
time: '2024-01-15 12:00:00', time: '2024-01-15 12:00:00',
statusColor: const Color.fromRGBO(102, 102, 102, 1.0), statusColor: Color.fromRGBO(102, 102, 102, 1.0),
), ),
InspectionDevice( InspectionDevice(
id: '8', id: '8',
...@@ -91,7 +87,7 @@ class InspectionCubit extends Cubit<InspectionState> { ...@@ -91,7 +87,7 @@ class InspectionCubit extends Cubit<InspectionState> {
location: '105台面', location: '105台面',
status: '关闭', status: '关闭',
time: '2024-01-15 14:15:00', time: '2024-01-15 14:15:00',
statusColor: const Color.fromRGBO(100, 116, 139, 1.0), statusColor: Color.fromRGBO(100, 116, 139, 1.0),
), ),
InspectionDevice( InspectionDevice(
id: '9', id: '9',
...@@ -101,7 +97,7 @@ class InspectionCubit extends Cubit<InspectionState> { ...@@ -101,7 +97,7 @@ class InspectionCubit extends Cubit<InspectionState> {
location: '105天花板', location: '105天花板',
status: '正常', status: '正常',
time: '2024-01-15 14:30:00', time: '2024-01-15 14:30:00',
statusColor: const Color.fromRGBO(26, 188, 156, 1.0), statusColor: Color.fromRGBO(26, 188, 156, 1.0),
), ),
InspectionDevice( InspectionDevice(
id: '10', id: '10',
...@@ -111,7 +107,7 @@ class InspectionCubit extends Cubit<InspectionState> { ...@@ -111,7 +107,7 @@ class InspectionCubit extends Cubit<InspectionState> {
location: '106书桌', location: '106书桌',
status: '关闭', status: '关闭',
time: '2024-01-15 14:10:00', time: '2024-01-15 14:10:00',
statusColor: const Color.fromRGBO(100, 116, 139, 1.0), statusColor: Color.fromRGBO(100, 116, 139, 1.0),
), ),
InspectionDevice( InspectionDevice(
id: '11', id: '11',
...@@ -121,20 +117,73 @@ class InspectionCubit extends Cubit<InspectionState> { ...@@ -121,20 +117,73 @@ class InspectionCubit extends Cubit<InspectionState> {
location: '101', location: '101',
status: '正常', status: '正常',
time: '2024-01-15 14:30:00', time: '2024-01-15 14:30:00',
statusColor: const Color.fromRGBO(26, 188, 156, 1.0), statusColor: Color.fromRGBO(26, 188, 156, 1.0),
), ),
]; ];
InspectionCubit() : super(const InspectionState()) { InspectionCubit({InspectionRoomService? service})
: _service = service ??
InspectionRoomService(
repository: InspectionRoomRepository(),
),
super(const InspectionState(isLoading: true)) {
initData(); initData();
} }
void initData() { Future<void> initData() async {
emit(state.copyWith(deviceList: _filterDevices())); try {
final rooms = await _service.getRooms();
final roomNumbers = rooms.map((r) => r.roomNumber).toList();
final tabCounts = _computeTabCounts(rooms);
emit(state.copyWith(
isLoading: false,
error: null,
allRooms: roomNumbers,
selectedRooms: roomNumbers.isNotEmpty ? [roomNumbers.first] : [],
tabCounts: tabCounts,
deviceList: _filterDevices(
selectedTab: DeviceStatusTab.total,
selectedRooms: roomNumbers.isNotEmpty ? [roomNumbers.first] : [],
),
));
} catch (e) {
emit(state.copyWith(
isLoading: false,
error: e.toString(),
deviceList: _filterDevices(),
));
}
}
Map<DeviceStatusTab, int> _computeTabCounts(List<InspectionRoomBO> rooms) {
int normal = 0;
int warning = 0;
int offline = 0;
int fault = 0;
for (final room in rooms) {
normal += room.runStatus.normal;
warning += room.runStatus.warning;
offline += room.runStatus.offline;
fault += room.runStatus.fault;
}
return {
DeviceStatusTab.total: normal + warning + offline + fault,
DeviceStatusTab.on: normal,
DeviceStatusTab.off: 0,
DeviceStatusTab.offline: offline,
DeviceStatusTab.alarm: warning,
DeviceStatusTab.fault: fault,
};
} }
void selectTab(DeviceStatusTab tab) { void selectTab(DeviceStatusTab tab) {
emit(state.copyWith(selectedTab: tab, deviceList: _filterDevices(selectedTab: tab))); emit(state.copyWith(
selectedTab: tab,
deviceList: _filterDevices(selectedTab: tab),
));
} }
void toggleRoom(String room) { void toggleRoom(String room) {
...@@ -161,7 +210,8 @@ class InspectionCubit extends Cubit<InspectionState> { ...@@ -161,7 +210,8 @@ class InspectionCubit extends Cubit<InspectionState> {
if (rooms.isNotEmpty) { if (rooms.isNotEmpty) {
filtered = filtered.where((device) { filtered = filtered.where((device) {
return rooms.any((room) => device.location.contains(room) || device.name.contains(room)); return rooms.any(
(room) => device.location.contains(room) || device.name.contains(room));
}).toList(); }).toList();
} }
...@@ -186,4 +236,4 @@ class InspectionCubit extends Cubit<InspectionState> { ...@@ -186,4 +236,4 @@ class InspectionCubit extends Cubit<InspectionState> {
return filtered; return filtered;
} }
} }
\ No newline at end of file
...@@ -26,34 +26,46 @@ class InspectionDevice { ...@@ -26,34 +26,46 @@ class InspectionDevice {
} }
class InspectionState extends Equatable { class InspectionState extends Equatable {
final bool isLoading;
final String? error;
final DeviceStatusTab selectedTab; final DeviceStatusTab selectedTab;
final List<String> selectedRooms; final List<String> selectedRooms;
final List<String> allRooms;
final List<InspectionDevice> deviceList; final List<InspectionDevice> deviceList;
final Map<DeviceStatusTab, int> tabCounts; final Map<DeviceStatusTab, int> tabCounts;
const InspectionState({ const InspectionState({
this.isLoading = false,
this.error,
this.selectedTab = DeviceStatusTab.total, this.selectedTab = DeviceStatusTab.total,
this.selectedRooms = const ['101'], this.selectedRooms = const [],
this.allRooms = const [],
this.deviceList = const [], this.deviceList = const [],
this.tabCounts = const { this.tabCounts = const {
DeviceStatusTab.total: 11, DeviceStatusTab.total: 0,
DeviceStatusTab.on: 7, DeviceStatusTab.on: 0,
DeviceStatusTab.off: 2, DeviceStatusTab.off: 0,
DeviceStatusTab.offline: 1, DeviceStatusTab.offline: 0,
DeviceStatusTab.alarm: 1, DeviceStatusTab.alarm: 0,
DeviceStatusTab.fault: 0, DeviceStatusTab.fault: 0,
}, },
}); });
InspectionState copyWith({ InspectionState copyWith({
bool? isLoading,
String? error,
DeviceStatusTab? selectedTab, DeviceStatusTab? selectedTab,
List<String>? selectedRooms, List<String>? selectedRooms,
List<String>? allRooms,
List<InspectionDevice>? deviceList, List<InspectionDevice>? deviceList,
Map<DeviceStatusTab, int>? tabCounts, Map<DeviceStatusTab, int>? tabCounts,
}) { }) {
return InspectionState( return InspectionState(
isLoading: isLoading ?? this.isLoading,
error: this.error,
selectedTab: selectedTab ?? this.selectedTab, selectedTab: selectedTab ?? this.selectedTab,
selectedRooms: selectedRooms ?? this.selectedRooms, selectedRooms: selectedRooms ?? this.selectedRooms,
allRooms: allRooms ?? this.allRooms,
deviceList: deviceList ?? this.deviceList, deviceList: deviceList ?? this.deviceList,
tabCounts: tabCounts ?? this.tabCounts, tabCounts: tabCounts ?? this.tabCounts,
); );
...@@ -61,9 +73,12 @@ class InspectionState extends Equatable { ...@@ -61,9 +73,12 @@ class InspectionState extends Equatable {
@override @override
List<Object?> get props => [ List<Object?> get props => [
isLoading,
error,
selectedTab, selectedTab,
selectedRooms, selectedRooms,
allRooms,
deviceList, deviceList,
tabCounts, tabCounts,
]; ];
} }
\ No newline at end of file
...@@ -37,29 +37,73 @@ class InspectionDetailView extends StatelessWidget { ...@@ -37,29 +37,73 @@ class InspectionDetailView extends StatelessWidget {
), ),
centerTitle: true, centerTitle: true,
), ),
body: SingleChildScrollView( body: BlocBuilder<InspectionCubit, InspectionState>(
padding: EdgeInsets.symmetric(horizontal: 28.w, vertical: 20.h), builder: (context, state) {
child: BlocBuilder<InspectionCubit, InspectionState>( final cubit = context.read<InspectionCubit>();
builder: (context, state) {
final cubit = context.read<InspectionCubit>(); if (state.isLoading) {
return Column( return const Center(child: CircularProgressIndicator());
}
if (state.error != null) {
return Center(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 28.w),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'加载失败: ${state.error}',
style: TextStyle(
fontSize: 28.sp,
color: const Color.fromRGBO(255, 100, 101, 1),
),
textAlign: TextAlign.center,
),
SizedBox(height: 24.h),
ElevatedButton(
onPressed: () => cubit.initData(),
child: Text('重新加载', style: TextStyle(fontSize: 28.sp)),
),
],
),
),
);
}
return SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 28.w, vertical: 20.h),
child: Column(
children: [ children: [
FilterPanel( FilterPanel(
selectedTab: state.selectedTab, selectedTab: state.selectedTab,
tabCounts: state.tabCounts, tabCounts: state.tabCounts,
onTabSelected: (tab) => cubit.selectTab(tab), onTabSelected: (tab) => cubit.selectTab(tab),
selectedRooms: state.selectedRooms, selectedRooms: state.selectedRooms,
allRooms: state.allRooms,
onRoomToggle: (room) => cubit.toggleRoom(room), onRoomToggle: (room) => cubit.toggleRoom(room),
), ),
SizedBox(height: 20.h), SizedBox(height: 20.h),
DeviceList(deviceList: state.deviceList), if (state.deviceList.isEmpty)
Padding(
padding: EdgeInsets.only(top: 40.h),
child: Text(
'暂无设备数据',
style: TextStyle(
fontSize: 28.sp,
color: const Color.fromRGBO(100, 116, 139, 1),
),
),
)
else
DeviceList(deviceList: state.deviceList),
SizedBox(height: 30.h), SizedBox(height: 30.h),
], ],
); ),
}, );
), },
), ),
), ),
); );
} }
} }
\ No newline at end of file
...@@ -7,6 +7,7 @@ class FilterPanel extends StatelessWidget { ...@@ -7,6 +7,7 @@ class FilterPanel extends StatelessWidget {
final Map<DeviceStatusTab, int> tabCounts; final Map<DeviceStatusTab, int> tabCounts;
final Function(DeviceStatusTab) onTabSelected; final Function(DeviceStatusTab) onTabSelected;
final List<String> selectedRooms; final List<String> selectedRooms;
final List<String> allRooms;
final Function(String) onRoomToggle; final Function(String) onRoomToggle;
const FilterPanel({ const FilterPanel({
...@@ -15,18 +16,10 @@ class FilterPanel extends StatelessWidget { ...@@ -15,18 +16,10 @@ class FilterPanel extends StatelessWidget {
required this.tabCounts, required this.tabCounts,
required this.onTabSelected, required this.onTabSelected,
required this.selectedRooms, required this.selectedRooms,
required this.allRooms,
required this.onRoomToggle, required this.onRoomToggle,
}); });
static const List<String> allRooms = [
'101',
'102',
'103',
'104',
'105',
'106',
];
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
...@@ -74,28 +67,29 @@ class FilterPanel extends StatelessWidget { ...@@ -74,28 +67,29 @@ class FilterPanel extends StatelessWidget {
), ),
), ),
), ),
Positioned( if (count > 0)
top: 0, Positioned(
right: -8.w, top: 0,
child: Container( right: -8.w,
width: 28.w, child: Container(
height: 28.h, width: 28.w,
decoration: BoxDecoration( height: 28.h,
color: _getTabColor(tab), decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.r), color: _getTabColor(tab),
), borderRadius: BorderRadius.circular(12.r),
child: Center( ),
child: Text( child: Center(
count.toString(), child: Text(
style: TextStyle( count.toString(),
fontSize: 20.sp, style: TextStyle(
fontWeight: FontWeight.bold, fontSize: 20.sp,
color: Colors.white, fontWeight: FontWeight.bold,
color: Colors.white,
),
), ),
), ),
), ),
), ),
),
], ],
), ),
), ),
...@@ -144,42 +138,51 @@ class FilterPanel extends StatelessWidget { ...@@ -144,42 +138,51 @@ class FilterPanel extends StatelessWidget {
), ),
), ),
SizedBox(height: 16.h), SizedBox(height: 16.h),
Wrap( if (allRooms.isEmpty)
spacing: 5.w, Text(
runSpacing: 12.h, '暂无房间数据',
children: allRooms.map((room) { style: TextStyle(
final isSelected = selectedRooms.contains(room); fontSize: 24.sp,
return GestureDetector( color: const Color.fromRGBO(100, 116, 139, 0.6),
onTap: () => onRoomToggle(room), ),
child: Container( )
width: 120.w, else
padding: EdgeInsets.symmetric(horizontal: 28.w, vertical: 12.h), Wrap(
decoration: BoxDecoration( spacing: 5.w,
color: isSelected runSpacing: 12.h,
? const Color.fromRGBO(235, 244, 255, 1.0) children: allRooms.map((room) {
: Colors.white, final isSelected = selectedRooms.contains(room);
borderRadius: BorderRadius.circular(12.r), return GestureDetector(
border: Border.all( onTap: () => onRoomToggle(room),
child: Container(
width: 120.w,
padding: EdgeInsets.symmetric(horizontal: 28.w, vertical: 12.h),
decoration: BoxDecoration(
color: isSelected color: isSelected
? const Color.fromRGBO(66, 165, 245, 1.0) ? const Color.fromRGBO(235, 244, 255, 1.0)
: const Color.fromRGBO(200, 208, 220, 1.0), : Colors.white,
width: 1.w, borderRadius: BorderRadius.circular(12.r),
border: Border.all(
color: isSelected
? const Color.fromRGBO(66, 165, 245, 1.0)
: const Color.fromRGBO(200, 208, 220, 1.0),
width: 1.w,
),
), ),
), child: Text(
child: Text( room,
room, style: TextStyle(
style: TextStyle( fontSize: 24.sp,
fontSize: 24.sp, color: isSelected
color: isSelected ? const Color.fromRGBO(66, 165, 245, 1.0)
? const Color.fromRGBO(66, 165, 245, 1.0) : const Color.fromRGBO(100, 116, 139, 1.0),
: const Color.fromRGBO(100, 116, 139, 1.0), fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, ),
), ),
), ),
), );
); }).toList(),
}).toList(), ),
),
], ],
); );
} }
...@@ -217,4 +220,4 @@ class FilterPanel extends StatelessWidget { ...@@ -217,4 +220,4 @@ class FilterPanel extends StatelessWidget {
return const Color.fromRGBO(239, 68, 68, 1.0); return const Color.fromRGBO(239, 68, 68, 1.0);
} }
} }
} }
\ No newline at end of file
...@@ -374,6 +374,14 @@ packages: ...@@ -374,6 +374,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.2" version: "3.1.2"
flutter_staggered_grid_view:
dependency: transitive
description:
name: flutter_staggered_grid_view
sha256: "19e7abb550c96fbfeb546b23f3ff356ee7c59a019a651f8f102a4ba9b7349395"
url: "https://pub.dev"
source: hosted
version: "0.7.0"
flutter_switch: flutter_switch:
dependency: "direct main" dependency: "direct main"
description: description:
...@@ -456,6 +464,14 @@ packages: ...@@ -456,6 +464,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.8.0" version: "4.8.0"
infinite_scroll_pagination:
dependency: "direct main"
description:
name: infinite_scroll_pagination
sha256: b0d28e37cd8f62490ff6aef63f9db93d4c78b7f11b7c6b26f33c69d8476fda78
url: "https://pub.dev"
source: hosted
version: "5.1.1"
io: io:
dependency: transitive dependency: transitive
description: description:
...@@ -749,6 +765,14 @@ packages: ...@@ -749,6 +765,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
sliver_tools:
dependency: transitive
description:
name: sliver_tools
sha256: eae28220badfb9d0559207badcbbc9ad5331aac829a88cb0964d330d2a4636a6
url: "https://pub.dev"
source: hosted
version: "0.2.12"
source_gen: source_gen:
dependency: transitive dependency: transitive
description: description:
......
...@@ -47,6 +47,7 @@ dependencies: ...@@ -47,6 +47,7 @@ dependencies:
# fl_chart: ^1.2.0 # fl_chart: ^1.2.0
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
# fl_chart: ^0.71.0 # fl_chart: ^0.71.0
# fl_chart: ^1.1.0 # fl_chart: ^1.1.0
......
# 智慧酒店 App 接口对接方案 # 智慧酒店 App 接口对接方案
...@@ -189,7 +189,17 @@ class xxxBO extends Equatable { ...@@ -189,7 +189,17 @@ class xxxBO extends Equatable {
| 设备控制 |POST | /app/device/control/powerOff | 已对接 | 设备控制-远程断电 | 需要做下防重复提交,处理过程中不可点。处理完成之后弹出操作成功/操作失败提示| | 设备控制 |POST | /app/device/control/powerOff | 已对接 | 设备控制-远程断电 | 需要做下防重复提交,处理过程中不可点。处理完成之后弹出操作成功/操作失败提示|
| 设备拓扑 |GET | /app/device/topology | 未对接 | 设备拓扑 | 相关页面:InspectionTopologyView | | 设备拓扑 |GET | /app/device/topology | 已对接 | 设备拓扑 | 相关页面:InspectionTopologyView |
| 告警列表 |GET | /app/alert/list | 已对接 | 告警列表 | 相关页面:AbnormalListView |
| 设备巡检-房间 |GET | /app/device/inspection/rooms | 已对接 | 告警列表 | 相关页面:FilterPanel |
| 设备巡检-设备列表 |GET | /app/device/inspection | 未对接 | 告警列表 | 相关页面:DeviceList |
| 设备拓扑图 |GET | /app/device/detail | 未对接 | 设备拓扑图 | 相关页面:DeviceList |
| 设备拓扑图-开始巡检 |GET | /app/device/inspection/start | 未对接 | 设备拓扑图-开始巡检 | 相关页面:DeviceList |
...@@ -491,6 +501,179 @@ class xxxBO extends Equatable { ...@@ -491,6 +501,179 @@ class xxxBO extends Equatable {
``` ```
#### 告警列表
**GET /app/alert/list**
```
求体:
{
"pageSize": 20,
"pageNum": 1
}
应 data:
{
"code": 0,
"msg": "string",
"data": {
"total": 0,
"tabCount": {
"all": 0,
"pending": 0,
"processed": 0,
"falseAlarm": 0
},
"rows": [
{
"alertId": 0,
"alertTitle": "string",
"alertIcon": "string",
"deviceLocationText": "string",
"alertTime": "string",
"handleTime": "string",
"alertLevel": "string",
"alertStatus": "string"
}
]
}
}
```
#### 设备巡检-房间
**GET /app/device/inspection/rooms**
```
求体:
应 data:
{
"code": 0,
"msg": "string",
"data": [
{
"roomId": 0,
"roomNumber": "string", // 房间号,筛选栏标签显示文字,如'301'、'302'、'801'、'802'等
"runStatus": { // 运行状态码,与入参保持一致:online=开启(绿) offline=关闭/离线(灰) warning=告警(黄) fault=故障(红)。由 hotel_device.run_status + online_status 组合映射生成
"normal": 0,
"warning": 0,
"offline": 0,
"fault": 0
}
}
]
}
```
#### 设备巡检-设备列表
**GET /app/device/inspection**
```
求体:
{
"pageSize": 20,
"pageNum": 1
}
应 data:
{
"total": 0,
"data": [
{
"deviceId": 0,
"deviceName": "string",
"deviceTypeIcon": "string",
"deviceTypeName": "string",
"runStatus": "string",
"runStatusText": "string",
"roomName": "string",
"lastHeartbeat": "string",
"lastHeartbeatDisplay": "string",
"onlineStatus": "string",
"powerStatus": "string",
"roomId": 0
}
],
"code": 0,
"msg": "string"
}
```
#### 设备拓扑图
**GET /app/device/detail**
```
求体:
{
"deviceId": 0,
}
应 data:
{
"code": 0,
"msg": "string",
"data": {
"basicInfo": { // 设备基础信息区块,对应页面中「基本信息」区域的所有字段
"deviceId": 0,
"deviceName": "string",
"deviceTypeName": "string",
"onlineStatus": "string",
"deviceModel": "string",
"areaName": "string",
"responsiblePerson": "string",
"lastInspectTime": "string"
},
"latestInspection": { // 最近一条巡检记录,对应页面中「巡检历史」区域的单条卡片,无记录时该对象为null
"inspectId": 0,
"inspectorName": "string",
"inspectTime": "string",
"inspectStatus": "string",
"remark": "string"
},
"inspectionItems": [ // 巡检项明细列表
{
"name": "string",
"value": "string",
"status": "string"
}
]
}
}
```
#### 设备拓扑图-开始巡检
**GET /app/device/inspection/start**
```
求体:
{
"deviceId": 0,
"inspectorId": 0 // 巡检人ID
}
应 data:
{
"code": 0,
"msg": "string",
"data": {
"inspectId": 0,
"inspectStatus": "string",
"inspectTime": "string",
"totalItems": 0
}
}
```
......
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