2020-05-18 18:06:00
围观(11912)
开发一些商城会遇上这样的场景:

例如一件衣服, 可能有 红色 / 黄色.
红色和黄色衣服的尺码又有 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;
});如果不需要异步提交, 把这段代码删除即可.
提交测试效果:

可以看到能打印出提交的数据, 接着就是将数据处理一下, 直接上代码(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);
}最后得到了这样的数组:

有了最终的这些数据, 就可以保存到数据库了.
本文地址 : bubaijun.com/page.php?id=182
版权声明 : 未经允许禁止转载!
上一篇文章: 使用PHP Laravel 框架开发淘客站点
下一篇文章: Laravel使用EasyWechat开发微信支付
@挑食的小草莓 那就要看你后端和前端的实际配合了, 假设后端数据如最后一张图那样存储, 后端利用 attrs_name 的索引就能拿到规格名称(颜色 尺码)再拿到对应的值返回给前端, 前端再进行处理就好了。
评论时间:2020-07-18 14:38:47