and so on

This commit is contained in:
2026-08-18 08:52:33 +08:00
parent 87609c7dc4
commit ab75e8c5b0
56 changed files with 1816 additions and 1146 deletions

View File

@@ -1,52 +1,3 @@
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<BoxShadow> get cardShadow => [
const BoxShadow(
color: Color(0x0D000000),
blurRadius: 4,
offset: Offset(0, 2),
),
];
}
/// 工单模块尺寸常量
class WorkOrderDimensions {
WorkOrderDimensions._();
@@ -71,21 +22,10 @@ class WorkOrderDimensions {
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<BoxShadow> get cardShadow => WorkOrderColors.cardShadow;
}
/// 工单状态枚举

View File

@@ -141,6 +141,7 @@ import '../../features/devices/domain/usecases/recovery_task_usecase.dart';
import '../../features/devices/presentation/bloc/device_task_cubit.dart';
import '../app/app_user_cubit.dart';
import '../localization/locale_cubit.dart';
import '../theme/theme_cubit.dart';
import '../../features/home/presentation/bloc/permission_request_bloc.dart';
import '../network/dio_client.dart';
import '../network/net_message_dispatcher.dart';
@@ -216,7 +217,8 @@ Future<void> init() async {
);
/// 1.5 --- MQTT Data Sources ---
sl.registerFactory<DroneOsdDataSource>(
/// 改为 LazySingleton:列表页预启动订阅和详情页共享同一实例,stream 互通
sl.registerLazySingleton<DroneOsdDataSource>(
() =>
DroneOsdDataSourceImpl(sl<MqttClient>(instanceName: 'droneOsdClient')),
);
@@ -451,6 +453,9 @@ Future<void> init() async {
// 🔥 语言管理 Cubit (单例)
sl.registerLazySingleton(() => LocaleCubit());
// 🔥 主题管理 Cubit (单例,管理亮/暗/跟随系统)
sl.registerLazySingleton(() => ThemeCubit());
// Tab 配置 Cubit (单例)
sl.registerLazySingleton(() => TabConfigCubit(sl<SharedPreferences>()));

View File

@@ -46,6 +46,7 @@ class DroneOsdDataSourceImpl implements DroneOsdDataSource {
}) async {
if (_isDisposed) return;
// 同一设备(SN 相同):只增加引用计数,共享订阅
if (_listenerCount > 0 &&
_deviceSn == deviceSn &&
_gatewaySn == gatewaySn &&
@@ -55,7 +56,11 @@ class DroneOsdDataSourceImpl implements DroneOsdDataSource {
return;
}
// 切换到不同设备:强制清理旧订阅
// 不管引用计数多少,SN 变了就必须先取消旧订阅再订阅新的
if (_deviceSn != null || _gatewaySn != null) {
// 重置引用计数为 1,让 stopListening 能真正取消 MQTT 订阅
_listenerCount = 1;
await stopListening();
}
@@ -113,15 +118,19 @@ class DroneOsdDataSourceImpl implements DroneOsdDataSource {
if (_isDisposed) return;
try {
final jsonData = jsonDecode(message.payload) as Map<String, dynamic>;
final osdData = DroneOsdEntity.fromJson(jsonData);
if (_gatewaySn != null &&
_gatewaySn!.isNotEmpty &&
message.topic.contains(_gatewaySn!)) {
// 注入来源 SN,供下游解析层验证消息归属
jsonData['_sourceSn'] = _gatewaySn;
final osdData = DroneOsdEntity.fromJson(jsonData);
_stationOsdController.add(osdData);
} else if (_deviceSn != null &&
_deviceSn!.isNotEmpty &&
message.topic.contains(_deviceSn!)) {
jsonData['_sourceSn'] = _deviceSn;
final osdData = DroneOsdEntity.fromJson(jsonData);
_droneOsdController.add(osdData);
}
} catch (e) {
@@ -130,8 +139,27 @@ class DroneOsdDataSourceImpl implements DroneOsdDataSource {
}
void dispose() {
if (_isDisposed) return;
_isDisposed = true;
stopListening();
// 强制清理:不受引用计数限制,确保 MQTT 订阅被真正取消
// 避免切换机场时旧订阅残留导致数据串台
_listenerCount = 0;
_subscription?.cancel();
_subscription = null;
if (_deviceSn != null && _deviceSn!.isNotEmpty) {
final deviceSn = _deviceSn!;
mqttClient.unsubscribe('thing/product/$deviceSn/osd');
}
if (_gatewaySn != null && _gatewaySn!.isNotEmpty) {
final gatewaySn = _gatewaySn!;
mqttClient.unsubscribe('thing/product/$gatewaySn/osd');
}
_deviceSn = null;
_gatewaySn = null;
_droneOsdController.close();
_stationOsdController.close();
}

View File

@@ -2,12 +2,159 @@ import 'dart:io';
import 'package:flutter/material.dart';
/// 应用级语义色扩展(浅色/深色各一套,随主题自动切换)
///
/// 用法:`context.appColors.cardBackground`
/// 原理:颜色挂载在 ThemeData.extensions 上,主题切换时组件树自动重建,
/// 页面无需写 `isDark ? A : B` 判断
@immutable
class AppColors extends ThemeExtension<AppColors> {
const AppColors({
required this.pageBackground,
required this.cardBackground,
required this.fillBackground,
required this.textPrimary,
required this.textSecondary,
required this.textTertiary,
required this.divider,
required this.cardShadow,
required this.primary,
required this.danger,
required this.warning,
required this.success,
});
/// 页面背景色
final Color pageBackground;
/// 卡片背景色
final Color cardBackground;
/// 卡片内嵌块填充色(输入框、上传按钮等)
final Color fillBackground;
/// 主要文字色
final Color textPrimary;
/// 次要文字色
final Color textSecondary;
/// 辅助文字色(占位、说明)
final Color textTertiary;
/// 分割线/浅色边框
final Color divider;
/// 卡片阴影色
final Color cardShadow;
/// 品牌主色(两种模式下保持一致)
final Color primary;
/// 危险/错误色(两种模式下保持一致)
final Color danger;
/// 警告色(两种模式下保持一致)
final Color warning;
/// 成功/完成色(两种模式下保持一致)
final Color success;
/// 浅色语义色(取自设计稿)
static const AppColors light = AppColors(
pageBackground: Color(0xFFF5F7FA),
cardBackground: Color(0xFFFFFFFF),
fillBackground: Color(0xFFF8F9FA),
textPrimary: Color(0xFF1D2129),
textSecondary: Color(0xFF4E5969),
textTertiary: Color(0xFF86909C),
divider: Color(0xFFF0F0F0),
cardShadow: Color(0x0D000000),
primary: Color(0xFF165DFF),
danger: Color(0xFFF53F3F),
warning: Color(0xFFFF7D00),
success: Color(0xFF00B42A),
);
/// 深色语义色(与 AppTheme.darkTheme 的 surface 色保持一致)
static const AppColors dark = AppColors(
pageBackground: Color(0xFF1C1C1E),
cardBackground: Color(0xFF2C2C2E),
fillBackground: Color(0xFF3A3A3C),
textPrimary: Color(0xFFF2F3F5),
textSecondary: Color(0xFFC9CDD4),
textTertiary: Color(0xFF86909C),
divider: Color(0xFF3A3A3C),
cardShadow: Color(0x00000000), // 深色下无阴影,靠色差分层级
primary: Color(0xFF165DFF),
danger: Color(0xFFF53F3F),
warning: Color(0xFFFF7D00),
success: Color(0xFF00B42A),
);
@override
AppColors copyWith({
Color? pageBackground,
Color? cardBackground,
Color? fillBackground,
Color? textPrimary,
Color? textSecondary,
Color? textTertiary,
Color? divider,
Color? cardShadow,
Color? primary,
Color? danger,
Color? warning,
Color? success,
}) {
return AppColors(
pageBackground: pageBackground ?? this.pageBackground,
cardBackground: cardBackground ?? this.cardBackground,
fillBackground: fillBackground ?? this.fillBackground,
textPrimary: textPrimary ?? this.textPrimary,
textSecondary: textSecondary ?? this.textSecondary,
textTertiary: textTertiary ?? this.textTertiary,
divider: divider ?? this.divider,
cardShadow: cardShadow ?? this.cardShadow,
primary: primary ?? this.primary,
danger: danger ?? this.danger,
warning: warning ?? this.warning,
success: success ?? this.success,
);
}
@override
AppColors lerp(AppColors? other, double t) {
if (other == null) return this;
return AppColors(
pageBackground: Color.lerp(pageBackground, other.pageBackground, t)!,
cardBackground: Color.lerp(cardBackground, other.cardBackground, t)!,
fillBackground: Color.lerp(fillBackground, other.fillBackground, t)!,
textPrimary: Color.lerp(textPrimary, other.textPrimary, t)!,
textSecondary: Color.lerp(textSecondary, other.textSecondary, t)!,
textTertiary: Color.lerp(textTertiary, other.textTertiary, t)!,
divider: Color.lerp(divider, other.divider, t)!,
cardShadow: Color.lerp(cardShadow, other.cardShadow, t)!,
primary: Color.lerp(primary, other.primary, t)!,
danger: Color.lerp(danger, other.danger, t)!,
warning: Color.lerp(warning, other.warning, t)!,
success: Color.lerp(success, other.success, t)!,
);
}
}
/// 便捷访问扩展:`context.appColors`
extension AppColorsX on BuildContext {
AppColors get appColors => Theme.of(this).extension<AppColors>()!;
}
class AppTheme {
// 基础色值定义
static const Color _pureWhite = Color(0xFFFFFFFF);
static const Color _offWhite = Color(0xFFfafafc); // 稍微带点灰的白,用于背景增加层次感
static const Color _pureBlack = Color(0xFF000000);
static const Color _darkGrey = Color(0xFF1C1C1E); // iOS 风格的深灰黑色
static const Color _surfaceDark = Color(0xFF2C2C2E); // 深色模式下卡片、弹窗表面色
static ThemeData get lightTheme {
return ThemeData(
@@ -15,6 +162,9 @@ class AppTheme {
brightness: Brightness.light,
fontFamily: (Platform.isWindows) ? 'Microsoft YaHei' : null,
// 语义色扩展
extensions: const [AppColors.light],
// 1. 整体背景色
scaffoldBackgroundColor: _offWhite,
@@ -85,4 +235,84 @@ class AppTheme {
),
);
}
/// 深色主题 (与浅色主题对称的黑白简约风格)
static ThemeData get darkTheme {
return ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
fontFamily: (Platform.isWindows) ? 'Microsoft YaHei' : null,
// 语义色扩展
extensions: const [AppColors.dark],
// 1. 整体背景色 (iOS 风格深灰黑)
scaffoldBackgroundColor: _darkGrey,
// 2. 核心颜色方案 (与浅色主题反色:主色白色)
colorScheme: const ColorScheme.dark(
primary: _pureWhite, // 主色设为白色
onPrimary: _pureBlack, // 白色背景上的文字设为黑色
surface: _surfaceDark, // 卡片、弹窗等表面颜色
onSurface: _pureWhite, // 表面上的文字色
),
// 3. 全局按钮主题 (白底黑字)
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: _pureWhite,
foregroundColor: _pureBlack,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 24),
),
),
// 4. 填充按钮主题 (常用语 Material 3)
filledButtonTheme: FilledButtonThemeData(
style: FilledButton.styleFrom(
backgroundColor: _pureWhite,
foregroundColor: _pureBlack,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
// 5. AppBar 主题 (深灰黑背景,白色文字)
appBarTheme: const AppBarTheme(
backgroundColor: _darkGrey,
surfaceTintColor: Colors.transparent,
elevation: 0,
centerTitle: true,
iconTheme: IconThemeData(color: _pureWhite),
titleTextStyle: TextStyle(
color: _pureWhite,
fontSize: 18,
fontWeight: FontWeight.w700,
letterSpacing: -0.5,
),
),
// 6. 输入框主题 (深色填充,白色边框)
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: _surfaceDark,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Colors.white12),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Colors.white12),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: _pureWhite, width: 1.5),
),
),
);
}
}

View File

@@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:shared_preferences/shared_preferences.dart';
/// 全局主题管理 Cubit(仿 LocaleCubit 模式)
/// 支持 light / dark / system 三态,持久化到 SharedPreferences
class ThemeCubit extends Cubit<ThemeMode> {
static const String _themeKey = 'app_theme_mode';
ThemeCubit() : super(ThemeMode.light) {
_loadSavedThemeMode();
}
/// 从本地存储恢复上次保存的主题模式
Future<void> _loadSavedThemeMode() async {
try {
final prefs = await SharedPreferences.getInstance();
final saved = prefs.getString(_themeKey);
emit(_parseThemeMode(saved));
} catch (e) {
emit(ThemeMode.light);
}
}
/// 设置主题模式并持久化
Future<void> setThemeMode(ThemeMode mode) async {
try {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_themeKey, mode.name);
emit(mode);
} catch (e) {
debugPrint('❌ [ThemeCubit] 保存主题设置失败: $e');
}
}
/// 夜间模式开关(Switch 二态切换,不涉及跟随系统)
Future<void> toggleDarkMode(bool enabled) =>
setThemeMode(enabled ? ThemeMode.dark : ThemeMode.light);
/// 当前是否为深色模式
bool get isDarkMode => state == ThemeMode.dark;
/// 将持久化的字符串解析为 ThemeMode
ThemeMode _parseThemeMode(String? saved) {
switch (saved) {
case 'dark':
return ThemeMode.dark;
case 'system':
return ThemeMode.system;
default:
return ThemeMode.light;
}
}
}

View File

