下面源码时练习时做的,实现了购物车的单选,全选,总价计算,
需要定义数量和是否选中状态为响应式的数据。
CardListModel
js
import 'package:get/get.dart';
class CardListModel {
int? id;
RxInt? num; // 使用 RxInt 使 num 成为响应式的
String? name;
double? price;
RxBool show; // 添加 RxBool 类型的 show 字段
CardListModel({
this.id,
int? numValue, // 接收一个普通的 int,然后将其转换为 RxInt
this.name,
this.price,
bool showValue = false, // 默认为 false
}) : num = RxInt(numValue ?? 0),
show = RxBool(showValue);
factory CardListModel.fromJson(Map<String, dynamic> json) => CardListModel(
id: json['id'] as int?,
numValue: json['num'] as int?, // 注意这里使用 numValue 而不是直接 num
name: json['name'] as String?,
price: json['price'] as double?,
showValue: false, // JSON 数据中没有 show 字段,所以设置为 false
);
Map<String, dynamic> toJson() => {
'id': id,
'num': num!.value,
'name': name,
'price': price
};
}
controller控制器
js
import 'package:flutter_aidishi/utils/loading.dart';
import 'package:get/get.dart';
import '../../../models/home/card_list.dart';
class CardController extends GetxController {
CardController();
RxList<CardListModel> cardList = <CardListModel>[].obs;// 响应式数据
List<String> selectIds = []; // 选中项的id数组
bool isAllSelected = false; // 底部是否全选状态
double total = 0.0; // 总价
bool isEdit = false; // 编辑/完成
_initData() {
// 模拟接口返回的数据结构
var data = [
{
'name':'生菜500g/份',
'price':100.5,
'id':1,
'num':1,
},
{
'name':'云南产地直发生菜500g/份',
'price':300.0,
'id':2,
'num':1,
},
];
for (var item in data) {
cardList.add(CardListModel.fromJson(item));
}
update(["card"]);
}
// 单选
void onSelect(bool selected,int index){
var id = cardList[index].id.toString();
var p = cardList[index].price as double;
var num = cardList[index].num?.value;
var itemTotal = (p*num!);
if(selected){
cardList[index].show.value = true;
selectIds.add(id);
total += itemTotal;
if(selectIds.length == cardList.length){
isAllSelected = true;
}
}else{
cardList[index].show.value = false;
selectIds.remove(id);
total -= itemTotal;
isAllSelected = false;
}
update(["card_foot"]);
}
// 全选
void onSelectAll(bool selected){
isAllSelected = selected;
total = 0.0;
selectIds.clear();
if(selected){
// 全选,把所有购车中的id取出来,放到selectIds中
for(var i = 0; i <cardList.length;i++){
var id = cardList[i].id.toString();
selectIds.add(id);
var p = cardList[i].price as double;
var num = cardList[i].num?.value;
var itemTotal = (p*num!);
total += itemTotal;
}
}
update(["card"]);
}
// 加减价格修改
void onTapItemNum(index,value){
cardList[index].num?.value = value;
total = 0.0;
for(var i = 0; i <cardList.length;i++){
if(cardList[i].show.value){
var p = cardList[i].price as double;
var num = cardList[i].num?.value;
var itemTotal = (p*num!);
total += itemTotal;
}
}
update(["card_foot"]);
}
// 编辑/完成切换
void onTapEdit(){
total = 0.0;
isAllSelected = false;
isEdit = !isEdit;
update(["card"]);
}
// 删除
void submit_del(){
if(selectIds.length == 0){
Loading.error('请先选择要删除的商品');
}
print('点击删除');
}
// 结算
void submit_pay(){
if(selectIds.length == 0){
Loading.error('请先选择要结算的商品');
}
print('点击下单');
}
@override
void onInit() {
super.onInit();
}
@override
void onReady() {
super.onReady();
_initData();
}
@override
void onClose() {
super.onClose();
}
}
主视图
js
import 'package:flutter/material.dart';
import 'package:flutter_aidishi/extension/index.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:tdesign_flutter/tdesign_flutter.dart';
import '../../../widget/index.dart';
import 'index.dart';
import 'widgets/cardFoot.dart';
class CardPage extends GetView<CardController> {
const CardPage({super.key});
// 购物车订单
Widget _buildCardList(){
// 分隔列表
return ListView.separated(
// 每一个item项
itemBuilder: (BuildContext context,int index){
return TDCheckboxGroupContainer(
selectIds: controller.isAllSelected ? controller.selectIds : [],
child: <Widget>[
// 复选框
TDCheckbox(
id: controller.cardList[index].id.toString(),
style: TDCheckboxStyle.circle,
showDivider: false,
onCheckBoxChanged: (selected){
controller.onSelect(selected,index);
},
),
SizedBox(width: 8.w,),
// 商品图
Container(
width: 80.w,
height: 80.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.w),
image: DecorationImage(image: AssetImage('assets/images/goods.jpg'))
),
),
SizedBox(width: 10.w,),
<Widget>[
Text('${controller.cardList[index].name}',maxLines: 1,overflow: TextOverflow.ellipsis,style: TextStyle(fontSize: 14.sp,fontWeight: FontWeight.bold),),
SizedBox(height: 5.w,),
<Widget>[
Text('¥',style: TextStyle(fontSize: 11.sp,color: Color(0xffFF770F)),),
Text('${controller.cardList[index].price}',style: TextStyle(fontSize: 17.sp,color: Color(0xffFF770F),fontWeight: FontWeight.bold),)
].toRow(),
SizedBox(height: 5.w,),
<Widget>[
// 步进器
TDStepper(
theme: TDStepperTheme.filled,
value:controller.cardList[index].num?.value,
min:1,
onChange:(value){
controller.onTapItemNum(index,value);
}
)
].toRow(mainAxisAlignment: MainAxisAlignment.end)
].toColumn(crossAxisAlignment: CrossAxisAlignment.start).expanded()
].toRow()
.paddingOnly(top: 15.w,right: 15.w,bottom: 15.w,left: 15.w)
.card(radius: 10.w,color: Colors.white)
.marginOnly(left: 15.w,right: 15.w)
);
},
// 定义每个item之间的分隔距离
separatorBuilder: (BuildContext context,int index){
return SizedBox(height: 10.w,);
},
// 一共有多少条记录
itemCount: controller.cardList.length,
);
}
// 主视图
Widget _buildView() {
return <Widget>[
SizedBox(height: 10.w,),
_buildCardList().expanded(),
CardFootPage(), // 底部单独抽成组件更新全选总价的状态
].toColumn();
}
@override
Widget build(BuildContext context) {
return GetBuilder<CardController>(
init: CardController(),
id: "card",
builder: (_) {
return Scaffold(
appBar: AppBar(
title: Text("购物车(${controller.cardList.length})"),
actions: [
GestureDetector(
onTap: (){
controller.onTapEdit();
},
child: Container(
margin: EdgeInsets.only(right: 15.w),
child: Text(controller.isEdit ? '完成' : '编辑',style: TextStyle(fontSize: 14.sp,fontWeight: FontWeight.w500),),
),
)
],
),
body: SafeArea(
child: _buildView(),
),
backgroundColor: AppColors.ColorPageBg,
);
},
);
}
}
CardFootPage
js
import 'package:flutter/material.dart';
import 'package:flutter_aidishi/extension/index.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:tdesign_flutter/tdesign_flutter.dart';
import '../../../../widget/index.dart';
import '../index.dart';
class CardFootPage extends GetView<CardController> {
const CardFootPage({super.key});
// 底部
Widget _buildFooter(){
return <Widget>[
<Widget>[
TDCheckboxGroupContainer(
selectIds: controller.isAllSelected ? ['1'] : [],
child: TDCheckbox(
id: '1',
style: TDCheckboxStyle.circle,
showDivider: false,
onCheckBoxChanged: (bool selected){
controller.onSelectAll(selected);
},
)
),
SizedBox(width: 10.w,),
Text('合计',style: TextStyle(fontSize: 14.sp,color: AppColors.Color999),),
Text('¥',style: TextStyle(fontSize: 11.sp,color: Color(0xffFF770F)),),
Text('${controller.total}',style: TextStyle(fontSize: 17.sp,color: Color(0xffFF770F),fontWeight: FontWeight.bold),)
].toRow().expanded(),
controller.isEdit ? TDButton(
text: '删除',
height: 52.w,
width:100.w,
iconWidget: TDLoading(
size: TDLoadingSize.small,
icon: TDLoadingIcon.circle,
iconColor: AppColors.mainColor,
),
size: TDButtonSize.large,
type: TDButtonType.fill,
shape: TDButtonShape.filled,
theme: TDButtonTheme.danger,
onTap: controller.submit_del,
) : TDButton(
text: '结算',
height: 52.w,
width:100.w,
iconWidget: TDLoading(
size: TDLoadingSize.small,
icon: TDLoadingIcon.circle,
iconColor: AppColors.mainColor,
),
size: TDButtonSize.large,
type: TDButtonType.fill,
shape: TDButtonShape.filled,
theme: TDButtonTheme.primary,
onTap: controller.submit_pay,
)
].toRow(mainAxisAlignment: MainAxisAlignment.spaceBetween)
.paddingOnly(left: 15.w)
.decorated(
color: AppColors.Colorfff.withOpacity(1),
).boxShadow(
color: AppColors.Color999.withOpacity(0.5),
blurRadius:8,
spreadRadius:1
).height(52.w);
}
// 主视图
Widget _buildView() {
return <Widget>[
_buildFooter(),
].toColumn();
}
@override
Widget build(BuildContext context) {
return GetBuilder<CardController>(
init: CardController(),
id: "card_foot",
builder: (_) {
return _buildView();
},
);
}
}