商城开发 商品属性与规格

时间:2020-05-09 标签: 商城开发

在商城系统开发中,往往一个商品会存在多种销售规格如:颜色、内存等,而这些由销售规格组成的集合我们称之为商品的 SKU,商品的主体为 SPU。本文主要讲解商城项目开发中,商品销售规格及属性的逻辑理论和开发示例。

规格属性定义

我们先来看下传统意义上对商品规格与属性的定义:

商品规格:是指物件的体积、大小,型号是用来识别物品的编号。百度百科商品规格

商品属性:产品属性是指产品本身所固有的性质,是产品在不同领域差异性(不同于其他产品的性质)的集合。百度百科产品属性

运用到商城开发中的规格与属性:

规格是影响价格的,属性是不影响价格只展示的。本文统一命名为销售规格与商品属性。

销售规格:

销售规格在京东商家后台被叫做销售属性,在淘宝商家后台被叫做销售规格。

商品属性:

商品属性在客户端显示为规格与包装(京东) / 规格参数(淘宝天猫)。

商品SKU

1)SKU(或称商品SKU)指的是商品子实体。
2)商品和商品 SKU 是主次关系,一个商品包含若干个商品 SKU 子实体,商品 SKU 从属于商品。
3)SKU 不是编码,每个 SKU 包含一个唯一编码,即SKU Code,用于管理。
4)产品本身也有一个编码,即 Product Code,但不作为直接库存管理使用。有时为了方便管理,会通过产品的 Product Code 作为前缀生成 SKU Code。

SPU与SKU的区别

SPU 为商品的“款”,SKU 为商品的“件”。

SPU = Standard Product Unit (标准化产品单元),SPU是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。
例如:iPhone11 就是 SPU。

SKU = stock keeping unit(库存量单位)
SKU 即库存进出计量的单位,可以是以件、盒、托盘等为单位。
SKU 是物理上不可分割的最小存货单元。在使用时要根据不同业态,不同管理模式来处理。在服装、鞋类商品中使用最多最普遍。
例如:黑色 256G iPhone11 就是 SKU。

销售规格

销售规格其实就是会影响商品价格的元素。如下图为某型号手机在售卖时,用户可以选择不同的商品销售规格:

在了解销售规格之前需要先理解上面提到的商品SKU。

在商品创建编辑时,勾选对应的销售规格,生成 SKU 信息。如下图所示:在颜色区域,勾选了“酒红”和“红咖”,在尺码区域,勾选了 “165/84A”,那么将自动生成 “酒红/165/84A” 和 “红咖/165/84A” 2条 SKU 信息,以此类推。取消勾选后,与此属性值相关的 SKU 将自动去掉。

SKU 是指您的商品编号,对应到您每一款商品的每一款颜色和型号,在您编辑了商品的销售规格后即可生成,客户可以通过 SKU 直接搜索到您店铺的商品。商品 SKU 会显示在您的商品链接和商品介绍中。

每种组合出来的 SKU 可能会有不同的售价、运费与库存剩余情况。所以用户在购买时,不仅需要记录所购买的商品 ID,同时也需要记录购买的该商品的具体规格。

商品属性

商品属性是展示在客户端页面上的规格参数。如下图是某型号手机的商品属性:

不同类型的商品拥有不同的商品属性。如下图是京东商家后台中女装-棉服类目下需要填写的商品属性,但 * 带红色星号的信息为必填项:

规格属性绑定

商品独立管理

即商品独立管理使用规格与属性。
优点:基本没有。
缺点:这种比较不靠谱,因为会导致工作量过大。虽然可以通过“复制”功能来稍稍简化,但依然不会很理想。所以基本不会采用。

商品绑定

即商品独立绑定,需要使用规格与属性时调取。
这里我们习惯把规格、属性放在一个商品模型中去。比如有一个叫做手机的商品模型,下面包含了销售规格:内存(从列表中选择)、颜色(从列表中选择);商品属性:产地(手动输入)、操作系统(手动输入)等。

