Files
flutterApp/lib/features/v2/home/presentation/pages/home_v2_page.dart
2026-06-05 19:06:21 +08:00

295 lines
12 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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());
},
);
},
),
),
],
),
);
},
);
},
);
}
}