商品sku设计

商品sku设计

sku是什么?

是一种表示库存进出计量的单位,例如,盒,件。如今的sku被广泛引申为某款产品的统一编号的简称,每一个产品都有它独一无二的sku号。sku号包括其商品的品牌、型号、等级、配置、单位、用途、产地、价格、生产日期、保质期等等一系列属性,每一件商品的这些属性与其他任何商品都不一样,这样的商品称为一个单品,其sku号是独一无二的。

从货品角度来讲,sku是指单独的一个商品,商品所有的属性都已经被确定。这可以这么说,只要商品的属性不一样,那么两者的sku就不一样。商品是属性包括:品牌、型号、等级、配置、成分、花色、用途等等因素。只要商品的某一个属性不同,就可以定义为不同的sku。

从业务管理的角度来讲,sku也包含商品包装的信息。计量单位,包装单位不一样,其适应的管理不一样,我们也可以分成不同的sku。打个比方,袜子,我们都是以“双”为单位的,也就是一双袜子是一个sku,袜子参数都一样,只是在打包过程中,一包里面12双袜子,那么12双袜子与1双袜子又是不同的sku。

多规格sku设计,一直挺让我头疼的,当然,也可能是一直没有做的原因,这次做的时候,也想了好多,我的做法实现逻辑不是很好,只是实现了功能,小伙伴们可以做一个参考,有更好的办法也可以分享一下。

下面开始说我的实现步骤,先贴数据表
DROP TABLE IF EXISTS `dishes`;
CREATE TABLE `dishes`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `shop_id` int(11) NOT NULL COMMENT '店铺id',
  `class_id` varchar(255) NOT NULL COMMENT '顶级分类id',
  `sub_classid` varchar(30) NOT NULL COMMENT '商品分类id',
  `keywork` varchar(255) NOT NULL COMMENT '关键字,以|隔开',
  `shop_menu_id` varchar(255) NULL COMMENT '菜谱id',
  `name` varchar(200) NOT NULL COMMENT '商品名',
  `main_picture` bigint(15) NOT NULL COMMENT '商品主图',
  `picture` varchar(2000) NOT NULL COMMENT '商品图片列表',
  `explain` varchar(255) NULL COMMENT '说明',
  `isspec` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否多规格,1/0',
  `original_price` int(10) NOT NULL COMMENT '原价价格,100 = 1元',
  `price` int(10) NOT NULL COMMENT '现价,100 = 1元',
  `status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '上下架,1/0',
  `cost_price` int(10) NOT NULL DEFAULT 0 COMMENT '成本价,计算利润',
  `sort` int(10) NOT NULL COMMENT '排序(仅在后台生效)',
  `sales` int(10) NOT NULL DEFAULT 0 COMMENT '销量',
  `grade` decimal(2, 1) NOT NULL DEFAULT 5.0 COMMENT '评分',
  `weight` decimal(10.2) NOT NULL DEFAULT 1000.00 COMMENT '权重,新菜品权重1000,一星期后下降至500,为起始值,5星好评+1,4星不增不减,3星-0.5,1星-2,2星-1,自动评价不增不减',
  `created_at` int(10) NOT NULL COMMENT '创建时间',
  `updated_at` int(10) NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`),
  INDEX `shop_id`(`shop_id`) USING BTREE COMMENT '店铺id',
  INDEX `shop_menu_id`(`shop_menu_id`) USING BTREE COMMENT '菜谱id'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT='菜品表';

DROP TABLE IF EXISTS `dishes_attr`;
CREATE TABLE `dishes_attr`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `shop_id` int(10) NOT NULL COMMENT '店铺id',
  `dishes_id` int(10) NOT NULL COMMENT '所属商品id',
  `sort` int(10) NULL COMMENT '排序',
  `name` varchar(200) NOT NULL COMMENT '属性名',
  `sku` varchar(100) NOT NULL COMMENT 'sku值',
  PRIMARY KEY (`id`),
  INDEX `shop_id`(`shop_id`) USING BTREE COMMENT '店铺id',
  INDEX `dishes_id`(`dishes_id`) USING BTREE COMMENT '商品id'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT='菜品属性表';

