Commit cbae61b1 authored by akari's avatar akari

merge master

parents d697b832 983bc0a8
......@@ -62,6 +62,7 @@ class _MyAppState extends State<MyApp> {
late final AppRouter _appRouter;
late final AuthBloc _authBloc;
StreamSubscription? _tokenExpiredSubscription;
bool _isInitializing = true;
@override
void initState() {
......@@ -102,9 +103,45 @@ class _MyAppState extends State<MyApp> {
// Token 有效,尝试恢复用户信息
_authBloc.add(const AuthFetchUserInfo());
}
_endInitializing();
} else {
// 无 Token,执行静默登录
await _performSilentLogin();
}
}
/// 静默登录:使用预设账号自动登录
Future<void> _performSilentLogin() async {
try {
await _authService.login('13800000001', '111111');
// 登录成功,获取用户信息
_authBloc.add(const AuthFetchUserInfo());
} catch (e) {
// 静默登录失败,不做任何处理,初始化结束
} finally {
_endInitializing();
}
}
void _endInitializing() {
if (mounted) {
setState(() {
_isInitializing = false;
});
}
}
Widget _buildSplash() {
return Positioned.fill(
child: ColoredBox(
color: const Color(0xFFF5F7FA),
child: Center(
child: CircularProgressIndicator(),
),
),
);
}
@override
void dispose() {
_tokenExpiredSubscription?.cancel();
......@@ -115,7 +152,7 @@ class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return ScreenUtilInit(
designSize: Size(1024, 1366),
designSize: Size(1920, 1199),
builder: (context, child) {
return MultiBlocProvider(
providers: [
......@@ -143,10 +180,15 @@ class _MyAppState extends State<MyApp> {
// BlocListener<UserBloc, UserState>(listener: ...),
// BlocListener<SettingsBloc, SettingsState>(listener: ...),
],
child: MaterialApp.router(
scaffoldMessengerKey: _scaffoldMessengerKey,
routerConfig: _appRouter.config(),
debugShowCheckedModeBanner: false,
child: Stack(
children: [
MaterialApp.router(
scaffoldMessengerKey: _scaffoldMessengerKey,
routerConfig: _appRouter.config(),
debugShowCheckedModeBanner: false,
),
if (_isInitializing) _buildSplash(),
],
),
),
);
......
......@@ -52,8 +52,8 @@ class PatientInfoBO extends Equatable {
/// 患者名称
final String name;
/// 家长信息
final String owner;
/// 宠物类型(猫/狗等)
final String type;
/// 联系电话
final String phone;
......@@ -67,28 +67,44 @@ class PatientInfoBO extends Equatable {
/// 患者头像地址
final String avatarUrl;
/// 宠物病因
final String disease;
/// 入住时间
final String checkInDate;
/// 护理等级
final String careLevel;
const PatientInfoBO({
required this.name,
required this.owner,
required this.type,
required this.phone,
required this.breed,
required this.doctor,
required this.disease,
required this.checkInDate,
required this.careLevel,
this.avatarUrl = '',
});
factory PatientInfoBO.fromJson(Map<String, dynamic> json) {
return PatientInfoBO(
name: json['name'] as String? ?? '',
owner: json['owner'] as String? ?? '',
type: json['type'] as String? ?? '',
phone: json['phone'] as String? ?? '',
breed: json['breed'] as String? ?? '',
doctor: json['doctor'] as String? ?? '',
disease: json['disease'] as String? ?? '',
checkInDate: json['checkInDate'] as String? ?? '',
careLevel: json['careLevel'] as String? ?? '',
avatarUrl: json['avatarUrl'] as String? ?? '',
);
}
@override
List<Object?> get props => [name, owner, phone, breed, doctor, avatarUrl];
List<Object?> get props =>
[name, type, phone, breed, doctor, disease, checkInDate, careLevel, avatarUrl];
}
/// 用途:首页监护舱告警信息展示数据
......@@ -129,24 +145,39 @@ class AlertInfoBO extends Equatable {
/// 用途:首页监护舱右侧设备菜单数据
/// 涉及页面:首页监护舱页面
class MonitoringMenuItemBO extends Equatable {
/// 菜单ID
final String id;
/// 菜单标题
final String title;
/// 菜单图标文案
/// 菜单图标(emoji或文字,用于旧版本兼容)
final String icon;
/// 菜单图标路径(新版本)
final String iconPath;
/// 是否为TSAAS特殊样式
final bool isTsaas;
const MonitoringMenuItemBO({
required this.id,
required this.title,
required this.icon,
this.icon = '',
this.iconPath = '',
this.isTsaas = false,
});
factory MonitoringMenuItemBO.fromJson(Map<String, dynamic> json) {
return MonitoringMenuItemBO(
id: json['id'] as String? ?? '',
title: json['title'] as String? ?? '',
icon: json['icon'] as String? ?? '',
iconPath: json['iconPath'] as String? ?? '',
isTsaas: json['isTsaas'] as bool? ?? false,
);
}
@override
List<Object?> get props => [title, icon];
List<Object?> get props => [id, title, icon, iconPath, isTsaas];
}
......@@ -50,11 +50,14 @@ class MonitoringMockData {
static PatientInfoBO getMockPatientInfo() {
return const PatientInfoBO(
name: '妮蔻 Niko',
owner: '家长:王女士',
phone: '联系电话:15845310589',
breed: '品种:长毛三花',
doctor: '医生:程医生',
name: '妮蔻',
type: '猫',
phone: '130-1111-3333',
breed: '长毛三花',
doctor: '张医生',
disease: '胃炎',
checkInDate: '2025-6-15',
careLevel: '特级',
avatarUrl: '',
);
}
......@@ -67,22 +70,59 @@ class MonitoringMockData {
time: '2026-05-04 16:22:49',
status: '待处理',
),
AlertInfoBO(
title: '温度异常',
description: '温度高于阈值26℃,当前温度29℃,请立即检查温度传感器,字…',
time: '2026-05-04 16:22:49',
status: '已处理',
),
];
}
static List<MonitoringMenuItemBO> getMockMenuItems() {
return const [
MonitoringMenuItemBO(
title: '动物血压监测',
id: 'icu_central',
title: 'ICU中控',
icon: '🏥',
iconPath: 'lib/assets/menu/menu1.png',
),
MonitoringMenuItemBO(
id: 'blood_pressure',
title: '血压监测',
icon: '❤️',
iconPath: 'lib/assets/menu/menu2.png',
),
MonitoringMenuItemBO(
title: '多功能动物体征检测仪',
id: 'vital_signs',
title: '体征仪',
icon: '📏',
iconPath: 'lib/assets/menu/menu3.png',
),
MonitoringMenuItemBO(
title: '穿戴式多导心电监测系统',
id: 'ecg_system',
title: '多导心电系统',
icon: '💓',
iconPath: 'lib/assets/menu/menu4.png',
),
MonitoringMenuItemBO(
id: 'tsaas',
title: 'TSAAS',
icon: '🔵',
iconPath: 'lib/assets/menu/menu5.png',
isTsaas: true,
),
MonitoringMenuItemBO(
id: 'record',
title: '记录',
icon: '📝',
iconPath: 'lib/assets/menu/menu6.png',
),
MonitoringMenuItemBO(
id: 'settings',
title: '设置',
icon: '⚙️',
iconPath: 'lib/assets/menu/menu7.png',
),
];
}
......
// ignore_for_file: unused_import
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/login/login_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 'package:laki_icu_app/views/blood_pressure/blood_pressure_index_view.dart';
import 'package:laki_icu_app/views/vital_signs/vital_signs_index_view.dart';
import 'package:laki_icu_app/views/ecg_system/ecg_system_index_view.dart';
import 'package:laki_icu_app/views/tsaas/tsaas_index_view.dart';
import 'package:laki_icu_app/views/record/record_index_view.dart';
import 'package:laki_icu_app/views/settings/settings_index_view.dart';
import 'app_router.gr.dart';
@AutoRouterConfig(replaceInRouteName: 'View,Route')
......@@ -16,15 +21,26 @@ class AppRouter extends $AppRouter {
@override
List<AutoRoute> get routes => [
// LoginRoute / LoginView(登录页)
AutoRoute(page: LoginRoute.page, initial: true),
// DefaultLayoutRoute / DefaultLayoutView(默认布局页/主框架
AutoRoute(page: LoginRoute.page),
// DefaultLayoutRoute / DefaultLayoutView(主框架 Shell
AutoRoute(
page: DefaultLayoutRoute.page,
initial: true,
children: [
// MonitoringIndexRoute / MonitoringIndexView(监控页
// MonitoringIndexRoute / MonitoringIndexView(ICU中控 - Tab 0
AutoRoute(page: MonitoringIndexRoute.page, initial: true),
// HomeRoute / HomeView(首页,物理文件保留)
AutoRoute(page: HomeIndexRoute.page),
// BloodPressureIndexRoute / BloodPressureIndexView(血压监测 - Tab 1)
AutoRoute(page: BloodPressureIndexRoute.page),
// VitalSignsIndexRoute / VitalSignsIndexView(体征仪 - Tab 2)
AutoRoute(page: VitalSignsIndexRoute.page),
// EcgSystemIndexRoute / EcgSystemIndexView(多导心电系统 - Tab 3)
AutoRoute(page: EcgSystemIndexRoute.page),
// TsaasIndexRoute / TsaasIndexView(TSAAS - Tab 4)
AutoRoute(page: TsaasIndexRoute.page),
// RecordIndexRoute / RecordIndexView(记录 - Tab 5)
AutoRoute(page: RecordIndexRoute.page),
// SettingsIndexRoute / SettingsIndexView(设置 - Tab 6)
AutoRoute(page: SettingsIndexRoute.page),
],
),
];
......
......@@ -8,52 +8,104 @@
// coverage:ignore-file
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:auto_route/auto_route.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/layout/default_view.dart' as _i1;
import 'package:laki_icu_app/views/login/login_view.dart' as _i3;
import 'package:auto_route/auto_route.dart' as _i10;
import 'package:flutter/material.dart' as _i11;
import 'package:laki_icu_app/views/blood_pressure/blood_pressure_index_view.dart'
as _i1;
import 'package:laki_icu_app/views/ecg_system/ecg_system_index_view.dart'
as _i3;
import 'package:laki_icu_app/views/layout/default_view.dart' as _i2;
import 'package:laki_icu_app/views/login/login_view.dart' as _i4;
import 'package:laki_icu_app/views/monitoring/index/monitoring_index_view.dart'
as _i4;
abstract class $AppRouter extends _i5.RootStackRouter {
as _i5;
import 'package:laki_icu_app/views/record/record_index_view.dart' as _i6;
import 'package:laki_icu_app/views/settings/settings_index_view.dart' as _i7;
import 'package:laki_icu_app/views/tsaas/tsaas_index_view.dart' as _i8;
import 'package:laki_icu_app/views/vital_signs/vital_signs_index_view.dart'
as _i9;
abstract class $AppRouter extends _i10.RootStackRouter {
$AppRouter({super.navigatorKey});
@override
final Map<String, _i5.PageFactory> pagesMap = {
final Map<String, _i10.PageFactory> pagesMap = {
BloodPressureIndexRoute.name: (routeData) {
return _i10.AutoRoutePage<dynamic>(
routeData: routeData,
child: const _i1.BloodPressureIndexView(),
);
},
DefaultLayoutRoute.name: (routeData) {
return _i5.AutoRoutePage<dynamic>(
return _i10.AutoRoutePage<dynamic>(
routeData: routeData,
child: const _i1.DefaultLayoutView(),
child: const _i2.DefaultLayoutView(),
);
},
HomeIndexRoute.name: (routeData) {
return _i5.AutoRoutePage<dynamic>(
EcgSystemIndexRoute.name: (routeData) {
return _i10.AutoRoutePage<dynamic>(
routeData: routeData,
child: const _i2.HomeIndexView(),
child: const _i3.EcgSystemIndexView(),
);
},
LoginRoute.name: (routeData) {
final args = routeData.argsAs<LoginRouteArgs>(
orElse: () => const LoginRouteArgs());
return _i5.AutoRoutePage<dynamic>(
return _i10.AutoRoutePage<dynamic>(
routeData: routeData,
child: _i3.LoginView(key: args.key),
child: _i4.LoginView(key: args.key),
);
},
MonitoringIndexRoute.name: (routeData) {
return _i5.AutoRoutePage<dynamic>(
return _i10.AutoRoutePage<dynamic>(
routeData: routeData,
child: const _i5.MonitoringIndexView(),
);
},
RecordIndexRoute.name: (routeData) {
return _i10.AutoRoutePage<dynamic>(
routeData: routeData,
child: const _i6.RecordIndexView(),
);
},
SettingsIndexRoute.name: (routeData) {
return _i10.AutoRoutePage<dynamic>(
routeData: routeData,
child: const _i7.SettingsIndexView(),
);
},
TsaasIndexRoute.name: (routeData) {
return _i10.AutoRoutePage<dynamic>(
routeData: routeData,
child: const _i8.TsaasIndexView(),
);
},
VitalSignsIndexRoute.name: (routeData) {
return _i10.AutoRoutePage<dynamic>(
routeData: routeData,
child: const _i4.MonitoringIndexView(),
child: const _i9.VitalSignsIndexView(),
);
},
};
}
/// generated route for
/// [_i1.DefaultLayoutView]
class DefaultLayoutRoute extends _i5.PageRouteInfo<void> {
const DefaultLayoutRoute({List<_i5.PageRouteInfo>? children})
/// [_i1.BloodPressureIndexView]
class BloodPressureIndexRoute extends _i10.PageRouteInfo<void> {
const BloodPressureIndexRoute({List<_i10.PageRouteInfo>? children})
: super(
BloodPressureIndexRoute.name,
initialChildren: children,
);
static const String name = 'BloodPressureIndexRoute';
static const _i10.PageInfo<void> page = _i10.PageInfo<void>(name);
}
/// generated route for
/// [_i2.DefaultLayoutView]
class DefaultLayoutRoute extends _i10.PageRouteInfo<void> {
const DefaultLayoutRoute({List<_i10.PageRouteInfo>? children})
: super(
DefaultLayoutRoute.name,
initialChildren: children,
......@@ -61,29 +113,29 @@ class DefaultLayoutRoute extends _i5.PageRouteInfo<void> {
static const String name = 'DefaultLayoutRoute';
static const _i5.PageInfo<void> page = _i5.PageInfo<void>(name);
static const _i10.PageInfo<void> page = _i10.PageInfo<void>(name);
}
/// generated route for
/// [_i2.HomeIndexView]
class HomeIndexRoute extends _i5.PageRouteInfo<void> {
const HomeIndexRoute({List<_i5.PageRouteInfo>? children})
/// [_i3.EcgSystemIndexView]
class EcgSystemIndexRoute extends _i10.PageRouteInfo<void> {
const EcgSystemIndexRoute({List<_i10.PageRouteInfo>? children})
: super(
HomeIndexRoute.name,
EcgSystemIndexRoute.name,
initialChildren: children,
);
static const String name = 'HomeIndexRoute';
static const String name = 'EcgSystemIndexRoute';
static const _i5.PageInfo<void> page = _i5.PageInfo<void>(name);
static const _i10.PageInfo<void> page = _i10.PageInfo<void>(name);
}
/// generated route for
/// [_i3.LoginView]
class LoginRoute extends _i5.PageRouteInfo<LoginRouteArgs> {
/// [_i4.LoginView]
class LoginRoute extends _i10.PageRouteInfo<LoginRouteArgs> {
LoginRoute({
_i6.Key? key,
List<_i5.PageRouteInfo>? children,
_i11.Key? key,
List<_i10.PageRouteInfo>? children,
}) : super(
LoginRoute.name,
args: LoginRouteArgs(key: key),
......@@ -92,14 +144,14 @@ class LoginRoute extends _i5.PageRouteInfo<LoginRouteArgs> {
static const String name = 'LoginRoute';
static const _i5.PageInfo<LoginRouteArgs> page =
_i5.PageInfo<LoginRouteArgs>(name);
static const _i10.PageInfo<LoginRouteArgs> page =
_i10.PageInfo<LoginRouteArgs>(name);
}
class LoginRouteArgs {
const LoginRouteArgs({this.key});
final _i6.Key? key;
final _i11.Key? key;
@override
String toString() {
......@@ -108,9 +160,9 @@ class LoginRouteArgs {
}
/// generated route for
/// [_i4.MonitoringIndexView]
class MonitoringIndexRoute extends _i5.PageRouteInfo<void> {
const MonitoringIndexRoute({List<_i5.PageRouteInfo>? children})
/// [_i5.MonitoringIndexView]
class MonitoringIndexRoute extends _i10.PageRouteInfo<void> {
const MonitoringIndexRoute({List<_i10.PageRouteInfo>? children})
: super(
MonitoringIndexRoute.name,
initialChildren: children,
......@@ -118,5 +170,61 @@ class MonitoringIndexRoute extends _i5.PageRouteInfo<void> {
static const String name = 'MonitoringIndexRoute';
static const _i5.PageInfo<void> page = _i5.PageInfo<void>(name);
static const _i10.PageInfo<void> page = _i10.PageInfo<void>(name);
}
/// generated route for
/// [_i6.RecordIndexView]
class RecordIndexRoute extends _i10.PageRouteInfo<void> {
const RecordIndexRoute({List<_i10.PageRouteInfo>? children})
: super(
RecordIndexRoute.name,
initialChildren: children,
);
static const String name = 'RecordIndexRoute';
static const _i10.PageInfo<void> page = _i10.PageInfo<void>(name);
}
/// generated route for
/// [_i7.SettingsIndexView]
class SettingsIndexRoute extends _i10.PageRouteInfo<void> {
const SettingsIndexRoute({List<_i10.PageRouteInfo>? children})
: super(
SettingsIndexRoute.name,
initialChildren: children,
);
static const String name = 'SettingsIndexRoute';
static const _i10.PageInfo<void> page = _i10.PageInfo<void>(name);
}
/// generated route for
/// [_i8.TsaasIndexView]
class TsaasIndexRoute extends _i10.PageRouteInfo<void> {
const TsaasIndexRoute({List<_i10.PageRouteInfo>? children})
: super(
TsaasIndexRoute.name,
initialChildren: children,
);
static const String name = 'TsaasIndexRoute';
static const _i10.PageInfo<void> page = _i10.PageInfo<void>(name);
}
/// generated route for
/// [_i9.VitalSignsIndexView]
class VitalSignsIndexRoute extends _i10.PageRouteInfo<void> {
const VitalSignsIndexRoute({List<_i10.PageRouteInfo>? children})
: super(
VitalSignsIndexRoute.name,
initialChildren: children,
);
static const String name = 'VitalSignsIndexRoute';
static const _i10.PageInfo<void> page = _i10.PageInfo<void>(name);
}
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
@RoutePage()
class BloodPressureIndexView extends StatelessWidget {
const BloodPressureIndexView({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: Text(
'血压监测',
style: TextStyle(color: Colors.white, fontSize: 24),
),
);
}
}
import 'package:flutter_bloc/flutter_bloc.dart';
import 'blood_pressure_index_state.dart';
class BloodPressureIndexCubit extends Cubit<BloodPressureIndexState> {
BloodPressureIndexCubit() : super(const BloodPressureIndexState());
}
import 'package:equatable/equatable.dart';
class BloodPressureIndexState extends Equatable {
const BloodPressureIndexState();
@override
List<Object?> get props => [];
}
import 'package:flutter_bloc/flutter_bloc.dart';
import 'ecg_system_index_state.dart';
class EcgSystemIndexCubit extends Cubit<EcgSystemIndexState> {
EcgSystemIndexCubit() : super(const EcgSystemIndexState());
}
import 'package:equatable/equatable.dart';
class EcgSystemIndexState extends Equatable {
const EcgSystemIndexState();
@override
List<Object?> get props => [];
}
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
@RoutePage()
class EcgSystemIndexView extends StatelessWidget {
const EcgSystemIndexView({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: Text(
'多导心电系统',
style: TextStyle(color: Colors.white, fontSize: 24),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
class AlertInfoCard extends StatelessWidget {
final List<AlertInfoBO> alerts;
const AlertInfoCard({
super.key,
required this.alerts,
});
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF1E10B6), Color(0xFF3A87FF)],
),
borderRadius: BorderRadius.circular(20.r),
border: Border.all(color: Colors.white24, width: 1),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'告警信息',
style: TextStyle(
fontSize: 24.sp,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
if (alerts.isNotEmpty) _buildStatus(alerts.first.status),
],
),
SizedBox(height: 12.h),
if (alerts.isNotEmpty)
...alerts.map((alert) => _buildAlertItem(alert))
else
Text(
'暂无告警信息',
style: TextStyle(color: Colors.white70, fontSize: 20.sp),
),
SizedBox(height: 4.h),
Align(
alignment: Alignment.centerRight,
child: TextButton(
onPressed: () {},
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
minimumSize: Size(40.w, 30.h),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
child: Text(
'详情 >',
style: TextStyle(
fontSize: 18.sp,
color: Colors.white70,
),
),
),
),
],
),
);
}
Widget _buildStatus(String status) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 4.h),
decoration: BoxDecoration(
color: const Color(0xFFFFB800),
borderRadius: BorderRadius.circular(20.r),
),
child: Text(
status,
style: TextStyle(
fontSize: 18.sp,
color: Colors.white,
fontWeight: FontWeight.w500,
),
),
);
}
Widget _buildAlertItem(AlertInfoBO alert) {
return Container(
margin: EdgeInsets.only(bottom: 12.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.warning_amber_rounded,
color: const Color(0xFFFF7D00),
size: 20.w,
),
SizedBox(width: 6.w),
Expanded(
child: Text(
alert.title,
style: TextStyle(
fontSize: 22.sp,
fontWeight: FontWeight.w500,
color: const Color(0xFFFF7D00),
),
),
),
],
),
SizedBox(height: 6.h),
Padding(
padding: EdgeInsets.only(left: 26.w),
child: Text(
alert.description,
style: TextStyle(
fontSize: 18.sp,
color: Colors.white70,
),
),
),
SizedBox(height: 4.h),
Padding(
padding: EdgeInsets.only(left: 26.w),
child: Text(
alert.time,
style: TextStyle(
fontSize: 16.sp,
color: Colors.white54,
),
),
),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
class MetricCard extends StatelessWidget {
final MonitoringMetricBO metric;
const MetricCard({
super.key,
required this.metric,
});
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 8.w),
decoration: BoxDecoration(
color: Color(metric.backgroundColorValue),
borderRadius: BorderRadius.circular(20.r),
border: Border.all(
color: Color(metric.borderColorValue),
width: 1,
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
metric.value,
style: TextStyle(
fontSize: 36.sp,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
SizedBox(height: 4.h),
Text(
metric.unit,
style: TextStyle(
fontSize: 20.sp,
color: Colors.white70,
),
),
SizedBox(height: 8.h),
Text(
metric.label,
style: TextStyle(
fontSize: 18.sp,
color: Colors.white70,
),
textAlign: TextAlign.center,
),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
class PatientInfoCard extends StatelessWidget {
final PatientInfoBO patientInfo;
const PatientInfoCard({
super.key,
required this.patientInfo,
});
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF1E10B6), Color(0xFF3A87FF)],
),
borderRadius: BorderRadius.circular(20.r),
border: Border.all(color: Colors.white24, width: 1),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(20.r),
child: Container(
width: 80.w,
height: 80.w,
decoration: BoxDecoration(
border: Border.all(color: Colors.white24, width: 1),
borderRadius: BorderRadius.circular(20.r),
),
child: patientInfo.avatarUrl.isEmpty
? Icon(Icons.pets, size: 48.w, color: Colors.white54)
: Image.network(
patientInfo.avatarUrl,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Icon(
Icons.pets,
size: 48.w,
color: Colors.white54,
);
},
),
),
),
SizedBox(width: 16.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
patientInfo.name,
style: TextStyle(
fontSize: 28.sp,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
SizedBox(height: 8.h),
_buildInfoText(patientInfo.owner),
SizedBox(height: 4.h),
_buildInfoText(patientInfo.phone),
SizedBox(height: 4.h),
_buildInfoText(patientInfo.breed),
SizedBox(height: 4.h),
_buildInfoText(patientInfo.doctor),
],
),
),
],
),
);
}
Widget _buildInfoText(String text) {
return Text(
text,
style: TextStyle(
fontSize: 20.sp,
color: Colors.white70,
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
class SidebarMenuItem extends StatelessWidget {
final MonitoringMenuItemBO item;
final VoidCallback? onTap;
const SidebarMenuItem({
super.key,
required this.item,
this.onTap,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 16.h),
decoration: BoxDecoration(
gradient: const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xCC63A0FF), Color(0xCC0024C4)],
),
borderRadius: BorderRadius.circular(20.r),
border: Border.all(color: Colors.white24, width: 1),
),
child: Row(
children: [
Text(
item.icon,
style: TextStyle(fontSize: 32.w),
),
SizedBox(width: 12.w),
Expanded(
child: Text(
item.title,
style: TextStyle(
fontSize: 20.sp,
color: Colors.white,
fontWeight: FontWeight.w500,
),
),
),
],
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:vsdk/app_player.dart';
import '../../../../enums/video_stream_mode_enum.dart';
/// 通用视频全屏播放页面(兼容 WebRTC + P2P)
///
/// 全屏展示摄像头实时画面,点击任意位置退出全屏。
///
/// 涉及页面:首页监护舱 - 监控画面
class VideoFullscreenPage extends StatelessWidget {
/// 当前视频传输模式
final VideoStreamMode mode;
/// WebRTC 模式下的视频渲染器
final RTCVideoRenderer? webrtcRenderer;
/// P2P 模式下的播放控制器
final AppPlayerController? p2pController;
const VideoFullscreenPage({
super.key,
required this.mode,
this.webrtcRenderer,
this.p2pController,
});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: Stack(
fit: StackFit.expand,
children: [
// 根据模式渲染不同的视频画面
_buildVideoContent(),
// 底部提示栏
_buildBottomBar(),
],
),
),
);
}
/// 按模式渲染视频内容
Widget _buildVideoContent() {
if (mode == VideoStreamMode.p2p && p2pController != null) {
return AppPlayerView(controller: p2pController!);
}
if (mode == VideoStreamMode.webrtc && webrtcRenderer != null) {
return RTCVideoView(
webrtcRenderer!,
objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitContain,
);
}
return const SizedBox.shrink();
}
/// 底部提示栏(左下角)
Widget _buildBottomBar() {
return Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
padding: const EdgeInsets.fromLTRB(20, 12, 20, 48),
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [Colors.black54, Colors.transparent],
),
),
child: const Row(
children: [
Icon(Icons.fullscreen_exit, color: Colors.white70, size: 20),
SizedBox(width: 8),
Text(
'点击任意位置退出全屏',
style: TextStyle(color: Colors.white70, fontSize: 14),
),
],
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:vsdk/app_player.dart';
import '../../../../enums/video_stream_mode_enum.dart';
import '../../../../services/webrtc_service.dart';
import 'video_fullscreen_page.dart';
import 'video_stream_switch.dart';
/// 通用实时视频播放组件(兼容 WebRTC + P2P 双模式)
///
/// 职责:
/// - WebRTC 模式:通过 [RTCVideoView] 渲染远程摄像头实时画面
/// - P2P 模式:通过 [AppPlayerView] 渲染 Texture 视频画面
/// - 依据当前模式的连接状态显示对应的状态指示器
/// - 右下角提供模式切换按钮(P2P/RTC)和全屏按钮
///
/// 涉及页面:首页监护舱 - 监控画面
class VideoPlayerWidget extends StatelessWidget {
/// 当前视频传输模式
final VideoStreamMode streamMode;
/// 是否正在切换模式
final bool isSwitchingMode;
/// WebRTC 视频渲染器(WebRTC 模式时使用)
final RTCVideoRenderer? webrtcRenderer;
/// WebRTC 连接状态
final WebrtcConnectionState webrtcConnectionState;
/// P2P 视频播放控制器(P2P 模式时使用)
final AppPlayerController? p2pController;
/// P2P 模式是否已连接
final bool isP2pConnected;
/// 连接失败时点击重试回调
final VoidCallback? onRetry;
/// 模式切换回调
final VoidCallback? onToggleMode;
const VideoPlayerWidget({
super.key,
required this.streamMode,
this.isSwitchingMode = false,
this.webrtcRenderer,
required this.webrtcConnectionState,
this.p2pController,
this.isP2pConnected = false,
this.onRetry,
this.onToggleMode,
});
/// 当前模式视频是否正在显示
bool get _isVideoShowing {
if (streamMode == VideoStreamMode.webrtc) {
return webrtcConnectionState == WebrtcConnectionState.connected;
}
return isP2pConnected && p2pController != null;
}
@override
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: BorderRadius.circular(20.r),
child: Container(
decoration: BoxDecoration(
gradient: const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xCC63A0FF), Color(0xCC0024C4)],
),
border: Border.all(color: Colors.white24, width: 1),
),
child: Stack(
fit: StackFit.expand,
children: [
// 视频画面层
_buildVideoLayer(),
// 状态指示层
_buildStatusOverlay(),
// 模式切换按钮(右下角倒数第二个位置)
if (onToggleMode != null && !isSwitchingMode)
_buildToggleModeButton(),
// 全屏按钮(右下角最后一个位置)
if (_isVideoShowing) _buildFullscreenButton(context),
],
),
),
);
}
// ==================== 视频画面层 ====================
/// 视频画面
Widget _buildVideoLayer() {
if (streamMode == VideoStreamMode.webrtc) {
if (webrtcConnectionState == WebrtcConnectionState.connected &&
webrtcRenderer != null) {
return RTCVideoView(
webrtcRenderer!,
objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitContain,
);
}
return _buildPlaceholder();
}
// P2P 模式
if (isP2pConnected && p2pController != null) {
return AppPlayerView(controller: p2pController!);
}
return _buildPlaceholder();
}
// ==================== 状态遮罩层 ====================
/// 状态遮罩层
Widget _buildStatusOverlay() {
if (isSwitchingMode) {
return _buildSwitchingOverlay();
}
if (streamMode == VideoStreamMode.webrtc) {
switch (webrtcConnectionState) {
case WebrtcConnectionState.connecting:
return _buildLoadingOverlay('正在连接摄像头...');
case WebrtcConnectionState.reconnecting:
return _buildLoadingOverlay('正在重新连接...');
case WebrtcConnectionState.failed:
return _buildErrorOverlay();
default:
return const SizedBox.shrink();
}
}
// P2P 模式:未连接显示加载
if (!isP2pConnected) {
return _buildLoadingOverlay('正在 P2P 连接...');
}
return const SizedBox.shrink();
}
/// 模式切换中遮罩
Widget _buildSwitchingOverlay() {
return Container(
color: Colors.black26,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const CircularProgressIndicator(color: Colors.white70),
SizedBox(height: 16.h),
Text(
'正在切换传输模式...',
style: TextStyle(fontSize: 18.sp, color: Colors.white70),
),
],
),
),
);
}
/// 加载中遮罩
Widget _buildLoadingOverlay(String message) {
return Container(
color: Colors.black26,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const CircularProgressIndicator(color: Colors.white70),
SizedBox(height: 16.h),
Text(
message,
style: TextStyle(fontSize: 18.sp, color: Colors.white70),
),
],
),
),
);
}
/// 错误遮罩
Widget _buildErrorOverlay() {
return Container(
color: Colors.black38,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.error_outline, size: 48.w, color: Colors.white54),
SizedBox(height: 16.h),
Text(
'视频连接失败',
style: TextStyle(
fontSize: 20.sp,
color: Colors.white70,
fontWeight: FontWeight.w500,
),
),
SizedBox(height: 24.h),
ElevatedButton.icon(
onPressed: onRetry,
icon: const Icon(Icons.refresh),
label: const Text('重新连接'),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF3A87FF),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.r),
),
),
),
],
),
),
);
}
// ==================== 叠加按钮 ====================
/// 全屏按钮(右下角)
Widget _buildFullscreenButton(BuildContext context) {
return Positioned(
right: 12.w,
bottom: 12.h,
child: GestureDetector(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => VideoFullscreenPage(
mode: streamMode,
webrtcRenderer: webrtcRenderer,
p2pController: p2pController,
),
),
);
},
child: Container(
width: 40.w,
height: 40.w,
decoration: BoxDecoration(
color: Colors.black38,
borderRadius: BorderRadius.circular(8.r),
),
child: Icon(
Icons.fullscreen,
color: Colors.white,
size: 22.w,
),
),
),
);
}
/// 模式切换按钮(全屏按钮左侧 8px 间距)
Widget _buildToggleModeButton() {
return Positioned(
right: 12.w + 40.w + 8.w,
bottom: 12.h,
child: VideoStreamSwitch(
currentMode: streamMode,
onToggle: onToggleMode!,
),
);
}
/// 未连接时的占位
Widget _buildPlaceholder() {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.videocam_off, size: 48.w, color: Colors.white38),
SizedBox(height: 16.h),
Text(
'暂无视频画面',
style: TextStyle(
fontSize: 20.sp,
color: Colors.white54,
fontWeight: FontWeight.w500,
),
),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../../../enums/video_stream_mode_enum.dart';
/// P2P/WebRTC 模式切换控件
///
/// 显示当前传输模式(P2P / RTC),点击切换至另一种模式。
/// 位置:视频容器右下角,全屏按钮左侧。
///
/// 涉及页面:首页监护舱 - 监控画面
class VideoStreamSwitch extends StatelessWidget {
/// 当前视频传输模式
final VideoStreamMode currentMode;
/// 点击切换回调
final VoidCallback onToggle;
const VideoStreamSwitch({
super.key,
required this.currentMode,
required this.onToggle,
});
@override
Widget build(BuildContext context) {
final isP2p = currentMode == VideoStreamMode.p2p;
return GestureDetector(
onTap: onToggle,
child: Container(
width: 40.w,
height: 40.w,
decoration: BoxDecoration(
color: isP2p
? const Color(0xFF3A87FF).withValues(alpha: 0.6)
: Colors.black38,
borderRadius: BorderRadius.circular(8.r),
border: Border.all(
color: isP2p ? Colors.white54 : Colors.white24,
width: 1,
),
),
child: Center(
child: Text(
isP2p ? 'P2P' : 'RTC',
style: TextStyle(
fontSize: 10.sp,
color: Colors.white,
fontWeight: FontWeight.w500,
),
),
),
),
);
}
}
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:laki_icu_app/services/monitoring_service.dart';
import 'default_layout_state.dart';
/// 菜单 ID 与 Tab 索引的映射
///
/// Tab 顺序必须与 mock 数据中 menuItems 的定义顺序一致:
/// 0: icu_central, 1: blood_pressure, 2: vital_signs,
/// 3: ecg_system, 4: tsaas, 5: record, 6: settings
const _menuIdToIndex = <String, int>{
'icu_central': 0,
'blood_pressure': 1,
'vital_signs': 2,
'ecg_system': 3,
'tsaas': 4,
'record': 5,
'settings': 6,
};
const _indexToMenuId = <int, String>{
0: 'icu_central',
1: 'blood_pressure',
2: 'vital_signs',
3: 'ecg_system',
4: 'tsaas',
5: 'record',
6: 'settings',
};
/// 将 Tab 索引转换为菜单 ID
String menuIdFromIndex(int index) {
return _indexToMenuId[index] ?? 'icu_central';
}
/// 将菜单 ID 转换为 Tab 索引
int indexFromMenuId(String menuId) {
return _menuIdToIndex[menuId] ?? 0;
}
class DefaultLayoutCubit extends Cubit<DefaultLayoutState> {
final MonitoringService _monitoringService;
DefaultLayoutCubit()
: _monitoringService = MonitoringService(),
super(const DefaultLayoutState()) {
_loadMenuItems();
}
Future<void> _loadMenuItems() async {
emit(state.copyWith(isLoading: true, error: null));
try {
final menuItems = await _monitoringService.getMenuItems();
emit(state.copyWith(isLoading: false, menuItems: menuItems));
} catch (e) {
emit(state.copyWith(isLoading: false, error: e.toString()));
}
}
Future<void> refresh() async {
await _loadMenuItems();
}
}
import 'package:equatable/equatable.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
class DefaultLayoutState extends Equatable {
final List<MonitoringMenuItemBO> menuItems;
final bool isLoading;
final String? error;
const DefaultLayoutState({
this.menuItems = const [],
this.isLoading = false,
this.error,
});
DefaultLayoutState copyWith({
List<MonitoringMenuItemBO>? menuItems,
bool? isLoading,
String? error,
}) {
return DefaultLayoutState(
menuItems: menuItems ?? this.menuItems,
isLoading: isLoading ?? this.isLoading,
error: error ?? this.error,
);
}
@override
List<Object?> get props => [menuItems, isLoading, error];
}
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 '../../routes/app_router.gr.dart';
import 'cubit/default_layout_cubit.dart';
import 'cubit/default_layout_state.dart';
import 'widgets/monitoring_bottom_menu.dart';
import 'widgets/monitoring_top_status_bar.dart';
@RoutePage()
class DefaultLayoutView extends StatelessWidget {
......@@ -7,6 +15,105 @@ class DefaultLayoutView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return const AutoRouter();
return BlocProvider(
create: (_) => DefaultLayoutCubit(),
child: const _DefaultLayoutContent(),
);
}
}
class _DefaultLayoutContent extends StatelessWidget {
const _DefaultLayoutContent();
@override
Widget build(BuildContext context) {
return BlocBuilder<DefaultLayoutCubit, DefaultLayoutState>(
builder: (context, state) {
if (state.isLoading) {
return const Scaffold(
backgroundColor: Color(0xFF010B1D),
body: Center(
child: CircularProgressIndicator(color: Colors.white),
),
);
}
if (state.error != null && state.menuItems.isEmpty) {
return Scaffold(
backgroundColor: const Color(0xFF010B1D),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'菜单加载失败: ${state.error}',
style: const TextStyle(color: Colors.white),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () =>
context.read<DefaultLayoutCubit>().refresh(),
child: const Text('重试'),
),
],
),
),
);
}
return AutoTabsScaffold(
backgroundColor: const Color(0xFF010B1D),
resizeToAvoidBottomInset: false,
homeIndex: 0,
routes: const [
MonitoringIndexRoute(),
BloodPressureIndexRoute(),
VitalSignsIndexRoute(),
EcgSystemIndexRoute(),
TsaasIndexRoute(),
RecordIndexRoute(),
SettingsIndexRoute(),
],
appBarBuilder: (context, tabsRouter) {
return PreferredSize(
preferredSize: Size.fromHeight(104.h),
child: Container(
decoration: const BoxDecoration(
// border: BoxBorder.all(width: 1.w,color: Colors.red)
),
child: Stack(
children: [
Positioned(
right: 10,
top: 20,
child: Image.asset(
'lib/assets/monitoring/top_line.png',
width: 906.w,
height: 162.h,
),
),
Container(
height: 104.h,
child: const MonitoringTopStatusBar(),
),
],
),
),
);
},
bottomNavigationBuilder: (context, tabsRouter) {
final selectedMenuId = menuIdFromIndex(tabsRouter.activeIndex);
return MonitoringBottomMenu(
menuItems: state.menuItems,
selectedMenuId: selectedMenuId,
onMenuItemTap: (menuId) {
final index = indexFromMenuId(menuId);
tabsRouter.setActiveIndex(index);
},
);
},
);
},
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
typedef OnMenuItemTap = Function(String menuId);
class MonitoringBottomMenu extends StatelessWidget {
final List<MonitoringMenuItemBO> menuItems;
final String? selectedMenuId;
final OnMenuItemTap? onMenuItemTap;
const MonitoringBottomMenu({
super.key,
required this.menuItems,
this.selectedMenuId,
this.onMenuItemTap,
});
@override
Widget build(BuildContext context) {
// 将菜单项分成左侧、TSAAS特殊项、右侧
final leftItems = menuItems.where((item) => !item.isTsaas).take(4).toList();
final tsaasItem = menuItems.firstWhere((item) => item.isTsaas);
final rightItems = menuItems.where((item) => !item.isTsaas).skip(4).toList();
return Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('lib/assets/monitoring/bottom_menu_bg.png'),
fit: BoxFit.fill,
),
),
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 12.h),
child: Stack(
children: [
Container(
height: 100.h,
margin: EdgeInsets.only(top: 50.h),
decoration: BoxDecoration(
// border: Border.all(
// width: 1.w,
// color: Colors.red
// )
),
child: Row(
children: [
// 左侧4个菜单项
...leftItems.map((item) => _buildMenuItem(item)).toList(),
// 中间占位区域(TSAAS品牌部分通过Stack叠加)
const Spacer(),
// 右侧3个菜单项(包含TSAAS按钮)
_buildMenuItemTSAAS(tsaasItem),
...rightItems.map((item) => _buildMenuItem(item)).toList(),
],
),
),
// 中间的TSAAS品牌标识
// _buildBrandCenter(),
],
),
);
}
Widget _buildMenuItemTSAAS(MonitoringMenuItemBO item) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 40.w),
child: GestureDetector(
onTap: () {
debugPrint('[MonitoringBottomMenu] 点击菜单: ${item.id} (${item.title})');
onMenuItemTap?.call(item.id);
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 图标
Container(
width: 167.w,
height: 62.h,
child: Image.asset(
item.iconPath,
fit: BoxFit.contain,
),
),
],
),
),
);
}
Widget _buildMenuItem(MonitoringMenuItemBO item) {
final isSelected = selectedMenuId == item.id;
return Padding(
padding: EdgeInsets.symmetric(horizontal: 40.w),
child: GestureDetector(
onTap: () {
debugPrint('[MonitoringBottomMenu] 点击菜单: ${item.id} (${item.title})');
onMenuItemTap?.call(item.id);
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 图标
Container(
width: 62.w,
height: 62.h,
child: Image.asset(
item.iconPath,
fit: BoxFit.contain,
),
),
SizedBox(height: 6.h),
// 文字
Text(
item.title,
style: TextStyle(
color: isSelected
? const Color(0xFF00FFFF)
: const Color(0xB3FFFFFF),
fontSize: 20.sp,
fontWeight: FontWeight.w400,
),
),
],
),
),
);
}
Widget _buildBrandCenter() {
return Center(
child: Container(
height: 72.h,
constraints: BoxConstraints(minWidth: 260.w),
padding: EdgeInsets.symmetric(horizontal: 24.w),
decoration: BoxDecoration(
gradient: const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFF1A4B6F),
Color(0xFF0A1F30),
],
),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(32.w),
topRight: Radius.circular(32.w),
),
border: Border(
left: BorderSide(color: const Color(0xFF206090), width: 1.w),
right: BorderSide(color: const Color(0xFF206090), width: 1.w),
top: BorderSide(color: const Color(0xFF206090), width: 1.w),
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'加邦TSAAS',
style: TextStyle(
color: const Color(0xFF00FFFF),
fontSize: 16.sp,
fontWeight: FontWeight.bold,
letterSpacing: 1.2,
),
),
Text(
'动物医疗监护舱',
style: TextStyle(
color: Colors.white.withValues(alpha: 0.9),
fontSize: 14.sp,
fontWeight: FontWeight.w400,
letterSpacing: 0.8,
),
),
],
),
),
);
}
}
......@@ -13,34 +13,36 @@ class MonitoringTopStatusBar extends StatelessWidget {
fit: BoxFit.fill,
),
),
padding: EdgeInsets.only(left: 20.w,bottom: 22.h),
padding: EdgeInsets.only(left: 32.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(),
// SizedBox(width: 18.w),
Text(
'总运行时长:9999h',
style: TextStyle(
color: Colors.white,
fontSize: 25.sp,
),
),
SizedBox(width: 32.w),
Text(
'环境温度:30.68',
style: TextStyle(
color: Colors.white,
fontSize: 25.sp,
),
),
SizedBox(width: 32.w),
Text(
'总制氧时长:500h',
style: TextStyle(
color: Colors.white,
fontSize: 25.sp,
),
),
// const Spacer(),
],
),
);
......
......@@ -66,8 +66,6 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
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,
......@@ -75,7 +73,6 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
metrics: metrics,
patientInfo: patientInfo,
alerts: alerts,
menuItems: menuItems,
error: null,
));
......@@ -184,15 +181,12 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
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,
metrics: metrics,
patientInfo: patientInfo,
alerts: alerts,
menuItems: menuItems,
error: null,
));
} catch (e) {
......
......@@ -17,7 +17,6 @@ class MonitoringIndexState extends Equatable {
final List<MonitoringMetricBO> metrics;
final PatientInfoBO? patientInfo;
final List<AlertInfoBO> alerts;
final List<MonitoringMenuItemBO> menuItems;
/// WebRTC 视频连接状态
final WebrtcConnectionState videoConnectionState;
......@@ -38,7 +37,6 @@ class MonitoringIndexState extends Equatable {
this.metrics = const [],
this.patientInfo,
this.alerts = const [],
this.menuItems = const [],
this.videoConnectionState = WebrtcConnectionState.disconnected,
this.videoStreamMode = VideoStreamMode.p2p,
this.isSwitchingMode = false,
......@@ -52,7 +50,6 @@ class MonitoringIndexState extends Equatable {
List<MonitoringMetricBO>? metrics,
PatientInfoBO? patientInfo,
List<AlertInfoBO>? alerts,
List<MonitoringMenuItemBO>? menuItems,
WebrtcConnectionState? videoConnectionState,
VideoStreamMode? videoStreamMode,
bool? isSwitchingMode,
......@@ -65,7 +62,6 @@ class MonitoringIndexState extends Equatable {
metrics: metrics ?? this.metrics,
patientInfo: patientInfo ?? this.patientInfo,
alerts: alerts ?? this.alerts,
menuItems: menuItems ?? this.menuItems,
videoConnectionState:
videoConnectionState ?? this.videoConnectionState,
videoStreamMode: videoStreamMode ?? this.videoStreamMode,
......@@ -82,7 +78,6 @@ class MonitoringIndexState extends Equatable {
metrics,
patientInfo,
alerts,
menuItems,
videoConnectionState,
videoStreamMode,
isSwitchingMode,
......
......@@ -8,10 +8,8 @@ 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/video_player_widget.dart';
@RoutePage()
......@@ -45,23 +43,19 @@ class _MonitoringIndexContentState extends State<MonitoringIndexContent> {
@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);
},
),
return 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 _buildMainArea(state);
},
);
}
......@@ -84,69 +78,23 @@ class _MonitoringIndexContentState extends State<MonitoringIndexContent> {
);
}
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) {
Widget _buildMainArea(MonitoringIndexState state) {
return Row(
children: [
Expanded(
flex: 20,
flex: 26,
child: MonitoringPetInfoCard(patientInfo: state.patientInfo),
),
// SizedBox(width: gap),
SizedBox(width: 24.w),
Expanded(
flex: 40,
flex: 74,
child: Column(
children: [
Expanded(
flex: 32,
child: Row(
children: [
Expanded(
flex: 4,
child: _buildMetrics(state.metrics, gap),
),
// SizedBox(width: gap),
],
),
child: _buildMetrics(state.metrics),
),
SizedBox(height: gap),
SizedBox(height: 24.h),
Expanded(
flex: 34,
child: Row(
......@@ -155,7 +103,7 @@ class _MonitoringIndexContentState extends State<MonitoringIndexContent> {
flex: 10,
child: MonitoringAlertCard(alerts: state.alerts),
),
SizedBox(width: gap),
SizedBox(width: 10.w),
Expanded(
flex: 10,
child: VideoPlayerWidget(
......@@ -181,25 +129,26 @@ class _MonitoringIndexContentState extends State<MonitoringIndexContent> {
);
}
Widget _buildMetrics(List<MonitoringMetricBO> metrics, double gap) {
Widget _buildMetrics(List<MonitoringMetricBO> metrics) {
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),
),
final children = <Widget>[];
for (int i = 0; i < displayMetrics.length; i++) {
children.add(
Expanded(
child: Container(
decoration: BoxDecoration(
// border: BoxBorder.all(width: 1.w, color: Colors.red),
),
)
.toList(),
child: MonitoringMetricCard(metric: displayMetrics[i]),
),
),
);
if (i < displayMetrics.length - 1) {
children.add(SizedBox(width: 20.w));
}
}
return Row(
children: children,
);
}
......
......@@ -2,8 +2,6 @@ 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;
......@@ -14,43 +12,38 @@ class MonitoringAlertCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
final hasPending = alerts.any((item) => item.status.contains('待处理'));
return Container(
padding: EdgeInsets.zero,
padding: EdgeInsets.symmetric(horizontal: 35.w, vertical: 30.h),
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('lib/assets/monitoring/alert_box_bg_2.png'),
image: AssetImage(
hasPending
? 'lib/assets/monitoring/alert_box_bg_2.png'
: 'lib/assets/monitoring/alert_box_bg_1.png',
),
fit: BoxFit.fill,
),
border: Border.all(
color: Colors.red,
width: 1.w
)
),
// 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: [
Container(
width: double.infinity,
height: 100.h,
decoration: BoxDecoration(
border: Border.all(
color: Colors.red,
width: 1.w
)
children: [
Text(
'告警信息',
style: TextStyle(
color: Colors.white,
fontSize: 28.sp,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 24.h),
Expanded(
child: _buildEmpty()// alerts.isEmpty ? _buildEmpty() : _buildList(),
child: alerts.isEmpty ? _buildEmpty() : _buildList(),
),
],
),
);
}
Widget _buildEmpty() {
return Center(
......@@ -63,44 +56,103 @@ class MonitoringAlertCard extends StatelessWidget {
Widget _buildList() {
return ListView.separated(
padding: EdgeInsets.zero,
// padding: EdgeInsets.symmetric(vertical: 6.h),
itemCount: alerts.length,
separatorBuilder: (_, __) => SizedBox(height: 12.h),
separatorBuilder: (_, __) => SizedBox(height: 16.h),
itemBuilder: (context, index) {
final item = alerts[index];
final pending = item.status.contains('待处理');
return Container(
height: 170.h,
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 14.h),
decoration: BoxDecoration(
border: Border.all(
width: 1.w,
color: Colors.red
)
// 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: Column(
children: [
Text(
item.title,
style: TextStyle(
color: Colors.white,
fontSize: 20.sp,
fontWeight: FontWeight.bold
),
),
return _buildAlertItem(item, pending);
},
);
}
Widget _buildAlertItem(AlertInfoBO item, bool pending) {
return IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildIconBlock(pending),
SizedBox(width: 20.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
item.title,
style: TextStyle(
color: pending ? const Color(0xFFF26522) : const Color(0xFFFFC14A),
fontSize: 28.sp,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8.h),
Text(
item.description,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 23.sp,
),
),
// SizedBox(height: 10.h),
Text(
item.time,
style: TextStyle(
color: Colors.white,
fontSize: 20.sp,
),
),
],
),
),
],
),
);
}
],
Widget _buildIconBlock(bool pending) {
return Container(
width: 196.w,
height: 148.h,
alignment: Alignment.topLeft,
child: Stack(
children: [
Container(
decoration: BoxDecoration(
color: const Color(0xFF2A2E3B),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(18.r),
topRight: Radius.circular(18.r),
bottomLeft: Radius.circular(18.r),
bottomRight: Radius.circular(18.r),
),
),
),
);
},
Container(
// width: 83.w,
// height: 34.h,
padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 6.h),
decoration: BoxDecoration(
color: pending ? const Color(0xFFFBB03B) : const Color(0xFF6FA8DC),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(18.r),
bottomRight: Radius.circular(18.r),
),
),
child: Text(
pending ? '待处理' : '已处理',
style: TextStyle(
color: Colors.white,
fontSize: 16.sp,
fontWeight: FontWeight.bold,
),
),
),
],
),
);
}
}
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:flutter_switch/flutter_switch.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
import 'monitoring_panel.dart';
......@@ -7,53 +8,63 @@ import 'monitoring_panel.dart';
class MonitoringMetricCard extends StatelessWidget {
final MonitoringMetricBO metric;
/// 开关状态
final bool switchValue;
/// 开关切换回调
final ValueChanged<bool>? onSwitchToggle;
const MonitoringMetricCard({
super.key,
required this.metric,
this.switchValue = false,
this.onSwitchToggle,
});
@override
Widget build(BuildContext context) {
final displayLabel = _displayLabel(metric.label);
return Container(
decoration: const BoxDecoration(
return Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('lib/assets/monitoring/box_bg_325x372.png'),
fit: BoxFit.cover,
),
fit: BoxFit.fill,
),
// border: BoxBorder.all(width: 1.w,color: Colors.red)
),
// padding: EdgeInsets.symmetric(horizontal: 18.w, vertical: 18.h),
padding: EdgeInsets.only(left: 10.w,right: 10.w, top: 54.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 60.h,),
children: [
Row(
children: [
SizedBox(width: 20.w),
SizedBox(width: 32.w),
Expanded(
flex: 2,
flex: 3,
child: Text(
displayLabel,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 18.sp,
fontSize: 28.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,
flex: 2,
child: Align(
alignment: Alignment.topLeft,
child: FlutterSwitch(
width: 60.w,
height: 30.h,
value: switchValue,
onToggle: onSwitchToggle ?? (_) {},
activeColor: const Color(0xFF75C1DD),
inactiveColor: Color(0xFF0A1B34),
toggleSize: 22.w,
padding: 4.w,
),
),
),
......@@ -61,7 +72,7 @@ class MonitoringMetricCard extends StatelessWidget {
),
// const Spacer(),
Container(
padding: EdgeInsets.only(left: 20.w,top: 40.h),
padding: EdgeInsets.only(left: 32.w,top: 24.h),
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
......@@ -69,19 +80,19 @@ class MonitoringMetricCard extends StatelessWidget {
metric.value,
style: TextStyle(
color: const Color(0xFF9DF5FF),
fontSize: 36.sp,
fontSize: 90.sp,
height: 1,
fontWeight: FontWeight.bold,
),
),
SizedBox(width: 6.w),
SizedBox(width: 18.w),
Padding(
padding: EdgeInsets.only(bottom: 3.h),
child: Text(
metric.unit,
style: TextStyle(
color: Colors.white70,
fontSize: 18.sp,
color: Colors.white,
fontSize: 30.sp,
fontWeight: FontWeight.w500,
),
),
......@@ -90,15 +101,15 @@ class MonitoringMetricCard extends StatelessWidget {
),
),
SizedBox(height: 12.h),
SizedBox(height: 22.h),
Container(
width: 100.w,
margin: EdgeInsets.only(left: 20.w),
width: 220.w,
margin: EdgeInsets.only(left: 32.w,),
decoration: BoxDecoration(
border: Border.all(
color: Colors.red,
width: 1.w
)
// border: Border.all(
// color: Colors.red,
// width: 1.w
// )
),
child: Stack(
clipBehavior: Clip.none,
......@@ -110,12 +121,12 @@ class MonitoringMetricCard extends StatelessWidget {
"设定24"+metric.unit,
style: TextStyle(
color: Colors.white,
fontSize: 14.sp,
fontSize: 24.sp,
),
),
Container(
height: 5.h,
margin: EdgeInsets.only(top: 15.h),
margin: EdgeInsets.only(top: 18.h),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(3.r),
gradient: const LinearGradient(
......@@ -126,12 +137,12 @@ class MonitoringMetricCard extends StatelessWidget {
],
),
Positioned(
right: -45.w,
top: -40.h,
right: -65.w,
top: -20.h,
child: Image.asset(
_iconAsset(metric.label),
width: 60.w,
height: 60.w,
width: 102.w,
height: 102.w,
fit: BoxFit.contain,
),
),
......@@ -139,10 +150,10 @@ class MonitoringMetricCard extends StatelessWidget {
),
),
Container(
margin: EdgeInsets.only(left: 14),
margin: EdgeInsets.only(left: 2.w),
child: Image.asset(
'lib/assets/monitoring/box_bottom_char.png',
width: 140.w,
// width: 140.w,
// height: 60.w,
fit: BoxFit.contain,
),
......
......@@ -11,10 +11,10 @@ class MonitoringVideoCard extends StatelessWidget {
return Container(
decoration: BoxDecoration(
image: DecorationImage(image: AssetImage('lib/assets/monitoring/box_bg_674x509.png'),fit: BoxFit.fill),
border: Border.all(
color: Colors.red,
width: 1.w
)
// border: Border.all(
// color: Colors.red,
// width: 1.w
// )
),
child: Stack(
children: [
......
......@@ -66,13 +66,13 @@ class VideoPlayerWidget extends StatelessWidget {
Widget build(BuildContext context) {
return ClipRRect(
child: Container(
padding: EdgeInsets.only(top: 15.h,bottom: 35.h,left: 18.w,right: 18.w),
padding: EdgeInsets.only(top: 15.h,bottom: 35.h,left: 10.w,right: 15.w),
decoration: BoxDecoration(
image: const DecorationImage(
image: AssetImage('lib/assets/monitoring/box_bg_674x509.png'),
image: const DecorationImage(
image: AssetImage('lib/assets/monitoring/box_border.png'),
fit: BoxFit.fill,
),
border: Border.all(color: Colors.red, width: 1),
// border: Border.all(color: Colors.red, width: 1),
),
child: Stack(
fit: StackFit.expand,
......
import 'package:flutter_bloc/flutter_bloc.dart';
import 'record_index_state.dart';
class RecordIndexCubit extends Cubit<RecordIndexState> {
RecordIndexCubit() : super(const RecordIndexState());
}
import 'package:equatable/equatable.dart';
class RecordIndexState extends Equatable {
const RecordIndexState();
@override
List<Object?> get props => [];
}
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
@RoutePage()
class RecordIndexView extends StatelessWidget {
const RecordIndexView({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: Text(
'记录',
style: TextStyle(color: Colors.white, fontSize: 24),
),
);
}
}
import 'package:flutter_bloc/flutter_bloc.dart';
import 'settings_index_state.dart';
class SettingsIndexCubit extends Cubit<SettingsIndexState> {
SettingsIndexCubit() : super(const SettingsIndexState());
}
import 'package:equatable/equatable.dart';
class SettingsIndexState extends Equatable {
const SettingsIndexState();
@override
List<Object?> get props => [];
}
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
@RoutePage()
class SettingsIndexView extends StatelessWidget {
const SettingsIndexView({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: Text(
'设置',
style: TextStyle(color: Colors.white, fontSize: 24),
),
);
}
}
import 'package:flutter_bloc/flutter_bloc.dart';
import 'tsaas_index_state.dart';
class TsaasIndexCubit extends Cubit<TsaasIndexState> {
TsaasIndexCubit() : super(const TsaasIndexState());
}
import 'package:equatable/equatable.dart';
class TsaasIndexState extends Equatable {
const TsaasIndexState();
@override
List<Object?> get props => [];
}
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
@RoutePage()
class TsaasIndexView extends StatelessWidget {
const TsaasIndexView({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: Text(
'TSAAS',
style: TextStyle(color: Colors.white, fontSize: 24),
),
);
}
}
import 'package:flutter_bloc/flutter_bloc.dart';
import 'vital_signs_index_state.dart';
class VitalSignsIndexCubit extends Cubit<VitalSignsIndexState> {
VitalSignsIndexCubit() : super(const VitalSignsIndexState());
}
import 'package:equatable/equatable.dart';
class VitalSignsIndexState extends Equatable {
const VitalSignsIndexState();
@override
List<Object?> get props => [];
}
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
@RoutePage()
class VitalSignsIndexView extends StatelessWidget {
const VitalSignsIndexView({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: Text(
'体征仪',
style: TextStyle(color: Colors.white, fontSize: 24),
),
);
}
}
......@@ -93,6 +93,7 @@ flutter:
# To add assets to your application, add an assets section, like this:
assets:
- lib/assets/
- lib/assets/menu/
- lib/assets/logo/
- lib/assets/login/
- lib/assets/tab/
......
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