<template> <el-card class="step-card"> <div slot="header" class="flex between"> <div class="step-card-title">Step3 Confirm</div> </div> <div v-loading="loading"> <div> <div v-if="addressList.length"> <div class="card-sub-title">Ship to</div> <div class="form-item-label">Shipping address</div> <div class="flex between"> <div> <div> <div class="flex"> <div class="address-text name">{{ defaultAddr.name }}</div> <div class="address-text"> {{ defaultAddr.phone }}</div> </div> <div class="address-text">{{ defaultAddr.address }}</div> <div class="flex"> <div class="address-text">{{ defaultAddr.city }}</div> <div class="address-text"> {{ defaultAddr.post_code }} </div> </div> </div> </div> <el-button type="text" @click="visibleOfSelectAddressDialog = true" >edit address</el-button > </div> </div> <el-button @click="addAddress" v-else size="mini" plain >+ Add delivery address</el-button > </div> <el-form :model="form" :rules="rules" label-width="0" label-position="left" ref="form"> <el-form-item label-width="120px" label="Freight type"> <el-radio-group v-loading="freightLoading" v-model="form.freight_type"> <el-radio :label="2">AAE</el-radio> <el-radio :label="1">Road express</el-radio> </el-radio-group> </el-form-item> <div class="price-title flex"> <div>Pricing Optiopns</div> </div> <div class="divide-solid"></div> <div class="price-table"> <el-table class="" v-loading="freightLoading" :data="computedPriceTableData"> <el-table-column v-for="(column, index) in computedPriceTableColumns" :fixed="column.fixed || false" :width="column.width || '100px'" :key="`${column.prop}-${index}`" :label="column.label || column.prop" :prop="column.prop"></el-table-column> </el-table> </div> <el-form-item label-width="0" prop="job"> <div class="form-item-label"> <span style="color: red">*</span> Reference or Job name </div> <el-select v-model="form.job" filterable allow-create default-first-option style="width: 100%" placeholder="Ref, Choose or create new one"> <el-option v-for="item in jobOptions" :key="item.label_type" :label="item.label_type" :value="item.label_type"> </el-option> </el-select> </el-form-item> <div class="form-item-label">Note</div> <el-form-item prop="note" label-width="0"> <el-input v-model="form.note" placeholder="Any note"></el-input> </el-form-item> </el-form> <div class="flex center"> <el-button type="primary" @click="addToProject"> <div class="flex center"> <span class="btn-icon el-icon-shopping-cart-2"></span> Add to project </div> </el-button> <el-button type="info" @click="sendEnquiry"> <div class="flex"> <span class="btn-icon el-icon-shopping-bag-1"></span> Send enquiry </div> </el-button> </div> <add-address-dialog :dialogVisible.sync="visibleOfEditAddressDialog" :data="addressDetail" :componentVisible="patternOfEditAddressDialog" @close="closeAddressDialog" @update="getAddressData" /> <select-address-dialog :addressDetail="form.defaultAddr" :addressList="addressList" :visible.sync="visibleOfSelectAddressDialog" @add-new-addr="addAddress" @select-addr="selectAddress" @close="closeSelectAddressDialog"></select-address-dialog> </div> </el-card> </template> <script> import { plus } from 'number-precision' import _ from 'lodash' import { mapState } from 'vuex' import stepMixin from './stepMixin' import selectAddressDialog from './DialogSelectAddr.vue' import addAddressDialog from '@/components/addAddressDialog.vue' import { formatPrice, getUnit, getSetup, getPrint, getAddon, getPackaging, getFright, } from '@/utils/price' export default { name: 'Step3', components: { addAddressDialog, selectAddressDialog, }, mixins: [stepMixin], props: { // 步骤1中选中的周期. 不能直接拿form1来取form1.cycle, 否则步骤1变更时, 涉及到form1的computed会全部重新计算, 可能会导致其他异常 cycle: { type: Number, default: function () { return 0 }, }, // 同cycle model: { type: Number, default: function () { return 0 }, }, // 同cycle. form1的autoForm, 记录对应颜色的 商品购买数量表单. buyForm: { type: Array, default: function () { return [] }, }, // 步骤2的表单. 因其设计选中规格, 为当前步骤的前置数据, 故需从父组件获取. 只用来计算, 本组件不更改这个数据. form2: { type: Object, default: function () { return {} }, }, weightInfo: { type: Object, default: function () { return {} }, }, }, data() { return { rules: { job: [ { required: true, message: 'Please select or create a job name', trigger: 'change', }, ], }, form: { // 默认的配送地址 defaultAddr: {}, freight_type: 1, job: '', note: '', }, freightLoading: false, // 运费信息 freightInfo: {}, // 运费浮动比. 从product页面来的数据, 记录在local storage里面 sellFreight: 0, addressList: [], addressDetail: {}, visibleOfSelectAddressDialog: false, visibleOfEditAddressDialog: false, patternOfEditAddressDialog: 1, jobOptions: [], priceTableColumns: [ { prop: 'qty', label: 'Qty', fixed: 'left', }, ], dynamicPriceTableColumns: [], dynamicPriceTableData: [], priceTableData: [ { qty: 'Unit', }, { qty: 'Set up', }, { qty: 'Print Option', }, { qty: 'Addon', }, { qty: 'Packing', }, { qty: 'Fright', }, { qty: 'SubTotal', }, ], } }, computed: { ...mapState('config', { configInfo: state => state.configInfo }), // 商品 当前选中周期的 基础价格和打印价格数据 computedPriceData() { return this.priceData.priceList.filter( item => item.cycle_id === this.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.model ) }, /** * 当前选中周期下, 商品对应的各型号打印价格. */ computedPrintPriceData() { const model = this.computedPriceData.length ? this.computedPriceData[0] : {} // 属性‘1’里面是基础价格数据, 属性 ’2‘是打印价格数据 if (model['2']) { return model['2'].slice() } else { return [] } }, // 其实没必要的, 只是方便html上不用读多一层属性 from.defaultAddr.****这样 defaultAddr() { return this.form.defaultAddr }, computedPriceTableColumns() { return this.priceTableColumns.concat(this.dynamicPriceTableColumns) }, computedPriceTableData() { return this.priceTableData.map((item, index) => Object.assign({}, item, this.dynamicPriceTableData[index] || {}) ) }, }, watch: { addressList() { const temp = this.addressList.filter(item => item.is_default === 1) if (temp.length) { this.form.defaultAddr = temp[0] } else if (this.addressList.length) { this.form.defaultAddr = this.addressList[0] } else { this.form.defaultAddr = {} } if (this.form.defaultAddr.post_code) { this.getFreight() } }, 'form.freight_type'() { this.getFreight() }, 'form.defaultAddr'() { this.getFreight() }, buyForm: { deep: true, immediate: true, handler: function () { const temp = [] if (!this.loaded) return temp this.dynamicPriceTableColumns = this.buyForm.map(item => { return { prop: `${item.value}`, buyNum: item.value, } }) this.getDynamicPriceTableData() }, }, form2: { deep: true, immediate: true, handler: function () { this.getDynamicPriceTableData() }, }, }, mounted() { if (localStorage.getItem('sellFreight')) { this.sellFreight = localStorage.getItem('sellFreight') } this.getAddressData().then(() => { this.loaded = true }) this.getJobList() }, 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 step3 form error')) } }) }) }, addToProject() { this.$emit('check', 1) }, sendEnquiry() { this.$emit('check', 2) }, // Job 下拉/输入框候选数据 getJobList() { this.$axios .post('/goods_cart/cartLabelLists', { keyword: [] }) .then(res => { this.jobOptions = res.result }) }, // 获取配送地址数据 async getAddressData() { return await this.$axios .get('address/list', { params: { keyword: '', page: 1, limit: 100, }, }) .then(res => { if (res.code === 1) { this.addressList = res.result.data this.addressTotal = res.result.total } }) }, addAddress() { this.addressDetail = {} this.patternOfEditAddressDialog = 2 this.visibleOfEditAddressDialog = true }, closeAddressDialog() { this.visibleOfEditAddressDialog = false }, selectAddress(data) { const temp = this.addressList.filter(item => item.id === data) if (temp.length) this.form.defaultAddr = temp[0] this.closeSelectAddressDialog() }, closeSelectAddressDialog() { this.visibleOfSelectAddressDialog = false }, getDynamicPriceTableData() { const temp = [ {}, // unit {}, // setup 打印和附加价格之和 {}, // print option {}, // addon 附加服务除了packing之外的总价 {}, // packing / packaging {}, // fright 运费 {}, // 汇总 ] const ratio = plus(this.sellFreight / 100, 1) // { buyNum数量, prop标签label } this.dynamicPriceTableColumns.forEach(v => { const unitPrice = getUnit( v.buyNum, this.computedBasePriceIndex, this.priceData.attributeList, this.computedBasePriceData ) temp[0][`${v.prop}`] = formatPrice(unitPrice) temp[0][`${v.prop}_value`] = unitPrice const setupPrice = getSetup( v.buyNum, this.form2, this.priceData.additionList ) temp[1][`${v.prop}`] = formatPrice(setupPrice) temp[1][`${v.prop}_value`] = setupPrice const printPrice = getPrint( v.buyNum, this.form2, this.priceData.attributeList ) temp[2][`${v.prop}`] = formatPrice(printPrice) temp[2][`${v.prop}_value`] = printPrice const addonPrice = getAddon( v.buyNum, this.form2, this.priceData.attributeList, this.priceData.additionList ) temp[3][`${v.prop}`] = formatPrice(addonPrice) temp[3][`${v.prop}_value`] = addonPrice const packagingPrice = getPackaging( v.buyNum, this.form2, this.priceData.attributeList, this.priceData.additionList ) temp[4][`${v.prop}`] = formatPrice(packagingPrice) temp[4][`${v.prop}_value`] = packagingPrice const frightPrice = getFright( v.buyNum, this.configInfo, this.freightInfo, this.weightInfo, ratio ) temp[5][`${v.prop}`] = formatPrice(frightPrice) temp[5][`${v.prop}_value`] = frightPrice // 最后一行汇总行的数据 const total = [ temp[0][`${v.prop}_value`], temp[1][`${v.prop}_value`], temp[2][`${v.prop}_value`], temp[3][`${v.prop}_value`], temp[4][`${v.prop}_value`], temp[5][`${v.prop}_value`], ].reduce((total, curr) => { const value = Number(curr) if (total === 'POA' || curr === 'POA') { return 'POA' } else if (typeof value === 'number') { return plus(total, value) } else { return total } }, 0) temp[6][`${v.prop}`] = typeof total === 'number' ? formatPrice(total) : total temp[6][`${v.prop}_value`] = total }) // console.log(temp, '计算动态表格数据') this.dynamicPriceTableData = temp }, getFreight: _.debounce(function () { if (!this.form.defaultAddr.post_code) return this.freightLoading = true this.$axios .post('/quote/freight', { postcode: this.form.defaultAddr.post_code, type: this.form.freight_type, }) .then(res => { this.freightInfo = res.result this.getDynamicPriceTableData() }) .finally(() => { setTimeout(() => { this.freightLoading = false }, 300) }) }, 200), }, } </script> <style lang="scss" scoped> @import './step.scss'; div { box-sizing: border-box; } .btn-icon { font-size: 20px; } .address-text { font-size: 14px; line-height: 18px; color: #000; font-family: Proxima Nova, sans-serif; &.name { font-weight: bold; } } .price-title { height: 32px; padding: 0 8px; background: #f8f9fd; width: 100%; & > div { font-size: 16px; color: #000; font-family: Proxima Nova, sans-serif; } } .divide-solid { height: 4px; width: 100%; background-color: #a9aeba; margin: 10px auto 12px; } .price-table { :deep(.el-table) { font-family: Proxima Nova; &::before { background-color: transparent; } td.el-table__cell { font-size: 15px; border-bottom: 2px solid #dddcdc; } th.el-table__cell { font-weight: 600; font-size: 20px; color: #fff; background-color: #626469; border-bottom: none; } } } </style>