Commit 4b88b06f authored by 张宏's avatar 张宏

1

parent 97313115
import 'package:equatable/equatable.dart';
class InspectionDeviceBO extends Equatable {
final int deviceId;
final String deviceName;
final String deviceTypeIcon;
final String deviceTypeName;
final String runStatus;
final String runStatusText;
final String roomName;
final String lastHeartbeat;
final String lastHeartbeatDisplay;
final String onlineStatus;
final String powerStatus;
final int roomId;
const InspectionDeviceBO({
required this.deviceId,
required this.deviceName,
required this.deviceTypeIcon,
required this.deviceTypeName,
required this.runStatus,
required this.runStatusText,
required this.roomName,
required this.lastHeartbeat,
required this.lastHeartbeatDisplay,
required this.onlineStatus,
required this.powerStatus,
required this.roomId,
});
factory InspectionDeviceBO.fromJson(Map<String, dynamic> json) {
return InspectionDeviceBO(
deviceId: int.tryParse(json['deviceId']?.toString() ?? '') ?? 0,
deviceName: json['deviceName'] as String? ?? '',
deviceTypeIcon: json['deviceTypeIcon'] as String? ?? '',
deviceTypeName: json['deviceTypeName'] as String? ?? '',
runStatus: json['runStatus'] as String? ?? '',
runStatusText: json['runStatusText'] as String? ?? '',
roomName: json['roomName'] as String? ?? '',
lastHeartbeat: json['lastHeartbeat'] as String? ?? '',
lastHeartbeatDisplay: json['lastHeartbeatDisplay'] as String? ?? '',
onlineStatus: json['onlineStatus'] as String? ?? '',
powerStatus: json['powerStatus'] as String? ?? '',
roomId: int.tryParse(json['roomId']?.toString() ?? '') ?? 0,
);
}
@override
List<Object?> get props => [
deviceId,
deviceName,
deviceTypeIcon,
deviceTypeName,
runStatus,
runStatusText,
roomName,
lastHeartbeat,
lastHeartbeatDisplay,
onlineStatus,
powerStatus,
roomId,
];
}
class InspectionDeviceListBO extends Equatable {
final int total;
final List<InspectionDeviceBO> data;
const InspectionDeviceListBO({
required this.total,
required this.data,
});
factory InspectionDeviceListBO.fromJson(Map<String, dynamic> json) {
final list = json['rows'] as List<dynamic>? ?? [];
return InspectionDeviceListBO(
total: int.tryParse(json['total']?.toString() ?? '') ?? 0,
data: list
.map((e) => InspectionDeviceBO.fromJson(e as Map<String, dynamic>))
.toList(),
);
}
@override
List<Object?> get props => [total, data];
}
\ No newline at end of file
...@@ -13,7 +13,7 @@ class InspectionRoomBO extends Equatable { ...@@ -13,7 +13,7 @@ class InspectionRoomBO extends Equatable {
factory InspectionRoomBO.fromJson(Map<String, dynamic> json) { factory InspectionRoomBO.fromJson(Map<String, dynamic> json) {
return InspectionRoomBO( return InspectionRoomBO(
roomId: json['roomId'] as int? ?? 0, roomId: int.tryParse(json['roomId']?.toString() ?? '') ?? 0,
roomNumber: json['roomNumber'] as String? ?? '', roomNumber: json['roomNumber'] as String? ?? '',
runStatus: InspectionRoomRunStatusBO.fromJson( runStatus: InspectionRoomRunStatusBO.fromJson(
json['runStatus'] as Map<String, dynamic>? ?? {}, json['runStatus'] as Map<String, dynamic>? ?? {},
...@@ -40,10 +40,10 @@ class InspectionRoomRunStatusBO extends Equatable { ...@@ -40,10 +40,10 @@ class InspectionRoomRunStatusBO extends Equatable {
factory InspectionRoomRunStatusBO.fromJson(Map<String, dynamic> json) { factory InspectionRoomRunStatusBO.fromJson(Map<String, dynamic> json) {
return InspectionRoomRunStatusBO( return InspectionRoomRunStatusBO(
normal: json['normal'] as int? ?? 0, normal: int.tryParse(json['normal']?.toString() ?? '') ?? 0,
warning: json['warning'] as int? ?? 0, warning: int.tryParse(json['warning']?.toString() ?? '') ?? 0,
offline: json['offline'] as int? ?? 0, offline: int.tryParse(json['offline']?.toString() ?? '') ?? 0,
fault: json['fault'] as int? ?? 0, fault: int.tryParse(json['fault']?.toString() ?? '') ?? 0,
); );
} }
......
import '../../utils/http/response_model.dart';
import '../../utils/http/dio_request.dart';
import '../../models/bo/inspection_device_bo.dart';
class InspectionDeviceRepository {
Future<ResponseModel<InspectionDeviceListBO>> getList({
required int pageSize,
required int pageNum,
int? roomId
}) {
return DioRequest.instance.get<InspectionDeviceListBO>(
'/app/device/inspection',
queryParameters: {'pageSize': pageSize, 'pageNum': pageNum, "roomId": roomId},
fromJsonT: (data) =>
InspectionDeviceListBO.fromJson(data as Map<String, dynamic>),
);
}
}
\ No newline at end of file
import '../repositories/inspection_device_repository.dart';
import '../models/bo/inspection_device_bo.dart';
class InspectionDeviceService {
final InspectionDeviceRepository _repository;
InspectionDeviceService({required InspectionDeviceRepository repository})
: _repository = repository;
Future<InspectionDeviceListBO> getList({
required int pageSize,
required int pageNum,
int? roomId
}) async {
final result =
await _repository.getList(pageSize: pageSize, pageNum: pageNum, roomId: roomId);
if (result.success && result.data != null) {
return result.data!;
}
throw Exception(result.msg);
}
}
\ No newline at end of file
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/models/bo/inspection_room_bo.dart';
import 'package:smart_hotel_app/repositories/inspection_device_repository.dart';
import 'package:smart_hotel_app/repositories/inspection_room_repository.dart'; import 'package:smart_hotel_app/repositories/inspection_room_repository.dart';
import 'package:smart_hotel_app/services/inspection_device_service.dart';
import 'package:smart_hotel_app/services/inspection_room_service.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> {
final InspectionRoomService _service; final InspectionRoomService _roomService;
final InspectionDeviceService _deviceService;
static const List<InspectionDevice> allDevices = [ static const int _pageSize = 20;
InspectionDevice(
id: '1',
icon: Icons.wifi,
name: '101主控网关',
type: '智能网关',
location: '101电视柜',
status: '正常',
time: '2024-01-15 14:30:00',
statusColor: Color.fromRGBO(26, 188, 156, 1.0),
),
InspectionDevice(
id: '2',
icon: Icons.thermostat,
name: '温湿度传感器-101',
type: '传感器',
location: '101角落',
status: '正常',
time: '2024-01-15 14:25:00',
statusColor: Color.fromRGBO(26, 188, 156, 1.0),
),
InspectionDevice(
id: '3',
icon: Icons.videocam,
name: '智能摄像头-102',
type: '摄像头',
location: '102门口',
status: '告警',
time: '2024-01-15 10:00:00',
statusColor: Color.fromRGBO(255, 77, 79, 1.0),
),
InspectionDevice(
id: '4',
icon: Icons.light,
name: '智能吸顶灯-102',
type: '传感器',
location: '102天花板',
status: '正常',
time: '2024-01-15 14:25:00',
statusColor: Color.fromRGBO(26, 188, 156, 1.0),
),
InspectionDevice(
id: '5',
icon: Icons.thermostat,
name: '温湿度传感器-103',
type: '传感器',
location: '103角落',
status: '正常',
time: '2024-01-15 14:25:00',
statusColor: Color.fromRGBO(26, 188, 156, 1.0),
),
InspectionDevice(
id: '6',
icon: Icons.light,
name: '103灯-智能',
type: '智能灯',
location: '103',
status: '开启',
time: '2024-01-15 14:20:00',
statusColor: Color.fromRGBO(26, 188, 156, 1.0),
),
InspectionDevice(
id: '7',
icon: Icons.sensor_door,
name: '门窗传感器-104',
type: '传感器',
location: '104门',
status: '离线',
time: '2024-01-15 12:00:00',
statusColor: Color.fromRGBO(102, 102, 102, 1.0),
),
InspectionDevice(
id: '8',
icon: Icons.kitchen,
name: '智能插座-105',
type: '插座',
location: '105台面',
status: '关闭',
time: '2024-01-15 14:15:00',
statusColor: Color.fromRGBO(100, 116, 139, 1.0),
),
InspectionDevice(
id: '9',
icon: Icons.smoke_free,
name: '烟雾报警器-105',
type: '报警器',
location: '105天花板',
status: '正常',
time: '2024-01-15 14:30:00',
statusColor: Color.fromRGBO(26, 188, 156, 1.0),
),
InspectionDevice(
id: '10',
icon: Icons.desk,
name: '智能台灯-106',
type: '智能灯',
location: '106书桌',
status: '关闭',
time: '2024-01-15 14:10:00',
statusColor: Color.fromRGBO(100, 116, 139, 1.0),
),
InspectionDevice(
id: '11',
icon: Icons.ac_unit,
name: '智能空调-101',
type: '空调',
location: '101',
status: '正常',
time: '2024-01-15 14:30:00',
statusColor: Color.fromRGBO(26, 188, 156, 1.0),
),
];
InspectionCubit({InspectionRoomService? service}) InspectionCubit({
: _service = service ?? InspectionRoomService? roomService,
InspectionRoomService( InspectionDeviceService? deviceService,
repository: InspectionRoomRepository(), }) : _roomService = roomService ??
), InspectionRoomService(repository: InspectionRoomRepository()),
_deviceService = deviceService ??
InspectionDeviceService(
repository: InspectionDeviceRepository()),
super(const InspectionState(isLoading: true)) { super(const InspectionState(isLoading: true)) {
initData(); initData();
} }
Future<void> initData() async { Future<void> initData() async {
try { try {
final rooms = await _service.getRooms(); final rooms = await _roomService.getRooms();
final roomNumbers = rooms.map((r) => r.roomNumber).toList(); final roomsMap = <int, String>{};
for (final r in rooms) {
roomsMap[r.roomId] = r.roomNumber;
}
final tabCounts = _computeTabCounts(rooms); final tabCounts = _computeTabCounts(rooms);
final firstRoomIds = rooms.isNotEmpty ? [rooms.first.roomId] : <int>[];
emit(state.copyWith( emit(state.copyWith(
isLoading: false, isLoading: false,
error: null, error: null,
allRooms: roomNumbers, allRooms: roomsMap,
selectedRooms: roomNumbers.isNotEmpty ? [roomNumbers.first] : [], selectedRoomIds: firstRoomIds,
tabCounts: tabCounts, tabCounts: tabCounts,
deviceList: _filterDevices(
selectedTab: DeviceStatusTab.total,
selectedRooms: roomNumbers.isNotEmpty ? [roomNumbers.first] : [],
),
)); ));
} catch (e) { } catch (e) {
emit(state.copyWith( emit(state.copyWith(
isLoading: false, isLoading: false,
error: e.toString(), error: e.toString(),
deviceList: _filterDevices(),
)); ));
} }
} }
/// 供 PagingController.fetchPage 回调使用
Future<List<InspectionDevice>> fetchPage(int pageKey) async {
final roomId = state.selectedRoomIds.length == 1
? state.selectedRoomIds.first
: null;
final result = await _deviceService.getList(
pageSize: _pageSize,
pageNum: pageKey,
roomId: roomId,
);
return result.data
.map((e) => InspectionDevice.fromInspectionDeviceBO(e))
.toList();
}
Map<DeviceStatusTab, int> _computeTabCounts(List<InspectionRoomBO> rooms) { Map<DeviceStatusTab, int> _computeTabCounts(List<InspectionRoomBO> rooms) {
int normal = 0; int normal = 0;
int warning = 0; int warning = 0;
...@@ -180,60 +90,17 @@ class InspectionCubit extends Cubit<InspectionState> { ...@@ -180,60 +90,17 @@ class InspectionCubit extends Cubit<InspectionState> {
} }
void selectTab(DeviceStatusTab tab) { void selectTab(DeviceStatusTab tab) {
emit(state.copyWith( emit(state.copyWith(selectedTab: tab));
selectedTab: tab,
deviceList: _filterDevices(selectedTab: tab),
));
} }
void toggleRoom(String room) { void toggleRoom(int roomId) {
final List<String> newSelectedRooms = List.from(state.selectedRooms); final List<int> newSelected = List.from(state.selectedRoomIds);
if (newSelectedRooms.contains(room)) { if (newSelected.contains(roomId)) {
newSelectedRooms.remove(room); newSelected.remove(roomId);
} else { } else {
newSelectedRooms.add(room); newSelected.clear();
} newSelected.add(roomId);
emit(state.copyWith(
selectedRooms: newSelectedRooms,
deviceList: _filterDevices(selectedRooms: newSelectedRooms),
));
}
List<InspectionDevice> _filterDevices({
DeviceStatusTab? selectedTab,
List<String>? selectedRooms,
}) {
DeviceStatusTab tab = selectedTab ?? state.selectedTab;
List<String> rooms = selectedRooms ?? state.selectedRooms;
List<InspectionDevice> filtered = List.from(allDevices);
if (rooms.isNotEmpty) {
filtered = filtered.where((device) {
return rooms.any(
(room) => device.location.contains(room) || device.name.contains(room));
}).toList();
} }
emit(state.copyWith(selectedRoomIds: newSelected));
if (tab != DeviceStatusTab.total) {
filtered = filtered.where((device) {
switch (tab) {
case DeviceStatusTab.on:
return device.status == '正常' || device.status == '开启';
case DeviceStatusTab.off:
return device.status == '关闭';
case DeviceStatusTab.offline:
return device.status == '离线';
case DeviceStatusTab.alarm:
return device.status == '告警';
case DeviceStatusTab.fault:
return device.status == '故障';
default:
return true;
}
}).toList();
}
return filtered;
} }
} }
\ No newline at end of file
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:smart_hotel_app/models/bo/inspection_device_bo.dart';
enum DeviceStatusTab { total, on, off, offline, alarm, fault } enum DeviceStatusTab { total, on, off, offline, alarm, fault }
...@@ -23,24 +24,78 @@ class InspectionDevice { ...@@ -23,24 +24,78 @@ class InspectionDevice {
required this.time, required this.time,
required this.statusColor, required this.statusColor,
}); });
factory InspectionDevice.fromInspectionDeviceBO(InspectionDeviceBO bo) {
return InspectionDevice(
id: bo.deviceId.toString(),
icon: _mapIcon(bo.deviceTypeIcon),
name: bo.deviceName,
type: bo.deviceTypeName,
location: bo.roomName,
status: bo.runStatusText,
time: bo.lastHeartbeatDisplay,
statusColor: _mapStatusColor(bo.runStatus),
);
}
static IconData _mapIcon(String iconType) {
switch (iconType) {
case 'gateway':
return Icons.wifi;
case 'sensor':
return Icons.thermostat;
case 'camera':
return Icons.videocam;
case 'light':
return Icons.light;
case 'air_conditioner':
case 'ac':
return Icons.ac_unit;
case 'socket':
return Icons.kitchen;
case 'alarm':
return Icons.smoke_free;
case 'door_sensor':
return Icons.sensor_door;
default:
return Icons.devices_other;
}
}
static Color _mapStatusColor(String runStatus) {
switch (runStatus) {
case 'normal':
case 'online':
return const Color.fromRGBO(26, 188, 156, 1.0);
case 'warning':
case 'alarm':
return const Color.fromRGBO(255, 77, 79, 1.0);
case 'offline':
return const Color.fromRGBO(102, 102, 102, 1.0);
case 'fault':
return const Color.fromRGBO(239, 68, 68, 1.0);
case 'off':
return const Color.fromRGBO(100, 116, 139, 1.0);
default:
return const Color.fromRGBO(100, 116, 139, 1.0);
}
}
} }
class InspectionState extends Equatable { class InspectionState extends Equatable {
final bool isLoading; final bool isLoading;
final String? error; final String? error;
final DeviceStatusTab selectedTab; final DeviceStatusTab selectedTab;
final List<String> selectedRooms; final List<int> selectedRoomIds;
final List<String> allRooms; final Map<int, String> allRooms;
final List<InspectionDevice> deviceList;
final Map<DeviceStatusTab, int> tabCounts; final Map<DeviceStatusTab, int> tabCounts;
const InspectionState({ const InspectionState({
this.isLoading = false, this.isLoading = false,
this.error, this.error,
this.selectedTab = DeviceStatusTab.total, this.selectedTab = DeviceStatusTab.total,
this.selectedRooms = const [], this.selectedRoomIds = const [],
this.allRooms = const [], this.allRooms = const {},
this.deviceList = const [],
this.tabCounts = const { this.tabCounts = const {
DeviceStatusTab.total: 0, DeviceStatusTab.total: 0,
DeviceStatusTab.on: 0, DeviceStatusTab.on: 0,
...@@ -55,18 +110,16 @@ class InspectionState extends Equatable { ...@@ -55,18 +110,16 @@ class InspectionState extends Equatable {
bool? isLoading, bool? isLoading,
String? error, String? error,
DeviceStatusTab? selectedTab, DeviceStatusTab? selectedTab,
List<String>? selectedRooms, List<int>? selectedRoomIds,
List<String>? allRooms, Map<int, String>? allRooms,
List<InspectionDevice>? deviceList,
Map<DeviceStatusTab, int>? tabCounts, Map<DeviceStatusTab, int>? tabCounts,
}) { }) {
return InspectionState( return InspectionState(
isLoading: isLoading ?? this.isLoading, isLoading: isLoading ?? this.isLoading,
error: this.error, error: error,
selectedTab: selectedTab ?? this.selectedTab, selectedTab: selectedTab ?? this.selectedTab,
selectedRooms: selectedRooms ?? this.selectedRooms, selectedRoomIds: selectedRoomIds ?? this.selectedRoomIds,
allRooms: allRooms ?? this.allRooms, allRooms: allRooms ?? this.allRooms,
deviceList: deviceList ?? this.deviceList,
tabCounts: tabCounts ?? this.tabCounts, tabCounts: tabCounts ?? this.tabCounts,
); );
} }
...@@ -76,9 +129,8 @@ class InspectionState extends Equatable { ...@@ -76,9 +129,8 @@ class InspectionState extends Equatable {
isLoading, isLoading,
error, error,
selectedTab, selectedTab,
selectedRooms, selectedRoomIds,
allRooms, allRooms,
deviceList,
tabCounts, tabCounts,
]; ];
} }
\ No newline at end of file
...@@ -2,10 +2,12 @@ import 'package:auto_route/auto_route.dart'; ...@@ -2,10 +2,12 @@ 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:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:smart_hotel_app/routes/app_router.gr.dart';
import 'package:smart_hotel_app/views/home/inspection/cubit/inspection_cubit.dart'; import 'package:smart_hotel_app/views/home/inspection/cubit/inspection_cubit.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';
import 'package:smart_hotel_app/views/home/inspection/widget/filter_panel.dart';
import 'package:smart_hotel_app/views/home/inspection/widget/device_list.dart'; import 'package:smart_hotel_app/views/home/inspection/widget/device_list.dart';
import 'package:smart_hotel_app/views/home/inspection/widget/filter_panel.dart';
@RoutePage() @RoutePage()
class InspectionDetailView extends StatelessWidget { class InspectionDetailView extends StatelessWidget {
...@@ -15,38 +17,78 @@ class InspectionDetailView extends StatelessWidget { ...@@ -15,38 +17,78 @@ class InspectionDetailView extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (_) => InspectionCubit(), create: (_) => InspectionCubit(),
child: Scaffold( child: const _InspectionDetailBody(),
backgroundColor: const Color.fromRGBO(242, 243, 245, 1), );
appBar: AppBar( }
backgroundColor: Colors.white, }
elevation: 0,
leading: IconButton( class _InspectionDetailBody extends StatefulWidget {
icon: const Icon(Icons.arrow_back_ios, const _InspectionDetailBody();
color: Color.fromRGBO(100, 116, 139, 1)),
onPressed: () { @override
context.popRoute(); State<_InspectionDetailBody> createState() => _InspectionDetailBodyState();
}
class _InspectionDetailBodyState extends State<_InspectionDetailBody> {
late final PagingController<int, InspectionDevice> _pagingController;
DeviceStatusTab? _lastTab;
List<int>? _lastRoomIds;
@override
void initState() {
super.initState();
_pagingController = PagingController<int, InspectionDevice>(
getNextPageKey: (state) {
if (state.keys == null || state.keys!.isEmpty) return 1;
return state.lastPageIsEmpty ? null : state.nextIntPageKey;
}, },
), fetchPage: (pageKey) =>
title: Text( context.read<InspectionCubit>().fetchPage(pageKey),
'设备巡检', );
style: TextStyle( }
color: const Color.fromRGBO(10, 13, 20, 1),
fontSize: 32.sp, void _checkFilterChanged(InspectionState state) {
fontWeight: FontWeight.w200, if (_lastTab != state.selectedTab ||
), _lastRoomIds != state.selectedRoomIds) {
), _lastTab = state.selectedTab;
centerTitle: true, _lastRoomIds = state.selectedRoomIds;
), _pagingController.refresh();
body: BlocBuilder<InspectionCubit, InspectionState>( }
builder: (context, state) { }
@override
void dispose() {
_pagingController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final cubit = context.read<InspectionCubit>(); final cubit = context.read<InspectionCubit>();
return BlocListener<InspectionCubit, InspectionState>(
listener: (context, state) {
if (_lastTab != null) {
_checkFilterChanged(state);
}
},
child: BlocBuilder<InspectionCubit, InspectionState>(
builder: (context, state) {
_checkFilterChanged(state);
if (state.isLoading) { if (state.isLoading) {
return const Center(child: CircularProgressIndicator()); return Scaffold(
backgroundColor: const Color.fromRGBO(242, 243, 245, 1),
appBar: _buildAppBar(context),
body: const Center(child: CircularProgressIndicator()),
);
} }
if (state.error != null) { if (state.error != null) {
return Center( return Scaffold(
backgroundColor: const Color.fromRGBO(242, 243, 245, 1),
appBar: _buildAppBar(context),
body: Center(
child: Padding( child: Padding(
padding: EdgeInsets.symmetric(horizontal: 28.w), padding: EdgeInsets.symmetric(horizontal: 28.w),
child: Column( child: Column(
...@@ -56,54 +98,90 @@ class InspectionDetailView extends StatelessWidget { ...@@ -56,54 +98,90 @@ class InspectionDetailView extends StatelessWidget {
'加载失败: ${state.error}', '加载失败: ${state.error}',
style: TextStyle( style: TextStyle(
fontSize: 28.sp, fontSize: 28.sp,
color: const Color.fromRGBO(255, 100, 101, 1), color:
const Color.fromRGBO(255, 100, 101, 1),
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
SizedBox(height: 24.h), SizedBox(height: 24.h),
ElevatedButton( ElevatedButton(
onPressed: () => cubit.initData(), onPressed: () => cubit.initData(),
child: Text('重新加载', style: TextStyle(fontSize: 28.sp)), child: Text('重新加载',
style: TextStyle(fontSize: 28.sp)),
), ),
], ],
), ),
), ),
),
); );
} }
return SingleChildScrollView( return Scaffold(
padding: EdgeInsets.symmetric(horizontal: 28.w, vertical: 20.h), backgroundColor: const Color.fromRGBO(242, 243, 245, 1),
child: Column( appBar: _buildAppBar(context),
children: [ body: PagingListener<int, InspectionDevice>(
FilterPanel( controller: _pagingController,
builder: (context, pagingState, fetchNextPage) {
return CustomScrollView(
slivers: [
SliverPadding(
padding: EdgeInsets.symmetric(
horizontal: 28.w, vertical: 20.h),
sliver: SliverToBoxAdapter(
child: FilterPanel(
selectedTab: state.selectedTab, selectedTab: state.selectedTab,
tabCounts: state.tabCounts, tabCounts: state.tabCounts,
onTabSelected: (tab) => cubit.selectTab(tab), onTabSelected: cubit.selectTab,
selectedRooms: state.selectedRooms, selectedRoomIds: state.selectedRoomIds,
allRooms: state.allRooms, allRooms: state.allRooms,
onRoomToggle: (room) => cubit.toggleRoom(room), onRoomToggle: cubit.toggleRoom,
), ),
SizedBox(height: 20.h), ),
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),
), ),
SliverToBoxAdapter(
child: SizedBox(height: 20.h),
),
DeviceList(
pagingState: pagingState,
fetchNextPage: fetchNextPage,
onTap: (item, ctx) {
ctx.pushRoute(InspectionDeviceRoute(
deviceId: item.id));
},
),
SliverToBoxAdapter(
child: SizedBox(height: 30.h),
), ),
)
else
DeviceList(deviceList: state.deviceList),
SizedBox(height: 30.h),
], ],
);
},
), ),
); );
}, },
), ),
);
}
PreferredSizeWidget _buildAppBar(BuildContext context) {
return AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios,
color: Color.fromRGBO(100, 116, 139, 1)),
onPressed: () {
context.popRoute();
},
),
title: Text(
'设备巡检',
style: TextStyle(
color: const Color.fromRGBO(10, 13, 20, 1),
fontSize: 32.sp,
fontWeight: FontWeight.w200,
), ),
),
centerTitle: true,
); );
} }
} }
\ No newline at end of file
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/routes/app_router.gr.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.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';
import 'package:smart_hotel_app/views/home/inspection/widget/device_list_item.dart'; import 'package:smart_hotel_app/views/home/inspection/widget/device_list_item.dart';
class DeviceList extends StatelessWidget { class DeviceList extends StatelessWidget {
final List<InspectionDevice> deviceList; final PagingState<int, InspectionDevice> pagingState;
final VoidCallback fetchNextPage;
final void Function(InspectionDevice item, BuildContext context) onTap;
const DeviceList({ const DeviceList({
super.key, super.key,
required this.deviceList, required this.pagingState,
required this.fetchNextPage,
required this.onTap,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return SliverPadding(
crossAxisAlignment: CrossAxisAlignment.start, padding: EdgeInsets.symmetric(horizontal: 28.w),
sliver: PagedSliverList<int, InspectionDevice>(
state: pagingState,
fetchNextPage: fetchNextPage,
builderDelegate: PagedChildBuilderDelegate<InspectionDevice>(
firstPageErrorIndicatorBuilder: (_) => Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(
'设备列表 (${deviceList.length})', '加载失败: ${pagingState.error}',
style: TextStyle( style: TextStyle(
fontSize: 28.sp, fontSize: 28.sp,
fontWeight: FontWeight.bold, color: const Color.fromRGBO(255, 100, 101, 1),
color: const Color.fromRGBO(10, 13, 20, 1.0), ),
textAlign: TextAlign.center,
),
SizedBox(height: 24.h),
ElevatedButton(
onPressed: fetchNextPage,
child:
Text('重新加载', style: TextStyle(fontSize: 28.sp)),
),
],
),
),
noItemsFoundIndicatorBuilder: (_) => Padding(
padding: EdgeInsets.only(top: 60.h),
child: Text(
'暂无设备数据',
style: TextStyle(
fontSize: 28.sp,
color: const Color.fromRGBO(100, 116, 139, 1),
),
),
),
newPageErrorIndicatorBuilder: (_) => Center(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 20.h),
child: Column(
children: [
Text(
'加载失败: ${pagingState.error}',
style: TextStyle(
fontSize: 24.sp,
color: const Color.fromRGBO(255, 100, 101, 1),
), ),
), ),
SizedBox(height: 12.h), SizedBox(height: 12.h),
ListView.separated( TextButton(
shrinkWrap: true, onPressed: fetchNextPage,
physics: const NeverScrollableScrollPhysics(), child: Text('重试', style: TextStyle(fontSize: 24.sp)),
itemCount: deviceList.length, ),
separatorBuilder: (context, index) => SizedBox(height: 12.h), ],
itemBuilder: (context, index) { ),
),
),
itemBuilder: (_, item, __) {
return DeviceListItem( return DeviceListItem(
device: deviceList[index], device: item,
onTap: () { onTap: () => onTap(item, context),
context.pushRoute(
InspectionDeviceRoute(deviceId: deviceList[index].id),
);
},
); );
}, },
), ),
], ),
); );
} }
} }
\ No newline at end of file
...@@ -6,16 +6,16 @@ class FilterPanel extends StatelessWidget { ...@@ -6,16 +6,16 @@ class FilterPanel extends StatelessWidget {
final DeviceStatusTab selectedTab; final DeviceStatusTab selectedTab;
final Map<DeviceStatusTab, int> tabCounts; final Map<DeviceStatusTab, int> tabCounts;
final Function(DeviceStatusTab) onTabSelected; final Function(DeviceStatusTab) onTabSelected;
final List<String> selectedRooms; final List<int> selectedRoomIds;
final List<String> allRooms; final Map<int, String> allRooms;
final Function(String) onRoomToggle; final Function(int) onRoomToggle;
const FilterPanel({ const FilterPanel({
super.key, super.key,
required this.selectedTab, required this.selectedTab,
required this.tabCounts, required this.tabCounts,
required this.onTabSelected, required this.onTabSelected,
required this.selectedRooms, required this.selectedRoomIds,
required this.allRooms, required this.allRooms,
required this.onRoomToggle, required this.onRoomToggle,
}); });
...@@ -126,6 +126,7 @@ class FilterPanel extends StatelessWidget { ...@@ -126,6 +126,7 @@ class FilterPanel extends StatelessWidget {
} }
Widget _buildRoomSelector() { Widget _buildRoomSelector() {
final roomEntries = allRooms.entries.toList();
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
...@@ -138,7 +139,7 @@ class FilterPanel extends StatelessWidget { ...@@ -138,7 +139,7 @@ class FilterPanel extends StatelessWidget {
), ),
), ),
SizedBox(height: 16.h), SizedBox(height: 16.h),
if (allRooms.isEmpty) if (roomEntries.isEmpty)
Text( Text(
'暂无房间数据', '暂无房间数据',
style: TextStyle( style: TextStyle(
...@@ -150,10 +151,10 @@ class FilterPanel extends StatelessWidget { ...@@ -150,10 +151,10 @@ class FilterPanel extends StatelessWidget {
Wrap( Wrap(
spacing: 5.w, spacing: 5.w,
runSpacing: 12.h, runSpacing: 12.h,
children: allRooms.map((room) { children: roomEntries.map((entry) {
final isSelected = selectedRooms.contains(room); final isSelected = selectedRoomIds.contains(entry.key);
return GestureDetector( return GestureDetector(
onTap: () => onRoomToggle(room), onTap: () => onRoomToggle(entry.key),
child: Container( child: Container(
width: 120.w, width: 120.w,
padding: EdgeInsets.symmetric(horizontal: 28.w, vertical: 12.h), padding: EdgeInsets.symmetric(horizontal: 28.w, vertical: 12.h),
...@@ -170,7 +171,7 @@ class FilterPanel extends StatelessWidget { ...@@ -170,7 +171,7 @@ class FilterPanel extends StatelessWidget {
), ),
), ),
child: Text( child: Text(
room, entry.value,
style: TextStyle( style: TextStyle(
fontSize: 24.sp, fontSize: 24.sp,
color: isSelected color: isSelected
......
# 智慧酒店 App 接口对接方案 # 智慧酒店 App 接口对接方案
...@@ -195,11 +195,21 @@ class xxxBO extends Equatable { ...@@ -195,11 +195,21 @@ class xxxBO extends Equatable {
| 设备巡检-房间 |GET | /app/device/inspection/rooms | 已对接 | 告警列表 | 相关页面:FilterPanel | | 设备巡检-房间 |GET | /app/device/inspection/rooms | 已对接 | 告警列表 | 相关页面:FilterPanel |
| 设备巡检-设备列表 |GET | /app/device/inspection | 未对接 | 告警列表 | 相关页面:DeviceList | | 设备巡检-设备列表 |GET | /app/device/inspection | 已对接 | 设备巡检 | 相关页面:DeviceList |
| 设备拓扑图 |GET | /app/device/detail | 未对接 | 设备拓扑图 | 相关页面:InspectionDeviceView |
| 设备拓扑图-开始巡检 |POST | /app/device/inspection/start | 未对接 | 设备拓扑图-开始巡检 | 相关页面:InspectionDeviceView |
| 巡检记录 |GET | /app/device/inspection/list | 未对接 | 设备拓扑图 | 相关页面:InspectionHistoryView |
| 获取当前登录用户信息 |GET | /app/auth/user-info | 未对接 | 获取当前登录用户信息 | |
| 设备拓扑图 |GET | /app/device/detail | 未对接 | 设备拓扑图 | 相关页面:DeviceList |
| 设备拓扑图-开始巡检 |GET | /app/device/inspection/start | 未对接 | 设备拓扑图-开始巡检 | 相关页面:DeviceList |
...@@ -574,6 +584,7 @@ class xxxBO extends Equatable { ...@@ -574,6 +584,7 @@ class xxxBO extends Equatable {
``` ```
求体: 求体:
{ {
"roomId":0,
"pageSize": 20, "pageSize": 20,
"pageNum": 1 "pageNum": 1
} }
...@@ -674,6 +685,87 @@ class xxxBO extends Equatable { ...@@ -674,6 +685,87 @@ class xxxBO extends Equatable {
``` ```
#### 巡检记录
**GET /app/device/inspection/list**
```
求体:
{
"pageSize": 10,
"pageNum": 1,
"deviceId": 0
}
应 data:
{
"code": 0,
"msg": "string",
"data": {
"total": 0,
"tabCount": { // 各Tab对应的巡检数量统计,用于顶部Tab标签右侧的数字角标(如「总计(6) 通过(3) 警告(1) 不通过(2)」),一次性返回4种状态的数量避免多次请求
"total": 0,
"passCount": 0,
"warnCount": 0,
"failCount": 0
},
"deviceInfo": { // 当前查询目标设备的基础信息
"deviceId": 0,
"deviceName": "string",
"deviceIcon": "string"
},
"rows": [ // 巡检记录行列表
{
"inspectId": 0,
"inspectorName": "string",
"inspectTime": "string",
"inspectStatus": "string",
"remark": "string",
"itemsDetail": [ // 巡检项明细列表
{
"name": "string",
"value": "string",
"status": "string"
}
]
}
]
}
}
```
#### 获取当前登录用户信息
**GET /app/auth/user-info**
```
求体:
{
}
应 data:
{
"code": 0,
"msg": "string",
"data": {
"userId": 0,
"userName": "string",
"nickName": "string",
"phonenumber": "string",
"email": "string",
"sex": "string",
"avatarUrl": "string",
"deptName": "string",
"tenantId": "string"
}
}
```
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment