当前位置: 首页 > article >正文

Flutter:商品多规格内容总结,响应式数据,高亮切换显示。

如图所示:
在这里插入图片描述

代码为练习时写的项目,写的一般,功能实现了,等以后再来优化。

自己模拟的数据结构

var data = {
  'id':1,
  'name':'精品小米等多种五谷杂粮精品小等多种五谷杂粮',
  'logo':'https://cdn.uviewui.com/uview/swiper/1.jpg',
  'price':100.5,
  'old_price':200.0,
  'stock':998,
  'images':[
    {
      'id':1,
      'src':'https://cdn.uviewui.com/uview/swiper/1.jpg',
    },
    {
      'id':2,
      'src':'https://cdn.uviewui.com/uview/swiper/2.jpg',
    },
    {
      'id':3,
      'src':'https://cdn.uviewui.com/uview/swiper/3.jpg',
    }
  ],
  'sku':[
    {
      'name':'颜色',
      'list':[
        {
          'id':8,
          'name':'红色',
        },
        {
          'id':9,
          'name':'蓝色'
        },
        {
          'id':10,
          'name':'白加黑'
        },
        {
          'id':11,
          'name':'紫色'
        },
        {
          'id':12,
          'name':'绿色'
        },
      ]
    },
    {
      'name':'尺码',
      'list':[
        {
          'id':13,
          'name':'S'
        },
        {
          'id':14,
          'name':'M'
        },
        {
          'id':15,
          'name':'L'
        },
        {
          'id':16,
          'name':'XL'
        },
        {
          'id':17,
          'name':'XXL'
        },
      ]
    },
  ]
};

GoodsModel

import 'package:get/get.dart';

class GoodsModel {
  final int id;
  final String name;
  final String logo;
  final double price;
  final double oldPrice;
  final int stock;
  final List<Image> images;
  final List<Sku> sku;

  GoodsModel({
    required this.id,
    required this.name,
    required this.logo,
    required this.price,
    required this.oldPrice,
    required this.stock,
    required this.images,
    required this.sku,
  });

  // 从JSON对象创建GoodsModel实例
  factory GoodsModel.fromJson(Map<String, dynamic> json) {
    return GoodsModel(
      id: json['id'] as int,
      name: json['name'] as String,
      logo: json['logo'] as String,
      price: json['price'] as double,
      oldPrice: json['old_price'] as double,
      stock: json['stock'] as int,
      images: List.from(json['images'] as List).map((e) => Image.fromJson(e)).toList(),
      sku: List.from(json['sku'] as List).map((e) => Sku.fromJson(e)).toList(),
    );
  }

  // 将GoodsModel实例转换为JSON对象
  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'name': name,
      'logo': logo,
      'price': price,
      'old_price': oldPrice,
      'stock': stock,
      'images': images.map((e) => e.toJson()).toList(),
      'sku': sku.map((e) => e.toJson()).toList(),
    };
  }
}

class Image {
  final int id;
  final String src;

  Image({
    required this.id,
    required this.src,
  });

  // 从JSON对象创建Image实例
  factory Image.fromJson(Map<String, dynamic> json) {
    return Image(
      id: json['id'] as int,
      src: json['src'] as String,
    );
  }

  // 将Image实例转换为JSON对象
  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'src': src,
    };
  }
}

class Sku {
  final String name;
  final List<SkuItem> list;

  Sku({
    required this.name,
    required this.list,
  });

  // 从JSON对象创建Sku实例
  factory Sku.fromJson(Map<String, dynamic> json) {
    return Sku(
      name: json['name'] as String,
      list: List.from(json['list'] as List).map((e) => SkuItem.fromJson(e)).toList(),
    );
  }

  // 将Sku实例转换为JSON对象
  Map<String, dynamic> toJson() {
    return {
      'name': name,
      'list': list.map((e) => e.toJson()).toList(),
    };
  }
}

class SkuItem {
  final int id;
  final String name;
  RxBool show; // 自定义响应式变量,判断当前项是否选中

  SkuItem({
    required this.id,
    required this.name,
    bool showValue = false, // 默认false
  }) : show = RxBool(showValue);

  // 从JSON对象创建SkuItem实例
  factory SkuItem.fromJson(Map<String, dynamic> json) {
    return SkuItem(
      id: json['id'] as int,
      name: json['name'] as String,
      showValue: false, // JSON 数据中没有 show 字段,所以自定义设置为 false
    );
  }