DROP TABLE IF EXISTS `dishes_spec`;
CREATE TABLE `dishes_spec`  (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `dishes_id` int(10) NOT NULL COMMENT '商品id',
  `attr_path` varchar(100) NOT NULL COMMENT '属性序列化数据',
  `attr_name` varchar(300) NOT NULL COMMENT '属性名称,以-分开',
  `picture` bigint(15) NULL COMMENT '规格图片',
  `original_price` int(10) NOT NULL DEFAULT 0 COMMENT '原价',
  `price` int(10) NOT NULL COMMENT '现价',
  `cost_price` int(10) NOT NULL COMMENT '成本价',
  PRIMARY KEY (`id`),
  INDEX `dishes_id`(`dishes_id`) USING BTREE COMMENT '商品id',
  INDEX `attr_path`(`attr_path`) USING BTREE COMMENT '属性路径'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT='商品规格表';
添加商品时提交的数据格式

前端代码没有实现,我在项目里只做后端,所以这边只贴后端代码,这边只说前端需要提交的数据格式

{
    "class_id":1,        // 分类id
    "sub_classid":2,    // 下级分类id
    "shop_menu_id":4,    // 菜谱id
    "name":"商品名",        // 商品名
    "main_picture":"https://www.baidu.com",        // 商品主图
    "picture":"https://www.baidu.com,https://www.baidu.com,https://www.baidu.com",        // 商品其他图片 
    "explain":"本商品是一份,不是 两份,这是个说明",        // 商品详情,不建议太长
    "isspec":1,        // 是否多规格:1/0
    "original_price":1000,        // 原价,注意,如果是10.00元,则为1000
    "price":500,    // 现价,注意,如果是10.00元,则为1000
    "status":1,        // 是否上架:1/0
    "cost_price":1500,    // 成本价,用于统计例如
    "sort":15,            // 排序,仅在后台和商品详情使用
    "attr":[    
        {
            "name":"颜色",    // 属性1
            "sku":"红色"        // 属性值1.1
        },
        {
            "name":"颜色",    // 属性1
            "sku":"蓝色"        // 属性值1.2
        },
        {
            "name":"尺码",
            "sku":"l"
        },
        {
            "name":"尺码",
            "sku":"xl"
        },
        {
            "name":"款式",
            "sku":2018
        },
        {
            "name":"款式",
            "sku":2019
        }
    ],
    "spec":[
        {
            "attr_name":"红色+l+2018",        // 属性值拼接数据,注意顺序,不可以错误,按照表单顺序拼接
            "picture":"http://www.baidu.com",        // 图片,可不传
            "original_price":"1500",        // 原价
            "price":"1000",                    // 现价
            "cost_price":"500"                // 成本价
        },
        {
            "attr_name":"红色+l+2019",
            "picture":"http://www.baidu.com",
            "original_price":"1500",
            "price":"1000",
            "cost_price":"500"
        },
        {
            "attr_name":"红色+xl+2018",
            "picture":"http://www.baidu.com",
            "original_price":"1500",
            "price":"1000",
            "cost_price":"500"
        },
        {
            "attr_name":"红色+xl+2019",
            "picture":"http://www.baidu.com",
            "original_price":"1500",
            "price":"1000",
            "cost_price":"500"
        },
        {
            "attr_name":"蓝色+l+2018",
            "picture":"http://www.baidu.com",
            "original_price":"1500",
            "price":"1000",
            "cost_price":"500"
        },
        {
            "attr_name":"蓝色+l+2019",
            "picture":"http://www.baidu.com",
            "original_price":"1500",
            "price":"1000",
            "cost_price":"500"
        },
        {
            "attr_name":"蓝色+xl+2018",
            "picture":"http://www.baidu.com",
            "original_price":"1500",
            "price":"1000",
            "cost_price":"500"
        },
        {
            "attr_name":"蓝色+xl+2019",
            "picture":"http://www.baidu.com",
            "original_price":"1500",
            "price":"1000",
            "cost_price":"500"
        }
    ]
}
商品控制器
    /**
     * 创建商品
     * @param  Request $request [description]
     * @param  Dishes  $dishes  [description]
     * @return [type]           [description]
     */
    public function createDishes( Request $request , Dishes $dishes ) {

        // 获取参数
        $param = $request->input();

        // 启用事务
        DB::beginTransaction();

        $data = $dishes->store( $request );

        if ( !$data ) {
            return $this->fail( 30017 );
        }

        // 是否是多规格
        if ( !$param['isspec'] ) {

            // 提交非多规格商品
            DB::commit();
            return $this->success( $data );

        } else {

            // 组织添加规格属性
            $attrData = $this->attr( $param , $data );

            if ( $attrData != true ) {
                DB::rollBack();
                return $this->fail( $attrData );
            } else {
                DB::commit();
                return $this->success( '成功' );
            }
        }
    }

    /**
     * 组织商品属性
     * @param  [array] $param [添加商品的表单数据]
     * @param  [int] $id [商品id]
     * @return [array]        [属性添加成功后从mysql里的取值]
     */
    public function attr( $param , $id ) {

          if ( !is_array( $param ) || !$param ) {
              return 30018;
          }

          $attrData = [];

          $i = 1;

          foreach ($param['attr'] as $key => $value) {

              $attrData[] = [
                  'shop_id' => $this->shop_id,
                  'dishes_id' => $id,
                  'sort' => $i,
                  'name' => $value['name'],
                  'sku' => $value['sku']
              ];

              $i++;
          }
        // 启用事务
        DB::beginTransaction();

          // 属性组
          $attr = new Dishes_Attr();

          $data = $attr->store( $attrData );

          if ( !$data ) {
              return 30020;
          } else {

              // 查询刚刚添加的属性
              $result = $attr->selAttrs( $id );

              if ( !$result ) {
                  return 30020;
              }

            $paramData = [
                // 数据库中取出的属性参数
                'attr' => $result,
                // 表单数据中规格
                'spec' => $param['spec'],
                // 商品id
                'dishes_id' => $id
            ];
              return $this->spec( $paramData );
          }
    }

    /**
     * 添加规格
     * @param  [type] $data [description]
     * @return [type]       [description]
     */
    public function spec( $data ) {

        // 处理属性从数据库中取出的数据
        $attr = $this->attrGroup( $data['attr'] );
        // 拼接规格参数
        $result = $this->CartesianProduct( $attr );
        // 预定义规格数组
        $specData = [];

        // 处理规格数组
        foreach ( $result as $value ) {

            foreach ($data['spec'] as $val) {

                if ( $val['attr_name'] == $value['attr_name'] ) {
                    // 组织规格数据
                    $specData[] = [
                        'dishes_id' => $data['dishes_id'],
                        'attr_name' => $value['attr_name'],
                        'attr_path' => $value['attr_path'],
                        'picture' => $val['picture'],
                        'original_price' => intval ( $val['original_price'] ? $val['original_price'] : 0 ),
                        'price' => intval( $val['price'] ),
                        'cost_price' => intval ( $val['cost_price'] ? $val['cost_price'] : 0 )
                    ];

                    // 跳出循环
                    break;
                }

            }

        }

        if ( count( $specData ) != count( $result ) ) {
            return 30019;
        }

        // 添加规格
        $spec = new Dishes_Spec();

        $insertSpec = $spec->store( $specData );

        if ( $insertSpec ) {
            return true;
        } else {
            return 30020;
        }

    }

    /**
     * 处理属性从数据库中取出的数据
     * @param  [type] $data [description]
     * @return [type]       [description]
     */
    public function attrGroup( $data ) {

        $attr = [];
        $attr_spec = [];

        // 处理数组
        $i = 0;
        foreach ($data as $val){

            if ( isset($attr[$val['name']]) ) {
                continue;
            }

            // 定义数组
            if ( !isset($attr[$val['name']]) ) {
                $attr[$val['name']] = 1;
            }

            foreach( $data as $value ) {

                if ( $attr[$val['name']] == 1 ) {
                    $i++;
                }

                $attr[$val['name']] = 2;

                if ( $val['name'] == $value['name'] ) {

                    $attr_spec[$i][] = [
                            'id' => $value['id'],
                            'shop_id' => $value['shop_id'],
                            'dishes_id' => $value['dishes_id'],
                            'sort' => $value['sort'],
                            'sku' => $value['sku']
                        ];

                } else {

                    continue;
                }

            }
        }

        return array_values( $attr_spec );
    }

    /**
     * 卡迪尔算法,拼接规格参数
     * @param [type] $goods [description]
     */
    public function CartesianProduct($goods)
    {

        $returnData = [];

        for ($i=0; $i < count($goods)-1 ; $i++) { 

            // 初始化
            if($i == 0)
            {
                foreach( $goods[$i] as $value1 ) {
                    $returnData[] =  [
                        'attr_name' => $value1['sku'],
                        'attr_path' => $value1['id']
                    ];
                }

            }

            $tempArray = []; // 临时数组

            foreach( $returnData as $value ) {

                foreach( $goods[$i+1] as $val ) {

                    $tempArray[] = [
                        'attr_name' => $value['attr_name'].'+'.$val['sku'],
                        'attr_path' => $value['attr_path'].','.$val['id']
                    ];

                }

            }

            //重新赋值
            $returnData = $tempArray;

        }

        return $returnData;
    }
本作品采用《CC 协议》,转载必须注明作者和本文链接
马江川@13753441707
本帖由系统于 2年前 自动加精
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 8

sku麻烦的地方从来不是入库,而是根据当前选择的数据动态计算其他属性搭配的sku是否可选(库存是否足够)

3年前 评论
mjc123456 (楼主) 3年前

你听过shopify没,我觉得shopify的产品,变种(SKU),图片之间的关系应该是最适合做小型电商的

3年前 评论
mjc123456 (楼主) 3年前
arukas (作者) 3年前

我抄的有赞的结构...

3年前 评论
mjc123456 (楼主) 3年前

从来只做代码的搬运工 :joy:

3年前 评论
mjc123456 (楼主) 3年前

楼主是想做一个餐馆的自助点餐收款的小程序吗?赚钱不这个?

3年前 评论
mjc123456 (楼主) 3年前

shopify的商品结构限制了只有三个属性,这样比较好定位属性和sku。。我现在也是你这样设计的。但是呢,产品傻逼的需求。。例如现在我的一个属性颜色,第二个是大小,,然后要用户可以控制 属性的排列未知。。而且当属性特别多的时候例如1000个,不怎么好定位sku的位置,例如有现在要删除所有颜色为红色的sku。就特别难受,最近老哥有新的想法没有。。

2年前 评论

卡迪尔算法,什么鬼 :joy:

2年前 评论

@爱吃藤藤菜 这个再做一下验证,自己验证一下规则,最后组合算一下数量,整理一下数组的数据和后端数据的数量和内容是否一致就可以了。

2年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!