Flutter 多规格商品选择器核心工具 SKU

团队打算用Flutter 覆写项目,但是遇到了一个有点恼火的事情,以前安卓和IOS在商品的多规格选择一般都有现成的库。Flutter 由于刚兴起,这方面的库我目前还没找到,于是只能自己撸一个了。
首先看看我们某个商品数据结构:

{
  "attributes": [],
  "goods_id": 1636,
  "goods_name": "珍珠奶茶",
  "image": "http://********/static/goods_images/Rlp6d1536542627.png",
  "multi_spec": [
    {
      "image": "http://********/static/goods_images/EQqf01536544612.png",
      "product_code": "",
      "sales_amount": 45,
      "spec_info_list": [
        {
          "spec_name": "规格",
          "spec_name_id": 336,
          "spec_value": "大杯",
          "spec_value_id": 1019
        },
        {
          "spec_name": "颜色",
          "spec_name_id": 337,
          "spec_value": "红色",
          "spec_value_id": 1021
        },
        {
          "spec_name": "重量",
          "spec_name_id": 338,
          "spec_value": "半斤",
          "spec_value_id": 1023
        }
      ],
      "specification_id": 1918,
      "stock": -1,
      "unit": "杯",
      "unit_price": 7
    },
    {
      "image": "http://********/static/goods_images/Fe30S1536544637.png",
      "product_code": "",
      "sales_amount": 16,
      "spec_info_list": [
        {
          "spec_name": "规格",
          "spec_name_id": 336,
          "spec_value": "中杯",
          "spec_value_id": 1020
        },
        {
          "spec_name": "颜色",
          "spec_name_id": 337,
          "spec_value": "绿色",
          "spec_value_id": 1022
        },
        {
          "spec_name": "重量",
          "spec_name_id": 338,
          "spec_value": "一斤",
          "spec_value_id": 1024
        }
      ],
      "specification_id": 1919,
      "stock": -1,
      "unit": "杯",
      "unit_price": 6
    },
    {
      "image": "http://********/static/goods_images/Fe30S1536544637.png",
      "product_code": "",
      "sales_amount": 16,
      "spec_info_list": [
        {
          "spec_name": "规格",
          "spec_name_id": 336,
          "spec_value": "中杯",
          "spec_value_id": 1020
        },
        {
          "spec_name": "颜色",
          "spec_name_id": 337,
          "spec_value": "红色",
          "spec_value_id": 1021
        },
        {
          "spec_name": "重量",
          "spec_name_id": 338,
          "spec_value": "一斤",
          "spec_value_id": 1024
        }
      ],
      "specification_id": 1919,
      "stock": -1,
      "unit": "杯",
      "unit_price": 6
    }
  ],
  "pack_cost": 0,
  "product_code": null,
  "sales_amount": 61,
  "stock": -2,
  "unit": null,
  "unit_price": null
}

multi_spec字段下就是这商品的所有规格搭配,spec_info_list字段下为组成该搭配的各规格值。
我们给分别给规格搭配和组成规格搭配的规格值做了 model :

///商品规格搭配,对应multi_spec的元素
class ShopGoodsMultiSpec {
    String image;
    String unit;
    int specificationId;
    List<ShopGoodsMultiSpecSpecInfo> specInfoList;
    int salesAmount;
    String productCode;
    int stock;
    double unitPrice;
 ///构造函数什么的省略.....
}
///组成规格搭配的规格值,对应spec_info_list的元素
class ShopGoodsMultiSpecSpecInfo {
    int specValueId;
    int specNameId;
    String specName;
    String specValue;
    
     ///重载这个==运算符,为了防止使同一个规格值的两个ShopGoodsMultiSpecSpecInfo对象
     ///被判断为不等,在工具中的 allSpecValue[spec_info.specName].contains(spec_info) 有用,不然会出问题
    // @override
    // int get hashCode => "ShopGoodsMultiSpecSpecInfo_$specValueId".hashCode;
    
    bool operator ==(o){
        ShopGoodsMultiSpecSpecInfo obj = o;
    return obj.specValueId == specValueId;
    }

///构造函数什么的省略.....
}