  // 将SkuItem实例转换为JSON对象
  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'name': name,
    };
  }
}

GoodsModel中,要确保sku下的SkuItem中,每一项都需要添加一个动态响应式数据show字段来实现规格切换高亮显示,

controller

import 'package:flutter_aidishi/utils/loading.dart';
import 'package:get/get.dart';
import '../../../models/home/goods_detail.dart';

class GoodsDetailController extends GetxController {
  GoodsDetailController();
  final ProductId = Get.arguments['id'];

  GoodsModel? goodsDetail; // 商品详情
  List<Image> bannerList = []; // 商品列表
  RxList<Sku> skuList = RxList<Sku>([]); // 直接指定类型为RxList
  List skuSelected = []; // 选中后的sku
  bool loading = false; // 提交状态
  int type = 1; // 下单状态,1加入购物车,2立即购买
  RxInt payNum = 1.obs; // 下单数量

  _initData() {
    // 模拟接口请求
    var data = {
      'id':1,
      'name':'精品小米等多种五谷杂粮精品小等多种五谷杂粮',
      'logo':'https://cdn.uviewui.com/uview/swiper/1.jpg',
      'price':100.5,
      'old_price':200.0,
      'stock':998,
      'images':[
        {
          'id':1,
          'src':'https://cdn.uviewui.com/uview/swiper/1.jpg',
        },
        {
          'id':2,
          'src':'https://cdn.uviewui.com/uview/swiper/2.jpg',
        },
        {
          'id':3,
          'src':'https://cdn.uviewui.com/uview/swiper/3.jpg',
        }
      ],
      'sku':[
        {
          'name':'颜色',
          'list':[
            {
              'id':8,
              'name':'红色',
            },
            {
              'id':9,
              'name':'蓝色'
            },
            {
              'id':10,
              'name':'白加黑'
            },
            {
              'id':11,
              'name':'紫色'
            },
            {
              'id':12,
              'name':'绿色'
            },
          ]
        },
        {
          'name':'尺码',
          'list':[
            {
              'id':13,
              'name':'S'
            },
            {
              'id':14,
              'name':'M'
            },
            {
              'id':15,
              'name':'L'
            },
            {
              'id':16,
              'name':'XL'
            },
            {
              'id':17,
              'name':'XXL'
            },
          ]
        },
      ]
    };
    goodsDetail = GoodsModel.fromJson(data);
    bannerList = goodsDetail!.images;
    // 使用assignAll方法将普通的List<Sku>赋值给RxList<Sku>。这是GetX库中推荐的方法来填充响应式列表。
    skuList.assignAll(goodsDetail!.sku); // 正确地将List<Sku>转换为RxList<Sku>
    update(["goods_detail"]);
  }

  // 更改数量
  onTapChangePayNum(int value){
    payNum.value = value;
  }

  // SKU切换
  void onTapChangeSku(index,i){
    // 先循环,当前点击的小规格默认全部为false
    skuList[index].list.forEach((sku)=> sku.show.value = false);
    // 在赋值,当前点击项true
    skuList[index].list[i].show.value = true;
    update(["goods_detail"]);
  }

  // 下单
  void submit(){
    // 每次先清空,在去把已选择的sku放到skuSelected中
    skuSelected.clear();
    skuList.forEach((item){
      item.list.forEach((val){
        if(val.show.value){
          skuSelected.add(val);
        }
      });
    });
    if(skuSelected.length != skuList.length){
      Loading.error('请先选择完整规格');
    }else{
      print('下单方式:$type,购买数量:$payNum,看看选择了哪些规格:${skuSelected[0].id},${skuSelected[1].id}');
    }
  }


  @override
  void onInit() {
    super.onInit();
  }

  @override
  void onReady() {
    super.onReady();
    _initData();
  }

  @override
  void onClose() {
    super.onClose();
  }
}

view

import 'package:flutter/material.dart';
import 'package:flutter_aidishi/extension/index.dart';
import 'package:flutter_aidishi/widget/index.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:tdesign_flutter/tdesign_flutter.dart';
import 'package:flutter_swiper_null_safety/flutter_swiper_null_safety.dart';

import 'index.dart';

class GoodsDetailPage extends GetView<GoodsDetailController> {
  const GoodsDetailPage({super.key});

