基于element ui的商品sku多规格设置,完整版,mysql+vue+php+附件

商品多规格多属性sku的设置一直都是商城系统里比较好玩的部分,因为涉及多个相关表的操作。原理:

设置属性》设置属性值》多属性组合。

下面是我找到的一个vue功能,人家分享出来也是不完整的,我自己进行了调整和封装成了组件,附上php代码。


开启多规格:

image.png

数据库效果:

image.png

不开启多规格时:

image.png

要优化的地方:一键操作、规格图片上传。


php接收数据(我是基于thinkphp的,自己可以用file_get_ contents("php//input")功能获取):

public function set()
{
    //获取sku数据
    $sku_list = input('sku_list', '{}');
    $sku = @json_decode($sku_list, true);
    $goods_id = input('goods_id', 0, 'intval');//todo 默认写了一个,可以自己进行商品的增改操作后获取



    if ($sku) {
        Db::startTrans();
        try {
            //基础的字段
            $baseFields = ['cost_price', 'market_price', 'price', 'store_count', 'src'];
            foreach ($sku as $k => $specItems) {
                if (empty($specItems['price'])) {
                    throw new \Exception('价格填写不完整');
                }
                if (empty($specItems['cost_price'])) {
                    throw new \Exception('成本价不完整');
                }
                if (empty($specItems['store_count'])) {
                    throw new \Exception('库存不完整');
                }

                //基础结构
                $spec_goods_price = [
                    'goods_id' => $goods_id,//商品spu的id
                    'key' => '',//属性值的id拼接
                    'key_name' => '',//规格名称组合
                    'cost_price' => $specItems['cost_price'],//成本价
                    'market_price' => $specItems['market_price'],//市场价
                    'price' => $specItems['price'],//销售价格
                    'store_count' => $specItems['store_count'],//库存
                    'src' => $specItems['src'],//封面图片
                ];
                $spec_item_ids = [];
                //单规格
                if (count($specItems) <= count($baseFields)) {
                    $specItems['规格'] = '默认';
                    $specItems['src'] = '';
                }

                foreach ($specItems as $key => $value) {

                    //处理属性和属性值
                    if (!in_array($key, $baseFields)) {
                        //属性表
                        $spec_id = Db::name('sku_spec')->where(['name' => $key])->value('id');
                        if (!$spec_id) {
                            $spec_id = Db::name('sku_spec')->insertGetId([
                                'name' => $key,
                                'addtime' => time()
                            ]);
                        }
                        //属性值表
                        $spec_item_id = Db::name('sku_spec_value')->where(['spec_id' => $spec_id, 'item' => $value])->value('id');
                        if (!$spec_item_id) {
                            $spec_item_id = Db::name('sku_spec_value')->insertGetId(['spec_id' => $spec_id, 'item' => $value]);
                        }
                        $spec_item_ids[] = $spec_item_id;//key
                    }
                }

                //从小到大排序,方便下单计算
                sort($spec_item_ids);
                $key_names = [];
                foreach ($spec_item_ids as $id) {
                    $spec_item = Db::name('sku_spec_value')->where(['id' => $id])->find();
                    $specName = Db::name('sku_spec')->where(['id' => $spec_item['spec_id']])->value('name');
                    $key_names[] = $specName . ':' . $spec_item['item'];
                }
                if (count($spec_item_ids) > 1) {
                    $glueKey = '_';
                    $glueName = ' ';
                } else {
                    $glueKey = '';
                    $glueName = '';
                }
                //拼接字key
                $spec_goods_price_key = join($glueKey, $spec_item_ids);
                //拼接规格名称
                $spec_goods_price_key_name = join($glueName, $key_names);
                $spec_goods_price['key'] = $spec_goods_price_key;
                $spec_goods_price['key_name'] = $spec_goods_price_key_name;
                //最终的规格属性库存表(sku表)
                if ($hasId = Db::name('sku_spec_price')->where(['goods_id' => $goods_id, 'key' => $spec_goods_price_key])->value('id')) {
                    Db::name('sku_spec_price')->where(['id' => $hasId])->save($spec_goods_price);
                } else {
                    $res = Db::name('sku_spec_price')->insertGetId($spec_goods_price);
                    if (!$res) {
                        throw new Exception('添加规格信息失败');
                    }
                }

            }


        } catch (Exception $e) {
            Db::rollback();

            data_return($e->getMessage(), 500);
        }
        Db::commit();

        data_return('success', 200, [
            'param' => $sku,
        ]);

    }

    data_return('success', 200);
}


前端vue在商品详情页调用组件:

js里面监听sku提交:


引入组件:


import goodsSku from "@/xxxxx/GoodsSku.vue";


components: {goodsSku},
methods: {
  //监听提交的数据
  onSubmitSku(postData) {
    console.log('skuInfo',postData)
  },
},

