最终效果gif
本想录制一个视频的,但是好像不支持
文章的最后会给出一个完整的例子
相关的数据结构
input输入框部分
点击 颜色
, 尺寸
, 重量
会生成 sku_arr
数据结构
sku_arr: [
{ attr:"颜色", valueList:["黑","白"] },
{ attr:"尺寸", valueList:["大","中"] }
]
table表格头的数据
table_column: ["颜色","图片","尺寸","销售价格","市场价格","库存"]
table表格内的数据
这里直接以属性的名字作为对象的 key
table_data: [
{ "颜色": "黑", "尺寸": "大", "销售价格": "", "市场价格": "", "库存": "", "图片": "" },
{ "颜色": "黑", "尺寸": "中", "销售价格": "", "市场价格": "", "库存": "", "图片": "" },
{ "颜色": "白", "尺寸": "大", "销售价格": "", "市场价格": "", "库存": "", "图片": "" },
{ "颜色": "白", "尺寸": "中", "销售价格": "", "市场价格": "", "库存": "", "图片": "" },
]
需要做的事情
点击添加规格时生成 sku_arr
, 就是input输入框那里
这个比较简单, 直接向数组 push
即可
this.form.sku_arr.push({ attr: attr_name, valueList: ['黑', '白'] })
同时生成 table表头
表格头中除了属性,还有图片,价格,库存。
而且图片一直在第二列,行合并的规则与第一列的规则相同(下文会讲到)
用数组的 map
函数取到属性的名字,再进行数组合并
// 生成表头的方法
async generateTableColumn() {
this.table_column = this.form.sku_arr.map(x => x.attr).concat( ['销售价格', '市场价格', '库存'])
/*
不写 `$nextTick`会有bug, 没想明白为啥, 大概是vue懒得更新dom吧
bug复现方式: 删除`$nextTick`后,点击颜色,再点击尺寸,再删除颜色,观察el-table
*/
await this.$nextTick()
if (this.form.sku_arr.length != 0) this.table_column.splice(1, 0, '图片')
},
同时生成 表格内的数据
这里涉及到计算 笛卡尔积, (我感觉就是把所有排列的情况组合出来)
例如上面的数据
sku_arr: [
{ attr:"颜色", valueList:["黑","白"] },
{ attr:"尺寸", valueList:["大","中"] }
]
-
需要得到
黑-大
,黑-中
,白-大
,白-中
四项,即表格内的四行数据 -
如果加了一个
重量
为1kg
和2kg
, 那应该得到黑-大-1kg
,黑-大-2kg
,黑-中-1kg
,黑-中-5kg
,白-大-1kg
,白-大-5kg
,白-中-1kg
,白-中-2kg
八项,即表格内的八行数据
笛卡尔积的计算(核心)
/* 重新实现笛卡尔积 传入的数组 '为空', '长度为1', '长度大于1' 三种情况 分别处理
入参数组格式: [
{ attr:"颜色", valueList:["黑","白"] },
{ attr:"尺寸", valueList:["大","中"] }
]
返回的数组格式:[
{"颜色":"黑","尺寸":"大"},
{"颜色":"黑","尺寸":"中"},
{"颜色":"白","尺寸":"大"},
{"颜色":"白","尺寸":"中"}
]
*/
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
}
})
}
if (arr.length >= 1) {
return arr.reduce((accumulator, spec_item) => {
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].constructor === Object) {
temp_data = {
...acc_value_list[i],
[spec_item.attr]: item_value_list[j]
}
// 否则如果是字符串
} else {
temp_data[accumulator.attr] = acc_value_list[i]
temp_data[spec_item.attr] = item_value_list[j]
}
result.push(temp_data)
}
}
return result
})
}
}
最终的表格数据
用上边的笛卡尔积返回的数组进行map
循环返回新数组即可
this.table_data = generateBaseData(arr).map(item => ({ ...item, '销售价格': '', '市场价格': '', '库存': '', '图片': '' }))
生成新数据的问题已经说完了,渲染页面就比较简单了,对 el-table-column
进行循环即可,注意一下key
的使用 (当然还要判断列的属性对应不同的页面展示)
<el-table :span-method="spanMethod" border>
<template v-for="item_column in table_column">
<el-table-column :key="item_column" :prop="item_column" :label="item_column" />
</template>
</el-table>
删除属性的操作
删除属性是比较简单的
只要删除 sku_arr
的数据后,在重新生成表头
和表格数据
即可
删除属性值的操作
是不是也是删除 sku_arr
的数据后,在重新生成表头
和表格数据
即可呢?
当然是可以的
// 删除属性值 四个参数:'一级数组索引', '二级数组索引', '属性名字', '属性值'
deleteAttrVal(idx, kdx, attr_name, attr_val) {
this.form.sku_arr[idx].valueList.splice(kdx, 1)
// 删除table行
let data_length = this.form.table_data.length
for (let i = 0; i < data_length; i++) {
if (this.form.table_data[i][attr_name] == attr_val) {
this.form.table_data.splice(i, 1)
i = i - 1
data_length = data_length - 1
}
}
}
方法的后两个参数是, 当前属性名字
和 当前属性值
, 利用数组的splice
方法直接删除表格数据
编辑属性值的操作
同样,编辑属性值也不应该让用户数据丢失
newAttrValueBlur(curr_attr, newVal) {
if (newVal === old_attr_value) return
// 这里根据规格生成的笛卡尔积计算出table中需要改变的行索引 ( 包含新增和修改 )
let cartesian_arr = this.generateBaseData(this.form.sku_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.form.sku_arr.map(x => x.valueList.length)
let new_table_length = length_arr.reduce((acc, curr_item) => acc * curr_item) // 新的表格数据长度 求乘积
let old_table_length = this.form.table_data.length // 旧的表格数据长度
// 如果是修改
if (new_table_length === old_table_length) {
this.form.table_data.forEach((item, index) => {
if (change_idx_arr.includes(index)) this.form.table_data[index][curr_attr] = newVal
})
return
}
// 如果是新增
if (new_table_length > old_table_length) {
// 得到当前属性的当前值和其他规格的 sku_arr, 构造新的表格数据
let other_sku_arr = this.form.sku_arr.map(item => {
if (item.attr !== curr_attr) return item
else return { attr: item.attr, valueList: [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.form.table_data.splice(item_idx, 0, new_table_data[index])
})
}
}
当属性值的输入框失去焦点的时候,执行保存操作
区分一下 修改旧的
和 创建新的
- 失去焦点后得到新的
sku_arr
,重新计算笛卡尔积 - 遍历新的笛卡尔积,根据失去焦点时的
属性
和属性值
找到需要被改变的表格数据的索引
,即change_idx_arr
(新增或者修改的索引都在这里存着) - 用
新的数组长度
和已有的表格数据数组长度比较
, 相等则为修改,新的大于旧的则为新增
修改逻辑
比较简单,单纯的修改数组元素即可
// 如果是修改
if (new_table_length === old_table_length) {
this.form.table_data.forEach((item, index) => {
if (change_idx_arr.includes(index)) this.form.table_data[index][curr_attr] = newVal
})
return
}
新增逻辑
新增的时候要用新添加的属性值
,和其他属性下的所有值生成新的笛卡尔积,
再遍历 change_idx_arr
, 使用 splice
将数据插入到指定位置
// 如果是新增
if (new_table_length > old_table_length) {
// 得到当前属性的当前值和其他规格的 sku_arr, 构造新的表格数据
let other_sku_arr = this.form.sku_arr.map(item => {
if (item.attr !== curr_attr) return item
else return { attr: item.attr, valueList: [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.form.table_data.splice(item_idx, 0, new_table_data[index])
})
}
至此,所有的表格数据相关的操作均已完成,接下来就是 el-table
的行合并操作
感谢同事 王思涵 同学的帮助,最终实现了无限合并
<el-table :data="table_data" :span-method="spanMethod" border>
// 合并行
spanMethod({ row, column, rowIndex, columnIndex }) {
if (columnIndex == 0) {
let key_0 = column.label
let first_idx = this.form.table_data.findIndex(x => x[key_0] == row[key_0])
const calcSameLength = () => this.form.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
// 第二列的图片与第一列主规格使用相同合并规则 ( 恰好el-table的合并方法是横着走的 )
} else if (columnIndex == 1) {
return first_column_rule
// 其他列
} else {
// 表格数据的每一项,
const callBacks = (table_item, start_idx = 0) => {
if (columnIndex < start_idx) return true
let curr_key = this.table_column[start_idx]
return table_item[curr_key] === row[curr_key] && callBacks(table_item, ++start_idx)
}
let first_idx = this.form.table_data.findIndex(x => callBacks(x))
const calcSameLength = () => this.form.table_data.filter(x => callBacks(x)).length
return rowIndex == first_idx ? [calcSameLength(), 1] : [0, 0]
}
}
这里有两个注意点
-
由于业务需要,第二列的图片的合并规则要与第一列一样,恰好这个方法在表格中是横向一个单元格一个单元格走的,所以在处理第一列的时候,保存一下第一列的规则,当进行到第二列的时候使用第一列规则,
-
接下来就是无限合并
有个大问题就是后边的合并依赖之前合并,直到第一列 (单凭借文字好像描述的不够清楚)
通过递归函数 callBacks
从第一列 start_idx = 0
开始,一直往后找,递归终止的条件是 columnIndex < start_idx
,
在执行到某个单元格的时候 columnIndex
是固定的, start_idx
进行加加,直到大于 columnIndex
,代表这个单元格计算完毕,得到合并规则
自此,所有功能介绍完毕
经历了一次又一次的修改,最终总算是让自己稍微满意了一些,
相比之前的版本,解决了用户在做某些极限操作的时候,导致页面产生的bug(主要是修改属性值的时候产生的)
- 在修改
属性
或者删除属性
的时候,没有保存用户输入的数据, 因为我觉得修改了属性后,那么原来的数据就成为了垃圾数据(不知这么说能不能解释的通)
不足之处
- 一些计算方式可能不是太好,可能会影响性能
- 属性太多的时候,生成表格的速度会下降
- 从点击属性到完整的表格数据在控制台打印出来,是秒打印的
- 那么就是 vue 或者 组件 的渲染问题了, 暂时还没什么办法
html 示例 可直接运行
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>sku</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<style type="text/css">
/* 保存时如果没有验证通过, 要触发浏览器滚动, 这里是设置table表格中的 销售价格 最终渲染的label样式 */
label[for*='table_data'] {
visibility: hidden;
margin-top: -40px;
}
</style>
</head>
<body>
<div id="app">
<el-form ref="form" :rules="rules" :model="form">
<el-form-item label="添加规格" prop="sku_arr">
<div style="display: flex;">
<div>
<el-button v-for="(item, idx) in default_attr" :key="idx" :disabled="attrBtnDisabled" @click="clickDefaultAttr(item)">{{item}}</el-button>
</div>
<el-popover placement="top" width="240" v-model="add_popover_bool" @after-enter="$refs.addValueInput.focus()">
<div style="display: flex; grid-gap: 10px;">
<el-input ref="addValueInput" v-model.trim="add_value" @keyup.enter.native="confirm()" />
<el-button type="primary" @click="confirm()">确定</el-button>
</div>
<el-button slot="reference" size="small" type="primary" :disabled="attrBtnDisabled" style="margin-left: 40px;">自定义</el-button>
</el-popover>
<el-button type="primary" @click="onSubmit()" style="margin-left: 100px;">提交</el-button>
</div>
</el-form-item>
<!-- 规格列表 和 表格 -->
<section style="margin: 0 0 20px 50px;">
<!-- 展示已经选择的 -->
<div v-for="(item, index) in form.sku_arr" :key="index" style="margin-top: 10px;">
<!-- 属性 -->
<div>
<el-input v-model.trim="item.attr" placeholder="属性" style="width: 120px;" @focus="attrFocus(item.attr)" @blur="attrBlur(item.attr)"></el-input>
<el-button type="danger" size="mini" icon="el-icon-delete" circle @click="deleteAttr(index)"></el-button>
</div>
<!-- 属性值 -->
<div style="display: flex; margin-top: 10px;">
<div v-for="(ktem, kndex) in item.valueList" :key="kndex" style="margin-right: 20px;">
<el-input size="small" ref="attrValueInput" v-model.trim="item.valueList[kndex]" placeholder="值" style="width: 100px;" @focus="attrValueFocus(item.valueList[kndex])" @blur="newAttrValueBlur(item.attr, item.valueList[kndex])"></el-input>
<el-button v-if="item.valueList.length > 1" type="danger" size="mini" icon="el-icon-delete" circle @click="deleteSmall(index, kndex, item.attr, item.valueList[kndex])" />
</div>
<el-button type="primary" size="mini" :disabled="item.valueList.includes('')" icon="el-icon-plus" @click="addAttributeValue(index)">添加值</el-button>
</div>
</div>
<el-table :data="form.table_data" :span-method="spanMethod" style="margin-top: 20px;" border>
<template v-for="item_column in table_column">
<el-table-column v-if="item_column == '图片'" :key="item_column" min-width="150" width="170" align="center" :resizable="false" label="图片">
<template slot-scope="{ row, $index }">
<!-- <el-form-item :prop="`table_data.${ $index }.图片`" :rules="rules.sku_img" label-width="0"> -->
图片组件
<!-- </el-form-item> -->
</template>
</el-table-column>
<!-- 销售价格 使用表单验证 和 自定义表头 -->
<el-table-column v-else-if="item_column == '销售价格'" :key="item_column" align="center" :resizable="false">
<!-- 自定义表头 -->
<template slot="header">
<div><span style="color: #ff5353;">*</span>销售价格</div>
</template>
<template slot-scope="{ row, $index }">
<el-form-item :prop="`table_data.${$index}.销售价格`" :rules="rules.sku_sale_price" label-width="0" label=" ">
<el-input v-model="row[item_column]" :placeholder="item_column" />
</el-form-item>
</template>
</el-table-column>
<!-- 市场价格 -->
<el-table-column v-else-if="item_column == '市场价格'" :key="item_column" align="center" :resizable="false" :label="item_column">
<template slot-scope="{ row }">
<div style="height: 62px;">
<el-input v-model="row[item_column]" :placeholder="item_column" />
</div>
</template>
</el-table-column>
<!-- 库存 -->
<el-table-column v-else-if="item_column == '库存'" :key="item_column" align="center" :resizable="false" :label="item_column">
<template slot-scope="{ row, $index }">
<div style="height: 62px;">
<el-input v-model="row[item_column]" :placeholder="item_column" />
</div>
</template>
</el-table-column>
<!-- 其他属性列 -->
<el-table-column v-else align="center" :resizable="false" :key="item_column" :prop="item_column" :label="item_column" />
</template>
</el-table>
</section>
</el-form>
</div>
<script>
window.onload = () => {
let attr_name_value = new Map([ // Map 数据结构, 根据属性名获取对应属性值 返回数组
['颜色', ['黑', '白', '红']],
['尺寸', ['大', '中', '小']],
['重量', ['500g', '1kg', '5kg']]
])
let base_column = ['销售价格', '市场价格', '库存'] // 基本的列
let first_column_rule = [] // 第一列与第二列使用相同的合并规则 (不能存在data中)
let old_attr = '' // 每次当属性获得焦点时都会获取输入框内的值,保存于此
let old_attr_value = '' // 每次当属性值获得焦点时都会获取输入框内的值,保存于此
new Vue({
el: '#app',
computed: {
// 已添加的属性(字符串数组)
selectedAttr() {
return this.form.sku_arr.map(x => x.attr)
},
// 是否可以添加属性 最多两个属性
attrBtnDisabled() {
return false
return this.form.sku_arr.length >= 2
}
},
data: {
default_attr: ['颜色', '尺寸', '重量'], // 默认规格
table_column: base_column, // 表格列
add_popover_bool: false, // 添加属性的小弹窗
add_value: '', // 添加属性的
// 上边的数据是录入sku相关
// 表单
form: {
sku_arr: [],
table_data: [], // 表格中的数据
},
// 验证规则
rules: {
// sku 相关验证
sku_arr: {
validator: (rule, value, callback) => {
if (value.length === 0) return callback(new Error('请添加规格'))
else return callback()
},
trigger: 'blur'
},
sku_img: [
{ required: true, message: '图片不能为空', trigger: 'blur' },
{ type: 'string', message: '请等待图片上传完毕', trigger: 'blur' },
],
sku_sale_price: { required: true, message: '价格不能为空', trigger: 'blur' }
}
},
methods: {
// 点击默认的规格按钮
clickDefaultAttr(attr_name) {
if (this.selectedAttr.includes(attr_name)) return
this.form.sku_arr.push({ attr: attr_name, valueList: [...attr_name_value.get(attr_name)] }) //解决引用类型导致的问题
this.generateTableColumn()
this.traverseSku() // 处理SKU, 生成表格
console.log(this.form.sku_arr)
},
// 点击自定义里的确定 添加新的规格
confirm() {
if (!this.add_value) return
this.form.sku_arr.push({ attr: this.add_value, valueList: [''] })
this.generateTableColumn()
this.traverseSku()
this.add_popover_bool = false
this.add_value = ''
},
// 属性获得焦点时 得到旧的值 存一下
attrFocus(oldVal) {
old_attr = oldVal
},
// 属性失去焦点时
attrBlur(newVal) {
console.log('attrBlur')
// 如果 '新值等于旧值' 或者 '空' 什么也不做
if (newVal === old_attr || newVal === '') return
// 生成处理表头数据和表格数据
this.generateTableColumn()
this.traverseSku()
},
// 删除属性
deleteAttr(idx) {
this.form.sku_arr.splice(idx, 1)
// 生成处理表头数据和表格数据
this.generateTableColumn()
this.traverseSku()
},
// 添加属性值
async addAttributeValue(idx) {
this.form.sku_arr[idx].valueList.push('')
// 让新增的输入框获得焦点
await this.$nextTick()
this.$refs.attrValueInput[this.$refs.attrValueInput.length - 1].focus()
},
// 属性值获得焦点时 得到旧的值 在输入框失去焦点的时候, 如果值没有变化, 则什么也不做
attrValueFocus(oldVal) {
old_attr_value = oldVal
},
// 属性值失去焦点时, 操作表格数据 (新版本 可以实现无限个规格)
newAttrValueBlur(curr_attr, newVal) {
if (newVal === old_attr_value) return
// 这里根据规格生成的笛卡尔积计算出table中需要改变的行索引 ( 包含新增和修改 )
let cartesian_arr = this.generateBaseData(this.form.sku_arr)
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.form.sku_arr.map(x => x.valueList.length)
let new_table_length = length_arr.reduce((acc, curr_item) => acc * curr_item) // 新的表格数据长度 求乘积
let old_table_length = this.form.table_data.length // 旧的表格数据长度
// 如果是修改
if (new_table_length === old_table_length) {
this.form.table_data.forEach((item, index) => {
if (change_idx_arr.includes(index)) this.form.table_data[index][curr_attr] = newVal
})
return
}
// 如果是新增
if (new_table_length > old_table_length) {
// 得到当前属性的当前值和其他规格的 sku_arr, 构造新的表格数据
let other_sku_arr = this.form.sku_arr.map(item => {
if (item.attr !== curr_attr) return item
else return { attr: item.attr, valueList: [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.form.table_data.splice(item_idx, 0, new_table_data[index])
})
}
},
// 删除属性值 四个参数:'一级数组索引', '二级索引', '属性名字', '属性值' 后两个参数用来删除行
deleteSmall(idx, kdx, attr_name, attr_val) {
this.form.sku_arr[idx].valueList.splice(kdx, 1)
// 删除table行
let data_length = this.form.table_data.length
for (let i = 0; i < data_length; i++) {
if (this.form.table_data[i][attr_name] == attr_val) {
this.form.table_data.splice(i, 1)
i = i - 1
data_length = data_length - 1
}
}
},
// 根据 `this.form.sku_arr` 生成表格列, `table_column` 用于 el-table-column 的 v-for
async generateTableColumn() {
this.table_column = this.form.sku_arr.map(x => x.attr).concat(base_column)
/*
不写 `$nextTick`会有bug, 没想明白为啥, 大概是vue懒得更新dom吧
bug复现方式: 删除`$nextTick`后,勾选颜色,再勾选尺寸,再取消勾选颜色,观察el-table
*/
await this.$nextTick()
if (this.form.sku_arr.length != 0) this.table_column.splice(1, 0, '图片')
},
// 合并行
spanMethod({ row, column, rowIndex, columnIndex }) {
if (columnIndex == 0) {
let key_0 = column.label
let first_idx = this.form.table_data.findIndex(x => x[key_0] == row[key_0])
const calcSameLength = () => this.form.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
// 第二列的图片与第一列主规格使用相同合并规则 ( 恰好el-table的合并方法是横着走的 )
} else if (columnIndex == 1) {
return first_column_rule
// 其他列
} else {
// 表格数据的每一项,
const callBacks = (table_item, start_idx = 0) => {
if (columnIndex < start_idx) return true
let curr_key = this.table_column[start_idx]
return table_item[curr_key] === row[curr_key] && callBacks(table_item, ++start_idx)
}
let first_idx = this.form.table_data.findIndex(x => callBacks(x))
const calcSameLength = () => this.form.table_data.filter(x => callBacks(x)).length
return rowIndex == first_idx ? [calcSameLength(), 1] : [0, 0]
}
},
// 合并 sku 与 '图片', '销售价格', '库存', '市场价格' , 返回整个表格数据数组
mergeTableData(arr) {
return arr.map(item => ({ ...item, '销售价格': '', '市场价格': '', '库存': '', '图片': '' }))
},
// 遍历 `sku_arr` 生成表格数据
traverseSku() {
let ready_map = this.generateBaseData(this.form.sku_arr)
this.form.table_data = this.mergeTableData(ready_map)
},
// 重新实现笛卡尔积 入参是: this.form.sku_arr 传入的数组 '为空', '长度为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
}
})
}
if (arr.length >= 1) {
return arr.reduce((accumulator, spec_item) => {
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].constructor === Object) {
temp_data = {
...acc_value_list[i],
[spec_item.attr]: item_value_list[j]
}
// 否则如果是字符串
} else {
temp_data[accumulator.attr] = acc_value_list[i]
temp_data[spec_item.attr] = item_value_list[j]
}
result.push(temp_data)
}
}
return result
})
}
},
onSubmit() {
this.$refs.form.validate(async (valid, object) => {
if (!valid) {
// 获取元素距离body顶部的距离
let getTop = dom => {
let top = dom.offsetTop
// while括号里的值是 dom.offsetParent
while (dom = dom.offsetParent) top = top + dom.offsetTop
return top
}
let [first_prop] = Object.keys(object)
let top = getTop(document.querySelector(`label[for='${first_prop}']`))
window.scrollTo({ top: top - 70, behavior: 'smooth' })
return
}
this.btn_loading = true
})
}
}
})
}
</script>
</body>
</html>
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!