  //  轮播图
  Widget _buildBanner() {
    return Container(
      height: 312.w,
      child: Swiper(
        autoplay: true, // 自动轮播
        itemCount: 2, // 轮播数量
        loop: true, // 循环
        pagination: const SwiperPagination(
          alignment: Alignment.bottomCenter, // 指示器位置
          builder: TDSwiperPagination.dotsBar, // 具体样式
        ),
        itemBuilder: (BuildContext context, int index) {
          return GestureDetector(
            onTap: (){
              print('点击了第$index个图片');
            },
            // child: TDImage(imgUrl: controller.bannerList[index].src,),
            child: TDImage(assetUrl: 'assets/images/banner.png',type: TDImageType.square,),
          );
        },
      ),
    );
  }

  // 商品信息
  Widget _buildGoodsName() {
    return Container(
      padding: EdgeInsets.only(left: 15.w,right: 15.w,top: 20.w,bottom: 20.w),
      width: 375.w,
      color: Colors.white,
      child: <Widget>[
        Text("${controller.goodsDetail?.name}",
          maxLines: 2,
          overflow: TextOverflow.ellipsis,
          style: TextStyle(fontSize: 15.sp,fontWeight: FontWeight.bold),
        ),
        SizedBox(height: 15.w,),
        <Widget>[
          <Widget>[
            Text('¥',style: TextStyle(fontSize: 13.sp,color: AppColors.mainColor),),
            Text('${controller.goodsDetail?.price}',style: TextStyle(fontSize: 24.sp,color: AppColors.mainColor),),
            Text('/现价',style: TextStyle(fontSize: 13.sp,color: AppColors.mainColor),),
            SizedBox(width: 10.w,),
            Text('¥${controller.goodsDetail?.oldPrice}/原价',
              style: TextStyle(
                fontSize: 14.sp,
                color: AppColors.Color999,
                decoration: TextDecoration.lineThrough,
                decorationColor: AppColors.Color999
              ),
            )
          ].toRow(
            crossAxisAlignment: CrossAxisAlignment.baseline,
            textBaseline: TextBaseline.alphabetic
          ),
          Text('库存 ${controller.goodsDetail?.stock}',style: TextStyle(fontSize: 11.sp,color: AppColors.Color999),)
        ].toRow(
          mainAxisAlignment: MainAxisAlignment.spaceBetween
        )
      ].toColumn(
        crossAxisAlignment: CrossAxisAlignment.start
      ),
    );
  }

  // 商品详情
  Widget _buildGoodsDetail() {
    return <Widget>[
      SizedBox(height: 10.w,),
      <Widget>[
        Container(
          width: 50.w,
          height: 1,
          color: Color(0xffe1e1e1),
        ),
        SizedBox(width: 10.w,),
        Text('商品详情',style: TextStyle(fontSize: 11.sp,color: AppColors.Color999),),
        SizedBox(width: 10.w,),
        Container(
          width: 50.w,
          height: 1,
          color: Color(0xffe1e1e1),
        ),
      ].toRow(
        mainAxisAlignment: MainAxisAlignment.center,
      ),
      SizedBox(height: 10.w,),
      Container(
        height: 500.w,
        color: Colors.blueGrey,
        child: Center(
          child: Text('富文本'),
        ),
      )
    ].toColumn();
  }

  // 可滚动内容区域
  Widget _buildTop(){
    return SingleChildScrollView(
      child: <Widget>[
        _buildBanner(),
        SizedBox(height: 15.w,),
        _buildGoodsName(),
        _buildGoodsDetail(),
      ].toColumn(),
    );
  }

