后台配置商品规格sku(vue+element)

效果gif:

主要数据结构:

规格项:

goodsSpecs: [
    { attr:"颜色", valueList:[{"title": "红"},{"title": "黄"}]},
    { attr:"尺寸", valueList:[{"title": "大"},{"title": "中"},{"title": "小"}] }
]

 table表格头默认的数据:

tableHeaderList: ['stock', 'withhold_stock', 'market_price', 'product_price', 'cost_price', 'goods_sn', 'product_sn', 'weight', 'volume', 'thumb', ], // 表格列

table表格内的数据:

[
    {
        cost_price: "0.00",
        goods_sn: "",
        id: "SP3",
        market_price: "0.00",
        product_price: "",
        product_sn: "",
        stock: "10",
        thumb: "",
        volume: "0.000",
        weight: "0.00",
        withhold_stock: "",
        大小: "大",
        颜色: "红",
    },
    {
        cost_price: "0.00",
        goods_sn: "",
        id: "SP4",
        market_price: "0.00",
        product_price: "",
        product_sn: "",
        stock: "10",
        thumb: "",
        volume: "0.000",
        weight: "0.00",
        withhold_stock: "",
        大小: "中",
        颜色: "红",
    }
]

 具体实现:

点击添加规格名时生成 goodsSpecs, 就是input输入框那里

这个比较简单, 直接向数组 push 即可,注意要验证规格名是否重复否则对后面表格数据计算有影响

  this.tableColumnList.goodsSpecs.push({ attr: attr_name, valueList: [{title:""}] }) 

注意, 在v-for="(item, index) in arr"循环 input 的时候, v-model要以 arr[index] 的方式赋值, 不能直接用 item 赋值

同时生成表头数据

this.tableHeaderList = this.tableColumnList.goodsSpecs.map(x => x.attr).concat(['stock', 'withhold_stock', 'market_price', 'product_price', 'cost_price', 'goods_sn', 'product_sn', 'weight', 'volume', 'thumb', ])

和表格数据(笛卡尔积)

// 重新实现笛卡尔积  入参是: this.tableColumnList.goodsSpecs 传入的数组 '为空', '长度为1', '长度大于1' 三种情况 分别处理
    generateBaseData(arr) {
      if (arr.length === 0) return []
      if (arr.length === 1) {
        let [item_spec] = arr
        return item_spec.valueList.map(x => {
          return {
            [item_spec.attr]: x.title
          }
        })
      }
      if (arr.length >= 1) {
        return arr.reduce((accumulator, spec_item) => {
          // accumulator判断是之前整合的规格数组还是单个规格项
          let acc_value_list = Array.isArray(accumulator.valueList) ? accumulator.valueList : accumulator
          let item_value_list = spec_item.valueList
          let result = []
          for (let i in acc_value_list) {
            for (let j in item_value_list) {
              let temp_data = {}
              if (!acc_value_list[i].title) {
                // accumulator不是Array的情况
                temp_data = {
                  ...acc_value_list[i],
                  [spec_item.attr]: item_value_list[j].title
                }

                // 否则如果是单个规格项
              } else {
                temp_data[accumulator.attr] = acc_value_list[i].title
                temp_data[spec_item.attr] = item_value_list[j].title
              }
              result.push(temp_data)
            }
          }
          return result
        })
      }
    },
//用上边的笛卡尔积返回的数组进行map循环返回新数组即可
// 合并 goodsSpecs 与 '现价', '库存', '市场价格' , 返回整个表格数据数组
    mergeTableData(arr) {
      return arr.map((item) => {
        this.sp_id++;
        return { ...item, id: 'SP'+ this.sp_id, 'stock': '', 'withhold_stock': '', 'market_price': '0.00', 'product_price': '', 'cost_price': '0.00', 'goods_sn': '', 'product_sn': '', 'weight': '0.00', 'volume': '0.000', 'thumb': '', }
      })
    },
