347 lines
11 KiB
Dart
347 lines
11 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import '../providers/order_provider.dart';
|
|
import '../theme.dart';
|
|
import '../l10n/app_localizations.dart';
|
|
import '../widgets/skeleton.dart';
|
|
|
|
class OrderDetailPage extends ConsumerStatefulWidget {
|
|
final String orderId;
|
|
const OrderDetailPage({super.key, required this.orderId});
|
|
|
|
@override
|
|
ConsumerState<OrderDetailPage> createState() => _OrderDetailPageState();
|
|
}
|
|
|
|
class _OrderDetailPageState extends ConsumerState<OrderDetailPage> {
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
Future.microtask(() {
|
|
ref.read(orderProvider.notifier).getDetail(widget.orderId);
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final l10n = AppLocalizations.of(context)!;
|
|
final state = ref.watch(orderProvider);
|
|
final order = state.currentOrder;
|
|
|
|
if (order == null) {
|
|
return Scaffold(
|
|
backgroundColor: AppColors.background,
|
|
appBar: AppBar(title: Text(l10n.orderDetail)),
|
|
body: const DetailPageSkeleton(),
|
|
);
|
|
}
|
|
|
|
return Scaffold(
|
|
backgroundColor: AppColors.background,
|
|
appBar: AppBar(title: Text(l10n.orderDetail)),
|
|
body: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildStatusHeader(order),
|
|
const SizedBox(height: 20),
|
|
_buildProductCard(order, l10n),
|
|
const SizedBox(height: 20),
|
|
_buildCustomerCard(order, l10n),
|
|
const SizedBox(height: 20),
|
|
_buildPaymentCard(order, l10n),
|
|
const SizedBox(height: 20),
|
|
_buildInfoCard(order, l10n),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildStatusHeader(order) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(24),
|
|
decoration: BoxDecoration(
|
|
gradient: AppGradients.primary,
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withOpacity(0.2),
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: Text(
|
|
order.statusText,
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
const Spacer(),
|
|
Text(
|
|
order.orderNo,
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
color: Colors.white.withOpacity(0.8),
|
|
fontFamily: 'monospace',
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 20),
|
|
Text(
|
|
'¥${order.amount.toStringAsFixed(2)}',
|
|
style: const TextStyle(
|
|
fontSize: 36,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
order.productName,
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
color: Colors.white.withOpacity(0.9),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildProductCard(order, AppLocalizations l10n) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(20),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.surface,
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
l10n.productInfo,
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.textPrimary,
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Row(
|
|
children: [
|
|
Container(
|
|
width: 64,
|
|
height: 64,
|
|
decoration: BoxDecoration(
|
|
color: AppColors.primary.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: const Icon(Icons.image, color: AppColors.primary, size: 28),
|
|
),
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
order.productName,
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.textPrimary,
|
|
),
|
|
),
|
|
const SizedBox(height: 6),
|
|
Text(
|
|
'${l10n.quantity}: x${order.quantity}',
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
color: AppColors.textSecondary,
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
'${l10n.unitPrice}: ¥${(order.amount / order.quantity).toStringAsFixed(2)}',
|
|
style: TextStyle(
|
|
fontSize: 13,
|
|
color: AppColors.textTertiary,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildCustomerCard(order, AppLocalizations l10n) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(20),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.surface,
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
_buildInfoRow(l10n.customerName, order.customerName, Icons.person_outline),
|
|
const Divider(height: 24),
|
|
_buildInfoRow(l10n.contactPhone, order.customerPhone, Icons.phone_outlined),
|
|
if (order.scenicSpot != null) ...[
|
|
const Divider(height: 24),
|
|
_buildInfoRow(l10n.belongScenic, order.scenicSpot!, Icons.place_outlined),
|
|
],
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildPaymentCard(order, AppLocalizations l10n) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(20),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.surface,
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
l10n.paymentInfo,
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.textPrimary,
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
_buildPaymentRow(l10n.orderAmount, '¥${order.amount.toStringAsFixed(2)}', isBold: true),
|
|
const SizedBox(height: 8),
|
|
_buildPaymentRow(l10n.actualAmount, '¥${order.amount.toStringAsFixed(2)}'),
|
|
if (order.paidAt != null) ...[
|
|
const SizedBox(height: 8),
|
|
_buildPaymentRow(
|
|
l10n.paymentTime,
|
|
'${order.paidAt!.month}/${order.paidAt!.day} ${order.paidAt!.hour.toString().padLeft(2, '0')}:${order.paidAt!.minute.toString().padLeft(2, '0')}',
|
|
),
|
|
],
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildPaymentRow(String label, String value, {bool isBold = false}) {
|
|
return Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
label,
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
color: isBold ? AppColors.textPrimary : AppColors.textSecondary,
|
|
),
|
|
),
|
|
Text(
|
|
value,
|
|
style: TextStyle(
|
|
fontSize: 15,
|
|
fontWeight: isBold ? FontWeight.w600 : FontWeight.w400,
|
|
color: isBold ? AppColors.primary : AppColors.textPrimary,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildInfoCard(order, AppLocalizations l10n) {
|
|
return Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(20),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.surface,
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
l10n.orderInfo,
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.textPrimary,
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
_buildInfoRow(l10n.orderNo, order.orderNo, Icons.tag),
|
|
const Divider(height: 24),
|
|
_buildInfoRow(l10n.createTime, _formatDateTime(order.createdAt), Icons.access_time),
|
|
if (order.verifiedAt != null) ...[
|
|
const Divider(height: 24),
|
|
_buildInfoRow(l10n.verifyTime, _formatDateTime(order.verifiedAt!), Icons.check_circle_outline),
|
|
],
|
|
if (order.refundedAt != null) ...[
|
|
const Divider(height: 24),
|
|
_buildInfoRow(l10n.refundTime, _formatDateTime(order.refundedAt!), Icons.replay),
|
|
],
|
|
if (order.verifyCode != null) ...[
|
|
const Divider(height: 24),
|
|
_buildInfoRow(l10n.verifyCode, order.verifyCode!, Icons.qr_code),
|
|
],
|
|
if (order.remark != null) ...[
|
|
const Divider(height: 24),
|
|
_buildInfoRow(l10n.remark, order.remark!, Icons.notes),
|
|
],
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildInfoRow(String label, String value, IconData icon) {
|
|
return Row(
|
|
children: [
|
|
Icon(icon, size: 18, color: AppColors.primary),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
label,
|
|
style: TextStyle(fontSize: 12, color: AppColors.textTertiary),
|
|
),
|
|
const SizedBox(height: 2),
|
|
Text(
|
|
value,
|
|
style: TextStyle(
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.w500,
|
|
color: AppColors.textPrimary,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
String _formatDateTime(DateTime dt) {
|
|
return '${dt.year}-${dt.month.toString().padLeft(2, '0')}-${dt.day.toString().padLeft(2, '0')} ${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}';
|
|
}
|
|
}
|