PHP 商品SKU 发表于 2019-12-23 | 分类于 PHP 商品SKUps: 暂时只有新增, 后续补充编辑 创建相关表1234567891011121314151617181920212223242526272829303132333435363738394041424344CREATE TABLE `goods` (`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '商品ID',`goods_title` VARCHAR(128) NOT NULL COMMENT '商品标题',`generate_time` DATETIME NOT NULL COMMENT '创建时间',PRIMARY KEY (`id`) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品表';CREATE TABLE `goods_sku` (`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'SKU ID',`goods_id` INT(11) NOT NULL COMMENT '商品ID',`title` VARCHAR(255) NOT NULL COMMENT '集成名称',`price` DECIMAL(11,2) NOT NULL COMMENT '价格',`stock` INT(11) NOT NULL COMMENT '库存',PRIMARY KEY (`id`) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品SKU信息表'; *CREATE TABLE `goods_sku_serial` (`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,`goods_id` INT(11) NOT NULL COMMENT '商品ID',`goods_sku_id` INT(11) NOT NULL COMMENT '商品SKU ID',`goods_spec_type_id` INT(11) NOT NULL COMMENT '商品规格类型ID',`goods_spec_type_name` VARCHAR(32) NOT NULL COMMENT '商品规格类型名称',`goods_spec_value_id` INT(11) NOT NULL COMMENT '商品规格类型值ID',`goods_spec_value_name` VARCHAR(32) NOT NULL COMMENT '商品规格类型值名称',PRIMARY KEY (`id`) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='SKU与规格关系表';CREATE TABLE `goods_spec_type` (`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '规格类型ID',`goods_id` INT(11) NOT NULL COMMENT '商品ID',`type_name` VARCHAR(32) NOT NULL COMMENT '规格类型名称',PRIMARY KEY (`id`) USING BTREE,KEY `goods_id` (`goods_id`) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品规格类型表';CREATE TABLE `goods_spec_value` (`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '规格项ID',`goods_id` INT(11) NOT NULL COMMENT '商品ID',`spec_type_id` INT(11) NOT NULL COMMENT '规格类型ID',`value_name` VARCHAR(32) NOT NULL COMMENT '规格项名称',PRIMARY KEY (`id`) USING BTREE,KEY `goods_id` (`goods_id`) USING BTREE,KEY `spec_type_id` (`spec_type_id`) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品规格项表'; 前端代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>SKU</title> <link href="static/plugin/bootstrap/css/bootstrap.min.css" rel="stylesheet"> <link href="static/plugin/font-awesome/css/font-awesome.min.css" rel="stylesheet"> <link href="static/plugin/toastr/toastr.min.css" rel="stylesheet"> <script src="static/jquery-3.3.1.min.js"></script> <script src="static/plugin/bootstrap/js/bootstrap.min.js"></script> <script src="static/plugin/toastr/toastr.min.js"></script> <style> a, a:focus, a:hover {text-decoration: none;} .c-red { color: red; } #goods-spec { /*display: none;*/ } #goods-spec .spec { margin-bottom: 15px; padding: 10px; border: 1px solid #E7EAEC; } #goods-spec .spec .spec-items .spec-item { position: relative; float: left; width: 250px; margin: 0 10px 10px 0; } #goods-spec .spec .spec-items .spec-item:last-child { margin-bottom: -5px; } #goods-spec .spec-btn-table { margin-bottom: 15px; } #goods-spec .spec-btn-table > tbody > tr > td { padding-left: 0; border: 0; } #goods-spec .spec-btn-table .alert-danger { margin-bottom: 0; } #goods-spec .table > thead > tr > th, #goods-spec .table > tbody > tr > td { vertical-align: middle; } #goods-spec .spec-btn-table > tbody > tr:last-child { display: none; } #goods-spec .spec-btn-table a { margin-right: 10px; } #goods-spec .sku .sku-title { padding-bottom: 10px; text-align: center; } #goods-spec .sku .table { table-layout: fixed; } #goods-spec .sku .table input { font-weight: normal; } </style></head><body> <div class="container"> <form class="form-horizontal" action="sku.php" method="post"> <div id="goods-spec"> <div class="alert alert-info"> 1. 更改规格及规格项后请点击下方的【刷新规格项目表】来更新数据<br> 2. 每一种规格代表不同型号,例如颜色为一种规格,尺寸为一种规格,如果设置多规格,手机用户必须每一种规格都选择一个规格项,才能添加购物车或购买 </div> <div class="specs"></div> <table class="table spec-btn-table"> <tbody> <tr> <td> <a href="javascript:;" class="btn btn-primary" id="add-spec" title="添加规格"><i class="fa fa-plus"></i> 添加规格</a> <a href="javascript:;" title="刷新规格项目表" class="btn btn-danger refresh-spec"><i class="fa fa-refresh"></i> 刷新规格项目表</a> </td> </tr> <tr> <td> <div class="alert alert-danger">警告:规格数据有变动,请重新点击上方 [刷新规格项目表] 按钮!</div> </td> </tr> </tbody> </table> <div class="sku"></div> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <script> $(function() { toastr.options = { closeButton: true, progressBar: true, }; $('form').on('submit', function () { $.ajax({ url: 'sku.php', type: 'POST', data: $('form').serialize(), success: function () { } }); return false; }) $(document).on('input propertychange change', '.specs input', function () { window.optionchanged = true; $('.spec-btn-table > tbody > tr:last-child').show(); }); let goodsSku = {}; // 添加规格 $('#add-spec').on('click', function () { let specid = (new Date()).valueOf().toString(16); let html = ` <div class="spec" id="spec-${specid}"> <div class="form-group"> <div class="col-sm-12"> <div class="input-group"> <input type="text" name="spec[${specid}]" value="" class="form-control spec-title" placeholder="规格名称 (比如: 颜色)"/> <div class="input-group-btn"> <a href="javascript:;" specid="${specid}" class="btn btn-info add-spec-item"><i class="fa fa-plus"></i> 添加规格项</a> <a href="javascript:;" specid="${specid}" class="btn btn-danger remove-spec"><i class="fa fa-remove"></i></a> </div> </div> </div> </div> <div class="form-group"> <div class="col-md-12"> <div id="spec-items-${specid}" class="spec-items"></div> </div> </div> </div> `; $('.specs').append(html); }); // 删除规格 $(document).on('click', '.remove-spec', function () { $('#spec-' + $(this).attr('specid')).remove(); }); // 添加规格项 $(document).on('click', '.add-spec-item', function () { let specid = $(this).attr('specid'); let specitemid = (new Date()).valueOf().toString(16); let html = ` <div class="spec-item" data-specitemid="${specitemid}"> <div class="input-group"> <input type="text" class="form-control spec-item-title" name="spec_item[${specid}][${specitemid}]" value="" placeholder="规格项" /> <span class="input-group-addon"><a href="javascript:;" class="remove-spec-item"><i class="fa fa-times"></i></a></span> </div> </div> `; $('#spec-items-' + specid).append(html); let len = $('#spec-items-' + specid + ' .spec-item-title').length -1; $('#spec-items-' + specid + ' .spec-item-title:eq(' + len + ')').focus(); }); // 删除规格项 $(document).on('click', '.remove-spec-item', function () { $(this).closest('.spec-item').remove(); }); // 规格项目批量设置 $(document).on('click', '.sku-all', function() { let cls = '.sku-' + $(this).data('text'); $(cls).val($(cls + '-all').val()); }); // 刷新规格项目表 $('.refresh-spec').on('click', refreshSpec); // 刷新规格项目表 function refreshSpec() { let html = '<table class="table table-bordered table-condensed"><thead><tr class="active">'; let specs = []; let specFlag = true; // 未添加规格 if ($('.spec').length <= 0) { return; } // 获取规格/规格项JSON数据 $('.spec').each(function(i) { let _this = $(this); let specTitle = _this.find('.spec-title').val(); if (!specTitle || specTitle === 'undefined') { specFlag = false; _this.find('.spec-title').focus(); toastr.error('规格名称不能为空!'); return; } let spec = {title: specTitle}; if (_this.find('.spec-item').length <= 0) { specFlag = false; toastr.error('请先添加规格项!'); return; } let items = []; _this.find('.spec-item').each(function() { let __this = $(this); let itemTitle = __this.find('.spec-item-title').val(); if (!itemTitle || itemTitle === 'undefined') { specFlag = false; __this.find('.spec-item-title').focus(); toastr.error('规格项不能为空!'); return; } let item = { id: __this.data('specitemid'), title: itemTitle, }; items.push(item); }); spec.items = items; specs.push(spec); }); if (!specFlag) return; // 计算规格的所有组合,并生成rowSpans let specLen = specs.length; // 规格的所有组合 let skuNums = 1; // 规格表格数组 let specTable = new Array(specLen); // 单元格的rowSpan let rowSpans = new Array(specLen); for (let i = 0; i < specLen; i++) { html += '<th>' + specs[i].title + '</th>'; let itemLen = specs[i].items.length; if (itemLen <= 0) { itemLen = 1; } skuNums *= itemLen; specTable[i] = new Array(skuNums); for (let j = 0; j < skuNums; j++) { specTable[i][j] = new Array(); } itemLen = specs[i].items.length; rowSpans[i] = 1; for (let j = i + 1; j < specLen; j++) { rowSpans[i] *= specs[j].items.length; } } html += ` <th> <div class="sku-title"><span class="c-red">* </span>价格 (元)</div> <div class="input-group"> <input type="text" value="" class="form-control input-sm sku-price-all"> <span class="input-group-addon"> <a href="javascript:;" class="fa fa-angle-double-down sku-all" data-text="price" title="批量设置"></a> </span> </div> </th> <th> <div class="sku-title"><span class="c-red">* </span>数量 (件)</div> <div class="input-group"> <input type="text" value="" class="form-control input-sm sku-stock-all"> <span class="input-group-addon"> <a href="javascript:;" class="fa fa-angle-double-down sku-all" data-text="stock" title="批量设置"></a> </span> </div> </th> `; html += '</tr></thead>'; // 规格表格数据填充 for (let m = 0; m < specLen; m++) { let itemIndex = 0, k = 0; for (let j = 0; j < skuNums; j++) { let rowSpan = rowSpans[m]; let specItem = specs[m].items[itemIndex] || {}; let specItemTitle = !specItem.title || specItem.title === 'undefined' ? '' : specItem.title; let tdData = { id: specItem.id, title: specItemTitle, html: '', }; if (j % rowSpan === 0) { tdData.html = '<td rowspan="' + rowSpan + '">' + specItemTitle + '</td>'; } specTable[m][j] = tdData; k++; if (k === rowSpan) { itemIndex++; if (itemIndex > specs[m].items.length - 1) { itemIndex = 0; } k = 0; } } } // 生成规格项目表 let specTableHtml = ''; for (let i = 0; i < skuNums; i++) { specTableHtml += '<tr>'; let ids = [], titles = []; for (let j = 0; j < specLen; j++) { specTableHtml += specTable[j][i].html; ids.push(specTable[j][i].id); titles.push(specTable[j][i].title); } ids = ids.join('_'); let skuData = { id: goodsSku.hasOwnProperty(ids) ? goodsSku[ids].id : 0, price: goodsSku.hasOwnProperty(ids) ? goodsSku[ids].price : '', stock: goodsSku.hasOwnProperty(ids) ? goodsSku[ids].stock : '', }; specTableHtml += ` <td rowspan="1"> <input type="hidden" name="sku[${ids}][id]" value="${skuData.id}" class="form-control" /> <input type="text" name="sku[${ids}][price]" value="${skuData.price}" class="form-control sku-price" /> </td> <td rowspan="1"><input type="text" name="sku[${ids}][stock]" value="${skuData.stock}" class="form-control sku-stock" /></td> `; specTableHtml += '</tr>'; } html += specTableHtml; html += '</table>'; $('.sku').html(html); // 刷新后重置 window.optionchanged = false; $('.spec-btn-table > tbody > tr:last-child').hide(); } }); </script></body></html> 服务端代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142<?php$postData = $_POST;// 商品规格$specArray = $_POST['spec'];// 商品规格项$specItemArray = $_POST['spec_item'];// 商品SKU$skuArray = $_POST['sku'];// SKU所有组合数量$skuNums = 1;// 商品规格及规格项校验foreach ($specArray as $specId => $specName) { $specItem = $specItemArray[$specId] ?? []; if (!$specItem || empty($specName)) { exit('商品规格参数错误'); } foreach ($specItem as $itemName) { if (empty($itemName)) { exit('商品规格参数错误'); } } $skuNums *= count($specItem);}// 商品SKU一次不能生成太多,容易造成内存溢出if (count($skuArray) > 500) { exit('商品规格项目生成数量过多,请求失败');}// 商品SKUif (count($skuArray) !== $skuNums) { exit('商品规格参数错误');}// TODO 若严格点校验的话,可通过商品规格和规格项生成SKU,再进行验证foreach ($skuArray as &$sku) { $sku = [ 'price' => bcadd($sku['price'], 0, 2), 'stock' => (int)$sku['stock'], ];}unset($sku);try { $config = [ 'host' => 'localhost', 'dbname' => 'test', 'username' => 'root', 'password' => 'root', ]; $prefix = ''; $conn = new PDO("mysql:host={$config['host']};dbname={$config['dbname']}", $config['username'], $config['password']); $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // 开启事务 $conn->beginTransaction(); // 保存商品 $sth = $conn->prepare("INSERT INTO {$prefix}goods(goods_title, generate_time) VALUES(:goodsTitle, :generateTime)"); $sth->execute([ ':goodsTitle' => 'test' . mt_rand(), ':generateTime' => date('Y-m-d H:i:s') ]); // 商品ID $goodsId = $conn->lastInsertId(); // 商品规格项 $specValueArray = []; // 保存商品规格及规格项 foreach ($specArray as $specId => $specName) { // 保存商品规格类型 $sth = $conn->prepare("INSERT INTO {$prefix}goods_spec_type(goods_id, type_name) VALUES(:goodsId, :specName)"); $sth->execute([ ':goodsId' => $goodsId, ':specName' => $specName, ]); // 规格类型ID $specTypeId = $conn->lastInsertId(); foreach ($specItemArray[$specId] as $specValueId => $specValueName) { // 保存商品规格项 $sth = $conn->prepare("INSERT INTO {$prefix}goods_spec_value(goods_id, spec_type_id, value_name) VALUES(:goodsId, :specTypeId, :specValueName)"); $sth->execute([ ':goodsId' => $goodsId, ':specTypeId' => $specTypeId, ':specValueName' => $specValueName, ]); // 规格项ID $specTypeValueId = $conn->lastInsertId(); // SKU信息 $specValueArray[$specValueId] = [ 'specId' => $specTypeId, 'specName' => $specName, 'specValueId' => $specTypeValueId, 'specValueName' => $specValueName, ]; } } // 保存商品SKU foreach ($skuArray as $specValueIds => $sku) { $specValueIdArray = explode('_', $specValueIds); $skuTitle = ''; foreach ($specValueIdArray as $specValueId) { $skuTitle .= $specValueArray[$specValueId]['specName'] . ':' . $specValueArray[$specValueId]['specValueName'] . ' '; } // 保存商品SKU信息 $sth = $conn->prepare("INSERT INTO {$prefix}goods_sku(goods_id, title, price, stock) VALUES(:goodsId, :skuTitle, :skuPrice, :skuStock)"); $sth->execute([ ':goodsId' => $goodsId, ':skuTitle' => $skuTitle, ':skuPrice' => $sku['price'], ':skuStock' => $sku['stock'] ]); // SKU ID $skuId = $conn->lastInsertId(); // 保存商品SKU与规格关系 foreach ($specValueIdArray as $specValueId) { $specValue = $specValueArray[$specValueId]; $sth = $conn->prepare("INSERT INTO {$prefix}goods_sku_serial(goods_id, goods_sku_id, goods_spec_type_id, goods_spec_type_name, goods_spec_value_id, goods_spec_value_name) VALUES(:goodsId, :skuId, :specTypeId, :specTypeName, :specValueId, :specValueName)"); $sth->execute([ ':goodsId' => $goodsId, ':skuId' => $skuId, ':specTypeId' => $specValue['specId'], ':specTypeName' => $specValue['specName'], ':specValueId' => $specValue['specValueId'], ':specValueName' => $specValue['specValueName'], ]); } } // 提交事务 $conn->commit();} catch (Exception $e) { // 事务回滚 isset($conn) && $conn->rollBack(); exit("ERROR: {$e->getMessage()}");} 本文作者:Mr 本文链接: http://sevming.github.io/PHP/goods-sku.html 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!