不败君

前端萌新&初级后端攻城狮

使用Layui和Laravel开发商品多规格录入

使用Layui和Laravel开发商品多规格录入

2020-05-18 18:06:00

围观(1360)

开发一些商城会遇上这样的场景:

sku_1.png

例如一件衣服, 可能有 红色 / 黄色.

红色和黄色衣服的尺码又有 S 和 M 还有 L.


使用 Layui 实现前端, 直接上代码:

<!DOCTYPE html>
<html>
<head>
    <title>不败君 www.bubaijun.com</title>
    <meta charset="utf-8">
    <link rel="stylesheet" type="text/css" href="layui/css/layui.css">
    <script type="text/javascript" src="jquery-3.5.1.min.js"></script>
    <script type="text/javascript" src="layui/layui.js"></script>
    <style>
        layui-card-header {
            border-color: #EEEEEE;
        }

        .layui-form {
            margin-top: 30px;
            padding: 20px;
            background-color: #F9F9F9;
        }

        .layui-form-label {
            width: 100px;
        }

        .layui-input-block {
            margin-left: 130px;
        }

        .attr-key-item,#sku-table {
            background: #F9F9F9;
            padding: 20px;
        }

        .attr-key-item {
            padding-bottom: 0;
        }

        #sku-table table {
            width: 100%;
        }

        .attr-key-item .sku-title {
            margin-bottom: 10px
        }

        .attr-key-item .sku-title input{
            display: inline;
            width: 260px;
            margin-right: 10px;
        }

        .attr-vals {
            width: 100%;
            display: block;
        }

        .btn-remove-attr-key,.btn-add-attr-val,.btn-remove-attr-val {
            color: #0071F2;
        }

        #images .layui-input-inline {
            width: auto;
            margin-bottom: 10px;
        }

        .static-bottom {
            background: white;
            bottom: 0px;
            padding-bottom: 40px;
            left: 0;
            right: 0;
            padding-top: 20px;
            border-top: 1px solid #cccccc;
            text-align: center;
        }

        .static-bottom .layui-input-block {
            margin-left: 0;
        }
    </style>
</head>

