Files
zhinian_manage/lib/pages/order_work_page.dart

554 lines
18 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:flutter_animate/flutter_animate.dart';
import '../providers/work_order_provider.dart';
import '../providers/order_provider.dart';
import '../models/work_order.dart';
import '../models/order.dart';
import '../theme.dart';
import '../l10n/app_localizations.dart';
import '../widgets/skeleton.dart';
class OrderWorkPage extends ConsumerStatefulWidget {
final String initialTab;
const OrderWorkPage({super.key, this.initialTab = 'work'});
@override
ConsumerState<OrderWorkPage> createState() => _OrderWorkPageState();
}
class _OrderWorkPageState extends ConsumerState<OrderWorkPage>
with SingleTickerProviderStateMixin {
late TabController _tabController;
WorkOrderStatus _workStatus = WorkOrderStatus.all;
OrderStatus _orderStatus = OrderStatus.all;
@override
void initState() {
super.initState();
_tabController = TabController(
length: 2,
vsync: this,
initialIndex: widget.initialTab == 'order' ? 1 : 0,
);
_tabController.addListener(_onTabChanged);
Future.microtask(() {
ref.read(workOrderProvider.notifier).loadOrders(WorkOrderStatus.all);
ref.read(orderProvider.notifier).loadOrders(OrderStatus.all);
});
}
void _onTabChanged() {
if (!_tabController.indexIsChanging) {
setState(() {});
}
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
backgroundColor: AppColors.background,
appBar: AppBar(
title: Text(l10n.ordersAndWorkOrders),
backgroundColor: AppColors.surface,
elevation: 0,
scrolledUnderElevation: 0,
surfaceTintColor: Colors.transparent,
bottom: PreferredSize(
preferredSize: const Size.fromHeight(52),
child: Container(
color: AppColors.surface,
padding: const EdgeInsets.fromLTRB(16, 0, 16, 12),
child: Container(
height: 38,
decoration: BoxDecoration(
color: AppColors.background,
borderRadius: BorderRadius.circular(10),
),
child: TabBar(
controller: _tabController,
tabs: [
Tab(text: l10n.workOrder),
Tab(text: l10n.order),
],
indicator: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: AppColors.surface,
),
indicatorSize: TabBarIndicatorSize.tab,
indicatorPadding: const EdgeInsets.all(3),
labelColor: AppColors.textPrimary,
unselectedLabelColor: AppColors.textSecondary,
labelStyle: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
unselectedLabelStyle: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
dividerColor: Colors.transparent,
dividerHeight: 0,
splashFactory: NoSplash.splashFactory,
overlayColor: WidgetStateProperty.all(Colors.transparent),
),
),
),
),
),
body: TabBarView(
controller: _tabController,
children: [
_WorkOrderTab(
status: _workStatus,
onStatusChanged: (s) {
setState(() => _workStatus = s);
ref.read(workOrderProvider.notifier).loadOrders(s);
},
),
_OrderTab(
status: _orderStatus,
onStatusChanged: (s) {
setState(() => _orderStatus = s);
ref.read(orderProvider.notifier).loadOrders(s);
},
),
],
),
);
}
}
class _WorkOrderTab extends ConsumerWidget {
final WorkOrderStatus status;
final ValueChanged<WorkOrderStatus> onStatusChanged;
const _WorkOrderTab({required this.status, required this.onStatusChanged});
@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context)!;
final state = ref.watch(workOrderProvider);
return Column(
children: [
_buildWorkFilterChips(context, l10n),
Expanded(
child: state.isLoading
? SkeletonList(
count: 5,
itemBuilder: (_) => const WorkOrderCardSkeleton(),
)
: state.orders.isEmpty
? _buildEmptyState(l10n.noWorkOrders)
: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: state.orders.length,
itemBuilder: (context, index) {
return _buildWorkOrderCard(context, state.orders[index], index);
},
),
),
],
);
}
Widget _buildWorkFilterChips(BuildContext context, AppLocalizations l10n) {
final filters = [
(WorkOrderStatus.all, l10n.all),
(WorkOrderStatus.pending, l10n.pending),
(WorkOrderStatus.processing, l10n.processing),
(WorkOrderStatus.completed, l10n.completed),
];
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
color: AppColors.surface,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: filters.map((f) {
final isSelected = status == f.$1;
return Padding(
padding: const EdgeInsets.only(right: 8),
child: FilterChip(
selected: isSelected,
onSelected: (_) => onStatusChanged(f.$1),
backgroundColor: AppColors.background,
selectedColor: AppColors.primary.withOpacity(0.1),
checkmarkColor: AppColors.primary,
label: Text(f.$2),
labelStyle: TextStyle(
fontSize: 13,
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400,
color: isSelected ? AppColors.primary : AppColors.textSecondary,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
side: BorderSide(
color: isSelected ? AppColors.primary : Colors.transparent,
),
),
),
);
}).toList(),
),
),
);
}
Widget _buildWorkOrderCard(BuildContext context, WorkOrder order, int index) {
final l10n = AppLocalizations.of(context)!;
return GestureDetector(
onTap: () => context.push('/work-order/${order.id}'),
child: Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.03),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
order.title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
),
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: order.statusColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
),
child: Text(
order.statusText,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w500,
color: order.statusColor,
),
),
),
],
),
const SizedBox(height: 8),
Text(
order.description,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 14,
color: AppColors.textSecondary,
height: 1.4,
),
),
const SizedBox(height: 12),
Row(
children: [
Icon(Icons.person_outline, size: 14, color: AppColors.textTertiary),
const SizedBox(width: 4),
Text(
'${l10n.creator}: ${order.creatorName}',
style: TextStyle(fontSize: 12, color: AppColors.textTertiary),
),
if (order.assigneeName != null) ...[
const SizedBox(width: 12),
Icon(Icons.assignment_ind_outlined, size: 14, color: AppColors.textTertiary),
const SizedBox(width: 4),
Text(
'${order.status == WorkOrderStatus.pending ? l10n.transferDept : l10n.assignee}: ${order.assigneeName}',
style: TextStyle(fontSize: 12, color: AppColors.textTertiary),
),
],
const Spacer(),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: _getPriorityColor(order.priority).withOpacity(0.1),
borderRadius: BorderRadius.circular(4),
),
child: Text(
_getPriorityText(context, order.priority),
style: TextStyle(
fontSize: 11,
color: _getPriorityColor(order.priority),
fontWeight: FontWeight.w500,
),
),
),
],
),
],
),
),
).animate().fadeIn(duration: 300.ms, delay: (index * 50).ms).slideY(begin: 0.1, end: 0, duration: 300.ms);
}
Color _getPriorityColor(String priority) {
switch (priority) {
case 'urgent':
return AppColors.error;
case 'high':
return AppColors.warning;
default:
return AppColors.textTertiary;
}
}
String _getPriorityText(BuildContext context, String priority) {
final l10n = AppLocalizations.of(context)!;
switch (priority) {
case 'urgent':
return l10n.urgent;
case 'high':
return l10n.high;
default:
return l10n.normal;
}
}
Widget _buildEmptyState(String text) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.inbox_outlined, size: 64, color: AppColors.textTertiary.withOpacity(0.5)),
const SizedBox(height: 16),
Text(text, style: TextStyle(fontSize: 16, color: AppColors.textSecondary)),
],
),
);
}
}
class _OrderTab extends ConsumerWidget {
final OrderStatus status;
final ValueChanged<OrderStatus> onStatusChanged;
const _OrderTab({required this.status, required this.onStatusChanged});
@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context)!;
final state = ref.watch(orderProvider);
return Column(
children: [
_buildOrderFilterChips(context, l10n),
Expanded(
child: state.isLoading
? SkeletonList(
count: 5,
itemBuilder: (_) => const OrderCardSkeleton(),
)
: state.orders.isEmpty
? _buildEmptyState(l10n.noOrders)
: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: state.orders.length,
itemBuilder: (context, index) {
return _buildOrderCard(context, state.orders[index], index);
},
),
),
],
);
}
Widget _buildOrderFilterChips(BuildContext context, AppLocalizations l10n) {
final filters = [
(OrderStatus.all, l10n.all),
(OrderStatus.pendingPayment, l10n.pendingPayment),
(OrderStatus.pendingVerification, l10n.pendingVerify),
(OrderStatus.verified, l10n.verified),
(OrderStatus.pendingRefund, l10n.pendingRefund),
(OrderStatus.refunded, l10n.refunded),
];
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
color: AppColors.surface,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: filters.map((f) {
final isSelected = status == f.$1;
return Padding(
padding: const EdgeInsets.only(right: 8),
child: FilterChip(
selected: isSelected,
onSelected: (_) => onStatusChanged(f.$1),
backgroundColor: AppColors.background,
selectedColor: AppColors.primary.withOpacity(0.1),
checkmarkColor: AppColors.primary,
label: Text(f.$2),
labelStyle: TextStyle(
fontSize: 13,
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400,
color: isSelected ? AppColors.primary : AppColors.textSecondary,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
side: BorderSide(
color: isSelected ? AppColors.primary : Colors.transparent,
),
),
),
);
}).toList(),
),
),
);
}
Widget _buildOrderCard(BuildContext context, OrderItem order, int index) {
final l10n = AppLocalizations.of(context)!;
return GestureDetector(
onTap: () => context.push('/order/${order.id}'),
child: Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.03),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
order.orderNo,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
),
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: order.statusColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
),
child: Text(
order.statusText,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w500,
color: order.statusColor,
),
),
),
],
),
const SizedBox(height: 12),
Text(
order.productName,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w500,
color: AppColors.textPrimary,
),
),
const SizedBox(height: 8),
Row(
children: [
Icon(Icons.person_outline, size: 14, color: AppColors.textTertiary),
const SizedBox(width: 4),
Text(
order.customerName,
style: TextStyle(fontSize: 13, color: AppColors.textSecondary),
),
const SizedBox(width: 16),
Icon(Icons.confirmation_number_outlined, size: 14, color: AppColors.textTertiary),
const SizedBox(width: 4),
Text(
'x${order.quantity}',
style: TextStyle(fontSize: 13, color: AppColors.textSecondary),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Text(
'¥${order.amount.toStringAsFixed(2)}',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppColors.primary,
),
),
const Spacer(),
if (order.verifyCode != null)
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: AppColors.background,
borderRadius: BorderRadius.circular(6),
),
child: Text(
'${l10n.verifyCode}: ${order.verifyCode}',
style: TextStyle(
fontSize: 12,
fontFamily: 'monospace',
color: AppColors.textSecondary,
),
),
),
],
),
],
),
),
).animate().fadeIn(duration: 300.ms, delay: (index * 50).ms).slideY(begin: 0.1, end: 0, duration: 300.ms);
}
Widget _buildEmptyState(String text) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.receipt_long_outlined, size: 64, color: AppColors.textTertiary.withOpacity(0.5)),
const SizedBox(height: 16),
Text(text, style: TextStyle(fontSize: 16, color: AppColors.textSecondary)),
],
),
);
}
}