<template> <div class="goods-specifications"> <div class="spe-con" v-for="(speItem,index) in speData" :key="index"> <div class="el-form-item-h" > <label class="el-form-item-h__label">规格名称:</label> <div class="el-form-item-h__content"> <div> <el-select :ref="'speNameDom' + index " v-model="speItem.spec_name" size="small" style="width: 300px" filterable default-first-option :clearable="true" @change="speNameChange(speItem.spec_name,index)" @visible-change='bv=> visibleChange(bv,"speNameDom",index)' > <el-option v-for="(itemSN,indexSN) in speOptions" :key="itemSN.id" :label="itemSN.name" :value="itemSN.name" > <span style="float: left" class="span-style">{{ itemSN.name }}</span> <div class="flag"> <svg-icon icon="edit" iconClass="template_edit_style" @click="addSpeName(index)"/> </div> </el-option> </el-select> <!--<span>数据填写不完整</span>--> </div> <div class="spe-params-con"> <div style="display: inline-block" v-for="(itemSV,indexSV) in speItem.spec_value" :key="indexSV"> <el-input v-model="itemSV.value" size="small" placeholder="请输入规格参数" class="spe-params-input-item" @blur="paramNameInputBlur(index,indexSV)"> <i v-if="speItem.spec_value.length !== 1" slot="suffix" class="el-input__icon el-icon-delete el-icon-delete-h" @click="deleteSpeParam(index,indexSV)"></i> </el-input> </div> <el-button type="primary" plain class="el-icon-plus" size="mini" @click="addSpeParam(index)">添加规格参数</el-button> </div> </div> </div> <div style="margin-right:auto;" v-if="(speData.length > 1) && speData.length !== 1"> <el-button type="danger" plain icon="el-icon-delete" size="mini" @click="deleteSpe(index)">删除该规格</el-button> </div> </div> <el-button @click="addSpe" class="el-icon-plus" size="mini">添加规格</el-button> <!-- 添加 规格名称 --> <el-dialog title="添加规格名称" :visible.sync="addSpecNameDialog" :destroy-on-close="true" :before-close="cancelSpec" width="500px" center append-to-body > <div> <p class="add-spec-dialog-con"> <span style="margin-right:30px;">输入规格名称:</span> <span> <el-input v-model="specName_sm" placeholder="请输入规格名称" size="small" style="width: 280px;"></el-input> </span> </p> </div> <span slot="footer" class="dialog-footer"> <el-button @click="cancelSpec" size="mini">取 消</el-button> <el-button type="primary" @click="saveSpecName" size="mini">确 定</el-button> </span> </el-dialog> </div> </template> <script> import descartes from "@/utils/dikaerjs.js"; import { UploadImg } from '@/api/module/goods' export default { name: "GoodsSpecifications", props: { specificationsdata: { type: Array, required: true } }, data() { return { speData: [], // 商品规格总数据 specsGroup: { spec_name: '', // 规格名称 spec_value: [] }, speParamName:{ value: '' }, speOptions: [{ id: '1', name: '颜色' }, { id: '2', name: '尺码' }], localSpeOptions: [], // 规格名称,value组成的数组 speNameList: [], addSpecNameDialog: false, specName_sm: '', }; }, created() { }, mounted() { /** 先获取 本地存储的localStorage **/ this.getLocalSpecNameOption(); // 先判断是新增还是修改 //console.log(123,this.specificationsdata) if(this.specificationsdata.length === 0) { this.$set(this.specsGroup.spec_value,this.specsGroup.spec_value.length,this.speParamName); this.speData = []; this.$set(this.speData,this.speData.length,this.specsGroup); }else { this.speData = this.specificationsdata; this.$forceUpdate() } }, watch:{ // 'specificationsdata': { // handler(newValue) { // console.log(133,newValue); // }, // deep: true // } }, methods: { /** 初始化 商品规格数据 */ initInfo() { // debugger this.$set(this.specsGroup.spec_value,this.specsGroup.spec_value.length,this.speParamName); this.speData = []; this.$set(this.speData,this.speData.length,this.specsGroup); }, /** 添加规格*/ addSpe() { let specsGroup = { spec_name: '', // 规格名称 spec_value: [{value: ''}] }; this.$set(this.speData,this.speData.length,specsGroup); }, /** 删除 规格数据 */ deleteSpe(index) { this.$confirm('是否删除当前规格的所有数据?', '警告', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(async() => { this.speData.splice(index,1); this.specCalcResult(); }).catch(() => {}) }, // /** 规格名称,值改变时, 此组件 lable 和 value 绑定同一个值 */ speNameChange(val,index) { if( val ) { // 当有多个值的时候,才校验,规格名称是否重复 if( this.speData.length > 1 ) { let coypSpeData = []; coypSpeData = this.speData.concat(); coypSpeData.splice(index,1); for(let i = 0; i < coypSpeData.length; i++) { if( val === coypSpeData[i].spec_name ) { this.speData[index].spec_name = ''; this.$message({type: 'error',message: '此规格名称已经使用,请重新选取或新增其他规格名称'}); break; } } } } // 重新 计算 规格组合数据 this.specCalcResult(); }, // 添加规格参数 addSpeParam(index) { let valueObj = { value: ''} this.$set(this.speData[index].spec_value,this.speData[index].spec_value.length,valueObj); }, /** 删除 规格参数 */ deleteSpeParam(index,indexSV) { // 先判断,删除前是否有值,如果是空,则没有必要再次计算 if( this.speData[index].spec_value[indexSV].value ) { this.speData[index].spec_value.splice(indexSV,1); this.specCalcResult(); }else { this.speData[index].spec_value.splice(indexSV,1); } }, /** * 规格名称 下拉框 出现/隐藏 触发 * 为element-ui的Select和Cascader添加弹层底部操作按钮 * @param visible * @param refName 设定的ref名称 * @param index 保证操作是当前的下拉框 * @param addname 底部按钮名称, 可不用 */ visibleChange(visible, refName, index) { let _this = this; let realRefName = refName + index; //console.log(realRefName); if(visible) { // 动态绑定 ref的值的时候,下面的dom需要 加一个 [0],非常重要 const speNameDom = _this.$refs[realRefName][0]; let popper = speNameDom.$refs.popper; if( popper.$el ) { popper = popper.$el; } if(!Array.from(popper.children).some(v => v.className === 'el-template-menu__list')) { const el = document.createElement('ul'); el.className = 'el-template-menu__list'; el.style = 'border-top\:1px solid rgb(219 225 241)\; padding\:0\;margin\:0\; color\:rgb(64 158 255)\;font-size\: 14px'; el.innerHTML = '<li class="el-cascader-node text-center" style="height:40px;line-height: 40px">' + '<span class="el-cascader-node__label"><i class="el-icon-plus"></i>添加规格名称</span>' + '</li>'; popper.appendChild(el) el.onclick = () => { // 底部按钮的点击事件 _this.addSpeName(index); // localSttorage 存储 下拉数据 if(speNameDom.toggleDropDownVisible) { speNameDom.toggleDropDownVisible(false); } else { speNameDom.visible = false; } } } } }, getLocalSpecNameOption() { //console.log("先获取:",window.localStorage.getItem('localSpeOptions')); if(!window.localStorage.getItem('localSpeOptions')) { this.speOptions = []; }else { let options = JSON.parse(window.localStorage.getItem('localSpeOptions')); this.speOptions = options; } }, /* 增加 规格名称 */ addSpeName(index) { this.addSpecNameDialog = true; }, // 添加规格名称,对话框的退出 cancelSpec() { this.specName_sm = ''; this.addSpecNameDialog = false; }, // 规格名称,保存 saveSpecName() { let localObj = { id: 0, name: '' }; let localArr = []; let localJSON = ''; // 去 首尾空格 this.specName_sm = this.specName_sm.trim(); if( !this.specName_sm ) { this.$message({ type:'error',message: '规格名称不能为空' }); return } if(this.speOptions.length === 0) { let localObj = { id: 0, name: this.specName_sm }; localArr.push(localObj); localJSON = JSON.stringify(localArr); }else { let localObj = { id: this.speOptions.length, name: this.specName_sm }; this.speOptions.push(localObj); localJSON = JSON.stringify(this.speOptions) } window.localStorage.setItem('localSpeOptions',localJSON) this.addSpecNameDialog = false; this.$message({type:'success',message:'新增成功'}); this.getLocalSpecNameOption(); }, /** 规格参数 失焦时,进行笛卡尔积算法,并渲染 商品售价 */ paramNameInputBlur(index,indexSV) { // // 如果失焦的input中有值,才再次计算 // if( this.speData[index].spec_value[indexSV].value ) { this.specCalcResult(); // } }, /** 规格参数 变化时,对数据进行处理后,再进行笛卡尔积算法,最终计算出商品售价的数据 */ specCalcResult() { // 商品规格原始数据 let orgSpeData = this.deepClone(this.speData); // 进行数据处理后的数据, let proSpeData = this.processData(orgSpeData); // 需要将 所有规格名称,提出一个数组集合, this.speNameList = []; orgSpeData.forEach((item,index)=> { if( item.spec_name ) { let obj = { spec_name: '' }; // 注意, item.spec_value是否一定为一个长度大于 1 的数组 if( item.spec_value.length > 0) { for(let i = 0; i < item.spec_value.length; i++) { if( item.spec_value[i].value ) { obj.spec_name = item.spec_name; this.speNameList.push(obj); break; } } } } }) // 当处理后的数据,是一个空数组,则不需要再进行 笛卡尔积 计算,可以直接给出计算结果 if (proSpeData.length === 0) { let goodsPam = { pamNameList: [], pamDataList: [], pamTableList: [], }; this.$emit('calcSpeTable', goodsPam); } else { let goodsParametersList = this.cartesian(proSpeData); let goodsPam = { pamNameList: [], pamDataList: [], pamTableList: [], }; goodsPam.pamNameList = this.speNameList; goodsPam.pamDataList = goodsParametersList; goodsParametersList.forEach((item)=> { let prePamTable = [ { js_price:'', sl_price:'', price:'', sc_price:'', stock:'', weight:'', thumb:'',spec_values: [] } ]; goodsPam.pamTableList.push(prePamTable) }); this.$emit('calcSpeTable',goodsPam); } }, /** 数据处理为,二维数组,供笛卡尔积算法方法使用,对空数据进行过滤 */ processData(list) { let result = []; if (list && list.length > 0) { for (let i = 0; i < list.length; i++) { let childList = []; if(list[i].spec_name) { for(let j = 0; j < list[i].spec_value.length; j++) { if(list[i].spec_value[j].value) { childList.push(list[i].spec_value[j].value); } } } if(childList.length > 0) { result.push(childList); } } } return result; }, /** 在保存时,同步更新界面dom */ upDateDom() { if(this.speData.length === 1) { if(this.speData[0].spec_name === '' ) { this.speData[0].spec_value = [{ value: ''}] }else { for(let i = 0; i < this.speData[0].spec_value.length; i++) { if(this.speData[0].spec_value[i].value === '') { this.speData[0].spec_value.splice(i,1) i = i - 1; } } if(this.speData[0].spec_value.length === 0) { this.speData[0].spec_value = [{ value: '' }]; } } }else if( this.speData.length > 1 ) { for(let i=0; i<this.speData.length; i++){ if( this.speData[i].spec_name === '' || this.speData[i].spec_value.length === 0) { this.speData.splice(i,1); } else if( this.speData[i].spec_name !== '' ) { // for( let j = 0; j < this.speData[i].spec_value.length; j++) { // // } } } } //console.log(404,this.speData); }, /** 笛卡尔积算法 */ cartesian(arr) { if(arr.length < 2) { return arr[0] || []; }else { return [].reduce.call(arr, function(col, set) { let res = []; col.forEach( c => { set.forEach(s => { let t = [].concat(Array.isArray(c) ? c : [c]); t.push(s); res.push(t); }); }); return res }); } } }, // methods end }; </script> <style scoped> .goods-specifications { padding: 20px; /*margin-top: 18px;*/ } .spe-con { display: flex; justify-content: flex-start; } /* 仿 element 的from-item 样式,注意,size为 samll */ .el-form-item-h { width:80%; margin: 0 20px 20px 0; padding: 20px 0 0; background: #F7F8FA; border: 1px solid #E5E5E5; border-radius: 2px; } .el-form-item-h__label { width: 150px; text-align: right; vertical-align: middle; float: left; font-size:14px; color: #606266; line-height: 32px; padding: 0 12px 0 0; box-sizing: border-box; } /*.el-form-item-h__label:before {*/ /* content: '';*/ /* color: #ff4949;*/ /* margin-right: 4px;*/ /*}*/ .el-form-item-h__content { margin-left: 150px; position: relative; font-size: 14px; } .spe-params-con { margin: 20px 0; } /* 商品规格 - 规格名称 - 参数input */ .spe-params-input-item { width: 150px; margin: 0 10px 20px 0; } .el-icon-delete-h:hover { font-size: 14px; color: #ff4949; cursor: pointer; } .add-spec-dialog-con { display: flex; flex-direction: row; justify-content: center; align-items: center; } </style>