在vue上面写组件:

<template>
  <div class="sku">
    <goods-sku :goods_id="goods_id" @submitSku="onSubmitSku"></goods-sku>
  </div>
</template>


组件代码(style样式太长了,没放,整个组件在附件):

<template>
  <div class="sku_set">

    <el-form :model="ruleForm" :inline="false"   ref="ruleForm" label-width="100px" class="demo-ruleForm">

      <el-form-item label="是否多规格:" >
        <el-switch v-model="ruleForm.specType"></el-switch>
      </el-form-item>

      <!-- 规格 -->
      <el-divider></el-divider>
      <div>
        <div class="goods-spec" v-if="ruleForm.specType">
          <h3>商品规格</h3>
          <el-link type="primary" @click="addPrivateSpec" class="goods-spec-add">添加规格</el-link>
        </div>
        <div v-if="ruleForm.specType" class="goods-container" v-for="(attr, index) in items" :key="index">
          <div class="goods-content">
            <div class="goods-content-box">
              <div class="goods-content-left">
                <el-form label-width="80px" style="width:400px">
                  <el-form-item label="规格名">
                    <el-input v-model="attr.value" placeholder="请输入规格名"></el-input>
                  </el-form-item>
                  <el-form-item label="规格值">
                    <el-tag v-for="tag in attr.detail" :key="tag" closable :disable-transitions="false" @close="handleClose(tag, attr)">
                      {{ tag }}
                    </el-tag>
                    <el-input
                        class="input-new-tag"
                        v-if="attr.inputVisible"
                        v-model="attr.inputValue"
                        :ref="`saveTagInput${index}`"
                        size="small"
                        @keyup.enter.native="handleInputConfirm(attr.inputValue, attr)"
                        @blur="handleInputConfirm(attr.inputValue, attr)"
                    >
                    </el-input>
                    <el-button v-else class="button-new-tag" size="small" @click="showInput(attr, index)">+ 添加</el-button>
                  </el-form-item>
                </el-form>
              </div>
              <div class="goods-content-right">
                <el-link type="danger" @click="delPrivateSpec(index)">删除规格</el-link>
              </div>
            </div>
          </div>
        </div>
        <p style="margin:24px 0 10px 0">价格 / 库存</p>
        <el-table ref="multipleTable" :data="tableColumnList.tableBodyList" stripe tooltip-effect="dark" style="width: 100%;margin-top:1%;">
          <el-table-column :label="item.propName" :property="item.prop" v-for="item in tableColumnList.tableHeaderList" :key="item.prop" align="center">
            <template v-slot="scope">
              <span>{{ scope.row[scope.column.property] }}</span>
            </template>
          </el-table-column>
          <el-table-column label="价格(元)">
            <template v-slot="scope">
              <el-input v-model.number="scope.row.price"></el-input>
            </template>
          </el-table-column>
          <el-table-column label="成本价(元)">
            <template v-slot="scope">
              <el-input v-model.number="scope.row.cost_price"></el-input>
            </template>
          </el-table-column>
          <el-table-column label="原价(元)">
            <template v-slot="scope">
              <el-input v-model.number="scope.row.market_price"></el-input>
            </template>
          </el-table-column>
          <el-table-column label="库存">
            <template v-slot="scope">
              <el-input v-model.number="scope.row.store_count"></el-input>
            </template>
          </el-table-column>

          <el-table-column label="图片">
            <template v-slot="scope">
              <!--上传图片的组件哦-->
              <el-image v-if="scope.row.src" :src="scope.row.src"></el-image>
              <el-tag  v-else class="el-icon el-icon-plus">上传</el-tag>
            </template>
          </el-table-column>

        </el-table>
      </div>
      <el-button type="primary" @click="submit()" style="width: 20%;margin-left: 30%;margin-top: 20px;">保存</el-button>
    </el-form>
  </div>
</template>


