Commit 11226a07 authored by 张宏's avatar 张宏

111111111

parent 6a1c8b13
This diff is collapsed.
# 项目涉及技术及版本
- flutter 3.41.9
- dart 3.11.5
- 技术栈bloc+cubit+auto_route
# 页面模块相关目录结构
注意:模块下的根文件命名规则:模块名称_index_view.dart、模块名称_list_view.dart、模块名称_detail_view.dart
```
views/
└── 首页/
├── 模块1/
│ ├── widgets/
│ │ ├── xxx_block.dart
│ │ └── xxx_block.dart
│ ├── cubit/
│ │ ├── 模块1_index_cubit.dart (内容格式:详见xxx_index_cubit 模版)
│ │ └── 模块1_index_state.dart (内容格式:详见xxx_index_state 模版)
│ └── 模块1_index_view.dart
├── 模块2/
│ ├── widgets/
│ │ ├── xxx_block.dart
│ │ └── xxx_block.dart
│ ├── cubit/
│ │ ├── 模块2_list_cubit.dart
│ │ └── 模块2_list_state.dart
│ └── 模块2_list_view.dart
├── 模块3/
│ ├── widgets/
│ │ ├── xxx_block.dart
│ │ └── xxx_block.dart
│ ├── cubit/
│ │ ├── 模块3_detail_cubit.dart
│ │ └── 模块3_detail_state.dart
│ └── 模块3_detail_view.dart
└── 模块4/
配置/
├── 模块1/
├── 模块2/
├── 模块3/
└── 模块4/
我的/
├── 模块1/
├── 模块2/
├── 模块3/
└── 模块4/
```
> **说明:** 每个页面模块包含三部分 —— `widgets/`(组件)、`cubit/`(状态管理)、`*_view.dart`(页面视图)。模块名替换为实际业务名称,如 `banner/`、`goods_list/` 等。
# 页面调用链路
链路分2种:
1. view>cubit>services>repositories
2. view>bloc>services>repositories
# 模拟数据
在开发页面时,相关数据全部先模拟,后续接口接入后在调整为真实。
切换开关在constants.dart 中 static const bool useMockData = true; // true=Mock, false=真实接口
# 注意事项
- 禁止删除注释代码
\ No newline at end of file
import 'dart:async'; import 'dart:async';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:laki_icu_app/blocs/auth/auth_bloc.dart'; import 'package:laki_icu_app/blocs/auth/auth_bloc.dart';
import 'package:laki_icu_app/blocs/auth/auth_event.dart'; import 'package:laki_icu_app/blocs/auth/auth_event.dart';
...@@ -79,6 +80,9 @@ class _MyAppState extends State<MyApp> { ...@@ -79,6 +80,9 @@ class _MyAppState extends State<MyApp> {
); );
_appRouter = AppRouter(); _appRouter = AppRouter();
// 全局全屏:隐藏系统状态栏和底部导航栏
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
// 监听 Token 过期事件 // 监听 Token 过期事件
_tokenExpiredSubscription = _tokenExpiredSubscription =
eventBus.on<TokenExpiredEvent>().listen((event) { eventBus.on<TokenExpiredEvent>().listen((event) {
......
// ignore_for_file: unused_import
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:laki_icu_app/views/home/index/home_index_view.dart'; import 'package:laki_icu_app/views/home/index/home_index_view.dart';
import 'package:laki_icu_app/views/login/login_view.dart'; import 'package:laki_icu_app/views/login/login_view.dart';
import 'package:laki_icu_app/views/layout/default_view.dart'; import 'package:laki_icu_app/views/layout/default_view.dart';
import 'package:laki_icu_app/views/monitoring/index/monitoring_index_view.dart';
import 'app_router.gr.dart'; import 'app_router.gr.dart';
@AutoRouterConfig(replaceInRouteName: 'View,Route') @AutoRouterConfig(replaceInRouteName: 'View,Route')
...@@ -18,8 +21,10 @@ class AppRouter extends $AppRouter { ...@@ -18,8 +21,10 @@ class AppRouter extends $AppRouter {
AutoRoute( AutoRoute(
page: DefaultLayoutRoute.page, page: DefaultLayoutRoute.page,
children: [ children: [
// HomeRoute / HomeView(首页) // MonitoringIndexRoute / MonitoringIndexView(监控页)
AutoRoute(page: HomeIndexRoute.page, initial: true), AutoRoute(page: MonitoringIndexRoute.page, initial: true),
// HomeRoute / HomeView(首页,物理文件保留)
AutoRoute(page: HomeIndexRoute.page),
], ],
), ),
]; ];
......
...@@ -8,25 +8,27 @@ ...@@ -8,25 +8,27 @@
// coverage:ignore-file // coverage:ignore-file
// ignore_for_file: no_leading_underscores_for_library_prefixes // ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:auto_route/auto_route.dart' as _i4; import 'package:auto_route/auto_route.dart' as _i5;
import 'package:flutter/material.dart' as _i5; import 'package:flutter/material.dart' as _i6;
import 'package:laki_icu_app/views/home/index/home_index_view.dart' as _i2; import 'package:laki_icu_app/views/home/index/home_index_view.dart' as _i2;
import 'package:laki_icu_app/views/layout/default_view.dart' as _i1; import 'package:laki_icu_app/views/layout/default_view.dart' as _i1;
import 'package:laki_icu_app/views/login/login_view.dart' as _i3; import 'package:laki_icu_app/views/login/login_view.dart' as _i3;
import 'package:laki_icu_app/views/monitoring/index/monitoring_index_view.dart'
as _i4;
abstract class $AppRouter extends _i4.RootStackRouter { abstract class $AppRouter extends _i5.RootStackRouter {
$AppRouter({super.navigatorKey}); $AppRouter({super.navigatorKey});
@override @override
final Map<String, _i4.PageFactory> pagesMap = { final Map<String, _i5.PageFactory> pagesMap = {
DefaultLayoutRoute.name: (routeData) { DefaultLayoutRoute.name: (routeData) {
return _i4.AutoRoutePage<dynamic>( return _i5.AutoRoutePage<dynamic>(
routeData: routeData, routeData: routeData,
child: const _i1.DefaultLayoutView(), child: const _i1.DefaultLayoutView(),
); );
}, },
HomeIndexRoute.name: (routeData) { HomeIndexRoute.name: (routeData) {
return _i4.AutoRoutePage<dynamic>( return _i5.AutoRoutePage<dynamic>(
routeData: routeData, routeData: routeData,
child: const _i2.HomeIndexView(), child: const _i2.HomeIndexView(),
); );
...@@ -34,18 +36,24 @@ abstract class $AppRouter extends _i4.RootStackRouter { ...@@ -34,18 +36,24 @@ abstract class $AppRouter extends _i4.RootStackRouter {
LoginRoute.name: (routeData) { LoginRoute.name: (routeData) {
final args = routeData.argsAs<LoginRouteArgs>( final args = routeData.argsAs<LoginRouteArgs>(
orElse: () => const LoginRouteArgs()); orElse: () => const LoginRouteArgs());
return _i4.AutoRoutePage<dynamic>( return _i5.AutoRoutePage<dynamic>(
routeData: routeData, routeData: routeData,
child: _i3.LoginView(key: args.key), child: _i3.LoginView(key: args.key),
); );
}, },
MonitoringIndexRoute.name: (routeData) {
return _i5.AutoRoutePage<dynamic>(
routeData: routeData,
child: const _i4.MonitoringIndexView(),
);
},
}; };
} }
/// generated route for /// generated route for
/// [_i1.DefaultLayoutView] /// [_i1.DefaultLayoutView]
class DefaultLayoutRoute extends _i4.PageRouteInfo<void> { class DefaultLayoutRoute extends _i5.PageRouteInfo<void> {
const DefaultLayoutRoute({List<_i4.PageRouteInfo>? children}) const DefaultLayoutRoute({List<_i5.PageRouteInfo>? children})
: super( : super(
DefaultLayoutRoute.name, DefaultLayoutRoute.name,
initialChildren: children, initialChildren: children,
...@@ -53,13 +61,13 @@ class DefaultLayoutRoute extends _i4.PageRouteInfo<void> { ...@@ -53,13 +61,13 @@ class DefaultLayoutRoute extends _i4.PageRouteInfo<void> {
static const String name = 'DefaultLayoutRoute'; static const String name = 'DefaultLayoutRoute';
static const _i4.PageInfo<void> page = _i4.PageInfo<void>(name); static const _i5.PageInfo<void> page = _i5.PageInfo<void>(name);
} }
/// generated route for /// generated route for
/// [_i2.HomeIndexView] /// [_i2.HomeIndexView]
class HomeIndexRoute extends _i4.PageRouteInfo<void> { class HomeIndexRoute extends _i5.PageRouteInfo<void> {
const HomeIndexRoute({List<_i4.PageRouteInfo>? children}) const HomeIndexRoute({List<_i5.PageRouteInfo>? children})
: super( : super(
HomeIndexRoute.name, HomeIndexRoute.name,
initialChildren: children, initialChildren: children,
...@@ -67,15 +75,15 @@ class HomeIndexRoute extends _i4.PageRouteInfo<void> { ...@@ -67,15 +75,15 @@ class HomeIndexRoute extends _i4.PageRouteInfo<void> {
static const String name = 'HomeIndexRoute'; static const String name = 'HomeIndexRoute';
static const _i4.PageInfo<void> page = _i4.PageInfo<void>(name); static const _i5.PageInfo<void> page = _i5.PageInfo<void>(name);
} }
/// generated route for /// generated route for
/// [_i3.LoginView] /// [_i3.LoginView]
class LoginRoute extends _i4.PageRouteInfo<LoginRouteArgs> { class LoginRoute extends _i5.PageRouteInfo<LoginRouteArgs> {
LoginRoute({ LoginRoute({
_i5.Key? key, _i6.Key? key,
List<_i4.PageRouteInfo>? children, List<_i5.PageRouteInfo>? children,
}) : super( }) : super(
LoginRoute.name, LoginRoute.name,
args: LoginRouteArgs(key: key), args: LoginRouteArgs(key: key),
...@@ -84,17 +92,31 @@ class LoginRoute extends _i4.PageRouteInfo<LoginRouteArgs> { ...@@ -84,17 +92,31 @@ class LoginRoute extends _i4.PageRouteInfo<LoginRouteArgs> {
static const String name = 'LoginRoute'; static const String name = 'LoginRoute';
static const _i4.PageInfo<LoginRouteArgs> page = static const _i5.PageInfo<LoginRouteArgs> page =
_i4.PageInfo<LoginRouteArgs>(name); _i5.PageInfo<LoginRouteArgs>(name);
} }
class LoginRouteArgs { class LoginRouteArgs {
const LoginRouteArgs({this.key}); const LoginRouteArgs({this.key});
final _i5.Key? key; final _i6.Key? key;
@override @override
String toString() { String toString() {
return 'LoginRouteArgs{key: $key}'; return 'LoginRouteArgs{key: $key}';
} }
} }
/// generated route for
/// [_i4.MonitoringIndexView]
class MonitoringIndexRoute extends _i5.PageRouteInfo<void> {
const MonitoringIndexRoute({List<_i5.PageRouteInfo>? children})
: super(
MonitoringIndexRoute.name,
initialChildren: children,
);
static const String name = 'MonitoringIndexRoute';
static const _i5.PageInfo<void> page = _i5.PageInfo<void>(name);
}
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:laki_icu_app/services/monitoring_service.dart';
import 'monitoring_index_state.dart';
class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
final MonitoringService _monitoringService;
MonitoringIndexCubit()
: _monitoringService = MonitoringService(),
super(const MonitoringIndexState()) {
loadData();
}
Future<void> loadData() async {
emit(state.copyWith(
status: MonitoringIndexStatus.loading,
isLoading: true,
error: null,
));
try {
final metrics = await _monitoringService.getMetrics();
if (isClosed) return;
final patientInfo = await _monitoringService.getPatientInfo();
if (isClosed) return;
final alerts = await _monitoringService.getAlerts();
if (isClosed) return;
final menuItems = await _monitoringService.getMenuItems();
if (isClosed) return;
emit(state.copyWith(
status: MonitoringIndexStatus.success,
isLoading: false,
metrics: metrics,
patientInfo: patientInfo,
alerts: alerts,
menuItems: menuItems,
error: null,
));
} catch (e) {
if (isClosed) return;
emit(state.copyWith(
status: MonitoringIndexStatus.failure,
isLoading: false,
error: e.toString(),
));
}
}
}
import 'package:equatable/equatable.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
enum MonitoringIndexStatus {
initial,
loading,
success,
failure,
}
class MonitoringIndexState extends Equatable {
final MonitoringIndexStatus status;
final bool isLoading;
final String? error;
final List<MonitoringMetricBO> metrics;
final PatientInfoBO? patientInfo;
final List<AlertInfoBO> alerts;
final List<MonitoringMenuItemBO> menuItems;
const MonitoringIndexState({
this.status = MonitoringIndexStatus.initial,
this.isLoading = false,
this.error,
this.metrics = const [],
this.patientInfo,
this.alerts = const [],
this.menuItems = const [],
});
MonitoringIndexState copyWith({
MonitoringIndexStatus? status,
bool? isLoading,
String? error,
List<MonitoringMetricBO>? metrics,
PatientInfoBO? patientInfo,
List<AlertInfoBO>? alerts,
List<MonitoringMenuItemBO>? menuItems,
}) {
return MonitoringIndexState(
status: status ?? this.status,
isLoading: isLoading ?? this.isLoading,
error: error ?? this.error,
metrics: metrics ?? this.metrics,
patientInfo: patientInfo ?? this.patientInfo,
alerts: alerts ?? this.alerts,
menuItems: menuItems ?? this.menuItems,
);
}
@override
List<Object?> get props => [
status,
isLoading,
error,
metrics,
patientInfo,
alerts,
menuItems,
];
}
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
import 'cubit/monitoring_index_cubit.dart';
import 'cubit/monitoring_index_state.dart';
import 'widgets/monitoring_alert_card.dart';
import 'widgets/monitoring_bottom_menu.dart';
import 'widgets/monitoring_metric_card.dart';
import 'widgets/monitoring_pet_info_card.dart';
import 'widgets/monitoring_top_status_bar.dart';
import 'widgets/monitoring_video_card.dart';
@RoutePage()
class MonitoringIndexView extends StatelessWidget {
const MonitoringIndexView({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => MonitoringIndexCubit(),
child: const MonitoringIndexContent(),
);
}
}
class MonitoringIndexContent extends StatefulWidget {
const MonitoringIndexContent({super.key});
@override
State<MonitoringIndexContent> createState() => _MonitoringIndexContentState();
}
class _MonitoringIndexContentState extends State<MonitoringIndexContent> {
late final MonitoringIndexCubit _monitoringIndexCubit;
@override
void initState() {
super.initState();
_monitoringIndexCubit = context.read<MonitoringIndexCubit>();
}
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: const Color(0xFF010B1D),
body: BlocBuilder<MonitoringIndexCubit, MonitoringIndexState>(
builder: (context, state) {
if (state.isLoading && state.metrics.isEmpty) {
return const Center(
child: CircularProgressIndicator(color: Colors.white),
);
}
if (state.status == MonitoringIndexStatus.failure &&
state.metrics.isEmpty) {
return _buildError(state.error);
}
return _buildDashboard(state);
},
),
);
}
Widget _buildError(String? error) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'加载失败: ${error ?? '未知错误'}',
style: TextStyle(color: Colors.white, fontSize: 22.sp),
),
SizedBox(height: 22.h),
ElevatedButton(
onPressed: () => _monitoringIndexCubit.loadData(),
child: const Text('重试'),
),
],
),
);
}
Widget _buildDashboard(MonitoringIndexState state) {
return LayoutBuilder(
builder: (context, constraints) {
final topHeight = constraints.maxHeight * 0.11;
final bottomHeight = constraints.maxHeight * 0.15;
final gap = 10.w;
return Column(
children: [
Container(
height: topHeight.clamp(80.0, 104.0),
decoration: BoxDecoration(
border: BoxBorder.all(width: 1.w,color: Colors.red)
),
child: const MonitoringTopStatusBar(),
),
Expanded(
child: Container(
padding: EdgeInsets.zero,
decoration: BoxDecoration(
border: BoxBorder.all(width: 1.w,color: Colors.red)
),
child: _buildMainArea(state, gap),
),
),
Container(
height: bottomHeight.clamp(92.0, 134.0),
decoration: BoxDecoration(
border: BoxBorder.all(width: 1.w,color: Colors.red)
),
child: MonitoringBottomMenu(menuItems: state.menuItems),
),
],
);
},
);
}
Widget _buildMainArea(MonitoringIndexState state, double gap) {
return Row(
children: [
Expanded(
flex: 20,
child: MonitoringPetInfoCard(patientInfo: state.patientInfo),
),
// SizedBox(width: gap),
Expanded(
flex: 40,
child: Column(
children: [
Expanded(
flex: 32,
child: Row(
children: [
Expanded(
flex: 4,
child: _buildMetrics(state.metrics, gap),
),
// SizedBox(width: gap),
],
),
),
SizedBox(height: gap),
Expanded(
flex: 34,
child: Row(
children: [
Expanded(
flex: 10,
child: MonitoringAlertCard(alerts: state.alerts),
),
SizedBox(width: gap),
Expanded(
flex: 10,
child: const MonitoringVideoCard(),
),
],
),
),
],
),
),
],
);
}
Widget _buildMetrics(List<MonitoringMetricBO> metrics, double gap) {
final displayMetrics = _environmentMetrics(metrics);
return Row(
children: displayMetrics
.map(
(metric) => Expanded(
child: Container(
padding: EdgeInsets.zero,
decoration: BoxDecoration(
border: BoxBorder.all(width: 1.w,color: Colors.red)
),
// padding: EdgeInsets.only(
// right: metric == displayMetrics.last ? 0 : gap,
// ),
child: MonitoringMetricCard(metric: metric),
),
),
)
.toList(),
);
}
List<MonitoringMetricBO> _environmentMetrics(
List<MonitoringMetricBO> metrics) {
final matched = metrics
.where((item) =>
item.label.contains('温度') ||
item.label.contains('湿度') ||
item.label.contains('氧气') ||
item.label.contains('二氧化碳') ||
item.label.toUpperCase().contains('CO'))
.take(4)
.toList();
if (matched.length == 4) return matched;
return const [
MonitoringMetricBO(value: '--', unit: '℃', label: '舱内温度'),
MonitoringMetricBO(value: '--', unit: 'RH', label: '舱内湿度'),
MonitoringMetricBO(value: '--', unit: '%', label: '氧气浓度'),
MonitoringMetricBO(value: '--', unit: '%', label: '二氧化碳'),
];
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
import 'monitoring_panel.dart';
class MonitoringAlertCard extends StatelessWidget {
final List<AlertInfoBO> alerts;
const MonitoringAlertCard({
super.key,
required this.alerts,
});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('lib/assets/monitoring/alert_box_bg_2.png'),
fit: BoxFit.cover,
),
),
// backgroundAsset: alerts.any((item) => item.status.contains('待处理'))
// ? 'lib/assets/monitoring/alert_box_bg_2.png'
// : 'lib/assets/monitoring/alert_box_bg_1.png',
// padding: EdgeInsets.symmetric(horizontal: 24.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: alerts.isEmpty ? _buildEmpty() : _buildList(),
),
],
),
);
}
Widget _buildEmpty() {
return Center(
child: Text(
'暂无告警信息',
style: TextStyle(color: Colors.white70, fontSize: 18.sp),
),
);
}
Widget _buildList() {
return ListView.separated(
padding: EdgeInsets.zero,
itemCount: alerts.length,
separatorBuilder: (_, __) => SizedBox(height: 12.h),
itemBuilder: (context, index) {
final item = alerts[index];
final pending = item.status.contains('待处理');
return Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 14.h),
decoration: BoxDecoration(
// color: pending ? const Color(0x24FFB800) : const Color(0x1820E5FF),
// borderRadius: BorderRadius.circular(14.r),
// border: Border.all(
// color:
// pending ? const Color(0x99FFB800) : const Color(0x6639D8FF),
// width: 1,
// ),
),
child: Text("s"),
);
},
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
class MonitoringBottomMenu extends StatelessWidget {
final List<MonitoringMenuItemBO> menuItems;
const MonitoringBottomMenu({
super.key,
required this.menuItems,
});
@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('lib/assets/monitoring/bottom_menu_bg.png'),
fit: BoxFit.fill,
),
),
padding: EdgeInsets.symmetric(horizontal: 36.w, vertical: 16.h),
child: Row(
children: [
// todo 这里需要绝对定位几个热区,用于点击
],
),
);
}
Widget _buildBrandTitle() {
return Container(
height: 58.h,
constraints: BoxConstraints(minWidth: 190.w),
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('lib/assets/monitoring/title_bg_spacewalk.png'),
fit: BoxFit.fill,
),
),
alignment: Alignment.center,
child: Text(
'SPACE WALK',
style: TextStyle(
color: Colors.white,
fontSize: 22.sp,
fontWeight: FontWeight.w800,
letterSpacing: 2.5,
),
),
);
}
Widget _buildMenuButton(String icon, String title) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h),
decoration: BoxDecoration(
color: const Color(0x1A42D8FF),
borderRadius: BorderRadius.circular(18.r),
border: Border.all(color: const Color(0x665EE6FF), width: 1),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(icon, style: TextStyle(fontSize: 20.sp)),
SizedBox(width: 8.w),
Text(
title,
style: TextStyle(color: Colors.white, fontSize: 16.sp),
),
],
),
);
}
Widget _buildActionButton(IconData icon, String title) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 11.h),
decoration: BoxDecoration(
color: const Color(0x1420E5FF),
borderRadius: BorderRadius.circular(16.r),
border: Border.all(color: const Color(0x5542D8FF), width: 1),
),
child: Row(
children: [
Icon(icon, color: const Color(0xFF9DF5FF), size: 20.w),
SizedBox(width: 7.w),
Text(
title,
style: TextStyle(color: Colors.white70, fontSize: 15.sp),
),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
import 'monitoring_panel.dart';
class MonitoringMetricCard extends StatelessWidget {
final MonitoringMetricBO metric;
const MonitoringMetricCard({
super.key,
required this.metric,
});
@override
Widget build(BuildContext context) {
final displayLabel = _displayLabel(metric.label);
return Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('lib/assets/monitoring/box_bg_325x372.png'),
fit: BoxFit.cover,
),
),
// padding: EdgeInsets.symmetric(horizontal: 18.w, vertical: 18.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 60.h,),
Row(
children: [
SizedBox(width: 20.w),
Expanded(
flex: 2,
child: Text(
displayLabel,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 18.sp,
fontWeight: FontWeight.bold,
),
),
),
Expanded(
flex: 1,
child: Text(
"开关",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 19.sp,
fontWeight: FontWeight.w600,
),
),
),
],
),
// const Spacer(),
Container(
padding: EdgeInsets.only(left: 20.w,top: 40.h),
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
metric.value,
style: TextStyle(
color: const Color(0xFF9DF5FF),
fontSize: 36.sp,
height: 1,
fontWeight: FontWeight.bold,
),
),
SizedBox(width: 6.w),
Padding(
padding: EdgeInsets.only(bottom: 3.h),
child: Text(
metric.unit,
style: TextStyle(
color: Colors.white70,
fontSize: 18.sp,
fontWeight: FontWeight.w500,
),
),
),
],
),
),
SizedBox(height: 12.h),
Container(
width: 100.w,
margin: EdgeInsets.only(left: 20.w),
decoration: BoxDecoration(
border: Border.all(
color: Colors.red,
width: 1.w
)
),
child: Stack(
clipBehavior: Clip.none,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"设定24度",
style: TextStyle(
color: Colors.white,
fontSize: 14.sp,
),
),
Container(
height: 5.h,
margin: EdgeInsets.only(top: 15.h),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(3.r),
gradient: const LinearGradient(
colors: [Color(0xFF20E5FF), Color(0x0020E5FF)],
),
),
),
],
),
Positioned(
right: -45.w,
top: -40.h,
child: Image.asset(
_iconAsset(metric.label),
width: 60.w,
height: 60.w,
fit: BoxFit.contain,
),
),
],
),
),
Container(
margin: EdgeInsets.only(left: 14),
child: Image.asset(
'lib/assets/monitoring/box_bottom_char.png',
width: 140.w,
// height: 60.w,
fit: BoxFit.contain,
),
)
],
),
);
}
String _displayLabel(String label) {
if (label.contains('二氧化碳')) return 'CO₂浓度';
return label;
}
String _iconAsset(String label) {
if (label.contains('温度')) {
return 'lib/assets/monitoring/cabin_temperature.png';
}
if (label.contains('湿度')) {
return 'lib/assets/monitoring/cabin_humidity.png';
}
if (label.contains('氧气')) {
return 'lib/assets/monitoring/oxygen_concentration.png';
}
return 'lib/assets/monitoring/carbon_dioxide_concentration.png';
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class MonitoringPanel extends StatelessWidget {
final String backgroundAsset;
final Widget child;
final EdgeInsetsGeometry? padding;
final BorderRadiusGeometry? borderRadius;
final BoxFit fit;
const MonitoringPanel({
super.key,
required this.backgroundAsset,
required this.child,
this.padding,
this.borderRadius,
this.fit = BoxFit.fill,
});
@override
Widget build(BuildContext context) {
return ClipRRect(
// borderRadius: borderRadius ?? BorderRadius.circular(18.r),
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(backgroundAsset),
fit: fit,
),
),
child: Padding(
padding: padding ?? EdgeInsets.all(18.w),
child: child,
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
import 'monitoring_panel.dart';
class MonitoringPetInfoCard extends StatelessWidget {
final PatientInfoBO? patientInfo;
const MonitoringPetInfoCard({
super.key,
required this.patientInfo,
});
@override
Widget build(BuildContext context) {
return MonitoringPanel(
backgroundAsset: 'lib/assets/monitoring/box_bg_460x905.png',
padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 28.h),
child: patientInfo == null ? _buildEmpty() : _buildContent(patientInfo!),
);
}
Widget _buildEmpty() {
return Center(
child: Text(
'暂无宠物信息',
style: TextStyle(color: Colors.white70, fontSize: 20.sp),
),
);
}
Widget _buildContent(PatientInfoBO info) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildTitle(),
SizedBox(height: 28.h),
Center(child: _buildAvatar()),
SizedBox(height: 20.h),
Center(
child: Text(
info.name,
style: TextStyle(
color: Colors.white,
fontSize: 28.sp,
fontWeight: FontWeight.w700,
),
),
),
SizedBox(height: 34.h),
_buildInfoRow(Icons.person_outline, info.owner),
_buildInfoRow(Icons.phone_outlined, info.phone),
_buildInfoRow(Icons.pets_outlined, info.breed),
_buildInfoRow(Icons.medical_services_outlined, info.doctor),
const Spacer(),
_buildCareBlock(),
],
);
}
Widget _buildTitle() {
return Row(
children: [
Container(
width: 4.w,
height: 22.h,
decoration: BoxDecoration(
color: const Color(0xFF5EE6FF),
borderRadius: BorderRadius.circular(2.r),
),
),
SizedBox(width: 10.w),
Text(
'宠物基础信息',
style: TextStyle(
color: Colors.white,
fontSize: 22.sp,
fontWeight: FontWeight.w600,
),
),
],
);
}
Widget _buildAvatar() {
return Container(
width: 112.w,
height: 112.w,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: const Color(0x261DA7FF),
border: Border.all(color: const Color(0xFF69E8FF), width: 2),
boxShadow: const [
BoxShadow(
color: Color(0x6625BFFF),
blurRadius: 22,
spreadRadius: 2,
),
],
),
child: Icon(
Icons.pets,
color: const Color(0xFFAAFAFF),
size: 54.w,
),
);
}
Widget _buildInfoRow(IconData icon, String text) {
return Padding(
padding: EdgeInsets.only(bottom: 18.h),
child: Row(
children: [
Icon(icon, color: const Color(0xFF77EFFF), size: 22.w),
SizedBox(width: 12.w),
Expanded(
child: Text(
text,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(color: Colors.white70, fontSize: 18.sp),
),
),
],
),
);
}
Widget _buildCareBlock() {
return Container(
width: double.infinity,
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h),
decoration: BoxDecoration(
color: const Color(0x1A61D9FF),
borderRadius: BorderRadius.circular(14.r),
border: Border.all(color: const Color(0x665EE6FF), width: 1),
),
child: Row(
children: [
Icon(Icons.health_and_safety_outlined,
color: const Color(0xFFFFD66B), size: 26.w),
SizedBox(width: 12.w),
Text(
'护理等级:特级',
style: TextStyle(
color: Colors.white,
fontSize: 20.sp,
fontWeight: FontWeight.w600,
),
),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class MonitoringTopStatusBar extends StatelessWidget {
const MonitoringTopStatusBar({super.key});
@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('lib/assets/monitoring/top_status_bg.png'),
fit: BoxFit.fill,
),
),
padding: EdgeInsets.only(left: 20.w,bottom: 22.h),
child: Row(
children: [
// SizedBox(width: 18.w),
Text(
'总运行时长:9999h',
style: TextStyle(
color: Colors.white,
fontSize: 16.sp,
),
),
SizedBox(width: 8.w),
Text(
'环境温度:30.68',
style: TextStyle(
color: Colors.white,
fontSize: 16.sp,
),
),
SizedBox(width: 8.w),
Text(
'总制氧时长:500h',
style: TextStyle(
color: Colors.white,
fontSize: 16.sp,
),
),
// const Spacer(),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'monitoring_panel.dart';
class MonitoringVideoCard extends StatelessWidget {
const MonitoringVideoCard({super.key});
@override
Widget build(BuildContext context) {
return MonitoringPanel(
backgroundAsset: 'lib/assets/monitoring/box_bg_674x509.png',
padding: EdgeInsets.all(18.w),
child: Stack(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHeader(),
SizedBox(height: 16.h),
Expanded(child: _buildPreview()),
],
),
Positioned(
right: 0,
bottom: 0,
child: _buildSwitch(),
),
],
),
);
}
Widget _buildHeader() {
return Row(
children: [
Container(
width: 8.w,
height: 8.w,
decoration: const BoxDecoration(
color: Color(0xFF41FFB0),
shape: BoxShape.circle,
),
),
SizedBox(width: 8.w),
Text(
'宠物预览图 / 视频卡片',
style: TextStyle(
color: Colors.white,
fontSize: 20.sp,
fontWeight: FontWeight.w600,
),
),
const Spacer(),
Text(
'LIVE',
style: TextStyle(
color: const Color(0xFF41FFB0),
fontSize: 15.sp,
fontWeight: FontWeight.w700,
letterSpacing: 1.2,
),
),
],
);
}
Widget _buildPreview() {
return Container(
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16.r),
gradient: const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0x662665FF), Color(0x3300164A)],
),
border: Border.all(color: const Color(0x5539D8FF), width: 1),
),
child: Stack(
children: [
Center(
child: Icon(
Icons.videocam_outlined,
color: const Color(0x889DF5FF),
size: 70.w,
),
),
Positioned(
left: 18.w,
bottom: 16.h,
child: Text(
'监护舱实时画面',
style: TextStyle(color: Colors.white70, fontSize: 17.sp),
),
),
],
),
);
}
Widget _buildSwitch() {
return Row(
children: [
Text(
'监控开关',
style: TextStyle(color: Colors.white70, fontSize: 15.sp),
),
SizedBox(width: 8.w),
Image.asset(
'lib/assets/monitoring/switch_open.png',
width: 56.w,
fit: BoxFit.contain,
),
],
);
}
}
...@@ -98,6 +98,7 @@ flutter: ...@@ -98,6 +98,7 @@ flutter:
- lib/assets/login/ - lib/assets/login/
- lib/assets/tab/ - lib/assets/tab/
- lib/assets/icon/ - lib/assets/icon/
- lib/assets/monitoring/
# - images/a_dot_ham.jpeg # - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see # An image asset can refer to one or more resolution-specific "variants", see
......
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