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: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';
class AbnormalListCubit extends Cubit<AbnormalListState> {
AbnormalListCubit() : super(AbnormalListState(currentFilter: AlarmStatus.all, alarmList: _getAlarmList()));
final AlertListService _service;
static List<AlarmItem> _getAlarmList() {
return [
const AlarmItem(
id: '1',
title: '温度异常告警',
deviceInfo: 'A-301智能空开·客房301',
alarmTime: '2026-5-30 16:32:31',
processTime: '2026-5-30 16:32:31',
status: AlarmStatus.pending,
level: '高警',
urgency: '紧急',
),
const AlarmItem(
id: '2',
title: '温度异常告警',
deviceInfo: 'A-301智能空开·客房301',
alarmTime: '2026-5-30 16:32:31',
processTime: '2026-5-30 16:32:31',
status: AlarmStatus.processed,
level: '高警',
urgency: '紧急',
),
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: '紧急',
),
];
AbnormalListCubit()
: _service = AlertListService(
repository: AlertListRepository(),
),
super(const AbnormalListState()) {
loadList();
}
Future<void> loadList() async {
emit(const AbnormalListState(isLoading: true));
try {
final alertList = await _service.getList();
emit(AbnormalListState(
alarmList:
alertList.rows.map((e) => AlarmItem.fromAlertListItemBO(e)).toList(),
allCount: alertList.tabCount.all,
pendingCount: alertList.tabCount.pending,
processedCount: alertList.tabCount.processed,
falseAlarmCount: alertList.tabCount.falseAlarm,
));
} catch (e) {
emit(AbnormalListState(error: e.toString()));
}
}
void setFilter(AlarmStatus filter) {
emit(state.copyWith(currentFilter: filter));
}
void onAlarmItemTap(AlarmItem item) {
print('点击告警: ${item.title}');
void onAlarmItemTap(AlarmItem item, BuildContext context) {
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 }
class AlarmItem {
class AlarmItem extends Equatable {
final String id;
final String title;
final String deviceInfo;
......@@ -20,24 +23,80 @@ class AlarmItem {
required this.level,
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 List<AlarmItem> alarmList;
final int allCount;
final int pendingCount;
final int processedCount;
final int falseAlarmCount;
const AbnormalListState({
required this.currentFilter,
required this.alarmList,
this.isLoading = false,
this.error,
this.currentFilter = AlarmStatus.all,
this.alarmList = const [],
this.allCount = 0,
this.pendingCount = 0,
this.processedCount = 0,
this.falseAlarmCount = 0,
});
AbnormalListState copyWith({
bool? isLoading,
String? error,
AlarmStatus? currentFilter,
List<AlarmItem>? alarmList,
int? allCount,
int? pendingCount,
int? processedCount,
int? falseAlarmCount,
}) {
return AbnormalListState(
isLoading: isLoading ?? this.isLoading,
error: error,
currentFilter: currentFilter ?? this.currentFilter,
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 {
}
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 {
backgroundColor: Colors.white,
elevation: 0,
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: () {
context.popRoute();
},
......@@ -39,6 +40,37 @@ class AbnormalListView extends StatelessWidget {
body: BlocBuilder<AbnormalListCubit, AbnormalListState>(
builder: (context, state) {
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(
padding: EdgeInsets.symmetric(horizontal: 28.w),
child: Column(
......@@ -46,14 +78,31 @@ class AbnormalListView extends StatelessWidget {
FilterTabBlock(
currentFilter: state.currentFilter,
onFilterChanged: cubit.setFilter,
allCount: state.allCount,
pendingCount: state.pendingCount,
processedCount: state.processedCount,
falseAlarmCount: state.falseAlarmCount,
),
SizedBox(height: 10.h),
...state.filteredList.map((item) {
return AlarmListItem(
item: item,
onTap: cubit.onAlarmItemTap,
);
}).toList(),
if (state.filteredList.isEmpty)
Padding(
padding: EdgeInsets.only(top: 60.h),
child: Text(
'暂无数据',
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),
],
),
......@@ -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
class FilterTabBlock extends StatelessWidget {
final AlarmStatus currentFilter;
final Function(AlarmStatus) onFilterChanged;
final int allCount;
final int pendingCount;
final int processedCount;
final int falseAlarmCount;
const FilterTabBlock({
super.key,
required this.currentFilter,
required this.onFilterChanged,
this.allCount = 0,
this.pendingCount = 0,
this.processedCount = 0,
this.falseAlarmCount = 0,
});
@override
......@@ -19,19 +27,19 @@ class FilterTabBlock extends StatelessWidget {
margin: EdgeInsets.only(top: 10.h),
child: Row(
children: [
_buildTab('全部', AlarmStatus.all),
_buildTab('全部', AlarmStatus.all, allCount),
SizedBox(width: 20.w),
_buildTab('待处理', AlarmStatus.pending),
_buildTab('待处理', AlarmStatus.pending, pendingCount),
SizedBox(width: 20.w),
_buildTab('已处理', AlarmStatus.processed),
_buildTab('已处理', AlarmStatus.processed, processedCount),
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;
return Expanded(
child: GestureDetector(
......@@ -39,19 +47,40 @@ class FilterTabBlock extends StatelessWidget {
child: Container(
height: 72.h,
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),
),
alignment: Alignment.center,
child: Text(
label,
style: TextStyle(
fontSize: 26.sp,
color: isSelected ? Colors.white : const Color.fromRGBO(100, 116, 139, 1),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
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_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';
class InspectionCubit extends Cubit<InspectionState> {
static const List<String> allRooms = [
'101',
'102',
'103',
'104',
'105',
'106',
];
final InspectionRoomService _service;
static final List<InspectionDevice> allDevices = [
static const List<InspectionDevice> allDevices = [
InspectionDevice(
id: '1',
icon: Icons.wifi,
......@@ -21,7 +17,7 @@ class InspectionCubit extends Cubit<InspectionState> {
location: '101电视柜',
status: '正常',
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(
id: '2',
......@@ -31,7 +27,7 @@ class InspectionCubit extends Cubit<InspectionState> {
location: '101角落',
status: '正常',
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(
id: '3',
......@@ -41,7 +37,7 @@ class InspectionCubit extends Cubit<InspectionState> {
location: '102门口',
status: '告警',
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(
id: '4',
......@@ -51,7 +47,7 @@ class InspectionCubit extends Cubit<InspectionState> {
location: '102天花板',
status: '正常',
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(
id: '5',
......@@ -61,7 +57,7 @@ class InspectionCubit extends Cubit<InspectionState> {
location: '103角落',
status: '正常',
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(
id: '6',
......@@ -71,7 +67,7 @@ class InspectionCubit extends Cubit<InspectionState> {
location: '103',
status: '开启',
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(
id: '7',
......@@ -81,7 +77,7 @@ class InspectionCubit extends Cubit<InspectionState> {
location: '104门',
status: '离线',
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(
id: '8',
......@@ -91,7 +87,7 @@ class InspectionCubit extends Cubit<InspectionState> {
location: '105台面',
status: '关闭',
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(
id: '9',
......@@ -101,7 +97,7 @@ class InspectionCubit extends Cubit<InspectionState> {
location: '105天花板',
status: '正常',
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(
id: '10',
......@@ -111,7 +107,7 @@ class InspectionCubit extends Cubit<InspectionState> {
location: '106书桌',
status: '关闭',
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(
id: '11',
......@@ -121,20 +117,73 @@ class InspectionCubit extends Cubit<InspectionState> {
location: '101',
status: '正常',
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();
}
void initData() {
emit(state.copyWith(deviceList: _filterDevices()));
Future<void> initData() async {
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) {
emit(state.copyWith(selectedTab: tab, deviceList: _filterDevices(selectedTab: tab)));
emit(state.copyWith(
selectedTab: tab,
deviceList: _filterDevices(selectedTab: tab),
));
}
void toggleRoom(String room) {
......@@ -161,7 +210,8 @@ class InspectionCubit extends Cubit<InspectionState> {
if (rooms.isNotEmpty) {
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();
}
......@@ -186,4 +236,4 @@ class InspectionCubit extends Cubit<InspectionState> {
return filtered;
}
}
}
\ No newline at end of file
......@@ -26,34 +26,46 @@ class InspectionDevice {
}
class InspectionState extends Equatable {
final bool isLoading;
final String? error;
final DeviceStatusTab selectedTab;
final List<String> selectedRooms;
final List<String> allRooms;
final List<InspectionDevice> deviceList;
final Map<DeviceStatusTab, int> tabCounts;
const InspectionState({
this.isLoading = false,
this.error,
this.selectedTab = DeviceStatusTab.total,
this.selectedRooms = const ['101'],
this.selectedRooms = const [],
this.allRooms = const [],
this.deviceList = const [],
this.tabCounts = const {
DeviceStatusTab.total: 11,
DeviceStatusTab.on: 7,
DeviceStatusTab.off: 2,
DeviceStatusTab.offline: 1,
DeviceStatusTab.alarm: 1,
DeviceStatusTab.total: 0,
DeviceStatusTab.on: 0,
DeviceStatusTab.off: 0,
DeviceStatusTab.offline: 0,
DeviceStatusTab.alarm: 0,
DeviceStatusTab.fault: 0,
},
});
InspectionState copyWith({
bool? isLoading,
String? error,
DeviceStatusTab? selectedTab,
List<String>? selectedRooms,
List<String>? allRooms,
List<InspectionDevice>? deviceList,
Map<DeviceStatusTab, int>? tabCounts,
}) {
return InspectionState(
isLoading: isLoading ?? this.isLoading,
error: this.error,
selectedTab: selectedTab ?? this.selectedTab,
selectedRooms: selectedRooms ?? this.selectedRooms,
allRooms: allRooms ?? this.allRooms,
deviceList: deviceList ?? this.deviceList,
tabCounts: tabCounts ?? this.tabCounts,
);
......@@ -61,9 +73,12 @@ class InspectionState extends Equatable {
@override
List<Object?> get props => [
isLoading,
error,
selectedTab,
selectedRooms,
allRooms,
deviceList,
tabCounts,
];
}
}
\ No newline at end of file
......@@ -37,29 +37,73 @@ class InspectionDetailView extends StatelessWidget {
),
centerTitle: true,
),
body: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 28.w, vertical: 20.h),
child: BlocBuilder<InspectionCubit, InspectionState>(
builder: (context, state) {
final cubit = context.read<InspectionCubit>();
return Column(
body: BlocBuilder<InspectionCubit, InspectionState>(
builder: (context, state) {
final cubit = context.read<InspectionCubit>();
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.initData(),
child: Text('重新加载', style: TextStyle(fontSize: 28.sp)),
),
],
),
),
);
}
return SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 28.w, vertical: 20.h),
child: Column(
children: [
FilterPanel(
selectedTab: state.selectedTab,
tabCounts: state.tabCounts,
onTabSelected: (tab) => cubit.selectTab(tab),
selectedRooms: state.selectedRooms,
allRooms: state.allRooms,
onRoomToggle: (room) => cubit.toggleRoom(room),
),
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),
],
);
},
),
),
);
},
),
),
);
}
}
}
\ No newline at end of file
......@@ -7,6 +7,7 @@ class FilterPanel extends StatelessWidget {
final Map<DeviceStatusTab, int> tabCounts;
final Function(DeviceStatusTab) onTabSelected;
final List<String> selectedRooms;
final List<String> allRooms;
final Function(String) onRoomToggle;
const FilterPanel({
......@@ -15,18 +16,10 @@ class FilterPanel extends StatelessWidget {
required this.tabCounts,
required this.onTabSelected,
required this.selectedRooms,
required this.allRooms,
required this.onRoomToggle,
});
static const List<String> allRooms = [
'101',
'102',
'103',
'104',
'105',
'106',
];
@override
Widget build(BuildContext context) {
return Container(
......@@ -74,28 +67,29 @@ class FilterPanel extends StatelessWidget {
),
),
),
Positioned(
top: 0,
right: -8.w,
child: Container(
width: 28.w,
height: 28.h,
decoration: BoxDecoration(
color: _getTabColor(tab),
borderRadius: BorderRadius.circular(12.r),
),
child: Center(
child: Text(
count.toString(),
style: TextStyle(
fontSize: 20.sp,
fontWeight: FontWeight.bold,
color: Colors.white,
if (count > 0)
Positioned(
top: 0,
right: -8.w,
child: Container(
width: 28.w,
height: 28.h,
decoration: BoxDecoration(
color: _getTabColor(tab),
borderRadius: BorderRadius.circular(12.r),
),
child: Center(
child: Text(
count.toString(),
style: TextStyle(
fontSize: 20.sp,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
),
),
],
),
),
......@@ -144,42 +138,51 @@ class FilterPanel extends StatelessWidget {
),
),
SizedBox(height: 16.h),
Wrap(
spacing: 5.w,
runSpacing: 12.h,
children: allRooms.map((room) {
final isSelected = selectedRooms.contains(room);
return GestureDetector(
onTap: () => onRoomToggle(room),
child: Container(
width: 120.w,
padding: EdgeInsets.symmetric(horizontal: 28.w, vertical: 12.h),
decoration: BoxDecoration(
color: isSelected
? const Color.fromRGBO(235, 244, 255, 1.0)
: Colors.white,
borderRadius: BorderRadius.circular(12.r),
border: Border.all(
if (allRooms.isEmpty)
Text(
'暂无房间数据',
style: TextStyle(
fontSize: 24.sp,
color: const Color.fromRGBO(100, 116, 139, 0.6),
),
)
else
Wrap(
spacing: 5.w,
runSpacing: 12.h,
children: allRooms.map((room) {
final isSelected = selectedRooms.contains(room);
return GestureDetector(
onTap: () => onRoomToggle(room),
child: Container(
width: 120.w,
padding: EdgeInsets.symmetric(horizontal: 28.w, vertical: 12.h),
decoration: BoxDecoration(
color: isSelected
? const Color.fromRGBO(66, 165, 245, 1.0)
: const Color.fromRGBO(200, 208, 220, 1.0),
width: 1.w,
? const Color.fromRGBO(235, 244, 255, 1.0)
: Colors.white,
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(
room,
style: TextStyle(
fontSize: 24.sp,
color: isSelected
? const Color.fromRGBO(66, 165, 245, 1.0)
: const Color.fromRGBO(100, 116, 139, 1.0),
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
child: Text(
room,
style: TextStyle(
fontSize: 24.sp,
color: isSelected
? const Color.fromRGBO(66, 165, 245, 1.0)
: const Color.fromRGBO(100, 116, 139, 1.0),
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
),
),
),
),
);
}).toList(),
),
);
}).toList(),
),
],
);
}
......@@ -217,4 +220,4 @@ class FilterPanel extends StatelessWidget {
return const Color.fromRGBO(239, 68, 68, 1.0);
}
}
}
}
\ No newline at end of file
......@@ -374,6 +374,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: "direct main"
description:
......@@ -456,6 +464,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
......@@ -749,6 +765,14 @@ packages:
description: flutter
source: sdk
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:
dependency: transitive
description:
......
......@@ -47,6 +47,7 @@ dependencies:
# fl_chart: ^1.2.0
flutter_switch: ^0.3.2
fl_chart: ^0.71.0
infinite_scroll_pagination: ^5.1.1
# fl_chart: ^0.71.0
# fl_chart: ^1.1.0
......
# 智慧酒店 App 接口对接方案
# 智慧酒店 App 接口对接方案
......@@ -189,7 +189,17 @@ class xxxBO extends Equatable {
| 设备控制 |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 {
```
#### 告警列表
**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