// 遍历 `goodsSpecs` 生成表格数据
    traverseSku() {
      let ready_map = this.generateBaseData(this.tableColumnList.goodsSpecs)
      this.tableColumnList.table_data = this.mergeTableData(ready_map)
      this.specCounts = this.tableColumnList.table_data.length;
    },

删除属性的操作

删除属性是比较简单的 只要删除 sku_arr 的数据后,在重新生成表头表格数据即可

删除属性值的操作

需要先找到goodsSpecs中对应规格值删除,  然后通过循环表格数据table_data 把含有对应规格值的哪一项删除

// 删除属性值 四个参数:'一级数组索引', '二级数组索引', '属性名字', '属性值'
removeSpecValue(specItemIndex, valueItemIndex, attr_name, attr_val) {
      this.tableColumnList.goodsSpecs[specItemIndex]["valueList"].splice(valueItemIndex, 1);

      // 删除table行
      let data_length = this.tableColumnList.table_data.length
      for (let i = 0; i < data_length; i++) {
        if (this.tableColumnList.table_data[i][attr_name] == attr_val) {
          this.tableColumnList.table_data.splice(i, 1)
          i = i - 1
          data_length = data_length - 1
        }
      }

      this.specCounts = this.tableColumnList.table_data.length;
    },

编辑属性值的操作

// 属性值失去焦点时, 操作表格数据 
    newAttrValueBlur(curr_attr, newVal, specItemIndex, valueItemIndex) {
      if (curr_attr === '') return
      if (newVal === old_attr_value) return
      let value_num = this.tableColumnList.goodsSpecs[specItemIndex].valueList.filter(item => item.title == newVal)
      if ( value_num.length > 1 ) {
        this.$message({
          message: `规格值不能重复`,
          type: 'warning'
        });
        this.tableColumnList.goodsSpecs[specItemIndex].valueList[valueItemIndex].title = "";
        return
      }

      //  这里根据规格生成的笛卡尔积计算出table中需要改变的行索引 ( 包含新增和修改 )
      let cartesian_arr = this.generateBaseData(this.tableColumnList.goodsSpecs)
      // console.log(cartesian_arr)
      let change_idx_arr = [] // 需要被改变的行的索引
      for (let i in cartesian_arr) {
        if (cartesian_arr[i][curr_attr] === newVal) change_idx_arr.push(Number(i))
      }
      console.log('change_idx_arr', change_idx_arr)

      // 新的表格应该有的长度与现有的表格长度比较, 区分新增还是修改
      let length_arr = this.tableColumnList.goodsSpecs.map(x => x.valueList.length)
      let new_table_length = length_arr.reduce((acc, curr_item) => acc * curr_item) // 新的表格数据长度 求乘积
      let old_table_length = this.tableColumnList.table_data.length // 旧的表格数据长度

      this.specCounts = new_table_length;
      // 如果是修改
      if (new_table_length === old_table_length) {
        this.tableColumnList.table_data.forEach((item, index) => {
          if (change_idx_arr.includes(index)) this.tableColumnList.table_data[index][curr_attr] = newVal
        })
        return
      }
      // 如果是新增
      if (new_table_length > old_table_length) {
        // 得到当前属性的当前值和其他规格的 goodsSpecs, 构造新的表格数据
        let other_sku_arr = this.tableColumnList.goodsSpecs.map(item => {
          if (item.attr !== curr_attr) return item
          else {
            return { id: this.tableColumnList.goodsSpecs[specItemIndex].id, attr: item.attr, valueList: [{
              "id": 'SV'+ this.tableColumnList.goodsSpecs[specItemIndex].valueList.length + '&' + this.tableColumnList.goodsSpecs[specItemIndex].id,
              "specid": this.tableColumnList.goodsSpecs[specItemIndex].id,
              "title": newVal
            }] }
          }
        })
        // 得到新增的表格数据
        let ready_map = this.generateBaseData(other_sku_arr)
        let new_table_data = this.mergeTableData(ready_map)

        change_idx_arr.forEach((item_idx, index) => {
          this.tableColumnList.table_data.splice(item_idx, 0, new_table_data[index])
        })
        
        this.tableColumnList.table_data.reverse().reverse() //更新视图
      }
    },

 el-table 的行合并操作

