商品规格sku算法应用

近期在做商城商品规格这个模块,规格联动这边看似简单,实则坑(学问)深着呢。只怪自己轻视了它,未经思考便上手,于是越写越棘手,越绕越晕。。。

两天后,成功地把自己套牢,写不下去了。

上网寻找,看了淘宝SKU组合查询算法实现后才恍然大悟,悔恨不已。上午研究了一下文中提到的解决思路,下午删除原先的代码采用此方案,完美解决。而且对于规格的扩展也一并兼容,功能强大的一比。

现整理一下思路供自己日后温习,感兴趣的朋友也欢迎批评指正。

先定义一下接下来会用到的名词:
规格类型—比如:颜色、尺码、大小
规格名称—比如:红色、M、大
规格标签—页面规格名称所在的dom元素
SKU—唯一确定一件商品,即{颜色:红;尺码:XL;大小:小}
SkuResult—所有不同规格类型之间规格名称库存大于0的组合,类型如[{“红色”:{id:555,count:100}},{“红色,XL”:{id:666,count:38}},{“红色,XL,小”:{id:888,pirce:25}}…]

考虑一下,页面初始化的时候,如果需求不是默认要选中一个sku的话(初始未选中任何一个规格标签),就需要判断每个规格标签是否要禁用。那么问题又来了,怎样判断呢?

思路如下:
1. 遍历每一个标签,标签信息包含了规格类型和名称
2. 查找这个标签是否存在于集合SkuResult
3. 存在则高亮,否则禁用

前端html显示:

<!-- 规格html -->
<div class="pucSpecifica">
    <div class="SpecificaBox">
        <h5>颜色:</h5>
        <ul>
            <li class="on">白色</li>
            <li class="disable">紫罗兰色</li>
            <li>粉红色</li>
            <li>蓝色</li>
            <li>黑色</li>
        </ul>
    </div>
    <div class="SpecificaBox">
        <h5>规格:</h5>
        <ul>
            <li>500ml</li>
            <li class="on">600ml</li>
            <li>1000ml</li>
            <li class="disabled">1500ml</li>
        </ul>
    </div>
    <div class="SpecificaBox">
        <h5>材质:</h5>
        <ul>
            <li>塑料</li>
            <li>玻璃</li>
            <li></li>
            <li>不锈钢</li>
        </ul>
    </div>
</div>

js实现初始禁用:

//#region 禁用标签
function DisableTag(self) {
    //已经选择的节点
    var selectedObjs = $('.SpecificaBox .on');

    if (selectedObjs.length) {
        //获得组合key价格
        var selectedSepc = [];
        selectedObjs.each(function () {
            selectedSepc.push($(this)[0].innerText);
        });
        var len = selectedSepc.length;

        //用已选中的节点验证待测试节点
        var filter;
        if (self) filter = self.siblings('li');//当前规格下的标签
        $(".SpecificaBox li").not(selectedObjs).not(filter).each(function () {
            var blowSepc = $(this)[0].parentNode.parentNode;
            var index = 0;//当前规格类型索引
            $(".SpecificaBox").each(function (i, item) {
                if (item == blowSepc) index = i;//获取所属规格类型的索引
            });
            var siblingsSelectedObj = $(this).siblings('.on');

            var specInfos = [];//待验证规格和另外N种选中规格的组合

            if (siblingsSelectedObj.length) {
                var siblingsSelectedName = siblingsSelectedObj[0].innerText;
                for (var i = 0; i < len; i++) {
                    if (selectedSepc[i] != siblingsSelectedName) specInfos.push(selectedSepc[i]);
                    else {
                        if (i == index)//只有属于当前规格才置null
                        { specInfos.push(null); sIndex = i; }
                        else specInfos.push(selectedSepc[i]);
                    }
                }
            } else {
                specInfos = selectedSepc.concat();
            }
            specInfos[sIndex] = $(this)[0].innerText;
            if (!SKUResult[specInfos.join(';')]) {
                $(this).addClass('disabled').removeClass('on');
            } else {
                $(this).removeClass('disabled');
            }
        });
    }
    else {//未选择任何标签时
        //设置属性状态
        $('.SpecificaBox li').each(function () {
            SKUResult[$(this)[0].innerText] ? $(this).removeClass('disabled') : $(this).addClass('disabled').removeClass('on');
        })
    }
}
//#endregion

