Vue2+Vuex:如何用计算属性更新Store中的商品价格
问题描述
我在一个简易测试电商项目中使用Vue2与Vuex框架,商品默认价格为0美元。在产品详情页(Show.vue)中,我通过计算属性calculatedPrice根据用户选择的尺寸参数计算商品价格,计算结果可正常展示,但无法更新Vuex Store中对应的product.price字段值,导致点击添加到购物车时,商品仍以默认0美元的价格加入。
相关代码
app.js
require('./bootstrap'); import Alpine from 'alpinejs'; window.Alpine = Alpine; Alpine.start(); import Vue from "vue"; import VuexPersist from 'vuex-persist'; import Vuex from "vuex"; Vue.use(Vuex); import VueRouter from "vue-router"; Vue.use(VueRouter); const router = new VueRouter({ mode: 'history', routes: require('./routes.js') }); const vuexLocalStorage = new VuexPersist({ key: 'vuex', // The key to store the state on in the storage provider. storage: window.sessionStorage, // or window.sessionStorage or localForage // Function that passes the state and returns the state with only the objects you want to store. // reducer: state => state, // Function that passes a mutation and lets you decide if it should update the state in localStorage. // filter: mutation => (true) }) const store = new Vuex.Store({ state: { products: [], cart: [], order: {} }, plugins: [vuexLocalStorage.plugin], mutations: { updateProducts(state, products) { state.products = products; }, addToCart(state, product) { let productInCartIndex = state.cart.findIndex(item => item.slug === product.slug); if (productInCartIndex !== -1) { state.cart[productInCartIndex].quantity++; return; } product.quantity = 1; state.cart.push(product); }, removeFromCart(state, index) { state.cart.splice(index, 1); }, updateOrder(state, order) { state.order = order; }, updateCart(state, cart) { state.cart = cart; } }, actions: { getCategories({commit}) { // fetch the categories and attached products from the api axios.get('/api/products') .then((response) => { commit('updateProducts', response.data); }) .catch((error) => console.error(error)); }, clearCart({commit}) { commit('updateCart', []); } } }); const app = new Vue({ router, store, el: '#app', created() { store.dispatch('getCategories') .then(_ => {}) .catch((error) => console.error(error)); } });
Show.vue
<template> ... <div class="mt-3"> <h2 class="sr-only">Product information</h2> <p v-text="formatCurrency(product.price)" class="text-3xl text-gray-900"></p> </div> ... <div class="mt-6"> <h3>Size</h3> <div class="grid grid-cols-4 gap-4"> <div class="mt-4 sm:mt-0 sm:pr-9"> <label for="width" class="block text-sm font-medium text-gray-700">Outer Width:</label> <select v-model="width" id="width" name="width" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md"> <option value="3">3"</option> <option value="4">4"</option> <option value="5">5"</option> </select> </div> <div class="mt-4 sm:mt-0 sm:pr-9"> <label for="height" class="block text-sm font-medium text-gray-700">Outer Height:</label> <select v-model="height" id="height" name="height" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md"> <option value="3">3"</option> <option value="4">4"</option> <option value="5">5"</option> </select> </div> <div class="mt-4 sm:mt-0 sm:pr-9"> <label for="length" class="block text-sm font-medium text-gray-700">Length:</label> <select v-model="length" id="length" name="length" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md"> <option value="10">10'</option> <option value="11">11'</option> <option value="12">12'</option> </select> </div> </div> </div> ... </template> <script> export default { methods: { formatCurrency(amount) { amount = (amount / 100); return amount.toLocaleString('en-US', {style: 'currency', currency: 'USD'}); }, }, computed: { products() { return this.$store.state.products; }, product() { return this.products.find(product => product.slug === this.$route.params.slug); }, calculatedPrice() { // I need to update product.price in store with this value return ((((this.width + (this.height * 2)) * 12) / 144) * 25) * this.length; }, }, data() { return { height: '4', width: '4', length: '10' } } } </script>
解决方案
方案一:添加购物车时传入计算后的价格(推荐)
不需要修改Vuex中products数组里的原始商品数据,而是在调用addToCart时,将计算后的价格和商品信息一起传入,既保证购物车价格正确,又不影响其他页面的商品展示。
步骤1:修改Show.vue的添加购物车逻辑
首先给添加按钮绑定点击事件:
<button @click="addToCartWithCalculatedPrice">添加到购物车</button>
然后在methods中添加对应的方法:
addToCartWithCalculatedPrice() { // 复制原始商品对象,避免直接修改store中的数据 const productToAdd = {...this.product}; // 转成分单位,匹配formatCurrency的处理逻辑 productToAdd.price = this.calculatedPrice * 100; this.$store.commit('addToCart', productToAdd); }
最后更新页面价格展示,直接使用计算后的价格:
<p v-text="formatCurrency(calculatedPrice * 100)" class="text-3xl text-gray-900"></p>
方案二:通过Vuex mutation实时更新商品价格
如果需要全局同步商品价格,可以添加新的mutation来更新store中的商品价格:
步骤1:在app.js的mutations中新增updateProductPrice
mutations: { // ...其他已有的mutation updateProductPrice(state, {slug, price}) { const targetProduct = state.products.find(item => item.slug === slug); if (targetProduct) { targetProduct.price = price; } } }
步骤2:在Show.vue中监听尺寸变化并提交mutation
添加watch监听尺寸参数,当用户选择不同尺寸时自动更新价格:
watch: { width() { this.syncProductPrice(); }, height() { this.syncProductPrice(); }, length() { this.syncProductPrice(); } }, methods: { // ...其他已有的方法 syncProductPrice() { const finalPrice = this.calculatedPrice * 100; this.$store.commit('updateProductPrice', { slug: this.product.slug, price: finalPrice }); } }
这样用户切换尺寸时,store中的商品价格会实时更新,添加到购物车时就会使用正确的价格。
注意事项
- 方案一更适合多用户场景,不会修改全局商品数据;方案二会同步更新所有页面的该商品价格,需根据业务需求选择。
- 注意价格单位统一:
formatCurrency方法是将金额除以100(分转美元),所以计算后的价格要乘以100转成分单位,避免显示错误。
内容的提问来源于stack exchange,提问作者Charles Smith
相关产品推荐
相关产品推荐

