295 lines
12 KiB
Dart
295 lines
12 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||
import 'package:maibu_satabot_v2/core/di/injection.dart';
|
||
import 'package:maibu_satabot_v2/features/v2/home/presentation/bloc/home_v2_bloc.dart';
|
||
import 'package:maibu_satabot_v2/features/v2/home/presentation/bloc/home_v2_event.dart';
|
||
import 'package:maibu_satabot_v2/features/v2/home/presentation/bloc/home_v2_state.dart';
|
||
import 'package:maibu_satabot_v2/features/v2/site/presentation/cubit/site_cubit.dart';
|
||
import 'package:maibu_satabot_v2/features/v2/home/presentation/widgets/power_card.dart';
|
||
import 'package:maibu_satabot_v2/features/v2/home/presentation/widgets/stats_grid.dart';
|
||
import 'package:maibu_satabot_v2/features/v2/home/presentation/widgets/work_order_card.dart';
|
||
import 'package:maibu_satabot_v2/features/v2/home/presentation/widgets/quick_entry_card.dart';
|
||
import 'package:maibu_satabot_v2/features/v2/home/presentation/widgets/power_trend_chart.dart';
|
||
import 'package:maibu_satabot_v2/features/v2/home/presentation/widgets/plant_overview_card.dart';
|
||
import 'package:maibu_satabot_v2/features/v2/home/presentation/widgets/tcp_status_indicator.dart';
|
||
|
||
class HomeV2Page extends StatefulWidget {
|
||
const HomeV2Page({super.key});
|
||
|
||
@override
|
||
State<HomeV2Page> createState() => _HomeV2PageState();
|
||
}
|
||
|
||
class _HomeV2PageState extends State<HomeV2Page> {
|
||
late HomeV2Bloc _bloc;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_bloc = context.read<HomeV2Bloc>();
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Scaffold(
|
||
backgroundColor: const Color(0xFFF5F7FA),
|
||
body: BlocBuilder<HomeV2Bloc, HomeV2State>(
|
||
builder: (context, state) {
|
||
if (state is HomeV2Error) {
|
||
return Center(
|
||
child: Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
const Icon(Icons.error_outline, size: 48, color: Color(0xFF86909C)),
|
||
const SizedBox(height: 16),
|
||
Text(state.message),
|
||
const SizedBox(height: 16),
|
||
ElevatedButton(
|
||
onPressed: () => _bloc.add(const HomeV2LoadData()),
|
||
child: const Text('重试'),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
if (state is HomeV2Loaded) {
|
||
return RefreshIndicator(
|
||
onRefresh: () async {
|
||
_bloc.add(const HomeV2Refresh());
|
||
},
|
||
child: CustomScrollView(
|
||
slivers: [
|
||
SliverAppBar(
|
||
pinned: true,
|
||
backgroundColor: Colors.white,
|
||
elevation: 0,
|
||
automaticallyImplyLeading: false,
|
||
flexibleSpace: FlexibleSpaceBar(
|
||
background: Container(
|
||
padding: const EdgeInsets.fromLTRB(16, 40, 16, 16),
|
||
color: Colors.white,
|
||
child: Row(
|
||
children: [
|
||
Expanded(
|
||
child: InkWell(
|
||
onTap: _showPlantSelector,
|
||
child: Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
Flexible(
|
||
child: StreamBuilder<SiteState>(
|
||
stream: sl<SiteCubit>().stream,
|
||
builder: (context, snapshot) {
|
||
final siteState = snapshot.data ?? sl<SiteCubit>().state;
|
||
return Text(
|
||
siteState.selectedSite?.siteName ?? '选择电站',
|
||
maxLines: 1,
|
||
overflow: TextOverflow.ellipsis,
|
||
style: const TextStyle(
|
||
fontSize: 18,
|
||
fontWeight: FontWeight.bold,
|
||
color: Color(0xFF1D2129),
|
||
),
|
||
);
|
||
},
|
||
),
|
||
),
|
||
const SizedBox(width: 4),
|
||
const Icon(Icons.arrow_drop_down, size: 20, color: Color(0xFF1D2129)),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
const TcpStatusIndicator(),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
SliverPadding(
|
||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 40),
|
||
sliver: SliverList(
|
||
delegate: SliverChildListDelegate([
|
||
const SizedBox(height: 16),
|
||
// 天气和告警
|
||
Row(
|
||
children: [
|
||
Expanded(
|
||
child: Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFFF2F3F5),
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
const Icon(Icons.cloud_queue, size: 16, color: Color(0xFF165DFF)),
|
||
const SizedBox(width: 6),
|
||
const Text(
|
||
'多云 28°C',
|
||
style: TextStyle(
|
||
fontSize: 13,
|
||
color: Color(0xFF4E5969),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(width: 8),
|
||
Expanded(
|
||
child: Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFFFFF0F0),
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
const Icon(Icons.warning_amber_rounded, size: 16, color: Color(0xFFF53F3F)),
|
||
const SizedBox(width: 6),
|
||
const Expanded(
|
||
child: Text(
|
||
'紧急告警',
|
||
style: TextStyle(
|
||
fontSize: 13,
|
||
color: Color(0xFFF53F3F),
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(width: 4),
|
||
const Icon(Icons.arrow_forward_ios, size: 12, color: Color(0xFFF53F3F)),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 16),
|
||
PowerCard(data: state.homeData),
|
||
const SizedBox(height: 10),
|
||
StatsGrid(data: state.homeData),
|
||
const SizedBox(height: 10),
|
||
PlantOverviewCard(overview: state.homeData.plantOverview),
|
||
const SizedBox(height: 10),
|
||
WorkOrderCard(stats: state.homeData.workOrderStats),
|
||
const SizedBox(height: 10),
|
||
QuickEntryCard(),
|
||
const SizedBox(height: 10),
|
||
PowerTrendChart(
|
||
data: state.homeData.powerTrendData,
|
||
trendType: state.trendType,
|
||
onToggleType: (type) => _bloc.add(HomeV2ToggleTrendType(type)),
|
||
),
|
||
const SizedBox(height: 40),
|
||
]),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
return const SizedBox();
|
||
},
|
||
),
|
||
);
|
||
}
|
||
|
||
@override
|
||
void didChangeDependencies() {
|
||
super.didChangeDependencies();
|
||
// 首次进入时自动加载数据,但不显示转圈
|
||
if (_bloc.state is HomeV2Initial) {
|
||
// 延迟 100ms,确保 AppUserCubit 状态已更新
|
||
Future.delayed(const Duration(milliseconds: 100), () {
|
||
if (mounted) {
|
||
_bloc.add(const HomeV2LoadData());
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
void _showPlantSelector() {
|
||
if (_bloc.state is! HomeV2Loaded) return;
|
||
|
||
final currentState = _bloc.state as HomeV2Loaded;
|
||
final sites = currentState.sites;
|
||
|
||
if (sites.isEmpty) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
const SnackBar(content: Text('暂无可用电站')),
|
||
);
|
||
return;
|
||
}
|
||
|
||
showModalBottomSheet(
|
||
context: context,
|
||
backgroundColor: Colors.white,
|
||
shape: const RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
|
||
),
|
||
builder: (context) {
|
||
return BlocBuilder<HomeV2Bloc, HomeV2State>(
|
||
bloc: _bloc,
|
||
builder: (context, state) {
|
||
if (state is! HomeV2Loaded) return Container();
|
||
|
||
return Container(
|
||
padding: const EdgeInsets.all(14),
|
||
constraints: BoxConstraints(
|
||
maxHeight: MediaQuery.of(context).size.height * 0.6,
|
||
),
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
const Text(
|
||
'选择电站',
|
||
style: TextStyle(
|
||
fontSize: 18,
|
||
fontWeight: FontWeight.bold,
|
||
color: Color(0xFF1D2129),
|
||
),
|
||
),
|
||
const SizedBox(height: 14),
|
||
Expanded(
|
||
child: ListView.builder(
|
||
shrinkWrap: true,
|
||
itemCount: state.sites.length,
|
||
itemBuilder: (context, index) {
|
||
final site = state.sites[index];
|
||
final isSelected = state.selectedSite?.id == site.id;
|
||
|
||
return ListTile(
|
||
title: Text(site.siteName),
|
||
subtitle: site.siteCode != null && site.siteCode!.isNotEmpty
|
||
? Text('编号: ${site.siteCode}')
|
||
: null,
|
||
trailing: isSelected
|
||
? const Icon(Icons.check, color: Color(0xFF165DFF))
|
||
: null,
|
||
onTap: () {
|
||
// 更新全局选中的场站
|
||
sl<SiteCubit>().selectSite(site);
|
||
Navigator.pop(context);
|
||
|
||
// 切换场站后,重新加载首页数据
|
||
_bloc.add(const HomeV2LoadData());
|
||
},
|
||
);
|
||
},
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
},
|
||
);
|
||
},
|
||
);
|
||
}
|
||
}
|