备注:这个js方法需要在初始化以及规格标签点击事件时调用,前者不传参数,后一个传当前选中的标签$(this)。

再来看看标签禁用的原理吧,遍历每一个class不是on(即未选中)的标签,比如现在是“红色”标签,那么需要再获取到其余类型(本示例中是规格:1500ml和材质:还未选中)class=on的标签,则根据SkuResult[红色,1500ml]存在与否禁用红色标签即可。

示例中是先找到class=on的规格标签放进Array,然后用待验证的标签替换掉数组中对应的规格标签形成新的Array,这个新数组join成字符串就是要放到SkuResult中验证存在的属性。


SkuResult的构建过程:

以上示例均是建立在SkuResult实例化的基础上的,一下贴出SkuResult的构建代码,详见注释:

//#region 获得对象的key
function getObjKeys(obj) {
    if (obj !== Object(obj)) throw new TypeError('Invalid object');
    var keys = [];
    for (var key in obj)
        if (Object.prototype.hasOwnProperty.call(obj, key))
            keys[keys.length] = key;
    return keys;
}
//#endregion
//#region 把组合的key放入结果集SKUResult
function add2SKUResult(combArrItem, sku) {
    var key = combArrItem.join(";");
    if (SKUResult[key]) {//SKU信息key属性·
        SKUResult[key].count += sku.count;//库存求和
        SKUResult[key].id.push(sku.id);
    } else {
        SKUResult[key] = {
            count: sku.count,
            id: [sku.id]
        };
    }
}
//#endregion
//#region 初始化得到结果集
function initSKU() {
    var i, j, skuKeys = getObjKeys(data);
    for (i = 0; i < skuKeys.length; i++) {
        var skuKey = skuKeys[i];//一条SKU信息key:'红色;中;XL'
        var sku = data[skuKey]; //一条SKU信息value:{id:660,count:999}
        var skuKeyAttrs = skuKey.split(";"); //SKU信息key属性值数组

        //对每个SKU信息key属性值进行拆分组合
        var combArr = arrayCombine(skuKeyAttrs);
        for (j = 0; j < combArr.length; j++) {
            add2SKUResult(combArr[j], sku);
        }

        //结果集接放入SKUResult
        if (sku.count)
            SKUResult[skuKey] = {
                count: sku.count,
                id: [sku.id]//包括规格(skuKey)的所有规格id集合
            }
    }
}
//#endregion
//#region 从数组中生成指定长度的组合
/**
 * 从数组中生成指定长度的组合
 */
function arrayCombine(targetArr) {
    if (!targetArr || !targetArr.length) {
        return [];
    }

    var len = targetArr.length;
    var resultArrs = [];

    // 所有组合
    for (var n = 1; n < len; n++) {
        var flagArrs = getFlagArrs(len, n);
        while (flagArrs.length) {
            var flagArr = flagArrs.shift();
            var combArr = [];
            for (var i = 0; i < len; i++) {
                flagArr[i] && combArr.push(targetArr[i]);
            }
            resultArrs.push(combArr);
        }
    }

    return resultArrs;
}
//#endregion

备注:data即数据源,是所有的sku组合,构建后的SkuResult其实包含了data的结果集。

如果你后台获取的数据源有差异,则需要自己处理一下,转成data对象,
var data = {};//对象化规格数据{"白色,大":{id:660,count:0},"白色,中":{id:661,count:88}...}
示例中是按照此格式转化的,id是规格编号,count是库存。

堆这么长,而且这么枯燥,相信除了自己,没人 能看到这里了,总之,哪怕能对你有一点启发或帮助,我已经很满足了!

阅读更多
文章标签: 算法 商城
个人分类: 学习笔记
上一篇Js Click事件重复注册
下一篇IIS最大工作进程数设置引发串号
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