@@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:maibu_satabot_v2/core/app/app_user_cubit.dart';
import 'package:maibu_satabot_v2/core/di/injection.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import 'package:maibu_satabot_v2/features/devices/presentation/bloc/devices_cubit.dart';
import '../../../../core/router/route_paths.dart';
@@ -33,9 +34,9 @@ class _LoginPageState extends State<LoginPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
backgroundColor: context.appColors.pageBackground,
// 保持 AppBar 简洁或直接去掉
appBar: AppBar(backgroundColor: Colors.white, elevation: 0),
appBar: AppBar(backgroundColor: context.appColors.pageBackground, elevation: 0),
body: BlocListener<LoginCubit, LoginState>(
listener: (context, state) {
debugPrint('🔍 [LOGIN] 登录状态变化: $state');
@@ -81,7 +82,7 @@ class _LoginPageState extends State<LoginPage> {
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 10,
color: Colors.grey.shade600,
color: context.appColors.textTertiary,
height: 1.3,
),
),
@@ -91,12 +92,12 @@ class _LoginPageState extends State<LoginPage> {
),
const SizedBox(height: 16),
const Text(
Text(
"账号登录v1.1.5",
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: Colors.brown),
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: context.appColors.textPrimary),
),
const SizedBox(height: 8),
const Text("请填写以下信息以验证身份", style: TextStyle(fontSize: 15, color: Colors.grey)),
Text("请填写以下信息以验证身份", style: TextStyle(fontSize: 15, color: context.appColors.textTertiary)),
const SizedBox(height: 24),
// 表单区域
@@ -104,7 +105,8 @@ class _LoginPageState extends State<LoginPage> {
_buildInputLabel("账号"),
TextField(
controller: _userCtrl,
cursorColor: Colors.black,
style: TextStyle(fontSize: 14, color: context.appColors.textPrimary),
cursorColor: context.appColors.textPrimary,
decoration: _inputDecoration(hint: "请输入用户名"),
),
@@ -115,11 +117,12 @@ class _LoginPageState extends State<LoginPage> {
TextField(
controller: _pwdCtrl,
obscureText: _obscurePwd,
cursorColor: Colors.black,
style: TextStyle(fontSize: 14, color: context.appColors.textPrimary),
cursorColor: context.appColors.textPrimary,
decoration: _inputDecoration(
hint: "请输入密码",
suffixIcon: IconButton(
icon: Icon(_obscurePwd ? Icons.visibility_off_outlined : Icons.visibility_outlined, color: Colors.grey, size: 20),
icon: Icon(_obscurePwd ? Icons.visibility_off_outlined : Icons.visibility_outlined, color: context.appColors.textTertiary, size: 20),
onPressed: () => setState(() => _obscurePwd = !_obscurePwd),
),
),
@@ -165,9 +168,9 @@ class _LoginPageState extends State<LoginPage> {
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
Text(
"没有账号吗?",
style: TextStyle(fontSize: 14, color: Colors.grey),
style: TextStyle(fontSize: 14, color: context.appColors.textTertiary),
),
InkWell(
onTap: () {
@@ -182,7 +185,7 @@ class _LoginPageState extends State<LoginPage> {
"立即注册",
style: TextStyle(
fontSize: 14,
color: Colors.black,
color: context.appColors.textPrimary,
fontWeight: FontWeight.bold,
),
),
@@ -218,7 +221,7 @@ class _LoginPageState extends State<LoginPage> {
Widget _buildInputLabel(String label) {
return Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(label, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600)),
child: Text(label, style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: context.appColors.textSecondary)),
);
}
@@ -226,17 +229,17 @@ class _LoginPageState extends State<LoginPage> {
InputDecoration _inputDecoration({required String hint, Widget? suffixIcon}) {
return InputDecoration(
hintText: hint,
hintStyle: TextStyle(color: Colors.grey.shade400, fontSize: 14),
hintStyle: TextStyle(color: context.appColors.textTertiary, fontSize: 14),
filled: true,
fillColor: Colors.grey.shade50,
fillColor: context.appColors.fillBackground,
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: Colors.grey.shade200),
borderSide: BorderSide(color: context.appColors.divider),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Colors.black, width: 1),
borderSide: BorderSide(color: context.appColors.primary, width: 1),
),
suffixIcon: suffixIcon,
);
@@ -252,7 +255,9 @@ class _LoginPageState extends State<LoginPage> {
width: 24,
child: Checkbox(
value: _isAgreed,
activeColor: Colors.black,
activeColor: context.appColors.primary,
checkColor: Colors.white,
side: BorderSide(color: context.appColors.divider),
onChanged: (val) => setState(() => _isAgreed = val!),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
),
@@ -261,9 +266,9 @@ class _LoginPageState extends State<LoginPage> {
Expanded(
child: Wrap(
children: [
const Text("我已阅读并同意 ", style: TextStyle(fontSize: 13, color: Colors.grey)),
Text("我已阅读并同意 ", style: TextStyle(fontSize: 13, color: context.appColors.textTertiary)),
_protocolText("《用户协议》", () => _showProtocolDetail("用户协议")),
const Text(" 和 ", style: TextStyle(fontSize: 13, color: Colors.grey)),
Text(" 和 ", style: TextStyle(fontSize: 13, color: context.appColors.textTertiary)),
_protocolText("《隐私政策》", () => _showProtocolDetail("隐私政策")),
],
),
@@ -277,7 +282,7 @@ class _LoginPageState extends State<LoginPage> {
onTap: onTap,
child: Text(
text,
style: const TextStyle(fontSize: 13, color: Colors.black, fontWeight: FontWeight.bold),
style: TextStyle(fontSize: 13, color: context.appColors.textPrimary, fontWeight: FontWeight.bold),
),
);
}
@@ -286,18 +291,18 @@ class _LoginPageState extends State<LoginPage> {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.white,
backgroundColor: context.appColors.cardBackground,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))),
builder: (context) => Container(
padding: const EdgeInsets.all(24),
height: MediaQuery.of(context).size.height * 0.7,
child: Column(
children: [
Text(title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const Divider(height: 32),
Text(title, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: context.appColors.textPrimary)),
Divider(height: 32, color: context.appColors.divider),
Expanded(
child: SingleChildScrollView(
child: Text("此处放置您的${title}详细内容...\n" * 20, style: const TextStyle(color: Colors.black87, height: 1.5)),
child: Text("此处放置您的${title}详细内容...\n" * 20, style: TextStyle(color: context.appColors.textSecondary, height: 1.5)),
),
),
const SizedBox(height: 16),

View File

@@ -2,6 +2,7 @@ import 'package:cc_ui_kit/cc_ui_kit.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:maibu_satabot_v2/core/router/route_paths.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
class RegisterPage extends StatefulWidget {
const RegisterPage({super.key});
@@ -29,12 +30,12 @@ class _RegisterPageState extends State<RegisterPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
backgroundColor: context.appColors.pageBackground,
appBar: AppBar(
backgroundColor: Colors.white,
backgroundColor: context.appColors.pageBackground,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.black),
icon: Icon(Icons.arrow_back, color: context.appColors.textPrimary),
onPressed: () => context.go(RoutePaths.login),
),
),
@@ -44,25 +45,26 @@ class _RegisterPageState extends State<RegisterPage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 40),
const Text(
Text(
"账号注册",
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.black,
color: context.appColors.textPrimary,
),
),
const SizedBox(height: 12),
const Text(
Text(
"请填写以下信息完成注册",
style: TextStyle(fontSize: 15, color: Colors.grey),
style: TextStyle(fontSize: 15, color: context.appColors.textTertiary),
),
const SizedBox(height: 40),
_buildInputLabel("用户名"),
TextField(
controller: _userCtrl,
cursorColor: Colors.black,
style: TextStyle(fontSize: 14, color: context.appColors.textPrimary),
cursorColor: context.appColors.textPrimary,
decoration: _inputDecoration(hint: "请设置用户名"),
),
@@ -72,7 +74,8 @@ class _RegisterPageState extends State<RegisterPage> {
TextField(
controller: _pwdCtrl,
obscureText: _obscurePwd,
cursorColor: Colors.black,
style: TextStyle(fontSize: 14, color: context.appColors.textPrimary),
cursorColor: context.appColors.textPrimary,
decoration: _inputDecoration(
hint: "请设置密码",
suffixIcon: IconButton(
@@ -80,7 +83,7 @@ class _RegisterPageState extends State<RegisterPage> {
_obscurePwd
? Icons.visibility_off_outlined
: Icons.visibility_outlined,
color: Colors.grey,
color: context.appColors.textTertiary,
size: 20,
),
onPressed: () => setState(() => _obscurePwd = !_obscurePwd),
@@ -94,7 +97,8 @@ class _RegisterPageState extends State<RegisterPage> {
TextField(
controller: _confirmPwdCtrl,
obscureText: _obscureConfirmPwd,
cursorColor: Colors.black,
style: TextStyle(fontSize: 14, color: context.appColors.textPrimary),
cursorColor: context.appColors.textPrimary,
decoration: _inputDecoration(
hint: "请再次输入密码",
suffixIcon: IconButton(
@@ -102,7 +106,7 @@ class _RegisterPageState extends State<RegisterPage> {
_obscureConfirmPwd
? Icons.visibility_off_outlined
: Icons.visibility_outlined,
color: Colors.grey,
color: context.appColors.textTertiary,
size: 20,
),
onPressed: () => setState(
@@ -132,17 +136,17 @@ class _RegisterPageState extends State<RegisterPage> {
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
Text(
"已有账号?",
style: TextStyle(fontSize: 14, color: Colors.grey),
style: TextStyle(fontSize: 14, color: context.appColors.textTertiary),
),
GestureDetector(
onTap: () => context.go(RoutePaths.login),
child: const Text(
child: Text(
"立即登录",
style: TextStyle(
fontSize: 14,
color: Colors.black,
color: context.appColors.textPrimary,
fontWeight: FontWeight.bold,
),
),
@@ -183,9 +187,10 @@ class _RegisterPageState extends State<RegisterPage> {
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(
label,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: context.appColors.textSecondary,
),
),
);
@@ -194,17 +199,17 @@ class _RegisterPageState extends State<RegisterPage> {
InputDecoration _inputDecoration({required String hint, Widget? suffixIcon}) {
return InputDecoration(
hintText: hint,
hintStyle: TextStyle(color: Colors.grey.shade400, fontSize: 14),
hintStyle: TextStyle(color: context.appColors.textTertiary, fontSize: 14),
filled: true,
fillColor: Colors.grey.shade50,
fillColor: context.appColors.fillBackground,
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: Colors.grey.shade200),
borderSide: BorderSide(color: context.appColors.divider),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Colors.black, width: 1),
borderSide: BorderSide(color: context.appColors.primary, width: 1),
),
suffixIcon: suffixIcon,
);
@@ -219,7 +224,9 @@ class _RegisterPageState extends State<RegisterPage> {
width: 24,
child: Checkbox(
value: _isAgreed,
activeColor: Colors.black,
activeColor: context.appColors.primary,
checkColor: Colors.white,
side: BorderSide(color: context.appColors.divider),
onChanged: (val) => setState(() => _isAgreed = val!),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
@@ -230,12 +237,12 @@ class _RegisterPageState extends State<RegisterPage> {
Expanded(
child: Wrap(
children: [
const Text(
Text(
"我已阅读并同意 ",
style: TextStyle(fontSize: 13, color: Colors.grey),
style: TextStyle(fontSize: 13, color: context.appColors.textTertiary),
),
_protocolText("《用户协议》", () => _showProtocolDetail("用户协议")),
const Text(" 和 ", style: TextStyle(fontSize: 13, color: Colors.grey)),
Text(" 和 ", style: TextStyle(fontSize: 13, color: context.appColors.textTertiary)),
_protocolText("《隐私政策》", () => _showProtocolDetail("隐私政策")),
],
),
@@ -249,9 +256,9 @@ class _RegisterPageState extends State<RegisterPage> {
onTap: onTap,
child: Text(
text,
style: const TextStyle(
style: TextStyle(
fontSize: 13,
color: Colors.black,
color: context.appColors.textPrimary,
fontWeight: FontWeight.bold,
),
),
@@ -262,7 +269,7 @@ class _RegisterPageState extends State<RegisterPage> {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.white,
backgroundColor: context.appColors.cardBackground,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
@@ -273,14 +280,14 @@ class _RegisterPageState extends State<RegisterPage> {
children: [
Text(
title,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: context.appColors.textPrimary),
),
const Divider(height: 32),
Divider(height: 32, color: context.appColors.divider),
Expanded(
child: SingleChildScrollView(
child: Text(
"此处放置您的${title}详细内容...\n" * 20,
style: const TextStyle(color: Colors.black87, height: 1.5),
style: TextStyle(color: context.appColors.textSecondary, height: 1.5),
),
),
),

View File

@@ -259,7 +259,8 @@ class PathRepositoryImpl implements PathRepository {
return records;
}
/// 鏍规嵁鍦虹珯ID鏌ヨ<E98F8C>宸ヤ綔璁板綍鍒楄〃锛圶ML鏍煎紡锛?
/// 鏍规嵁鍦虹珯ID鏌ヨ<E98F8C>宸ヤ綔璁板綍鍒楄
/// 〃锛圶ML鏍煎紡锛?
@override
Future<List<WorkRecordEntity>> getWorkRecordsBySiteId({
required int siteId,

View File

@@ -18,6 +18,7 @@ import 'package:maibu_satabot_v2/features/v2/report/presentation/pages/report_pa
import 'package:maibu_satabot_v2/features/v2/device_list/presentation/float_bar/view/float_bar_widget.dart';
import 'package:maibu_satabot_v2/features/v2/device_list/presentation/float_bar/cubit/float_bar_setting_cubit.dart';
import 'package:maibu_satabot_v2/core/di/injection.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
class MainWrapper extends StatefulWidget {
const MainWrapper({super.key});
@@ -205,7 +206,9 @@ class _MainWrapperState extends State<MainWrapper> {
],
),
bottomNavigationBar: Container(
decoration: const BoxDecoration(color: Colors.white),
decoration: BoxDecoration(
color: context.appColors.cardBackground,
),
child: SafeArea(
top: false,
child: LayoutBuilder(
@@ -217,15 +220,15 @@ class _MainWrapperState extends State<MainWrapper> {
),
padding: const EdgeInsets.symmetric(vertical: 2),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(25),
border: Border.all(
color: const Color(0xFFE5E6EB),
color: context.appColors.divider,
width: 1,
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
color: context.appColors.cardShadow,
blurRadius: 10,
offset: const Offset(0, 2),
),
@@ -244,10 +247,10 @@ class _MainWrapperState extends State<MainWrapper> {
width: 44,
height: 44,
decoration: BoxDecoration(
color: const Color(0xFFF2F3F5),
color: context.appColors.divider,
shape: BoxShape.circle,
border: Border.all(
color: const Color(0xFFE5E6EB),
color: context.appColors.divider,
width: 1,
),
),
@@ -274,8 +277,8 @@ class _MainWrapperState extends State<MainWrapper> {
_getIconData(item.icon),
size: 24,
color: isSelected
? const Color(0xFF1D2129)
: const Color(0xFF86909C),
? context.appColors.textPrimary
: context.appColors.textTertiary,
),
const SizedBox(height: 4),
Text(
@@ -283,8 +286,8 @@ class _MainWrapperState extends State<MainWrapper> {
style: TextStyle(
fontSize: 11,
color: isSelected
? const Color(0xFF1D2129)
: const Color(0xFF86909C),
? context.appColors.textPrimary
: context.appColors.textTertiary,
fontWeight: isSelected
? FontWeight.w600
: FontWeight.w400,

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import 'package:maibu_satabot_v2/features/main_container/presentation/cubit/tab_config_cubit.dart';
class CustomBottomNavBar extends StatefulWidget {
@@ -92,11 +93,11 @@ class _CustomBottomNavBarState extends State<CustomBottomNavBar>
return Container(
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
color: context.appColors.cardShadow,
blurRadius: 10,
offset: const Offset(0, -2),
),
@@ -135,11 +136,11 @@ class _CustomBottomNavBarState extends State<CustomBottomNavBar>
width: circleDiameter,
height: circleDiameter,
decoration: BoxDecoration(
color: const Color(0xFFF5F5F5),
color: context.appColors.divider,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.12),
color: context.appColors.cardShadow,
blurRadius: 8,
spreadRadius: 1,
offset: Offset.zero,
@@ -174,7 +175,9 @@ class _CustomBottomNavBarState extends State<CustomBottomNavBar>
children: [
Icon(
icon,
color: isSelected ? Colors.black : const Color(0xFF666666),
color: isSelected
? context.appColors.textPrimary
: context.appColors.textSecondary,
size: 24,
),
const SizedBox(height: 4),
@@ -182,7 +185,9 @@ class _CustomBottomNavBarState extends State<CustomBottomNavBar>
label,
style: TextStyle(
fontSize: 12,
color: isSelected ? Colors.black : const Color(0xFF666666),
color: isSelected
? context.appColors.textPrimary
: context.appColors.textSecondary,
fontWeight: isSelected ? FontWeight.w500 : FontWeight.w400,
),
),

View File

@@ -28,6 +28,7 @@ import '../../domain/entities/drone_station_entity.dart'; // 🔥 添加 DroneSt
import 'robot_list_page.dart';
import 'robot_control_page.dart';
import 'drone_station_detail_page.dart';
import '../../../../../core/network/mqtt/data/datasources/drone_osd_datasource.dart';
import 'qr_scanner_page.dart';
import 'bind_device_page.dart';
import '../../../../remote_control/presentation/bloc/remote_control_cubit.dart';
@@ -756,6 +757,12 @@ class _DeviceStatusViewState extends State<DeviceStatusView> {
return DroneStationItemCard(
station: station,
onTap: () {
// 预启动 OSD 订阅:进入详情页前就让 MQTT 数据流跑起来
// 这样详情页一进来就能从 stream 读到数据,无需等待首帧推送
sl<DroneOsdDataSource>().startListening(
deviceSn: station.deviceSn,
gatewaySn: station.gatewaySn,
);
Navigator.push(
context,
MaterialPageRoute(
@@ -936,7 +943,8 @@ class _DeviceStatusViewState extends State<DeviceStatusView> {
if (result != null && mounted) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => BindDevicePage(scanResult: result),
builder: (_) =>
BindDevicePage(scanResult: result),
),
);
}
@@ -1129,6 +1137,11 @@ class _DeviceStatusViewState extends State<DeviceStatusView> {
}
if (matchedStation != null) {
// 预启动 OSD 订阅:进入详情页前就让 MQTT 数据流跑起来
sl<DroneOsdDataSource>().startListening(
deviceSn: matchedStation.deviceSn,
gatewaySn: matchedStation.gatewaySn,
);
Navigator.push(
context,
MaterialPageRoute(

View File

@@ -37,18 +37,22 @@ class _DroneStationDetailPageState extends State<DroneStationDetailPage> {
late DroneOsdDataSource _osdDataSource;
StreamSubscription<DroneOsdEntity>? _stationOsdSubscription;
/// 静态缓存:跨页面实例保留上次的 host 数据
static final Map<String, dynamic> _cachedHostData = {};
/// 静态缓存:按 gatewaySn 分区,跨页面实例保留每个机场上次的 host 数据
/// 避免进入新机场时显示上一个机场的缓存数据
static final Map<String, Map<String, dynamic>> _cachedHostDataBySn = {};
final ValueNotifier<Map<String, dynamic>> _stationHostData =
ValueNotifier<Map<String, dynamic>>(
// 初始化时从缓存加载,进入页面立即显示
Map<String, dynamic>.from(_cachedHostData),
);
ValueNotifier<Map<String, dynamic>>({});
@override
void initState() {
super.initState();
// 进入页面时加载当前机场的缓存数据(切回时立即显示)
final cached = _cachedHostDataBySn[widget.station.gatewaySn];
if (cached != null && cached.isNotEmpty) {
_stationHostData.value = Map<String, dynamic>.from(cached);
}
_bloc = sl<DroneStationBloc>();
_bloc.add(
UAVDetailLoad(
@@ -64,8 +68,10 @@ class _DroneStationDetailPageState extends State<DroneStationDetailPage> {
}
void _startStationOsdListening() {
// 使用 station 自带的 deviceSn + gatewaySn,与列表页预订阅参数一致
// 避免因参数不同导致 DataSource 重新订阅、打断数据流
_osdDataSource.startListening(
deviceSn: '',
deviceSn: widget.station.deviceSn,
gatewaySn: widget.station.gatewaySn,
);
@@ -76,18 +82,32 @@ class _DroneStationDetailPageState extends State<DroneStationDetailPage> {
}
void _parseAndUpdateHostData(DroneOsdEntity osd) {
// 防护:验证消息来源 SN 是否匹配当前机场,避免串台
final sourceSn = osd.rawData['_sourceSn'];
if (sourceSn != null && sourceSn != widget.station.gatewaySn) {
debugPrint(
'⚠️ [DetailPage] 丢弃非当前机场 OSD: source=$sourceSn, current=${widget.station.gatewaySn}',
);
return;
}
final data = osd.rawData;
final hostData = data['data'] is Map ? (data['data'] as Map)['host'] : null;
if (hostData == null || hostData is! Map) return;
// 🔥 基于已有数据合并,只更新非 null 字段,避免 Type 2 消息覆盖已有值
final Map<String, dynamic> merged = Map<String, dynamic>.from(_stationHostData.value);
final Map<String, dynamic> merged = Map<String, dynamic>.from(
_stationHostData.value,
);
void _put(String key, dynamic value) {
if (value != null) merged[key] = value;
}
_put('environment_temperature', (hostData['environment_temperature'] as num?)?.toDouble());
_put(
'environment_temperature',
(hostData['environment_temperature'] as num?)?.toDouble(),
);
_put('humidity', (hostData['humidity'] as num?)?.toDouble());
_put('wind_speed', (hostData['wind_speed'] as num?)?.toDouble());
_put('rainfall', hostData['rainfall']?.toString());
@@ -95,7 +115,10 @@ class _DroneStationDetailPageState extends State<DroneStationDetailPage> {
_put('drone_in_dock', hostData['drone_in_dock']?.toString());
_put('temperature', (hostData['temperature'] as num?)?.toDouble());
_put('putter_state', hostData['putter_state']?.toString());
_put('supplement_light_state', hostData['supplement_light_state']?.toString());
_put(
'supplement_light_state',
hostData['supplement_light_state']?.toString(),
);
_put('alarm_state', hostData['alarm_state']?.toString());
_put('emergency_stop_state', hostData['emergency_stop_state']?.toString());
_put('silent_mode', hostData['silent_mode']?.toString());
@@ -104,28 +127,42 @@ class _DroneStationDetailPageState extends State<DroneStationDetailPage> {
_put('height', (hostData['height'] as num?)?.toDouble());
_put('latitude', (hostData['latitude'] as num?)?.toDouble());
_put('longitude', (hostData['longitude'] as num?)?.toDouble());
_put('home_position_is_valid', hostData['home_position_is_valid']?.toString());
_put(
'home_position_is_valid',
hostData['home_position_is_valid']?.toString(),
);
_put('battery_store_mode', hostData['battery_store_mode']?.toString());
_put('first_power_on', hostData['first_power_on']?.toString());
_put('drone_charge_state', hostData['drone_charge_state']);
_put('air_conditioner', hostData['air_conditioner']);
_put('network_state', hostData['network_state']);
_put('position_state', hostData['position_state']);
_put('storage', hostData['storage']);
_put('sub_device', hostData['sub_device']);
_put('alternate_land_point', hostData['alternate_land_point']);
if (hostData['air_conditioner'] is Map) {
final ac = hostData['air_conditioner'] as Map;
_put('air_conditioner_state', ac['air_conditioner_state']?.toString());
_put('air_conditioner_switch_time', ac['switch_time']?.toString());
// 🔥 优先处理空调信息(确保第一时间解析,避免与其他卡片显示不同步)
final acData = hostData['air_conditioner'];
if (acData is Map) {
_put('air_conditioner', acData);
// 解析空调开关状态
final acStateVal = int.tryParse(acData['air_conditioner_state']?.toString() ?? '') ?? -1;
if (acStateVal == 0 || acStateVal == 1) {
_put('air_conditioner_state', acStateVal.toString());
}
// 解析空调运行时间
final switchTime = acData['switch_time'];
if (switchTime != null) {
_put('air_conditioner_switch_time', '$switchTime');
}
}
_stationHostData.value = merged;
// 同步写入静态缓存
_cachedHostData
..clear()
..addAll(merged);
// 同步写入按 gatewaySn 分区的静态缓存
_cachedHostDataBySn[widget.station.gatewaySn] = Map<String, dynamic>.from(
merged,
);
}
/// 🔥 页面重新激活时调用(从其他页面返回时)
@@ -161,6 +198,9 @@ class _DroneStationDetailPageState extends State<DroneStationDetailPage> {
@override
void dispose() {
_stationOsdSubscription?.cancel();
// 只调用 stopListening 减少引用计数,不调用 dispose()
// 因为 DroneOsdDataSource 是 LazySingleton,被列表页和详情页共享
// 详情页退出后列表页可能仍需要该订阅
_osdDataSource.stopListening();
_stationHostData.dispose();
_bloc.close();
@@ -291,11 +331,14 @@ class _DroneStationDetailPageState extends State<DroneStationDetailPage> {
Widget _buildContent(UAVDetailEntity detail) {
_detail = detail;
_droneSn = detail.deviceSn;
final newDroneSn = detail.deviceSn;
if (_droneSn != null && _droneSn!.isNotEmpty) {
// 只在 droneSn 真正变化时才重新订阅,避免 Bloc 刷新时重复 startListening
// 导致引用计数虚增、dispose 时无法真正取消订阅
if (newDroneSn != null && newDroneSn.isNotEmpty && newDroneSn != _droneSn) {
_droneSn = newDroneSn;
_osdDataSource.startListening(
deviceSn: _droneSn!,
deviceSn: newDroneSn,
gatewaySn: widget.station.gatewaySn,
);
}
@@ -499,13 +542,18 @@ class _DroneStationDetailPageState extends State<DroneStationDetailPage> {
String acStr = '未知';
Color acColor = const Color(0xFF86909C);
if (acState != null) {
final state = int.tryParse(acState) ?? -1;
if (state == 1) {
// 🔥 增强兼容性:尝试多种数据格式(int、string等)
final acValue = acState is int ? acState : int.tryParse(acState.toString()) ?? -1;
if (acValue == 1) {
acStr = '开启';
acColor = const Color(0xFF00B42A);
} else if (state == 0) {
} else if (acValue == 0) {
acStr = '关闭';
acColor = const Color(0xFF86909C);
} else if (acValue == 2) {
// 部分设备使用 2 表示待机或其他状态
acStr = '待机';
acColor = const Color(0xFFFF7D00);
}
}

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
/// 底部导航栏组件
class DeviceBottomNavBar extends StatelessWidget {
@@ -15,9 +16,9 @@ class DeviceBottomNavBar extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
border: Border(
top: BorderSide(color: const Color(0xFFF2F3F5), width: 0.5),
top: BorderSide(color: context.appColors.divider, width: 0.5),
),
),
child: SafeArea(
@@ -26,30 +27,35 @@ class DeviceBottomNavBar extends StatelessWidget {
child: Row(
children: [
_buildNavItem(
context,
icon: Icons.home_outlined,
label: '首页',
isSelected: currentIndex == 0,
onTap: () => onTap(0),
),
_buildNavItem(
context,
icon: Icons.devices,
label: '设备',
isSelected: currentIndex == 1,
onTap: () => onTap(1),
),
_buildNavItem(
context,
icon: Icons.notifications_outlined,
label: '告警',
isSelected: currentIndex == 2,
onTap: () => onTap(2),
),
_buildNavItem(
context,
icon: Icons.assignment_outlined,
label: '工单',
isSelected: currentIndex == 3,
onTap: () => onTap(3),
),
_buildNavItem(
context,
icon: Icons.person_outlined,
label: '我的',
isSelected: currentIndex == 4,
@@ -62,7 +68,8 @@ class DeviceBottomNavBar extends StatelessWidget {
);
}
Widget _buildNavItem({
Widget _buildNavItem(
BuildContext context, {
required IconData icon,
required String label,
required bool isSelected,
@@ -78,7 +85,7 @@ class DeviceBottomNavBar extends StatelessWidget {
icon,
color: isSelected
? const Color(0xFF004CFD)
: const Color(0xFF86909C),
: context.appColors.textTertiary,
size: 24,
),
const SizedBox(height: 2),
@@ -88,7 +95,7 @@ class DeviceBottomNavBar extends StatelessWidget {
fontSize: 11,
color: isSelected
? const Color(0xFF004FFF)
: const Color(0xFF86909C),
: context.appColors.textTertiary,
fontWeight: isSelected ? FontWeight.w500 : FontWeight.normal,
),
),

View File

@@ -166,10 +166,29 @@ class _DroneOsdCardState extends State<DroneOsdCard> {
oldWidget.gatewaySn != widget.gatewaySn ||
oldWidget.isDroneOnline != widget.isDroneOnline) {
_subscription?.cancel();
// 切换机场/无人机:重置状态,避免显示上一个机场的旧数据
_resetStateForNewDevice();
_subscribeStream();
}
}
/// 切换到新机场/无人机时重置所有状态
void _resetStateForNewDevice() {
_hasReceivedData = false;
_isWaitingForPush = droneTaskStateManager.isWaitingForOsdPush.value;
_localWaitTimeoutTimer?.cancel();
if (_isWaitingForPush) {
_startLocalWaitTimeout();
}
// 重置所有字段为"未知"
for (final def in _fieldDefs) {
_valueNotifiers[def.key]?.value = '未知';
_colorNotifiers[def.key]?.value = def.defaultColor;
}
// 触发一次重建以切换回离线/等待卡片
if (mounted) setState(() {});
}
@override
void dispose() {
droneTaskStateManager.isWaitingForOsdPush.removeListener(_onWaitingChanged);
@@ -208,6 +227,12 @@ class _DroneOsdCardState extends State<DroneOsdCard> {
}
void _parseOsdFields(DroneOsdEntity osd) {
// 防护:验证消息来源 SN 是否匹配当前无人机,避免串台
final sourceSn = osd.rawData['_sourceSn'];
if (sourceSn != null && sourceSn != widget.deviceSn) {
return;
}
final data = osd.rawData;
final dataMap = data['data'] is Map ? data['data'] as Map : null;
Map? droneData;

View File

@@ -182,9 +182,9 @@ class _DroneStationOsdCardState extends State<DroneStationOsdCard> {
final Map<String, ValueNotifier<String>> _valueNotifiers = {};
final Map<String, ValueNotifier<Color>> _colorNotifiers = {};
/// 静态缓存:跨页面实例保留上次收到的值,进入页面立即显示
static final Map<String, String> _cachedValues = {};
static final Map<String, Color> _cachedColors = {};
/// 静态缓存:按 gatewaySn 分区,每个机场独立缓存
static final Map<String, Map<String, String>> _cachedValuesBySn = {};
static final Map<String, Map<String, Color>> _cachedColorsBySn = {};
/// 是否已收到过有效数据(用于首次标记)
bool _hasReceivedData = false;
@@ -197,17 +197,18 @@ class _DroneStationOsdCardState extends State<DroneStationOsdCard> {
}
void _initNotifiers() {
final sn = widget.gatewaySn;
final cachedVals = _cachedValuesBySn[sn] ?? {};
final cachedCols = _cachedColorsBySn[sn] ?? {};
for (final def in _fieldDefs) {
// 优先用缓存值,没有缓存则显示'未知'
_valueNotifiers[def.key] = ValueNotifier(
_cachedValues[def.key] ?? '未知',
);
// 优先用该机场的缓存值,没有缓存则显示'未知'
_valueNotifiers[def.key] = ValueNotifier(cachedVals[def.key] ?? '未知');
_colorNotifiers[def.key] = ValueNotifier(
_cachedColors[def.key] ?? def.defaultColor,
cachedCols[def.key] ?? def.defaultColor,
);
}
// 如果有缓存数据,标记为已收到
if (_cachedValues.isNotEmpty) {
// 如果该机场有缓存数据,标记为已收到
if (cachedVals.isNotEmpty) {
_hasReceivedData = true;
}
}
@@ -218,10 +219,39 @@ class _DroneStationOsdCardState extends State<DroneStationOsdCard> {
if (oldWidget.gatewaySn != widget.gatewaySn ||
oldWidget.isOnline != widget.isOnline) {
_subscription?.cancel();
// 切换机场:重置状态,立即清空旧机场数据
_resetStateForNewDevice();
_subscribeStream();
}
}
/// 切换到新机场时重置所有状态
/// 优先从静态缓存加载该机场的历史数据(实现切回时立即显示)
/// 无缓存则重置为"未知",避免显示上一个机场的旧数据
void _resetStateForNewDevice() {
final sn = widget.gatewaySn;
final cachedVals = _cachedValuesBySn[sn] ?? {};
final cachedCols = _cachedColorsBySn[sn] ?? {};
_hasReceivedData = cachedVals.isNotEmpty;
for (final def in _fieldDefs) {
final newVal = cachedVals[def.key] ?? '未知';
final newCol = cachedCols[def.key] ?? def.defaultColor;
final vNotifier = _valueNotifiers[def.key];
if (vNotifier != null && vNotifier.value != newVal) {
vNotifier.value = newVal;
}
final cNotifier = _colorNotifiers[def.key];
if (cNotifier != null && cNotifier.value != newCol) {
cNotifier.value = newCol;
}
}
// 触发一次重建以切换离线/数据卡片显示
if (mounted) setState(() {});
}
@override
void dispose() {
_subscription?.cancel();
@@ -246,10 +276,11 @@ class _DroneStationOsdCardState extends State<DroneStationOsdCard> {
/// 更新单个字段的值和颜色(仅当值变化时才触发 ValueNotifier 重建)
/// 同时写入静态缓存,下次进入页面可立即显示
void _updateField(String key, String value, [Color? color]) {
// 写入缓存
_cachedValues[key] = value;
// 写入该机场的缓存
final sn = widget.gatewaySn;
_cachedValuesBySn.putIfAbsent(sn, () => {})[key] = value;
if (color != null) {
_cachedColors[key] = color;
_cachedColorsBySn.putIfAbsent(sn, () => {})[key] = color;
}
final vNotifier = _valueNotifiers[key];
if (vNotifier != null && vNotifier.value != value) {
@@ -263,13 +294,19 @@ class _DroneStationOsdCardState extends State<DroneStationOsdCard> {
}
}
void _parseOsdFields(DroneOsdEntity osd) {
// 防护:验证消息来源 SN 是否匹配当前机场,避免串台
final sourceSn = osd.rawData['_sourceSn'];
if (sourceSn != null && sourceSn != widget.gatewaySn) {
return;
}
final data = osd.rawData;
final hostData = data['data'] is Map ? (data['data'] as Map)['host'] : null;
if (hostData == null || hostData is! Map) {
debugPrint('⚠️ [StationOsdCard] hostData 为空,rawData keys: ${data.keys.toList()}');
debugPrint(
'⚠️ [StationOsdCard] hostData 为空,rawData keys: ${data.keys.toList()}',
);
return;
}
@@ -278,17 +315,35 @@ class _DroneStationOsdCardState extends State<DroneStationOsdCard> {
// 🔥 调试日志:对比卡片期望的字段 vs 实际推送中存在的字段
final expectedKeys = [
'air_conditioner', 'cover_state', 'putter_state', 'supplement_light_state',
'temperature', 'drone_in_dock', 'drone_charge_state', 'humidity',
'wind_speed', 'rainfall', 'environment_temperature', 'alarm_state',
'emergency_stop_state', 'silent_mode', 'mode_code', 'network_state',
'position_state', 'storage', 'heading', 'height', 'sub_device',
'air_conditioner',
'cover_state',
'putter_state',
'supplement_light_state',
'temperature',
'drone_in_dock',
'drone_charge_state',
'humidity',
'wind_speed',
'rainfall',
'environment_temperature',
'alarm_state',
'emergency_stop_state',
'silent_mode',
'mode_code',
'network_state',
'position_state',
'storage',
'heading',
'height',
'sub_device',
'battery_store_mode',
];
final hostMap = hostData as Map;
final found = expectedKeys.where((k) => hostMap.containsKey(k)).toList();
final missing = expectedKeys.where((k) => !hostMap.containsKey(k)).toList();
debugPrint('[StationOsdCard] host keys(${hostMap.length}): ${hostMap.keys.join(", ")}');
debugPrint(
'[StationOsdCard] host keys(${hostMap.length}): ${hostMap.keys.join(", ")}',
);
debugPrint('[StationOsdCard] matched: ${found.join(", ")}');
if (missing.isNotEmpty) {
debugPrint('[StationOsdCard] MISSING: ${missing.join(", ")}');
@@ -303,9 +358,7 @@ class _DroneStationOsdCardState extends State<DroneStationOsdCard> {
_updateField(
'acState',
acState == 1 ? '开启' : '关闭',
acState == 1
? const Color(0xFF00B42A)
: const Color(0xFF86909C),
acState == 1 ? const Color(0xFF00B42A) : const Color(0xFF86909C),
);
}
@@ -322,9 +375,7 @@ class _DroneStationOsdCardState extends State<DroneStationOsdCard> {
_updateField(
'coverState',
coverState == 1 ? '开启' : '关闭',
coverState == 1
? const Color(0xFFFF7D00)
: const Color(0xFF00B42A),
coverState == 1 ? const Color(0xFFFF7D00) : const Color(0xFF00B42A),
);
}
@@ -335,9 +386,7 @@ class _DroneStationOsdCardState extends State<DroneStationOsdCard> {
_updateField(
'putterState',
putterState == 1 ? '动作中' : '归位',
putterState == 1
? const Color(0xFFFF7D00)
: const Color(0xFF00B42A),
putterState == 1 ? const Color(0xFFFF7D00) : const Color(0xFF00B42A),
);
}
@@ -349,9 +398,7 @@ class _DroneStationOsdCardState extends State<DroneStationOsdCard> {
_updateField(
'lightState',
lightState == 1 ? '开启' : '关闭',
lightState == 1
? const Color(0xFFFF7D00)
: const Color(0xFF86909C),
lightState == 1 ? const Color(0xFFFF7D00) : const Color(0xFF86909C),
);
}
@@ -376,9 +423,7 @@ class _DroneStationOsdCardState extends State<DroneStationOsdCard> {
_updateField(
'droneDock',
droneDock == 1 ? '在库内' : '出库',
droneDock == 1
? const Color(0xFF00B42A)
: const Color(0xFFFF7D00),
droneDock == 1 ? const Color(0xFF00B42A) : const Color(0xFFFF7D00),
);
}
@@ -391,9 +436,7 @@ class _DroneStationOsdCardState extends State<DroneStationOsdCard> {
_updateField(
'chargeState',
chargeState == 1 ? '充电中' : '未充电',
chargeState == 1
? const Color(0xFFFF7D00)
: const Color(0xFF86909C),
chargeState == 1 ? const Color(0xFFFF7D00) : const Color(0xFF86909C),
);
}
}
@@ -423,21 +466,18 @@ class _DroneStationOsdCardState extends State<DroneStationOsdCard> {
}
// 降雨
final rainfall =
int.tryParse(hostData['rainfall']?.toString() ?? '') ?? -1;
final rainfall = int.tryParse(hostData['rainfall']?.toString() ?? '') ?? -1;
if (rainfall >= 0) {
_updateField(
'rainfall',
rainfall == 1 ? '降雨中' : '无降雨',
rainfall == 1
? const Color(0xFF165DFF)
: const Color(0xFF00B42A),
rainfall == 1 ? const Color(0xFF165DFF) : const Color(0xFF00B42A),
);
}
// 外部温度
final externalTemp =
(hostData['environment_temperature'] as num?)?.toDouble();
final externalTemp = (hostData['environment_temperature'] as num?)
?.toDouble();
if (externalTemp != null) {
_updateField(
'externalTemp',
@@ -457,23 +497,18 @@ class _DroneStationOsdCardState extends State<DroneStationOsdCard> {
_updateField(
'alarmState',
alarmState != 0 ? '告警中' : '无告警',
alarmState != 0
? const Color(0xFFF53F3F)
: const Color(0xFF00B42A),
alarmState != 0 ? const Color(0xFFF53F3F) : const Color(0xFF00B42A),
);
}
// 急停状态
if (hostData.containsKey('emergency_stop_state')) {
final emergencyStop =
int.tryParse(hostData['emergency_stop_state']?.toString() ?? '') ??
0;
int.tryParse(hostData['emergency_stop_state']?.toString() ?? '') ?? 0;
_updateField(
'emergencyStop',
emergencyStop == 1 ? '已触发' : '未触发',
emergencyStop == 1
? const Color(0xFFF53F3F)
: const Color(0xFF00B42A),
emergencyStop == 1 ? const Color(0xFFF53F3F) : const Color(0xFF00B42A),
);
}
@@ -484,9 +519,7 @@ class _DroneStationOsdCardState extends State<DroneStationOsdCard> {
_updateField(
'silentMode',
silentMode == 1 ? '开启' : '关闭',
silentMode == 1
? const Color(0xFFFF7D00)
: const Color(0xFF86909C),
silentMode == 1 ? const Color(0xFFFF7D00) : const Color(0xFF86909C),
);
}
@@ -577,7 +610,6 @@ class _DroneStationOsdCardState extends State<DroneStationOsdCard> {
: const Color(0xFF86909C),
);
}
}
@override

View File

@@ -2,10 +2,10 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:maibu_satabot_v2/core/di/injection.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import 'package:maibu_satabot_v2/features/v2/message_center/domain/entities/message_category_entity.dart';
import 'package:maibu_satabot_v2/features/v2/message_center/presentation/bloc/message_center_cubit.dart';
import 'package:maibu_satabot_v2/features/v2/message_center/presentation/bloc/message_center_state.dart';
import 'package:maibu_satabot_v2/features/v2/waring_center/presentation/constants/alarm_constants.dart';
/// 消息中心页面
class MessageCenterPage extends StatelessWidget {
@@ -28,7 +28,7 @@ class MessageCenterView extends StatelessWidget {
final cubit = context.read<MessageCenterCubit>();
return Scaffold(
backgroundColor: AlarmColors.background,
backgroundColor: context.appColors.pageBackground,
body: BlocConsumer<MessageCenterCubit, MessageCenterState>(
listener: (context, state) {
if (state is MessageCenterActionInProgress) {
@@ -70,11 +70,11 @@ class MessageCenterView extends StatelessWidget {
// 右侧:一键已读按钮
GestureDetector(
onTap: () => cubit.markAllAsRead(),
child: const Text(
child: Text(
'一键已读',
style: TextStyle(
fontSize: 14,
color: AlarmColors.primary,
color: context.appColors.primary,
fontWeight: FontWeight.w500,
),
),
@@ -95,15 +95,15 @@ class MessageCenterView extends StatelessWidget {
return DropdownButton<MessageType>(
value: selectedType,
underline: const SizedBox(),
icon: const Icon(
icon: Icon(
Icons.keyboard_arrow_down,
size: 20,
color: AlarmColors.textSecondary,
color: context.appColors.textSecondary,
),
style: const TextStyle(
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AlarmColors.textPrimary,
color: context.appColors.textPrimary,
),
items: MessageType.values.map((type) {
return DropdownMenuItem<MessageType>(
@@ -123,7 +123,7 @@ class MessageCenterView extends StatelessWidget {
Widget _buildContent(BuildContext context, MessageCenterState state) {
if (state is MessageCenterLoading) {
return _buildSkeletonScreen();
return _buildSkeletonScreen(context);
}
if (state is MessageCenterError) {
return _buildErrorState(context, state.message);
@@ -142,9 +142,9 @@ class MessageCenterView extends StatelessWidget {
return Center(
child: Text(
'暂无消息',
style: const TextStyle(
style: TextStyle(
fontSize: 14,
color: AlarmColors.textSecondary,
color: context.appColors.textSecondary,
),
),
);
@@ -176,7 +176,7 @@ class MessageCenterView extends StatelessWidget {
}
}
Widget _buildSkeletonScreen() {
Widget _buildSkeletonScreen(BuildContext context) {
return ListView.separated(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
itemCount: 6,
@@ -185,11 +185,11 @@ class MessageCenterView extends StatelessWidget {
return Container(
height: 80,
decoration: BoxDecoration(
color: AlarmColors.background,
borderRadius: BorderRadius.circular(AlarmDimensions.borderRadius),
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(16),
),
child: const Center(
child: CircularProgressIndicator(),
child: Center(
child: CircularProgressIndicator(color: context.appColors.primary),
),
);
},
@@ -204,14 +204,14 @@ class MessageCenterView extends StatelessWidget {
Icon(
Icons.error_outline,
size: 64,
color: AlarmColors.danger,
color: context.appColors.danger,
),
const SizedBox(height: 16),
Text(
message,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
color: AlarmColors.textSecondary,
color: context.appColors.textSecondary,
),
textAlign: TextAlign.center,
),
@@ -244,19 +244,19 @@ class MessageCenterView extends StatelessWidget {
child: Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const CircularProgressIndicator(),
CircularProgressIndicator(color: context.appColors.primary),
const SizedBox(height: 16),
Text(
'$action中...',
style: const TextStyle(
style: TextStyle(
fontSize: 14,
color: AlarmColors.textSecondary,
color: context.appColors.textSecondary,
),
),
],
@@ -288,8 +288,8 @@ class _MessageCategoryCard extends StatelessWidget {
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AlarmColors.background,
borderRadius: BorderRadius.circular(AlarmDimensions.borderRadius),
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(16),
),
child: Row(
children: [
@@ -307,16 +307,16 @@ class _MessageCategoryCard extends StatelessWidget {
style: TextStyle(
fontSize: 15,
fontWeight: isUnread ? FontWeight.bold : FontWeight.w500,
color: AlarmColors.textPrimary,
color: context.appColors.textPrimary,
),
),
const SizedBox(height: 4),
// 内容摘要
Text(
message.content,
style: const TextStyle(
style: TextStyle(
fontSize: 13,
color: AlarmColors.textSecondary,
color: context.appColors.textSecondary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
@@ -330,13 +330,13 @@ class _MessageCategoryCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.end,
children: [
// 未读角标
if (isUnread) _buildUnreadBadge(),
if (isUnread) _buildUnreadBadge(context),
// 时间
Text(
message.time,
style: const TextStyle(
style: TextStyle(
fontSize: 12,
color: AlarmColors.textSecondary,
color: context.appColors.textSecondary,
),
),
],
@@ -391,13 +391,13 @@ class _MessageCategoryCard extends StatelessWidget {
);
}
Widget _buildUnreadBadge() {
Widget _buildUnreadBadge(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: AlarmColors.danger,
color: context.appColors.danger,
borderRadius: BorderRadius.circular(10),
),
child: Text(

View File

@@ -3,6 +3,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter/services.dart';
import 'package:maibu_satabot_v2/core/app/app_user_cubit.dart';
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import 'package:maibu_satabot_v2/core/theme/theme_cubit.dart';
import 'package:maibu_satabot_v2/features/v2/my/presentation/cubit/my_cubit.dart';
import 'package:maibu_satabot_v2/features/v2/my/presentation/states/my_state.dart';
import 'package:maibu_satabot_v2/features/v2/my/di/my_di.dart';
@@ -38,7 +40,7 @@ class _MyPageContent extends StatelessWidget {
),
child: Scaffold(
extendBodyBehindAppBar: true,
backgroundColor: MyColors.background,
backgroundColor: context.appColors.pageBackground,
body: BlocConsumer<MyCubit, MyState>(
listener: (context, state) {
if (state is MyError) {
@@ -59,6 +61,9 @@ class _MyPageContent extends StatelessWidget {
final appUserState = context.watch<AppUserCubit>().state;
final user = appUserState.user;
// 🔥 监听全局主题状态,夜间模式开关的真实值以 ThemeCubit 为准
final isDarkMode = context.watch<ThemeCubit>().isDarkMode;
// 获取用户信息(优先使用昵称,其次用户名)
final userName = user?.nickname ?? user?.username ?? '未知用户';
final userRole = user?.email ?? user?.phone ?? '未设置';
@@ -104,11 +109,11 @@ class _MyPageContent extends StatelessWidget {
bottom: 16,
),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: const Color(0x0D000000),
color: context.appColors.cardShadow,
blurRadius: 10,
offset: const Offset(0, 2),
),
@@ -123,11 +128,22 @@ class _MyPageContent extends StatelessWidget {
itemCount: state.menuItems.length,
itemBuilder: (context, index) {
final item = state.menuItems[index];
// 🔥 夜间模式开关值以全局 ThemeCubit 状态为准
final displayItem = item.id == 'night_mode'
? item.copyWith(switchValue: isDarkMode)
: item;
return MenuItemCard(
item: item,
item: displayItem,
onTap: () => _handleMenuItemTap(context, cubit, item),
onSwitchChanged: item.hasSwitch
? (value) => cubit.toggleNightMode(value)
? (value) {
// 更新菜单 UI 状态
cubit.toggleNightMode(value);
// 🔥 真正切换全局主题
context
.read<ThemeCubit>()
.toggleDarkMode(value);
}
: null,
);
},

View File

@@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:maibu_satabot_v2/core/app/app_user_cubit.dart';
import 'package:maibu_satabot_v2/core/app/app_user_state.dart';
import 'package:maibu_satabot_v2/core/domain/entities/user_entity.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
/// 个人信息详情页
class ProfileDetailPage extends StatelessWidget {
@@ -11,12 +12,11 @@ class ProfileDetailPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF5F7FA),
backgroundColor: context.appColors.pageBackground,
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Color(0xFF1D2129)),
icon: Icon(Icons.arrow_back, color: context.appColors.textPrimary),
onPressed: () => Navigator.pop(context),
),
title: const Text(
@@ -24,7 +24,6 @@ class ProfileDetailPage extends StatelessWidget {
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF1D2129),
),
),
centerTitle: true,
@@ -43,11 +42,11 @@ class ProfileDetailPage extends StatelessWidget {
padding: const EdgeInsets.all(16),
children: [
// 头像卡片
_buildAvatarCard(user),
_buildAvatarCard(context, user),
const SizedBox(height: 16),
// 信息列表
_buildInfoCard(user),
_buildInfoCard(context, user),
const SizedBox(height: 16),
// 操作按钮
@@ -60,15 +59,15 @@ class ProfileDetailPage extends StatelessWidget {
}
/// 头像卡片
Widget _buildAvatarCard(UserEntity user) {
Widget _buildAvatarCard(BuildContext context, UserEntity user) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 32),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: const Color(0x0D000000),
color: context.appColors.cardShadow,
blurRadius: 8,
offset: const Offset(0, 2),
),
@@ -95,10 +94,10 @@ class ProfileDetailPage extends StatelessWidget {
// 昵称
Text(
user.nickname,
style: const TextStyle(
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Color(0xFF1D2129),
color: context.appColors.textPrimary,
),
),
const SizedBox(height: 8),
@@ -107,7 +106,7 @@ class ProfileDetailPage extends StatelessWidget {
'@${user.username}',
style: TextStyle(
fontSize: 14,
color: const Color(0xFF86909C),
color: context.appColors.textTertiary,
),
),
],
@@ -142,14 +141,14 @@ class ProfileDetailPage extends StatelessWidget {
}
/// 信息卡片
Widget _buildInfoCard(UserEntity user) {
Widget _buildInfoCard(BuildContext context, UserEntity user) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: const Color(0x0D000000),
color: context.appColors.cardShadow,
blurRadius: 8,
offset: const Offset(0, 2),
),
@@ -158,18 +157,21 @@ class ProfileDetailPage extends StatelessWidget {
child: Column(
children: [
_buildInfoItem(
context,
icon: Icons.person_outline,
label: '用户ID',
value: user.userId,
showDivider: true,
),
_buildInfoItem(
context,
icon: Icons.account_circle_outlined,
label: '用户名',
value: user.username,
showDivider: true,
),
_buildInfoItem(
context,
icon: Icons.badge_outlined,
label: '昵称',
value: user.nickname,
@@ -177,6 +179,7 @@ class ProfileDetailPage extends StatelessWidget {
),
if (user.email != null)
_buildInfoItem(
context,
icon: Icons.email_outlined,
label: '邮箱',
value: user.email!,
@@ -184,6 +187,7 @@ class ProfileDetailPage extends StatelessWidget {
),
if (user.phone != null)
_buildInfoItem(
context,
icon: Icons.phone_outlined,
label: '手机号',
value: user.phone!,
@@ -195,7 +199,8 @@ class ProfileDetailPage extends StatelessWidget {
}
/// 信息项
Widget _buildInfoItem({
Widget _buildInfoItem(
BuildContext context, {
required IconData icon,
required String label,
required String value,
@@ -207,13 +212,13 @@ class ProfileDetailPage extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
child: Row(
children: [
Icon(icon, size: 22, color: const Color(0xFF86909C)),
Icon(icon, size: 22, color: context.appColors.textTertiary),
const SizedBox(width: 12),
Text(
label,
style: const TextStyle(
style: TextStyle(
fontSize: 15,
color: Color(0xFF86909C),
color: context.appColors.textTertiary,
),
),
const Spacer(),
@@ -221,9 +226,9 @@ class ProfileDetailPage extends StatelessWidget {
flex: 2,
child: Text(
value,
style: const TextStyle(
style: TextStyle(
fontSize: 15,
color: Color(0xFF1D2129),
color: context.appColors.textPrimary,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.right,
@@ -237,7 +242,7 @@ class ProfileDetailPage extends StatelessWidget {
Divider(
height: 1,
thickness: 1,
color: const Color(0xFFF2F3F5),
color: context.appColors.divider,
),
],
);

View File

@@ -5,6 +5,7 @@ import 'package:go_router/go_router.dart';
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
import 'package:maibu_satabot_v2/core/localization/locale_cubit.dart';
import 'package:maibu_satabot_v2/core/router/route_paths.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import 'package:maibu_satabot_v2/core/storage/user_storage.dart';
import 'package:maibu_satabot_v2/features/auth/presentation/bloc/auth_cubit.dart';
import 'package:maibu_satabot_v2/features/main_container/presentation/cubit/tab_config_cubit.dart';
@@ -22,20 +23,19 @@ class _SystemSettingsPageState extends State<SystemSettingsPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF5F6F8),
backgroundColor: context.appColors.pageBackground,
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Color(0xFF1D2129)),
icon: Icon(Icons.arrow_back, color: context.appColors.textPrimary),
onPressed: () => Navigator.pop(context),
),
title: Text(
AppLocalizations.of(context).translate('my.settings'),
style: const TextStyle(
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF1D2129),
color: context.appColors.textPrimary,
),
),
centerTitle: true,
@@ -65,11 +65,11 @@ class _SystemSettingsPageState extends State<SystemSettingsPage> {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
boxShadow: const [
boxShadow: [
BoxShadow(
color: Color(0x0D000000),
color: context.appColors.cardShadow,
blurRadius: 8,
offset: Offset(0, 2),
),
@@ -83,10 +83,10 @@ class _SystemSettingsPageState extends State<SystemSettingsPage> {
children: [
Text(
AppLocalizations.of(context).translate('my_v2.suspend_bar'),
style: const TextStyle(
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Color(0xFF1D2129),
color: context.appColors.textPrimary,
),
),
const SizedBox(height: 4),
@@ -94,9 +94,9 @@ class _SystemSettingsPageState extends State<SystemSettingsPage> {
AppLocalizations.of(
context,
).translate('my_v2.suspend_bar_desc'),
style: const TextStyle(
style: TextStyle(
fontSize: 13,
color: Color(0xFF86909C),
color: context.appColors.textTertiary,
),
),
],
@@ -124,11 +124,11 @@ class _SystemSettingsPageState extends State<SystemSettingsPage> {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
boxShadow: const [
boxShadow: [
BoxShadow(
color: Color(0x0D000000),
color: context.appColors.cardShadow,
blurRadius: 8,
offset: Offset(0, 2),
),
@@ -139,10 +139,10 @@ class _SystemSettingsPageState extends State<SystemSettingsPage> {
children: [
Text(
AppLocalizations.of(context).translate('my_v2.tab_settings'),
style: const TextStyle(
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Color(0xFF1D2129),
color: context.appColors.textPrimary,
),
),
const SizedBox(height: 16),
@@ -167,9 +167,9 @@ class _SystemSettingsPageState extends State<SystemSettingsPage> {
Expanded(
child: Text(
tab.name,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
color: Color(0xFF1D2129),
color: context.appColors.textPrimary,
),
),
),
@@ -197,11 +197,11 @@ class _SystemSettingsPageState extends State<SystemSettingsPage> {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
boxShadow: const [
boxShadow: [
BoxShadow(
color: Color(0x0D000000),
color: context.appColors.cardShadow,
blurRadius: 8,
offset: Offset(0, 2),
),
@@ -212,10 +212,10 @@ class _SystemSettingsPageState extends State<SystemSettingsPage> {
children: [
Text(
AppLocalizations.of(context).translate('my.language'),
style: const TextStyle(
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Color(0xFF1D2129),
color: context.appColors.textPrimary,
),
),
const SizedBox(height: 16),
@@ -267,7 +267,7 @@ class _SystemSettingsPageState extends State<SystemSettingsPage> {
border: Border.all(
color: isSelected
? const Color(0xFF165DFF)
: const Color(0xFFE5E6EB),
: context.appColors.divider,
width: 1,
),
),
@@ -280,7 +280,7 @@ class _SystemSettingsPageState extends State<SystemSettingsPage> {
fontSize: 14,
color: isSelected
? const Color(0xFF165DFF)
: const Color(0xFF1D2129),
: context.appColors.textPrimary,
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
),
),
@@ -306,11 +306,11 @@ class _SystemSettingsPageState extends State<SystemSettingsPage> {
child: Container(
padding: const EdgeInsets.symmetric(vertical: 16),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
boxShadow: const [
boxShadow: [
BoxShadow(
color: Color(0x0D000000),
color: context.appColors.cardShadow,
blurRadius: 8,
offset: Offset(0, 2),
),

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import 'package:maibu_satabot_v2/features/v2/my/domain/entities/my_entities.dart';
/// 菜单项卡片组件(100% 还原设计稿)
@@ -32,17 +33,17 @@ class MenuItemCard extends StatelessWidget {
Icon(
_getIconData(item.icon),
size: 20,
color: const Color(0xFF4E5969),
color: context.appColors.textSecondary,
),
const SizedBox(width: 12),
// 标题
Expanded(
child: Text(
_getLocalizedTitle(context, item),
style: const TextStyle(
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w500,
color: Color(0xFF1D2129),
color: context.appColors.textPrimary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
@@ -53,9 +54,9 @@ class MenuItemCard extends StatelessWidget {
Flexible(
child: Text(
item.subTitle!,
style: const TextStyle(
style: TextStyle(
fontSize: 13,
color: Color(0xFF86909C),
color: context.appColors.textTertiary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
@@ -73,14 +74,14 @@ class MenuItemCard extends StatelessWidget {
activeTrackColor: const Color(
0xFF165DFF,
).withOpacity(0.3),
inactiveThumbColor: const Color(0xFFC9CDD4),
inactiveTrackColor: const Color(0xFFE5E6EB),
inactiveThumbColor: context.appColors.textTertiary,
inactiveTrackColor: context.appColors.divider,
),
),
],
Icon(
Icons.chevron_right,
color: const Color(0xFFC9CDD4),
color: context.appColors.textTertiary,
size: 20,
),
],
@@ -94,7 +95,7 @@ class MenuItemCard extends StatelessWidget {
child: Divider(
height: 1,
thickness: 0.5,
color: const Color(0xFFF0F0F0),
color: context.appColors.divider,
),
),
],

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import 'package:maibu_satabot_v2/features/v2/my/presentation/constants/my_constants.dart';
/// 快捷入口卡片组件(我的消息、我的收藏、我的工单)
@@ -21,11 +22,11 @@ class QuickActionCard extends StatelessWidget {
margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.symmetric(vertical: 20),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: const Color(0x0D000000),
color: context.appColors.cardShadow,
blurRadius: 10,
offset: const Offset(0, 2),
),
@@ -36,23 +37,26 @@ class QuickActionCard extends StatelessWidget {
children: [
// 我的消息
_buildQuickActionItem(
context,
icon: Icons.notifications_outlined,
label: AppLocalizations.of(context).translate('my_v2.message'),
onTap: onMessageTap,
showBadge: true,
),
// 分割线
Container(width: 1, height: 40, color: const Color(0xFFF0F0F0)),
Container(width: 1, height: 40, color: context.appColors.divider),
// 我的收藏
_buildQuickActionItem(
context,
icon: Icons.favorite_border,
label: AppLocalizations.of(context).translate('my_v2.favorite'),
onTap: onFavoriteTap,
),
// 分割线
Container(width: 1, height: 40, color: const Color(0xFFF0F0F0)),
Container(width: 1, height: 40, color: context.appColors.divider),
// 我的工单
_buildQuickActionItem(
context,
icon: Icons.assignment_outlined,
label: AppLocalizations.of(context).translate('my_v2.work_order'),
onTap: onWorkOrderTap,
@@ -62,7 +66,8 @@ class QuickActionCard extends StatelessWidget {
);
}
Widget _buildQuickActionItem({
Widget _buildQuickActionItem(
BuildContext context, {
required IconData icon,
required String label,
required VoidCallback onTap,
@@ -98,9 +103,9 @@ class QuickActionCard extends StatelessWidget {
const SizedBox(height: 8),
Text(
label,
style: const TextStyle(
style: TextStyle(
fontSize: 13,
color: MyColors.textSecondary,
color: context.appColors.textSecondary,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,

View File

@@ -1,18 +1,5 @@
import 'package:flutter/material.dart';
/// 全局颜色常量
class AppColors {
static const Color primary = Color(0xFF165DFF);
static const Color danger = Color(0xFFF53F3F);
static const Color warning = Color(0xFFFF7D00);
static const Color textPrimary = Color(0xFF1D2129);
static const Color textSecondary = Color(0xFF4E5969);
static const Color textHint = Color(0xFF86909C);
static const Color background = Color(0xFFFFFFFF);
static const Color cardBackground = Color(0xFFF8F9FA);
static const Color borderColor = Color(0xFFE5E6EB);
}
/// 全局尺寸常量
class AppDimensions {
static const double borderRadius = 16.0;

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter/services.dart';
import 'package:image_picker/image_picker.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import '../cubit/report_cubit.dart';
import '../states/report_state.dart';
import '../constants/report_constants.dart';
@@ -30,10 +31,11 @@ class _ReportPageContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.dark,
value: isDark ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark,
child: Scaffold(
backgroundColor: AppColors.cardBackground,
backgroundColor: context.appColors.pageBackground,
body: BlocConsumer<ReportCubit, ReportState>(
listener: (context, state) {
if (state is ReportFormState) {
@@ -60,8 +62,8 @@ class _ReportPageContent extends StatelessWidget {
},
builder: (context, state) {
if (state is ReportSubmitting || state is ReportDevicesLoading) {
return const Center(
child: CircularProgressIndicator(color: AppColors.primary),
return Center(
child: CircularProgressIndicator(color: context.appColors.primary),
);
}
@@ -90,7 +92,7 @@ class _ReportPageContent extends StatelessWidget {
return PreferredSize(
preferredSize: const Size.fromHeight(100),
child: Container(
color: AppColors.background,
color: context.appColors.cardBackground,
child: SafeArea(
bottom: false,
child: Padding(
@@ -102,22 +104,22 @@ class _ReportPageContent extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
Text(
'现场上报',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: AppColors.textPrimary,
color: context.appColors.textPrimary,
),
),
GestureDetector(
onTap: () {
context.read<ReportCubit>().submitReport();
},
child: const Text(
child: Text(
'提交',
style: TextStyle(
color: AppColors.primary,
color: context.appColors.primary,
fontSize: 18,
fontWeight: FontWeight.w600,
),
@@ -132,9 +134,9 @@ class _ReportPageContent extends StatelessWidget {
},
child: Row(
children: [
const Icon(
Icon(
Icons.location_on,
color: AppColors.primary,
color: context.appColors.primary,
size: 16,
),
const SizedBox(width: 6),
@@ -143,7 +145,7 @@ class _ReportPageContent extends StatelessWidget {
state.location,
style: TextStyle(
fontSize: 14,
color: AppColors.textSecondary,
color: context.appColors.textSecondary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
@@ -153,7 +155,7 @@ class _ReportPageContent extends StatelessWidget {
Icon(
Icons.arrow_drop_down,
size: 20,
color: AppColors.textHint,
color: context.appColors.textTertiary,
),
],
),
@@ -327,7 +329,7 @@ class _ReportPageContent extends StatelessWidget {
showModalBottomSheet(
context: context,
backgroundColor: Colors.white,
backgroundColor: context.appColors.cardBackground,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
@@ -340,12 +342,12 @@ class _ReportPageContent extends StatelessWidget {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
Text(
'选择场站',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF1D2129),
color: context.appColors.textPrimary,
),
),
const SizedBox(height: 14),
@@ -364,7 +366,7 @@ class _ReportPageContent extends StatelessWidget {
? Text('站点编码: ${site.siteCode}')
: null,
trailing: isSelected
? const Icon(Icons.check, color: Color(0xFF165DFF))
? Icon(Icons.check, color: context.appColors.primary)
: null,
onTap: () {
cubit.selectSite(site);
@@ -462,12 +464,15 @@ class _ReportPageContent extends StatelessWidget {
if (isRobotType) {
if (newState.robots.isEmpty) {
deviceList = [
const Center(
Center(
child: Padding(
padding: EdgeInsets.all(32),
child: Text(
'暂无机器人数据',
style: TextStyle(fontSize: 14, color: Color(0xFF86909C)),
style: TextStyle(
fontSize: 14,
color: context.appColors.textTertiary,
),
),
),
),
@@ -492,12 +497,15 @@ class _ReportPageContent extends StatelessWidget {
} else if (isDroneType) {
if (newState.drones.isEmpty) {
deviceList = [
const Center(
Center(
child: Padding(
padding: EdgeInsets.all(32),
child: Text(
'暂无无人机数据',
style: TextStyle(fontSize: 14, color: Color(0xFF86909C)),
style: TextStyle(
fontSize: 14,
color: context.appColors.textTertiary,
),
),
),
),
@@ -520,7 +528,7 @@ class _ReportPageContent extends StatelessWidget {
showModalBottomSheet(
context: context,
backgroundColor: Colors.white,
backgroundColor: context.appColors.cardBackground,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
@@ -535,10 +543,10 @@ class _ReportPageContent extends StatelessWidget {
children: [
Text(
isRobotType ? '选择机器人' : '选择无人机',
style: const TextStyle(
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF1D2129),
color: context.appColors.textPrimary,
),
),
const SizedBox(height: 14),

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import '../constants/report_constants.dart';
/// 问题描述输入模块
@@ -22,11 +23,11 @@ class DescriptionInput extends StatelessWidget {
),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.background,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(AppDimensions.borderRadius),
boxShadow: [
BoxShadow(
color: const Color(0x0D000000),
color: context.appColors.cardShadow,
blurRadius: AppDimensions.cardShadowBlur,
offset: const Offset(0, 2),
),
@@ -37,10 +38,10 @@ class DescriptionInput extends StatelessWidget {
children: [
Text(
loc.translate('report.problem_description'),
style: const TextStyle(
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
color: context.appColors.textPrimary,
),
),
const SizedBox(height: 12),
@@ -50,37 +51,37 @@ class DescriptionInput extends StatelessWidget {
onChanged: onChanged,
decoration: InputDecoration(
hintText: loc.translate('report.description_hint'),
hintStyle: const TextStyle(color: AppColors.textHint),
hintStyle: TextStyle(color: context.appColors.textTertiary),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(
AppDimensions.borderRadiusSmall,
),
borderSide: const BorderSide(color: AppColors.borderColor),
borderSide: BorderSide(color: context.appColors.divider),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(
AppDimensions.borderRadiusSmall,
),
borderSide: const BorderSide(color: AppColors.borderColor),
borderSide: BorderSide(color: context.appColors.divider),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(
AppDimensions.borderRadiusSmall,
),
borderSide: const BorderSide(
color: AppColors.primary,
borderSide: BorderSide(
color: context.appColors.primary,
width: 2,
),
),
counterText: '',
),
style: const TextStyle(fontSize: 14, color: AppColors.textPrimary),
style: TextStyle(fontSize: 14, color: context.appColors.textPrimary),
),
Align(
alignment: Alignment.centerRight,
child: Text(
'${description?.length ?? 0}/200',
style: const TextStyle(fontSize: 12, color: AppColors.textHint),
style: TextStyle(fontSize: 12, color: context.appColors.textTertiary),
),
),
],

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import '../constants/report_constants.dart';
/// 设备选择器
@@ -19,11 +20,11 @@ class DeviceSelector extends StatelessWidget {
),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.background,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(AppDimensions.borderRadius),
boxShadow: [
BoxShadow(
color: const Color(0x0D000000),
color: context.appColors.cardShadow,
blurRadius: AppDimensions.cardShadowBlur,
offset: const Offset(0, 2),
),
@@ -34,10 +35,10 @@ class DeviceSelector extends StatelessWidget {
children: [
Text(
AppLocalizations.of(context).translate('report.select_device'),
style: const TextStyle(
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
color: context.appColors.textPrimary,
),
),
Expanded(
@@ -53,8 +54,8 @@ class DeviceSelector extends StatelessWidget {
style: TextStyle(
fontSize: 12,
color: selectedDevice != null
? AppColors.textPrimary
: AppColors.textHint,
? context.appColors.textPrimary
: context.appColors.textTertiary,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
@@ -65,7 +66,7 @@ class DeviceSelector extends StatelessWidget {
Icon(
Icons.arrow_drop_down,
size: 20,
color: AppColors.textHint,
color: context.appColors.textTertiary,
),
],
),

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import '../constants/report_constants.dart';
class LevelSelector extends StatelessWidget {
@@ -19,11 +20,11 @@ class LevelSelector extends StatelessWidget {
),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.background,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(AppDimensions.borderRadius),
boxShadow: [
BoxShadow(
color: const Color(0x0D000000),
color: context.appColors.cardShadow,
blurRadius: AppDimensions.cardShadowBlur,
offset: const Offset(0, 2),
),
@@ -32,12 +33,12 @@ class LevelSelector extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
Text(
'问题等级',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
color: context.appColors.textPrimary,
),
),
const SizedBox(height: 12),
@@ -53,14 +54,14 @@ class LevelSelector extends StatelessWidget {
decoration: BoxDecoration(
color: isSelected
? level.textColor.withOpacity(0.1)
: AppColors.background,
: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(
AppDimensions.borderRadiusSmall,
),
border: Border.all(
color: isSelected
? level.borderColor
: AppColors.borderColor,
: context.appColors.divider,
width: isSelected ? 2 : 1,
),
),

View File

@@ -1,6 +1,7 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import '../constants/report_constants.dart';
/// 媒体上传模块
@@ -31,11 +32,11 @@ class MediaUploader extends StatelessWidget {
),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.background,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(AppDimensions.borderRadius),
boxShadow: [
BoxShadow(
color: const Color(0x0D000000),
color: context.appColors.cardShadow,
blurRadius: AppDimensions.cardShadowBlur,
offset: const Offset(0, 2),
),
@@ -46,10 +47,10 @@ class MediaUploader extends StatelessWidget {
children: [
Text(
loc.translate('report.upload_media'),
style: const TextStyle(
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
color: context.appColors.textPrimary,
),
),
const SizedBox(height: 12),
@@ -62,6 +63,7 @@ class MediaUploader extends StatelessWidget {
children: [
// 拍照按钮
_buildUploadButton(
context,
icon: Icons.camera_alt,
label: loc.translate('report.take_photo'),
onTap: onCameraTap,
@@ -69,6 +71,7 @@ class MediaUploader extends StatelessWidget {
const SizedBox(width: 8),
// 录像按钮
_buildUploadButton(
context,
icon: Icons.videocam,
label: loc.translate('report.record_video'),
onTap: onVideoTap,
@@ -92,7 +95,7 @@ class MediaUploader extends StatelessWidget {
borderRadius: BorderRadius.circular(
AppDimensions.borderRadiusSmall,
),
color: AppColors.cardBackground,
color: context.appColors.fillBackground,
),
child: ClipRRect(
borderRadius: BorderRadius.circular(
@@ -171,6 +174,7 @@ class MediaUploader extends StatelessWidget {
}).toList(),
// 添加按钮
_buildUploadButton(
context,
icon: Icons.add,
label: '',
onTap: onAddFromGallery,
@@ -185,7 +189,8 @@ class MediaUploader extends StatelessWidget {
);
}
Widget _buildUploadButton({
Widget _buildUploadButton(
BuildContext context, {
required IconData icon,
required String label,
required VoidCallback onTap,
@@ -197,10 +202,14 @@ class MediaUploader extends StatelessWidget {
width: 80,
height: 80,
decoration: BoxDecoration(
color: isAddButton ? AppColors.cardBackground : AppColors.background,
color: isAddButton
? context.appColors.fillBackground
: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(AppDimensions.borderRadiusMedium),
border: Border.all(
color: isAddButton ? AppColors.borderColor : AppColors.primary,
color: isAddButton
? context.appColors.divider
: context.appColors.primary,
),
),
child: Column(
@@ -208,7 +217,9 @@ class MediaUploader extends StatelessWidget {
children: [
Icon(
icon,
color: isAddButton ? AppColors.textHint : AppColors.primary,
color: isAddButton
? context.appColors.textTertiary
: context.appColors.primary,
size: 24,
),
if (label.isNotEmpty) ...[
@@ -217,7 +228,9 @@ class MediaUploader extends StatelessWidget {
label,
style: TextStyle(
fontSize: 12,
color: isAddButton ? AppColors.textHint : AppColors.primary,
color: isAddButton
? context.appColors.textTertiary
: context.appColors.primary,
),
),
],

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import '../constants/report_constants.dart';
class ReportTypeSelector extends StatelessWidget {
@@ -33,20 +34,20 @@ class ReportTypeSelector extends StatelessWidget {
),
decoration: BoxDecoration(
color: isSelected
? AppColors.primary.withOpacity(0.1)
: AppColors.background,
? context.appColors.primary.withOpacity(0.1)
: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(
AppDimensions.borderRadiusSmall,
),
border: Border.all(
color: isSelected
? AppColors.primary
: AppColors.borderColor,
? context.appColors.primary
: context.appColors.divider,
width: isSelected ? 2 : 1,
),
boxShadow: [
BoxShadow(
color: const Color(0x0D000000),
color: context.appColors.cardShadow,
blurRadius: AppDimensions.cardShadowBlur,
offset: const Offset(0, 2),
),
@@ -58,8 +59,8 @@ class ReportTypeSelector extends StatelessWidget {
Icon(
type.icon,
color: isSelected
? AppColors.primary
: AppColors.textHint,
? context.appColors.primary
: context.appColors.textTertiary,
size: 24,
),
const SizedBox(height: 6),
@@ -69,8 +70,8 @@ class ReportTypeSelector extends StatelessWidget {
fontSize: 12,
height: 1.2,
color: isSelected
? AppColors.primary
: AppColors.textSecondary,
? context.appColors.primary
: context.appColors.textSecondary,
fontWeight: isSelected
? FontWeight.w600
: FontWeight.normal,

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import 'package:maibu_satabot_v2/features/v2/site/presentation/cubit/site_cubit.dart';
import 'package:maibu_satabot_v2/features/v2/home/domain/entities/site_entity.dart';
@@ -74,10 +75,10 @@ class _SiteSelectorWidgetState extends State<SiteSelectorWidget> {
text,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Color(0xFF1D2129),
color: context.appColors.textPrimary,
),
),
),
@@ -86,8 +87,8 @@ class _SiteSelectorWidgetState extends State<SiteSelectorWidget> {
Icons.keyboard_arrow_down,
size: iconSize,
color: widget.enabled
? const Color(0xFF4E5969)
: const Color(0xFFC9CDD4),
? context.appColors.textSecondary
: context.appColors.textTertiary,
),
],
);
@@ -126,10 +127,10 @@ class _SiteSelectorWidgetState extends State<SiteSelectorWidget> {
text,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF1D2129),
color: context.appColors.textPrimary,
),
),
),
@@ -138,8 +139,8 @@ class _SiteSelectorWidgetState extends State<SiteSelectorWidget> {
Icons.keyboard_arrow_down,
size: iconSize,
color: widget.enabled
? const Color(0xFF4E5969)
: const Color(0xFFC9CDD4),
? context.appColors.textSecondary
: context.appColors.textTertiary,
),
],
);
@@ -163,7 +164,7 @@ class _SiteSelectorWidgetState extends State<SiteSelectorWidget> {
showModalBottomSheet(
context: context,
backgroundColor: Colors.white,
backgroundColor: context.appColors.cardBackground,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
@@ -182,21 +183,21 @@ class _SiteSelectorWidgetState extends State<SiteSelectorWidget> {
child: Row(
children: [
const Spacer(),
const Text(
Text(
'选择场站',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF1D2129),
color: context.appColors.textPrimary,
),
),
const Spacer(),
GestureDetector(
onTap: () => Navigator.pop(context),
child: const Icon(
child: Icon(
Icons.close,
size: 22,
color: Color(0xFF86909C),
color: context.appColors.textTertiary,
),
),
],
@@ -217,7 +218,7 @@ class _SiteSelectorWidgetState extends State<SiteSelectorWidget> {
margin: const EdgeInsets.symmetric(vertical: 2),
decoration: BoxDecoration(
color: isSelected
? const Color(0xFFE8F3FF)
? context.appColors.primary.withOpacity(0.1)
: Colors.transparent,
borderRadius: BorderRadius.circular(8),
),
@@ -235,15 +236,15 @@ class _SiteSelectorWidgetState extends State<SiteSelectorWidget> {
? FontWeight.w600
: FontWeight.w400,
color: isSelected
? const Color(0xFF165DFF)
: const Color(0xFF1D2129),
? context.appColors.primary
: context.appColors.textPrimary,
),
),
trailing: isSelected
? const Icon(
? Icon(
Icons.check,
size: 20,
color: Color(0xFF165DFF),
color: context.appColors.primary,
)
: null,
onTap: () {

View File

@@ -4,7 +4,6 @@ import 'package:dio/dio.dart';
import 'package:maibu_satabot_v2/core/consts/http_api_consts.dart';
import 'package:maibu_satabot_v2/features/v2/waring_center/data/datasources/alarm_remote_datasource.dart';
import 'package:maibu_satabot_v2/features/v2/waring_center/data/models/alarm_model.dart';
import 'package:maibu_satabot_v2/features/v2/waring_center/presentation/constants/alarm_constants.dart';
/// 告警远程数据源实现
class AlarmRemoteDataSourceImpl implements AlarmRemoteDataSource {

View File

@@ -4,7 +4,8 @@ library;
import 'package:flutter/material.dart';
/// 颜色常量
/// 颜色常量(仅保留告警等级等模式无关的品牌语义色,
/// UI 层颜色请使用 context.appColors 语义色)
class AlarmColors {
/// 主色调
static const Color primary = Color(0xFF165DFF);
@@ -20,24 +21,6 @@ class AlarmColors {
/// 提示色 - 蓝色提示
static const Color info = Color(0xFF69b1ff);
/// 背景色
static const Color background = Color(0xFFFFFFFF);
/// 主要文字色
static const Color textPrimary = Color(0xFF1D2129);
/// 次要文字色
static const Color textSecondary = Color(0xFF4E5969);
/// 辅助文字色
static const Color textTertiary = Color(0xFF86909C);
/// 未处理背景色
static const Color unprocessedBg = Color(0xFFFFF0F0);
/// AI诊断卡片背景色
static const Color aiCardBg = Color(0xFFF0F7FF);
}
/// 尺寸常量
@@ -53,13 +36,6 @@ class AlarmDimensions {
/// 导航栏高度
static const double appBarHeight = 44.0;
/// 卡片阴影
static BoxShadow cardShadow = const BoxShadow(
color: Color(0x0D000000),
blurRadius: 4,
offset: Offset(0, 2),
);
}
/// 告警等级枚举

View File

@@ -5,9 +5,7 @@ import '../../domain/entities/alarm_detail_entity.dart';
import '../../domain/usecases/dispatch_workorder_usecase.dart';
import '../states/alarm_dispatch_state.dart';
import '../../../report/presentation/constants/report_constants.dart';
import '../../../home/domain/entities/site_entity.dart';
import '../../../home/domain/usecases/get_site_list_usecase.dart';
import '../../../home/domain/repositories/site_repository.dart';
import '../../../home/data/repositories/site_repository_impl.dart';
import '../../../home/data/datasources/site_datasource_impl.dart';
import 'package:maibu_satabot_v2/core/app/app_user_cubit.dart';

View File

@@ -6,6 +6,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:go_router/go_router.dart';
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import 'package:maibu_satabot_v2/features/v2/site/presentation/cubit/site_cubit.dart';
import 'package:maibu_satabot_v2/features/v2/waring_center/presentation/bloc/alarm_cubit.dart';
import 'package:maibu_satabot_v2/features/v2/waring_center/presentation/bloc/alarm_state.dart';
@@ -71,18 +72,22 @@ class _AlarmCenterPageState extends State<AlarmCenterPage> {
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.dark.copyWith(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.dark,
),
value: isDark
? SystemUiOverlayStyle.light
: SystemUiOverlayStyle.dark,
child: Scaffold(
backgroundColor: const Color(0xFFF5F7FA),
backgroundColor: context.appColors.pageBackground,
body: SafeArea(
child: BlocBuilder<AlarmCubit, AlarmState>(
builder: (context, state) {
if (state is AlarmLoading) {
return const Center(child: CircularProgressIndicator());
return Center(
child: CircularProgressIndicator(
color: context.appColors.primary,
),
);
}
if (state is AlarmError) {
@@ -90,10 +95,10 @@ class _AlarmCenterPageState extends State<AlarmCenterPage> {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icon(
Icons.error_outline,
size: 48,
color: AlarmColors.textTertiary,
color: context.appColors.textTertiary,
),
const SizedBox(height: 16),
Text(state.message),
@@ -192,7 +197,7 @@ class _AlarmCenterPageState extends State<AlarmCenterPage> {
void _showFilterDialog() {
showModalBottomSheet(
context: context,
backgroundColor: Colors.white,
backgroundColor: context.appColors.cardBackground,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
@@ -207,10 +212,10 @@ class _AlarmCenterPageState extends State<AlarmCenterPage> {
AppLocalizations.of(
context,
).translate('alarm_center.filter_title'),
style: const TextStyle(
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AlarmColors.textPrimary,
color: context.appColors.textPrimary,
),
),
const SizedBox(height: 20),
@@ -266,7 +271,7 @@ class _AlarmCenterPageState extends State<AlarmCenterPage> {
Navigator.pop(context);
},
style: ElevatedButton.styleFrom(
backgroundColor: AlarmColors.primary,
backgroundColor: context.appColors.primary,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
@@ -302,14 +307,14 @@ class _AlarmCenterPageState extends State<AlarmCenterPage> {
children: [
const Flexible(child: SiteSelectorWidget(compact: true)),
const SizedBox(width: 8),
Container(width: 1, height: 20, color: const Color(0xFFE5E6EB)),
Container(width: 1, height: 20, color: context.appColors.divider),
const SizedBox(width: 8),
Text(
AppLocalizations.of(context).translate('alarm_center.title'),
style: const TextStyle(
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Color(0xFF86909C),
color: context.appColors.textTertiary,
),
),
],
@@ -319,10 +324,10 @@ class _AlarmCenterPageState extends State<AlarmCenterPage> {
Stack(
children: [
IconButton(
icon: const Icon(
icon: Icon(
Icons.notifications_none,
size: 24,
color: Color(0xFF1D2129),
color: context.appColors.textPrimary,
),
onPressed: () {
context.push('/message_center');
@@ -335,8 +340,8 @@ class _AlarmCenterPageState extends State<AlarmCenterPage> {
child: Container(
width: 8,
height: 8,
decoration: const BoxDecoration(
color: Color(0xFFF53F3F),
decoration: BoxDecoration(
color: context.appColors.danger,
shape: BoxShape.circle,
),
),
@@ -345,10 +350,10 @@ class _AlarmCenterPageState extends State<AlarmCenterPage> {
),
// 筛选图标
IconButton(
icon: const Icon(
icon: Icon(
Icons.filter_list,
size: 24,
color: Color(0xFF1D2129),
color: context.appColors.textPrimary,
),
onPressed: _showFilterDialog,
),
@@ -360,7 +365,7 @@ class _AlarmCenterPageState extends State<AlarmCenterPage> {
Widget _buildTabBar(AlarmLoaded state) {
return AlarmTabBar(
selectedFilter: state.selectedFilter,
backgroundColor: const Color(0xFFF5F7FA),
backgroundColor: context.appColors.pageBackground,
onFilterChanged: (filter) {
_cubit.changeFilter(filter);
},
@@ -369,16 +374,19 @@ class _AlarmCenterPageState extends State<AlarmCenterPage> {
Widget _buildLoadMoreIndicator(AlarmLoaded state) {
if (state.isLoadingMore) {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 16),
return Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Center(
child: Column(
children: [
CircularProgressIndicator(strokeWidth: 2),
SizedBox(height: 8),
CircularProgressIndicator(
strokeWidth: 2,
color: context.appColors.primary,
),
const SizedBox(height: 8),
Text(
'加载中...',
style: TextStyle(fontSize: 12, color: AlarmColors.textTertiary),
style: TextStyle(fontSize: 12, color: context.appColors.textTertiary),
),
],
),
@@ -387,23 +395,23 @@ class _AlarmCenterPageState extends State<AlarmCenterPage> {
}
if (!state.hasMore) {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 16),
return Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Center(
child: Text(
'已加载全部数据',
style: TextStyle(fontSize: 12, color: AlarmColors.textTertiary),
style: TextStyle(fontSize: 12, color: context.appColors.textTertiary),
),
),
);
}
return const Padding(
padding: EdgeInsets.symmetric(vertical: 16),
return Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Center(
child: Text(
'上滑加载更多',
style: TextStyle(fontSize: 12, color: AlarmColors.textTertiary),
style: TextStyle(fontSize: 12, color: context.appColors.textTertiary),
),
),
);
@@ -415,10 +423,10 @@ class _AlarmCenterPageState extends State<AlarmCenterPage> {
children: [
Text(
title,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AlarmColors.textPrimary,
color: context.appColors.textPrimary,
),
),
const SizedBox(height: 8),
@@ -429,14 +437,14 @@ class _AlarmCenterPageState extends State<AlarmCenterPage> {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
border: Border.all(color: AlarmColors.textTertiary),
border: Border.all(color: context.appColors.divider),
borderRadius: BorderRadius.circular(20),
),
child: Text(
option,
style: const TextStyle(
style: TextStyle(
fontSize: 13,
color: AlarmColors.textSecondary,
color: context.appColors.textSecondary,
),
),
);

View File

@@ -2,10 +2,10 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import 'package:maibu_satabot_v2/features/v2/waring_center/domain/entities/alarm_detail_entity.dart';
import 'package:maibu_satabot_v2/features/v2/waring_center/presentation/bloc/alarm_detail_cubit.dart';
import 'package:maibu_satabot_v2/features/v2/waring_center/presentation/bloc/alarm_detail_state.dart';
import 'package:maibu_satabot_v2/features/v2/waring_center/presentation/constants/alarm_constants.dart';
import 'package:maibu_satabot_v2/features/v2/waring_center/presentation/widgets/metric_trend_chart.dart';
/// 告警详情页面
@@ -31,13 +31,13 @@ class _AlarmDetailPageState extends State<AlarmDetailPage> {
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.dark.copyWith(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.dark,
),
value: isDark
? SystemUiOverlayStyle.light
: SystemUiOverlayStyle.dark,
child: Scaffold(
backgroundColor: const Color(0xFFF5F7FA),
backgroundColor: context.appColors.pageBackground,
appBar: _buildAppBar(),
body: BlocConsumer<AlarmDetailCubit, AlarmDetailState>(
listener: (context, state) {
@@ -49,9 +49,9 @@ class _AlarmDetailPageState extends State<AlarmDetailPage> {
// 确认告警成功后显示提示
if (state is AlarmDetailLoaded && state.isConfirmed) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('告警已确认,正在处理中'),
backgroundColor: AlarmColors.success,
SnackBar(
content: const Text('告警已确认,正在处理中'),
backgroundColor: context.appColors.success,
),
);
}
@@ -91,26 +91,26 @@ class _AlarmDetailPageState extends State<AlarmDetailPage> {
PreferredSizeWidget _buildAppBar() {
return AppBar(
backgroundColor: Colors.white,
backgroundColor: context.appColors.cardBackground,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: AlarmColors.textPrimary),
icon: Icon(Icons.arrow_back, color: context.appColors.textPrimary),
onPressed: () => context.pop(),
),
title: const Text(
title: Text(
'告警详情',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AlarmColors.textPrimary,
color: context.appColors.textPrimary,
),
),
centerTitle: true,
actions: [
IconButton(
icon: const Icon(
icon: Icon(
Icons.share_outlined,
color: AlarmColors.textPrimary,
color: context.appColors.textPrimary,
),
onPressed: () {
// TODO: 分享功能
@@ -141,11 +141,11 @@ class _AlarmDetailPageState extends State<AlarmDetailPage> {
return Container(
height: height,
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: CircularProgressIndicator(color: AlarmColors.primary),
child: CircularProgressIndicator(color: context.appColors.primary),
),
);
}
@@ -156,17 +156,17 @@ class _AlarmDetailPageState extends State<AlarmDetailPage> {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icon(
Icons.error_outline,
size: 64,
color: AlarmColors.textTertiary,
color: context.appColors.textTertiary,
),
const SizedBox(height: 16),
Text(
message,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
color: AlarmColors.textSecondary,
color: context.appColors.textSecondary,
),
textAlign: TextAlign.center,
),
@@ -176,7 +176,7 @@ class _AlarmDetailPageState extends State<AlarmDetailPage> {
icon: const Icon(Icons.refresh),
label: const Text('重试'),
style: ElevatedButton.styleFrom(
backgroundColor: AlarmColors.primary,
backgroundColor: context.appColors.primary,
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 12),
),
),
@@ -191,18 +191,19 @@ class _AlarmDetailPageState extends State<AlarmDetailPage> {
color: Colors.black.withOpacity(0.3),
child: Center(
child: Card(
color: context.appColors.cardBackground,
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(color: AlarmColors.primary),
CircularProgressIndicator(color: context.appColors.primary),
const SizedBox(height: 16),
Text(
'$action...',
style: const TextStyle(
style: TextStyle(
fontSize: 14,
color: AlarmColors.textSecondary,
color: context.appColors.textSecondary,
),
),
],
@@ -261,43 +262,45 @@ class _AlarmDetailPageState extends State<AlarmDetailPage> {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
boxShadow: [AlarmDimensions.cardShadow],
boxShadow: [
BoxShadow(color: context.appColors.cardShadow, blurRadius: 4, offset: const Offset(0, 2)),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(
Icon(
Icons.warning_amber_rounded,
color: AlarmColors.danger,
color: context.appColors.danger,
size: 24,
),
const SizedBox(width: 8),
Expanded(
child: Text(
alarm.title,
style: const TextStyle(
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: AlarmColors.textPrimary,
color: context.appColors.textPrimary,
),
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: AlarmColors.danger.withOpacity(0.1),
color: context.appColors.danger.withOpacity(0.1),
borderRadius: BorderRadius.circular(4),
),
child: Text(
alarm.level.label,
style: const TextStyle(
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: AlarmColors.danger,
color: context.appColors.danger,
),
),
),
@@ -306,17 +309,17 @@ class _AlarmDetailPageState extends State<AlarmDetailPage> {
const SizedBox(height: 12),
Text(
alarm.deviceInfo,
style: const TextStyle(
style: TextStyle(
fontSize: 13,
color: AlarmColors.textSecondary,
color: context.appColors.textSecondary,
),
),
const SizedBox(height: 4),
Text(
alarm.occurTime,
style: const TextStyle(
style: TextStyle(
fontSize: 13,
color: AlarmColors.textTertiary,
color: context.appColors.textTertiary,
),
),
],
@@ -341,8 +344,8 @@ class _AlarmDetailPageState extends State<AlarmDetailPage> {
'告警状态',
alarm.alarmStatus,
valueColor: alarm.alarmStatus == '待处理'
? AlarmColors.danger
: AlarmColors.success,
? context.appColors.danger
: context.appColors.success,
);
addRow('发生时间', alarm.occurTime);
addRow('恢复时间', alarm.recoverTime);
@@ -384,9 +387,11 @@ class _AlarmDetailPageState extends State<AlarmDetailPage> {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
boxShadow: [AlarmDimensions.cardShadow],
boxShadow: [
BoxShadow(color: context.appColors.cardShadow, blurRadius: 4, offset: const Offset(0, 2)),
],
),
child: Column(
children: infoRows
@@ -445,9 +450,9 @@ class _AlarmDetailPageState extends State<AlarmDetailPage> {
children: [
Text(
label,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
color: AlarmColors.textSecondary,
color: context.appColors.textSecondary,
),
),
const Spacer(),
@@ -459,7 +464,7 @@ class _AlarmDetailPageState extends State<AlarmDetailPage> {
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: valueColor ?? AlarmColors.textPrimary,
color: valueColor ?? context.appColors.textPrimary,
),
),
),
@@ -469,7 +474,7 @@ class _AlarmDetailPageState extends State<AlarmDetailPage> {
}
Widget _buildDivider() {
return const Divider(height: 24, thickness: 0.5, color: Color(0xFFE5E6EB));
return Divider(height: 24, thickness: 0.5, color: context.appColors.divider);
}
bool _hasHistoryData(HistoryMetrics historyData) {
@@ -483,19 +488,21 @@ class _AlarmDetailPageState extends State<AlarmDetailPage> {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
boxShadow: [AlarmDimensions.cardShadow],
boxShadow: [
BoxShadow(color: context.appColors.cardShadow, blurRadius: 4, offset: const Offset(0, 2)),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
Text(
'处理建议',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: AlarmColors.textPrimary,
color: context.appColors.textPrimary,
),
),
const SizedBox(height: 12),
@@ -507,18 +514,18 @@ class _AlarmDetailPageState extends State<AlarmDetailPage> {
children: [
Text(
'${entry.key + 1}. ',
style: const TextStyle(
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AlarmColors.textPrimary,
color: context.appColors.textPrimary,
),
),
Expanded(
child: Text(
entry.value,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
color: AlarmColors.textSecondary,
color: context.appColors.textSecondary,
height: 1.5,
),
),
@@ -542,10 +549,10 @@ class _AlarmDetailPageState extends State<AlarmDetailPage> {
return Container(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 20),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
color: context.appColors.cardShadow,
blurRadius: 8,
offset: const Offset(0, -2),
),
@@ -583,9 +590,9 @@ class _AlarmDetailPageState extends State<AlarmDetailPage> {
isDisabled: true,
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('暂未开放'),
backgroundColor: AlarmColors.warning,
SnackBar(
content: const Text('暂未开放'),
backgroundColor: context.appColors.warning,
),
);
},
@@ -604,10 +611,10 @@ class _AlarmDetailPageState extends State<AlarmDetailPage> {
_cubit.loadAlarmDetail(widget.alarmId);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: AlarmColors.primary,
backgroundColor: context.appColors.cardBackground,
foregroundColor: context.appColors.primary,
elevation: 0,
side: const BorderSide(color: AlarmColors.primary),
side: BorderSide(color: context.appColors.primary),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
@@ -639,10 +646,10 @@ class _AlarmDetailPageState extends State<AlarmDetailPage> {
child: ElevatedButton(
onPressed: isLoading || isDisabled ? null : onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: isPrimary ? AlarmColors.primary : Colors.white,
foregroundColor: isPrimary ? Colors.white : AlarmColors.primary,
backgroundColor: isPrimary ? context.appColors.primary : context.appColors.cardBackground,
foregroundColor: isPrimary ? Colors.white : context.appColors.primary,
elevation: 0,
side: isPrimary ? null : const BorderSide(color: AlarmColors.primary),
side: isPrimary ? null : BorderSide(color: context.appColors.primary),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
child: isLoading
@@ -651,7 +658,7 @@ class _AlarmDetailPageState extends State<AlarmDetailPage> {
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: isPrimary ? Colors.white : AlarmColors.primary,
color: isPrimary ? Colors.white : context.appColors.primary,
),
)
: Text(
@@ -671,18 +678,22 @@ class _AlarmDetailPageState extends State<AlarmDetailPage> {
context: context,
builder: (context) {
return AlertDialog(
title: const Text(
backgroundColor: context.appColors.cardBackground,
title: Text(
'AI诊断结果',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: context.appColors.textPrimary),
),
content: SingleChildScrollView(
child: Text(
result,
style: const TextStyle(fontSize: 14, height: 1.6),
style: TextStyle(fontSize: 14, height: 1.6, color: context.appColors.textSecondary),
),
),
actions: [
TextButton(onPressed: () => context.pop(), child: const Text('关闭')),
TextButton(
onPressed: () => context.pop(),
child: Text('关闭', style: TextStyle(color: context.appColors.primary)),
),
],
);
},

View File

@@ -6,12 +6,12 @@ import 'package:go_router/go_router.dart';
import 'package:image_picker/image_picker.dart';
import '../../domain/entities/alarm_detail_entity.dart';
import '../../domain/usecases/dispatch_workorder_usecase.dart';
import '../../data/datasources/alarm_dispatch_remote_datasource.dart';
import '../../data/datasources/impl/alarm_dispatch_remote_datasource_impl.dart';
import '../../data/repositories/alarm_dispatch_repository_impl.dart';
import '../cubit/alarm_dispatch_cubit.dart';
import '../states/alarm_dispatch_state.dart';
import '../../../report/presentation/constants/report_constants.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import '../../../report/presentation/widgets/report_type_selector.dart';
import '../../../report/presentation/widgets/device_selector.dart';
import '../../../report/presentation/widgets/description_input.dart';
@@ -58,7 +58,7 @@ class _AlarmDispatchPageContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.cardBackground,
backgroundColor: context.appColors.pageBackground,
appBar: _buildAppBar(context),
body: BlocConsumer<AlarmDispatchCubit, AlarmDispatchState>(
listener: (context, state) {
@@ -78,14 +78,14 @@ class _AlarmDispatchPageContent extends StatelessWidget {
},
builder: (context, state) {
if (state is AlarmDispatchSubmitting) {
return const Center(
child: CircularProgressIndicator(color: AppColors.primary),
return Center(
child: CircularProgressIndicator(color: context.appColors.primary),
);
}
if (state is AlarmDispatchLoading) {
return const Center(
child: CircularProgressIndicator(color: AppColors.primary),
return Center(
child: CircularProgressIndicator(color: context.appColors.primary),
);
}
@@ -101,28 +101,28 @@ class _AlarmDispatchPageContent extends StatelessWidget {
PreferredSizeWidget _buildAppBar(BuildContext context) {
return AppBar(
backgroundColor: AppColors.background,
backgroundColor: context.appColors.cardBackground,
elevation: 0,
title: const Text(
title: Text(
'告警派发工单',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppColors.textPrimary,
color: context.appColors.textPrimary,
),
),
centerTitle: true,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios, size: 20, color: Colors.black),
icon: Icon(Icons.arrow_back_ios, size: 20, color: context.appColors.textPrimary),
onPressed: () => context.pop(),
),
actions: [
TextButton(
onPressed: () => context.read<AlarmDispatchCubit>().submitDispatch(),
child: const Text(
child: Text(
'提交',
style: TextStyle(
color: AppColors.primary,
color: context.appColors.primary,
fontSize: 16,
fontWeight: FontWeight.w600,
),
@@ -142,10 +142,10 @@ class _AlarmDispatchPageContent extends StatelessWidget {
children: [
const SizedBox(height: AppDimensions.moduleSpacing),
// 场站信息
_buildSiteInfoCard(state),
_buildSiteInfoCard(context, state),
const SizedBox(height: AppDimensions.moduleSpacing),
// 告警关联信息条
_buildAlarmInfoBar(state),
_buildAlarmInfoBar(context, state),
const SizedBox(height: AppDimensions.moduleSpacing),
// 上报类型选择
ReportTypeSelector(
@@ -199,18 +199,18 @@ class _AlarmDispatchPageContent extends StatelessWidget {
}
/// 场站信息卡片
Widget _buildSiteInfoCard(AlarmDispatchFormState state) {
Widget _buildSiteInfoCard(BuildContext context, AlarmDispatchFormState state) {
return Container(
margin: const EdgeInsets.symmetric(
horizontal: AppDimensions.horizontalPadding,
),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: AppColors.background,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(AppDimensions.borderRadius),
boxShadow: [
BoxShadow(
color: const Color(0x0D000000),
color: context.appColors.cardShadow,
blurRadius: AppDimensions.cardShadowBlur,
offset: const Offset(0, 2),
),
@@ -219,16 +219,16 @@ class _AlarmDispatchPageContent extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Row(
Row(
children: [
Icon(Icons.location_on, size: 18, color: AppColors.primary),
SizedBox(width: 6),
Icon(Icons.location_on, size: 18, color: context.appColors.primary),
const SizedBox(width: 6),
Text(
'场站信息',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
color: context.appColors.textPrimary,
),
),
],
@@ -247,7 +247,7 @@ class _AlarmDispatchPageContent extends StatelessWidget {
// ),
// ),
// const SizedBox(width: 24),
_buildInfoLabel('场站名称'),
_buildInfoLabel(context, '场站名称'),
const SizedBox(width: 8),
Expanded(
child: Text(
@@ -255,8 +255,8 @@ class _AlarmDispatchPageContent extends StatelessWidget {
style: TextStyle(
fontSize: 14,
color: state.siteName != null
? AppColors.textPrimary
: AppColors.textHint,
? context.appColors.textPrimary
: context.appColors.textTertiary,
fontWeight: FontWeight.w500,
),
overflow: TextOverflow.ellipsis,
@@ -267,14 +267,14 @@ class _AlarmDispatchPageContent extends StatelessWidget {
const SizedBox(height: 8),
Row(
children: [
_buildInfoLabel('告警编号'),
_buildInfoLabel(context, '告警编号'),
const SizedBox(width: 8),
Expanded(
child: Text(
state.alarmNo ?? state.alarmId,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
color: AppColors.textPrimary,
color: context.appColors.textPrimary,
fontWeight: FontWeight.w500,
),
overflow: TextOverflow.ellipsis,
@@ -287,45 +287,45 @@ class _AlarmDispatchPageContent extends StatelessWidget {
);
}
Widget _buildInfoLabel(String label) {
Widget _buildInfoLabel(BuildContext context, String label) {
return Text(
label,
style: const TextStyle(fontSize: 12, color: AppColors.textHint),
style: TextStyle(fontSize: 12, color: context.appColors.textTertiary),
);
}
/// 告警关联信息条
Widget _buildAlarmInfoBar(AlarmDispatchFormState state) {
Widget _buildAlarmInfoBar(BuildContext context, AlarmDispatchFormState state) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final barColor = isDark ? const Color(0xFFE6B15A) : const Color(0xFFFA8C16);
return Container(
margin: const EdgeInsets.symmetric(
horizontal: AppDimensions.horizontalPadding,
),
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
decoration: BoxDecoration(
color: const Color(0xFFFFF7E6),
color: isDark ? const Color(0xFF3A2E14) : const Color(0xFFFFF7E6),
borderRadius: BorderRadius.circular(AppDimensions.borderRadiusSmall),
border: Border.all(color: const Color(0xFFFFD591)),
border: Border.all(
color: isDark ? const Color(0xFF6B5315) : const Color(0xFFFFD591),
),
),
child: Row(
children: [
const Icon(
Icons.warning_amber_rounded,
color: Color(0xFFFA8C16),
size: 18,
),
Icon(Icons.warning_amber_rounded, color: barColor, size: 18),
const SizedBox(width: 8),
Expanded(
child: Text(
'关联告警: ${state.alarmNo ?? state.alarmId}',
style: const TextStyle(
style: TextStyle(
fontSize: 13,
color: Color(0xFF8C6E1F),
color: isDark ? const Color(0xFFE6C877) : const Color(0xFF8C6E1F),
fontWeight: FontWeight.w500,
),
overflow: TextOverflow.ellipsis,
),
),
const Icon(Icons.chevron_right, size: 18, color: Color(0xFFFA8C16)),
Icon(Icons.chevron_right, size: 18, color: barColor),
],
),
);
@@ -343,11 +343,11 @@ class _AlarmDispatchPageContent extends StatelessWidget {
),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.background,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(AppDimensions.borderRadius),
boxShadow: [
BoxShadow(
color: const Color(0x0D000000),
color: context.appColors.cardShadow,
blurRadius: AppDimensions.cardShadowBlur,
offset: const Offset(0, 2),
),
@@ -356,16 +356,17 @@ class _AlarmDispatchPageContent extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
Text(
'计划时间',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
color: context.appColors.textPrimary,
),
),
const SizedBox(height: 12),
_buildTimeRow(
context,
label: '计划开始',
time: state.planStartTime,
onTap: () => _pickDateTime(context, (time) {
@@ -387,7 +388,8 @@ class _AlarmDispatchPageContent extends StatelessWidget {
);
}
Widget _buildTimeRow({
Widget _buildTimeRow(
BuildContext context, {
required String label,
required DateTime? time,
required VoidCallback onTap,
@@ -398,17 +400,17 @@ class _AlarmDispatchPageContent extends StatelessWidget {
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
decoration: BoxDecoration(
color: AppColors.cardBackground,
color: context.appColors.fillBackground,
borderRadius: BorderRadius.circular(AppDimensions.borderRadiusSmall),
border: Border.all(color: AppColors.borderColor),
border: Border.all(color: context.appColors.divider),
),
child: Row(
children: [
Text(
'$label${required ? ' *' : ''}',
style: const TextStyle(
style: TextStyle(
fontSize: 14,
color: AppColors.textSecondary,
color: context.appColors.textSecondary,
),
),
const Spacer(),
@@ -420,15 +422,15 @@ class _AlarmDispatchPageContent extends StatelessWidget {
style: TextStyle(
fontSize: 14,
color: time != null
? AppColors.textPrimary
: AppColors.textHint,
? context.appColors.textPrimary
: context.appColors.textTertiary,
),
),
const SizedBox(width: 4),
const Icon(
Icon(
Icons.calendar_today,
size: 16,
color: AppColors.textHint,
color: context.appColors.textTertiary,
),
],
),

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:maibu_satabot_v2/core/di/injection.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import 'package:maibu_satabot_v2/features/v2/waring_center/domain/entities/alarm_detail_entity.dart';
import 'package:maibu_satabot_v2/features/v2/waring_center/presentation/bloc/alarm_detail_cubit.dart';
import 'package:maibu_satabot_v2/features/v2/waring_center/presentation/bloc/alarm_detail_state.dart';
@@ -54,7 +55,6 @@ class _AlarmHandlePageContentState extends State<_AlarmHandlePageContent> {
'label': '确认告警',
'icon': Icons.check_circle_outline,
'color': const Color(0xFF52C41A),
'bgColor': const Color(0xFFF6FFED),
'confirmedLabel': '已确认告警',
'confirmedTip': '确认无需',
'unconfirmedTip': '确认无需',
@@ -63,35 +63,30 @@ class _AlarmHandlePageContentState extends State<_AlarmHandlePageContent> {
'label': '派发工单',
'icon': Icons.file_copy_outlined,
'color': const Color(0xFF1890FF),
'bgColor': const Color(0xFFE6F7FF),
'tip': '请填下方表格',
},
{
'label': '联系负责人',
'icon': Icons.person_outline,
'color': const Color(0xFFFA8C16),
'bgColor': const Color(0xFFFFF7E6),
'tip': '暂无功能',
},
{
'label': '拍照取证',
'icon': Icons.camera_alt_outlined,
'color': const Color(0xFF722ED1),
'bgColor': const Color(0xFFF9F0FF),
'tip': '暂不支持',
},
{
'label': '备注记录',
'icon': Icons.edit_note_outlined,
'color': const Color(0xFF13C2C2),
'bgColor': const Color(0xFFE6FFFB),
'tip': '请填下方表格',
},
{
'label': '更多操作',
'icon': Icons.more_horiz,
'color': const Color(0xFF8C8C8C),
'bgColor': const Color(0xFFF5F5F5),
'tip': '暂未开发',
},
];
@@ -99,16 +94,16 @@ class _AlarmHandlePageContentState extends State<_AlarmHandlePageContent> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF5F5F5),
backgroundColor: context.appColors.pageBackground,
appBar: _buildAppBar(),
body: BlocConsumer<AlarmDetailCubit, AlarmDetailState>(
listener: (context, state) {
if (state is AlarmDetailLoaded && state.isHandleSuccess) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('处理成功'),
backgroundColor: Color(0xFF52C41A),
duration: Duration(seconds: 1),
SnackBar(
content: const Text('处理成功'),
backgroundColor: context.appColors.success,
duration: const Duration(seconds: 1),
),
);
Future.delayed(const Duration(seconds: 1), () {
@@ -139,17 +134,17 @@ class _AlarmHandlePageContentState extends State<_AlarmHandlePageContent> {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icon(
Icons.error_outline,
size: 64,
color: Color(0xFFBFBFBF),
color: context.appColors.textTertiary,
),
const SizedBox(height: 16),
Text(
state.message,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
color: Color(0xFF8C8C8C),
color: context.appColors.textTertiary,
),
textAlign: TextAlign.center,
),
@@ -161,7 +156,7 @@ class _AlarmHandlePageContentState extends State<_AlarmHandlePageContent> {
icon: const Icon(Icons.refresh),
label: const Text('重试'),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF1890FF),
backgroundColor: context.appColors.primary,
padding: const EdgeInsets.symmetric(
horizontal: 32,
vertical: 12,
@@ -173,7 +168,9 @@ class _AlarmHandlePageContentState extends State<_AlarmHandlePageContent> {
);
}
return const Center(child: CircularProgressIndicator());
return Center(
child: CircularProgressIndicator(color: context.appColors.primary),
);
},
),
bottomNavigationBar: _buildBottomSubmitButton(),
@@ -183,39 +180,39 @@ class _AlarmHandlePageContentState extends State<_AlarmHandlePageContent> {
AppBar _buildAppBar() {
return AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios, color: Colors.black, size: 20),
icon: Icon(Icons.arrow_back_ios, color: context.appColors.textPrimary, size: 20),
onPressed: () => Navigator.pop(context),
),
title: const Text(
title: Text(
'告警处理',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.black,
color: context.appColors.textPrimary,
),
),
backgroundColor: Colors.white,
backgroundColor: context.appColors.cardBackground,
elevation: 0,
);
}
Widget _buildAlarmInfoCard(AlarmDetailEntity alarm) {
final levelColor = alarm.level == AlarmLevel.danger
? const Color(0xFFFF4D4F)
? context.appColors.danger
: alarm.level == AlarmLevel.warning
? const Color(0xFFFA8C16)
? context.appColors.warning
: alarm.level == AlarmLevel.low
? const Color(0xFF1890FF)
: const Color(0xFF52C41A);
? context.appColors.primary
: context.appColors.success;
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
color: context.appColors.cardShadow,
blurRadius: 4,
offset: const Offset(0, 2),
),
@@ -231,10 +228,10 @@ class _AlarmHandlePageContentState extends State<_AlarmHandlePageContent> {
Expanded(
child: Text(
alarm.title,
style: const TextStyle(
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.black,
color: context.appColors.textPrimary,
),
overflow: TextOverflow.ellipsis,
),
@@ -260,7 +257,7 @@ class _AlarmHandlePageContentState extends State<_AlarmHandlePageContent> {
const SizedBox(height: 8),
Text(
'${alarm.alarmNo ?? alarm.id} | ${alarm.deviceInfo} | ${alarm.occurTime}',
style: const TextStyle(fontSize: 13, color: Color(0xFF8C8C8C)),
style: TextStyle(fontSize: 13, color: context.appColors.textTertiary),
),
],
),
@@ -271,11 +268,11 @@ class _AlarmHandlePageContentState extends State<_AlarmHandlePageContent> {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
color: context.appColors.cardShadow,
blurRadius: 4,
offset: const Offset(0, 2),
),
@@ -319,7 +316,7 @@ class _AlarmHandlePageContentState extends State<_AlarmHandlePageContent> {
decoration: BoxDecoration(
color: isSelected
? (button['color'] as Color)
: (button['bgColor'] as Color),
: (button['color'] as Color).withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: isSelected
? Border.all(color: button['color'] as Color, width: 2)
@@ -391,11 +388,11 @@ class _AlarmHandlePageContentState extends State<_AlarmHandlePageContent> {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
color: context.appColors.cardShadow,
blurRadius: 4,
offset: const Offset(0, 2),
),
@@ -425,22 +422,23 @@ class _AlarmHandlePageContentState extends State<_AlarmHandlePageContent> {
controller: _rootCauseController,
maxLines: 4,
maxLength: 200,
decoration: const InputDecoration(
decoration: InputDecoration(
hintText: '请输入根因分析,最多200字',
hintStyle: TextStyle(color: context.appColors.textTertiary),
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 12),
contentPadding: const EdgeInsets.symmetric(vertical: 12),
isDense: false,
alignLabelWithHint: true,
),
style: const TextStyle(fontSize: 14, color: Colors.black),
style: TextStyle(fontSize: 14, color: context.appColors.textPrimary),
),
Align(
alignment: Alignment.bottomRight,
child: Text(
'${_rootCauseController.text.length}/200',
style: const TextStyle(
style: TextStyle(
fontSize: 12,
color: Color(0xFFBFBFBF),
color: context.appColors.textTertiary,
),
),
),
@@ -452,13 +450,14 @@ class _AlarmHandlePageContentState extends State<_AlarmHandlePageContent> {
label: '处理人',
child: TextField(
controller: _handlerController,
decoration: const InputDecoration(
decoration: InputDecoration(
hintText: '请输入处理人',
hintStyle: TextStyle(color: context.appColors.textTertiary),
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 12),
contentPadding: const EdgeInsets.symmetric(vertical: 12),
isDense: false,
),
style: const TextStyle(fontSize: 14, color: Colors.black),
style: TextStyle(fontSize: 14, color: context.appColors.textPrimary),
),
),
_buildDivider(),
@@ -471,22 +470,23 @@ class _AlarmHandlePageContentState extends State<_AlarmHandlePageContent> {
controller: _remarkController,
maxLines: 4,
maxLength: 200,
decoration: const InputDecoration(
decoration: InputDecoration(
hintText: '请输入处理备注,最多200字',
hintStyle: TextStyle(color: context.appColors.textTertiary),
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 12),
contentPadding: const EdgeInsets.symmetric(vertical: 12),
isDense: false,
alignLabelWithHint: true,
),
style: const TextStyle(fontSize: 14, color: Colors.black),
style: TextStyle(fontSize: 14, color: context.appColors.textPrimary),
),
Align(
alignment: Alignment.bottomRight,
child: Text(
'${_remarkController.text.length}/200',
style: const TextStyle(
style: TextStyle(
fontSize: 12,
color: Color(0xFFBFBFBF),
color: context.appColors.textTertiary,
),
),
),
@@ -508,7 +508,7 @@ class _AlarmHandlePageContentState extends State<_AlarmHandlePageContent> {
width: 72,
child: Text(
label,
style: const TextStyle(fontSize: 14, color: Color(0xFF8C8C8C)),
style: TextStyle(fontSize: 14, color: context.appColors.textSecondary),
),
),
const SizedBox(width: 12),
@@ -533,7 +533,7 @@ class _AlarmHandlePageContentState extends State<_AlarmHandlePageContent> {
item.isEmpty ? hintText ?? '' : item,
style: TextStyle(
fontSize: 14,
color: item.isEmpty ? const Color(0xFFBFBFBF) : Colors.black,
color: item.isEmpty ? context.appColors.textTertiary : context.appColors.textPrimary,
),
),
);
@@ -544,8 +544,8 @@ class _AlarmHandlePageContentState extends State<_AlarmHandlePageContent> {
contentPadding: EdgeInsets.symmetric(vertical: 12),
isDense: false,
),
style: const TextStyle(fontSize: 14, color: Colors.black),
icon: const Icon(Icons.keyboard_arrow_down, color: Color(0xFFBFBFBF)),
style: TextStyle(fontSize: 14, color: context.appColors.textPrimary),
icon: Icon(Icons.keyboard_arrow_down, color: context.appColors.textTertiary),
);
}
@@ -553,11 +553,11 @@ class _AlarmHandlePageContentState extends State<_AlarmHandlePageContent> {
return DropdownButtonFormField<String>(
value: _handleResult.isEmpty ? null : _handleResult,
items: [
const DropdownMenuItem<String>(
DropdownMenuItem<String>(
value: '',
child: Text(
'请选择处理结果',
style: TextStyle(fontSize: 14, color: Color(0xFFBFBFBF)),
style: TextStyle(fontSize: 14, color: context.appColors.textTertiary),
),
),
..._handleResultMap.entries.map((entry) {
@@ -565,7 +565,7 @@ class _AlarmHandlePageContentState extends State<_AlarmHandlePageContent> {
value: entry.key,
child: Text(
entry.value,
style: const TextStyle(fontSize: 14, color: Colors.black),
style: TextStyle(fontSize: 14, color: context.appColors.textPrimary),
),
);
}).toList(),
@@ -578,23 +578,23 @@ class _AlarmHandlePageContentState extends State<_AlarmHandlePageContent> {
contentPadding: EdgeInsets.symmetric(vertical: 12),
isDense: false,
),
style: const TextStyle(fontSize: 14, color: Colors.black),
icon: const Icon(Icons.keyboard_arrow_down, color: Color(0xFFBFBFBF)),
style: TextStyle(fontSize: 14, color: context.appColors.textPrimary),
icon: Icon(Icons.keyboard_arrow_down, color: context.appColors.textTertiary),
);
}
Widget _buildDivider() {
return const Divider(height: 1, color: Color(0xFFF0F0F0));
return Divider(height: 1, color: context.appColors.divider);
}
Widget _buildBottomSubmitButton() {
return Container(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 20),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
color: context.appColors.cardShadow,
blurRadius: 8,
offset: const Offset(0, -2),
),
@@ -605,7 +605,7 @@ class _AlarmHandlePageContentState extends State<_AlarmHandlePageContent> {
child: ElevatedButton(
onPressed: _submitHandle,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF1890FF),
backgroundColor: context.appColors.primary,
foregroundColor: Colors.white,
elevation: 0,
padding: const EdgeInsets.symmetric(vertical: 14),

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import 'package:maibu_satabot_v2/features/v2/waring_center/presentation/constants/alarm_constants.dart';
/// AI 诊断建议卡片
@@ -18,7 +19,7 @@ class AiDiagnosisCard extends StatelessWidget {
margin: const EdgeInsets.all(AlarmDimensions.horizontalPadding),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AlarmColors.aiCardBg,
color: context.appColors.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(AlarmDimensions.borderRadius),
),
child: Column(
@@ -29,8 +30,8 @@ class AiDiagnosisCard extends StatelessWidget {
Container(
width: 24,
height: 24,
decoration: const BoxDecoration(
color: AlarmColors.primary,
decoration: BoxDecoration(
color: context.appColors.primary,
shape: BoxShape.circle,
),
child: const Icon(
@@ -40,12 +41,12 @@ class AiDiagnosisCard extends StatelessWidget {
),
),
const SizedBox(width: 8),
const Text(
Text(
'AI诊断建议',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: AlarmColors.textPrimary,
color: context.appColors.textPrimary,
),
),
],
@@ -53,9 +54,9 @@ class AiDiagnosisCard extends StatelessWidget {
const SizedBox(height: 12),
Text(
content,
style: const TextStyle(
style: TextStyle(
fontSize: 13,
color: AlarmColors.textSecondary,
color: context.appColors.textSecondary,
height: 1.5,
),
),
@@ -67,19 +68,19 @@ class AiDiagnosisCard extends StatelessWidget {
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
Text(
'查看详情',
style: TextStyle(
fontSize: 13,
color: AlarmColors.primary,
color: context.appColors.primary,
fontWeight: FontWeight.w500,
),
),
const SizedBox(width: 4),
const Icon(
Icon(
Icons.arrow_forward_ios,
size: 12,
color: AlarmColors.primary,
color: context.appColors.primary,
),
],
),

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import 'package:maibu_satabot_v2/features/v2/waring_center/domain/entities/alarm_count_entity.dart';
import 'package:maibu_satabot_v2/features/v2/waring_center/presentation/constants/alarm_constants.dart';
class AlarmCountCard extends StatelessWidget {
const AlarmCountCard({
@@ -17,41 +17,46 @@ class AlarmCountCard extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(
horizontal: AlarmDimensions.horizontalPadding,
horizontal: 16,
),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AlarmColors.background,
borderRadius: BorderRadius.circular(AlarmDimensions.borderRadius),
boxShadow: [AlarmDimensions.cardShadow],
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(color: context.appColors.cardShadow, blurRadius: 4, offset: const Offset(0, 2)),
],
),
child: Row(
children: [
Expanded(
child: _buildCountItem(
context,
label: '待处理',
value: count.unprocessed,
color: AlarmColors.danger,
bgColor: AlarmColors.unprocessedBg,
labelColor: AlarmColors.danger,
color: context.appColors.danger,
bgColor: context.appColors.danger.withOpacity(0.1),
labelColor: context.appColors.danger,
),
),
_buildDivider(),
_buildDivider(context),
Expanded(
child: _buildCountItem(
context,
label: '已关闭',
value: count.confirmed,
color: const Color(0xFF1D2129),
color: context.appColors.textPrimary,
),
),
_buildDivider(),
_buildDivider(context),
Expanded(
child: _buildCountItem(
context,
label: AppLocalizations.of(
context,
).translate('alarm_center.today_new'),
value: count.todayNew,
color: const Color(0xFF1D2129),
color: context.appColors.textPrimary,
),
),
],
@@ -59,7 +64,8 @@ class AlarmCountCard extends StatelessWidget {
);
}
Widget _buildCountItem({
Widget _buildCountItem(
BuildContext context, {
required String label,
required int value,
required Color color,
@@ -78,7 +84,7 @@ class AlarmCountCard extends StatelessWidget {
label,
style: TextStyle(
fontSize: 13,
color: labelColor ?? AlarmColors.textSecondary,
color: labelColor ?? context.appColors.textSecondary,
),
),
const SizedBox(height: 4),
@@ -106,12 +112,12 @@ class AlarmCountCard extends StatelessWidget {
);
}
Widget _buildDivider() {
Widget _buildDivider(BuildContext context) {
return Container(
width: 1,
height: 50,
margin: const EdgeInsets.symmetric(horizontal: 8),
color: const Color(0xFFE5E6EB),
color: context.appColors.divider,
);
}
}

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import 'package:maibu_satabot_v2/features/v2/waring_center/domain/entities/alarm_entity.dart';
import 'package:maibu_satabot_v2/features/v2/waring_center/presentation/constants/alarm_constants.dart';
@@ -21,9 +22,11 @@ class AlarmItemCard extends StatelessWidget {
),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AlarmColors.background,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(AlarmDimensions.borderRadius),
boxShadow: [AlarmDimensions.cardShadow],
boxShadow: [
BoxShadow(color: context.appColors.cardShadow, blurRadius: 4, offset: const Offset(0, 2)),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -36,10 +39,10 @@ class AlarmItemCard extends StatelessWidget {
Expanded(
child: Text(
alarm.title,
style: const TextStyle(
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: AlarmColors.textPrimary,
color: context.appColors.textPrimary,
),
),
),
@@ -67,9 +70,9 @@ class AlarmItemCard extends StatelessWidget {
// 第二行:区域和设备
Text(
'${AppLocalizations.of(context).translate('alarm_center.area')}:${alarm.area} / ${AppLocalizations.of(context).translate('alarm_center.device')}:${alarm.device}',
style: const TextStyle(
style: TextStyle(
fontSize: 13,
color: AlarmColors.textSecondary,
color: context.appColors.textSecondary,
),
),
const SizedBox(height: 8),
@@ -78,9 +81,9 @@ class AlarmItemCard extends StatelessWidget {
children: [
Text(
alarm.time,
style: const TextStyle(
style: TextStyle(
fontSize: 12,
color: AlarmColors.textTertiary,
color: context.appColors.textTertiary,
),
),
const Spacer(),
@@ -88,7 +91,7 @@ class AlarmItemCard extends StatelessWidget {
alarm.status.label,
style: TextStyle(
fontSize: 12,
color: _getStatusColor(alarm.status),
color: _getStatusColor(context, alarm.status),
fontWeight: FontWeight.w500,
),
),
@@ -96,7 +99,7 @@ class AlarmItemCard extends StatelessWidget {
Icon(
Icons.arrow_forward_ios,
size: 12,
color: _getStatusColor(alarm.status),
color: _getStatusColor(context, alarm.status),
),
],
),
@@ -121,14 +124,14 @@ class AlarmItemCard extends StatelessWidget {
}
}
Color _getStatusColor(AlarmStatus status) {
Color _getStatusColor(BuildContext context, AlarmStatus status) {
switch (status) {
case AlarmStatus.pending:
return AlarmColors.danger;
return context.appColors.danger;
case AlarmStatus.processing:
case AlarmStatus.closed:
case AlarmStatus.ignored:
return AlarmColors.textTertiary;
return context.appColors.textTertiary;
}
}
}

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import 'package:maibu_satabot_v2/features/v2/waring_center/presentation/constants/alarm_constants.dart';
/// 告警筛选标签栏 - 全部/未处理/已确认/已恢复
@@ -77,8 +78,8 @@ class AlarmTabBar extends StatelessWidget {
fontSize: 15,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
color: isSelected
? AlarmColors.primary
: AlarmColors.textSecondary,
? context.appColors.primary
: context.appColors.textSecondary,
),
),
const SizedBox(height: 4),
@@ -86,7 +87,7 @@ class AlarmTabBar extends StatelessWidget {
height: 2,
width: 20,
decoration: BoxDecoration(
color: isSelected ? AlarmColors.primary : Colors.transparent,
color: isSelected ? context.appColors.primary : Colors.transparent,
borderRadius: BorderRadius.circular(1),
),
),

View File

@@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import 'package:maibu_satabot_v2/features/v2/waring_center/domain/entities/alarm_detail_entity.dart';
import 'package:maibu_satabot_v2/features/v2/waring_center/presentation/bloc/alarm_detail_state.dart';
import 'package:maibu_satabot_v2/features/v2/waring_center/presentation/constants/alarm_constants.dart';
/// 指标趋势图组件
class MetricTrendChart extends StatelessWidget {
@@ -21,37 +21,39 @@ class MetricTrendChart extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
boxShadow: [AlarmDimensions.cardShadow],
boxShadow: [
BoxShadow(color: context.appColors.cardShadow, blurRadius: 4, offset: const Offset(0, 2)),
],
),
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
Text(
'指标趋势',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: AlarmColors.textPrimary,
color: context.appColors.textPrimary,
),
),
const SizedBox(height: 12),
// 指标切换标签
_buildMetricTabs(),
_buildMetricTabs(context),
const SizedBox(height: 16),
// 折线图
SizedBox(
height: 200,
child: _buildChart(),
child: _buildChart(context),
),
],
),
);
}
Widget _buildMetricTabs() {
Widget _buildMetricTabs(BuildContext context) {
return Row(
children: MetricType.values.map((metric) {
final isSelected = metric == selectedMetric;
@@ -62,7 +64,7 @@ class MetricTrendChart extends StatelessWidget {
padding: const EdgeInsets.symmetric(vertical: 8),
margin: const EdgeInsets.symmetric(horizontal: 4),
decoration: BoxDecoration(
color: isSelected ? const Color(0xFFE8F3FF) : Colors.transparent,
color: isSelected ? context.appColors.primary.withOpacity(0.1) : Colors.transparent,
borderRadius: BorderRadius.circular(6),
),
child: Text(
@@ -71,7 +73,7 @@ class MetricTrendChart extends StatelessWidget {
style: TextStyle(
fontSize: 13,
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
color: isSelected ? AlarmColors.primary : AlarmColors.textSecondary,
color: isSelected ? context.appColors.primary : context.appColors.textSecondary,
),
),
),
@@ -81,7 +83,7 @@ class MetricTrendChart extends StatelessWidget {
);
}
Widget _buildChart() {
Widget _buildChart(BuildContext context) {
List<MetricDataPoint> dataPoints;
double maxY;
@@ -101,10 +103,10 @@ class MetricTrendChart extends StatelessWidget {
}
if (dataPoints.isEmpty) {
return const Center(
return Center(
child: Text(
'暂无数据',
style: TextStyle(color: AlarmColors.textTertiary),
style: TextStyle(color: context.appColors.textTertiary),
),
);
}
@@ -123,13 +125,13 @@ class MetricTrendChart extends StatelessWidget {
horizontalInterval: maxY / 5,
getDrawingHorizontalLine: (value) {
return FlLine(
color: const Color(0xFFE5E6EB).withOpacity(0.3),
color: context.appColors.divider.withOpacity(0.3),
strokeWidth: 1,
);
},
getDrawingVerticalLine: (value) {
return FlLine(
color: const Color(0xFFE5E6EB).withOpacity(0.3),
color: context.appColors.divider.withOpacity(0.3),
strokeWidth: 1,
);
},
@@ -146,9 +148,9 @@ class MetricTrendChart extends StatelessWidget {
if (index == 0 || index == dataPoints.length ~/ 2 || index == dataPoints.length - 1) {
return Text(
dataPoints[index].time,
style: const TextStyle(
style: TextStyle(
fontSize: 11,
color: AlarmColors.textTertiary,
color: context.appColors.textTertiary,
),
);
}
@@ -165,9 +167,9 @@ class MetricTrendChart extends StatelessWidget {
getTitlesWidget: (value, meta) {
return Text(
value.toInt().toString(),
style: const TextStyle(
style: TextStyle(
fontSize: 11,
color: AlarmColors.textTertiary,
color: context.appColors.textTertiary,
),
);
},
@@ -189,16 +191,16 @@ class MetricTrendChart extends StatelessWidget {
LineChartBarData(
spots: spots,
isCurved: true,
color: AlarmColors.primary,
color: context.appColors.primary,
barWidth: 2,
dotData: FlDotData(
show: true,
getDotPainter: (spot, percent, barData, index) {
return FlDotCirclePainter(
radius: 3,
color: AlarmColors.danger,
color: context.appColors.danger,
strokeWidth: 1,
strokeColor: Colors.white,
strokeColor: context.appColors.cardBackground,
);
},
),
@@ -206,8 +208,8 @@ class MetricTrendChart extends StatelessWidget {
show: true,
gradient: LinearGradient(
colors: [
AlarmColors.primary.withOpacity(0.2),
AlarmColors.primary.withOpacity(0.0),
context.appColors.primary.withOpacity(0.2),
context.appColors.primary.withOpacity(0.0),
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,

View File

@@ -1,6 +1,5 @@
import 'package:dio/dio.dart';
import '../../../../../core/consts/http_api_consts.dart';
import '../../../../../core/consts/workorder_consts.dart';
import 'workorder_remote_datasource.dart';
import '../models/workorder_model.dart';

View File

@@ -1,4 +1,3 @@
import '../../../../../core/consts/workorder_consts.dart';
import '../../../../../core/error/failure.dart';
import '../repositories/workorder_repository.dart';
import '../entities/workorder_entity.dart';

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import '../../../../../core/app/app_user_cubit.dart';
import '../../../../../core/network/dio_client.dart';
import '../../../site/presentation/cubit/site_cubit.dart';
@@ -76,8 +77,8 @@ class _TransferDialogContentState extends State<_TransferDialogContent> {
},
builder: (context, state) {
return Container(
decoration: const BoxDecoration(
color: Colors.white,
decoration: BoxDecoration(
color: context.appColors.cardBackground,
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
padding: EdgeInsets.only(
@@ -107,19 +108,23 @@ class _TransferDialogContentState extends State<_TransferDialogContent> {
Widget _buildHeader(BuildContext context) {
return Row(
children: [
const Expanded(
Expanded(
child: Text(
'转派工单',
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.w600,
color: Color(0xFF1D2129),
color: context.appColors.textPrimary,
),
),
),
GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: const Icon(Icons.close, size: 24, color: Color(0xFF86909C)),
child: Icon(
Icons.close,
size: 24,
color: context.appColors.textTertiary,
),
),
],
);
@@ -136,12 +141,15 @@ class _TransferDialogContentState extends State<_TransferDialogContent> {
}
if (state.users.isEmpty) {
return const Padding(
return Padding(
padding: EdgeInsets.all(24),
child: Center(
child: Text(
'暂无可转派人员',
style: TextStyle(color: Color(0xFF86909C), fontSize: 14),
style: TextStyle(
color: context.appColors.textTertiary,
fontSize: 14,
),
),
),
);
@@ -167,12 +175,12 @@ class _TransferDialogContentState extends State<_TransferDialogContent> {
margin: const EdgeInsets.only(bottom: 8),
decoration: BoxDecoration(
color: isSelected
? const Color(0xFFE8F3FF)
: const Color(0xFFF7F8FA),
? context.appColors.primary.withOpacity(0.1)
: context.appColors.fillBackground,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: isSelected
? const Color(0xFF165DFF)
? context.appColors.primary
: Colors.transparent,
),
),
@@ -181,8 +189,8 @@ class _TransferDialogContentState extends State<_TransferDialogContent> {
CircleAvatar(
radius: 18,
backgroundColor: isSelected
? const Color(0xFF165DFF)
: const Color(0xFFC9CDD4),
? context.appColors.primary
: context.appColors.textTertiary,
child: Text(
userName.isNotEmpty ? userName[0] : '?',
style: const TextStyle(color: Colors.white, fontSize: 14),
@@ -192,16 +200,16 @@ class _TransferDialogContentState extends State<_TransferDialogContent> {
Expanded(
child: Text(
userName.isNotEmpty ? userName : '未知用户',
style: const TextStyle(
style: TextStyle(
fontSize: 15,
color: Color(0xFF1D2129),
color: context.appColors.textPrimary,
),
),
),
if (isSelected)
const Icon(
Icon(
Icons.check_circle,
color: Color(0xFF165DFF),
color: context.appColors.primary,
size: 22,
),
],
@@ -217,28 +225,30 @@ class _TransferDialogContentState extends State<_TransferDialogContent> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
Text(
'派发备注(选填)',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Color(0xFF1D2129),
color: context.appColors.textPrimary,
),
),
const SizedBox(height: 8),
Container(
decoration: BoxDecoration(
color: const Color(0xFFF7F8FA),
color: context.appColors.fillBackground,
borderRadius: BorderRadius.circular(8),
),
child: TextField(
controller: _remarkController,
maxLines: 3,
maxLength: 200,
style: TextStyle(color: context.appColors.textPrimary),
onChanged: (value) =>
context.read<TransferCubit>().updateRemark(value),
decoration: const InputDecoration(
decoration: InputDecoration(
hintText: '请输入派发备注',
hintStyle: TextStyle(color: context.appColors.textTertiary),
border: InputBorder.none,
contentPadding: EdgeInsets.all(12),
isDense: true,
@@ -262,7 +272,7 @@ class _TransferDialogContentState extends State<_TransferDialogContent> {
);
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF165DFF),
backgroundColor: context.appColors.primary,
foregroundColor: Colors.white,
elevation: 0,
padding: EdgeInsets.zero,
@@ -289,24 +299,32 @@ class _TransferDialogContentState extends State<_TransferDialogContent> {
showDialog(
context: context,
builder: (_) => AlertDialog(
backgroundColor: context.appColors.cardBackground,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.error_outline, size: 48, color: Color(0xFFF53F3F)),
Icon(
Icons.error_outline,
size: 48,
color: context.appColors.danger,
),
const SizedBox(height: 12),
const Text(
Text(
'转派失败',
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.w600,
color: Color(0xFF1D2129),
color: context.appColors.textPrimary,
),
),
const SizedBox(height: 8),
Text(
message,
style: const TextStyle(fontSize: 14, color: Color(0xFF86909C)),
style: TextStyle(
fontSize: 14,
color: context.appColors.textTertiary,
),
textAlign: TextAlign.center,
),
],
@@ -317,7 +335,7 @@ class _TransferDialogContentState extends State<_TransferDialogContent> {
child: ElevatedButton(
onPressed: () => Navigator.of(context).pop(),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF165DFF),
backgroundColor: context.appColors.primary,
foregroundColor: Colors.white,
elevation: 0,
padding: const EdgeInsets.symmetric(vertical: 12),

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import 'package:video_player/video_player.dart';
import '../../../../../core/consts/workorder_consts.dart';
import '../cubit/workorder_detail_cubit.dart';
@@ -29,13 +30,18 @@ class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.dark.copyWith(
value: isDark
? SystemUiOverlayStyle.light.copyWith(
statusBarColor: Colors.transparent,
)
: SystemUiOverlayStyle.dark.copyWith(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.dark,
),
child: Scaffold(
backgroundColor: WorkOrderColors.pageBackground,
backgroundColor: context.appColors.pageBackground,
appBar: _buildAppBar(),
body: BlocBuilder<WorkOrderDetailCubit, WorkOrderDetailState>(
builder: (context, state) {
@@ -61,26 +67,26 @@ class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
PreferredSizeWidget _buildAppBar() {
return AppBar(
backgroundColor: Colors.white,
backgroundColor: context.appColors.cardBackground,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: WorkOrderColors.primaryText),
icon: Icon(Icons.arrow_back, color: context.appColors.textPrimary),
onPressed: () => context.pop(),
),
title: const Text(
title: Text(
'工单详情',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: WorkOrderColors.primaryText,
color: context.appColors.textPrimary,
),
),
centerTitle: true,
actions: [
IconButton(
icon: const Icon(
icon: Icon(
Icons.more_horiz,
color: WorkOrderColors.primaryText,
color: context.appColors.textPrimary,
),
onPressed: () {},
),
@@ -89,9 +95,9 @@ class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
}
Widget _buildLoadingView() {
return const Center(
return Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(WorkOrderColors.primary),
valueColor: AlwaysStoppedAnimation<Color>(context.appColors.primary),
),
);
}
@@ -104,21 +110,21 @@ class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
Icon(
Icons.error_outline,
size: 48,
color: WorkOrderColors.auxiliaryText,
color: context.appColors.textTertiary,
),
const SizedBox(height: 16),
Text(
message,
style: TextStyle(
fontSize: 14,
color: WorkOrderColors.auxiliaryText,
color: context.appColors.textTertiary,
),
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () => _cubit.loadWorkOrderDetail(widget.orderId),
style: ElevatedButton.styleFrom(
backgroundColor: WorkOrderColors.primary,
backgroundColor: context.appColors.primary,
foregroundColor: Colors.white,
),
child: const Text('重试'),
@@ -167,18 +173,24 @@ class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
boxShadow: WorkOrderColors.cardShadow,
boxShadow: [
BoxShadow(
color: context.appColors.cardShadow,
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
Text(
'工单编号',
style: TextStyle(
fontSize: 13,
color: WorkOrderColors.auxiliaryText,
color: context.appColors.textTertiary,
),
),
const SizedBox(height: 4),
@@ -187,10 +199,10 @@ class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
Expanded(
child: Text(
workOrder.orderNo,
style: const TextStyle(
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: WorkOrderColors.primaryText,
color: context.appColors.textPrimary,
),
),
),
@@ -201,7 +213,7 @@ class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
vertical: 4,
),
decoration: BoxDecoration(
color: const Color(0xFFFF4D4F),
color: context.appColors.danger,
borderRadius: BorderRadius.circular(4),
),
child: const Text(
@@ -218,22 +230,22 @@ class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
const SizedBox(height: 12),
Text(
workOrder.title,
style: const TextStyle(
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: WorkOrderColors.primaryText,
color: context.appColors.textPrimary,
),
),
const SizedBox(height: 16),
_buildInfoRow(
'状态',
_getStatusLabel(workOrder.status),
valueColor: _getStatusColor(workOrder.status),
valueColor: _getStatusColor(context, workOrder.status),
),
_buildInfoRow(
'优先级',
_getPriorityLabel(workOrder.priority),
valueColor: _getPriorityColor(workOrder.priority),
valueColor: _getPriorityColor(context, workOrder.priority),
),
_buildInfoRow('来源', workOrder.source ?? '--'),
_buildInfoRow(
@@ -250,19 +262,25 @@ class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
boxShadow: WorkOrderColors.cardShadow,
boxShadow: [
BoxShadow(
color: context.appColors.cardShadow,
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
Text(
'基本信息',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: WorkOrderColors.primaryText,
color: context.appColors.textPrimary,
),
),
const SizedBox(height: 12),
@@ -294,19 +312,25 @@ class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
boxShadow: WorkOrderColors.cardShadow,
boxShadow: [
BoxShadow(
color: context.appColors.cardShadow,
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
Text(
'设备信息',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: WorkOrderColors.primaryText,
color: context.appColors.textPrimary,
),
),
const SizedBox(height: 12),
@@ -321,19 +345,25 @@ class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
boxShadow: WorkOrderColors.cardShadow,
boxShadow: [
BoxShadow(
color: context.appColors.cardShadow,
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
Text(
'时间信息',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: WorkOrderColors.primaryText,
color: context.appColors.textPrimary,
),
),
const SizedBox(height: 12),
@@ -359,19 +389,25 @@ class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
boxShadow: WorkOrderColors.cardShadow,
boxShadow: [
BoxShadow(
color: context.appColors.cardShadow,
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
Text(
'任务描述',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: WorkOrderColors.primaryText,
color: context.appColors.textPrimary,
),
),
const SizedBox(height: 12),
@@ -379,7 +415,7 @@ class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
workOrder.taskDescription!,
style: TextStyle(
fontSize: 14,
color: WorkOrderColors.secondaryText,
color: context.appColors.textSecondary,
height: 1.6,
),
),
@@ -392,19 +428,25 @@ class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
boxShadow: WorkOrderColors.cardShadow,
boxShadow: [
BoxShadow(
color: context.appColors.cardShadow,
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
Text(
'处理信息',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: WorkOrderColors.primaryText,
color: context.appColors.textPrimary,
),
),
const SizedBox(height: 12),
@@ -428,21 +470,27 @@ class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
boxShadow: WorkOrderColors.cardShadow,
boxShadow: [
BoxShadow(
color: context.appColors.cardShadow,
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Text(
Text(
'附件',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: WorkOrderColors.primaryText,
color: context.appColors.textPrimary,
),
),
const SizedBox(width: 8),
@@ -450,7 +498,7 @@ class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
'(${hasImages ? workOrder.attachments!.length : 0}张图片${hasVideos ? ' ${workOrder.videoUrls!.length}个视频' : ''})',
style: TextStyle(
fontSize: 14,
color: WorkOrderColors.auxiliaryText,
color: context.appColors.textTertiary,
),
),
],
@@ -482,9 +530,9 @@ class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
width: 80,
height: 80,
decoration: BoxDecoration(
color: const Color(0xFFF7F8FA),
color: context.appColors.fillBackground,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: const Color(0xFFE5E6EB)),
border: Border.all(color: context.appColors.divider),
),
child: attachment.url.isNotEmpty
? ClipRRect(
@@ -496,10 +544,10 @@ class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icon(
Icons.image_outlined,
size: 32,
color: WorkOrderColors.auxiliaryText,
color: context.appColors.textTertiary,
),
const SizedBox(height: 4),
Text(
@@ -508,7 +556,7 @@ class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
: '图片',
style: TextStyle(
fontSize: 11,
color: WorkOrderColors.auxiliaryText,
color: context.appColors.textTertiary,
),
textAlign: TextAlign.center,
maxLines: 2,
@@ -522,17 +570,17 @@ class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icon(
Icons.image_outlined,
size: 32,
color: WorkOrderColors.auxiliaryText,
color: context.appColors.textTertiary,
),
const SizedBox(height: 4),
Text(
attachment.fileName.isNotEmpty ? attachment.fileName : '图片',
style: TextStyle(
fontSize: 11,
color: WorkOrderColors.auxiliaryText,
color: context.appColors.textTertiary,
),
textAlign: TextAlign.center,
maxLines: 2,
@@ -551,24 +599,24 @@ class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
width: 80,
height: 80,
decoration: BoxDecoration(
color: const Color(0xFFF7F8FA),
color: context.appColors.fillBackground,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: const Color(0xFFE5E6EB)),
border: Border.all(color: context.appColors.divider),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icon(
Icons.video_library_outlined,
size: 32,
color: WorkOrderColors.primary,
color: context.appColors.primary,
),
const SizedBox(height: 4),
const Text(
Text(
'视频',
style: TextStyle(
fontSize: 11,
color: WorkOrderColors.auxiliaryText,
color: context.appColors.textTertiary,
),
textAlign: TextAlign.center,
maxLines: 2,
@@ -644,10 +692,10 @@ class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
width: double.infinity,
padding: const EdgeInsets.fromLTRB(16, 10, 16, 16),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
color: context.appColors.cardShadow,
blurRadius: 8,
offset: const Offset(0, -2),
),
@@ -658,8 +706,8 @@ class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
child: ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFF2F3F5),
foregroundColor: const Color(0xFF86909C),
backgroundColor: context.appColors.fillBackground,
foregroundColor: context.appColors.textTertiary,
elevation: 0,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
@@ -679,10 +727,10 @@ class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
width: double.infinity,
padding: const EdgeInsets.fromLTRB(16, 10, 16, 16),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
color: context.appColors.cardShadow,
blurRadius: 8,
offset: const Offset(0, -2),
),
@@ -698,10 +746,10 @@ class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
_showTransferDialog(workOrder);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: WorkOrderColors.primary,
backgroundColor: context.appColors.cardBackground,
foregroundColor: context.appColors.primary,
elevation: 0,
side: const BorderSide(color: WorkOrderColors.primary),
side: BorderSide(color: context.appColors.primary),
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 12,
@@ -728,7 +776,7 @@ class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
);
},
style: ElevatedButton.styleFrom(
backgroundColor: WorkOrderColors.primary,
backgroundColor: context.appColors.primary,
foregroundColor: Colors.white,
elevation: 0,
padding: const EdgeInsets.symmetric(vertical: 12),
@@ -752,10 +800,10 @@ class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
context.push('/workorder/detail/$widget.orderId/process');
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: WorkOrderColors.primary,
backgroundColor: context.appColors.cardBackground,
foregroundColor: context.appColors.primary,
elevation: 0,
side: const BorderSide(color: WorkOrderColors.primary),
side: BorderSide(color: context.appColors.primary),
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 12,
@@ -806,7 +854,7 @@ class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
label,
style: TextStyle(
fontSize: 13,
color: WorkOrderColors.auxiliaryText,
color: context.appColors.textTertiary,
),
),
const Spacer(),
@@ -818,7 +866,7 @@ class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: valueColor ?? WorkOrderColors.primaryText,
color: valueColor ?? context.appColors.textPrimary,
),
),
),
@@ -838,14 +886,14 @@ class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
}
}
Color _getPriorityColor(WorkOrderPriority priority) {
Color _getPriorityColor(BuildContext context, WorkOrderPriority priority) {
switch (priority) {
case WorkOrderPriority.high:
return const Color(0xFFFF4D4F);
return context.appColors.danger;
case WorkOrderPriority.medium:
return const Color(0xFFFF7D00);
return context.appColors.warning;
case WorkOrderPriority.low:
return WorkOrderColors.auxiliaryText;
return context.appColors.textTertiary;
}
}
@@ -862,16 +910,16 @@ class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
}
}
Color _getStatusColor(WorkOrderStatus status) {
Color _getStatusColor(BuildContext context, WorkOrderStatus status) {
switch (status) {
case WorkOrderStatus.pending:
return const Color(0xFFFF7D00);
return context.appColors.warning;
case WorkOrderStatus.executing:
return WorkOrderColors.primary;
return context.appColors.primary;
case WorkOrderStatus.completed:
return const Color(0xFF00B42A);
return context.appColors.success;
case WorkOrderStatus.all:
return WorkOrderColors.auxiliaryText;
return context.appColors.textTertiary;
}
}

View File

@@ -4,6 +4,7 @@ import 'package:flutter/services.dart';
import 'package:get_it/get_it.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import 'package:video_player/video_player.dart';
import '../../../../../core/consts/workorder_consts.dart';
import '../../domain/entities/workorder_entity.dart';
@@ -101,35 +102,43 @@ class _WorkOrderOnSitePageState extends State<WorkOrderOnSitePage> {
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.dark.copyWith(
value: isDark
? SystemUiOverlayStyle.light.copyWith(
statusBarColor: Colors.transparent,
)
: SystemUiOverlayStyle.dark.copyWith(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.dark,
),
child: Scaffold(
backgroundColor: const Color(0xFFF5F5F5),
backgroundColor: context.appColors.pageBackground,
appBar: AppBar(
backgroundColor: Colors.white,
backgroundColor: context.appColors.cardBackground,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Color(0xFF1F2329)),
icon: Icon(Icons.arrow_back, color: context.appColors.textPrimary),
onPressed: () => context.pop(_dataChanged),
),
title: const Text(
title: Text(
'现场执行',
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.w600,
color: Color(0xFF1F2329),
color: context.appColors.textPrimary,
),
),
centerTitle: true,
actions: [
TextButton(
onPressed: () {},
child: const Text(
child: Text(
'更多',
style: TextStyle(fontSize: 15, color: Color(0xFF1890FF)),
style: TextStyle(
fontSize: 15,
color: context.appColors.primary,
),
),
),
],
@@ -166,7 +175,7 @@ class _WorkOrderOnSitePageState extends State<WorkOrderOnSitePage> {
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
),
child: Column(
@@ -178,10 +187,10 @@ class _WorkOrderOnSitePageState extends State<WorkOrderOnSitePage> {
Expanded(
child: Text(
workOrder.orderNo,
style: const TextStyle(
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF1F2329),
color: context.appColors.textPrimary,
),
),
),
@@ -192,7 +201,7 @@ class _WorkOrderOnSitePageState extends State<WorkOrderOnSitePage> {
vertical: 4,
),
decoration: BoxDecoration(
color: const Color(0xFFFF4D4F),
color: context.appColors.danger,
borderRadius: BorderRadius.circular(4),
),
child: const Text(
@@ -242,7 +251,10 @@ class _WorkOrderOnSitePageState extends State<WorkOrderOnSitePage> {
children: [
Text(
label,
style: const TextStyle(fontSize: 13, color: Color(0xFF86909C)),
style: TextStyle(
fontSize: 13,
color: context.appColors.textTertiary,
),
),
const SizedBox(height: 4),
Row(
@@ -250,19 +262,19 @@ class _WorkOrderOnSitePageState extends State<WorkOrderOnSitePage> {
Expanded(
child: Text(
value,
style: const TextStyle(
style: TextStyle(
fontSize: 13,
color: Color(0xFF1F2329),
color: context.appColors.textPrimary,
),
),
),
if (showArrow)
const Padding(
Padding(
padding: EdgeInsets.only(left: 8),
child: Icon(
Icons.chevron_right,
size: 18,
color: Color(0xFFBBBBBB),
color: context.appColors.textTertiary,
),
),
],
@@ -279,32 +291,38 @@ class _WorkOrderOnSitePageState extends State<WorkOrderOnSitePage> {
children: [
Text(
label,
style: const TextStyle(fontSize: 13, color: Color(0xFF86909C)),
style: TextStyle(
fontSize: 13,
color: context.appColors.textTertiary,
),
),
Row(
children: [
Text(
value,
style: const TextStyle(fontSize: 13, color: Color(0xFF1F2329)),
style: TextStyle(
fontSize: 13,
color: context.appColors.textPrimary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
if (showLocation)
const Padding(
Padding(
padding: EdgeInsets.only(left: 8),
child: Icon(
Icons.location_on,
size: 16,
color: Color(0xFF1890FF),
color: context.appColors.primary,
),
),
if (showArrow)
const Padding(
Padding(
padding: EdgeInsets.only(left: 8),
child: Icon(
Icons.chevron_right,
size: 18,
color: Color(0xFFBBBBBB),
color: context.appColors.textTertiary,
),
),
],
@@ -327,13 +345,16 @@ class _WorkOrderOnSitePageState extends State<WorkOrderOnSitePage> {
children: [
Text(
label,
style: const TextStyle(fontSize: 13, color: Color(0xFF86909C)),
style: TextStyle(
fontSize: 13,
color: context.appColors.textTertiary,
),
),
Text(
value,
style: TextStyle(
fontSize: 13,
color: valueColor ?? const Color(0xFF1F2329),
color: valueColor ?? context.appColors.textPrimary,
fontWeight: FontWeight.w500,
),
),
@@ -358,13 +379,13 @@ class _WorkOrderOnSitePageState extends State<WorkOrderOnSitePage> {
Color _getStatusColor(WorkOrderStatus status) {
switch (status) {
case WorkOrderStatus.pending:
return const Color(0xFFFF7D00);
return context.appColors.warning;
case WorkOrderStatus.executing:
return const Color(0xFF1890FF);
return context.appColors.primary;
case WorkOrderStatus.completed:
return const Color(0xFF00B42A);
return context.appColors.success;
case WorkOrderStatus.all:
return const Color(0xFF86909C);
return context.appColors.textTertiary;
}
}
@@ -382,11 +403,11 @@ class _WorkOrderOnSitePageState extends State<WorkOrderOnSitePage> {
Color _getPriorityColor(WorkOrderPriority priority) {
switch (priority) {
case WorkOrderPriority.high:
return const Color(0xFFFF4D4F);
return context.appColors.danger;
case WorkOrderPriority.medium:
return const Color(0xFFFF7D00);
return context.appColors.warning;
case WorkOrderPriority.low:
return const Color(0xFF86909C);
return context.appColors.textTertiary;
}
}
@@ -425,18 +446,18 @@ class _WorkOrderOnSitePageState extends State<WorkOrderOnSitePage> {
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
Text(
'检查项清单 (5/7)',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
color: Color(0xFF1F2329),
color: context.appColors.textPrimary,
),
),
const SizedBox(height: 16),
@@ -457,10 +478,10 @@ class _WorkOrderOnSitePageState extends State<WorkOrderOnSitePage> {
style: TextStyle(
fontSize: 14,
color: item.checked
? const Color(0xFF52C41A)
? context.appColors.success
: item.current
? const Color(0xFF1890FF)
: const Color(0xFF1F2329),
? context.appColors.primary
: context.appColors.textPrimary,
),
),
Container(
@@ -470,16 +491,16 @@ class _WorkOrderOnSitePageState extends State<WorkOrderOnSitePage> {
shape: BoxShape.circle,
border: Border.all(
color: item.checked
? const Color(0xFF52C41A)
? context.appColors.success
: item.current
? const Color(0xFF1890FF)
: const Color(0xFFD9D9D9),
? context.appColors.primary
: context.appColors.divider,
width: 2,
),
color: item.checked
? const Color(0xFF52C41A)
? context.appColors.success
: item.current
? const Color(0xFFE6F7FF)
? context.appColors.primary.withOpacity(0.1)
: Colors.transparent,
),
child: item.checked
@@ -488,9 +509,9 @@ class _WorkOrderOnSitePageState extends State<WorkOrderOnSitePage> {
? Container(
width: 8,
height: 8,
decoration: const BoxDecoration(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Color(0xFF1890FF),
color: context.appColors.primary,
),
)
: Container(),
@@ -508,18 +529,18 @@ class _WorkOrderOnSitePageState extends State<WorkOrderOnSitePage> {
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
Text(
'现场记录',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
color: Color(0xFF1F2329),
color: context.appColors.textPrimary,
),
),
const SizedBox(height: 16),
@@ -535,16 +556,23 @@ class _WorkOrderOnSitePageState extends State<WorkOrderOnSitePage> {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
decoration: BoxDecoration(
color: const Color(0xFFF5F7FA),
color: context.appColors.fillBackground,
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
const Icon(Icons.mic_none, size: 24, color: Color(0xFFBBBBBB)),
Icon(
Icons.mic_none,
size: 24,
color: context.appColors.textTertiary,
),
const SizedBox(width: 12),
const Text(
Text(
'录音备注',
style: TextStyle(fontSize: 13, color: Color(0xFF86909C)),
style: TextStyle(
fontSize: 13,
color: context.appColors.textTertiary,
),
),
],
),
@@ -562,24 +590,30 @@ class _WorkOrderOnSitePageState extends State<WorkOrderOnSitePage> {
children: [
Row(
children: [
const Text(
Text(
'现场照片',
style: TextStyle(fontSize: 13, color: Color(0xFF86909C)),
style: TextStyle(
fontSize: 13,
color: context.appColors.textTertiary,
),
),
const SizedBox(width: 8),
Text(
'(${hasImages ? workOrder.attachments!.length : 0}张图片${hasVideos ? ' ${workOrder.videoUrls!.length}个视频' : ''})',
style: const TextStyle(fontSize: 13, color: Color(0xFF1890FF)),
style: TextStyle(fontSize: 13, color: context.appColors.primary),
),
],
),
const SizedBox(height: 12),
if (!hasImages && !hasVideos)
const Padding(
Padding(
padding: EdgeInsets.symmetric(vertical: 16),
child: Text(
'暂无照片或视频',
style: TextStyle(fontSize: 13, color: Color(0xFFBBBBBB)),
style: TextStyle(
fontSize: 13,
color: context.appColors.textTertiary,
),
),
)
else
@@ -613,8 +647,8 @@ class _WorkOrderOnSitePageState extends State<WorkOrderOnSitePage> {
height: 64,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: const Color(0xFFF7F8FA),
border: Border.all(color: const Color(0xFFE5E6EB)),
color: context.appColors.fillBackground,
border: Border.all(color: context.appColors.divider),
),
child: isImage
? ClipRRect(
@@ -623,19 +657,19 @@ class _WorkOrderOnSitePageState extends State<WorkOrderOnSitePage> {
url,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return const Icon(
return Icon(
Icons.image_outlined,
size: 24,
color: Color(0xFFBBBBBB),
color: context.appColors.textTertiary,
);
},
),
)
: const Center(
: Center(
child: Icon(
Icons.video_library_outlined,
size: 24,
color: Color(0xFF1890FF),
color: context.appColors.primary,
),
),
),
@@ -706,9 +740,13 @@ class _WorkOrderOnSitePageState extends State<WorkOrderOnSitePage> {
height: 64,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
border: Border.all(color: const Color(0xFFD9D9D9), width: 1),
border: Border.all(color: context.appColors.divider, width: 1),
),
child: Icon(
Icons.camera_alt,
size: 24,
color: context.appColors.textTertiary,
),
child: const Icon(Icons.camera_alt, size: 24, color: Color(0xFFBBBBBB)),
);
}
@@ -717,10 +755,10 @@ class _WorkOrderOnSitePageState extends State<WorkOrderOnSitePage> {
width: double.infinity,
padding: const EdgeInsets.fromLTRB(16, 10, 16, 16),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
color: context.appColors.cardShadow,
blurRadius: 8,
offset: const Offset(0, -2),
),
@@ -734,7 +772,7 @@ class _WorkOrderOnSitePageState extends State<WorkOrderOnSitePage> {
child: ElevatedButton(
onPressed: _isOperating ? null : _handleStartWork,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF52C41A),
backgroundColor: context.appColors.success,
foregroundColor: Colors.white,
elevation: 0,
padding: const EdgeInsets.symmetric(vertical: 12),
@@ -770,7 +808,7 @@ class _WorkOrderOnSitePageState extends State<WorkOrderOnSitePage> {
child: ElevatedButton(
onPressed: _isOperating ? null : _handleSuspendWork,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFFAAD14),
backgroundColor: context.appColors.warning,
foregroundColor: Colors.white,
elevation: 0,
padding: const EdgeInsets.symmetric(vertical: 12),
@@ -814,7 +852,7 @@ class _WorkOrderOnSitePageState extends State<WorkOrderOnSitePage> {
}
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF1890FF),
backgroundColor: context.appColors.primary,
foregroundColor: Colors.white,
elevation: 0,
padding: const EdgeInsets.symmetric(vertical: 12),

View File

@@ -7,8 +7,8 @@ import 'package:get_it/get_it.dart';
import 'package:go_router/go_router.dart';
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
import 'package:maibu_satabot_v2/core/network/dio_client.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import 'package:maibu_satabot_v2/features/v2/site/presentation/cubit/site_cubit.dart';
import '../../../../../core/consts/workorder_consts.dart';
import '../../../site/presentation/widgets/site_selector_widget.dart';
import '../cubit/workorder_cubit.dart';
import '../widgets/workorder_tabbar.dart';
@@ -79,12 +79,13 @@ class _WorkOrderPageState extends State<WorkOrderPage> {
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return BlocProvider.value(
value: _cubit,
child: AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.dark,
value: isDark ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark,
child: Scaffold(
backgroundColor: const Color(0xFFF7F8FA),
backgroundColor: context.appColors.pageBackground,
body: BlocConsumer<WorkOrderCubit, WorkOrderState>(
listener: (context, state) {
if (state is WorkOrderLoaded && state.errorMessage != null) {
@@ -115,7 +116,7 @@ class _WorkOrderPageState extends State<WorkOrderPage> {
Widget _buildLoadingView() {
return Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(AppConstants.primaryColor),
valueColor: AlwaysStoppedAnimation<Color>(context.appColors.primary),
),
);
}
@@ -128,14 +129,14 @@ class _WorkOrderPageState extends State<WorkOrderPage> {
Icon(
Icons.inbox_outlined,
size: 48,
color: AppConstants.auxiliaryTextColor,
color: context.appColors.textTertiary,
),
const SizedBox(height: 16),
Text(
'无数据',
style: TextStyle(
fontSize: 14,
color: AppConstants.auxiliaryTextColor,
color: context.appColors.textTertiary,
),
),
],
@@ -195,14 +196,18 @@ class _WorkOrderPageState extends State<WorkOrderPage> {
children: [
const Flexible(child: SiteSelectorWidget(compact: true)),
const SizedBox(width: 8),
Container(width: 1, height: 20, color: const Color(0xFFE5E6EB)),
Container(
width: 1,
height: 20,
color: context.appColors.divider,
),
const SizedBox(width: 8),
Text(
AppLocalizations.of(context).translate('work_order_v2.title'),
style: const TextStyle(
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Color(0xFF86909C),
color: context.appColors.textTertiary,
),
),
],
@@ -210,10 +215,10 @@ class _WorkOrderPageState extends State<WorkOrderPage> {
),
GestureDetector(
onTap: () => context.push('/workorder/report'),
child: const Icon(
child: Icon(
Icons.bar_chart,
size: 24,
color: Color(0xFF1D2129),
color: context.appColors.textPrimary,
),
),
],
@@ -237,17 +242,23 @@ class _WorkOrderPageState extends State<WorkOrderPage> {
child: Center(child: CircularProgressIndicator()),
);
} else if (state.hasMore) {
return const Padding(
return Padding(
padding: EdgeInsets.symmetric(vertical: 16),
child: Center(
child: Text('上滑加载更多', style: TextStyle(color: Color(0xFF86909C))),
child: Text(
'上滑加载更多',
style: TextStyle(color: context.appColors.textTertiary),
),
),
);
} else {
return const Padding(
return Padding(
padding: EdgeInsets.symmetric(vertical: 16),
child: Center(
child: Text('已加载全部数据', style: TextStyle(color: Color(0xFF86909C))),
child: Text(
'已加载全部数据',
style: TextStyle(color: context.appColors.textTertiary),
),
),
);
}

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
class WorkOrderProcessPage extends StatelessWidget {
final String orderId;
@@ -9,26 +10,26 @@ class WorkOrderProcessPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF5F5F5),
backgroundColor: context.appColors.pageBackground,
appBar: AppBar(
backgroundColor: Colors.white,
backgroundColor: context.appColors.cardBackground,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Color(0xFF1F2329)),
icon: Icon(Icons.arrow_back, color: context.appColors.textPrimary),
onPressed: () => context.pop(),
),
title: const Text(
title: Text(
'工单流程',
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.w600,
color: Color(0xFF1F2329),
color: context.appColors.textPrimary,
),
),
centerTitle: true,
actions: [
IconButton(
icon: const Icon(Icons.share, color: Color(0xFF86909C)),
icon: Icon(Icons.share, color: context.appColors.textTertiary),
onPressed: () {},
),
],
@@ -37,18 +38,18 @@ class WorkOrderProcessPage extends StatelessWidget {
padding: const EdgeInsets.all(16),
child: Column(
children: [
_buildProcessTimeline(),
_buildProcessTimeline(context),
const SizedBox(height: 24),
_buildExecutionRecords(),
_buildExecutionRecords(context),
const SizedBox(height: 32),
],
),
),
bottomNavigationBar: _buildBottomIndicator(),
bottomNavigationBar: _buildBottomIndicator(context),
);
}
Widget _buildProcessTimeline() {
Widget _buildProcessTimeline(BuildContext context) {
final processes = [
_ProcessStep(
status: ProcessStatus.completed,
@@ -92,7 +93,7 @@ class WorkOrderProcessPage extends StatelessWidget {
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
),
child: Column(
@@ -102,13 +103,14 @@ class WorkOrderProcessPage extends StatelessWidget {
final isLast = index == processes.length - 1;
final nextStatus = isLast ? null : processes[index + 1].status;
return _buildProcessItem(step, nextStatus, isLast);
return _buildProcessItem(context, step, nextStatus, isLast);
}).toList(),
),
);
}
Widget _buildProcessItem(
BuildContext context,
_ProcessStep step,
ProcessStatus? nextStatus,
bool isLast,
@@ -120,8 +122,8 @@ class WorkOrderProcessPage extends StatelessWidget {
children: [
Column(
children: [
_buildStatusIcon(step.status, 0),
if (showLine) _buildProcessLine(step.status, nextStatus!),
_buildStatusIcon(context, step.status, 0),
if (showLine) _buildProcessLine(context, step.status, nextStatus!),
],
),
const SizedBox(width: 12),
@@ -137,8 +139,8 @@ class WorkOrderProcessPage extends StatelessWidget {
fontSize: 14,
fontWeight: FontWeight.w600,
color: step.status == ProcessStatus.completed
? const Color(0xFF1F2329)
: const Color(0xFFBBBBBB),
? context.appColors.textPrimary
: context.appColors.textTertiary,
),
),
if (step.time.isNotEmpty) ...[
@@ -148,8 +150,8 @@ class WorkOrderProcessPage extends StatelessWidget {
style: TextStyle(
fontSize: 12,
color: step.status == ProcessStatus.completed
? const Color(0xFF86909C)
: const Color(0xFFBBBBBB),
? context.appColors.textTertiary
: context.appColors.textTertiary,
),
),
],
@@ -162,8 +164,8 @@ class WorkOrderProcessPage extends StatelessWidget {
style: TextStyle(
fontSize: 12,
color: step.status == ProcessStatus.completed
? const Color(0xFF86909C)
: const Color(0xFFBBBBBB),
? context.appColors.textTertiary
: context.appColors.textTertiary,
),
),
),
@@ -175,7 +177,7 @@ class WorkOrderProcessPage extends StatelessWidget {
);
}
Widget _buildStatusIcon(ProcessStatus status, int index) {
Widget _buildStatusIcon(BuildContext context, ProcessStatus status, int index) {
Widget icon;
Color iconColor;
Color backgroundColor;
@@ -183,18 +185,18 @@ class WorkOrderProcessPage extends StatelessWidget {
switch (status) {
case ProcessStatus.completed:
icon = const Icon(Icons.check, size: 16);
iconColor = const Color(0xFF52C41A);
backgroundColor = const Color(0xFFF6FFED);
iconColor = context.appColors.success;
backgroundColor = context.appColors.success.withOpacity(0.1);
break;
case ProcessStatus.current:
icon = const Icon(Icons.check, size: 16);
iconColor = const Color(0xFF1890FF);
backgroundColor = const Color(0xFFE6F7FF);
iconColor = context.appColors.primary;
backgroundColor = context.appColors.primary.withOpacity(0.1);
break;
case ProcessStatus.pending:
icon = Container();
iconColor = const Color(0xFFBBBBBB);
backgroundColor = const Color(0xFFFAFAFA);
iconColor = context.appColors.textTertiary;
backgroundColor = context.appColors.fillBackground;
break;
}
@@ -211,18 +213,19 @@ class WorkOrderProcessPage extends StatelessWidget {
}
Widget _buildProcessLine(
BuildContext context,
ProcessStatus currentStatus,
ProcessStatus nextStatus,
) {
Color lineColor;
if (currentStatus == ProcessStatus.completed &&
nextStatus == ProcessStatus.completed) {
lineColor = const Color(0xFF52C41A);
lineColor = context.appColors.success;
} else if (currentStatus == ProcessStatus.completed &&
nextStatus == ProcessStatus.current) {
lineColor = const Color(0xFF1890FF);
lineColor = context.appColors.primary;
} else {
lineColor = const Color(0xFFE8E8E8);
lineColor = context.appColors.divider;
}
return Container(
@@ -233,7 +236,7 @@ class WorkOrderProcessPage extends StatelessWidget {
);
}
Widget _buildExecutionRecords() {
Widget _buildExecutionRecords(BuildContext context) {
final records = [
_ExecutionRecord(time: '2025-05-21 09:20', action: '到达现场'),
_ExecutionRecord(time: '2025-05-21 09:35', action: '现场定位打卡'),
@@ -249,41 +252,47 @@ class WorkOrderProcessPage extends StatelessWidget {
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
Text(
'执行记录',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
color: Color(0xFF1F2329),
color: context.appColors.textPrimary,
),
),
const SizedBox(height: 16),
...records.map((record) => _buildRecordItem(record)),
...records.map((record) => _buildRecordItem(context, record)),
],
),
);
}
Widget _buildRecordItem(_ExecutionRecord record) {
Widget _buildRecordItem(BuildContext context, _ExecutionRecord record) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
Text(
record.time,
style: const TextStyle(fontSize: 12, color: Color(0xFF86909C)),
style: TextStyle(
fontSize: 12,
color: context.appColors.textTertiary,
),
),
const SizedBox(width: 32),
Expanded(
child: Text(
record.action,
style: const TextStyle(fontSize: 13, color: Color(0xFF1F2329)),
style: TextStyle(
fontSize: 13,
color: context.appColors.textPrimary,
),
),
),
],
@@ -291,15 +300,15 @@ class WorkOrderProcessPage extends StatelessWidget {
);
}
Widget _buildBottomIndicator() {
Widget _buildBottomIndicator(BuildContext context) {
return Container(
width: double.infinity,
padding: const EdgeInsets.fromLTRB(16, 10, 16, 16),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
color: context.appColors.cardShadow,
blurRadius: 8,
offset: const Offset(0, -2),
),
@@ -309,8 +318,8 @@ class WorkOrderProcessPage extends StatelessWidget {
height: 48,
child: Container(
width: double.infinity,
decoration: const BoxDecoration(
color: Color(0xFF1890FF),
decoration: BoxDecoration(
color: context.appColors.primary,
borderRadius: BorderRadius.all(Radius.circular(8)),
),
child: const Center(

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import '../../domain/entities/workorder_entity.dart';
import '../../../work_order/domain/usecases/work_order_usecases.dart';
@@ -45,18 +46,18 @@ class _WorkOrderReceiptPageState extends State<WorkOrderReceiptPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF7F8FA),
backgroundColor: context.appColors.pageBackground,
appBar: AppBar(
backgroundColor: Colors.white,
backgroundColor: context.appColors.cardBackground,
elevation: 0.5,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Color(0xFF1D2129)),
icon: Icon(Icons.arrow_back, color: context.appColors.textPrimary),
onPressed: () => context.pop(),
),
title: const Text(
title: Text(
'工单回执',
style: TextStyle(
color: Color(0xFF1D2129),
color: context.appColors.textPrimary,
fontSize: 17,
fontWeight: FontWeight.w600,
),
@@ -65,10 +66,10 @@ class _WorkOrderReceiptPageState extends State<WorkOrderReceiptPage> {
actions: [
TextButton(
onPressed: _isSubmitting ? null : _handleSubmit,
child: const Text(
child: Text(
'提交',
style: TextStyle(
color: Color(0xFF165DFF),
color: context.appColors.primary,
fontSize: 15,
fontWeight: FontWeight.w500,
),
@@ -183,19 +184,22 @@ class _WorkOrderReceiptPageState extends State<WorkOrderReceiptPage> {
Widget _buildOrderNumberCard() {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
color: Colors.white,
color: context.appColors.cardBackground,
child: Row(
children: [
const Text(
Text(
'工单编号',
style: TextStyle(color: Color(0xFF4E5969), fontSize: 14),
style: TextStyle(
color: context.appColors.textSecondary,
fontSize: 14,
),
),
const SizedBox(width: 12),
Expanded(
child: Text(
widget.workOrder.orderNo,
style: const TextStyle(
color: Color(0xFF1D2129),
style: TextStyle(
color: context.appColors.textPrimary,
fontSize: 14,
fontWeight: FontWeight.w500,
),
@@ -211,7 +215,7 @@ class _WorkOrderReceiptPageState extends State<WorkOrderReceiptPage> {
const results = ['已解决', '部分解决', '未解决'];
return Container(
padding: const EdgeInsets.all(16),
color: Colors.white,
color: context.appColors.cardBackground,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -228,16 +232,22 @@ class _WorkOrderReceiptPageState extends State<WorkOrderReceiptPage> {
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: selected ? const Color(0xFF165DFF) : Colors.white,
color: selected
? context.appColors.primary
: context.appColors.cardBackground,
border: Border.all(
color: selected ? const Color(0xFF165DFF) : const Color(0xFFE5E6EB),
color: selected
? context.appColors.primary
: context.appColors.divider,
),
),
child: Text(
results[index],
style: TextStyle(
fontSize: 13,
color: selected ? Colors.white : const Color(0xFF4E5969),
color: selected
? Colors.white
: context.appColors.textSecondary,
fontWeight: selected ? FontWeight.w500 : FontWeight.normal,
),
),
@@ -260,7 +270,7 @@ class _WorkOrderReceiptPageState extends State<WorkOrderReceiptPage> {
}) {
return Container(
padding: const EdgeInsets.all(16),
color: Colors.white,
color: context.appColors.cardBackground,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -268,27 +278,27 @@ class _WorkOrderReceiptPageState extends State<WorkOrderReceiptPage> {
const SizedBox(height: 10),
TextField(
controller: controller,
style: const TextStyle(fontSize: 14, color: Color(0xFF1D2129)),
style: TextStyle(fontSize: 14, color: context.appColors.textPrimary),
decoration: InputDecoration(
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
filled: true,
fillColor: const Color(0xFFF7F8FA),
fillColor: context.appColors.fillBackground,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(6),
borderSide: const BorderSide(color: Color(0xFFE5E6EB)),
borderSide: BorderSide(color: context.appColors.divider),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(6),
borderSide: const BorderSide(color: Color(0xFFE5E6EB)),
borderSide: BorderSide(color: context.appColors.divider),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(6),
borderSide: const BorderSide(color: Color(0xFF165DFF)),
borderSide: BorderSide(color: context.appColors.primary),
),
isDense: true,
suffixIcon: PopupMenuButton<String>(
padding: EdgeInsets.zero,
icon: const Icon(Icons.keyboard_arrow_down, color: Color(0xFF86909C), size: 20),
icon: Icon(Icons.keyboard_arrow_down, color: context.appColors.textTertiary, size: 20),
onSelected: (value) {
controller.text = value;
},
@@ -310,7 +320,7 @@ class _WorkOrderReceiptPageState extends State<WorkOrderReceiptPage> {
}) {
return Container(
padding: const EdgeInsets.all(16),
color: Colors.white,
color: context.appColors.cardBackground,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -319,24 +329,24 @@ class _WorkOrderReceiptPageState extends State<WorkOrderReceiptPage> {
TextField(
controller: controller,
maxLines: maxLines,
style: const TextStyle(fontSize: 14, color: Color(0xFF1D2129), height: 1.5),
style: TextStyle(fontSize: 14, color: context.appColors.textPrimary, height: 1.5),
decoration: InputDecoration(
hintText: hintText,
hintStyle: const TextStyle(color: Color(0xFFC9CDD4), fontSize: 14),
hintStyle: TextStyle(color: context.appColors.textTertiary, fontSize: 14),
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
filled: true,
fillColor: const Color(0xFFF7F8FA),
fillColor: context.appColors.fillBackground,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(6),
borderSide: const BorderSide(color: Color(0xFFE5E6EB)),
borderSide: BorderSide(color: context.appColors.divider),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(6),
borderSide: const BorderSide(color: Color(0xFFE5E6EB)),
borderSide: BorderSide(color: context.appColors.divider),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(6),
borderSide: const BorderSide(color: Color(0xFF165DFF)),
borderSide: BorderSide(color: context.appColors.primary),
),
isDense: true,
),
@@ -355,16 +365,19 @@ class _WorkOrderReceiptPageState extends State<WorkOrderReceiptPage> {
return Container(
padding: const EdgeInsets.all(16),
color: Colors.white,
color: context.appColors.cardBackground,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildLabel('现场照片', required: true),
const SizedBox(height: 12),
if (!hasImages && !hasVideos)
const Text(
Text(
'暂无照片或视频',
style: TextStyle(fontSize: 13, color: Color(0xFFBBBBBB)),
style: TextStyle(
fontSize: 13,
color: context.appColors.textTertiary,
),
)
else
Wrap(
@@ -394,8 +407,11 @@ class _WorkOrderReceiptPageState extends State<WorkOrderReceiptPage> {
return Container(
width: 72,
height: 72,
color: const Color(0xFFF7F8FA),
child: const Icon(Icons.broken_image, color: Color(0xFFBBBBBB)),
color: this.context.appColors.fillBackground,
child: Icon(
Icons.broken_image,
color: this.context.appColors.textTertiary,
),
);
},
),
@@ -407,11 +423,15 @@ class _WorkOrderReceiptPageState extends State<WorkOrderReceiptPage> {
width: 72,
height: 72,
decoration: BoxDecoration(
color: const Color(0xFFF7F8FA),
color: context.appColors.fillBackground,
borderRadius: BorderRadius.circular(6),
border: Border.all(color: const Color(0xFFE5E6EB)),
border: Border.all(color: context.appColors.divider),
),
child: Icon(
Icons.video_library_outlined,
size: 24,
color: context.appColors.primary,
),
child: const Icon(Icons.video_library_outlined, size: 24, color: Color(0xFF1890FF)),
);
}
@@ -424,11 +444,15 @@ class _WorkOrderReceiptPageState extends State<WorkOrderReceiptPage> {
width: 72,
height: 72,
decoration: BoxDecoration(
color: const Color(0xFFF7F8FA),
color: context.appColors.fillBackground,
borderRadius: BorderRadius.circular(6),
border: Border.all(color: const Color(0xFFE5E6EB)),
border: Border.all(color: context.appColors.divider),
),
child: Icon(
Icons.add,
color: context.appColors.textTertiary,
size: 28,
),
child: const Icon(Icons.add, color: Color(0xFFC9CDD4), size: 28),
),
);
}
@@ -437,7 +461,7 @@ class _WorkOrderReceiptPageState extends State<WorkOrderReceiptPage> {
Widget _buildSignatureCard() {
return Container(
padding: const EdgeInsets.all(16),
color: Colors.white,
color: context.appColors.cardBackground,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -449,9 +473,12 @@ class _WorkOrderReceiptPageState extends State<WorkOrderReceiptPage> {
onTap: () {
// TODO: 清除签名
},
child: const Text(
child: Text(
'清空',
style: TextStyle(color: Color(0xFF165DFF), fontSize: 13),
style: TextStyle(
color: context.appColors.primary,
fontSize: 13,
),
),
),
],
@@ -461,14 +488,18 @@ class _WorkOrderReceiptPageState extends State<WorkOrderReceiptPage> {
height: 100,
width: double.infinity,
decoration: BoxDecoration(
color: const Color(0xFFF7F8FA),
color: context.appColors.fillBackground,
borderRadius: BorderRadius.circular(6),
border: Border.all(color: const Color(0xFFE5E6EB)),
border: Border.all(color: context.appColors.divider),
),
child: const Center(
child: Center(
child: Text(
'李建国',
style: TextStyle(fontSize: 24, color: Color(0xFF1D2129), fontFamily: 'KaiTi'),
style: TextStyle(
fontSize: 24,
color: context.appColors.textPrimary,
fontFamily: 'KaiTi',
),
),
),
),
@@ -482,14 +513,14 @@ class _WorkOrderReceiptPageState extends State<WorkOrderReceiptPage> {
return Row(
children: [
if (required)
const Text(
Text(
'* ',
style: TextStyle(color: Color(0xFFF53F3F), fontSize: 14),
style: TextStyle(color: context.appColors.danger, fontSize: 14),
),
Text(
text,
style: const TextStyle(
color: Color(0xFF4E5969),
style: TextStyle(
color: context.appColors.textSecondary,
fontSize: 14,
fontWeight: FontWeight.w500,
),
@@ -507,16 +538,18 @@ class _WorkOrderReceiptPageState extends State<WorkOrderReceiptPage> {
top: 12,
bottom: 12 + MediaQuery.of(context).padding.bottom,
),
decoration: const BoxDecoration(
color: Colors.white,
border: Border(top: BorderSide(color: Color(0xFFE5E6EB), width: 0.5)),
decoration: BoxDecoration(
color: context.appColors.cardBackground,
border: Border(
top: BorderSide(color: context.appColors.divider, width: 0.5),
),
),
child: SizedBox(
height: 48,
child: ElevatedButton(
onPressed: _isSubmitting ? null : _handleSubmit,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF165DFF),
backgroundColor: context.appColors.primary,
foregroundColor: Colors.white,
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:go_router/go_router.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
class WorkOrderReportPage extends StatefulWidget {
const WorkOrderReportPage({super.key});
@@ -37,18 +38,18 @@ class _WorkOrderReportPageState extends State<WorkOrderReportPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF7F8FA),
backgroundColor: context.appColors.pageBackground,
appBar: AppBar(
backgroundColor: Colors.white,
backgroundColor: context.appColors.cardBackground,
elevation: 0.5,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Color(0xFF1D2129)),
icon: Icon(Icons.arrow_back, color: context.appColors.textPrimary),
onPressed: () => context.pop(),
),
title: const Text(
title: Text(
'移动报表概览',
style: TextStyle(
color: Color(0xFF1D2129),
color: context.appColors.textPrimary,
fontSize: 17,
fontWeight: FontWeight.w600,
),
@@ -57,10 +58,10 @@ class _WorkOrderReportPageState extends State<WorkOrderReportPage> {
actions: [
TextButton(
onPressed: () {},
child: const Text(
child: Text(
'更多',
style: TextStyle(
color: Color(0xFF165DFF),
color: context.appColors.primary,
fontSize: 14,
),
),
@@ -93,16 +94,27 @@ class _WorkOrderReportPageState extends State<WorkOrderReportPage> {
Widget _buildTimeRange() {
return Row(
children: [
const Icon(Icons.calendar_today, size: 16, color: Color(0xFF4E5969)),
Icon(
Icons.calendar_today,
size: 16,
color: context.appColors.textSecondary,
),
const SizedBox(width: 6),
const Text(
Text(
'本周 05-19 ~ 05-25',
style: TextStyle(fontSize: 14, color: Color(0xFF4E5969)),
style: TextStyle(
fontSize: 14,
color: context.appColors.textSecondary,
),
),
const Spacer(),
GestureDetector(
onTap: () {},
child: const Icon(Icons.keyboard_arrow_down, size: 20, color: Color(0xFF86909C)),
child: Icon(
Icons.keyboard_arrow_down,
size: 20,
color: context.appColors.textTertiary,
),
),
],
);
@@ -112,11 +124,11 @@ class _WorkOrderReportPageState extends State<WorkOrderReportPage> {
Widget _buildMetricCards() {
return Row(
children: [
Expanded(child: _metricCard('个人工单总数', '$_totalOrders', '单', const Color(0xFFE8F3FF), const Color(0xFF165DFF))),
Expanded(child: _metricCard('个人工单总数', '$_totalOrders', '单', context.appColors.primary.withOpacity(0.1), context.appColors.primary)),
const SizedBox(width: 10),
Expanded(child: _metricCard('已完成', '$_completedOrders', '单', const Color(0xFFE8FFEA), const Color(0xFF00B42A))),
Expanded(child: _metricCard('已完成', '$_completedOrders', '单', context.appColors.success.withOpacity(0.1), context.appColors.success)),
const SizedBox(width: 10),
Expanded(child: _metricCard('完成率', '$_completionRate', '%', const Color(0xFFFFF3E0), const Color(0xFFFF7D00))),
Expanded(child: _metricCard('完成率', '$_completionRate', '%', context.appColors.warning.withOpacity(0.1), context.appColors.warning)),
],
);
}
@@ -131,7 +143,7 @@ class _WorkOrderReportPageState extends State<WorkOrderReportPage> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: const TextStyle(fontSize: 12, color: Color(0xFF86909C))),
Text(title, style: TextStyle(fontSize: 12, color: context.appColors.textTertiary)),
const SizedBox(height: 8),
RichText(
text: TextSpan(
@@ -157,13 +169,13 @@ class _WorkOrderReportPageState extends State<WorkOrderReportPage> {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('工单类型分布', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: Color(0xFF1D2129))),
Text('工单类型分布', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: context.appColors.textPrimary)),
const SizedBox(height: 16),
Row(
children: [
@@ -204,10 +216,10 @@ class _WorkOrderReportPageState extends State<WorkOrderReportPage> {
),
),
const SizedBox(width: 8),
Expanded(child: Text(d.label, style: const TextStyle(fontSize: 13, color: Color(0xFF4E5969)))),
Expanded(child: Text(d.label, style: TextStyle(fontSize: 13, color: context.appColors.textSecondary))),
Text(
'${d.value.toStringAsFixed(0)}%',
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: Color(0xFF1D2129)),
style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: context.appColors.textPrimary),
),
],
),
@@ -227,7 +239,7 @@ class _WorkOrderReportPageState extends State<WorkOrderReportPage> {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
),
child: Column(
@@ -235,11 +247,11 @@ class _WorkOrderReportPageState extends State<WorkOrderReportPage> {
children: [
Row(
children: [
const Text('本周趋势', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: Color(0xFF1D2129))),
Text('本周趋势', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: context.appColors.textPrimary)),
const Spacer(),
_legendDot(const Color(0xFF165DFF), '创建数'),
_legendDot(context.appColors.primary, '创建数'),
const SizedBox(width: 12),
_legendDot(const Color(0xFF00B42A), '完成数'),
_legendDot(context.appColors.success, '完成数'),
],
),
const SizedBox(height: 16),
@@ -255,13 +267,13 @@ class _WorkOrderReportPageState extends State<WorkOrderReportPage> {
barRods: [
BarChartRodData(
toY: _createdData[i].toDouble(),
color: const Color(0xFF165DFF),
color: context.appColors.primary,
width: 10,
borderRadius: const BorderRadius.vertical(top: Radius.circular(3)),
),
BarChartRodData(
toY: _completedData[i].toDouble(),
color: const Color(0xFF00B42A),
color: context.appColors.success,
width: 10,
borderRadius: const BorderRadius.vertical(top: Radius.circular(3)),
),
@@ -275,7 +287,7 @@ class _WorkOrderReportPageState extends State<WorkOrderReportPage> {
reservedSize: 30,
getTitlesWidget: (value, meta) {
if (value % 6 == 0) {
return Text('${value.toInt()}', style: const TextStyle(fontSize: 11, color: Color(0xFF86909C)));
return Text('${value.toInt()}', style: TextStyle(fontSize: 11, color: context.appColors.textTertiary));
}
return const SizedBox.shrink();
},
@@ -289,7 +301,7 @@ class _WorkOrderReportPageState extends State<WorkOrderReportPage> {
if (i >= 0 && i < _trendDays.length) {
return Padding(
padding: const EdgeInsets.only(top: 6),
child: Text(_trendDays[i], style: const TextStyle(fontSize: 10, color: Color(0xFF86909C))),
child: Text(_trendDays[i], style: TextStyle(fontSize: 10, color: context.appColors.textTertiary)),
);
}
return const SizedBox.shrink();
@@ -304,7 +316,7 @@ class _WorkOrderReportPageState extends State<WorkOrderReportPage> {
drawVerticalLine: false,
horizontalInterval: 6,
getDrawingHorizontalLine: (value) => FlLine(
color: const Color(0xFFF0F0F0),
color: context.appColors.divider,
strokeWidth: 1,
),
),
@@ -323,7 +335,7 @@ class _WorkOrderReportPageState extends State<WorkOrderReportPage> {
children: [
Container(width: 8, height: 8, decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(2))),
const SizedBox(width: 4),
Text(label, style: const TextStyle(fontSize: 12, color: Color(0xFF86909C))),
Text(label, style: TextStyle(fontSize: 12, color: context.appColors.textTertiary)),
],
);
}
@@ -333,13 +345,13 @@ class _WorkOrderReportPageState extends State<WorkOrderReportPage> {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('待处理工单 TOP5', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: Color(0xFF1D2129))),
Text('待处理工单 TOP5', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: context.appColors.textPrimary)),
const SizedBox(height: 12),
...List.generate(_topPendingList.length, (i) {
final item = _topPendingList[i];
@@ -352,7 +364,9 @@ class _WorkOrderReportPageState extends State<WorkOrderReportPage> {
width: 22,
height: 22,
decoration: BoxDecoration(
color: i < 3 ? const Color(0xFF165DFF) : const Color(0xFFC9CDD4),
color: i < 3
? context.appColors.primary
: context.appColors.fillBackground,
borderRadius: BorderRadius.circular(4),
),
child: Center(
@@ -361,7 +375,9 @@ class _WorkOrderReportPageState extends State<WorkOrderReportPage> {
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: i < 3 ? Colors.white : const Color(0xFF86909C),
color: i < 3
? Colors.white
: context.appColors.textTertiary,
),
),
),
@@ -371,23 +387,27 @@ class _WorkOrderReportPageState extends State<WorkOrderReportPage> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(item.title, style: const TextStyle(fontSize: 14, color: Color(0xFF1D2129))),
Text(item.title, style: TextStyle(fontSize: 14, color: context.appColors.textPrimary)),
const SizedBox(height: 2),
Text(item.area, style: const TextStyle(fontSize: 12, color: Color(0xFF86909C))),
Text(item.area, style: TextStyle(fontSize: 12, color: context.appColors.textTertiary)),
],
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: isUrgent ? const Color(0xFFFFECE8) : const Color(0xFFF7F8FA),
color: isUrgent
? context.appColors.danger.withOpacity(0.1)
: context.appColors.fillBackground,
borderRadius: BorderRadius.circular(4),
),
child: Text(
'剩余${item.remainHours}h',
style: TextStyle(
fontSize: 12,
color: isUrgent ? const Color(0xFFF53F3F) : const Color(0xFF86909C),
color: isUrgent
? context.appColors.danger
: context.appColors.textTertiary,
fontWeight: FontWeight.w500,
),
),
@@ -411,8 +431,8 @@ class _WorkOrderReportPageState extends State<WorkOrderReportPage> {
icon: const Icon(Icons.download, size: 18),
label: const Text('导出报表', style: TextStyle(fontSize: 14)),
style: OutlinedButton.styleFrom(
foregroundColor: const Color(0xFF165DFF),
side: const BorderSide(color: Color(0xFF165DFF)),
foregroundColor: context.appColors.primary,
side: BorderSide(color: context.appColors.primary),
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
@@ -425,7 +445,7 @@ class _WorkOrderReportPageState extends State<WorkOrderReportPage> {
icon: const Icon(Icons.share, size: 18),
label: const Text('分享', style: TextStyle(fontSize: 14)),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF165DFF),
backgroundColor: context.appColors.primary,
foregroundColor: Colors.white,
elevation: 0,
padding: const EdgeInsets.symmetric(vertical: 12),

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import '../../../../../core/consts/workorder_consts.dart';
import '../../domain/entities/workorder_entity.dart';
@@ -18,41 +19,51 @@ class WorkOrderCountCard extends StatelessWidget {
),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: AppConstants.backgroundColor,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
boxShadow: AppConstants.cardShadow,
boxShadow: [
BoxShadow(
color: context.appColors.cardShadow,
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Row(
children: [
_buildCountItem(
context,
label: AppLocalizations.of(
context,
).translate('work_order_v2.pending'),
value: count.pendingCount.toString(),
valueColor: WorkOrderColors.primaryText,
valueColor: context.appColors.textPrimary,
),
_buildDivider(),
_buildDivider(context),
_buildCountItem(
context,
label: AppLocalizations.of(
context,
).translate('work_order_v2.executing'),
value: count.executingCount.toString(),
valueColor: WorkOrderColors.primaryText,
valueColor: context.appColors.textPrimary,
),
_buildDivider(),
_buildDivider(context),
_buildCountItem(
context,
label: AppLocalizations.of(
context,
).translate('work_order_v2.today_completed'),
value: count.todayCompletedCount.toString(),
valueColor: WorkOrderColors.primary,
valueColor: context.appColors.primary,
),
],
),
);
}
Widget _buildCountItem({
Widget _buildCountItem(
BuildContext context, {
required String label,
required String value,
required Color valueColor,
@@ -64,7 +75,7 @@ class WorkOrderCountCard extends StatelessWidget {
label,
style: TextStyle(
fontSize: 13,
color: AppConstants.auxiliaryTextColor,
color: context.appColors.textTertiary,
),
),
const SizedBox(height: 8),
@@ -81,7 +92,11 @@ class WorkOrderCountCard extends StatelessWidget {
);
}
Widget _buildDivider() {
return Container(width: 1, height: 40, color: const Color(0xFFF0F0F0));
Widget _buildDivider(BuildContext context) {
return Container(
width: 1,
height: 40,
color: context.appColors.divider,
);
}
}

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import '../../../../../core/consts/workorder_consts.dart';
import '../../domain/entities/workorder_entity.dart';
@@ -22,9 +23,15 @@ class WorkOrderItemCard extends StatelessWidget {
),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppConstants.backgroundColor,
color: context.appColors.cardBackground,
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
boxShadow: AppConstants.cardShadow,
boxShadow: [
BoxShadow(
color: context.appColors.cardShadow,
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Stack(
children: [
@@ -41,7 +48,7 @@ class WorkOrderItemCard extends StatelessWidget {
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: AppConstants.primaryTextColor,
color: context.appColors.textPrimary,
),
),
),
@@ -52,6 +59,7 @@ class WorkOrderItemCard extends StatelessWidget {
// 详情信息
_buildDetailRow(
context,
AppLocalizations.of(
context,
).translate('work_order_v2.order_no'),
@@ -61,17 +69,19 @@ class WorkOrderItemCard extends StatelessWidget {
// 优先级
_buildDetailRow(
context,
AppLocalizations.of(
context,
).translate('work_order_v2.priority'),
_getPriorityText(context, workOrder.priority),
valueColor: _getPriorityColor(workOrder.priority),
valueColor: _getPriorityColor(context, workOrder.priority),
),
const SizedBox(height: 6),
// 根据状态显示不同信息
if (workOrder.status == WorkOrderStatus.pending) ...[
_buildDetailRow(
context,
AppLocalizations.of(
context,
).translate('work_order_v2.create_time'),
@@ -79,6 +89,7 @@ class WorkOrderItemCard extends StatelessWidget {
),
const SizedBox(height: 6),
_buildDetailRow(
context,
AppLocalizations.of(
context,
).translate('work_order_v2.location'),
@@ -86,6 +97,7 @@ class WorkOrderItemCard extends StatelessWidget {
),
] else if (workOrder.status == WorkOrderStatus.executing) ...[
_buildDetailRow(
context,
AppLocalizations.of(
context,
).translate('work_order_v2.executor'),
@@ -93,6 +105,7 @@ class WorkOrderItemCard extends StatelessWidget {
),
const SizedBox(height: 6),
_buildDetailRow(
context,
AppLocalizations.of(
context,
).translate('work_order_v2.location'),
@@ -104,6 +117,7 @@ class WorkOrderItemCard extends StatelessWidget {
],
] else if (workOrder.status == WorkOrderStatus.completed) ...[
_buildDetailRow(
context,
AppLocalizations.of(
context,
).translate('work_order_v2.complete_time'),
@@ -113,6 +127,7 @@ class WorkOrderItemCard extends StatelessWidget {
),
const SizedBox(height: 6),
_buildDetailRow(
context,
AppLocalizations.of(
context,
).translate('work_order_v2.location'),
@@ -148,23 +163,23 @@ class WorkOrderItemCard extends StatelessWidget {
switch (workOrder.status) {
case WorkOrderStatus.pending:
color = AppConstants.pendingColor;
color = context.appColors.warning;
text = AppLocalizations.of(context).translate('work_order_v2.pending');
break;
case WorkOrderStatus.executing:
color = AppConstants.executingColor;
color = context.appColors.primary;
text = AppLocalizations.of(
context,
).translate('work_order_v2.executing');
break;
case WorkOrderStatus.completed:
color = AppConstants.completedColor;
color = context.appColors.success;
text = AppLocalizations.of(
context,
).translate('work_order_v2.completed');
break;
default:
color = AppConstants.auxiliaryTextColor;
color = context.appColors.textTertiary;
text = AppLocalizations.of(context).translate('common.unknown');
}
@@ -196,7 +211,12 @@ class WorkOrderItemCard extends StatelessWidget {
);
}
Widget _buildDetailRow(String label, String value, {Color? valueColor}) {
Widget _buildDetailRow(
BuildContext context,
String label,
String value, {
Color? valueColor,
}) {
return Row(
children: [
Flexible(
@@ -204,7 +224,7 @@ class WorkOrderItemCard extends StatelessWidget {
'$label:',
style: TextStyle(
fontSize: 13,
color: AppConstants.auxiliaryTextColor,
color: context.appColors.textTertiary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
@@ -215,7 +235,7 @@ class WorkOrderItemCard extends StatelessWidget {
value,
style: TextStyle(
fontSize: 13,
color: valueColor ?? AppConstants.secondaryTextColor,
color: valueColor ?? context.appColors.textSecondary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
@@ -235,15 +255,15 @@ class WorkOrderItemCard extends StatelessWidget {
'${AppLocalizations.of(context).translate('work_order_v2.progress')}:',
style: TextStyle(
fontSize: 13,
color: AppConstants.auxiliaryTextColor,
color: context.appColors.textTertiary,
),
),
Expanded(
child: LinearProgressIndicator(
value: workOrder.progress! / 100,
backgroundColor: const Color(0xFFF0F0F0),
backgroundColor: context.appColors.divider,
valueColor: AlwaysStoppedAnimation<Color>(
AppConstants.primaryColor,
context.appColors.primary,
),
minHeight: 6,
borderRadius: BorderRadius.circular(3),
@@ -266,14 +286,14 @@ class WorkOrderItemCard extends StatelessWidget {
}
}
Color _getPriorityColor(WorkOrderPriority priority) {
Color _getPriorityColor(BuildContext context, WorkOrderPriority priority) {
switch (priority) {
case WorkOrderPriority.high:
return AppConstants.pendingColor;
return context.appColors.warning;
case WorkOrderPriority.medium:
return AppConstants.pendingColor;
return context.appColors.warning;
case WorkOrderPriority.low:
return AppConstants.auxiliaryTextColor;
return context.appColors.textTertiary;
}
}

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import '../../../../../core/consts/workorder_consts.dart';
import '../../domain/entities/workorder_entity.dart';
/// 工单标签栏组件
/// 展示待处理/执行中/已完成/全部四个静态标签
@@ -86,8 +86,8 @@ class WorkOrderTabBar extends StatelessWidget {
fontWeight:
isSelected ? FontWeight.bold : FontWeight.normal,
color: isSelected
? AppConstants.primaryColor
: AppConstants.secondaryTextColor,
? context.appColors.primary
: context.appColors.textSecondary,
),
),
if (showBadge)
@@ -111,7 +111,7 @@ class WorkOrderTabBar extends StatelessWidget {
width: 24,
height: 3,
decoration: BoxDecoration(
color: AppConstants.primaryColor,
color: context.appColors.primary,
borderRadius: BorderRadius.circular(1.5),
),
),

View File

@@ -12,6 +12,7 @@ import 'package:maibu_satabot_v2/core/app/app_user_cubit.dart';
import 'package:maibu_satabot_v2/core/infrastructure/logging/app_bloc_observer.dart';
import 'package:maibu_satabot_v2/core/logging/i_logger_service.dart';
import 'package:maibu_satabot_v2/core/theme/AppTheme.dart';
import 'package:maibu_satabot_v2/core/theme/theme_cubit.dart';
import 'package:maibu_satabot_v2/core/update/update_cubit.dart';
import 'package:maibu_satabot_v2/core/update/version_check_service.dart';
import 'package:maibu_satabot_v2/core/update/update_dialog.dart';
@@ -71,6 +72,7 @@ class MyApp extends StatelessWidget {
sl<AuthCubit>().appStarted();
final deviceStatusBloc = sl<DeviceStatusBloc>();
final localeCubit = sl<LocaleCubit>();
final themeCubit = sl<ThemeCubit>();
return MultiBlocProvider(
providers: [
@@ -98,6 +100,7 @@ class MyApp extends StatelessWidget {
),
BlocProvider<DeviceStatusBloc>.value(value: deviceStatusBloc),
BlocProvider<LocaleCubit>.value(value: localeCubit),
BlocProvider<ThemeCubit>.value(value: themeCubit),
BlocProvider<TabConfigCubit>.value(value: sl<TabConfigCubit>()),
BlocProvider<PermissionRequestBloc>(
create: (_) => sl<PermissionRequestBloc>(),
@@ -113,6 +116,9 @@ class MyApp extends StatelessWidget {
child: BlocBuilder<LocaleCubit, Locale>(
bloc: localeCubit,
builder: (context, locale) {
return BlocBuilder<ThemeCubit, ThemeMode>(
bloc: themeCubit,
builder: (context, themeMode) {
return MaterialApp.router(
title: 'Maibu Satabot',
locale: locale,
@@ -124,12 +130,16 @@ class MyApp extends StatelessWidget {
GlobalCupertinoLocalizations.delegate,
],
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
themeMode: themeMode,
routerConfig: sl<GoRouter>(),
builder: (context, child) {
return _LifecycleListener(child: _UpdateChecker(child: child!));
},
);
},
);
},
),
);
}

View File

@@ -984,10 +984,10 @@ packages:
dependency: "direct main"
description:
name: mobile_scanner
sha256: ce3f059ebd6dbfab7292bba0e893e354b46730636820d3c9ef69005ce2d55bce
sha256: "1b60b8f9d4ce0cb0e7d7bc223c955d083a0737bee66fa1fcfe5de48225e0d5b3"
url: "https://pub.flutter-io.cn"
source: hosted
version: "7.4.0"
version: "3.5.7"
mqtt_client:
dependency: "direct main"
description:

View File

@@ -139,7 +139,7 @@ dependencies:
markdown_widget: ^2.0.0 # 产品参数用的 markdown 渲染
flutter_markdown: ^0.7.1
mobile_scanner: ^7.4.0
mobile_scanner: ^3.4.1
vibration: ^3.1.8
flutter_patcher: ^0.1.2 # Add flutter_patcher here
open_file: ^3.3.2 # 打开文件(安装 APK)