<body>
    <form class="layui-form" action="add" id="myForm" method="post" style="">

        <div class="layui-row layui-col-space15">
            <div class="layui-col-md12">
                <div class="layui-card">
                    <div class="layui-card-header">销售信息</div>
                    <div class="layui-card-body">

                        <div class="layui-form-item">
                            <label class="layui-form-label">商品规格</label>
                            <div class="layui-input-inline">
                                <button type="button" class="layui-btn layui-btn-primary btn-add-attr-key">
                                    添加自定义商品规格
                                </button>
                            </div>
                        </div>

                        <!-- 放多规格 -->
                        <div id="sku-container"></div>

                        <!-- SKU表 -->
                        <div class="layui-form-item" id="sku-table"></div>
                    </div>
                </div>
            </div>
        </div>


        <div class="layui-form-item static-bottom">
            <div class="layui-input-block">
                <button class="layui-btn" lay-submit="myForm" lay-filter="myForm">立即提交</button>
            </div>
        </div>
    </form>

    <script type="text/javascript">
        // layui 公共配置
        const layconfig = {
            base: 'layui/js/',
            version: true,
            debug: true,
        };
    </script>
    <script type="text/javascript">
        const init = function (options) {
            var _modules = options.modules || [];
            _modules = _modules.concat('layer', 'form', 'element');

            layui.config(layconfig).use(_modules, function (opt) {

                var form = layui.form,
                    laytpl = layui.laytpl,
                    element = layui.element;

                // 其他事件
                if (options.exec) {
                    options.exec(layui);
                }

                form.render();
            });
        };
    </script>

    <script type="text/javascript">
    $(function () {
        var opt = {
            modules: ['table', 'form', 'element', 'laytpl', 'tree'],
            exec: function (layui) {

                // -- 商品规格开始 --

                // 添加自定义 attr key
                var already_set_sku_vals = {};      // 已经设置的SKU值数据
                var arr_sku = {};                   // 已经设置的SKU值数据
                var attr_key_id = '';               // 自定义 attr key id
                var attr_key_sort = 0;              // 自定义 attr key sort
                var attr_val_id = '';               // 自定义 attr val id
                var attr_val_sort = 0;              // 自定义 attr val sort

                $(document).on("click", ".btn-add-attr-key", function () {
                    var tpl_attr_key_model = $("#tpl-attr-key-model").html();
                    attr_key_id = uuid(32, 16);
                    attr_val_id = uuid(32, 16);
                    attr_key_sort++;
                    var data = {attr_key_id: attr_key_id, attr_val_id: attr_val_id, attr_key_sort: attr_key_sort};
                    layui.laytpl(tpl_attr_key_model).render(data, (html) => {
                        $("#sku-container").append(html);   // 添加到该按钮的前面
                        layui.form.render('checkbox');
                        initSkuTable();
                    });
                });

                // 删除自定义 attr key
                $(document).on("click", ".btn-remove-attr-key", function () {
                    $(this).parent().parent().remove(); // 添加到该按钮的前面
                    initSkuTable();
                });

                // 监听 attr key 输入
                $(document).on("change", ".attr-key", function () {
                    var current_val = $(this).val();
                    // 判断标题是否已经重复了
                    var appear_time = 0;
                    $(".attr-key").each(function () {
                        var val = $(this).val();
                        if (current_val == val) {
                            appear_time++;
                        }
                    });

                    if (appear_time > 1) {
                        layer.msg('该规格名称已经存在!', {time: 1000});
                        $(this).val("");
                        return;
                    }
                    initSkuTable();
                });

                // 监听attr val 输入
                $(document).on("change", ".attr-val", function () {
                    var current_val = $(this).val();
                    // 判断值是否已经重复了
                    var appear_time = 0;
                    $(".attr-val").each(function () {
                        var val = $(this).val();
                        if (current_val == val) {
                            appear_time++;
                        }
                    });
                    if (appear_time > 1) {
                        layer.msg('该规格值已经存在!', {time: 1000});
                        $(this).val("");
                        return;
                    }
                    initSkuTable();
                });

                // 监听attr_val 输入
                layui.form.on('checkbox(attr-val)', function (data) {
                    initSkuTable();
                });

                // 绘制表格
                function initSkuTable() {
                    get_already_set_sku_vals();     // 获取已经设置的SKU值
                    var attrs =  [];                // 存放属性数组
                    var total_row = 1;              // 总行数
                    var sku_table_dom = "";         // sku表格dom

                    // 获取元素类型
                    $(".attr-key-item").each(function(index, item) {
                        var attr_key = {};
                        attr_key.attr_key = $(item).find(".attr-key").val();

                        var attr_vals = [];
                        $(item).find('.attr-val').each(function(index, item) {

                            var val = $(item).val();
                            if (val == undefined || val == "") {
                                return;
                            }
                            var attr_val = {};  // attr val对象
                            attr_val.attr_key_id = $(this).attr("attr_key_id");
                            attr_val.attr_val_id = $(this).attr("attr_val_id");
                            attr_val.attr_val = val;
                            attr_vals.push(attr_val);
                        });

                        if (attr_vals && attr_vals.length > 0) {
                            total_row = total_row * attr_vals.length;
                            attr_key.attr_vals = attr_vals;         // sku值数组
                            attrs.push(attr_key);                   // 保存进数组中
                        }
                    });

                    sku_table_dom = "<table class='layui-table skuTable'><thead><tr>";
                    // 创建表头
                    for(var i = 0 ; i < attrs.length ; i ++){
                        sku_table_dom += '<th>'+attrs[i].attr_key+'</th>';
                    }
                    sku_table_dom += '<th>价格</th><th>库存</th>';
                    sku_table_dom += "</thead></tr>";

                    // 循环处理表体
                    for(var i = 0 ; i < total_row ; i ++){ //总共需要创建多少行
                        var curr_row_doms = "";
                        var row_count = 1;      // 记录行数
                        var attr_key_ids = [];  // 记录 attr key id
                        var attr_val_ids = [];  // 记录 attr val id
                        var attr_key_arr = [];  // 记录 attr key
                        var attr_val_arr = [];  // 记录 attr val

                        for(var j = 0 ; j < attrs.length ; j ++) {
                            var attr_vals = attrs[j].attr_vals;                   // attr val 数组
                            var attr_vals_len = attr_vals.length;                 // attr val 长度
                            row_count = (row_count * attr_vals_len);              // 目前的生成的总行数
                            var an_inter_bank_num = (total_row / row_count);      // 跨行数
                            var point = ((i / an_inter_bank_num) % attr_vals_len);
                            attr_key_arr.push(attrs[j].attr_key);
                            if (0  == (i % an_inter_bank_num)) {
                                // 需要创建td
                                curr_row_doms += '<td rowspan='+an_inter_bank_num+'>' + attr_vals[point].attr_val + '</td>';
                                attr_val_arr.push(attr_vals[point].attr_val);
                                attr_val_ids.push(attr_vals[point].attr_val_id);
                                attr_key_ids.push(attr_vals[point].attr_key_id);
                            }
                            else{
                                // 当前单元格为跨行
                                attr_val_arr.push(attr_vals[parseInt(point)].attr_val);
                                attr_val_ids.push(attr_vals[parseInt(point)].attr_val_id);
                                attr_key_ids.push(attr_vals[parseInt(point)].attr_key_id);
                            }
                        }

                        var str_val_ids = attr_val_ids.toString()
                        var already_set_sku_price = ""; // 已经设置的SKU价格
                        var already_set_sku_stock = ""; // 已经设置的SKU库存

                        if(already_set_sku_vals){
                            var curr_group_sku_val = already_set_sku_vals[str_val_ids];//当前这组SKU值
                            if(curr_group_sku_val){
                                already_set_sku_price = curr_group_sku_val.sku_price;
                                already_set_sku_stock = curr_group_sku_val.sku_stock;
                            }
                        }

                        sku_table_dom += '<tr attr_val_ids="'+attr_val_ids+'" attr_key_ids="'+attr_key_ids.toString()+'" attr_val_names="'+attr_val_arr.join(";")+'"  propnames="'+attr_key_arr.join(";")+'" class="sku_table_tr">' + curr_row_doms +
                            '<td><input type="number" step="0.01" min="0.01" name="sku_price" class="layui-input" value="'+already_set_sku_price+'"/></td>' +
                            '<td><input type="number" step="0.01" min="0.01" name="sku_stock" class="layui-input" value="'+already_set_sku_stock+'"/></td>' +
                            '</tr>';
                    }
                    $("#sku-table").html(sku_table_dom);
                    get_already_set_sku_vals();
                    layui.form.render("checkbox");
                }

                // 单击添加 attr val
                $(document).on("click", ".btn-add-attr-val", function () {
                    var tpl_attr_val_model = $("#tpl-attr-val-model").html();
                    attr_val_sort++;
                    attr_val_id = uuid(32, 16);
                    attr_key_id = $(this).attr('attr_key_id');
                    var data = {attr_key_id: attr_key_id, attr_val_id: attr_val_id, attr_val_sort: attr_val_sort};
                    layui.laytpl(tpl_attr_val_model).render(data, (html) => {
                        $(this).parent().parent().parent().find(".attr-vals").append(html); // 添加到该按钮的前面
                        layui.form.render('checkbox');
                    });
                    initSkuTable();
                });

                // 单击删除 attr val
                $(document).on("click", ".btn-remove-attr-val", function () {
                    $(this).parent().parent().remove(); // 添加到该按钮的前面
                    initSkuTable();
                });

                function get_already_set_sku_vals(){
                    already_set_sku_vals = {};
                    arr_sku = {};
                    //获取设置的SKU属性值
                    $("tr.sku_table_tr").each(function() {
                        var sku_price = $(this).find("input[name=sku_price]").val();    // SKU价格
                        var sku_stock = $(this).find("input[name=sku_stock]").val();    // SKU库存
                        if (sku_price || sku_stock) {
                            // 已经设置了全部或部分值
                            var attr_val_ids = $(this).attr("attr_val_ids");    // SKU值id集合
                            // var attr_val_names = $(this).attr("attr_val_names");//SKU值name集合
                            already_set_sku_vals[attr_val_ids] = {
                                "sku_price" : sku_price,
                                "sku_stock" : sku_stock,
                            };
                            arr_sku[attr_val_ids] = {
                                "sku_price" : sku_price,
                                "sku_stock" : sku_stock,
                            }
                        }
                    });
                }

                layui.form.on('checkbox(unify-price)', function (data) {
                    var unify_price = $("input[name=unify_price]").val();
                    if (data.elem.checked) {
                        $("input[name=sku_price]").val(unify_price);
                    }
                    get_already_set_sku_vals();
                });

                layui.form.on('checkbox(unify-stock)', function (data) {
                    var unify_stock = $("input[name=unify_stock]").val();
                    if (data.elem.checked) {
                        $("input[name=sku_stock]").val(unify_stock);
                    }
                });

                function uuid(len, radix) {
                    var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
                    var uuid = [], i;
                    radix = radix || chars.length;

                    if (len) {
                        // Compact form
                        for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix];
                    } else {
                        // rfc4122, version 4 form
                        var r;

                        // rfc4122 requires these characters
                        uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
                        uuid[14] = '4';

                        // Fill in random data.  At i==19 set the high bits of clock sequence as
                        // per rfc4122, sec. 4.1.5
                        for (i = 0; i < 36; i++) {
                            if (!uuid[i]) {
                                r = 0 | Math.random() * 16;
                                uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
                            }
                        }
                    }

                    return uuid.join('');
                }

                // -- 商品规格结束 --

                layui.form.on('submit(myForm)', function () {
                    get_already_set_sku_vals();
                    var data = $('#myForm').serialize() + "&sku=" + JSON.stringify(arr_sku);
                    console.log(data);

                    $.ajax({
                        url: 'http://www.bubaijun.com/api/add_product_sku',
                        method: 'POST',
                        data: data,
                        success: function(res){
                            console.log(res);
                        }
                    });

                    return false;
                });

            }
        };
        init(opt);
    });