  // 底部悬浮按钮
  Widget _buildGoodsFoot(BuildContext context) {
    return <Widget>[
      GestureDetector(
          onTap: (){
            print('弹出购物车');
            controller.type = 1;
            Navigator.of(context).push(
                TDSlidePopupRoute(
                    modalBarrierColor: TDTheme.of(context).fontGyColor2,
                    slideTransitionFrom: SlideTransitionFrom.bottom,
                    builder: (context) {
                      return Container(
                        width: 375.w,
                        height: 500.w,
                        decoration: BoxDecoration(
                            color: Colors.white,
                            borderRadius: BorderRadius.only(topLeft: Radius.circular(10.w),topRight: Radius.circular(10.w)),
                        ),
                        child: _buildFootPopup(context),
                      );
                    }
                )
            );
          },
          child: Container(
            width: 187.5.w,
            height: 49.w,
            color: Colors.white,
            child: Center(
              child: Text('加入购物车',style: TextStyle(fontSize: 14.sp),),
            ),
          )
      ),
      GestureDetector(
          onTap: (){
            print('立即结算');
            controller.type = 2;
            Navigator.of(context).push(
                TDSlidePopupRoute(
                    modalBarrierColor: TDTheme.of(context).fontGyColor2,
                    slideTransitionFrom: SlideTransitionFrom.bottom,
                    builder: (context) {
                      return Container(
                        width: 375.w,
                        height: 500.w,
                        decoration: BoxDecoration(
                            color: Colors.white,
                            borderRadius: BorderRadius.circular(10.w)
                        ),
                        child: _buildFootPopup(context),
                      );
                    }
                )
            );
          },
          child: Container(
            width: 187.5.w,
            height: 49.w,
            color: AppColors.mainColor,
            child: Center(
              child: Text('立即购买',style: TextStyle(fontSize: 14.sp,color: AppColors.Colorfff),),
            ),
          )
      ),
    ].toRow();
  }

  // 多规格弹窗
  Widget _buildFootPopup(BuildContext context){
    return Container(
      padding: EdgeInsets.all(15.w),
      child: <Widget>[
        // 商品信息
        <Widget>[
          TDImage(
            assetUrl: 'assets/images/goods.png',
            type: TDImageType.roundedSquare,
            width: 74,
            height: 74,
          ),
          Container(
            width: 220.w,
            child: <Widget>[
              Text('精品小米等多种五谷杂粮精品小米等多精品小米等多种五谷杂粮精品小米等多种五谷杂粮种五谷杂粮',
                maxLines: 2,
                overflow: TextOverflow.ellipsis,
                style: TextStyle(fontSize: 14.sp,fontWeight: FontWeight.bold),
              ),
              SizedBox(height: 10.w,),
              <Widget>[
                Text('¥',style: TextStyle(fontSize: 12.sp,color: AppColors.mainColor),),
                Text('2500',style: TextStyle(fontSize: 16.sp,color: AppColors.mainColor),),
                Text('/现价',style: TextStyle(fontSize: 12.sp,color: AppColors.mainColor),),
                SizedBox(width: 10.w,),
                Text('¥2000/原价',
                  style: TextStyle(
                      fontSize: 10.sp,
                      color: AppColors.Color999,
                      decoration: TextDecoration.lineThrough,
                      decorationColor: AppColors.Color999
                  ),
                )
              ].toRow(
                crossAxisAlignment: CrossAxisAlignment.baseline,
                textBaseline: TextBaseline.alphabetic
              )
            ].toColumn(),
          ),
          GestureDetector(
            onTap: ()=> Navigator.of(context).pop(),
            child: TDImage(
            assetUrl: 'assets/images/close.png',
            type: TDImageType.roundedSquare,
            width: 20,
            height: 20,
          ),
          )
        ].toRow(
          crossAxisAlignment: CrossAxisAlignment.start,
          mainAxisAlignment: MainAxisAlignment.spaceBetween
        ),
        SizedBox(height: 15.w,),
        Container(
          width: 345.w,
          height: 1,
          color: Color(0xfff5f5f5),
        ),
        SizedBox(height: 10.w,),
        // 库存
        <Widget>[
          Text('库存:886',style: TextStyle(fontSize: 13.sp),),
          <Widget>[
            Text('数量',style: TextStyle(fontSize: 13.sp),),
            SizedBox(width: 10.w,),
            // 选择数量
            TDStepper(
              theme: TDStepperTheme.filled,
              value:controller.payNum.value,
              min:1,
              onChange:(value){
                controller.onTapChangePayNum(value);
              }
            )
          ].toRow()
        ].toRow(
          mainAxisAlignment: MainAxisAlignment.spaceBetween
        ),
        SizedBox(height: 20.w,),
        Container(
          height: 260.w, // 固定高度
          color: Colors.white, // 可选:可改成灰色,查看滚动区域
          child: SingleChildScrollView(
            // 如果内容在垂直方向上超出Container的高度,则可以滚动
            child: Column(
              // 使用Column来垂直排列子组件
              children: <Widget>[
                // 大规格
                for(var index = 0;index<controller.skuList.length;index++)
                  <Widget>[
                    Container(
                      width: 375.w,
                      child: Text('${controller.skuList[index].name}'),
                    ),
                    SizedBox(height: 10.w,),
                    <Widget>[
                      // 小规格
                      // for(var val in item.list)
                      for(var i = 0;i<controller.skuList[index].list.length;i++)
                        GestureDetector(
                          onTap: (){
                            controller.onTapChangeSku(index,i);
                          },
                          child: Obx(() =>Container(
                            margin: EdgeInsets.only(right: 20.w,bottom: 20.w,left: 0),
                            padding: EdgeInsets.only(left: 30.w,right: 30.w,top: 6.w,bottom: 6.w),
                            child: Text('${controller.skuList[index].list[i].name}'),
                            decoration: BoxDecoration(
                                color: controller.skuList[index].list[i].show.value ? Color(0xffffffff) : Color(0xffF8F8F8),
                                borderRadius: BorderRadius.circular(5.w),
                                border: Border.all(
                                    color: controller.skuList[index].list[i].show.value ? Color(0xffFF770F) : Color(0xff999999),
                                    width: 1.0
                                )
                            ),
                          )),
                        )
                    ].toWrap()
                  ].toColumn(
                      crossAxisAlignment: CrossAxisAlignment.start
                  ),
                // 你可以继续添加更多的子组件
              ],
            ),
          ),
        ),


        // 结算
        SizedBox(height: 20.w,),
        Container(
          width: 345.w,
          child: TDButton(
            text: '确定', // 按钮文案
            height: 43.w, // 自定义高度
            // 文案左侧图标,根据提交状态判断是否显示loading
            iconWidget: controller.loading ? TDLoading(
              size: TDLoadingSize.small,
              icon: TDLoadingIcon.circle,
            ) : Container(),
            size: TDButtonSize.large, // 按钮尺寸
            type: TDButtonType.fill, // 类型:填充,描边,文字
            theme: TDButtonTheme.primary, // 主题
            shape: TDButtonShape.round, // 形状:圆角,胶囊,方形,圆形,填充 rectangle, round, square, circle, filled
            isBlock: true, // 是否时Block
            onTap:controller.submit, // 点击提交按钮触发
            padding: EdgeInsets.all(0),
            margin: EdgeInsets.all(0),
          ),
        )


      ].toColumn(),
    );
  }

