Flutter 多規格商品選擇器核心工具 SKU

CyJay發表於2019-03-11

團隊打算用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。

相關文章