底部tab的灵活可控
This commit is contained in:
119
lib/features/main_container/domain/tab_config.dart
Normal file
119
lib/features/main_container/domain/tab_config.dart
Normal file
@@ -0,0 +1,119 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class TabConfigItem extends Equatable {
|
||||
final String id;
|
||||
final String name;
|
||||
final String nameEn;
|
||||
final String icon;
|
||||
final bool isEnabled;
|
||||
final int order;
|
||||
|
||||
const TabConfigItem({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.nameEn,
|
||||
required this.icon,
|
||||
required this.isEnabled,
|
||||
required this.order,
|
||||
});
|
||||
|
||||
TabConfigItem copyWith({
|
||||
String? id,
|
||||
String? name,
|
||||
String? nameEn,
|
||||
String? icon,
|
||||
bool? isEnabled,
|
||||
int? order,
|
||||
}) {
|
||||
return TabConfigItem(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
nameEn: nameEn ?? this.nameEn,
|
||||
icon: icon ?? this.icon,
|
||||
isEnabled: isEnabled ?? this.isEnabled,
|
||||
order: order ?? this.order,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [id, name, nameEn, icon, isEnabled, order];
|
||||
}
|
||||
|
||||
class TabConfig extends Equatable {
|
||||
final List<TabConfigItem> items;
|
||||
|
||||
const TabConfig({required this.items});
|
||||
|
||||
List<TabConfigItem> get enabledItems {
|
||||
final enabled = items.where((item) => item.isEnabled).toList();
|
||||
enabled.sort((a, b) => a.order.compareTo(b.order));
|
||||
return enabled;
|
||||
}
|
||||
|
||||
TabConfig copyWith({List<TabConfigItem>? items}) {
|
||||
return TabConfig(items: items ?? this.items);
|
||||
}
|
||||
|
||||
TabConfig toggleItem(String itemId) {
|
||||
final currentItem = items.firstWhere((item) => item.id == itemId);
|
||||
|
||||
// 如果当前项是启用的,检查是否是最后一个启用的
|
||||
if (currentItem.isEnabled) {
|
||||
final enabledCount = items.where((item) => item.isEnabled).length;
|
||||
if (enabledCount <= 1) {
|
||||
// 至少保留一个 Tab,不允许关闭
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
final updatedItems = items.map((item) {
|
||||
if (item.id == itemId) {
|
||||
return item.copyWith(isEnabled: !item.isEnabled);
|
||||
}
|
||||
return item;
|
||||
}).toList();
|
||||
return copyWith(items: updatedItems);
|
||||
}
|
||||
|
||||
static TabConfig defaultConfig() {
|
||||
return const TabConfig(
|
||||
items: [
|
||||
TabConfigItem(
|
||||
id: 'home',
|
||||
name: '状态',
|
||||
nameEn: 'Status',
|
||||
icon: 'grid_view_rounded',
|
||||
isEnabled: true,
|
||||
order: 1,
|
||||
),
|
||||
TabConfigItem(
|
||||
id: 'ai',
|
||||
name: 'AI',
|
||||
nameEn: 'AI',
|
||||
icon: 'auto_awesome_rounded',
|
||||
isEnabled: true,
|
||||
order: 2,
|
||||
),
|
||||
TabConfigItem(
|
||||
id: 'warning',
|
||||
name: '告警中心',
|
||||
nameEn: 'Alerts',
|
||||
icon: 'warning_amber_rounded',
|
||||
isEnabled: true,
|
||||
order: 3,
|
||||
),
|
||||
TabConfigItem(
|
||||
id: 'my',
|
||||
name: '我的',
|
||||
nameEn: 'Profile',
|
||||
icon: 'person_rounded',
|
||||
isEnabled: true,
|
||||
order: 4,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [items];
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:maibu_satabot_v2/features/main_container/domain/tab_config.dart';
|
||||
|
||||
class TabConfigState {
|
||||
final TabConfig config;
|
||||
final int selectedIndex;
|
||||
|
||||
const TabConfigState({
|
||||
required this.config,
|
||||
this.selectedIndex = 0,
|
||||
});
|
||||
|
||||
TabConfigState copyWith({
|
||||
TabConfig? config,
|
||||
int? selectedIndex,
|
||||
}) {
|
||||
return TabConfigState(
|
||||
config: config ?? this.config,
|
||||
selectedIndex: selectedIndex ?? this.selectedIndex,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TabConfigCubit extends Cubit<TabConfigState> {
|
||||
TabConfigCubit() : super(TabConfigState(config: TabConfig.defaultConfig()));
|
||||
|
||||
/// 切换 Tab 的启用状态
|
||||
void toggleTab(String tabId) {
|
||||
final newConfig = state.config.toggleItem(tabId);
|
||||
|
||||
// 如果当前选中的 Tab 被禁用,自动切换到第一个启用的 Tab
|
||||
int newIndex = state.selectedIndex;
|
||||
if (newIndex >= newConfig.enabledItems.length) {
|
||||
newIndex = 0;
|
||||
}
|
||||
|
||||
emit(state.copyWith(config: newConfig, selectedIndex: newIndex));
|
||||
}
|
||||
|
||||
/// 切换选中的 Tab
|
||||
void selectTab(int index) {
|
||||
if (index >= 0 && index < state.config.enabledItems.length) {
|
||||
emit(state.copyWith(selectedIndex: index));
|
||||
}
|
||||
}
|
||||
|
||||
/// 重置为默认配置
|
||||
void resetToDefault() {
|
||||
emit(TabConfigState(config: TabConfig.defaultConfig()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'dart:convert';
|
||||
import '../../domain/tab_config.dart';
|
||||
|
||||
abstract class TabConfigState {}
|
||||
|
||||
class TabConfigInitial extends TabConfigState {}
|
||||
|
||||
class TabConfigLoaded extends TabConfigState {
|
||||
final TabConfig config;
|
||||
|
||||
TabConfigLoaded(this.config);
|
||||
}
|
||||
|
||||
class TabConfigCubit extends Cubit<TabConfigState> {
|
||||
final SharedPreferences _prefs;
|
||||
static const String _configKey = 'tab_config';
|
||||
|
||||
TabConfigCubit(this._prefs) : super(TabConfigInitial()) {
|
||||
_loadConfig();
|
||||
}
|
||||
|
||||
void _loadConfig() {
|
||||
try {
|
||||
final configJson = _prefs.getString(_configKey);
|
||||
if (configJson != null) {
|
||||
final List<dynamic> decoded = jsonDecode(configJson);
|
||||
final items = decoded.map((e) {
|
||||
final map = Map<String, dynamic>.from(e);
|
||||
return TabConfigItem(
|
||||
id: map['id'] as String,
|
||||
name: map['name'] as String,
|
||||
nameEn: map['nameEn'] as String,
|
||||
icon: map['icon'] as String,
|
||||
isEnabled: map['isEnabled'] as bool,
|
||||
order: map['order'] as int,
|
||||
);
|
||||
}).toList();
|
||||
emit(TabConfigLoaded(TabConfig(items: items)));
|
||||
} else {
|
||||
final defaultConfig = TabConfig.defaultConfig();
|
||||
_saveConfig(defaultConfig);
|
||||
emit(TabConfigLoaded(defaultConfig));
|
||||
}
|
||||
} catch (e) {
|
||||
final defaultConfig = TabConfig.defaultConfig();
|
||||
emit(TabConfigLoaded(defaultConfig));
|
||||
}
|
||||
}
|
||||
|
||||
void _saveConfig(TabConfig config) {
|
||||
try {
|
||||
final configList = config.items.map((item) => {
|
||||
'id': item.id,
|
||||
'name': item.name,
|
||||
'nameEn': item.nameEn,
|
||||
'icon': item.icon,
|
||||
'isEnabled': item.isEnabled,
|
||||
'order': item.order,
|
||||
}).toList();
|
||||
_prefs.setString(_configKey, jsonEncode(configList));
|
||||
} catch (e) {
|
||||
print('保存 Tab 配置失败: $e');
|
||||
}
|
||||
}
|
||||
|
||||
void toggleTab(String itemId) {
|
||||
if (state is TabConfigLoaded) {
|
||||
final currentState = state as TabConfigLoaded;
|
||||
final newConfig = currentState.config.toggleItem(itemId);
|
||||
|
||||
// 只有配置真正改变时才保存和 emit
|
||||
if (newConfig != currentState.config) {
|
||||
_saveConfig(newConfig);
|
||||
emit(TabConfigLoaded(newConfig));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TabConfig? getConfig() {
|
||||
if (state is TabConfigLoaded) {
|
||||
return (state as TabConfigLoaded).config;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:maibu_satabot_v2/features/ai/presentation/pages/ai_page.dart';
|
||||
import 'package:maibu_satabot_v2/features/home/presentation/pages/home_page.dart';
|
||||
import 'package:maibu_satabot_v2/features/my/presentation/pages/my_page.dart';
|
||||
import 'package:maibu_satabot_v2/features/main_container/presentation/bloc/tab_config_cubit.dart';
|
||||
import 'package:maibu_satabot_v2/features/main_container/presentation/widgets/custom_bottom_nav_bar.dart';
|
||||
|
||||
import '../../../waring_center/presentation/pages/warning_center_page.dart';
|
||||
|
||||
class CustomMainContainer extends StatefulWidget {
|
||||
const CustomMainContainer({super.key});
|
||||
|
||||
@override
|
||||
State<CustomMainContainer> createState() => _CustomMainContainerState();
|
||||
}
|
||||
|
||||
class _CustomMainContainerState extends State<CustomMainContainer> with AutomaticKeepAliveClientMixin {
|
||||
@override
|
||||
bool get wantKeepAlive => true; // 保持页面状态
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
|
||||
debugPrint('🔍 [CustomMainContainer] build 被调用');
|
||||
|
||||
return BlocBuilder<TabConfigCubit, TabConfigState>(
|
||||
builder: (context, state) {
|
||||
final enabledTabs = state.config.enabledItems;
|
||||
final currentIndex = state.selectedIndex;
|
||||
|
||||
debugPrint('🔍 [CustomMainContainer] enabledTabs=${enabledTabs.length}, currentIndex=$currentIndex');
|
||||
debugPrint('🔍 [CustomMainContainer] enabledTabs IDs=${enabledTabs.map((e) => e.id).toList()}');
|
||||
|
||||
if (enabledTabs.isEmpty) {
|
||||
return const Scaffold(
|
||||
body: Center(child: Text('请至少启用一个Tab')),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
body: IndexedStack(
|
||||
index: currentIndex,
|
||||
children: _buildPages(enabledTabs),
|
||||
),
|
||||
bottomNavigationBar: const CustomBottomNavBar(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildPages(List<dynamic> enabledTabs) {
|
||||
return enabledTabs.map((tab) {
|
||||
switch (tab.id) {
|
||||
case 'home':
|
||||
return const HomePage();
|
||||
case 'ai':
|
||||
return const AiPage();
|
||||
case 'warning':
|
||||
return const WarningCenterPage();
|
||||
case 'my':
|
||||
return const MyPage();
|
||||
default:
|
||||
return const HomePage();
|
||||
}
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:maibu_satabot_v2/features/main_container/presentation/bloc/tab_config_cubit.dart';
|
||||
import 'package:cc_ui_kit/cc_ui_kit.dart';
|
||||
|
||||
class TabSettingsPage extends StatelessWidget {
|
||||
const TabSettingsPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Tab 配置'),
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Colors.black,
|
||||
elevation: 0,
|
||||
),
|
||||
body: BlocBuilder<TabConfigCubit, TabConfigState>(
|
||||
builder: (context, state) {
|
||||
final tabs = state.config.items;
|
||||
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemCount: tabs.length,
|
||||
itemBuilder: (context, index) {
|
||||
final tab = tabs[index];
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
child: ListTile(
|
||||
leading: Icon(_getIconData(tab.icon)),
|
||||
title: Text(tab.name),
|
||||
subtitle: Text(tab.nameEn),
|
||||
trailing: Switch(
|
||||
value: tab.isEnabled,
|
||||
onChanged: (value) {
|
||||
context.read<TabConfigCubit>().toggleTab(tab.id);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
bottomNavigationBar: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: CCPrimaryButton(
|
||||
text: '重置为默认',
|
||||
onPressed: () {
|
||||
context.read<TabConfigCubit>().resetToDefault();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('已重置为默认配置')),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
IconData _getIconData(String iconName) {
|
||||
switch (iconName) {
|
||||
case 'grid_view_rounded':
|
||||
return Icons.grid_view_rounded;
|
||||
case 'auto_awesome_rounded':
|
||||
return Icons.auto_awesome_rounded;
|
||||
case 'warning_amber_rounded':
|
||||
return Icons.warning_amber_rounded;
|
||||
case 'person_rounded':
|
||||
return Icons.person_rounded;
|
||||
default:
|
||||
return Icons.home_rounded;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:maibu_satabot_v2/features/main_container/presentation/bloc/tab_config_cubit.dart';
|
||||
import 'package:cc_ui_kit/cc_ui_kit.dart';
|
||||
|
||||
class CustomBottomNavBar extends StatelessWidget {
|
||||
const CustomBottomNavBar({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<TabConfigCubit, TabConfigState>(
|
||||
builder: (context, state) {
|
||||
final enabledTabs = state.config.enabledItems;
|
||||
|
||||
if (enabledTabs.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, -2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: List.generate(enabledTabs.length, (index) {
|
||||
final tab = enabledTabs[index];
|
||||
final isSelected = index == state.selectedIndex;
|
||||
|
||||
return _buildNavItem(
|
||||
context,
|
||||
icon: _getIconData(tab.icon),
|
||||
label: tab.name,
|
||||
isSelected: isSelected,
|
||||
onTap: () => context.read<TabConfigCubit>().selectTab(index),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNavItem(
|
||||
BuildContext context, {
|
||||
required IconData icon,
|
||||
required String label,
|
||||
required bool isSelected,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return Expanded(
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
color: isSelected ? const Color(0xFF2196F3) : Colors.grey,
|
||||
size: 24,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: isSelected ? const Color(0xFF2196F3) : Colors.grey,
|
||||
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
IconData _getIconData(String iconName) {
|
||||
switch (iconName) {
|
||||
case 'grid_view_rounded':
|
||||
return Icons.grid_view_rounded;
|
||||
case 'auto_awesome_rounded':
|
||||
return Icons.auto_awesome_rounded;
|
||||
case 'warning_amber_rounded':
|
||||
return Icons.warning_amber_rounded;
|
||||
case 'person_rounded':
|
||||
return Icons.person_rounded;
|
||||
default:
|
||||
return Icons.home_rounded;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'warning_center_event.dart';
|
||||
import 'warning_center_state.dart';
|
||||
|
||||
class WarningCenterBloc extends Bloc<WarningCenterEvent, WarningCenterState> {
|
||||
WarningCenterBloc() : super(const WarningCenterInitial()) {
|
||||
on<WarningCenterLoadData>(_onLoadData);
|
||||
on<WarningCenterFilterByLevel>(_onFilterByLevel);
|
||||
on<WarningCenterFilterByTime>(_onFilterByTime);
|
||||
on<WarningCenterSearch>(_onSearch);
|
||||
}
|
||||
|
||||
Future<void> _onLoadData(
|
||||
WarningCenterLoadData event,
|
||||
Emitter<WarningCenterState> emit,
|
||||
) async {
|
||||
emit(const WarningCenterLoading());
|
||||
|
||||
try {
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
|
||||
final stats = const WarningStats(
|
||||
totalCount: 156,
|
||||
todayCount: 156,
|
||||
severeCount: 8,
|
||||
processingCount: 15,
|
||||
closedCount: 133,
|
||||
avgResponseTime: 2.3,
|
||||
changePercent: -12,
|
||||
);
|
||||
|
||||
final warnings = [
|
||||
const WarningItem(
|
||||
id: '1',
|
||||
level: '严重',
|
||||
deviceName: '逆变器12',
|
||||
deviceType: '组串式',
|
||||
content: '逆变器停机',
|
||||
status: '处理中',
|
||||
time: '2025-05-25 09:18:32',
|
||||
duration: '2小时18分',
|
||||
responsiblePerson: '张运维',
|
||||
),
|
||||
const WarningItem(
|
||||
id: '2',
|
||||
level: '重要',
|
||||
deviceName: '逆变器13',
|
||||
deviceType: '组串式',
|
||||
content: '逆变器停机',
|
||||
status: '处理中',
|
||||
time: '2025-05-25 09:18:32',
|
||||
duration: '2小时18分',
|
||||
responsiblePerson: '张运维',
|
||||
),
|
||||
const WarningItem(
|
||||
id: '3',
|
||||
level: '严重',
|
||||
deviceName: '逆变器05',
|
||||
deviceType: '组串式',
|
||||
content: '逆变器停机',
|
||||
status: '处理中',
|
||||
time: '2025-05-25 09:18:32',
|
||||
duration: '2小时18分',
|
||||
responsiblePerson: '张运维',
|
||||
),
|
||||
const WarningItem(
|
||||
id: '4',
|
||||
level: '一般',
|
||||
deviceName: '逆变器08',
|
||||
deviceType: '组串式',
|
||||
content: '温度过高告警',
|
||||
status: '待处理',
|
||||
time: '2025-05-25 08:30:15',
|
||||
duration: '3小时05分',
|
||||
responsiblePerson: '李工程师',
|
||||
),
|
||||
const WarningItem(
|
||||
id: '5',
|
||||
level: '提示',
|
||||
deviceName: '逆变器15',
|
||||
deviceType: '组串式',
|
||||
content: '效率低于阈值',
|
||||
status: '已关闭',
|
||||
time: '2025-05-24 16:45:20',
|
||||
duration: '1天5小时',
|
||||
responsiblePerson: '王技术员',
|
||||
),
|
||||
const WarningItem(
|
||||
id: '6',
|
||||
level: '严重',
|
||||
deviceName: '汇流箱03',
|
||||
deviceType: '直流汇流',
|
||||
content: '通信中断',
|
||||
status: '处理中',
|
||||
time: '2025-05-25 07:20:10',
|
||||
duration: '4小时15分',
|
||||
responsiblePerson: '张运维',
|
||||
),
|
||||
const WarningItem(
|
||||
id: '7',
|
||||
level: '重要',
|
||||
deviceName: '逆变器20',
|
||||
deviceType: '组串式',
|
||||
content: '输出电压异常',
|
||||
status: '待处理',
|
||||
time: '2025-05-25 10:05:30',
|
||||
duration: '1小时30分',
|
||||
responsiblePerson: '赵工程师',
|
||||
),
|
||||
const WarningItem(
|
||||
id: '8',
|
||||
level: '一般',
|
||||
deviceName: '变压器02',
|
||||
deviceType: '升压变压器',
|
||||
content: '负载率超过80%',
|
||||
status: '已关闭',
|
||||
time: '2025-05-24 14:20:45',
|
||||
duration: '1天10小时',
|
||||
responsiblePerson: '李工程师',
|
||||
),
|
||||
];
|
||||
|
||||
emit(WarningCenterLoaded(
|
||||
stats: stats,
|
||||
warnings: warnings,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(WarningCenterError('加载数据失败:$e'));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onFilterByLevel(
|
||||
WarningCenterFilterByLevel event,
|
||||
Emitter<WarningCenterState> emit,
|
||||
) async {
|
||||
if (state is WarningCenterLoaded) {
|
||||
final currentState = state as WarningCenterLoaded;
|
||||
|
||||
List<WarningItem> filteredWarnings;
|
||||
if (event.level == '全部') {
|
||||
filteredWarnings = currentState.warnings;
|
||||
} else {
|
||||
filteredWarnings = currentState.warnings
|
||||
.where((w) => w.level == event.level)
|
||||
.toList();
|
||||
}
|
||||
|
||||
emit(currentState.copyWith(
|
||||
selectedLevel: event.level,
|
||||
warnings: filteredWarnings,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onFilterByTime(
|
||||
WarningCenterFilterByTime event,
|
||||
Emitter<WarningCenterState> emit,
|
||||
) async {
|
||||
if (state is WarningCenterLoaded) {
|
||||
final currentState = state as WarningCenterLoaded;
|
||||
emit(currentState.copyWith(
|
||||
selectedTimeRange: event.timeRange,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onSearch(
|
||||
WarningCenterSearch event,
|
||||
Emitter<WarningCenterState> emit,
|
||||
) async {
|
||||
if (state is WarningCenterLoaded) {
|
||||
final currentState = state as WarningCenterLoaded;
|
||||
|
||||
List<WarningItem> filteredWarnings;
|
||||
if (event.keyword.isEmpty) {
|
||||
filteredWarnings = currentState.warnings;
|
||||
} else {
|
||||
filteredWarnings = currentState.warnings
|
||||
.where((w) =>
|
||||
w.deviceName.contains(event.keyword) ||
|
||||
w.content.contains(event.keyword) ||
|
||||
w.deviceType.contains(event.keyword))
|
||||
.toList();
|
||||
}
|
||||
|
||||
emit(currentState.copyWith(
|
||||
searchKeyword: event.keyword,
|
||||
warnings: filteredWarnings,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
abstract class WarningCenterEvent extends Equatable {
|
||||
const WarningCenterEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class WarningCenterLoadData extends WarningCenterEvent {
|
||||
const WarningCenterLoadData();
|
||||
}
|
||||
|
||||
class WarningCenterFilterByLevel extends WarningCenterEvent {
|
||||
final String level;
|
||||
|
||||
const WarningCenterFilterByLevel(this.level);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [level];
|
||||
}
|
||||
|
||||
class WarningCenterFilterByTime extends WarningCenterEvent {
|
||||
final String timeRange;
|
||||
|
||||
const WarningCenterFilterByTime(this.timeRange);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [timeRange];
|
||||
}
|
||||
|
||||
class WarningCenterSearch extends WarningCenterEvent {
|
||||
final String keyword;
|
||||
|
||||
const WarningCenterSearch(this.keyword);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [keyword];
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class WarningStats extends Equatable {
|
||||
final int totalCount;
|
||||
final int todayCount;
|
||||
final int severeCount;
|
||||
final int processingCount;
|
||||
final int closedCount;
|
||||
final double avgResponseTime;
|
||||
final double changePercent;
|
||||
|
||||
const WarningStats({
|
||||
required this.totalCount,
|
||||
required this.todayCount,
|
||||
required this.severeCount,
|
||||
required this.processingCount,
|
||||
required this.closedCount,
|
||||
required this.avgResponseTime,
|
||||
required this.changePercent,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
totalCount,
|
||||
todayCount,
|
||||
severeCount,
|
||||
processingCount,
|
||||
closedCount,
|
||||
avgResponseTime,
|
||||
changePercent,
|
||||
];
|
||||
}
|
||||
|
||||
class WarningItem extends Equatable {
|
||||
final String id;
|
||||
final String level;
|
||||
final String deviceName;
|
||||
final String deviceType;
|
||||
final String content;
|
||||
final String status;
|
||||
final String time;
|
||||
final String duration;
|
||||
final String responsiblePerson;
|
||||
|
||||
const WarningItem({
|
||||
required this.id,
|
||||
required this.level,
|
||||
required this.deviceName,
|
||||
required this.deviceType,
|
||||
required this.content,
|
||||
required this.status,
|
||||
required this.time,
|
||||
required this.duration,
|
||||
required this.responsiblePerson,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
id,
|
||||
level,
|
||||
deviceName,
|
||||
deviceType,
|
||||
content,
|
||||
status,
|
||||
time,
|
||||
duration,
|
||||
responsiblePerson,
|
||||
];
|
||||
}
|
||||
|
||||
abstract class WarningCenterState extends Equatable {
|
||||
const WarningCenterState();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class WarningCenterInitial extends WarningCenterState {
|
||||
const WarningCenterInitial();
|
||||
}
|
||||
|
||||
class WarningCenterLoading extends WarningCenterState {
|
||||
const WarningCenterLoading();
|
||||
}
|
||||
|
||||
class WarningCenterLoaded extends WarningCenterState {
|
||||
final WarningStats stats;
|
||||
final List<WarningItem> warnings;
|
||||
final String selectedLevel;
|
||||
final String selectedTimeRange;
|
||||
final String searchKeyword;
|
||||
|
||||
const WarningCenterLoaded({
|
||||
required this.stats,
|
||||
required this.warnings,
|
||||
this.selectedLevel = '全部',
|
||||
this.selectedTimeRange = '近7天',
|
||||
this.searchKeyword = '',
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
stats,
|
||||
warnings,
|
||||
selectedLevel,
|
||||
selectedTimeRange,
|
||||
searchKeyword,
|
||||
];
|
||||
|
||||
WarningCenterLoaded copyWith({
|
||||
WarningStats? stats,
|
||||
List<WarningItem>? warnings,
|
||||
String? selectedLevel,
|
||||
String? selectedTimeRange,
|
||||
String? searchKeyword,
|
||||
}) {
|
||||
return WarningCenterLoaded(
|
||||
stats: stats ?? this.stats,
|
||||
warnings: warnings ?? this.warnings,
|
||||
selectedLevel: selectedLevel ?? this.selectedLevel,
|
||||
selectedTimeRange: selectedTimeRange ?? this.selectedTimeRange,
|
||||
searchKeyword: searchKeyword ?? this.searchKeyword,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class WarningCenterError extends WarningCenterState {
|
||||
final String message;
|
||||
|
||||
const WarningCenterError(this.message);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
}
|
||||
@@ -0,0 +1,638 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
|
||||
import 'package:maibu_satabot_v2/features/waring_center/presentation/bloc/warning_center_bloc.dart';
|
||||
import 'package:maibu_satabot_v2/features/waring_center/presentation/bloc/warning_center_event.dart';
|
||||
import 'package:maibu_satabot_v2/features/waring_center/presentation/bloc/warning_center_state.dart';
|
||||
|
||||
class WarningCenterPage extends StatefulWidget {
|
||||
const WarningCenterPage({super.key});
|
||||
|
||||
@override
|
||||
State<WarningCenterPage> createState() => _WarningCenterPageState();
|
||||
}
|
||||
|
||||
class _WarningCenterPageState extends State<WarningCenterPage> {
|
||||
late WarningCenterBloc _bloc;
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_bloc = WarningCenterBloc();
|
||||
_bloc.add(const WarningCenterLoadData());
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_searchController.dispose();
|
||||
_bloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _bloc,
|
||||
child: Scaffold(
|
||||
backgroundColor: const Color(0xFF0A2E6B),
|
||||
body: BlocBuilder<WarningCenterBloc, WarningCenterState>(
|
||||
builder: (context, state) {
|
||||
if (state is WarningCenterLoading) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(color: Colors.white),
|
||||
);
|
||||
}
|
||||
|
||||
if (state is WarningCenterError) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.error_outline, color: Colors.white, size: 48),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
state.message,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (state is WarningCenterLoaded) {
|
||||
return _buildContent(context, state);
|
||||
}
|
||||
|
||||
return const SizedBox();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContent(BuildContext context, WarningCenterLoaded state) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverAppBar(
|
||||
expandedHeight: 80,
|
||||
floating: false,
|
||||
pinned: true,
|
||||
backgroundColor: const Color(0xFF0A2E6B),
|
||||
title: Row(
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context).translate('warning_center.title'),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF4A7AFF),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Text(
|
||||
'1',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
Stack(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.notifications_none, color: Colors.white, size: 28),
|
||||
onPressed: () {},
|
||||
),
|
||||
Positioned(
|
||||
right: 8,
|
||||
top: 8,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.red,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 16,
|
||||
minHeight: 16,
|
||||
),
|
||||
child: const Text(
|
||||
'8',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: _buildTodayStatsCard(context, state),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildStatsCard(
|
||||
context,
|
||||
AppLocalizations.of(context).translate('warning_center.severe_alerts'),
|
||||
state.stats.severeCount.toString(),
|
||||
const Color(0xFFE74C3C),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: _buildStatsCard(
|
||||
context,
|
||||
AppLocalizations.of(context).translate('warning_center.processing'),
|
||||
state.stats.processingCount.toString(),
|
||||
const Color(0xFFF39C12),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildStatsCard(
|
||||
context,
|
||||
AppLocalizations.of(context).translate('warning_center.closed'),
|
||||
state.stats.closedCount.toString(),
|
||||
const Color(0xFF27AE60),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: _buildStatsCard(
|
||||
context,
|
||||
AppLocalizations.of(context).translate('warning_center.avg_response_time'),
|
||||
'${state.stats.avgResponseTime}h',
|
||||
const Color(0xFF3498DB),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Column(
|
||||
children: [
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
_buildFilterChip(context, state, AppLocalizations.of(context).translate('warning_center.all')),
|
||||
_buildFilterChip(context, state, AppLocalizations.of(context).translate('warning_center.severe')),
|
||||
_buildFilterChip(context, state, AppLocalizations.of(context).translate('warning_center.important')),
|
||||
_buildFilterChip(context, state, AppLocalizations.of(context).translate('warning_center.general')),
|
||||
_buildFilterChip(context, state, AppLocalizations.of(context).translate('warning_center.hint')),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey.shade300),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context).translate('warning_center.device_type'),
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: state.selectedLevel == AppLocalizations.of(context).translate('warning_center.all')
|
||||
? const Color(0xFF4A7AFF)
|
||||
: Colors.grey.shade700,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Icon(Icons.keyboard_arrow_down, size: 16, color: Colors.grey.shade600),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
_buildTimeFilterChip(context, state, AppLocalizations.of(context).translate('warning_center.last_7_days')),
|
||||
const SizedBox(width: 8),
|
||||
_buildTimeFilterChip(context, state, AppLocalizations.of(context).translate('warning_center.specific_date')),
|
||||
const SizedBox(width: 8),
|
||||
_buildTimeFilterChip(context, state, AppLocalizations.of(context).translate('warning_center.all')),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Container(
|
||||
height: 36,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
border: Border.all(color: Colors.grey.shade300),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _searchController,
|
||||
decoration: InputDecoration(
|
||||
hintText: AppLocalizations.of(context).translate('warning_center.search_placeholder'),
|
||||
hintStyle: TextStyle(fontSize: 13, color: Colors.grey.shade400),
|
||||
border: InputBorder.none,
|
||||
isDense: true,
|
||||
),
|
||||
style: const TextStyle(fontSize: 13),
|
||||
onChanged: (value) {
|
||||
_bloc.add(WarningCenterSearch(value));
|
||||
},
|
||||
),
|
||||
),
|
||||
Icon(Icons.search, size: 18, color: Colors.grey.shade400),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
final warning = state.warnings[index];
|
||||
return _buildWarningCard(context, warning);
|
||||
},
|
||||
childCount: state.warnings.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SliverToBoxAdapter(
|
||||
child: SizedBox(height: 100),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTodayStatsCard(BuildContext context, WarningCenterLoaded state) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [Color(0xFF4A7AFF), Color(0xFF2E5CDB)],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context).translate('warning_center.today_total'),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFE74C3C),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(Icons.arrow_downward, color: Colors.white, size: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
state.stats.todayCount.toString(),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 36,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.arrow_downward, color: Colors.white, size: 14),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'${state.stats.changePercent.abs()}% ${AppLocalizations.of(context).translate('warning_center.vs_yesterday')}',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatsCard(BuildContext context, String title, String value, Color color) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFilterChip(BuildContext context, WarningCenterLoaded state, String label) {
|
||||
final isSelected = state.selectedLevel == label;
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
_bloc.add(WarningCenterFilterByLevel(label));
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? const Color(0xFF4A7AFF) : Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: isSelected ? const Color(0xFF4A7AFF) : Colors.grey.shade300,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: isSelected ? Colors.white : Colors.grey.shade700,
|
||||
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTimeFilterChip(BuildContext context, WarningCenterLoaded state, String label) {
|
||||
final isSelected = state.selectedTimeRange == label;
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
_bloc.add(WarningCenterFilterByTime(label));
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? const Color(0xFF4A7AFF) : Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: isSelected ? const Color(0xFF4A7AFF) : Colors.grey.shade300,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: isSelected ? Colors.white : Colors.grey.shade700,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildWarningCard(BuildContext context, WarningItem warning) {
|
||||
Color levelColor;
|
||||
switch (warning.level) {
|
||||
case '严重':
|
||||
levelColor = const Color(0xFFE74C3C);
|
||||
break;
|
||||
case '重要':
|
||||
levelColor = const Color(0xFFF39C12);
|
||||
break;
|
||||
case '一般':
|
||||
levelColor = const Color(0xFF27AE60);
|
||||
break;
|
||||
default:
|
||||
levelColor = const Color(0xFF3498DB);
|
||||
}
|
||||
|
||||
Color statusColor;
|
||||
switch (warning.status) {
|
||||
case '处理中':
|
||||
statusColor = const Color(0xFF4A7AFF);
|
||||
break;
|
||||
case '待处理':
|
||||
statusColor = const Color(0xFFF39C12);
|
||||
break;
|
||||
case '已关闭':
|
||||
statusColor = const Color(0xFF27AE60);
|
||||
break;
|
||||
default:
|
||||
statusColor = Colors.grey;
|
||||
}
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: levelColor,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
warning.level,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'${warning.deviceName}(${warning.deviceType})',
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(0xFF1A1A1A),
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
warning.duration,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: statusColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(color: statusColor),
|
||||
),
|
||||
child: Text(
|
||||
warning.status,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: statusColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'${AppLocalizations.of(context).translate('warning_center.alert_content')}:${warning.content}',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: Color(0xFF333333),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'${AppLocalizations.of(context).translate('warning_center.alert_time')}:${warning.time}',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context).translate('warning_center.pending_status'),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
warning.status,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(0xFF1A1A1A),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
AppLocalizations.of(context).translate('warning_center.responsible_person'),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
warning.responsiblePerson,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(0xFF1A1A1A),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:maibu_satabot_v2/core/router/route_paths.dart';
|
||||
import 'package:maibu_satabot_v2/features/waring_center/presentation/pages/warning_center_page.dart';
|
||||
|
||||
class WarningCenterRoutes {
|
||||
static List<RouteBase> get routes => [
|
||||
GoRoute(
|
||||
path: RoutePaths.warningCenter,
|
||||
builder: (context, state) => const WarningCenterPage(),
|
||||
),
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user