// 合并单元格
    mergeRowComputed({ row, column, rowIndex, columnIndex }) {
      if (columnIndex == 0) {
        let key_0 = column.label
        let first_idx = this.tableColumnList.table_data.findIndex(x => x[key_0] == row[key_0])
        const calcSameLength = () => this.tableColumnList.table_data.filter(x => x[key_0] == row[key_0]).length
        first_column_rule = rowIndex == first_idx ? [calcSameLength(), 1] : [0, 0]
        return first_column_rule

      }else {
        // 表格数据的每一项, 
        const callBacks = (table_item, start_idx = 0) => {
          if (columnIndex < start_idx) return true
          let curr_key = this.tableHeaderList[start_idx]
          return table_item[curr_key] === row[curr_key] && callBacks(table_item, ++start_idx)
        }
        let first_idx = this.tableColumnList.table_data.findIndex(x => callBacks(x))
        const calcSameLength = () => this.tableColumnList.table_data.filter(x => callBacks(x)).length
        return rowIndex == first_idx ? [calcSameLength(), 1] : [0, 0]
      }
    },

还有后面新增的拖拽排序重新计算表格数据功能

changeOption() {
      let oldData = [];
      // 先保存旧的表格数据
      let old_table_data = JSON.parse(JSON.stringify(this.tableColumnList.table_data));
      let old_data_length = old_table_data.length
      for (let i = 0; i < old_data_length; i++) {
        let title = "";
        this.tableColumnList.goodsSpecs.forEach((item)=> {
          title = title + old_table_data[i][item.attr] + '+'
        })
        oldData.push({
          title: title.slice(0,title.length-1),
          index: i
        })
      }
      // 重新生成处理表头数据和表格数据
      this.generateTableColumn();
      this.traverseSku();

      let data_length = this.tableColumnList.table_data.length
      for (let i = 0; i < data_length; i++) {
        // 循环新的表格数据进行替换
        let title = "";
        this.tableColumnList.goodsSpecs.forEach((item)=> {
          title = title + this.tableColumnList.table_data[i][item.attr] + '+'
        })
        oldData.forEach((item)=> {
          if(item.title == title.slice(0,title.length-1)) {
            // this.tableColumnList.table_data[i] = old_table_data[item.index]  这样赋值会导致输入框有问题  只能像下面这样
            this.tableColumnList.table_data[i].stock = old_table_data[item.index].stock;
            this.tableColumnList.table_data[i].withhold_stock = old_table_data[item.index].withhold_stock;
            this.tableColumnList.table_data[i].market_price = old_table_data[item.index].market_price;
            this.tableColumnList.table_data[i].product_price = old_table_data[item.index].product_price;
            this.tableColumnList.table_data[i].cost_price = old_table_data[item.index].cost_price;
            this.tableColumnList.table_data[i].goods_sn = old_table_data[item.index].goods_sn;
            this.tableColumnList.table_data[i].product_sn = old_table_data[item.index].product_sn;
            this.tableColumnList.table_data[i].weight = old_table_data[item.index].weight;
            this.tableColumnList.table_data[i].volume = old_table_data[item.index].volume;
            this.tableColumnList.table_data[i].thumb = old_table_data[item.index].thumb;
            this.tableColumnList.table_data[i].id = old_table_data[item.index].id;
          }
        })
      }
    },

附上GitHub的demo: GitHub - harrietjia/vue-sku: 后台配置商品规格sku(vue+element)   (觉得有用帮忙给个star)

参考链接:

 https://github.com/zaxlct/vue-sku

电商平台商品 SKU 组合查询算法实现 | 9935

快速上手 | vue-sku-form

  • 5
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值