# 利用element组件库实现sku规格配置及展示
话不多说,先上一波示例。
# 涉及技术点
图片裁剪点击跳转图片裁剪
el-select下拉选项新增删除按钮 点击前往
阻止回车事件弹开的弹框会默认确认的问题点击前往
合并表格列相同项点击前往
动态表单校验问题点击前往
排列组合使用笛卡尔积点击前往
# el-select下拉选项新增删除按钮
代码大致如下
<el-select v-model="select" clearable ref="skuName" filterable placeholder="请选择规格名">
<div class="select-list">
<el-option label="选项1" :value="1"></el-option>
<i class="del-sku-name el-icon-delete"></i>
</div>
<div class="select-list">
<el-option label="选项2" :value="2"></el-option>
<i class="del-sku-name el-icon-delete"></i>
</div>
<div class="select-list">
<el-option label="选项3" :value="3"></el-option>
<i class="del-sku-name el-icon-delete"></i>
</div>
</el-select>
首先设置好样式,由于选项列表是在body之外的,所以不能使用scoped 且为了避免影响全局样式,最好设置特殊的唯一的类名
<style lang="less">
.el-scrollbar {
.select-list {
width: 100%;
line-height: 34px;
position: relative;
box-sizing: border-box;
.del-sku-name {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
}
}
}
</style>
由于使用filterable,可对选项进行过滤,此时过滤只会过滤 option ,但是 上面的 i 不会被过滤。会出现下图的情况
全部没有查找到的时候
查找到部分数据,未隐藏查不到的数据的垃圾桶
因此最终less代码如下
<style lang="less">
.el-scrollbar {
&.is-empty {
display: none;
}
.select-list {
width: 100%;
line-height: 34px;
position: relative;
box-sizing: border-box;
.del-sku-name {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
}
.el-select-dropdown__item[style="display: none;"] + .del-sku-name{
display: none;
}
}
}
</style>
# 阻止回车事件弹开的弹框会默认确认的问题
当遇到回车键触发的 显示 elment的 confirm弹框时,可能会直接触发确认,导致用户无法点击取消该事件。解决方法如下
this.$confirm(`新增规格值【${specValue}】, 是否继续?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
beforeClose(action, instance, done) {
if (action == 'confirm') {
instance.$refs['confirm'].$el.onclick = a()
function a(e) {
e = e || window.event;
if (e.detail != 0) {
done()
}
}
} else {
done()
}
}
}).then(() => {
this.$message.info("您点击了确定")
}).catch(() => {
this.$message.info("您您点击了取消")
})
# 合并表格列相同项
此功能展示大致如下
将列相同的值进行合并
关键属性就是 el-table 的span-method
属性
由于此处只对规格值进行合并,因此只需计算规格值需要合并的数据即可。
<el-table :data="tableData":span-method="objectSpan"></el-table>
// 合并行数
objectSpan({ row, column, rowIndex, columnIndex }) {
//如果需要上传规格图片 则从 第二列开始 否则从第一列开始 然后进行规格列的长度 未经处理的默认为 rowspan: 1, colspan: 1
if ((columnIndex < (this.uploadSkuImg ? (Object.keys(this.skuTableHeader).length + 1) : Object.keys(this.skuTableHeader).length)) && (columnIndex >= (this.uploadSkuImg ? 1 : 0))) {
const _row = this.uploadSkuImg ? this.spanArr[rowIndex][columnIndex - 1] : this.spanArr[rowIndex][columnIndex]
const _col = _row > 0 ? 1 : 0
return {
rowspan: _row,
colspan: _col
}
}
},
//计算位置的方法 来得到 spanArr 数列
getSpanArr(val) {
const data = JSON.parse(JSON.stringify(val))
let posObj = {}
//遍历表格行数
for (var i = 0; i < data.length; i++) {
let j = 0
//存储要合并的数据
this.spanArr[i] = []
//遍历规格列数
Object.keys(this.skuTableHeader).forEach(key => {
//如果是第一行 先定义个 占用 1格
if (i === 0) {
this.spanArr[i][j] = 1
posObj[key] = 0 //标记位置为 第一行
} else {
// 判断当前元素与上一个元素是否相同 如果相同,此行的span设置为占用0格 上一行的占用格数 +1
if (data[i][key] === data[i - 1][key]) {
this.spanArr[posObj[key]][j] += 1 //将第一个出现该值的 格子+1个占用格
this.spanArr[i][j] = 0
} else {
this.spanArr[i][j] = 1
posObj[key] = i //没出现一样的,位置重新标记
}
}
j++
})
}
}
先计算位置 getSpanArr,比如想要得到如下所示的结果
想要得到上面的显示效果,需要获得数据 spanArr 为 如下数据
spanArr: [
[2, 1],
[0, 1],
[2, 1],
[0, 1]
]
最外层的数据的长度表示有几行
第一行的 [2, 1] 表示 第一行的第一格子占用 2个高度的格子,第一行的第二个格子占用1个高度的格子。
第二行的 [0, 1] 表示 第二行的第一格子占用 0个高度的格子(因为已经被第一行给占了,如果非0会导致表格错位),第二行的第二个格子占用1个高度的格子。
以此类推
上述方法就是为了获取 spanArr的方法,然后又由于涉及是否上传规格图片,所以得判断。
# 正常对所有表格列都进行相同项合并的话代码如下
// 合并行数
objectSpan({ row, column, rowIndex, columnIndex }) {
const _row = this.spanArr[rowIndex][columnIndex]
const _col = _row > 0 ? 1 : 0
return {
rowspan: _row,
colspan: _col
}
},
//计算位置的方法 来得到 spanArr 数列 val 为表格数据
getSpanArr(val) {
const data = JSON.parse(JSON.stringify(val))
let posObj = {}
//遍历表格行数
for (var i = 0; i < data.length; i++) {
let j = 0
//存储要合并的数据
this.spanArr[i] = []
//遍历规格列数
Object.keys(data[i]).forEach(key => {
//如果是第一行 先定义个 占用 1格
if (i === 0) {
this.spanArr[i][j] = 1
posObj[key] = 0 //标记位置为 第一行
} else {
// 判断当前元素与上一个元素是否相同 如果相同,此行的span设置为占用0格 上一行的占用格数 +1
if (data[i][key] === data[i - 1][key]) {
this.spanArr[posObj[key]][j] += 1 //将第一个出现该值的 格子+1个占用格
this.spanArr[i][j] = 0
} else {
this.spanArr[i][j] = 1
posObj[key] = i //没出现一样的,位置重新标记
}
}
j++
})
}
}
# 动态表单校验问题
此处配置了最简单的规格设置,只设置了一个价格配置项。价格就涉及到校验填写内容是否正确,就需要做到校验,按以往form添加rule对此处已不再适用,研究许久,可自己判断并设置校验错误信息。
<el-form ref="form" :model="skuTableForm" size="small">
<el-table :data="tableData" :key="tablehead.length" :span-method="objectSpan">
<el-table-column width="160px" align="center" v-if="showUploadImg" :prop="format.imgField" :label="format.imgName">
<template slot-scope="scope">
<el-form-item :error="scope.row['error' + key]">
<el-input v-model="scope.row[key]" clearable @blur="tableValueBlur(scope.row, key)" @input="tableValueChange(scope.row, key, item)" :placeholder="'请输入' + item"></el-input>
</el-form-item>
</template>
</el-table-column>
</el-table>
</el-form>
/**
* @description: 触发表单校验
* @param {*} row
* @param {*} key
* @return {*}
* @author: syx
*/
tableValueChange(row, key, item) {
if (!row[key]) {
row['error' + key] = item + '不能为空'
return
}
if (!(/^\d+(\.\d+)?$/).test(row[key])) {
row['error' + key] = '请输入正确格式的' + item
return
}
row['error' + key] = ''
},
/**
* @description: 失去焦点事件
* @param {*} row
* @param {*} key
* @return {*}
* @author: syx
*/
tableValueBlur(row, key) {
if (!row['error' + key]) {
row[key] = Number(row[key]).toFixed(2)
}
},
这样就会当输入框输入的时候触发校验并会及时显示错误信息。
WARNING
缺点:validate方法不能触发校验,需要手工触发校验。
# 排列组合使用笛卡尔积
填写规格名,就会排列组合成多种多样的商品,于是需要用到计算笛卡尔积的方法,具体方法如下
/**
* @description: 计算笛卡尔积的 方法
* @param {*} arr
* @return {*}
* @author: syx
*/
descartes(arr) {
let array = arr.filter(item => {
return item.length > 0
})
if (!array || !array.length) {
return []
}
if (array.length === 1) {
return array[0].map(item => {
return [item]
})
}
let result = [].reduce.call(array, function(col, set) {
var res = [];
col.forEach(function(c) {
set.forEach(function(s) {
var t = [].concat(Array.isArray(c) ? c : [c]);
t.push(s);
res.push(t);
})
});
return res;
})
return result
},
通过笛卡尔列表制作表格数据
/**
* @description: 获取笛卡尔列表数据
* @param {*}
* @return {*}
* @author: syx
*/
descartesArray() {
let descartesTable = []
for (let i = 0; i < this.skuList.length; i++) {
const item = this.skuList[i]
const obj = {}
for (let j = 0; j < item.length; j++) {
const childItem = item[j]
//解决id存数字的话 对象会被重新排序
obj["syx" + childItem[this.format.specNameId]] = childItem[this.format.specValue]
}
descartesTable.push(obj)
}
return descartesTable
}
# 总结
一个规格列表,耗掉半管血。difficult,关注微信公众号【爆米花小布】,不用再造轮子啦。如有bug,也可通过公众号进行反馈。 更加严谨的规格代码实现可阅读此篇文章 el-table上下行单元格合并,实现好看的SKU表格 (opens new window)