</script>
<!-- 自定义商品规格key -->
<script type="text/html" id="tpl-attr-key-model">
    <div class="layui-form-item attr-key-item">
        <div class="sku-title">
            <input type="text" name="attr_key[{{d.attr_key_id}}]" placeholder="规格名称,例如:颜色,尺寸" class="layui-input attr-key"><a class="btn-remove-attr-key" href="javascript:void(0);">删除</a>
        </div>
        <div class="attr-vals">
            <div class="layui-inline">
                <div class="layui-input-inline">
                    <input attr_val_id="{{d.attr_val_id}}" attr_key_id="{{d.attr_key_id}}" type="text" name="attr_val[{{d.attr_key_id}}][{{d.attr_val_id}}]" placeholder="例如:白色" lay-verify="required" class="attr-val layui-input">
                </div>
                <div class="layui-form-mid layui-word-aux">
                    <a class="btn-remove-attr-val" href="javascript:void(0);">删除</a>
                </div>
            </div>
        </div>

        <div class="layui-inline">
            <div class="layui-form-mid layui-word-aux">
                <a class="btn-add-attr-val" href="javascript:void(0);" attr_key_id="{{d.attr_key_id}}">添加自定义项</a>
            </div>
        </div>
    </div>
</script>
<script type="text/html" id="tpl-attr-val-model">
    <div class="layui-inline attr-vals-item">
        <div class="layui-input-inline">
            <input attr_val_id="{{d.attr_val_id}}" attr_key_id="{{d.attr_key_id}}" type="text" name="attr_val[{{d.attr_key_id}}][{{d.attr_val_id}}]" class="attr-val layui-input">
        </div>
        <div class="layui-form-mid layui-word-aux">
            <a class="btn-remove-attr-val" href="javascript:void(0);">删除</a>
        </div>
    </div>
</script>

</body>
</html>

其实上面这段前端代码, 是博主从工作的项目中提取的, 但是和原项目区别还是很大的(原项目代码 2K+ 行封装了很多东西, 上面这段代码只有400多行, 被博主优化删减只有商品规格了).


接下来就是后端, 后端使用 Laravel 测试. 注意看 409 行代码:

layui.form.on('submit(myForm)', function () {
    get_already_set_sku_vals();
    var data = $('#myForm').serialize() + "&sku=" + JSON.stringify(arr_sku);
    console.log(data);

    $.ajax({
        url: 'http://www.bubaijun.com/api/add_product_sku',
        method: 'POST',
        data: data,
        success: function(res){
            console.log(res);
        }
    });

    return false;
});

如果不需要异步提交, 把这段代码删除即可.

提交测试效果:

sku_2.png


可以看到能打印出提交的数据, 接着就是将数据处理一下, 直接上代码(Laravel 控制器内):

public function index(Request $request)
{
    $data = $request->all();

    $arr_attr_key = $data['attr_key'];
    $arr_attr_val = $data['attr_val'];

    $map_attr_key = [];
    $lst_obj_attr = []; // attr组 (一个attr 组 含 id,title,一堆跟title相应的值)

    foreach ($arr_attr_key as $attr_key_id => $attr_key) {

        if (empty($arr_attr_val[$attr_key_id])) {
            continue;
        }

        $attr_vals = $arr_attr_val[$attr_key_id];

        // attr_key
        $parent_sku_attrs = [
            'value' => $attr_key,
        ];

        $map_attr_key[$attr_key_id] = $parent_sku_attrs;

        foreach ($attr_vals as $attr_val_id => $attr_val) {

            // attr_val
            $obj_attr_val = [
                'value' => $attr_val,
            ];

            $obj_attr_val['attr_val_id'] = $attr_val_id;
            $lst_obj_attr[$attr_key_id][] = $obj_attr_val;
        }
    }

    $json_sku = json_decode($data['sku'], true);

    // 将多个attr整合SKU表数据
    $rows = [];

    foreach ($lst_obj_attr as $option => $items) {
        if (count($rows) > 0) {
            // 2、将第一列作为模板
            $clone = $rows;

            // 3、置空当前列表,因为只有第一列的数据,组合是不完整的
            $rows = [];

            // 4、遍历当前列,追加到模板中,使模板中的组合变得完整
            foreach ($items as $item) {
                $tmp = $clone;
                foreach ($tmp as $index => $value) {
                    $value[$option] = $item;
                    $tmp[$index] = $value;
                }

                // 5、将完整的组合拼回原列表中
                $rows = array_merge($rows, $tmp);
            }
        } else {
            // 1、先计算出第一列
            foreach ($items as $item) {
                $rows[][$option] = $item;
            }
        }
    }

    $lst_sku_data = $rows;

    $lst_product_sku = [];
    foreach ($lst_sku_data as $key => $sku) {

        $temp_data = [];
        $temp_data['product_id'] = '商品ID 可以是前端传过来的值 也可以是上方代码中增加保存商品然后获取商品主键ID';

        $attrs_name = [];
        $ext_attr_name = "";
        $sku_key = "";

        foreach ($sku as $attr_key_id => $attr_val) {
            if (empty($attr_val)) {
                dd('请检查规格属性是否输入完整');
            }

            $attrs_name[$map_attr_key[$attr_key_id]['value']] = $attr_val['value'];

            $ext_attr_name .= $attr_val['value'] . " ";
            $sku_key .= "{$attr_val['attr_val_id']},";
        }

        $attrs_name = json_encode($attrs_name, JSON_UNESCAPED_UNICODE);
        $sku_key = substr($sku_key, 0, -1); // 前端传来的设置的指针
        $sku = $json_sku[$sku_key];

        if (empty($sku) || empty($sku['sku_stock']) || empty($sku['sku_price'])) {
            dd('规格缺少库存以及价格');
        }

        $temp_data['attrs_name'] = $attrs_name;
        $temp_data['name'] = $ext_attr_name;
        $temp_data['stock'] = $sku['sku_stock'];

        $temp_data['price'] = $sku['sku_price'];    // 这里可以转换一下金额 例如数据库存入单位为分的
        $lst_product_sku[] = $temp_data;
    }

    dd($lst_product_sku);
}