<script>
import {sku_get,sku_set} from "@/request/api/sku";
export default {
  data() {
    return {
      ruleForm: {
        specType: false,
      },
      inputVisible: false,
      inputValue: '',
      tableColumnList: {
        tableHeaderList: [],
        tableBodyList: [
          {}
        ]
      },
      items: [    //sku属性
        {
          value: '', //规格名
          detail: [], //规格值数组
          inputVisible: false,
          inputValue: ''
        }
      ],
      id: "",   //无则添加有则编辑
      timer: '',  //刷新子组件
    }
  },
  mounted() {

  },
  props: {
    //商品 ID
    goods_id: {
      type: Number,
      default: 0,
    },

  },
  computed: {
    // 计算规格
    calculateAttribute() {
      // 初始化
      let obj = {}
      this.items.forEach((item) => {
        // 判断有没有输入规格名
        if (item.value) {
          //规格名:规格值     //'颜色':'尺寸'
          obj[item.value] = item.detail
        }
      })
      return obj
    }
  },
  watch: {
    // 监听规格数据
    calculateAttribute(newVal) {
      if (!this.ruleForm.specType) {
        return;
      }
      //this.attribute(newVal);
      let cloneNewVal = JSON.parse(JSON.stringify(newVal))
      let attrName = [] //规格名数组
      let attrValue = [] //规格值数组
      for (let key in cloneNewVal) {
        attrName.push(key)
        attrValue.push(cloneNewVal[key])
      }

      // 表格内容数据(笛卡尔积算法)
      let finalArr = this.cartesianProductOf(...attrValue)

      let tableObj = {
        tableBodyList: [],
        tableHeaderList: []
      }
      // 表格内容
      tableObj.tableBodyList = finalArr.map((item) => {

        let obj = {
          cost_price: 0,        //成本价
          market_price: 0,     //原价
          price: 0,       //现价
          store_count: 0,     //库存
          src: '',     //商品规格图片路径
        }
        for (let i = 0; i < item.length; i++) {
          obj[attrName[i]] = item[i]
        }
        return obj
      })
      this.tableColumnList.tableBodyList = tableObj.tableBodyList //表格内容数据

      // 表头
      let skuTableArr = Object.keys(newVal)
      tableObj.tableHeaderList = skuTableArr.map((item) => {
        return {
          prop: item,
          propName: item
        }
      })
      this.tableColumnList.tableHeaderList = tableObj.tableHeaderList // 表头

      console.log('new',newVal)

    },
    'ruleForm.specType': {
      deep: true,
      handler: function (newV, oldV) {
        console.log('xxx',newV)
        if (!newV) {
          this.tableColumnList = this.$options.data().tableColumnList;
          this.items = this.$options.data().items;
        }
      }
    }
  },
  methods: {

    submit() {
      console.log('header',this.tableColumnList.tableHeaderList)
      console.log('body',this.tableColumnList.tableBodyList)
      let body = this.tableColumnList.tableBodyList;

      //测试提交到后台set方法
      let postData = {
        goods_id: this.goods_id,
        sku_list:body
      };
      //同步到父组件
      this.$emit('submitSku',postData);
      //测试提交
      postData.sku_list = JSON.stringify(body)
      sku_set(postData).then((res)=>{
        console.log('set',res)
      }).catch(()=>{
        console.log('set err')
      })

    },

    removeEmptyChildren(node) {
      const that = this;
      node.forEach(item => {
        if ('children' in item && item.children.length === 0) {
          delete item.children
        } else if ('children' in item && item.children.length) {
          that.removeEmptyChildren(item.children)
        }
      })
      return node;
    },

    /*****规格*****/
    // 添加规格
    addPrivateSpec(index) {
      this.items.push({
        value: '',
        detail: [],
        inputVisible: false,
        inputValue: ''
      })
    },
    delPrivateSpec(index) {
      this.items.splice(index, 1)
    },
    handleInputConfirm(val, attr) {
      if (val) {
        attr.detail.push(val)
      }
      attr.inputVisible = false
      attr.inputValue = ''
    },
    handleClose(tag, item) {
      item.detail.splice(item.detail.indexOf(tag), 1)
    },
    showInput(attr, index) {
      attr.inputVisible = true
      this.$nextTick((_) => {
        this.$refs[`saveTagInput${index}`][0].$refs.input.focus()
      })
    },
    // 笛卡尔积算法
    cartesianProductOf(...args) {
      return args.reduce(
          (total, current) => {
            let ret = []
            total.forEach((a) => {
              current.forEach((b) => {
                ret.push(a.concat([b]))
              })
            })
            return ret
          },
          [[]]
      )
    }

  },
  components: {},
  onload: () => {
    console.log('多规格组件init')
  }
};
</script>


数据库结构:

CREATE TABLE `tp_sku_spec_price` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `goods_id` int(11) DEFAULT '0' COMMENT '商品id',
  `key` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '规格键名',
  `key_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '规格键名中文',
  `price` decimal(10,2) DEFAULT NULL COMMENT '价格',
  `store_count` int(11) unsigned DEFAULT '10' COMMENT '库存数量',
  `src` varchar(512) DEFAULT NULL COMMENT '商品规格图片路径',
  `cost_price` decimal(10,2) DEFAULT NULL COMMENT '底价',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key` (`key`) USING BTREE,
  KEY `goods_id` (`goods_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 COMMENT='商品sku规格库存表';

CREATE TABLE `tp_sku_spec` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '规格表',
  `name` varchar(55) DEFAULT NULL COMMENT '规格名称',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

CREATE TABLE `tp_sku_spec_value` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '规格项id',
  `spec_id` int(11) DEFAULT NULL COMMENT '规格id',
  `item` varchar(54) DEFAULT NULL COMMENT '规格项',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;

GoodsSku.zip


评论/留言