flutter/lib/pages/order/detail.dart
2025-09-19 17:56:34 +08:00

872 lines
32 KiB
Dart
Raw Permalink 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.

library;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:loopin/service/http.dart';
import 'package:loopin/api/shop_api.dart';
import 'package:loopin/components/my_toast.dart';
import 'package:shirne_dialog/shirne_dialog.dart';
import 'package:timer_count_down/timer_count_down.dart';
import 'package:loopin/utils/wxsdk.dart';
import 'package:qr_flutter/qr_flutter.dart';
import '../../behavior/custom_scroll_behavior.dart';
import '../../utils/lifecycle_handler.dart';
class OrderDetail extends StatefulWidget {
const OrderDetail({super.key});
@override
State<OrderDetail> createState() => _OrderDetailState();
}
class _OrderDetailState extends State<OrderDetail> with SingleTickerProviderStateMixin {
final String _orderId = Get.arguments['orderId'] ?? '';
dynamic orderGoodsInfo;
int _initialSeconds = 30 * 60; // 存储初始秒数
bool _countdownFinished = false; // 新增标志位,用于跟踪倒计时是否结束
bool _isLoading = true; // 添加加载状态
bool _showQrCodeDialog = false;
String _qrCodeUrl = '';
@override
void initState() {
super.initState();
getOrderDetail(orderId: _orderId);
LifecycleHandler.onAppResumed = _onAppResumed;
}
@override
void dispose() {
LifecycleHandler.onAppResumed = null;
super.dispose();
}
void _onAppResumed() {
print('App回到前台刷新订单状态,订单Id${_orderId}');
getOrderDetail(orderId: _orderId); // 刷新订单详情数据
_showPaymentResultDialog(); // 展示支付结果弹框
}
// 获取订单状态
void getOrderRealStatus({required String orderId}) async {
try {
final res = await Http.get('${ShopApi.goodsOrderStatus}/$orderId');
Get.toNamed('/myOrder');
} catch (e) {
print('报错-------------->${e}');
}
}
// 获取订单详情信息,包含商品参数
void getOrderDetail({required String orderId}) async {
try {
setState(() {
_isLoading = true;
});
final res = await Http.get('${ShopApi.goodsOrderDetail}/$orderId');
debugPrint(res['data'].toString(), wrapWidth: 600);
setState(() {
orderGoodsInfo = res['data'];
_isLoading = false;
});
} catch (e) {
setState(() {
_isLoading = false;
});
MyDialog.toast('获取订单详情失败');
}
}
// 取消订单
void _cancelOrder() async {
try {
final res = await Http.post('${ShopApi.cancelGoodsOrder}/$_orderId');
getOrderDetail(orderId: _orderId); // 刷新订单详情数据
} catch (e) {
MyDialog.toast('取消订单失败');
}
}
// 核销
void _writeOffQrCode(verificationCodes) async {
if (verificationCodes != null && verificationCodes.isNotEmpty) {
// 过滤可用的核销码status为0
List<dynamic> newVerifyList = verificationCodes.where((item) => item['status'] == 0).toList();
if (newVerifyList.isNotEmpty) {
setState(() {
_showQrCodeDialog = true;
_qrCodeUrl = newVerifyList[0]['code'] ?? '';
});
} else {
MyToast().tip(
title: '暂无可用的核销码',
position: 'center',
type: 'error',
);
}
} else {
MyToast().tip(
title: '暂无可用的核销码',
position: 'center',
type: 'error',
);
}
}
// 关闭二维码弹框
void _closeQrCodeDialog() {
setState(() {
_showQrCodeDialog = false;
_qrCodeUrl = '';
});
}
// 显示支付结果弹框
void _showPaymentResultDialog() {
showDialog(
context: context,
barrierDismissible: false,
barrierColor: Colors.black54,
builder: (BuildContext context) {
return Dialog(
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.0),
),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 图标
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: Color(0xFFFFF8E6),
shape: BoxShape.circle,
),
child: Icon(
Icons.payment,
size: 32,
color: Color(0xFFFFA500),
),
),
SizedBox(height: 16),
// 标题
Text(
'支付确认',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
SizedBox(height: 8),
// 描述
Text(
'请确认您的支付状态',
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
textAlign: TextAlign.center,
),
SizedBox(height: 24),
// 按钮区域
Row(
children: [
// 支付遇到问题按钮
Expanded(
child: OutlinedButton(
onPressed: () {
Navigator.of(context).pop();
getOrderRealStatus(orderId: _orderId); // 主动再次拉取订单状态
},
style: OutlinedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: Colors.grey[700],
side: BorderSide(color: Colors.grey[300]!),
padding: EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text('支付遇到问题'),
),
),
SizedBox(width: 12),
// 支付完成按钮
Expanded(
child: ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
getOrderRealStatus(orderId: _orderId); // 同时主动拉取订单状态
},
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFFFF5000),
foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text('支付完成'),
),
),
],
),
],
),
),
);
},
);
}
// 获取订单状态文本
String getOrderStatusText(int status) {
print('111111111111$status');
switch (status) {
case 0:
return '待付款';
case 1:
return '待核销';
case 2:
return '已完成';
case 3:
return '已关闭';
case 4:
return '退款中';
case 5:
return '已退款';
case 6:
return '已取消';
default:
return '未知状态';
}
}
// 获取订单状态颜色
Color getOrderStatusColor(int status) {
switch (status) {
case 0:
return Colors.grey;
case 1:
return Colors.blue;
case 2:
return Colors.green;
case 3:
return Colors.red;
case 4:
return Colors.orange;
case 5:
return Colors.grey;
case 6:
return Colors.grey;
default:
return Colors.black;
}
}
// 获取第一个商品信息
dynamic _getFirstProductInfo() {
if (orderGoodsInfo == null ||
orderGoodsInfo['items'] == null ||
orderGoodsInfo['items'] is! List ||
orderGoodsInfo['items'].isEmpty) {
return {};
}
return orderGoodsInfo['items'][0];
}
// 构建商品图片
Widget _buildProductImage() {
final productInfo = _getFirstProductInfo();
final picUrl = productInfo?['pic'];
if (picUrl == null || picUrl.isEmpty) {
return Container(
width: 80.0,
height: 80.0,
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(8.0),
),
child: Icon(
Icons.shopping_bag_outlined,
size: 40.0,
color: Colors.grey[400],
),
);
}
return Image.network(
picUrl,
width: 80.0,
height: 80.0,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Container(
width: 80.0,
height: 80.0,
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(8.0),
),
child: Icon(
Icons.shopping_bag_outlined,
size: 40.0,
color: Colors.grey[400],
),
);
},
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return Container(
width: 80.0,
height: 80.0,
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(8.0),
),
child: Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes!
: null,
),
),
);
},
);
}
// 构建底部按钮
Widget buildBottomButtons() {
if (orderGoodsInfo == null) return SizedBox.shrink();
int orderStatus = orderGoodsInfo?['status'] ?? 0;
List verificationCodes = orderGoodsInfo?['verificationCodes'] ?? [];
switch (orderStatus) {
case 0: // 待付款
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
ElevatedButton(
onPressed: () {
// 取消订单逻辑
_cancelOrder();
},
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all(Colors.white),
foregroundColor: WidgetStateProperty.all(Colors.black87),
side: WidgetStateProperty.all(BorderSide(color: Colors.grey[300]!)),
),
child: const Text('取消订单'),
),
const SizedBox(width: 10.0),
ElevatedButton(
onPressed: () {
// 打开微信小程序的某个页面地址如pages/index/index
Wxsdk.openMiniApp(orderId: _orderId);
},
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all(Color(0xff07c160)),
foregroundColor: WidgetStateProperty.all(Colors.white),
),
child: const Text('去支付'),
),
],
);
case 1: // 待核销
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
const SizedBox(width: 10.0),
ElevatedButton(
onPressed: () => _writeOffQrCode(verificationCodes),
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all(Colors.green),
foregroundColor: WidgetStateProperty.all(Colors.white),
),
child: const Text('核销码'),
),
],
);
case 2: // 已完成
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
const SizedBox(width: 10.0),
ElevatedButton(
onPressed: () {},
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all(Colors.green),
foregroundColor: WidgetStateProperty.all(Colors.white),
),
child: const Text('已完成'),
),
],
);
case 3: // 已关闭
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
const SizedBox(width: 10.0),
ElevatedButton(
onPressed: () {},
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all(Colors.grey),
foregroundColor: WidgetStateProperty.all(Colors.white),
),
child: const Text('已关闭'),
),
],
);
case 4: // 退款中
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
const SizedBox(width: 10.0),
ElevatedButton(
onPressed: () {},
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all(Colors.orange),
foregroundColor: WidgetStateProperty.all(Colors.white),
),
child: const Text('退款中'),
),
],
);
case 5: // 已退款
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
const SizedBox(width: 10.0),
ElevatedButton(
onPressed: () {},
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all(Colors.grey),
foregroundColor: WidgetStateProperty.all(Colors.white),
),
child: const Text('已退款'),
),
],
);
case 6: // 已取消
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
const SizedBox(width: 10.0),
ElevatedButton(
onPressed: () {},
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all(Colors.grey),
foregroundColor: WidgetStateProperty.all(Colors.white),
),
child: const Text('已取消'),
),
],
);
default:
return SizedBox.shrink();
}
}
Widget emptyTip() {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'assets/images/empty.png',
width: 100.0,
),
SizedBox(height: 16),
Text(
'还没有订单信息~',
style: TextStyle(color: Colors.grey, fontSize: 12.0),
)
],
);
}
Widget _buildQrCodeDialog() {
return GestureDetector(
onTap: _closeQrCodeDialog,
child: Container(
color: Colors.black54,
child: Center(
child: GestureDetector(
onTap: () {}, // 阻止点击内容区域关闭
child: Container(
width: MediaQuery.of(context).size.width * 0.8,
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15),
boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 10,
offset: Offset(0, 4),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'核销二维码',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
SizedBox(height: 20),
_qrCodeUrl.isNotEmpty
? Container(
padding: EdgeInsets.all(15),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Colors.grey[300]!),
borderRadius: BorderRadius.circular(8),
),
child: QrImageView(
data: _qrCodeUrl,
version: QrVersions.auto,
size: 180,
foregroundColor: Colors.black,
backgroundColor: Colors.white,
errorStateBuilder: (cxt, err) {
return Container(
width: 180,
height: 180,
color: Colors.grey[200],
child: Center(
child: Icon(
Icons.error_outline,
color: Colors.grey[400],
size: 40,
),
),
);
},
),
)
: Container(
width: 180,
height: 180,
color: Colors.grey[200],
child: Center(
child: CircularProgressIndicator(
color: Color(0xff07c160),
),
),
),
SizedBox(height: 15),
Text(
'订单号: $_orderId',
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
textAlign: TextAlign.center,
),
SizedBox(height: 10),
Text(
'核销码: $_qrCodeUrl',
style: TextStyle(
fontSize: 12,
color: Colors.grey[500],
fontFamily: 'monospace',
),
textAlign: TextAlign.center,
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _closeQrCodeDialog,
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xff07c160),
foregroundColor: Colors.white,
minimumSize: Size(120, 40),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
child: Text('关闭'),
),
],
),
),
),
),
),
);
}
Widget _buildOrderInfoRow(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6.0),
child: Row(
children: [
Text(
label,
style: TextStyle(color: Colors.grey, fontSize: 13),
),
Spacer(),
Text(value, style: TextStyle(fontSize: 13)),
],
),
);
}
@override
Widget build(BuildContext context) {
final productInfo = _getFirstProductInfo();
return Scaffold(
backgroundColor: Colors.grey[50],
appBar: AppBar(
backgroundColor: Color(0xFFFF5000),
foregroundColor: Colors.white,
title: Text('订单详情'),
titleSpacing: 1.0,
),
body: Stack(
children: [
// 主要内容
_isLoading
? Center(child: CircularProgressIndicator())
: orderGoodsInfo == null
? emptyTip()
: ScrollConfiguration(
behavior: CustomScrollBehavior().copyWith(scrollbars: false),
child: ListView(
physics: BouncingScrollPhysics(),
padding: EdgeInsets.all(10.0),
children: [
if (orderGoodsInfo?['status'] == 0)
Container(
padding: EdgeInsets.all(12.0),
margin: EdgeInsets.only(bottom: 10.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15.0),
boxShadow: [
BoxShadow(
color: Colors.black.withAlpha(10),
offset: Offset(0.0, 1.0),
blurRadius: 1.0,
spreadRadius: 0.0,
),
],
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.info,
size: 16.0,
color: Colors.orange,
),
SizedBox(width: 4),
Text(
getOrderStatusText(orderGoodsInfo?['status']),
style: TextStyle(color: Colors.orange),
),
SizedBox(width: 4),
Text(
_countdownFinished ? '倒计时已结束' : '剩余 ',
style: TextStyle(color: Colors.grey),
),
if (!_countdownFinished)
Countdown(
seconds: _initialSeconds,
build: (_, double time) {
int m = ((time % 3600) ~/ 60).toInt();
int s = (time % 60).toInt();
String formatted = "${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}";
return Text(
formatted,
style: TextStyle(color: Colors.red, fontWeight: FontWeight.bold),
);
},
interval: Duration(seconds: 1),
onFinished: () {
print("倒计时结束");
_cancelOrder();
setState(() {
_countdownFinished = true;
});
},
),
],
),
SizedBox(height: 4),
Text(
_countdownFinished
? '订单已自动取消'
: '超过30分钟未支付订单将自动取消',
style: TextStyle(color: Colors.grey, fontSize: 12.0),
),
],
),
),
// 商品信息
Container(
margin: EdgeInsets.only(bottom: 10.0),
padding: EdgeInsets.all(10.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15.0),
boxShadow: [
BoxShadow(
color: Colors.black.withAlpha(10),
offset: Offset(0.0, 1.0),
blurRadius: 1.0,
spreadRadius: 0.0,
),
],
),
child: Column(
children: [
Row(
children: [
Spacer(),
Text(
getOrderStatusText(orderGoodsInfo?['status']),
style: TextStyle(
color: getOrderStatusColor(orderGoodsInfo?['status']),
fontWeight: FontWeight.bold,
),
)
],
),
SizedBox(height: 10),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildProductImage(),
SizedBox(width: 10.0),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
productInfo['productName']?.toString() ?? '商品名称未知',
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 14),
),
SizedBox(height: 8),
Row(
children: [
Text(
'¥${productInfo['salePrice']?.toString() ?? '0.00'}',
style: TextStyle(color: Colors.red, fontWeight: FontWeight.bold),
),
Spacer(),
Text(
'x${productInfo['quantity']?.toString() ?? '1'}',
style: TextStyle(color: Colors.grey),
),
],
),
],
),
)
],
),
],
),
),
// 订单信息
Container(
margin: EdgeInsets.only(bottom: 10.0),
padding: EdgeInsets.all(10.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15.0),
boxShadow: [
BoxShadow(
color: Colors.black.withAlpha(10),
offset: Offset(0.0, 1.0),
blurRadius: 1.0,
spreadRadius: 0.0,
),
],
),
child: Column(
children: [
Row(
children: [
Text(
'订单信息',
style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold),
),
Spacer(),
InkWell(
child: Icon(
Icons.copy,
color: Colors.grey,
size: 18.0,
),
onTap: () async {
await Clipboard.setData(ClipboardData(text: orderGoodsInfo?['orderSn']));
MyDialog.toast('订单已复制到剪切板', icon: Icon(Icons.check_circle));
},
)
],
),
SizedBox(height: 10),
Column(
children: [
_buildOrderInfoRow('订单号', orderGoodsInfo?['orderSn'] ?? ''),
_buildOrderInfoRow('下单时间', orderGoodsInfo?['createTime'] ?? ''),
_buildOrderInfoRow('购买数量', (productInfo['quantity'] ?? 0).toString()),
_buildOrderInfoRow('订单金额', '¥${orderGoodsInfo?['totalAmount'] ?? '0.00'}'),
_buildOrderInfoRow('实付金额', '¥${orderGoodsInfo?['payAmount'] ?? '0.00'}'),
],
)
],
),
),
],
),
),
// 二维码对话框(条件渲染)
if (_showQrCodeDialog) _buildQrCodeDialog(),
],
),
bottomNavigationBar: orderGoodsInfo == null
? null
: SafeArea(
minimum: const EdgeInsets.all(10),
child: Container(
height: 60.0,
color: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0),
child: buildBottomButtons(),
),
),
);
}
}