| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431 |
- import 'package:flutter/material.dart';
- import 'package:intl/intl.dart';
- import '../../services/water_service.dart';
-
- /// 今日值班页面
- class DispatchPage extends StatefulWidget {
- const DispatchPage({super.key});
- @override
- State<DispatchPage> createState() => _DispatchPageState();
- }
-
- class _DispatchPageState extends State<DispatchPage> {
- final WaterService _service = WaterService.instance;
- DutyInfo? _dutyInfo;
- bool _isLoading = true;
- String? _error;
-
- @override
- void initState() {
- super.initState();
- _loadData();
- }
-
- Future<void> _loadData() async {
- setState(() {
- _isLoading = true;
- _error = null;
- });
- try {
- final data = await _service.getTodayDuty();
- if (mounted) {
- setState(() {
- _dutyInfo = data;
- _isLoading = false;
- });
- }
- } catch (e) {
- if (mounted) {
- setState(() {
- _error = e.toString();
- _isLoading = false;
- });
- }
- }
- }
-
- @override
- Widget build(BuildContext context) {
- final theme = Theme.of(context);
- return Scaffold(
- appBar: AppBar(
- title: const Text('今日值班'),
- centerTitle: true,
- actions: [
- IconButton(
- icon: const Icon(Icons.refresh),
- onPressed: _isLoading ? null : _loadData,
- ),
- ],
- ),
- body: _buildBody(theme),
- );
- }
-
- Widget _buildBody(ThemeData theme) {
- if (_isLoading) {
- return const Center(child: CircularProgressIndicator());
- }
- if (_error != null) {
- return Center(
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- Icon(Icons.error_outline, size: 48, color: theme.colorScheme.error),
- const SizedBox(height: 16),
- Text('加载失败', style: theme.textTheme.titleMedium),
- const SizedBox(height: 16),
- FilledButton.icon(
- onPressed: _loadData,
- icon: const Icon(Icons.refresh),
- label: const Text('重试'),
- ),
- ],
- ),
- );
- }
-
- final info = _dutyInfo!;
- final dateFormat = DateFormat('yyyy年MM月dd日');
-
- return RefreshIndicator(
- onRefresh: _loadData,
- child: ListView(
- padding: const EdgeInsets.all(16),
- children: [
- // 班次概览卡片
- _ShiftOverviewCard(
- date: dateFormat.format(info.date),
- shiftName: info.shiftName,
- shiftTime: info.shiftTime,
- memberCount: info.members.length + 1,
- instructionCount: info.instructions.length,
- ),
- const SizedBox(height: 20),
-
- // 值班长
- _SectionHeader(title: '值班长', icon: Icons.star),
- const SizedBox(height: 8),
- _PersonCard(
- person: info.leader,
- isLeader: true,
- ),
- const SizedBox(height: 20),
-
- // 值班人员
- _SectionHeader(title: '值班人员', icon: Icons.people),
- const SizedBox(height: 8),
- ...info.members.map((member) => Padding(
- padding: const EdgeInsets.only(bottom: 8),
- child: _PersonCard(person: member),
- )),
- const SizedBox(height: 20),
-
- // 调度指令台账
- _SectionHeader(
- title: '调度指令台账',
- icon: Icons.assignment,
- trailing: Text(
- '${info.instructions.length} 条',
- style: TextStyle(fontSize: 13, color: Colors.grey.shade600),
- ),
- ),
- const SizedBox(height: 8),
- ...info.instructions.map((instruction) => Padding(
- padding: const EdgeInsets.only(bottom: 8),
- child: _InstructionCard(instruction: instruction),
- )),
- const SizedBox(height: 16),
- ],
- ),
- );
- }
- }
-
- /// 班次概览卡片
- class _ShiftOverviewCard extends StatelessWidget {
- final String date;
- final String shiftName;
- final String shiftTime;
- final int memberCount;
- final int instructionCount;
-
- const _ShiftOverviewCard({
- required this.date,
- required this.shiftName,
- required this.shiftTime,
- required this.memberCount,
- required this.instructionCount,
- });
-
- @override
- Widget build(BuildContext context) {
- final theme = Theme.of(context);
- final primary = theme.colorScheme.primary;
-
- return Card(
- elevation: 0,
- color: theme.colorScheme.primaryContainer.withAlpha(80),
- shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
- child: Padding(
- padding: const EdgeInsets.all(20),
- child: Column(
- children: [
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Icon(Icons.calendar_today, size: 18, color: primary),
- const SizedBox(width: 8),
- Text(
- date,
- style: TextStyle(
- fontSize: 16,
- fontWeight: FontWeight.w600,
- color: primary,
- ),
- ),
- const SizedBox(width: 12),
- Container(
- padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 3),
- decoration: BoxDecoration(
- color: primary.withAlpha(30),
- borderRadius: BorderRadius.circular(12),
- ),
- child: Text(
- shiftName,
- style: TextStyle(fontSize: 12, color: primary, fontWeight: FontWeight.w600),
- ),
- ),
- ],
- ),
- const SizedBox(height: 12),
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceEvenly,
- children: [
- _InfoColumn(icon: Icons.schedule, label: '值班时间', value: shiftTime),
- Container(width: 1, height: 40, color: Colors.grey.shade300),
- _InfoColumn(icon: Icons.people, label: '值班人数', value: '$memberCount 人'),
- Container(width: 1, height: 40, color: Colors.grey.shade300),
- _InfoColumn(icon: Icons.assignment, label: '调度指令', value: '$instructionCount 条'),
- ],
- ),
- ],
- ),
- ),
- );
- }
- }
-
- class _InfoColumn extends StatelessWidget {
- final IconData icon;
- final String label;
- final String value;
-
- const _InfoColumn({required this.icon, required this.label, required this.value});
-
- @override
- Widget build(BuildContext context) {
- return Column(
- children: [
- Icon(icon, size: 20, color: Colors.grey.shade600),
- const SizedBox(height: 4),
- Text(label, style: TextStyle(fontSize: 11, color: Colors.grey.shade600)),
- const SizedBox(height: 2),
- Text(value, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600)),
- ],
- );
- }
- }
-
- /// 段落标题
- class _SectionHeader extends StatelessWidget {
- final String title;
- final IconData icon;
- final Widget? trailing;
-
- const _SectionHeader({required this.title, required this.icon, this.trailing});
-
- @override
- Widget build(BuildContext context) {
- final theme = Theme.of(context);
- return Row(
- children: [
- Icon(icon, size: 20, color: theme.colorScheme.primary),
- const SizedBox(width: 8),
- Text(title, style: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600)),
- const Spacer(),
- if (trailing != null) trailing!,
- ],
- );
- }
- }
-
- /// 值班人员卡片
- class _PersonCard extends StatelessWidget {
- final DutyPerson person;
- final bool isLeader;
-
- const _PersonCard({required this.person, this.isLeader = false});
-
- @override
- Widget build(BuildContext context) {
- final theme = Theme.of(context);
-
- return Card(
- elevation: 0,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(12),
- side: BorderSide(
- color: isLeader ? theme.colorScheme.primary.withAlpha(80) : Colors.grey.shade200,
- ),
- ),
- child: Padding(
- padding: const EdgeInsets.all(14),
- child: Row(
- children: [
- CircleAvatar(
- radius: 22,
- backgroundColor: isLeader
- ? theme.colorScheme.primary.withAlpha(30)
- : Colors.grey.shade100,
- child: Text(
- person.name.substring(0, 1),
- style: TextStyle(
- fontSize: 16,
- fontWeight: FontWeight.w600,
- color: isLeader ? theme.colorScheme.primary : Colors.grey.shade700,
- ),
- ),
- ),
- const SizedBox(width: 12),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- children: [
- Text(
- person.name,
- style: theme.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600),
- ),
- if (isLeader) ...[
- const SizedBox(width: 6),
- Container(
- padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 1),
- decoration: BoxDecoration(
- color: Colors.amber.shade100,
- borderRadius: BorderRadius.circular(4),
- ),
- child: Text(
- '值班长',
- style: TextStyle(fontSize: 10, color: Colors.amber.shade800),
- ),
- ),
- ],
- ],
- ),
- const SizedBox(height: 4),
- Text(
- person.role,
- style: TextStyle(fontSize: 12, color: Colors.grey.shade600),
- ),
- ],
- ),
- ),
- // 拨打电话按钮
- Container(
- decoration: BoxDecoration(
- color: Colors.green.shade50,
- borderRadius: BorderRadius.circular(8),
- ),
- child: IconButton(
- icon: Icon(Icons.phone, size: 20, color: Colors.green.shade700),
- onPressed: () {
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(
- content: Text('拨打电话: ${person.phone}'),
- duration: const Duration(seconds: 2),
- ),
- );
- },
- ),
- ),
- ],
- ),
- ),
- );
- }
- }
-
- /// 调度指令卡片
- class _InstructionCard extends StatelessWidget {
- final DutyInstruction instruction;
-
- const _InstructionCard({required this.instruction});
-
- @override
- Widget build(BuildContext context) {
- final theme = Theme.of(context);
- final statusColor = Color(instruction.status.color);
-
- return Card(
- elevation: 0,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(12),
- side: BorderSide(color: Colors.grey.shade200),
- ),
- child: Padding(
- padding: const EdgeInsets.all(14),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- children: [
- Expanded(
- child: Text(
- instruction.title,
- style: theme.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600),
- ),
- ),
- Container(
- padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
- decoration: BoxDecoration(
- color: statusColor.withAlpha(30),
- borderRadius: BorderRadius.circular(6),
- ),
- child: Text(
- instruction.status.label,
- style: TextStyle(fontSize: 11, color: statusColor, fontWeight: FontWeight.w500),
- ),
- ),
- ],
- ),
- const SizedBox(height: 8),
- Text(
- instruction.content,
- style: TextStyle(fontSize: 13, color: Colors.grey.shade700, height: 1.5),
- ),
- const SizedBox(height: 10),
- Row(
- children: [
- Icon(Icons.access_time, size: 12, color: Colors.grey.shade500),
- const SizedBox(width: 4),
- Text(
- instruction.time,
- style: TextStyle(fontSize: 11, color: Colors.grey.shade500),
- ),
- const SizedBox(width: 16),
- Icon(Icons.person_outline, size: 12, color: Colors.grey.shade500),
- const SizedBox(width: 4),
- Text(
- instruction.issuer,
- style: TextStyle(fontSize: 11, color: Colors.grey.shade500),
- ),
- ],
- ),
- ],
- ),
- ),
- );
- }
- }
|