團隊打算用Flutter 覆寫項目,但是遇到了一個有點惱火的事情,以前安卓和IOS在商品的多規(guī)格選擇一般都有現成的庫。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": "規(guī)格",
"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": "規(guī)格",
"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": "規(guī)格",
"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字段下就是這商品的所有規(guī)格搭配,spec_info_list字段下為組成該搭配的各規(guī)格值。
我們給分別給規(guī)格搭配和組成規(guī)格搭配的規(guī)格值做了 model :
///商品規(guī)格搭配,對應multi_spec的元素
class ShopGoodsMultiSpec {
String image;
String unit;
int specificationId;
List<ShopGoodsMultiSpecSpecInfo> specInfoList;
int salesAmount;
String productCode;
int stock;
double unitPrice;
///構造函數什么的省略.....
}
///組成規(guī)格搭配的規(guī)格值,對應spec_info_list的元素
class ShopGoodsMultiSpecSpecInfo {
int specValueId;
int specNameId;
String specName;
String specValue;
///重載這個==運算符,為了防止使同一個規(guī)格值的兩個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 = {};
/// 實例化工具,傳入所有規(guī)格搭配 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);
}
/// 返回所有{規(guī)格名:List<規(guī)格值對象>}
Map<String, List<ShopGoodsMultiSpecSpecInfo>> getAllSpecValue() {
return allSpecValue;
}
/// 設置已選中的 {規(guī)格名:規(guī)格值 id}
void setSelectedIds(Map<String, int> selected) {
this.selected = selected;
}
/// 檢查某屬性是否可選 {規(guī)格名:規(guī)格值 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("-"));
}
/// 獲取規(guī)格搭配對象
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;
}
}
核心原理就是,每個規(guī)格搭配下的規(guī)格值的任意 不重復使用元素 的無序組合 都能代表該規(guī)格搭配。
如:
a,b,c 能夠生成的組合就是 [a, b, c, ab, ac, bc, abc]
那么,如果我們把每個規(guī)格搭配下的規(guī)格值的任意不重復使用元素的無序組合都收集在一個list (這里叫它allSpecKey) 中,我們在校驗某個規(guī)格值是否可選時,只需要把已選擇的規(guī)格值和待選擇的規(guī)格值組合起來,然后判斷這個組合是否在allSpecKey中就可確定待選擇的規(guī)格值是否可選。
那么 在渲染每一個規(guī)格值按鈕組件時,只需要調用checkEnable方法,就可以檢查是否可選,getSpec方法可以在選擇完一個規(guī)格搭配后拿取過個搭配對象。如果沒選擇完的話,返回的是 null,剛剛方便識別是否有選擇完成。當然,每次選中一個規(guī)格值時一定要執(zhí)行setSelectedIds設置一下已經選中的規(guī)格Map。