在商品创建编辑时,去选择要使用的商品模型,调取相对应的销售规格与商品属性。

当然,你也可以分开去创建销售规格与商品属性。比如:手机类销售规格、手机类商品属性,然后在商品创建编辑时,去调取对应规格与属性。

优点:灵活性,易于后期维护。
缺点:适合模型不多的系统。

类目绑定

类目绑定是在商品类目中进行绑定规格与属性,当用户在该类目下创建商品时,直接调出该类目中需要填写的规格与属性。

类目绑定同样可以使用上面商品绑定中提到的商品模型。但是更好的选择是每个类目下都进行规格与属性的设置。这里需要注意,并不是所有的类目都需要去设置规格与属性,我们只需在被允许添加商品的二级、三级类目下设置规格与属性即可。

当然,根据业务需求,你也可更灵活的去设置类目绑定。比如:三级分类共用自己的父级二级分类的规格与属性;创建商品模型,相似的类目共用同一个商品模型等。

优点:灵活性,易于后期维护。容易进行严格的管理,不易出错。
缺点:工作量大,不适合中小型项目。

品牌绑定

品牌绑定与类目绑定相似。同样是在创建编辑商品时,选择品牌,就调取该品牌中需要填写的规格与属性。

优点:灵活性,易于后期维护。容易进行严格的管理,不易出错。
缺点:工作量大,不适合中小型项目。

开发逻辑

1,首先,我们要确定商品以 SPU 展示还是 SKU?

京东由于采取类似于海外电商亚马逊的模式,储存时会给每个 SKU 赋予唯一编码,并且每个 SKU 会以 SKU 形成一个链接,而 SPU 是系统后台对不同属性、规格但又同属同一系的子产品进行统一管理的编码。

淘宝天猫的逻辑,与京东不同,每一款产品都拥有一个独立的 SPU,即商品 ID,SKU 都是附着在 SPU 下。

详情可参考下这篇文章:电商商品应以 SPU 还是 SKU 展示?

本文以 SKU 展示。

2,设置 单SKU 和 多SKU?

大部分商品都存在销售规格,但也有少部分商品并不需要销售规格。那么对于这类商品我们应该如何处理呢?

因为我们是以 SKU 展示商品,所以在操作这种没有销售规格的商品时,需要把它当做一个没有销售规格的 SKU 存储在 product_sku 表中。

参考京东的处理逻辑,不存在销售规格的商品保存为一个 SKU,该 SKU 中除了没有销售规格对应的 ID 外,其余信息均一致。当后期需要对这个商品添加销售规格时,原有的 SKU 自动下架,并根据添加的销售规格生成对应的 SKU。

以下是各种情况下,后端的一些处理逻辑:

在下面演示的 product_sku 表中,default 字段用来区分 SKU 和 商品基础信息保存的SKU。

1)添加商品时,商品存在销售规格:基础信息存储于 product 表;生成的 SKU 存储于 product_sku 表。
2)修改商品时,商品存在销售规格:前端提交 product id、sku id、新增加的销售规格生成的新的 SKU 信息。
3)添加商品时,商品不存在销售规格:基础信息存储于 product 表,并在 product_sku 表中存储一条不存在销售规格 ID 的 SKU 信息。
4)修改商品时,商品存在销售规格,清空销售规格:之前存储的 SKU 下架,并存储一条不存在销售规格 ID 的 SKU 信息。
5)修改商品时,商品不存在销售规格,新增了销售规格:之前的 SKU 下架,并存储新的 SKU 信息。

数据设计

数据库使用 MySQL。数据结构设计仅供参考。

luck_product_specification 商品销售规格表:

CREATE TABLE `luck_product_specification` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `product_model_id` int(11) NOT NULL DEFAULT '0',
  `name` varchar(100) NOT NULL DEFAULT '',
  `sort` mediumint(8) NOT NULL DEFAULT '0',
  `status` tinyint(3) NOT NULL DEFAULT '1' COMMENT '1=Enabled 0=Disabled',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8mb4 COMMENT='商品销售规格';

