console
window.onload = () => {
let attr_name_value = new Map([
['颜色', ['黑', '白', '红']],
['尺寸', ['大', '中', '小']],
['重量', ['500g', '1kg', '5kg']],
['材料', ['水晶', '宝石', '玻璃']]
])
let base_column = ['销售价格', '市场价格', '库存']
let first_column_rule = []
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: '',
form: {
sku_arr: [],
table_data: [],
},
rules: {
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()
},
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
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) {
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)
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
}
}
},
async generateTableColumn() {
this.table_column = this.form.sku_arr.map(x => x.attr).concat(base_column)
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
} 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]
}
},
mergeTableData(arr) {
return arr.map(item => ({ ...item, '销售价格': '', '市场价格': '', '库存': '', '图片': '' }))
},
traverseSku() {
let ready_map = this.generateBaseData(this.form.sku_arr)
this.form.table_data = this.mergeTableData(ready_map)
},
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) {
let getTop = dom => {
let top = dom.offsetTop
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 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">
label[for*='table_data'] {
visibility: hidden;
margin-top: -40px;
}
</style>
<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 }">
图片组件
</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>