  // 主视图
  Widget _buildView(BuildContext context) {
    return <Widget>[
      _buildTop().expanded(),
      _buildGoodsFoot(context)
    ].toColumn();
  }

  @override
  Widget build(BuildContext context) {
    return GetBuilder<GoodsDetailController>(
      init: GoodsDetailController(),
      id: "goods_detail",
      builder: (_) {
        return Scaffold(
          appBar: AppBar(title: const Text("goods_detail")),
          body: _buildView(context),
          backgroundColor: Color(0xffF6F6F6),
        );
      },
    );
  }
}


http://www.kler.cn/a/427707.html

相关文章:

  • 单片机的中断系统
  • python编程Day12-属性和方法的分类
  • JavaWeb学习(2)(Cookie原理(超详细)、HTTP无状态)
  • 【触想智能】工业安卓一体机日常维护注意事项以及其应用领域分析
  • 基于Matlab的变压器仿真模型建模方法(8):三相变压器组的建模仿真
  • STORM写作系统用于多角度话题大纲的合成与检索
  • 了解 k8s 网络基础知识
  • 从excel数据导入到sqlsever遇到的问题
  • 第2章:CSS基本语法 --[CSS零基础入门]
  • 推荐在线Sql运行
  • Springboot 整合 Java DL4J 打造金融风险评估系统
  • IntelliJ+SpringBoot项目实战(25)--使用JavaMail发送邮件
  • Docker的彻底删除与重新安装(ubuntu22.04)
  • QT实战--带行号的支持高亮的编辑器实现(1)
  • Android12-Framework开机自启服务-应用-自动执行-循环检测
  • ORACLE表数据还原
  • 漫画之家:Spring Boot技术实现的漫画资源平台
  • 百度ueditor富文本插件多图片上传顺序混乱问题
  • JavaScript动态网络爬取:深入解析与实践指南
  • 分类算法中的样本不平衡问题及其解决方案