Vue js 结合两个组件的元素
Vue js combining the elements from two components
我正在尝试构建电子商务类型应用程序的结帐页面。在结帐时,我有一个来自数据库的 OrderItems 列表,每个都有价格、数量。您可以选择并组合这些。此外,我必须在其他地方呈现一个带有 "addons" 的列表到您的购物篮。这也是 OrderItems(相同的属性)但具有不同的类型。
我有一个 Vue.js 组件,用于呈现您可以从中选择的 OrderItems 数组。我的方法是渲染同一个组件两次。但是,"selected" 属性 采用来自一个列表或另一个列表的模型,但不能同时来自两个列表。我希望选定的道具包含两个列表中的项目(简单的 orderItems 和插件)
fiddle:https://jsfiddle.net/w8vfb64L/
代码:
模板:
<section class="content">
<div class="row" id="app">
<div class="col-md-8">
<div class="box box-primary">
<div class="box-body">
<div class="row">
<div class="col-md-12">
<div class="form-group">
<label class="control-label required">Items</label>
<div class="col-md-12">
<entries :entries="{ 0 : { shareSize : 'Small', quantity : '1', itemPrice : '24', frequency : '' }, 1 : { shareSize : 'Medium', quantity : '1', itemPrice : '35', frequency : '' }, 2 : { shareSize : 'Large', quantity : '1', itemPrice : '46', frequency : '' } }"
:selected="selected"></entries>
</div>
<div class="col-md-12">
<label class="control-label required">Addons</label>
<entries :entries="{ 0 : { shareSize : 'Large', quantity : '1', itemPrice : '46', frequency : '' } }" :selected="selected"></entries>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="box box-info">
<div class="box-body" style="padding:15px;">
<div class="container-fluid">
<div class="form-group">
<div class="control-label">
<label>Summary</label>
</div>
<div class="form-control" v-for="item in selected">
<span class="pull-left small-box-footer">{{ item.shareSize }}</span>
<span class="pull-right">{{ item.quantity + ' x $ ' + (item.itemPrice*item.quantity).toFixed(2)}}</span>
</div>
<div class="control-label">
<label>Payment plan</label>
</div>
<div class="col-md-12">
{{ '$ ' + totalAdvance.toFixed(2) }} - advance
</div>
<div class="col-md-12">
{{ '$ ' + totalFirstWeek.toFixed(2) }} - first week
</div>
<div class="col-md-12">
{{ '$ ' + onDeliveryPayment.toFixed(2) }}/ week on each of the {{ weeks }} weeks of the subscription
</div>
<div class="col-md-12 row">
<div class="control-label"><strong><span class="pull-left">Total</span><span class="pull-right">{{ '$ ' + total.toFixed(2) }}</span></strong></div>
</div>
<div class="col-md-12 row">
<div class="title"><strong><span class="pull-left">Total due now</span><span class="pull-right">{{ '$ ' + totalAdvance.toFixed(2) }}</span></strong></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- component template -->
<template id="entries">
<div class="col-md-12">
<div class="form-group" v-for="(entry, key) in entriesCopy" v-bind:entry="entry">
<div class="form-group col-md-12">
<div class="col-md-12">
<div class="col-md-4">
<input type="checkbox" v-bind:value="entry" v-model="selectedCopy">
</div>
<div class="col-md-4">{{entry.shareSize}}</div>
<div class="col-md-4">{{'$ ' + Number(entry.itemPrice).toFixed(2) }}</div>
</div>
<div class="form-group col-md-12">
<div class="col-md-6">
<input type="number" v-model="entry.quantity" :value="entry.quantity" />
</div>
</div>
</div>
</div>
</div>
</template>
Javascript:
var bus = new Vue();
var entriesComponent = Vue.component('entries', {
template: '#entries',
props: {
entries: [Array, Object],
selected: Array,
addons: Array,
frequencies: [Array, Object],
},
created: function() {
this.entriesCopy = this.entries;
this.selectedCopy = this.selected;
},
watch: {
selectedCopy: function(val, oldVal) {
bus.$emit('selected-changed', val);
}
},
data: function() {
return {
entriesCopy: [],
selectedCopy: []
}
}
});
new Vue({
el: '#app',
data: {
entries: [],
selected: [],
addons: [],
frequencies: [],
paymentConfig: {
advance: 25,
firstweek: 25,
ondelivery: 50,
},
weeks: 12,
},
components: {
'entriesComponent': entriesComponent,
},
created: function() {
// store this to use with Vue.set
var temp = this;
bus.$on('selected-changed', function(selected) {
// vm.$set deprecated
Vue.set(temp, 'selected', selected);
});
},
computed: {
totalAdvance: function() {
return (this.paymentConfig.advance * this.total) / 100;
},
totalFirstWeek: {
get: function() {
return (this.paymentConfig.firstweek * this.total) / 100;
},
},
onDeliveryPayment: {
get: function() {
return (this.paymentConfig.ondelivery * this.total) / (this.weeks * 100);
}
},
total: {
get: function() {
var sum = 0;
var weeks = this.weeks;
this.selected.forEach(function(item) {
sum += weeks * item.itemPrice * item.quantity;
});
console.log(sum);
return sum;
}
}
}
});
不得不进行大量重构,试图严格遵循您想要构建购物车的方式。然而,它确实需要重新考虑如何构建数据:
这是 fiddle:https://jsfiddle.net/thebigsurf/w8vfb64L/11/
编辑
要允许在产品对象上更新超过 1 个字段,我认为您目前不能为此使用 v-model。因此,不是在组件上设置 v-model 而是传递一个方法,您可以通过以下方式更新任何项目字段:
fiddle: https://jsfiddle.net/thebigsurf/0chtzwjd/2/
var entriesComponent = Vue.component('entries', {
template: '#entries',
props: {
item: Object,
itemKey: String,
selected: Boolean,
updateSelected: Function,
updateItem: Function,
},
data () {
return {
quantity: 0,
message: '',
}
},
created () {
this.quantity = this.item.quantity
this.message = this.item.message
},
watch: {
quantity () {
this.updateItem(this.itemKey, 'quantity', this.quantity)
},
message () {
this.updateItem(this.itemKey, 'message', this.message)
},
}
});
new Vue({
el: '#app',
data: {
allProducts: {
'foo': { shareSize: 'Small', quantity: '1', itemPrice: '24', message: '' },
'bar': { shareSize: 'Medium', quantity: '1', itemPrice: '35', message: '' },
'baz': { shareSize: 'Large', quantity: '1', itemPrice: '46', message: 'hello' },
'bop': { shareSize: 'Large', quantity: '1', itemPrice: '46', message: '' },
},
items: [ 'foo', 'bar', 'baz' ],
addons: [ 'bop' ],
selected: {},
paymentConfig: {
advance: 25,
firstweek: 25,
ondelivery: 50,
},
weeks: 12,
},
components: {
entriesComponent,
},
created () {
this.setSelectableItems()
},
computed: {
totalAdvance () {
return (this.paymentConfig.advance * this.total) / 100
},
totalFirstWeek () {
return (this.paymentConfig.firstweek * this.total) / 100
},
onDeliveryPayment () {
return (this.paymentConfig.ondelivery * this.total) / (this.weeks * 100)
},
total() {
var sum = 0
Object.keys(this.selected)
.forEach((productKey) => {
if (this.selected[productKey]) {
sum +=
this.weeks *
this.allProducts[productKey].itemPrice *
this.allProducts[productKey].quantity
}
})
return sum
},
},
methods: {
setSelectableItems () {
this.items
.forEach((productKey) => {
Vue.set(this.selected, productKey, false)
})
this.addons
.forEach((productKey) => {
Vue.set(this.selected, productKey, false)
})
},
setSelected (productKey, value) {
this.selected[productKey] = value
},
syncItem (key, field, value) {
this.allProducts[key][field] = value
},
},
});
.row {
background: #f1f1f1;
padding: 25px;
margin-top: 10px;
}
.row:nth-child(even) {
background: #f9f9f9;
}
.item {
background: #dcdcdc;
border: 1px solid #a2a2a2;
padding: 10px;
margin-top: 10px;
}
.item span {
margin-left: 10px;
}
.item input {
display: inline-block;
}
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<div class="row">
<label class="control-label required">Items</label>
<entries
v-for="productKey in items"
:update-item="syncItem"
:item="allProducts[productKey]"
:item-key="productKey"
:update-selected="setSelected"
:selected="selected[productKey]"></entries>
</div>
<div class="row">
<label class="control-label required">Addons</label>
<entries
v-for="productKey in addons"
:update-item="syncItem"
:item="allProducts[productKey]"
:item-key="productKey"
:update-selected="setSelected"
:selected="selected[productKey]"></entries>
</div>
<div class="row">
<label>Summary</label>
<div class="item" v-for="(value, productKey) in selected" v-if="value">
<span>{{ allProducts[productKey].shareSize }}</span>
<span>
{{ allProducts[productKey].quantity }}
x $
{{ (allProducts[productKey].itemPrice * allProducts[productKey].quantity).toFixed(2)}}
</span>
<span>{{ allProducts[productKey].message }}</span>
</div>
</div>
<div class="row">
<label>Payment plan</label>
<p>{{ '$ ' + totalAdvance.toFixed(2) }} - advance</p>
<p>{{ '$ ' + totalFirstWeek.toFixed(2) }} - first week</p>
<p>{{ '$ ' + onDeliveryPayment.toFixed(2) }}/ week on each of the {{ weeks }} weeks of the subscription</p>
</div>
<div class="row">
<p>
<span>Total</span>
<span>{{ '$ ' + total.toFixed(2) }}</span>
</p>
<p>
<span>Total due now</span>
<span>{{ '$ ' + totalAdvance.toFixed(2) }}</span>
</p>
</div>
</div>
<!-- component template -->
<template id="entries">
<div class="item">
<input
type="checkbox"
v-bind:value="selected"
@change="updateSelected(itemKey, $event.target.checked)">
<span>size: {{item.shareSize}}</span>
<span>price: {{'$ ' + Number(item.itemPrice).toFixed(2) }}</span>
<input type="number" v-model="quantity" />
<input type="text" v-model="message" />
</div>
</template>
我正在尝试构建电子商务类型应用程序的结帐页面。在结帐时,我有一个来自数据库的 OrderItems 列表,每个都有价格、数量。您可以选择并组合这些。此外,我必须在其他地方呈现一个带有 "addons" 的列表到您的购物篮。这也是 OrderItems(相同的属性)但具有不同的类型。
我有一个 Vue.js 组件,用于呈现您可以从中选择的 OrderItems 数组。我的方法是渲染同一个组件两次。但是,"selected" 属性 采用来自一个列表或另一个列表的模型,但不能同时来自两个列表。我希望选定的道具包含两个列表中的项目(简单的 orderItems 和插件)
fiddle:https://jsfiddle.net/w8vfb64L/
代码:
模板:
<section class="content">
<div class="row" id="app">
<div class="col-md-8">
<div class="box box-primary">
<div class="box-body">
<div class="row">
<div class="col-md-12">
<div class="form-group">
<label class="control-label required">Items</label>
<div class="col-md-12">
<entries :entries="{ 0 : { shareSize : 'Small', quantity : '1', itemPrice : '24', frequency : '' }, 1 : { shareSize : 'Medium', quantity : '1', itemPrice : '35', frequency : '' }, 2 : { shareSize : 'Large', quantity : '1', itemPrice : '46', frequency : '' } }"
:selected="selected"></entries>
</div>
<div class="col-md-12">
<label class="control-label required">Addons</label>
<entries :entries="{ 0 : { shareSize : 'Large', quantity : '1', itemPrice : '46', frequency : '' } }" :selected="selected"></entries>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="box box-info">
<div class="box-body" style="padding:15px;">
<div class="container-fluid">
<div class="form-group">
<div class="control-label">
<label>Summary</label>
</div>
<div class="form-control" v-for="item in selected">
<span class="pull-left small-box-footer">{{ item.shareSize }}</span>
<span class="pull-right">{{ item.quantity + ' x $ ' + (item.itemPrice*item.quantity).toFixed(2)}}</span>
</div>
<div class="control-label">
<label>Payment plan</label>
</div>
<div class="col-md-12">
{{ '$ ' + totalAdvance.toFixed(2) }} - advance
</div>
<div class="col-md-12">
{{ '$ ' + totalFirstWeek.toFixed(2) }} - first week
</div>
<div class="col-md-12">
{{ '$ ' + onDeliveryPayment.toFixed(2) }}/ week on each of the {{ weeks }} weeks of the subscription
</div>
<div class="col-md-12 row">
<div class="control-label"><strong><span class="pull-left">Total</span><span class="pull-right">{{ '$ ' + total.toFixed(2) }}</span></strong></div>
</div>
<div class="col-md-12 row">
<div class="title"><strong><span class="pull-left">Total due now</span><span class="pull-right">{{ '$ ' + totalAdvance.toFixed(2) }}</span></strong></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- component template -->
<template id="entries">
<div class="col-md-12">
<div class="form-group" v-for="(entry, key) in entriesCopy" v-bind:entry="entry">
<div class="form-group col-md-12">
<div class="col-md-12">
<div class="col-md-4">
<input type="checkbox" v-bind:value="entry" v-model="selectedCopy">
</div>
<div class="col-md-4">{{entry.shareSize}}</div>
<div class="col-md-4">{{'$ ' + Number(entry.itemPrice).toFixed(2) }}</div>
</div>
<div class="form-group col-md-12">
<div class="col-md-6">
<input type="number" v-model="entry.quantity" :value="entry.quantity" />
</div>
</div>
</div>
</div>
</div>
</template>
Javascript:
var bus = new Vue();
var entriesComponent = Vue.component('entries', {
template: '#entries',
props: {
entries: [Array, Object],
selected: Array,
addons: Array,
frequencies: [Array, Object],
},
created: function() {
this.entriesCopy = this.entries;
this.selectedCopy = this.selected;
},
watch: {
selectedCopy: function(val, oldVal) {
bus.$emit('selected-changed', val);
}
},
data: function() {
return {
entriesCopy: [],
selectedCopy: []
}
}
});
new Vue({
el: '#app',
data: {
entries: [],
selected: [],
addons: [],
frequencies: [],
paymentConfig: {
advance: 25,
firstweek: 25,
ondelivery: 50,
},
weeks: 12,
},
components: {
'entriesComponent': entriesComponent,
},
created: function() {
// store this to use with Vue.set
var temp = this;
bus.$on('selected-changed', function(selected) {
// vm.$set deprecated
Vue.set(temp, 'selected', selected);
});
},
computed: {
totalAdvance: function() {
return (this.paymentConfig.advance * this.total) / 100;
},
totalFirstWeek: {
get: function() {
return (this.paymentConfig.firstweek * this.total) / 100;
},
},
onDeliveryPayment: {
get: function() {
return (this.paymentConfig.ondelivery * this.total) / (this.weeks * 100);
}
},
total: {
get: function() {
var sum = 0;
var weeks = this.weeks;
this.selected.forEach(function(item) {
sum += weeks * item.itemPrice * item.quantity;
});
console.log(sum);
return sum;
}
}
}
});
不得不进行大量重构,试图严格遵循您想要构建购物车的方式。然而,它确实需要重新考虑如何构建数据:
这是 fiddle:https://jsfiddle.net/thebigsurf/w8vfb64L/11/
编辑
要允许在产品对象上更新超过 1 个字段,我认为您目前不能为此使用 v-model。因此,不是在组件上设置 v-model 而是传递一个方法,您可以通过以下方式更新任何项目字段:
fiddle: https://jsfiddle.net/thebigsurf/0chtzwjd/2/
var entriesComponent = Vue.component('entries', {
template: '#entries',
props: {
item: Object,
itemKey: String,
selected: Boolean,
updateSelected: Function,
updateItem: Function,
},
data () {
return {
quantity: 0,
message: '',
}
},
created () {
this.quantity = this.item.quantity
this.message = this.item.message
},
watch: {
quantity () {
this.updateItem(this.itemKey, 'quantity', this.quantity)
},
message () {
this.updateItem(this.itemKey, 'message', this.message)
},
}
});
new Vue({
el: '#app',
data: {
allProducts: {
'foo': { shareSize: 'Small', quantity: '1', itemPrice: '24', message: '' },
'bar': { shareSize: 'Medium', quantity: '1', itemPrice: '35', message: '' },
'baz': { shareSize: 'Large', quantity: '1', itemPrice: '46', message: 'hello' },
'bop': { shareSize: 'Large', quantity: '1', itemPrice: '46', message: '' },
},
items: [ 'foo', 'bar', 'baz' ],
addons: [ 'bop' ],
selected: {},
paymentConfig: {
advance: 25,
firstweek: 25,
ondelivery: 50,
},
weeks: 12,
},
components: {
entriesComponent,
},
created () {
this.setSelectableItems()
},
computed: {
totalAdvance () {
return (this.paymentConfig.advance * this.total) / 100
},
totalFirstWeek () {
return (this.paymentConfig.firstweek * this.total) / 100
},
onDeliveryPayment () {
return (this.paymentConfig.ondelivery * this.total) / (this.weeks * 100)
},
total() {
var sum = 0
Object.keys(this.selected)
.forEach((productKey) => {
if (this.selected[productKey]) {
sum +=
this.weeks *
this.allProducts[productKey].itemPrice *
this.allProducts[productKey].quantity
}
})
return sum
},
},
methods: {
setSelectableItems () {
this.items
.forEach((productKey) => {
Vue.set(this.selected, productKey, false)
})
this.addons
.forEach((productKey) => {
Vue.set(this.selected, productKey, false)
})
},
setSelected (productKey, value) {
this.selected[productKey] = value
},
syncItem (key, field, value) {
this.allProducts[key][field] = value
},
},
});
.row {
background: #f1f1f1;
padding: 25px;
margin-top: 10px;
}
.row:nth-child(even) {
background: #f9f9f9;
}
.item {
background: #dcdcdc;
border: 1px solid #a2a2a2;
padding: 10px;
margin-top: 10px;
}
.item span {
margin-left: 10px;
}
.item input {
display: inline-block;
}
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<div class="row">
<label class="control-label required">Items</label>
<entries
v-for="productKey in items"
:update-item="syncItem"
:item="allProducts[productKey]"
:item-key="productKey"
:update-selected="setSelected"
:selected="selected[productKey]"></entries>
</div>
<div class="row">
<label class="control-label required">Addons</label>
<entries
v-for="productKey in addons"
:update-item="syncItem"
:item="allProducts[productKey]"
:item-key="productKey"
:update-selected="setSelected"
:selected="selected[productKey]"></entries>
</div>
<div class="row">
<label>Summary</label>
<div class="item" v-for="(value, productKey) in selected" v-if="value">
<span>{{ allProducts[productKey].shareSize }}</span>
<span>
{{ allProducts[productKey].quantity }}
x $
{{ (allProducts[productKey].itemPrice * allProducts[productKey].quantity).toFixed(2)}}
</span>
<span>{{ allProducts[productKey].message }}</span>
</div>
</div>
<div class="row">
<label>Payment plan</label>
<p>{{ '$ ' + totalAdvance.toFixed(2) }} - advance</p>
<p>{{ '$ ' + totalFirstWeek.toFixed(2) }} - first week</p>
<p>{{ '$ ' + onDeliveryPayment.toFixed(2) }}/ week on each of the {{ weeks }} weeks of the subscription</p>
</div>
<div class="row">
<p>
<span>Total</span>
<span>{{ '$ ' + total.toFixed(2) }}</span>
</p>
<p>
<span>Total due now</span>
<span>{{ '$ ' + totalAdvance.toFixed(2) }}</span>
</p>
</div>
</div>
<!-- component template -->
<template id="entries">
<div class="item">
<input
type="checkbox"
v-bind:value="selected"
@change="updateSelected(itemKey, $event.target.checked)">
<span>size: {{item.shareSize}}</span>
<span>price: {{'$ ' + Number(item.itemPrice).toFixed(2) }}</span>
<input type="number" v-model="quantity" />
<input type="text" v-model="message" />
</div>
</template>