import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:shopping_app/api/api_service.dart';
import 'package:shopping_app/models/cart_item.dart';
import 'package:shopping_app/models/product.dart';
class ShoppingCartPage extends StatefulWidget {
const ShoppingCartPage({Key? key}) : super(key: key);
@override
_ShoppingCartPageState createState() => _ShoppingCartPageState();
}
class _ShoppingCartPageState extends State {
late List<List> cartList;
late double totalPrice;
@override
void initState() {
super.initState();
_getShoppingCart();
}
Future _getShoppingCart() async {
try {
List jsonData = await ApiService.getShoppingCart();
setState(() {
cartList = jsonData[0]
.map((cartJson) =>
CartItem.fromJson(cartJson, Product.fromJson(cartJson[1])))
.toList()
.fold<List<List>>([], (prev, cur) {
if (prev.isNotEmpty && prev.last.first.cartId == cur.cartId) {
prev.last.add(cur);
} else {
prev.add([cur]);
}
return prev;
});
totalPrice = jsonData[1];
});
} catch (e) {
Fluttertoast.showToast(msg: '获取购物车失败,请检查网络连接');
}
}
Future _updateCartItem(CartItem item, int quantity) async {
try {
dynamic jsonData =
await ApiService.updateCart(item.product.productId, quantity);
setState(() {
totalPrice = jsonData[0];
item.quantity = jsonData[1]['quantity'];
item.price = jsonData[1]['price'];
});
} catch (e) {
Fluttertoast.showToast(msg: '更新购物车失败,请检查网络连接');
}
}
Future _deleteCartItem(CartItem item) async {
try {
dynamic jsonData =
await ApiService.deleteCart(item.product.productId);
setState(() {
totalPrice = jsonData;
cartList.forEach((group) => group.remove(item));
cartList.removeWhere((group) => group.isEmpty);
});
} catch (e) {
Fluttertoast.showToast(msg: '删除购物车商品失败,请检查网络连接');
}
}
Widget _buildCartItem(CartItem item) {
return GestureDetector(
onTap: () async {
int? result = await showDialog(
context: context,
builder: (context) => _buildQuantityDialog(item),
);
if (result != null) {
await _updateCartItem(item, result);
}
},
child: Container(
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 15),
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: Colors.grey.shade300)),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 80,
height: 100,
margin: EdgeInsets.only(right: 10),
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(item.product.imageUrl),
fit: BoxFit.cover,
),
),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(item.product.name,
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
overflow: TextOverflow.ellipsis),
SizedBox(height: 5),
Row(
children: [
Text('单价:'),
Text(
'¥${item.product.price}',
style: TextStyle(
color: Colors.red, fontWeight: FontWeight.bold),
),
],
),
SizedBox(height: 5),
Row(
children: [
Text('数量:'),
Text(item.quantity.toString(),
style: TextStyle(fontWeight: FontWeight.bold)),
],
),
],
),
),
GestureDetector(
onTap: () async {
bool? confirm =
await showDialog(context: context, builder: (context) {
return AlertDialog(
title: Text('确认删除?'),
content: Text('您确定要从购物车中删除此商品吗?'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text('取消'),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text('删除'),
),
],
);
});
if (confirm == true) {
await _deleteCartItem(item);
}
},
child: Icon(
Icons.delete_outline,
size: 20,
color: Colors.grey.shade500,
),
),
],
),
),
);
}
Widget _buildQuantityDialog(CartItem item) {
return AlertDialog(
title: Text('修改数量'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('当前数量:${item.quantity}'),
Text('单价:${item.product.price}元'),
],
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('数量:'),
SizedBox(
width: 120,
child: TextField(
keyboardType: TextInputType.number,
controller: TextEditingController(
text: item.quantity.toString()),
decoration: InputDecoration(
border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(horizontal: 10),
),
),
),
],
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(null),
child: Text('取消'),
),
TextButton(
onPressed: () {
String text = (
context.findWidgetThatIsScrollable()!.controller as TextEditingController)
.text;
int quantity = int.tryParse(text) ?? 0;
if (quantity <= 0) {
Fluttertoast.showToast(msg: '数量必须为正整数');
} else {
Navigator.of(context).pop(quantity);
}
},
child: Text('确定'),
),
],
);
}
Widget _buildCheckoutButton() {
return ElevatedButton(
onPressed: () async {
List addressList = await ApiService.getAddresses();
bool? result = await showDialog(
context: context,
builder: (context) => _buildPaymentDialog(addressList),
);
if (result == true) {
// 结算
try {
await ApiService.submitOrder(1013, '支付宝');
Fluttertoast.showToast(msg: '结算成功!');
_getShoppingCart();
} catch (e) {
Fluttertoast.showToast(msg: '结算失败,请检查网络连接');
}
}
},
child: Text('结算'),
);
}
Widget _buildPaymentDialog(List addressList) {
return AlertDialog(
title: Text('结算'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('选择收货地址:'),
SizedBox(height: 10),
...addressList.map((address) {
return RadioListTile(
title: Text('${address['name']} ${address['phone']}'),
subtitle: Text('${address['country']} ${address['provience']} ${address['city']} ${address['district']} ${address['addressLine']}'),
value: address,
groupValue: null,
onChanged: null,
);
}).toList(),
SizedBox(height: 20),
Text('选择支付方式:'),
SizedBox(height: 10),
Row(
children: [
Radio(
value: '支付宝',
groupValue: null,
onChanged: null,
),
Text('支付宝'),
],
),
Row(
children: [
Radio(
value: '微信',
groupValue: null,
onChanged: null,
),
Text('微信'),
],
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text('取消'),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text('结算'),
),
],
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('购物车'),
),
body: cartList == null
? Center(child: CircularProgressIndicator())
: cartList.isEmpty
? Center(child: Text('购物车为空'))
: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: cartList.length,
itemBuilder: (context, index) {
return Column(
children: [
Container(
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 15),
color: Colors.grey.shade100,
child: Row(
children: [
Icon(Icons.location_on, size: 16),
SizedBox(width: 5),
Text(
'收货地址',
style: TextStyle(fontWeight: FontWeight.bold),
),
SizedBox(width: 10),
Text(
'东北石油大学',
),
],
),
),
...cartList[index].map(_buildCartItem),
],
);
},
),
),
Container(
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 15),
decoration: BoxDecoration(
border: Border(top: BorderSide(color: Colors.grey.shade300)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('合计:¥$totalPrice'),
_buildCheckoutButton(),
],
),
),
],
),
);
}
}
class CartItem {
late int cartId;
late int quantity;
late double price;
late Product product;
CartItem.fromJson(Map<String, dynamic> json, Product product)
: cartId = json['cartid'],
quantity = json['quantity'],
price = json['price'],
product = product;
}
// API 服务代码
import 'dart:convert';
import 'package:dio/dio.dart';
import '../models/product.dart';
class ApiService {
static final String baseUrl = 'http://book.musecloud.tech';
static Dio _dio = Dio(); // 全局Dio实例
static Dio dioWithCookie = Dio(); //登录
// 登录
static Future login(String email, String password) async {
print(email);
print(password);
//拦截302跳转并获取cookie
Response response = await _dio.get(
'$baseUrl/user/login',
queryParameters: {
'name': email,
'password': password,
},
options: Options(
followRedirects: false,
validateStatus: (status) {
print(status);
return status! < 500;
},
),
);
//如果response.data包含特色书籍字符串,则登录成功
if (response.statusCode == 302) {
//保存cookie
String? cookie = response.headers['set-cookie']?[0];
print(response.headers['set-cookie']);
//JSESSIONID=255559E896E3DDA5480CE00C6D01683D; Path=/; HttpOnly
//只要JESSIONID=255559E896E3DDA5480CE00C6D01683D
cookie = cookie!.substring(0, cookie.indexOf(';'));
//将cookie保存到全局Dio实例中
dioWithCookie.options.headers['cookie'] = cookie;
return true;
} else {
return false;
}
}
// 获取产品详情
static Future getProductDetail(int productId) async {
try {
Response response = await _dio.get(
'$baseUrl/getproductdetail',
queryParameters: {
'productId': productId,
},
);
dynamic jsonData = jsonDecode(response.data);
Product product = Product.fromJson(jsonData);
return product;
} catch (e) {
throw e;
}
}
// 获取产品列表
static Future<List> getProductList({
required int page,
required int limit,
}) async {
try {
Response response = await _dio.get(
'$baseUrl/getproductlist',
queryParameters: {
'page': page,
'limit': limit,
'category': '',
'keyword': '',
'minprice': '',
'maxprice': '',
'origin': '',
'brand': '',
'supplier': '',
'isActive': '',
'isDeleted': '',
},
);
List jsonData = jsonDecode(response.data);
List products =
jsonData.map((json) => Product.fromJson(json)).toList();
return products;
} catch (e) {
throw e;
}
}
// 添加到购物车
static Future addToCart(int productId, int quantity) async {
try {
Response response = await dioWithCookie.get(
'$baseUrl/addcart',
queryParameters: {
'productId': productId,
'quantity': quantity,
},
);
String result = response.data;
return result;
}
catch (e) {
throw e;
}
}
// 获取购物车
static Future<List> getShoppingCart() async {
try {
Response response = await dioWithCookie.get('$baseUrl/getshoppingcart');
List jsonData = jsonDecode(response.data);
return jsonData;
} catch (e) {
throw e;
}
}
// 更新购物车
static Future<List> updateCart(int productId, int quantity) async {
try {
Response response = await dioWithCookie.get(
'$baseUrl/updatecart',
queryParameters: {
'productId': productId,
'quantity': quantity,
},
);
List jsonData = jsonDecode(response.data);
return jsonData;
} catch (e) {
throw e;
}
}
// 删除购物车商品
static Future deleteCart(int productId) async {
try {
Response response = await dioWithCookie.get(
'$baseUrl/deletecart',
queryParameters: {
'productId': productId,
},
);
double jsonData = double.parse(response.data);
return jsonData;
} catch (e) {
throw e;
}
}
// 获取收货地址
static Future<List> getAddresses() async {
try {
Response response = await dioWithCookie.get('$baseUrl/getaddresses');
List jsonData = jsonDecode(response.data);
return jsonData;
} catch (e) {
throw e;
}
}
// 提交订单
static Future submitOrder(int addressId, String paymentMethod) async {
try {
Response response = await dioWithCookie.get(
'$baseUrl/submitorder',
queryParameters: {
'addressId': addressId,
'paymentMethod': paymentMethod,
},
);
} catch (e) {
throw e;
}
}
}