效果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)
参考链接: