diff --git a/android/.kotlin/errors/errors-1779355041254.log b/android/.kotlin/errors/errors-1779355041254.log
new file mode 100644
index 00000000..11a74030
--- /dev/null
+++ b/android/.kotlin/errors/errors-1779355041254.log
@@ -0,0 +1,45 @@
+kotlin version: 2.2.20
+error message: Failed connecting to the daemon in 4 retries
+
+error message: Daemon compilation failed: Could not connect to Kotlin compile daemon
+java.lang.RuntimeException: Could not connect to Kotlin compile daemon
+ at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214)
+ at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159)
+ at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111)
+ at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:74)
+ at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63)
+ at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66)
+ at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62)
+ at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100)
+ at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62)
+ at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
+ at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:210)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:205)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:67)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:60)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:167)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:60)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:54)
+ at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
+ at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59)
+ at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174)
+ at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
+ at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194)
+ at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127)
+ at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169)
+ at org.gradle.internal.Factories$1.create(Factories.java:31)
+ at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263)
+ at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127)
+ at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132)
+ at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
+ at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133)
+ at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
+ at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
+ at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
+ at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
+ at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
+ at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
+ at java.base/java.lang.Thread.run(Unknown Source)
+
+
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index e61ad546..f45e8a7d 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -7,6 +7,10 @@
+
+
+
+
diff --git a/assets/images/chucao.png b/assets/images/chucao.png
new file mode 100644
index 00000000..6732e851
Binary files /dev/null and b/assets/images/chucao.png differ
diff --git a/assets/images/dianzhan.png b/assets/images/dianzhan.png
new file mode 100644
index 00000000..c626c01b
Binary files /dev/null and b/assets/images/dianzhan.png differ
diff --git a/assets/images/qinxi.png b/assets/images/qinxi.png
new file mode 100644
index 00000000..c53baddb
Binary files /dev/null and b/assets/images/qinxi.png differ
diff --git a/assets/images/xunjian.png b/assets/images/xunjian.png
new file mode 100644
index 00000000..00b78839
Binary files /dev/null and b/assets/images/xunjian.png differ
diff --git a/assets/images/yiwancheng.png b/assets/images/yiwancheng.png
new file mode 100644
index 00000000..8222c395
Binary files /dev/null and b/assets/images/yiwancheng.png differ
diff --git a/devtools_options.yaml b/devtools_options.yaml
index e9174f49..caaff94e 100644
--- a/devtools_options.yaml
+++ b/devtools_options.yaml
@@ -2,4 +2,5 @@ description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:
- get_it: true
- - provider: true
\ No newline at end of file
+ - provider: true
+ - shared_preferences: true
\ No newline at end of file
diff --git a/docs/TAB_CONFIG_DEVELOPMENT_GUIDE.md b/docs/TAB_CONFIG_DEVELOPMENT_GUIDE.md
new file mode 100644
index 00000000..72dde6f7
--- /dev/null
+++ b/docs/TAB_CONFIG_DEVELOPMENT_GUIDE.md
@@ -0,0 +1,197 @@
+# Tab 配置系统开发规范
+
+## 📋 功能说明
+
+Tab 配置系统支持动态开关控制底部导航栏的 Tab 项显示/隐藏,配置自动保存到 SharedPreferences。
+
+## 🔧 添加新 Tab 项的步骤
+
+当需要添加新的 Tab 项时,必须按以下顺序修改 **3 个文件**:
+
+### 1️⃣ 修改 `tab_config.dart` - 添加默认配置
+
+**文件路径:** `lib/features/main_container/domain/tab_config.dart`
+
+在 `TabConfig.defaultConfig()` 方法的 `items` 列表中添加新的 `TabConfigItem`:
+
+```dart
+TabConfigItem(
+ id: 'your_tab_id', // 唯一标识符(小写+下划线)
+ name: '显示名称', // 中文名称(简洁,建议2-4个字)
+ nameEn: 'Display Name', // 英文名称
+ icon: 'icon_name_rounded', // 图标名称(Material Icons)
+ isEnabled: true, // 默认是否启用(true/false)
+ order: 7, // 排序序号(从1开始递增)
+),
+```
+
+**注意事项:**
+- `id` 必须唯一,建议使用有意义的英文标识
+- `name` 要简洁,符合 TAB 命名规范
+- `order` 决定 Tab 在导航栏中的显示顺序
+- `isEnabled` 控制默认是否显示该 Tab
+
+---
+
+### 2️⃣ 修改 `custom_main_container.dart` - 添加页面映射
+
+**文件路径:** `lib/features/main_container/presentation/pages/custom_main_container.dart`
+
+#### 2.1 导入新页面
+
+在文件顶部添加新页面的 import:
+
+```dart
+import 'package:maibu_satabot_v2/features/xxx/presentation/pages/your_page.dart';
+```
+
+#### 2.2 在 `_buildPages` 方法中添加 case
+
+```dart
+List _buildPages(List enabledTabs) {
+ return enabledTabs.map((tab) {
+ switch (tab.id) {
+ case 'home_v2':
+ return const HomeV2Page();
+ case 'home':
+ return const HomePage();
+ case 'device':
+ return const DeviceStatusPage();
+ case 'ai':
+ return const AiPage();
+ case 'warning':
+ return const WarningCenterPage();
+ case 'my':
+ return const MyPage();
+ case 'your_tab_id': // 👈 新增这个 case
+ return const YourPage();
+ default:
+ return const HomePage();
+ }
+ }).toList();
+}
+```
+
+---
+
+### 3️⃣ 修改 `tab_settings_page.dart` - 添加图标映射
+
+**文件路径:** `lib/features/main_container/presentation/pages/tab_settings_page.dart`
+
+在 `_getIconData` 方法中添加新图标的映射:
+
+```dart
+IconData _getIconData(String iconName) {
+ switch (iconName) {
+ case 'home_rounded':
+ return Icons.home_rounded;
+ case 'grid_view_rounded':
+ return Icons.grid_view_rounded;
+ case 'devices_rounded':
+ return Icons.devices_rounded;
+ case 'auto_awesome_rounded':
+ return Icons.auto_awesome_rounded;
+ case 'warning_amber_rounded':
+ return Icons.warning_amber_rounded;
+ case 'person_rounded':
+ return Icons.person_rounded;
+ case 'your_icon_name': // 👈 新增这个 case
+ return Icons.your_icon_name;
+ default:
+ return Icons.home_rounded;
+ }
+}
+```
+
+**注意:** 如果使用的是 Material Icons 标准图标,通常不需要修改此文件,除非使用了特殊图标。
+
+---
+
+## ✅ 检查清单
+
+添加新 Tab 后,请确认:
+
+- [ ] 已在 `tab_config.dart` 中添加 `TabConfigItem` 配置
+- [ ] 已在 `custom_main_container.dart` 中导入新页面
+- [ ] 已在 `custom_main_container.dart` 的 `switch` 中添加 case 分支
+- [ ] 已在 `tab_settings_page.dart` 中添加图标映射(如需要)
+- [ ] `id`、`order` 没有与其他 Tab 冲突
+- [ ] 测试开关功能是否正常
+- [ ] 测试切换 Tab 是否正常显示对应页面
+
+---
+
+## 🚀 完整示例:添加"消息"Tab
+
+假设要添加一个"消息"Tab,显示消息中心页面:
+
+### 步骤 1:修改 `tab_config.dart`
+
+```dart
+TabConfigItem(
+ id: 'message',
+ name: '消息',
+ nameEn: 'Messages',
+ icon: 'notifications_rounded',
+ isEnabled: true,
+ order: 7,
+),
+```
+
+### 步骤 2:修改 `custom_main_container.dart`
+
+```dart
+// 顶部添加导入
+import 'package:maibu_satabot_v2/features/message/presentation/pages/message_page.dart';
+
+// 在 _buildPages 方法中添加
+case 'message':
+ return const MessagePage();
+```
+
+### 步骤 3:修改 `tab_settings_page.dart`
+
+```dart
+case 'notifications_rounded':
+ return Icons.notifications_rounded;
+```
+
+---
+
+## ⚠️ 常见问题
+
+### Q1: 添加新 Tab 后看不到?
+**A:** 点击 Tab 配置页面的"重置为默认"按钮,或清除应用数据重新运行。
+
+### Q2: 点击 Tab 后页面不显示?
+**A:** 检查 `custom_main_container.dart` 中的 `switch-case` 是否正确添加了新 Tab 的映射。
+
+### Q3: 图标显示不正确?
+**A:** 检查 `tab_settings_page.dart` 中的 `_getIconData` 方法是否添加了对应的图标映射。
+
+### Q4: Tab 顺序不对?
+**A:** 检查 `tab_config.dart` 中各 Tab 的 `order` 值,确保按期望顺序排列。
+
+---
+
+## 📝 核心原理
+
+1. **配置管理:** `TabConfigCubit` 管理所有 Tab 的配置和选中状态
+2. **持久化:** 配置自动保存到 SharedPreferences,重启应用后保持
+3. **动态渲染:** 底部导航栏根据 `enabledItems` 动态渲染启用的 Tab
+4. **页面切换:** 使用 `IndexedStack` 保持各页面状态,通过 `selectedIndex` 切换
+
+---
+
+## 🎯 最佳实践
+
+1. **Tab 命名:** 保持简洁,中文 2-4 个字,避免过长
+2. **默认状态:** 新功能建议设置 `isEnabled: false`,通过灰度发布逐步开放
+3. **排序规划:** 预留 order 间隔(如 10, 20, 30),方便后续插入新 Tab
+4. **图标选择:** 优先使用 Material Icons 的 `_rounded` 版本,风格统一
+5. **测试验证:** 每次修改后测试开关、切换、持久化等功能
+
+---
+
+**最后更新:** 2026-05-20
+**维护者:** 开发团队
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index dc2c79d6..5bd423f2 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -66,5 +66,16 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
+
+ NSCameraUsageDescription
+ 需要使用相机拍照和录像
+
+ NSPhotoLibraryUsageDescription
+ 需要访问相册以选择图片和视频
+ NSPhotoLibraryAddUsageDescription
+ 需要保存拍摄的照片和视频到相册
+
+ NSMicrophoneUsageDescription
+ 录像时需要使用麦克风录制声音
diff --git a/lib/core/consts/workorder_consts.dart b/lib/core/consts/workorder_consts.dart
new file mode 100644
index 00000000..c1d6b021
--- /dev/null
+++ b/lib/core/consts/workorder_consts.dart
@@ -0,0 +1,104 @@
+import 'package:flutter/material.dart';
+
+/// 工单模块颜色常量
+class WorkOrderColors {
+ WorkOrderColors._();
+
+ /// 主色调
+ static const Color primary = Color(0xFF165DFF);
+
+ /// 待处理色(橙色)
+ static const Color pending = Color(0xFFFF7D00);
+
+ /// 执行中色(蓝色)
+ static const Color executing = Color(0xFF165DFF);
+
+ /// 已完成色(绿色)
+ static const Color completed = Color(0xFF00B42A);
+
+ /// 背景色
+ static const Color background = Color(0xFFFFFFFF);
+
+ /// 页面背景色
+ static const Color pageBackground = Color(0xFFF7F8FA);
+
+ /// 主要文字色
+ static const Color primaryText = Color(0xFF1D2129);
+
+ /// 次要文字色
+ static const Color secondaryText = Color(0xFF4E5969);
+
+ /// 辅助文字色
+ static const Color auxiliaryText = Color(0xFF86909C);
+
+ /// 分割线颜色
+ static const Color divider = Color(0xFFF0F0F0);
+
+ /// 进度条背景色
+ static const Color progressBackground = Color(0xFFF0F0F0);
+
+ /// 卡片阴影
+ static List get cardShadow => [
+ const BoxShadow(
+ color: Color(0x0D000000),
+ blurRadius: 4,
+ offset: Offset(0, 2),
+ ),
+ ];
+}
+
+/// 工单模块尺寸常量
+class WorkOrderDimensions {
+ WorkOrderDimensions._();
+
+ /// 通用圆角
+ static const double borderRadius = 16.0;
+
+ /// 水平边距
+ static const double horizontalPadding = 16.0;
+
+ /// 模块间距
+ static const double moduleSpacing = 20.0;
+
+ /// 导航栏高度
+ static const double navigationBarHeight = 44.0;
+
+ /// 标签栏高度
+ static const double tabBarHeight = 44.0;
+}
+
+/// 全局应用常量 (供 UI 组件使用)
+class AppConstants {
+ AppConstants._();
+
+ static const Color primaryColor = WorkOrderColors.primary;
+ static const Color backgroundColor = WorkOrderColors.background;
+ static const Color primaryTextColor = WorkOrderColors.primaryText;
+ static const Color secondaryTextColor = WorkOrderColors.secondaryText;
+ static const Color auxiliaryTextColor = WorkOrderColors.auxiliaryText;
+ static const Color pendingColor = WorkOrderColors.pending;
+ static const Color executingColor = WorkOrderColors.executing;
+ static const Color completedColor = WorkOrderColors.completed;
+
+ static const double borderRadius = WorkOrderDimensions.borderRadius;
+ static const double horizontalPadding = WorkOrderDimensions.horizontalPadding;
+ static const double moduleSpacing = WorkOrderDimensions.moduleSpacing;
+ static const double navigationBarHeight = WorkOrderDimensions.navigationBarHeight;
+
+ static List get cardShadow => WorkOrderColors.cardShadow;
+}
+
+/// 工单状态枚举
+enum WorkOrderStatus {
+ pending, // 待处理
+ executing, // 执行中
+ completed, // 已完成
+ all, // 全部
+}
+
+/// 工单优先级枚举
+enum WorkOrderPriority {
+ high, // 高
+ medium, // 中
+ low, // 低
+}
diff --git a/lib/core/di/injection.dart b/lib/core/di/injection.dart
index 90c533d1..0b833be9 100644
--- a/lib/core/di/injection.dart
+++ b/lib/core/di/injection.dart
@@ -61,6 +61,18 @@ import '../../features/v2/home/data/repositories/home_repository_impl.dart';
import '../../features/v2/home/domain/repositories/home_repository.dart';
import '../../features/v2/home/domain/usecases/get_home_data_usecase.dart';
import '../../features/v2/home/presentation/bloc/home_v2_bloc.dart';
+import '../../features/v2/device_list/data/datasources/device_remote_datasource.dart' as device_v2;
+import '../../features/v2/device_list/data/repositories/device_repository_impl.dart' as device_v2_repo;
+import '../../features/v2/device_list/domain/repositories/device_repository.dart' as device_v2_domain;
+import '../../features/v2/device_list/domain/usecases/get_device_status_data_usecase.dart' as device_v2_usecase;
+import '../../features/v2/device_list/presentation/bloc/device_status_bloc.dart' as device_v2_bloc;
+import '../../features/v2/waring_center/data/datasources/alarm_remote_datasource.dart';
+import '../../features/v2/waring_center/data/datasources/impl/alarm_remote_datasource_impl.dart';
+import '../../features/v2/waring_center/data/repositories/alarm_repository_impl.dart';
+import '../../features/v2/waring_center/domain/repositories/alarm_repository.dart';
+import '../../features/v2/waring_center/domain/usecases/get_alarm_count_usecase.dart';
+import '../../features/v2/waring_center/domain/usecases/get_alarm_list_usecase.dart';
+import '../../features/v2/waring_center/presentation/bloc/alarm_cubit.dart';
import '../app/app_user_cubit.dart';
import '../localization/locale_cubit.dart';
import '../../features/home/presentation/bloc/permission_request_bloc.dart';
@@ -194,6 +206,22 @@ Future init() async {
sl.registerLazySingleton(() => GetHomeDataUseCase(sl()));
sl.registerFactory(() => HomeV2Bloc(sl()));
+ /// Device Status V2
+ sl.registerLazySingleton(() => device_v2.DeviceRemoteDataSourceImpl());
+ sl.registerLazySingleton(() => device_v2_repo.DeviceRepositoryImpl(remoteDataSource: sl()));
+ sl.registerLazySingleton(() => device_v2_usecase.GetDeviceStatusDataUseCase(repository: sl()));
+ sl.registerFactory(() => device_v2_bloc.DeviceStatusBloc(sl()));
+
+ /// Alarm Center V2
+ sl.registerLazySingleton(() => AlarmRemoteDataSourceImpl());
+ sl.registerLazySingleton(() => AlarmRepositoryImpl(sl()));
+ sl.registerLazySingleton(() => GetAlarmListUseCase(sl()));
+ sl.registerLazySingleton(() => GetAlarmCountUseCase(sl()));
+ sl.registerFactory(() => AlarmCubit(
+ getAlarmListUseCase: sl(),
+ getAlarmCountUseCase: sl(),
+ ));
+
/// 5. 状态管理 (Cubit/Bloc)
sl.registerLazySingleton(() => AppUserCubit()); // AuthCubit 依赖它,必须先注册
diff --git a/lib/core/error/failure.dart b/lib/core/error/failure.dart
index 96dfe2de..8bacb6d5 100644
--- a/lib/core/error/failure.dart
+++ b/lib/core/error/failure.dart
@@ -6,3 +6,7 @@ class Failure {
class NetworkFailure extends Failure {
NetworkFailure(super.message);
}
+
+class ServerFailure extends Failure {
+ ServerFailure(super.message);
+}
diff --git a/lib/core/error/workorder_failure.dart b/lib/core/error/workorder_failure.dart
new file mode 100644
index 00000000..61ad41d9
--- /dev/null
+++ b/lib/core/error/workorder_failure.dart
@@ -0,0 +1,10 @@
+import 'package:maibu_satabot_v2/core/error/failure.dart';
+
+/// 工单模块特定的失败类型
+class WorkOrderFailure extends Failure {
+ WorkOrderFailure(super.message);
+}
+
+class UnknownFailure extends WorkOrderFailure {
+ UnknownFailure({required String message}) : super(message);
+}
diff --git a/lib/core/network/tcp/tcp_client.dart b/lib/core/network/tcp/tcp_client.dart
index 5b650c15..8ae0abbb 100644
--- a/lib/core/network/tcp/tcp_client.dart
+++ b/lib/core/network/tcp/tcp_client.dart
@@ -97,7 +97,7 @@ class TcpClient {
try {
var packets = _decoder.decode(data);
//debugPrint('📦 [TCP] 解码成功,包数量:${packets.length}');
- _logger.logWithLevel('✅ [TCP] 解码成功,包数量:${packets.length}',shouldLog: true);
+ _logger.logWithLevel('✅ [TCP] 解码成功,包数量:${packets.length}',shouldLog: false);
for (var packet in packets) {
// 🔥 最根部日志:收到任何推送都打印
// debugPrint('📥 [TCP-ROOT] 收到推送 CMD: 0x${packet.command.toRadixString(16).toUpperCase()}, Payload长度: ${packet.payload.length}');
@@ -106,23 +106,23 @@ class TcpClient {
if (!_controller.isClosed) {
_controller.add(packet);
//debugPrint('➡️ [TCP] 已分发 CMD: 0x${packet.command.toRadixString(16)}');
- _logger.logWithLevel('✅ [TCP] 已分发 CMD: 0x${packet.command.toRadixString(16)}' ,shouldLog: true);
+ _logger.logWithLevel('✅ [TCP] 已分发 CMD: 0x${packet.command.toRadixString(16)}' ,shouldLog: false);
}
if (packet.command == 0xFF) {
//debugPrint('收到服务端心跳,自动回复...');
- _logger.logWithLevel('✅ [TCP] 收到服务端心跳,自动回复...',shouldLog: true);
+ _logger.logWithLevel('✅ [TCP] 收到服务端心跳,自动回复...',shouldLog: false);
sendHeartbeat(); // 回复 AB AA FF AA AB
}
if (packet.command == 0x03) {
// debugPrint('⚠️ 收到认证响应:${packet.payload}');
- _logger.logWithLevel('⚠️ 收到认证响应:${packet.payload}',shouldLog: true);
+ _logger.logWithLevel('⚠️ 收到认证响应:${packet.payload}',shouldLog: false);
// 解析 payload 看是否有错误信息
}
}
} catch (e, stackTrace) {
// debugPrint('❌ [TCP] 解码数据时发生异常:$e\n$stackTrace'); // 🔥 捕获解码异常
- _logger.logWithLevel('❌ [TCP] 解码数据时发生异常:$e\n$stackTrace', shouldLog: true);
+ _logger.logWithLevel('❌ [TCP] 解码数据时发生异常:$e\n$stackTrace', shouldLog: false);
}
},
onDone: (){
@@ -130,7 +130,7 @@ class TcpClient {
// debugPrint('来到断线重连!');
if (!_isSwitching) {
//debugPrint('onDone❌ [TCP] 连接已断开!');
- _logger.logWithLevel('❌ [TCP] 连接已断开!', shouldLog: true);
+ _logger.logWithLevel('❌ [TCP] 连接已断开!', shouldLog: false);
_handleDisconnect();
}
_isSwitching = false; // 重置标志,以免影响下次
diff --git a/lib/core/router/app_router.dart b/lib/core/router/app_router.dart
index f2938dce..ac8d5f31 100644
--- a/lib/core/router/app_router.dart
+++ b/lib/core/router/app_router.dart
@@ -7,7 +7,7 @@ import 'package:maibu_satabot_v2/features/devices/domain/entities/device_entity.
import 'package:maibu_satabot_v2/features/home/presentation/routes/home_routes.dart';
import 'package:maibu_satabot_v2/features/main_container/presentation/pages/tab_settings_page.dart';
import 'package:maibu_satabot_v2/features/my/presentation/routes/my_routes.dart';
-import 'package:maibu_satabot_v2/features/waring_center/presentation/routes/warning_center_routes.dart';
+import 'package:maibu_satabot_v2/features/v2/waring_center/presentation/routes/alarm_center_routes.dart';
import '../../features/auth/presentation/bloc/auth_cubit.dart';
import '../../features/auth/presentation/bloc/auth_state.dart';
@@ -22,11 +22,14 @@ GoRouter createRouter(AuthCubit authCubit) {
redirect: (context, state) {
final loggedIn = authCubit.state is AuthAuthenticated;
final loggingIn = state.matchedLocation == RoutePaths.login;
+ final registering = state.matchedLocation == RoutePaths.register;
- if (!loggedIn && !loggingIn) {
+ // 如果未登录,只允许访问 login 和 register 页面
+ if (!loggedIn && !loggingIn && !registering) {
return RoutePaths.login;
}
- if (loggedIn && loggingIn) {
+ // 如果已登录,尝试访问 login 或 register,则跳转到首页
+ if (loggedIn && (loggingIn || registering)) {
return RoutePaths.home;
}
return null;
@@ -60,7 +63,7 @@ GoRouter createRouter(AuthCubit authCubit) {
...HomeRoutes.routes,
...AiRoutes.routes,
...MyRoutes.routes,
- ...WarningCenterRoutes.routes,
+ ...AlarmCenterRoutes.routes,
],
);
}
diff --git a/lib/features/auth/presentation/pages/login_page.dart b/lib/features/auth/presentation/pages/login_page.dart
index cb6c385a..5ec1f686 100644
--- a/lib/features/auth/presentation/pages/login_page.dart
+++ b/lib/features/auth/presentation/pages/login_page.dart
@@ -45,145 +45,157 @@ class _LoginPageState extends State {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('登录失败,${state.message}')));
}
},
- child: SingleChildScrollView(
- padding: const EdgeInsets.symmetric(horizontal: 30.0),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- const SizedBox(height: 15),
-
- // 顶部居中图片和认证信息
- Center(
- child: Column(
- children: [
- Image.asset(
- 'assets/images/huawei.png',
- width: 80,
- height: 80,
- fit: BoxFit.contain,
- ),
- const SizedBox(height: 8),
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 20.0),
- child: Text(
- "通过华为云生态技术认证,请放心使用,证书编号C202605301",
- textAlign: TextAlign.center,
- style: TextStyle(
- fontSize: 11,
- color: Colors.grey.shade600,
- height: 1.4,
+ child: LayoutBuilder(
+ builder: (context, constraints) {
+ return SingleChildScrollView(
+ padding: const EdgeInsets.symmetric(horizontal: 20.0),
+ child: ConstrainedBox(
+ constraints: BoxConstraints(
+ minHeight: constraints.maxHeight,
+ ),
+ child: IntrinsicHeight(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ // 顶部区域:图片 + 认证信息 + 标题(压缩空间)
+ const SizedBox(height: 10),
+
+ // 顶部居中图片和认证信息
+ Center(
+ child: Column(
+ children: [
+ Image.asset(
+ 'assets/images/huawei.png',
+ width: 60,
+ height: 60,
+ fit: BoxFit.contain,
+ ),
+ const SizedBox(height: 6),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 20.0),
+ child: Text(
+ "通过华为云生态技术认证,请放心使用,证书编号C202605301",
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ fontSize: 10,
+ color: Colors.grey.shade600,
+ height: 1.3,
+ ),
+ ),
+ ),
+ ],
),
),
- ),
- ],
- ),
- ),
-
- const SizedBox(height: 20),
- const Text(
- "账号登录",
- style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: Colors.black),
- ),
- const SizedBox(height: 12),
- const Text("请填写以下信息以验证身份", style: TextStyle(fontSize: 15, color: Colors.grey)),
- const SizedBox(height: 40),
+
+ const SizedBox(height: 16),
+ const Text(
+ "账号登录",
+ style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: Colors.black),
+ ),
+ const SizedBox(height: 8),
+ const Text("请填写以下信息以验证身份", style: TextStyle(fontSize: 15, color: Colors.grey)),
+ const SizedBox(height: 24),
- // 账号输入框
- _buildInputLabel("账号"),
- TextField(
- controller: _userCtrl,
- cursorColor: Colors.black,
- decoration: _inputDecoration(hint: "请输入用户名"),
- ),
+ // 表单区域
+ // 账号输入框
+ _buildInputLabel("账号"),
+ TextField(
+ controller: _userCtrl,
+ cursorColor: Colors.black,
+ decoration: _inputDecoration(hint: "请输入用户名"),
+ ),
- const SizedBox(height: 24),
+ const SizedBox(height: 20),
- // 密码输入框
- _buildInputLabel("密码"),
- TextField(
- controller: _pwdCtrl,
- obscureText: _obscurePwd,
- cursorColor: Colors.black,
- decoration: _inputDecoration(
- hint: "请输入密码",
- suffixIcon: IconButton(
- icon: Icon(_obscurePwd ? Icons.visibility_off_outlined : Icons.visibility_outlined, color: Colors.grey, size: 20),
- onPressed: () => setState(() => _obscurePwd = !_obscurePwd),
+ // 密码输入框
+ _buildInputLabel("密码"),
+ TextField(
+ controller: _pwdCtrl,
+ obscureText: _obscurePwd,
+ cursorColor: Colors.black,
+ decoration: _inputDecoration(
+ hint: "请输入密码",
+ suffixIcon: IconButton(
+ icon: Icon(_obscurePwd ? Icons.visibility_off_outlined : Icons.visibility_outlined, color: Colors.grey, size: 20),
+ onPressed: () => setState(() => _obscurePwd = !_obscurePwd),
+ ),
+ ),
+ ),
+
+ const SizedBox(height: 16),
+
+ // 协议勾选区域
+ _buildProtocolSection(),
+
+ const SizedBox(height: 30),
+
+ // 底部区域:登录按钮 + 注册链接
+ SizedBox(
+ width: double.infinity,
+ child: BlocBuilder(
+ builder: (context, state) {
+ bool isLoading = state is LoginLoading;
+
+ return CCPrimaryButton(
+ onPressed: isLoading
+ ? null
+ : () {
+ if (!_isAgreed) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(
+ content: Text('请先阅读并同意用户协议'),
+ duration: Duration(seconds: 1),
+ ),
+ );
+ return;
+ }
+ _handleLogin();
+ },
+ text: isLoading ? '登录中...' : '登 录',
+ );
+ },
+ ),
+ ),
+ const SizedBox(height: 16),
+
+ Center(
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ const Text(
+ "没有账号吗?",
+ style: TextStyle(fontSize: 14, color: Colors.grey),
+ ),
+ InkWell(
+ onTap: () {
+ debugPrint(' 点击了立即注册');
+ debugPrint('👉 跳转路径: ${RoutePaths.register}');
+ context.go(RoutePaths.register);
+ },
+ borderRadius: BorderRadius.circular(4),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
+ child: Text(
+ "立即注册",
+ style: TextStyle(
+ fontSize: 14,
+ color: Colors.black,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+
+ const SizedBox(height: 20),
+ ],
),
),
),
-
- const SizedBox(height: 20),
-
- // 协议勾选区域
- _buildProtocolSection(),
-
- const SizedBox(height: 40),
-
- // 登录按钮 (使用你的 CCPrimaryButton 并增加状态控制)
- SizedBox(
- width: double.infinity,
- child: BlocBuilder(
- builder: (context, state) {
- // 关键:判断是否正在 Loading
- bool isLoading = state is LoginLoading;
-
- return CCPrimaryButton(
- // 核心修复:正在加载时 → onPressed 为 null,按钮禁用
- onPressed: isLoading
- ? null
- : () {
- if (!_isAgreed) {
- ScaffoldMessenger.of(context).showSnackBar(
- const SnackBar(
- content: Text('请先阅读并同意用户协议'),
- duration: Duration(seconds: 1), // 这里设置显示时长:2秒
- ),
- );
- return;
- }
- _handleLogin();
- },
- text: isLoading ? '登录中...' : '登 录',
- );
- },
- ),
- ),
- const SizedBox(height: 24),
-
- Center(
- child: Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- const Text(
- "没有账号吗?",
- style: TextStyle(fontSize: 14, color: Colors.grey),
- ),
- GestureDetector(
- onTap: () {
- print('点击了立即注册');
- print('跳转路径: ${RoutePaths.register}');
- context.go(RoutePaths.register);
- },
- child: const Text(
- "立即注册",
- style: TextStyle(
- fontSize: 14,
- color: Colors.black,
- fontWeight: FontWeight.bold,
- ),
- ),
- ),
- ],
- ),
- ),
-
- const SizedBox(height: 15),
-
-
-
- ],
- ),
+ );
+ },
),
),
);
diff --git a/lib/features/devices/domain/usecases/generate_path_usecase.dart b/lib/features/devices/domain/usecases/generate_path_usecase.dart
index 523b9bb2..305e772e 100644
--- a/lib/features/devices/domain/usecases/generate_path_usecase.dart
+++ b/lib/features/devices/domain/usecases/generate_path_usecase.dart
@@ -1,6 +1,7 @@
import 'package:fpdart/fpdart.dart';
import '../../../../core/error/failure.dart';
+import '../../../../core/error/failure.dart' as core_error;
import '../../../auth/domain/errors/auth_failure.dart';
import '../../data/models/device_add_path_point_model.dart';
import '../../data/models/device_work_area_param_model.dart';
@@ -33,7 +34,7 @@ class GeneratePathUseCaseImpl implements GeneratePathUseCase {
final result = await _repository.generatePath(reference: reference, heading: heading, outer: outer, holes: holes, workType: workType);
return Right(result); // 成功时返回 Right
} catch (e) {
- return Left(ServerFailure(e.toString(), 1)); // 失败时返回 Left
+ return Left(core_error.ServerFailure(e.toString())); // 失败时返回 Left
}
}
}
diff --git a/lib/features/main_container/domain/tab_config.dart b/lib/features/main_container/domain/tab_config.dart
index 26ad0713..c002cda6 100644
--- a/lib/features/main_container/domain/tab_config.dart
+++ b/lib/features/main_container/domain/tab_config.dart
@@ -80,17 +80,17 @@ class TabConfig extends Equatable {
items: [
TabConfigItem(
id: 'home_v2',
- name: '首页V2',
- nameEn: 'Home V2',
+ name: '首页',
+ nameEn: 'Home',
icon: 'home_rounded',
- isEnabled: false,
+ isEnabled: true,
order: 1,
),
TabConfigItem(
- id: 'home',
- name: '状态',
- nameEn: 'Status',
- icon: 'grid_view_rounded',
+ id: 'device',
+ name: '设备',
+ nameEn: 'Device',
+ icon: 'devices_rounded',
isEnabled: true,
order: 2,
),
@@ -99,7 +99,7 @@ class TabConfig extends Equatable {
name: 'AI',
nameEn: 'AI',
icon: 'auto_awesome_rounded',
- isEnabled: true,
+ isEnabled: false,
order: 3,
),
TabConfigItem(
@@ -110,13 +110,37 @@ class TabConfig extends Equatable {
isEnabled: true,
order: 4,
),
+ TabConfigItem(
+ id: 'workorder',
+ name: '工单',
+ nameEn: 'WorkOrder',
+ icon: 'assignment_rounded',
+ isEnabled: true,
+ order: 5,
+ ),
+ TabConfigItem(
+ id: 'report',
+ name: '上报',
+ nameEn: 'Report',
+ icon: 'post_add_rounded',
+ isEnabled: true,
+ order: 6,
+ ),
TabConfigItem(
id: 'my',
+ name: '旧我的',
+ nameEn: 'Old Profile',
+ icon: 'person_rounded',
+ isEnabled: false,
+ order: 7,
+ ),
+ TabConfigItem(
+ id: 'me',
name: '我的',
nameEn: 'Profile',
icon: 'person_rounded',
isEnabled: true,
- order: 5,
+ order: 8,
),
],
);
diff --git a/lib/features/main_container/presentation/bloc/tab_config_cubit.dart b/lib/features/main_container/presentation/bloc/tab_config_cubit.dart
deleted file mode 100644
index dff65db0..00000000
--- a/lib/features/main_container/presentation/bloc/tab_config_cubit.dart
+++ /dev/null
@@ -1,51 +0,0 @@
-import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:maibu_satabot_v2/features/main_container/domain/tab_config.dart';
-
-class TabConfigState {
- final TabConfig config;
- final int selectedIndex;
-
- const TabConfigState({
- required this.config,
- this.selectedIndex = 0,
- });
-
- TabConfigState copyWith({
- TabConfig? config,
- int? selectedIndex,
- }) {
- return TabConfigState(
- config: config ?? this.config,
- selectedIndex: selectedIndex ?? this.selectedIndex,
- );
- }
-}
-
-class TabConfigCubit extends Cubit {
- TabConfigCubit() : super(TabConfigState(config: TabConfig.defaultConfig()));
-
- /// 切换 Tab 的启用状态
- void toggleTab(String tabId) {
- final newConfig = state.config.toggleItem(tabId);
-
- // 如果当前选中的 Tab 被禁用,自动切换到第一个启用的 Tab
- int newIndex = state.selectedIndex;
- if (newIndex >= newConfig.enabledItems.length) {
- newIndex = 0;
- }
-
- emit(state.copyWith(config: newConfig, selectedIndex: newIndex));
- }
-
- /// 切换选中的 Tab
- void selectTab(int index) {
- if (index >= 0 && index < state.config.enabledItems.length) {
- emit(state.copyWith(selectedIndex: index));
- }
- }
-
- /// 重置为默认配置
- void resetToDefault() {
- emit(TabConfigState(config: TabConfig.defaultConfig()));
- }
-}
diff --git a/lib/features/main_container/presentation/cubit/tab_config_cubit.dart b/lib/features/main_container/presentation/cubit/tab_config_cubit.dart
index 3cc5e00e..83aa0273 100644
--- a/lib/features/main_container/presentation/cubit/tab_config_cubit.dart
+++ b/lib/features/main_container/presentation/cubit/tab_config_cubit.dart
@@ -1,3 +1,4 @@
+import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';
@@ -9,8 +10,9 @@ class TabConfigInitial extends TabConfigState {}
class TabConfigLoaded extends TabConfigState {
final TabConfig config;
+ final int selectedIndex;
- TabConfigLoaded(this.config);
+ TabConfigLoaded(this.config, {this.selectedIndex = 0});
}
class TabConfigCubit extends Cubit {
@@ -22,31 +24,13 @@ class TabConfigCubit extends Cubit {
}
void _loadConfig() {
- try {
- final configJson = _prefs.getString(_configKey);
- if (configJson != null) {
- final List decoded = jsonDecode(configJson);
- final items = decoded.map((e) {
- final map = Map.from(e);
- return TabConfigItem(
- id: map['id'] as String,
- name: map['name'] as String,
- nameEn: map['nameEn'] as String,
- icon: map['icon'] as String,
- isEnabled: map['isEnabled'] as bool,
- order: map['order'] as int,
- );
- }).toList();
- emit(TabConfigLoaded(TabConfig(items: items)));
- } else {
- final defaultConfig = TabConfig.defaultConfig();
- _saveConfig(defaultConfig);
- emit(TabConfigLoaded(defaultConfig));
- }
- } catch (e) {
- final defaultConfig = TabConfig.defaultConfig();
- emit(TabConfigLoaded(defaultConfig));
- }
+ debugPrint('🔍 [TabConfigCubit] _loadConfig 被调用');
+ // 直接使用代码中的默认配置,不持久化
+ final defaultConfig = TabConfig.defaultConfig();
+ debugPrint('🔍 [TabConfigCubit] 默认配置 Tab 数量: ${defaultConfig.items.length}');
+ debugPrint('🔍 [TabConfigCubit] 启用的 Tab: ${defaultConfig.enabledItems.map((e) => e.id).toList()}');
+ emit(TabConfigLoaded(defaultConfig));
+ debugPrint('✅ [TabConfigCubit] 状态已 emit');
}
void _saveConfig(TabConfig config) {
@@ -70,17 +54,25 @@ class TabConfigCubit extends Cubit {
final currentState = state as TabConfigLoaded;
final newConfig = currentState.config.toggleItem(itemId);
- // 只有配置真正改变时才保存和 emit
- if (newConfig != currentState.config) {
- _saveConfig(newConfig);
- emit(TabConfigLoaded(newConfig));
+ // 不保存,直接更新状态
+ emit(TabConfigLoaded(newConfig, selectedIndex: currentState.selectedIndex));
+ }
+ }
+
+ void selectTab(int index) {
+ if (state is TabConfigLoaded) {
+ final currentState = state as TabConfigLoaded;
+ final enabledTabs = currentState.config.enabledItems;
+
+ // 确保索引在有效范围内
+ if (index >= 0 && index < enabledTabs.length) {
+ emit(TabConfigLoaded(currentState.config, selectedIndex: index));
}
}
}
void resetToDefault() {
final defaultConfig = TabConfig.defaultConfig();
- _saveConfig(defaultConfig);
emit(TabConfigLoaded(defaultConfig));
}
diff --git a/lib/features/main_container/presentation/main_wrapper.dart b/lib/features/main_container/presentation/main_wrapper.dart
index 60e2d55f..9ea3572e 100644
--- a/lib/features/main_container/presentation/main_wrapper.dart
+++ b/lib/features/main_container/presentation/main_wrapper.dart
@@ -5,9 +5,14 @@ import 'package:maibu_satabot_v2/features/main_container/domain/tab_config.dart'
import 'package:maibu_satabot_v2/features/home/presentation/pages/home_page.dart';
import 'package:maibu_satabot_v2/features/v2/home/presentation/pages/home_v2_page.dart';
import 'package:maibu_satabot_v2/features/v2/home/presentation/bloc/home_v2_bloc.dart';
+import 'package:maibu_satabot_v2/features/v2/device_list/presentation/pages/device_status_page.dart';
import 'package:maibu_satabot_v2/features/ai/presentation/pages/ai_page.dart';
-import 'package:maibu_satabot_v2/features/waring_center/presentation/pages/warning_center_page.dart';
+import 'package:maibu_satabot_v2/features/v2/waring_center/presentation/pages/alarm_center_page.dart';
+import 'package:maibu_satabot_v2/features/v2/waring_center/presentation/bloc/alarm_cubit.dart';
import 'package:maibu_satabot_v2/features/my/presentation/pages/my_page.dart';
+import 'package:maibu_satabot_v2/features/v2/my/presentation/pages/my_page.dart' as my_v2;
+import 'package:maibu_satabot_v2/features/v2/workorder/presentation/pages/workorder_page.dart';
+import 'package:maibu_satabot_v2/features/v2/report/presentation/pages/report_page.dart';
import 'package:maibu_satabot_v2/core/di/injection.dart';
class MainWrapper extends StatefulWidget {
@@ -20,6 +25,28 @@ class MainWrapper extends StatefulWidget {
class _MainWrapperState extends State {
int _currentIndex = 0;
final PageController _pageController = PageController();
+ final List _tabKeys = [];
+ double? _circleLeft;
+
+ @override
+ void initState() {
+ super.initState();
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ _updateCirclePosition();
+ });
+ }
+
+ void _updateCirclePosition() {
+ if (_tabKeys.isEmpty || _currentIndex >= _tabKeys.length) return;
+
+ final key = _tabKeys[_currentIndex];
+ final renderBox = key.currentContext?.findRenderObject() as RenderBox?;
+ if (renderBox != null) {
+ setState(() {
+ _circleLeft = renderBox.localToGlobal(Offset.zero).dx;
+ });
+ }
+ }
@override
void dispose() {
@@ -27,16 +54,32 @@ class _MainWrapperState extends State {
super.dispose();
}
+ void _onTabChanged(int index) {
+ setState(() {
+ _currentIndex = index;
+ });
+ _pageController.jumpToPage(index);
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ _updateCirclePosition();
+ });
+ }
+
IconData _getIconData(String iconName) {
switch (iconName) {
case 'home_rounded':
return Icons.home_rounded;
case 'grid_view_rounded':
return Icons.grid_view_rounded;
+ case 'devices_rounded':
+ return Icons.devices_rounded;
case 'auto_awesome_rounded':
return Icons.auto_awesome_rounded;
case 'warning_amber_rounded':
return Icons.warning_amber_rounded;
+ case 'assignment_rounded':
+ return Icons.assignment_rounded;
+ case 'post_add_rounded':
+ return Icons.post_add_rounded;
case 'person_rounded':
return Icons.person_rounded;
default:
@@ -44,6 +87,24 @@ class _MainWrapperState extends State {
}
}
+ double _getIndicatorPosition(int itemCount, int currentIndex) {
+ final screenWidth = MediaQuery.of(context).size.width;
+ final itemWidth = screenWidth / itemCount;
+ return currentIndex * itemWidth;
+ }
+
+ double _getCircularLeftPosition(int itemCount, int currentIndex) {
+ final screenWidth = MediaQuery.of(context).size.width;
+ final itemWidth = screenWidth / itemCount;
+ // 圆形应该在每个Tab项的中心
+ final tabCenterX = 12 + (currentIndex * itemWidth) + (itemWidth / 2);
+ final circularLeft = tabCenterX - 22; // 22 = 44/2,让圆形中心对齐Tab中心
+
+ // 边界限制
+ final maxLeft = screenWidth - 12 - 44;
+ return circularLeft.clamp(12.0, maxLeft);
+ }
+
Widget _buildCurrentPage(TabConfigItem tab) {
switch (tab.id) {
case 'home_v2':
@@ -53,12 +114,23 @@ class _MainWrapperState extends State {
);
case 'home':
return const HomePage();
+ case 'device':
+ return const DeviceStatusPage();
case 'ai':
return const AiPage();
case 'warning':
- return const WarningCenterPage();
+ return BlocProvider(
+ create: (context) => sl(),
+ child: const AlarmCenterPage(),
+ );
+ case 'workorder':
+ return const WorkOrderPage();
+ case 'report':
+ return const ReportPage();
case 'my':
return const MyPage();
+ case 'me':
+ return const my_v2.MyPage();
default:
return const HomePage();
}
@@ -87,8 +159,19 @@ class _MainWrapperState extends State {
_currentIndex = 0;
}
+ // 初始化 GlobalKey
+ if (_tabKeys.length != enabledItems.length) {
+ _tabKeys.clear();
+ for (int i = 0; i < enabledItems.length; i++) {
+ _tabKeys.add(GlobalKey());
+ }
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ _updateCirclePosition();
+ });
+ }
+
return Scaffold(
- extendBody: true,
+ extendBody: false,
body: PageView(
controller: _pageController,
physics: const NeverScrollableScrollPhysics(),
@@ -100,57 +183,95 @@ class _MainWrapperState extends State {
children: enabledItems.map((tab) => _buildCurrentPage(tab)).toList(),
),
bottomNavigationBar: Container(
- decoration: BoxDecoration(
+ decoration: const BoxDecoration(
color: Colors.white,
- boxShadow: [
- BoxShadow(
- color: Colors.black.withOpacity(0.08),
- blurRadius: 10,
- offset: const Offset(0, -2),
- ),
- ],
),
child: SafeArea(
top: false,
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceAround,
- children: enabledItems.asMap().entries.map((entry) {
- final index = entry.key;
- final item = entry.value;
- final isSelected = _currentIndex == index;
-
- return Expanded(
- child: InkWell(
- onTap: () {
- setState(() {
- _currentIndex = index;
- });
- _pageController.jumpToPage(index);
- },
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- const SizedBox(height: 8),
- Icon(
- _getIconData(item.icon),
- size: 24,
- color: isSelected ? Colors.blue : Colors.grey,
- ),
- const SizedBox(height: 4),
- Text(
- item.name,
- style: TextStyle(
- fontSize: 11,
- color: isSelected ? Colors.blue : Colors.grey,
- fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
+ child: LayoutBuilder(
+ builder: (context, constraints) {
+ return Container(
+ margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
+ padding: const EdgeInsets.symmetric(vertical: 2),
+ decoration: BoxDecoration(
+ color: Colors.white,
+ borderRadius: BorderRadius.circular(25),
+ border: Border.all(
+ color: const Color(0xFFE5E6EB),
+ width: 1,
+ ),
+ boxShadow: [
+ BoxShadow(
+ color: Colors.black.withOpacity(0.05),
+ blurRadius: 10,
+ offset: const Offset(0, 2),
+ ),
+ ],
+ ),
+ child: Stack(
+ children: [
+ // 滑动圆形背景
+ if (_circleLeft != null)
+ AnimatedPositioned(
+ duration: const Duration(milliseconds: 300),
+ curve: Curves.easeInOut,
+ left: _circleLeft! - 8, // 减去外层 margin + 向右偏移
+ top: 6,
+ child: Container(
+ width: 44,
+ height: 44,
+ decoration: BoxDecoration(
+ color: const Color(0xFFF2F3F5),
+ shape: BoxShape.circle,
+ border: Border.all(
+ color: const Color(0xFFE5E6EB),
+ width: 1,
+ ),
),
),
- const SizedBox(height: 8),
- ],
- ),
+ ),
+ // Tab 项
+ Row(
+ children: enabledItems.asMap().entries.map((entry) {
+ final index = entry.key;
+ final item = entry.value;
+ final isSelected = _currentIndex == index;
+
+ return Expanded(
+ key: _tabKeys[index],
+ child: InkWell(
+ onTap: () => _onTabChanged(index),
+ child: SizedBox(
+ height: 56,
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Icon(
+ _getIconData(item.icon),
+ size: 24,
+ color: isSelected ? const Color(0xFF1D2129) : const Color(0xFF86909C),
+ ),
+ const SizedBox(height: 4),
+ Text(
+ item.name,
+ style: TextStyle(
+ fontSize: 11,
+ color: isSelected ? const Color(0xFF1D2129) : const Color(0xFF86909C),
+ fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }).toList(),
+ ),
+ ],
),
);
- }).toList(),
+ },
),
),
),
diff --git a/lib/features/main_container/presentation/pages/custom_main_container.dart b/lib/features/main_container/presentation/pages/custom_main_container.dart
index 1f3fffcc..bab1105e 100644
--- a/lib/features/main_container/presentation/pages/custom_main_container.dart
+++ b/lib/features/main_container/presentation/pages/custom_main_container.dart
@@ -3,11 +3,14 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:maibu_satabot_v2/features/ai/presentation/pages/ai_page.dart';
import 'package:maibu_satabot_v2/features/home/presentation/pages/home_page.dart';
import 'package:maibu_satabot_v2/features/my/presentation/pages/my_page.dart';
-import 'package:maibu_satabot_v2/features/main_container/presentation/bloc/tab_config_cubit.dart';
+import 'package:maibu_satabot_v2/features/main_container/presentation/cubit/tab_config_cubit.dart';
import 'package:maibu_satabot_v2/features/main_container/presentation/widgets/custom_bottom_nav_bar.dart';
import 'package:maibu_satabot_v2/features/v2/home/presentation/pages/home_v2_page.dart';
+import 'package:maibu_satabot_v2/features/v2/device_list/presentation/pages/device_status_page.dart';
+import 'package:maibu_satabot_v2/features/v2/workorder/presentation/pages/workorder_page.dart';
+import 'package:maibu_satabot_v2/features/v2/report/presentation/pages/report_page.dart';
-import '../../../waring_center/presentation/pages/warning_center_page.dart';
+import '../../../v2/waring_center/presentation/pages/alarm_center_page.dart';
class CustomMainContainer extends StatefulWidget {
const CustomMainContainer({super.key});
@@ -16,28 +19,37 @@ class CustomMainContainer extends StatefulWidget {
State createState() => _CustomMainContainerState();
}
-class _CustomMainContainerState extends State with AutomaticKeepAliveClientMixin {
+class _CustomMainContainerState extends State
+ with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true; // 保持页面状态
@override
Widget build(BuildContext context) {
super.build(context);
-
+
debugPrint('🔍 [CustomMainContainer] build 被调用');
-
+
return BlocBuilder(
builder: (context, state) {
+ if (state is! TabConfigLoaded) {
+ return const Scaffold(
+ body: Center(child: CircularProgressIndicator()),
+ );
+ }
+
final enabledTabs = state.config.enabledItems;
final currentIndex = state.selectedIndex;
-
- debugPrint('🔍 [CustomMainContainer] enabledTabs=${enabledTabs.length}, currentIndex=$currentIndex');
- debugPrint('🔍 [CustomMainContainer] enabledTabs IDs=${enabledTabs.map((e) => e.id).toList()}');
-
+
+ debugPrint(
+ '🔍 [CustomMainContainer] enabledTabs=${enabledTabs.length}, currentIndex=$currentIndex',
+ );
+ debugPrint(
+ '🔍 [CustomMainContainer] enabledTabs IDs=${enabledTabs.map((e) => e.id).toList()}',
+ );
+
if (enabledTabs.isEmpty) {
- return const Scaffold(
- body: Center(child: Text('请至少启用一个Tab')),
- );
+ return const Scaffold(body: Center(child: Text('请至少启用一个Tab')));
}
return Scaffold(
@@ -45,7 +57,21 @@ class _CustomMainContainerState extends State with Automati
index: currentIndex,
children: _buildPages(enabledTabs),
),
- bottomNavigationBar: const CustomBottomNavBar(),
+ bottomNavigationBar: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ // 调试信息:显示当前 Tab ID
+ Container(
+ color: Colors.yellow.withOpacity(0.8),
+ padding: const EdgeInsets.all(4),
+ child: Text(
+ '当前选中Tab索引: $currentIndex, ID: ${enabledTabs[currentIndex].id}',
+ style: const TextStyle(fontSize: 10, color: Colors.black),
+ ),
+ ),
+ const CustomBottomNavBar(),
+ ],
+ ),
);
},
);
@@ -53,19 +79,32 @@ class _CustomMainContainerState extends State with Automati
List _buildPages(List enabledTabs) {
return enabledTabs.map((tab) {
+ debugPrint('🔍 [buildPages] Tab ID: ${tab.id}, Name: ${tab.name}');
switch (tab.id) {
case 'home_v2':
- return const HomeV2Page();
- case 'home':
- return const HomePage();
+ debugPrint('✅ 匹配到 home_v2');
+ return const HomeV2Page(key: ValueKey('home_v2'));
+ case 'device':
+ debugPrint('✅ 匹配到 device');
+ return const DeviceStatusPage(key: ValueKey('device'));
case 'ai':
- return const AiPage();
+ debugPrint('✅ 匹配到 ai');
+ return const AiPage(key: ValueKey('ai'));
case 'warning':
- return const WarningCenterPage();
+ debugPrint('✅ 匹配到 warning');
+ return const AlarmCenterPage(key: ValueKey('warning'));
+ case 'workorder':
+ debugPrint('✅ 匹配到 workorder');
+ return const WorkOrderPage(key: ValueKey('workorder'));
+ case 'report':
+ debugPrint('✅ 匹配到 report');
+ return const ReportPage(key: ValueKey('report'));
case 'my':
- return const MyPage();
+ debugPrint('✅ 匹配到 my');
+ return const MyPage(key: ValueKey('my'));
default:
- return const HomePage();
+ debugPrint('❌ 未匹配到 Tab ID: ${tab.id}');
+ return const SizedBox();
}
}).toList();
}
diff --git a/lib/features/main_container/presentation/pages/tab_settings_page.dart b/lib/features/main_container/presentation/pages/tab_settings_page.dart
index aea75775..3994bc91 100644
--- a/lib/features/main_container/presentation/pages/tab_settings_page.dart
+++ b/lib/features/main_container/presentation/pages/tab_settings_page.dart
@@ -68,10 +68,16 @@ class TabSettingsPage extends StatelessWidget {
return Icons.home_rounded;
case 'grid_view_rounded':
return Icons.grid_view_rounded;
+ case 'devices_rounded':
+ return Icons.devices_rounded;
case 'auto_awesome_rounded':
return Icons.auto_awesome_rounded;
case 'warning_amber_rounded':
return Icons.warning_amber_rounded;
+ case 'assignment_rounded':
+ return Icons.assignment_rounded;
+ case 'post_add_rounded':
+ return Icons.post_add_rounded;
case 'person_rounded':
return Icons.person_rounded;
default:
diff --git a/lib/features/main_container/presentation/widgets/custom_bottom_nav_bar.dart b/lib/features/main_container/presentation/widgets/custom_bottom_nav_bar.dart
index 118f58b5..2000681c 100644
--- a/lib/features/main_container/presentation/widgets/custom_bottom_nav_bar.dart
+++ b/lib/features/main_container/presentation/widgets/custom_bottom_nav_bar.dart
@@ -1,51 +1,162 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:maibu_satabot_v2/features/main_container/presentation/bloc/tab_config_cubit.dart';
-import 'package:cc_ui_kit/cc_ui_kit.dart';
+import 'package:maibu_satabot_v2/features/main_container/presentation/cubit/tab_config_cubit.dart';
-class CustomBottomNavBar extends StatelessWidget {
+class CustomBottomNavBar extends StatefulWidget {
const CustomBottomNavBar({super.key});
+ @override
+ State createState() => _CustomBottomNavBarState();
+}
+
+class _CustomBottomNavBarState extends State
+ with TickerProviderStateMixin {
+ late AnimationController _animationController;
+ late Animation _animation;
+ int _previousIndex = 0;
+
+ @override
+ void initState() {
+ super.initState();
+ _animationController = AnimationController(
+ duration: const Duration(milliseconds: 300),
+ vsync: this,
+ );
+ _animation = Tween(
+ begin: 0.0,
+ end: 0.0,
+ ).animate(
+ CurvedAnimation(
+ parent: _animationController,
+ curve: Curves.easeOutCubic,
+ ),
+ );
+ }
+
+ @override
+ void dispose() {
+ _animationController.dispose();
+ super.dispose();
+ }
+
@override
Widget build(BuildContext context) {
return BlocBuilder(
builder: (context, state) {
+ if (state is! TabConfigLoaded) {
+ return const SizedBox.shrink();
+ }
+
final enabledTabs = state.config.enabledItems;
-
+
if (enabledTabs.isEmpty) {
return const SizedBox.shrink();
}
- return Container(
- decoration: BoxDecoration(
- color: Colors.white,
- boxShadow: [
- BoxShadow(
- color: Colors.black.withOpacity(0.05),
- blurRadius: 10,
- offset: const Offset(0, -2),
- ),
- ],
+ return LayoutBuilder(
+ builder: (context, constraints) {
+ return _buildNavigationBar(
+ constraints: constraints,
+ enabledTabs: enabledTabs,
+ selectedIndex: state.selectedIndex,
+ );
+ },
+ );
+ },
+ );
+ }
+
+ Widget _buildNavigationBar({
+ required BoxConstraints constraints,
+ required List enabledTabs,
+ required int selectedIndex,
+ }) {
+ final tabWidth = constraints.maxWidth / enabledTabs.length;
+ final circleDiameter = 48.0;
+
+ // 当索引改变时,更新动画
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ if (_previousIndex != selectedIndex) {
+ final beginOffset = _previousIndex * tabWidth + tabWidth / 2;
+ final endOffset = selectedIndex * tabWidth + tabWidth / 2;
+
+ _animation = Tween(
+ begin: beginOffset - circleDiameter / 2,
+ end: endOffset - circleDiameter / 2,
+ ).animate(
+ CurvedAnimation(
+ parent: _animationController,
+ curve: Curves.easeOutCubic,
),
- child: SafeArea(
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceAround,
+ );
+
+ _previousIndex = selectedIndex;
+ _animationController.forward(from: 0.0);
+ }
+ });
+
+ return Container(
+ decoration: BoxDecoration(
+ color: Colors.white,
+ borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
+ boxShadow: [
+ BoxShadow(
+ color: Colors.black.withOpacity(0.05),
+ blurRadius: 10,
+ offset: const Offset(0, -2),
+ ),
+ ],
+ ),
+ child: SafeArea(
+ child: Stack(
+ alignment: Alignment.bottomCenter,
+ children: [
+ // 底层:Tab 列表
+ Row(
children: List.generate(enabledTabs.length, (index) {
final tab = enabledTabs[index];
- final isSelected = index == state.selectedIndex;
-
- return _buildNavItem(
- context,
- icon: _getIconData(tab.icon),
- label: tab.name,
- isSelected: isSelected,
- onTap: () => context.read().selectTab(index),
+ final isSelected = index == selectedIndex;
+
+ return Expanded(
+ child: _buildNavItem(
+ context,
+ icon: _getIconData(tab.icon),
+ label: tab.name,
+ isSelected: isSelected,
+ onTap: () => context.read().selectTab(index),
+ ),
);
}),
),
- ),
- );
- },
+ // 上层:动画圆形背景
+ AnimatedBuilder(
+ animation: _animation,
+ builder: (context, child) {
+ return Positioned(
+ bottom: 8,
+ left: _animation.value,
+ child: Container(
+ width: circleDiameter,
+ height: circleDiameter,
+ decoration: BoxDecoration(
+ color: const Color(0xFFF5F5F5),
+ shape: BoxShape.circle,
+ boxShadow: [
+ BoxShadow(
+ color: Colors.black.withOpacity(0.12),
+ blurRadius: 8,
+ spreadRadius: 1,
+ offset: Offset.zero,
+ ),
+ ],
+ ),
+ ),
+ );
+ },
+ ),
+ ],
+ ),
+ ),
);
}
@@ -56,15 +167,18 @@ class CustomBottomNavBar extends StatelessWidget {
required bool isSelected,
required VoidCallback onTap,
}) {
- return Expanded(
- child: InkWell(
- onTap: onTap,
+ return InkWell(
+ onTap: onTap,
+ child: Container(
+ height: 56,
+ alignment: Alignment.center,
child: Column(
mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
icon,
- color: isSelected ? const Color(0xFF2196F3) : Colors.grey,
+ color: isSelected ? Colors.black : const Color(0xFF666666),
size: 24,
),
const SizedBox(height: 4),
@@ -72,8 +186,8 @@ class CustomBottomNavBar extends StatelessWidget {
label,
style: TextStyle(
fontSize: 12,
- color: isSelected ? const Color(0xFF2196F3) : Colors.grey,
- fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
+ color: isSelected ? Colors.black : const Color(0xFF666666),
+ fontWeight: isSelected ? FontWeight.w500 : FontWeight.w400,
),
),
],
@@ -84,12 +198,18 @@ class CustomBottomNavBar extends StatelessWidget {
IconData _getIconData(String iconName) {
switch (iconName) {
+ case 'home_rounded':
+ return Icons.home_rounded;
case 'grid_view_rounded':
return Icons.grid_view_rounded;
+ case 'devices_rounded':
+ return Icons.devices_rounded;
case 'auto_awesome_rounded':
return Icons.auto_awesome_rounded;
case 'warning_amber_rounded':
return Icons.warning_amber_rounded;
+ case 'assignment_rounded':
+ return Icons.assignment_rounded;
case 'person_rounded':
return Icons.person_rounded;
default:
diff --git a/lib/features/v2/device_list/data/data.dart b/lib/features/v2/device_list/data/data.dart
new file mode 100644
index 00000000..a52aefaa
--- /dev/null
+++ b/lib/features/v2/device_list/data/data.dart
@@ -0,0 +1,4 @@
+// 数据层聚合导出
+export 'datasources/datasources.dart';
+export 'models/models.dart';
+export 'repositories/repositories.dart';
diff --git a/lib/features/v2/device_list/data/datasources/datasources.dart b/lib/features/v2/device_list/data/datasources/datasources.dart
new file mode 100644
index 00000000..63bf6290
--- /dev/null
+++ b/lib/features/v2/device_list/data/datasources/datasources.dart
@@ -0,0 +1,2 @@
+// 数据源层
+export 'device_remote_datasource.dart';
\ No newline at end of file
diff --git a/lib/features/v2/device_list/data/datasources/device_remote_datasource.dart b/lib/features/v2/device_list/data/datasources/device_remote_datasource.dart
new file mode 100644
index 00000000..2238d4bc
--- /dev/null
+++ b/lib/features/v2/device_list/data/datasources/device_remote_datasource.dart
@@ -0,0 +1,95 @@
+import '../../domain/entities/device_entity.dart';
+import '../../domain/entities/device_status_entity.dart';
+import '../models/device_data_model.dart';
+import '../models/device_status_data_model.dart';
+
+/// 设备远程数据源抽象接口
+abstract class DeviceRemoteDataSource {
+ Future getDeviceStatus();
+ Future> getDeviceList({String? typeFilter});
+}
+
+/// 设备远程数据源实现类 - 模拟从 API 获取数据
+class DeviceRemoteDataSourceImpl implements DeviceRemoteDataSource {
+ @override
+ Future getDeviceStatus() async {
+ // 模拟网络延迟
+ await Future.delayed(const Duration(seconds: 1));
+
+ // 模拟数据
+ return const DeviceStatusDataModel(
+ total: 136,
+ online: 122,
+ offline: 6,
+ exception: 8,
+ );
+ }
+
+ @override
+ Future> getDeviceList({String? typeFilter}) async {
+ // 模拟网络延迟
+ await Future.delayed(const Duration(milliseconds: 500));
+
+ // 模拟数据 - 根据设计图
+ final allDevices = [
+ const DeviceDataModel(
+ deviceId: '10001',
+ name: '逆变器 INV-001',
+ type: '逆变器',
+ status: '在线',
+ power: 125.8,
+ todayEnergy: 752.6,
+ ),
+ const DeviceDataModel(
+ deviceId: '10002',
+ name: '逆变器 INV-002',
+ type: '逆变器',
+ status: '在线',
+ power: 118.6,
+ todayEnergy: 698.2,
+ ),
+ const DeviceDataModel(
+ deviceId: '10003',
+ name: '汇流箱 SCB-001',
+ type: '汇流箱',
+ status: '在线',
+ power: 0,
+ todayEnergy: 0,
+ current: 12.6,
+ temperature: 36.2,
+ ),
+ const DeviceDataModel(
+ deviceId: '10004',
+ name: '组件组串 STR-001',
+ type: '组件',
+ status: '在线',
+ power: 85.3,
+ todayEnergy: 0,
+ radiation: 823,
+ ),
+ const DeviceDataModel(
+ deviceId: '10005',
+ name: '监控 MON-001',
+ type: '监控',
+ status: '离线',
+ power: 0,
+ todayEnergy: 0,
+ ),
+ const DeviceDataModel(
+ deviceId: '10006',
+ name: '逆变器 INV-003',
+ type: '逆变器',
+ status: '异常',
+ power: 45.2,
+ todayEnergy: 256.8,
+ ),
+ ];
+
+ // 根据类型过滤
+ if (typeFilter != null && typeFilter != '全部') {
+ return allDevices.where((device) => device.type == typeFilter).toList();
+ }
+
+ return allDevices;
+ }
+}
diff --git a/lib/features/v2/device_list/data/models/device_data_model.dart b/lib/features/v2/device_list/data/models/device_data_model.dart
new file mode 100644
index 00000000..f4cfaa8a
--- /dev/null
+++ b/lib/features/v2/device_list/data/models/device_data_model.dart
@@ -0,0 +1,76 @@
+import '../../domain/entities/device_entity.dart';
+
+/// 设备数据模型
+class DeviceDataModel {
+ final String deviceId;
+ final String name;
+ final String type;
+ final String status;
+ final double power;
+ final double todayEnergy;
+ final double? temperature;
+ final double? current;
+ final double? voltage;
+ final double? radiation;
+
+ const DeviceDataModel({
+ required this.deviceId,
+ required this.name,
+ required this.type,
+ required this.status,
+ required this.power,
+ required this.todayEnergy,
+ this.temperature,
+ this.current,
+ this.voltage,
+ this.radiation,
+ });
+
+ /// 从 JSON 创建数据模型
+ factory DeviceDataModel.fromJson(Map json) {
+ return DeviceDataModel(
+ deviceId: json['deviceId'] as String,
+ name: json['name'] as String,
+ type: json['type'] as String,
+ status: json['status'] as String,
+ power: (json['power'] as num).toDouble(),
+ todayEnergy: (json['todayEnergy'] as num).toDouble(),
+ temperature: json['temperature']?.toDouble(),
+ current: json['current']?.toDouble(),
+ voltage: json['voltage']?.toDouble(),
+ radiation: json['radiation']?.toDouble(),
+ );
+ }
+
+ /// 转换为 JSON
+ Map toJson() {
+ return {
+ 'deviceId': deviceId,
+ 'name': name,
+ 'type': type,
+ 'status': status,
+ 'power': power,
+ 'todayEnergy': todayEnergy,
+ 'temperature': temperature,
+ 'current': current,
+ 'voltage': voltage,
+ 'radiation': radiation,
+ };
+ }
+
+ /// 转换为实体
+ DeviceEntity toEntity() {
+ return DeviceEntity(
+ deviceId: deviceId,
+ name: name,
+ type: type,
+ status: status,
+ power: power,
+ todayEnergy: todayEnergy,
+ temperature: temperature,
+ current: current,
+ voltage: voltage,
+ radiation: radiation,
+ );
+ }
+}
diff --git a/lib/features/v2/device_list/data/models/device_status_data_model.dart b/lib/features/v2/device_list/data/models/device_status_data_model.dart
new file mode 100644
index 00000000..6a49498b
--- /dev/null
+++ b/lib/features/v2/device_list/data/models/device_status_data_model.dart
@@ -0,0 +1,46 @@
+import '../../domain/entities/device_status_entity.dart';
+
+/// 设备状态统计数据模型
+class DeviceStatusDataModel {
+ final int total;
+ final int online;
+ final int offline;
+ final int exception;
+
+ const DeviceStatusDataModel({
+ required this.total,
+ required this.online,
+ required this.offline,
+ required this.exception,
+ });
+
+ /// 从 JSON 创建数据模型
+ factory DeviceStatusDataModel.fromJson(Map json) {
+ return DeviceStatusDataModel(
+ total: json['total'] as int,
+ online: json['online'] as int,
+ offline: json['offline'] as int,
+ exception: json['exception'] as int,
+ );
+ }
+
+ /// 转换为 JSON
+ Map toJson() {
+ return {
+ 'total': total,
+ 'online': online,
+ 'offline': offline,
+ 'exception': exception,
+ };
+ }
+
+ /// 转换为实体
+ DeviceStatusEntity toEntity() {
+ return DeviceStatusEntity(
+ total: total,
+ online: online,
+ offline: offline,
+ exception: exception,
+ );
+ }
+}
diff --git a/lib/features/v2/device_list/data/models/models.dart b/lib/features/v2/device_list/data/models/models.dart
new file mode 100644
index 00000000..50c7fc1e
--- /dev/null
+++ b/lib/features/v2/device_list/data/models/models.dart
@@ -0,0 +1,3 @@
+// 数据模型层
+export 'device_data_model.dart';
+export 'device_status_data_model.dart';
diff --git a/lib/features/v2/device_list/data/repositories/device_repository_impl.dart b/lib/features/v2/device_list/data/repositories/device_repository_impl.dart
new file mode 100644
index 00000000..6097a4d4
--- /dev/null
+++ b/lib/features/v2/device_list/data/repositories/device_repository_impl.dart
@@ -0,0 +1,23 @@
+import '../../domain/entities/device_entity.dart';
+import '../../domain/entities/device_status_entity.dart';
+import '../../domain/repositories/device_repository.dart';
+import '../datasources/device_remote_datasource.dart';
+
+/// 设备仓储实现类
+class DeviceRepositoryImpl implements DeviceRepository {
+ final DeviceRemoteDataSource remoteDataSource;
+
+ const DeviceRepositoryImpl({required this.remoteDataSource});
+
+ @override
+ Future getDeviceStatus() async {
+ final dataModel = await remoteDataSource.getDeviceStatus();
+ return dataModel.toEntity();
+ }
+
+ @override
+ Future> getDeviceList({String? typeFilter}) async {
+ final dataModels = await remoteDataSource.getDeviceList(typeFilter: typeFilter);
+ return dataModels.map((model) => model.toEntity()).toList();
+ }
+}
diff --git a/lib/features/v2/device_list/data/repositories/repositories.dart b/lib/features/v2/device_list/data/repositories/repositories.dart
new file mode 100644
index 00000000..0c74205a
--- /dev/null
+++ b/lib/features/v2/device_list/data/repositories/repositories.dart
@@ -0,0 +1,2 @@
+// 仓储实现
+export 'device_repository_impl.dart';
diff --git a/lib/features/v2/device_list/device.dart b/lib/features/v2/device_list/device.dart
new file mode 100644
index 00000000..dafdfb3e
--- /dev/null
+++ b/lib/features/v2/device_list/device.dart
@@ -0,0 +1,6 @@
+// 设备状态模块 - v2 版本
+// 清洁架构实现
+
+export 'presentation/pages/device_status_page.dart';
+export 'presentation/bloc/bloc.dart';
+export 'presentation/widgets/widgets.dart';
diff --git a/lib/features/v2/device_list/domain/domain.dart b/lib/features/v2/device_list/domain/domain.dart
new file mode 100644
index 00000000..caefa312
--- /dev/null
+++ b/lib/features/v2/device_list/domain/domain.dart
@@ -0,0 +1,4 @@
+// 领域层聚合导出
+export 'entities/entities.dart';
+export 'repositories/device_repository.dart';
+export 'usecases/usecases.dart';
diff --git a/lib/features/v2/device_list/domain/entities/device_entity.dart b/lib/features/v2/device_list/domain/entities/device_entity.dart
new file mode 100644
index 00000000..874077f9
--- /dev/null
+++ b/lib/features/v2/device_list/domain/entities/device_entity.dart
@@ -0,0 +1,26 @@
+/// 设备实体类
+class DeviceEntity {
+ final String deviceId;
+ final String name;
+ final String type;
+ final String status;
+ final double power;
+ final double todayEnergy;
+ final double? temperature;
+ final double? current;
+ final double? voltage;
+ final double? radiation;
+
+ const DeviceEntity({
+ required this.deviceId,
+ required this.name,
+ required this.type,
+ required this.status,
+ required this.power,
+ required this.todayEnergy,
+ this.temperature,
+ this.current,
+ this.voltage,
+ this.radiation,
+ });
+}
diff --git a/lib/features/v2/device_list/domain/entities/device_status_entity.dart b/lib/features/v2/device_list/domain/entities/device_status_entity.dart
new file mode 100644
index 00000000..54a659d6
--- /dev/null
+++ b/lib/features/v2/device_list/domain/entities/device_status_entity.dart
@@ -0,0 +1,14 @@
+/// 设备状态统计实体类
+class DeviceStatusEntity {
+ final int total;
+ final int online;
+ final int offline;
+ final int exception;
+
+ const DeviceStatusEntity({
+ required this.total,
+ required this.online,
+ required this.offline,
+ required this.exception,
+ });
+}
diff --git a/lib/features/v2/device_list/domain/entities/entities.dart b/lib/features/v2/device_list/domain/entities/entities.dart
new file mode 100644
index 00000000..94859415
--- /dev/null
+++ b/lib/features/v2/device_list/domain/entities/entities.dart
@@ -0,0 +1,3 @@
+// 实体层
+export 'device_entity.dart';
+export 'device_status_entity.dart';
diff --git a/lib/features/v2/device_list/domain/repositories/device_repository.dart b/lib/features/v2/device_list/domain/repositories/device_repository.dart
new file mode 100644
index 00000000..81879d86
--- /dev/null
+++ b/lib/features/v2/device_list/domain/repositories/device_repository.dart
@@ -0,0 +1,8 @@
+import '../entities/device_entity.dart';
+import '../entities/device_status_entity.dart';
+
+/// 设备仓储抽象接口
+abstract class DeviceRepository {
+ Future getDeviceStatus();
+ Future> getDeviceList({String? typeFilter});
+}
diff --git a/lib/features/v2/device_list/domain/usecases/get_device_status_data_usecase.dart b/lib/features/v2/device_list/domain/usecases/get_device_status_data_usecase.dart
new file mode 100644
index 00000000..bd4aad8f
--- /dev/null
+++ b/lib/features/v2/device_list/domain/usecases/get_device_status_data_usecase.dart
@@ -0,0 +1,28 @@
+import '../entities/device_entity.dart';
+import '../entities/device_status_entity.dart';
+import '../repositories/device_repository.dart';
+
+/// 获取设备状态数据用例
+class GetDeviceStatusDataUseCase {
+ final DeviceRepository repository;
+
+ const GetDeviceStatusDataUseCase({required this.repository});
+
+ Future execute({String? typeFilter}) async {
+ try {
+ final status = await repository.getDeviceStatus();
+ final devices = await repository.getDeviceList(typeFilter: typeFilter);
+ return DeviceStatusResponse(status: status, devices: devices);
+ } catch (e) {
+ throw Exception('获取设备数据失败: ${e.toString()}');
+ }
+ }
+}
+
+/// 设备状态响应类
+class DeviceStatusResponse {
+ final DeviceStatusEntity status;
+ final List devices;
+
+ const DeviceStatusResponse({required this.status, required this.devices});
+}
diff --git a/lib/features/v2/device_list/domain/usecases/usecases.dart b/lib/features/v2/device_list/domain/usecases/usecases.dart
new file mode 100644
index 00000000..aa024d4a
--- /dev/null
+++ b/lib/features/v2/device_list/domain/usecases/usecases.dart
@@ -0,0 +1,2 @@
+// 用例层
+export 'get_device_status_data_usecase.dart';
diff --git a/lib/features/v2/device_list/presentation/bloc/bloc.dart b/lib/features/v2/device_list/presentation/bloc/bloc.dart
new file mode 100644
index 00000000..5444597a
--- /dev/null
+++ b/lib/features/v2/device_list/presentation/bloc/bloc.dart
@@ -0,0 +1,4 @@
+// BLoC 层
+export 'device_status_bloc.dart';
+export 'device_status_event.dart';
+export 'device_status_state.dart';
diff --git a/lib/features/v2/device_list/presentation/bloc/device_status_bloc.dart b/lib/features/v2/device_list/presentation/bloc/device_status_bloc.dart
new file mode 100644
index 00000000..aba6dedc
--- /dev/null
+++ b/lib/features/v2/device_list/presentation/bloc/device_status_bloc.dart
@@ -0,0 +1,85 @@
+import 'package:flutter_bloc/flutter_bloc.dart';
+import '../../domain/usecases/get_device_status_data_usecase.dart';
+import 'device_status_event.dart';
+import 'device_status_state.dart';
+
+class DeviceStatusBloc extends Bloc {
+ final GetDeviceStatusDataUseCase getDeviceStatusDataUseCase;
+
+ DeviceStatusBloc(this.getDeviceStatusDataUseCase)
+ : super(const DeviceStatusInitial()) {
+ on(_onLoadData);
+ on(_onRefresh);
+ on(_onChangeType);
+ on(_onSearch);
+ }
+
+ Future _onLoadData(
+ DeviceStatusLoadData event,
+ Emitter emit,
+ ) async {
+ emit(const DeviceStatusLoading());
+
+ try {
+ final response = await getDeviceStatusDataUseCase.execute();
+
+ emit(DeviceStatusLoaded(
+ deviceStatus: response.status,
+ devices: response.devices,
+ ));
+ } catch (e) {
+ emit(DeviceStatusError(e.toString()));
+ }
+ }
+
+ Future _onRefresh(
+ DeviceStatusRefresh event,
+ Emitter emit,
+ ) async {
+ if (state is DeviceStatusLoaded) {
+ final currentState = state as DeviceStatusLoaded;
+
+ try {
+ final response = await getDeviceStatusDataUseCase.execute(
+ typeFilter: currentState.selectedType == '全部'
+ ? null
+ : currentState.selectedType,
+ );
+
+ emit(currentState.copyWith(
+ deviceStatus: response.status,
+ devices: response.devices,
+ ));
+ } catch (e) {
+ emit(DeviceStatusError(e.toString()));
+ }
+ }
+ }
+
+ void _onChangeType(
+ DeviceStatusChangeType event,
+ Emitter emit,
+ ) {
+ if (state is DeviceStatusLoaded) {
+ final currentState = state as DeviceStatusLoaded;
+
+ // 🔥 直接更新选中类型,不重新请求接口(前端过滤)
+ emit(currentState.copyWith(
+ selectedType: event.type,
+ ));
+ }
+ }
+
+ void _onSearch(
+ DeviceStatusSearch event,
+ Emitter emit,
+ ) {
+ if (state is DeviceStatusLoaded) {
+ final currentState = state as DeviceStatusLoaded;
+
+ emit(currentState.copyWith(
+ searchKeyword: event.keyword,
+ ));
+ }
+ }
+}
diff --git a/lib/features/v2/device_list/presentation/bloc/device_status_event.dart b/lib/features/v2/device_list/presentation/bloc/device_status_event.dart
new file mode 100644
index 00000000..a967c3dd
--- /dev/null
+++ b/lib/features/v2/device_list/presentation/bloc/device_status_event.dart
@@ -0,0 +1,34 @@
+import 'package:equatable/equatable.dart';
+
+abstract class DeviceStatusEvent extends Equatable {
+ const DeviceStatusEvent();
+
+ @override
+ List