最后得到了这样的数组:

sku_3.png

有了最终的这些数据, 就可以保存到数据库了.

本文地址 : www.bubaijun.com/page.php?id=182

版权声明 : 未经允许禁止转载!

评论:我要评论

挑食的小草莓 沙发

规格要修改的时候,如何传值进去遍历出保存过的规格

评论时间:2020-07-18 14:03:05

回复

挑食的小草莓 板凳

规格要修改的时候,如何传值进去遍历出保存过的规格

评论时间:2020-07-18 14:03:06

回复

不败君

@挑食的小草莓 那就要看你后端和前端的实际配合了, 假设后端数据如最后一张图那样存储, 后端利用 attrs_name 的索引就能拿到规格名称(颜色 尺码)再拿到对应的值返回给前端, 前端再进行处理就好了。

评论时间:2020-07-18 14:38:47

回复

挑食的小草莓

@不败君 我现在是前后端都自己弄,自己的做的一个商场前端只是略懂,具体就是我返回数据前端不知道怎么去处理

评论时间:2020-07-18 15:02:38

回复

不败君

@挑食的小草莓 那就有点难了, 这个主要还是前端事情多。

评论时间:2020-07-18 17:56:41

回复

挑食的小草莓

@不败君 我看了一个下午的js基本弄好了,感谢您的代码

评论时间:2020-07-18 18:03:28

回复

不败君

@挑食的小草莓 😀能解决问题就好。

评论时间:2020-07-18 18:04:37

回复

发布评论:
Copyright © 不败君 粤ICP备18102917号-1

不败君

首 页 作 品 微 语