luck_product_specification_option 商品销售规格选项表:

CREATE TABLE `luck_product_specification_option` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `specification_id` int(11) NOT NULL DEFAULT '0',
  `value` varchar(100) NOT NULL DEFAULT '',
  `status` tinyint(3) NOT NULL DEFAULT '1' COMMENT '1=Enabled 0=Disabled',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=51 DEFAULT CHARSET=utf8mb4 COMMENT='商品销售规格选项';

luck_product 商品基础信息表:

CREATE TABLE `luck_product` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL DEFAULT '' COMMENT '产品名',
  `category_id` int(11) NOT NULL DEFAULT '0',
  `brand_id` int(11) NOT NULL DEFAULT '0',
  `content` text COMMENT '内容',
  `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `status` tinyint(2) NOT NULL DEFAULT '1' COMMENT '0=Disabled 1=Enabled',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=85 DEFAULT CHARSET=utf8mb4 COMMENT='商品基础信息 商品SPU';

luck_product_sku 商品SKU表:

CREATE TABLE `luck_product_sku` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `product_id` int(11) NOT NULL DEFAULT '0',
  `sku` varchar(200) NOT NULL DEFAULT '',
  `image` varchar(255) NOT NULL DEFAULT '' COMMENT 'sku主图',
  `stock` mediumint(8) NOT NULL DEFAULT '0',
  `original_price` decimal(10,2) NOT NULL DEFAULT '0.00',
  `sale_price` decimal(10,2) NOT NULL DEFAULT '0.00',
  `default` tinyint(3) NOT NULL DEFAULT '0' COMMENT '1=spu 0=sku',
  `status` tinyint(3) NOT NULL DEFAULT '1' COMMENT '0=Disabled 1=Enabled',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COMMENT='商品SKU';

luck_product_to_specification 商品关联销售规格表:

CREATE TABLE `luck_product_to_specification` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `product_id` int(11) DEFAULT NULL,
  `sku` int(11) NOT NULL DEFAULT '0',
  `specification_id` int(11) NOT NULL DEFAULT '0',
  `specification_option_id` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=505 DEFAULT CHARSET=utf8mb4 COMMENT='商品关联销售规格';

数据使用

1,商品详情

思路:通过 SKU ID 查询 SPU 基础信息,通过 SPU 调取所有 SKU。

我们先来假设后台设置了一款手机存在以下几个SKU:
1)颜色:黑色,内存:16G
2)颜色:黑色,内存:64G
3)颜色:黑色,内存:128G
4)颜色:白色,内存:16G

对应的数据库存储结果如下,可参考上面的数据设计结构理解:

商品销售规格数据,product_specification 表:

商品销售规格选项数据,product_specification_option 表:

商品基础数据,product 表:

商品 SKU 数据,product_sku 表:

商品关联销售规格数据,product_to_specification 表:

上面的商品数据查询,主要难点在销售规格的数据组装上。所以,这里也提供下关于销售规格数据组装的示例代码:

示例代码使用 ThinkPHP5.1

$sku = 1;

// 当前 sku 信息
$product_sku = Db::name('product_sku')->where('sku', $sku)->where('status', 1)->find();
if (empty($product_sku)) abort(404);

// 当前 sku 下的销售规格
$current_specifications = Db::name('product_to_specification')->where('sku', $sku)->select();
$current_specification_ids = array_column($current_specifications, 'specification_id');
$current_specification_option_ids = array_column($current_specifications, 'specification_option_id');

// 当前商品下的销售规格
$product_to_specifications = Db::name('product_to_specification')->alias('product_to_specification')
                            ->field('product_to_specification.*, product_specification.name as specification_name, product_specification_option.value as specification_option_value, product_sku.stock')
                            ->leftJoin('product_specification', 'product_specification.id = product_to_specification.specification_id')
                            ->leftJoin('product_specification_option', 'product_specification_option.id = product_to_specification.specification_option_id')
                            ->leftJoin('product_sku', 'product_sku.sku = product_to_specification.sku')
                            ->where('product_to_specification.product_id', $product_sku['product_id'])
                            ->select();

// 获取与当前销售规格有关联的sku
// 获取与当前销售规格有关联的商品关联规格ID
// 设置商品关联规格是否有效/可点击
$have_product_skus = [];
$have_product_to_specification_ids = [];
foreach ($product_to_specifications as $key => $value) {
    if (in_array($value['specification_option_id'], $current_specification_option_ids)) {
        $have_product_skus[] = $value['sku'];
        $have_product_to_specification_ids[] = $value['id'];
    }
}
foreach ($product_to_specifications as $key => $value) {
    $product_to_specifications[$key]['valid'] = 0;
    if (in_array($value['sku'], $have_product_skus)) {
        $product_to_specifications[$key]['valid'] = 1;
    }
}

// 组装数据
$array = [];
foreach ($product_to_specifications as $key => $value) {
    $array[$value['specification_id']]['specification_id'] = $value['specification_id'];
    $array[$value['specification_id']]['specification_name'] = $value['specification_name'];
    $array[$value['specification_id']]['options'][$value['specification_option_id']]['specification_option_id'] = $value['specification_option_id'];
    $array[$value['specification_id']]['options'][$value['specification_option_id']]['specification_option_value'] = $value['specification_option_value'];
    if ($value['valid'] == 1) {
        $array[$value['product_specification_id']]['options'][$value['product_specification_option_id']]['valid'] = $value['valid'];
        $array[$value['product_specification_id']]['options'][$value['product_specification_option_id']]['sku'] = $value['sku'];
        $array[$value['product_specification_id']]['options'][$value['product_specification_option_id']]['stock'] = $value['stock'];
    } 
    if ($value['sku'] == $sku) $array[$value['specification_id']]['options'][$value['specification_option_id']]['selected'] = 1;
}

dd($array);

示例代码中的打印结果如下:

array(2) {
  [1] => array(3) {
    ["specification_id"] => int(1)
    ["specification_name"] => string(6) "颜色"
    ["options"] => array(2) {
      [1] => array(5) {
        ["specification_option_id"] => int(1)
        ["specification_option_value"] => string(6) "黑色"
        ["valid"] => int(1)
        ["sku"] => int(3)
        ["stock"] => int(200)
        ["selected"] => int(1)
      }
      [2] => array(4) {
        ["specification_option_id"] => int(2)
        ["specification_option_value"] => string(6) "白色"
      }
    }
  }
  [2] => array(3) {
    ["specification_id"] => int(2)
    ["specification_name"] => string(6) "内存"
    ["options"] => array(3) {
      [4] => array(5) {
        ["specification_option_id"] => int(4)
        ["specification_option_value"] => string(3) "16G"
        ["valid"] => int(1)
        ["sku"] => int(4)
        ["stock"] => int(200)
      }
      [5] => array(5) {
        ["specification_option_id"] => int(5)
        ["specification_option_value"] => string(3) "64G"
        ["valid"] => int(1)
        ["sku"] => int(2)
        ["stock"] => int(200)
        ["selected"] => int(1)
      }
      [6] => array(5) {
        ["specification_option_id"] => int(6)
        ["specification_option_value"] => string(4) "128G"
        ["valid"] => int(1)
        ["sku"] => int(3)
        ["stock"] => int(200)
      }
    }
  }
}

从打印结果来看,可用 selected,valid 字段控制选中与可点击的操作。

2,商品列表

思路:查询 SPU,关联 SPU 下的 SKU。商品链接默认为该 SPU 下的第一个 SKU。

© 2020 Lh1010 - 豫ICP备16115435号-1