贴上工具代码:

class SpecSkuUtil {
  List<String> allSpecKey = [];
  Map<String, ShopGoodsMultiSpec> allSpec = {};
  Map<String, List<ShopGoodsMultiSpecSpecInfo>> allSpecValue = {};
  Map<String, int> selected = {};

  /// 实例化工具,传入所有规格搭配 list
  SpecSkuUtil(List<ShopGoodsMultiSpec> multiSpec) {
    for (ShopGoodsMultiSpec spec in multiSpec) {
      List valueIds = [];
      for (ShopGoodsMultiSpecSpecInfo spec_info in spec.specInfoList) {
        valueIds.add(spec_info.specValueId);
        if (!allSpecValue.containsKey(spec_info.specName)) {
          allSpecValue[spec_info.specName] = [spec_info];
        } else if (!allSpecValue[spec_info.specName].contains(spec_info)) {
          allSpecValue[spec_info.specName].add(spec_info);
        }
      }
      valueIds.sort((a, b) => a.compareTo(b));
      valueIds = valueIds.map((id) {
        return id.toString();
      }).toList();
      allSpec[valueIds.join("-")] = spec;
      _createCollocations(valueIds);
    }
  }

  void _createCollocations(List strList) {
    void build(List candidate, String prefix, int index) {
      if (!allSpecKey.contains(prefix)) {
        allSpecKey.add(prefix);
      }
      for (int i = index; i < candidate.length; i++) {
        List tmp = new List()..addAll(candidate);
        build(tmp, (prefix == "" ? "" : prefix + "-") + tmp.removeAt(i), i);
      }
    }

    build(strList, "", 0);
  }

  /// 返回所有{规格名:List<规格值对象>}
  Map<String, List<ShopGoodsMultiSpecSpecInfo>> getAllSpecValue() {
    return allSpecValue;
  }

  /// 设置已选中的 {规格名:规格值 id}
  void setSelectedIds(Map<String, int> selected) {
    this.selected = selected;
  }

  /// 检查某属性是否可选 {规格名:规格值 id}
  bool checkEnable(Map<String, int> candidate) {
    Map<String, int> tmpMap = Map.from(selected);
    tmpMap.addAll(candidate);
    List tmp = mapValue2List(tmpMap);
    tmp.sort((a, b) => a.compareTo(b));
    return allSpecKey.contains(tmp.join("-"));
  }

  /// 获取规格搭配对象
  ShopGoodsMultiSpec getSpec() {
    List tmp = mapValue2List(selected);
    tmp.sort((a, b) => a.compareTo(b));
    return allSpec[tmp.join("-")];
  }

  /// map的值转 list
  List mapValue2List(Map<String, int> map) {
    List tmp = new List();
    map.forEach((key, value) {
      ///至于判断不为空才加入 list,这个是看实际情况
      ///如果你的程序生成的已选中Map里面不会有 null 就可以不用判断
      if (value != null) {
        tmp.add(value);
      }
    });
    return tmp;
  }
}

核心原理就是,每个规格搭配下的规格值的任意 不重复使用元素 的无序组合 都能代表该规格搭配。
如:

a,b,c  能够生成的组合就是 [a, b, c, ab, ac, bc, abc]

那么,如果我们把每个规格搭配下的规格值的任意不重复使用元素的无序组合都收集在一个list (这里叫它allSpecKey) 中,我们在校验某个规格值是否可选时,只需要把已选择的规格值和待选择的规格值组合起来,然后判断这个组合是否在allSpecKey中就可确定待选择的规格值是否可选。

那么 在渲染每一个规格值按钮组件时,只需要调用checkEnable方法,就可以检查是否可选,getSpec方法可以在选择完一个规格搭配后拿取过个搭配对象。如果没选择完的话,返回的是 null,刚刚方便识别是否有选择完成。当然,每次选中一个规格值时一定要执行setSelectedIds设置一下已经选中的规格Map。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,519评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,842评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,544评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,742评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,646评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,027评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,169评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,324评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,268评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,299评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,996评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,591评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,911评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,288评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,871评论 2 341

推荐阅读更多精彩内容