最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • vue sku后台配置

    正文概述 掘金(人美声甜)   2021-01-05   693

    最终效果gif

    本想录制一个视频的,但是好像不支持

    文章的最后会给出一个完整的例子

    vue sku后台配置

    相关的数据结构

    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:["大","中"] }
    ]
    
    • 需要得到 黑-大, 黑-中, 白-大, 白-中 四项,即表格内的四行数据

    • 如果加了一个 重量1kg2kg , 那应该得到 黑-大-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>
    
    

    起源地下载网 » vue sku后台配置

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元