<template> <el-card class="step-card"> <div slot="header" class="flex between"> <div class="step-card-title">Step1 Choose model</div> <div>>></div> </div> <div v-loading="loading"> <div class="card-sub-title">{{ detail.product_name }}</div> <image-viewer v-if="isBigImageShow" :on-close="closeBigImage" :initial-index="0" :url-list="detail.main?.images" /> <div class="img-wrap flex center" @click="openBigImage"> <img :src="detail.main?.image" /> </div> <div class="divider"></div> <el-form :model="form" :rules="rules" label-width="90px" ref="form"> <el-form-item label="Time frame" prop="cycle"> <el-radio-group v-model="form.cycle"> <el-radio v-for="item in priceData.priceList" :key="item.cycle_id" :label="item.cycle_id" >{{ item.name }} </el-radio> </el-radio-group> </el-form-item> <div class="flex"> <el-form-item label-width="120px" label="Decorated in" prop="decorated"> <el-select v-model="form.decorated" size="small"> <el-option :label="'Local'" :value="'Local'"></el-option> <el-option :label="'China'" :value="'China'"></el-option> </el-select> </el-form-item> <el-form-item label-width="120px" prop="supplyChain" label="Supply chain" size="small"> <el-select v-model="form.supplyChain"> <el-option :label="'AU Stock'" :value="'AU Stock'"></el-option> <el-option :label="'Air Freight'" :value="'Air Freight'"></el-option> <el-option :label="'Sea Freight'" :value="'Sea Freight'"></el-option> </el-select> </el-form-item> </div> <el-form-item label="Model" prop="model"> <div class="custom-checkbox-wrap"> <div class="custom-checkbox" :class="{ active: form.model === item.id, disable: form.model && form.model !== item.id, }" v-for="item in computedBasePriceData" @click="modelClick(item)" :key="item.id"> {{ item.point }} </div> </div> <el-radio-group v-model="form.model" style="display: none" size="small"> <el-radio-button v-for="item in computedBasePriceData" :key="item.id" :label="item.id" >{{ item.point }}</el-radio-button > </el-radio-group> </el-form-item> <el-form-item label="Colour" prop="color"> <div class="custom-checkbox-wrap"> <div class="custom-checkbox" :class="{ active: form.color.includes(0), }" @click="colorClick({ id: 0 })"> Unspecified </div> <div class="custom-checkbox flex" :class="{ active: form.color.includes(item.id), }" v-for="item in detail.color" @click="colorClick(item)" :key="item.id"> <div class="color-selector-label flex center"> <img :src="item.img" /> </div> <div> {{ item.name }} </div> </div> <div class="custom-checkbox" :class="{ active: form.color.includes(999), }" @click="colorClick({ id: 999 })"> + PMS </div> </div> <el-checkbox-group v-model="form.color" style="display: none" size="small"> <el-checkbox-button :label="0">Unspecified</el-checkbox-button> <el-checkbox-button v-for="item in detail.color" :label="item.id" :key="item.id"> {{ item.name }} </el-checkbox-button> <el-checkbox-button :label="999">+ PMS</el-checkbox-button> </el-checkbox-group> </el-form-item> <el-form-item prop="colorPmsText"> <el-input v-show="form.color.includes(999)" type="textarea" :rows="5" placeholder="Product color comments. Multi color instructions, specific color matchong (CMYK, RGB, PMS) etc.." v-model="form.colorPmsText"> </el-input> </el-form-item> <div class="qty-title"> QTY ( MOQ:{{ computedMinBuyNumber }} ) </div> <div class="divider" v-show="form.color.length > 0"></div> <div class="flex wrap"> <el-form-item class="flex-auto color-form-item" label-width="140" v-for="(formItem, index) in form.autoForm" :prop="`autoForm.${index}.value`" :rules="{ required: true, trigger: 'change', type: 'number', message: 'the number should not less then MOQ', min: computedMinBuyNumber, }" :key="index"> <div class="flex end"> <div class="flex-auto flex end"> <img class="auto-form-color-image" v-if="formItem.img" :src="formItem.img" alt="" /> <div class="auto-form-label form-item-label"> {{ formItem.label }} </div> </div> <el-input @change=" value => { if (value >= computedMinBuyNumber) $refs.form.clearValidate(`autoForm.${index}.value`) } " style="width: 80px" :min="computedMinBuyNumber" type="number" v-model.number="formItem.value"></el-input> </div> </el-form-item> </div> </el-form> </div> </el-card> </template> <script> import imageViewer from 'element-ui/packages/image/src/image-viewer' import stepMixin from './stepMixin' export default { name: 'Step1', components: { 'image-viewer': imageViewer, }, mixins: [stepMixin], props: { // 步骤2的表单. 因其打印价格表单会反向影响本组件的最小起购数量, 故需从父组件获取. 只用来计算, 本组件不更改这个数据. form2: { type: Object, default: function () { return {} }, }, }, data() { // this.detail 记录在mixin里; return { // 查看大图 isBigImageShow: false, rules: { decorated: [{ required: true, message: '请选择', trigger: 'change' }], supplyChain: [{ required: true, message: '请选择', trigger: 'change' }], model: [ { required: true, trigger: 'change', type: 'number', min: 1, message: 'Please select model', }, ], color: [ { required: true, validator: (rules, value, cb) => { value.length > 0 ? cb() : cb(new Error('Please select at least one color')) }, trigger: 'change', }, ], colorPmsText: [{ trigger: 'blur', validator: this.checkColorPmsText }], cycle: [ { required: true, message: 'Please select time frame', trigger: 'change', }, ], }, form: { // 必须要有的初始值, 否则必定会报错Cannot read properties of undefined color: [], model: 0, colorPmsText: '', }, // 标志符,用于控制监听是否启用 loaded: false, } }, computed: { // 商品 当前选中周期的 基础价格和打印价格数据 computedPriceData() { return this.priceData.priceList.filter( item => item.cycle_id === this.form.cycle ) }, // 当前选中周期下, 商品对应的各型号基础价格数据. 可以推断当前周期有几个型号 computedBasePriceData() { const model = this.computedPriceData.length ? this.computedPriceData[0] : {} // 属性‘1’里面是基础价格数据, 属性 ’2‘是打印价格数据 if (model['1']) { return model['1'].slice() } else { return [] } }, // 当前型号位于基础价格数据的index, 用于取出型号对应的基础数据 computedBasePriceIndex() { return this.computedBasePriceData.findIndex( item => item.id === this.form.model ) }, // 该商品周期的最小起订量. attributeList的属性名(website_qty1, website_qty2)排序, 然后取属性值 computedMinBuyNumber() { // 算基础价格的第一个有效值 const target = this.computedBasePriceData.length ? this.computedBasePriceData[ this.computedBasePriceIndex > 0 ? this.computedBasePriceIndex : 0 ] : {} const candidate = [ target.website_qty1, target.website_qty2, target.website_qty3, target.website_qty4, target.website_qty5, target.website_qty6, target.website_qty7, target.website_qty8, ] if (!candidate.some(item => item !== undefined)) return 1 // 算出商品基础价格的第一个有效值 let index = this.findEffectIndex(candidate) // 算出打印服务和附加服务的第一个有效价格所处位置. Object.entries(this.form2).forEach(current => { if (/\d+/.test(current[0])) { const decoration = current[1].decorationList.filter( i => i.id === current[1].printService ) if (decoration.length && current[1].enable) { // 打印价格的基础阶梯价格 const index2 = this.findEffectIndex([ decoration[0].website_qty1, decoration[0].website_qty2, decoration[0].website_qty3, decoration[0].website_qty4, decoration[0].website_qty5, decoration[0].website_qty6, decoration[0].website_qty7, decoration[0].website_qty8, ]) // 附加价格 const index3 = this.findEffectIndex([ decoration[0].supplier_qty1, decoration[0].supplier_qty2, decoration[0].supplier_qty3, decoration[0].supplier_qty4, decoration[0].supplier_qty5, decoration[0].supplier_qty6, decoration[0].supplier_qty7, decoration[0].supplier_qty8, ]) // 让index记录最大值 if (index2 > index) index = index2 if (index3 > index) index = index3 } } else { // 附加服务的价格 const additions = this.priceData.additionList[current[0]].filter( item => current[1].includes(item.id) ) if (additions.length) { additions.forEach(target => { const index4 = this.findEffectIndex([ target.website_qty1, target.website_qty2, target.website_qty3, target.website_qty4, target.website_qty5, target.website_qty6, target.website_qty7, target.website_qty8, ]) // 让index记录最大值 if (index4 > index) index = index4 }) } } }) const temp = Object.entries(this.priceData.attributeList) .sort((a, b) => a[0].localeCompare(b[0])) .filter((item, i) => index === i) if (temp.length) { return temp[0][1] } else { return 1 } }, }, watch: { computedBasePriceData() { if (!this.loaded || !this.computedBasePriceData.length) return // fixme 如果有从商品详情页带过来的型号选择数据, 要选中该值, 而不是默认的第一个值 if ( this.computedBasePriceData.filter(i => i.id === this.preData.model) .length ) { this.form.model = this.preData.model } else { this.form.model = this.computedBasePriceData[0].id } }, 'form.model': { immediate: true, handler: function () { if (!this.loaded) return this.generateColorForm() }, }, 'form.color': { immediate: true, handler: function () { if (!this.loaded) return this.generateColorForm() this.$refs.form.clearValidate('colorPmsText') }, }, form2: { immediate: true, handler: function () { if (!this.loaded) return this.generateColorForm() }, }, }, mounted() { this.form = { supplyChain: 'AU Stock', decorated: 'Local', model: 0, // 型号的赋值在computedBasePriceData的监听里面, 因为其涉及到基础价格变更后的重新赋值. cycle: this.preData.cycle ? this.preData.cycle : this.priceData.priceList[0].cycle_id, color: [], colorPmsText: '', // 根据所选规格和颜色组合成的自动表单.需要在computedBasePriceData的watch里面生成 autoForm: [], } this.loaded = true }, methods: { checkForm() { return new Promise((resolve, reject) => { this.$refs.form.validate(valid => { if (valid) { resolve(JSON.parse(JSON.stringify(this.form))) } else { reject(new Error('validate step1 form error')) } }) }) }, checkColorPmsText(rule, value, cb) { // fixme. 颜色的两个固定值的id还未确定 if (!this.form.color.includes(999)) cb() if (this.form.colorPmsText.trim().length < 1) { cb(new Error('You must type color comment here if "+ PMS" is checked')) } else { cb() } }, // 找出第一个有效价格的index. 非变异方法, 勿对参数进行赋值操作. // 价格留空和111不算到起购量, 为正常数值或者0或者999(POA)就可以算到起购量, 所以在此处将空手动重置为NaN findEffectIndex(array) { return array .map(item => (item === '' ? Number.NaN : Number(item))) .findIndex(item => !Number.isNaN(item) && item !== 111) }, openBigImage() { this.isBigImageShow = true }, closeBigImage() { this.isBigImageShow = false }, generateColorForm() { if (!this.loaded || this.form.color.length < 1) { this.form.autoForm = [] return } // 生成所选规格和颜色组合成的自动表单 const targetModel = this.computedBasePriceData.filter( item => item.id === this.form.model ) const oldForm = JSON.parse(JSON.stringify(this.form.autoForm)) let color1 = [] if (targetModel.length) { color1 = this.detail.color.reduce((total, item) => { if (this.form.color.includes(item.id)) { const temp = { img: item.img || '', label: `${item.name}, ${targetModel[0].point}`, color: item.name, colorId: item.id, value: this.computedMinBuyNumber, } const temp2 = oldForm.filter(item => item.label === temp.label) if (temp2.length && temp2[0].value >= this.computedMinBuyNumber) { // 如果原来有值且原值大于等于最小起购, 则沿用原值 temp.value = temp2[0].value } total.push(temp) } return total }, []) if (this.form.color.includes(0)) { const temp = { img: '', label: `Unspecified, ${targetModel[0].point}`, color: 'Unspecified', colorId: -1, value: this.computedMinBuyNumber, } const temp2 = oldForm.filter(item => { return item.label === temp.label }) if (temp2.length && temp2[0].value >= this.computedMinBuyNumber) { // 如果原来有值且原值大于等于最小起购, 则沿用原值 temp.value = temp2[0].value } color1.unshift(temp) } if (this.form.color.includes(999)) { const temp = { img: '', label: `+ PMS, ${targetModel[0].point}`, color: 'PMS', colorId: -1, value: this.computedMinBuyNumber, } const temp2 = oldForm.filter(item => item.label === temp.label) if (temp2.length && temp2[0].value >= this.computedMinBuyNumber) { // 如果原来有值且原值大于等于最小起购, 则沿用原值 temp.value = temp2[0].value } color1.push(temp) } } this.$set(this.form, 'autoForm', color1) }, modelClick(item) { if (this.form.model === item.id) { this.form.model = 0 } else { this.form.model = item.id } }, colorClick(item) { const index = this.form.color.indexOf(item.id) if (index !== -1) { this.form.color.splice(index, 1) } else { this.form.color.push(item.id) } }, }, } </script> <style lang="scss" scoped> @import './step.scss'; div { box-sizing: border-box; } .img-wrap { $width: 140px; width: $width; height: $width; position: relative; padding: 4px; border: 1px solid #eee; cursor: pointer; margin-bottom: 16px; img { width: 100%; } } .el-checkbox-button { min-width: 76px; } .qty-title { margin-bottom: 16px; font-size: 14px; font-family: Proxima Nova, sans-serif; color: #000; } // 选择颜色 .color-selector-label { width: 16px; height: 16px; position: relative; margin-right: 4px; img { width: 100%; } } // 颜色型号对应购买数量输入项 .color-form-item { box-sizing: border-box; max-width: 50%; padding-left: 4px; .auto-form-label { text-align: right; padding: 0 12px; min-width: 140px; } :deep(.el-form-item__error) { text-align: right; width: 100%; } .auto-form-color-image { width: 20px; height: 20px; margin-right: -4px; } } </style>