文章目录
- 1. 项目介绍
- 2. 开发环境准备
- 3. 设计购物车界面
- 4. 创建Vue实例和数据模型
- 5. 实现购物车功能
- 5.1 从本地存储加载数据
- 5.2 监听数据变化保存到本地存储
- 5.3 实现全选/反选功能
- 5.4 计算选中商品的总价和总数量
- 5.5 实现修改商品数量功能
- 5.6 实现删除商品功能
- 5.7 实现结算功能
- 6. 添加样式
- 7. 完整代码
- 8. Vue知识点解析
- 8.1 数据渲染与绑定
- 8.2 条件渲染与列表渲染
- 8.3 类与样式绑定
- 8.4 事件处理
- 8.5 计算属性
- 8.6 侦听器
- 8.7 生命周期钩子
- 8.8 属性绑定
- 9. 功能扩展思路
- 10. 总结
1. 项目介绍
本教程将带领初学者开发一个基于Vue.js的购物车应用,不需要使用脚手架,仅通过引入Vue.js库即可完成。通过这个项目,你将学习Vue的基础知识,包括:
- 数据渲染与绑定
- 事件处理
- 计算属性
- 侦听器
- 条件渲染与列表渲染
- 本地存储
完成后的购物车应用具有以下功能:
- 商品列表渲染
- 删除商品
- 修改商品数量
- 全选/反选功能
- 统计选中商品的总价和总数量
- 数据持久化到本地存储
2. 开发环境准备
对于初学者,我们采用最简单的方式搭建环境:通过CDN引入Vue.js。
创建一个基本的HTML文件结构:
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Vue购物车</title><!-- 引入Vue.js --><script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script><!-- 添加一些基本样式 --><style>/* 样式稍后添加 */</style>
</head>
<body><div id="app"><!-- 这里将是我们的应用 --></div><script>// Vue代码将写在这里</script>
</body>
</html>
3. 设计购物车界面
我们设计一个简洁的购物车界面,包含以下部分:
- 标题
- 商品列表(每项包含复选框、图片、名称、单价、数量控制和删除按钮)
- 底部结算栏(全选按钮、总计和结算按钮)
更新HTML结构:
<div id="app"><div class="cart"><div class="cart-header"><h1>{{ title }}</h1></div><!-- 购物车为空时显示 --><div class="empty-cart" v-if="cartItems.length === 0"><p>购物车空空如也,快去选购商品吧!</p></div><!-- 购物车商品列表 --><div class="cart-items" v-else><div class="cart-item" v-for="(item, index) in cartItems" :key="item.id" :class="{'selected-item': item.checked}"><div class="item-check"><input type="checkbox" v-model="item.checked"></div><div class="item-img"><img :src="item.img" :alt="item.name"></div><div class="item-info"><h3>{{ item.name }}</h3><p class="item-price">¥{{ item.price.toFixed(2) }}</p></div><div class="item-quantity"><button @click="decreaseQuantity(item.id)" :disabled="item.quantity <= 1">-</button><input type="number" v-model.number="item.quantity" min="1"><button @click="increaseQuantity(item.id)">+</button></div><div class="item-subtotal"><p>¥{{ (item.price * item.quantity).toFixed(2) }}</p></div><div class="item-remove"><button @click="removeItem(item.id)">删除</button></div></div></div><!-- 购物车底部结算栏 --><div class="cart-footer" v-if="cartItems.length > 0"><div class="select-all"><input type="checkbox" v-model="selectAll"><label>全选</label></div><div class="cart-total"><p>已选择 <span>{{ selectedCount }}</span> 件商品</p><p>总计: <span>¥{{ totalPrice }}</span></p></div><div class="checkout"><button @click="checkout">结算</button></div></div></div>
</div>
4. 创建Vue实例和数据模型
在script标签中,我们创建Vue实例并定义数据模型:
new Vue({el: '#app',data: {title: 'Vue购物车',// 购物车商品数据cartItems: [{id: 1,name: '商品1',price: 199,quantity: 1,img: 'https://via.placeholder.com/80',checked: false},{id: 2,name: '商品2',price: 299,quantity: 2,img: 'https://via.placeholder.com/80',checked: false},{id: 3,name: '商品3',price: 399,quantity: 1,img: 'https://via.placeholder.com/80',checked: false}]},// 计算属性将在后面定义computed: {// 计算属性将在这里实现},// 方法将在后面定义methods: {// 方法将在这里实现},// 监听器将在后面定义watch: {// 监听器将在这里实现},// 生命周期钩子函数将在后面定义created() {// 从本地存储加载购物车数据}
});
5. 实现购物车功能
5.1 从本地存储加载数据
首先,我们修改生命周期钩子函数,从本地存储加载购物车数据:
created() {// 从本地存储加载购物车数据const savedCart = localStorage.getItem('vue-cart');if (savedCart) {this.cartItems = JSON.parse(savedCart);}
}
5.2 监听数据变化保存到本地存储
添加侦听器,将购物车数据保存到本地存储:
watch: {// 深度监听购物车数据变化cartItems: {handler(newValue) {// 将购物车数据保存到本地存储localStorage.setItem('vue-cart', JSON.stringify(newValue));},deep: true // 深度监听对象内部的变化}
}
5.3 实现全选/反选功能
添加计算属性和相关方法来实现全选/反选功能:
computed: {// 计算全选状态selectAll: {// 获取全选状态get() {// 如果购物车为空,返回falseif (this.cartItems.length === 0) return false;// 检查是否所有商品都被选中return this.cartItems.every(item => item.checked);},// 设置全选状态set(value) {// 将所有商品的选中状态设置为全选的状态this.cartItems.forEach(item => {item.checked = value;});}}
}
5.4 计算选中商品的总价和总数量
添加计算属性来统计选中商品的总价和总数量:
computed: {// 已定义的计算属性selectAll: {// ... 前面的代码},// 计算选中商品的总数量selectedCount() {return this.cartItems.reduce((total, item) => {return item.checked ? total + item.quantity : total;}, 0);},// 计算选中商品的总价totalPrice() {return this.cartItems.reduce((total, item) => {return item.checked ? total + (item.price * item.quantity) : total;}, 0).toFixed(2);}
}
5.5 实现修改商品数量功能
添加方法来增加和减少商品数量:
methods: {// 增加商品数量increaseQuantity(id) {const item = this.cartItems.find(item => item.id === id);if (item) {item.quantity++;}},// 减少商品数量decreaseQuantity(id) {const item = this.cartItems.find(item => item.id === id);if (item && item.quantity > 1) {item.quantity--;}}
}
5.6 实现删除商品功能
添加方法来删除购物车中的商品:
methods: {// 已定义的方法increaseQuantity(id) {// ... 前面的代码},decreaseQuantity(id) {// ... 前面的代码},// 删除商品removeItem(id) {if (confirm('确定要删除这个商品吗?')) {// 根据id查找商品索引const index = this.cartItems.findIndex(item => item.id === id);if (index !== -1) {// 从数组中删除商品this.cartItems.splice(index, 1);}}}
}
5.7 实现结算功能
添加一个简单的结算方法:
methods: {// 已定义的方法// ... 前面的代码// 结算方法checkout() {// 检查是否有选中的商品if (this.selectedCount === 0) {alert('请至少选择一件商品');return;}// 显示结算信息alert(`您已选择${this.selectedCount}件商品,总计:¥${this.totalPrice},感谢购买!`);// 实际应用中这里应该跳转到结算页面或发送请求到后端// 为了演示,我们仅移除已选中的商品this.cartItems = this.cartItems.filter(item => !item.checked);}
}
6. 添加样式
为了让购物车看起来更美观,我们添加一些CSS样式:
/* 将这些样式添加到<style>标签中 */
* {margin: 0;padding: 0;box-sizing: border-box;
}body {font-family: 'Microsoft YaHei', sans-serif;background-color: #f5f5f5;padding: 20px;
}.cart {max-width: 1000px;margin: 0 auto;background-color: white;border-radius: 8px;box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);overflow: hidden;
}.cart-header {padding: 20px;background-color: #f8f8f8;border-bottom: 1px solid #eee;
}.cart-header h1 {font-size: 24px;color: #333;text-align: center;
}.empty-cart {padding: 50px 20px;text-align: center;color: #999;
}.cart-items {padding: 0 20px;
}.cart-item {display: flex;align-items: center;padding: 20px 0;border-bottom: 1px solid #eee;
}.cart-item:last-child {border-bottom: none;
}.selected-item {background-color: rgba(135, 206, 250, 0.1);
}.item-check {width: 30px;
}.item-check input {width: 18px;height: 18px;
}.item-img {width: 80px;height: 80px;margin: 0 20px;
}.item-img img {width: 100%;height: 100%;object-fit: cover;border-radius: 4px;
}.item-info {flex: 1;padding-right: 20px;
}.item-info h3 {font-size: 16px;margin-bottom: 5px;color: #333;
}.item-price {color: #ff6700;font-weight: bold;
}.item-quantity {display: flex;align-items: center;margin: 0 20px;
}.item-quantity button {width: 30px;height: 30px;border: 1px solid #ddd;background-color: white;font-size: 16px;cursor: pointer;
}.item-quantity button:disabled {color: #ddd;cursor: not-allowed;
}.item-quantity input {width: 50px;height: 30px;border: 1px solid #ddd;border-left: none;border-right: none;text-align: center;font-size: 14px;
}.item-subtotal {width: 100px;text-align: right;font-weight: bold;color: #ff6700;
}.item-remove {width: 60px;text-align: center;
}.item-remove button {padding: 5px 10px;background-color: #ff6700;color: white;border: none;border-radius: 4px;cursor: pointer;
}.cart-footer {display: flex;justify-content: space-between;align-items: center;padding: 20px;background-color: #f8f8f8;border-top: 1px solid #eee;
}.select-all {display: flex;align-items: center;
}.select-all input {width: 18px;height: 18px;margin-right: 5px;
}.cart-total {flex: 1;text-align: right;padding-right: 20px;
}.cart-total p {margin-bottom: 5px;
}.cart-total span {color: #ff6700;font-weight: bold;font-size: 18px;
}.checkout button {padding: 10px 30px;background-color: #ff6700;color: white;border: none;border-radius: 4px;font-size: 16px;cursor: pointer;
}.checkout button:hover {background-color: #f25600;
}button:hover {opacity: 0.9;
}button:active {opacity: 0.8;
}/* 响应式布局 */
@media (max-width: 768px) {.cart-item {flex-wrap: wrap;}.item-info {width: 100%;padding: 10px 0;}.item-quantity, .item-subtotal, .item-remove {margin-top: 10px;}
}
7. 完整代码
以下是完整的HTML文件代码:
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Vue购物车</title><script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script><style>* {margin: 0;padding: 0;box-sizing: border-box;}body {font-family: 'Microsoft YaHei', sans-serif;background-color: #f5f5f5;padding: 20px;}.cart {max-width: 1000px;margin: 0 auto;background-color: white;border-radius: 8px;box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);overflow: hidden;}.cart-header {padding: 20px;background-color: #f8f8f8;border-bottom: 1px solid #eee;}.cart-header h1 {font-size: 24px;color: #333;text-align: center;}.empty-cart {padding: 50px 20px;text-align: center;color: #999;}.cart-items {padding: 0 20px;}.cart-item {display: flex;align-items: center;padding: 20px 0;border-bottom: 1px solid #eee;}.cart-item:last-child {border-bottom: none;}.selected-item {background-color: rgba(135, 206, 250, 0.1);}.item-check {width: 30px;}.item-check input {width: 18px;height: 18px;}.item-img {width: 80px;height: 80px;margin: 0 20px;}.item-img img {width: 100%;height: 100%;object-fit: cover;border-radius: 4px;}.item-info {flex: 1;padding-right: 20px;}.item-info h3 {font-size: 16px;margin-bottom: 5px;color: #333;}.item-price {color: #ff6700;font-weight: bold;}.item-quantity {display: flex;align-items: center;margin: 0 20px;}.item-quantity button {width: 30px;height: 30px;border: 1px solid #ddd;background-color: white;font-size: 16px;cursor: pointer;}.item-quantity button:disabled {color: #ddd;cursor: not-allowed;}.item-quantity input {width: 50px;height: 30px;border: 1px solid #ddd;border-left: none;border-right: none;text-align: center;font-size: 14px;}.item-subtotal {width: 100px;text-align: right;font-weight: bold;color: #ff6700;}.item-remove {width: 60px;text-align: center;}.item-remove button {padding: 5px 10px;background-color: #ff6700;color: white;border: none;border-radius: 4px;cursor: pointer;}.cart-footer {display: flex;justify-content: space-between;align-items: center;padding: 20px;background-color: #f8f8f8;border-top: 1px solid #eee;}.select-all {display: flex;align-items: center;}.select-all input {width: 18px;height: 18px;margin-right: 5px;}.cart-total {flex: 1;text-align: right;padding-right: 20px;}.cart-total p {margin-bottom: 5px;}.cart-total span {color: #ff6700;font-weight: bold;font-size: 18px;}.checkout button {padding: 10px 30px;background-color: #ff6700;color: white;border: none;border-radius: 4px;font-size: 16px;cursor: pointer;}.checkout button:hover {background-color: #f25600;}button:hover {opacity: 0.9;}button:active {opacity: 0.8;}/* 响应式布局 */@media (max-width: 768px) {.cart-item {flex-wrap: wrap;}.item-info {width: 100%;padding: 10px 0;}.item-quantity, .item-subtotal, .item-remove {margin-top: 10px;}}</style>
</head>
<body><div id="app"><div class="cart"><div class="cart-header"><h1>{{ title }}</h1></div><!-- 购物车为空时显示 --><div class="empty-cart" v-if="cartItems.length === 0"><p>购物车空空如也,快去选购商品吧!</p></div><!-- 购物车商品列表 --><div class="cart-items" v-else><div class="cart-item" v-for="(item, index) in cartItems" :key="item.id" :class="{'selected-item': item.checked}"><div class="item-check"><input type="checkbox" v-model="item.checked"></div><div class="item-img"><img :src="item.img" :alt="item.name"></div><div class="item-info"><h3>{{ item.name }}</h3><p class="item-price">¥{{ item.price.toFixed(2) }}</p></div><div class="item-quantity"><button @click="decreaseQuantity(item.id)" :disabled="item.quantity <= 1">-</button><input type="number" v-model.number="item.quantity" min="1"><button @click="increaseQuantity(item.id)">+</button></div><div class="item-subtotal"><p>¥{{ (item.price * item.quantity).toFixed(2) }}</p></div><div class="item-remove"><button @click="removeItem(item.id)">删除</button></div></div></div><!-- 购物车底部结算栏 --><div class="cart-footer" v-if="cartItems.length > 0"><div class="select-all"><input type="checkbox" v-model="selectAll"><label>全选</label></div><div class="cart-total"><p>已选择 <span>{{ selectedCount }}</span> 件商品</p><p>总计: <span>¥{{ totalPrice }}</span></p></div><div class="checkout"><button @click="checkout">结算</button></div></div></div></div><script>new Vue({el: '#app',data: {title: 'Vue购物车',// 购物车商品数据cartItems: [{id: 1,name: '商品1',price: 199,quantity: 1,img: 'https://via.placeholder.com/80',checked: false},{id: 2,name: '商品2',price: 299,quantity: 2,img: 'https://via.placeholder.com/80',checked: false},{id: 3,name: '商品3',price: 399,quantity: 1,img: 'https://via.placeholder.com/80',checked: false}]},computed: {// 计算全选状态selectAll: {// 获取全选状态get() {// 如果购物车为空,返回falseif (this.cartItems.length === 0) return false;// 检查是否所有商品都被选中return this.cartItems.every(item => item.checked);},// 设置全选状态set(value) {// 将所有商品的选中状态设置为全选的状态this.cartItems.forEach(item => {item.checked = value;});}},// 计算选中商品的总数量selectedCount() {return this.cartItems.reduce((total, item) => {return item.checked ? total + item.quantity : total;}, 0);},// 计算选中商品的总价totalPrice() {return this.cartItems.reduce((total, item) => {return item.checked ? total + (item.price * item.quantity) : total;}, 0).toFixed(2);}},methods: {// 增加商品数量increaseQuantity(id) {const item = this.cartItems.find(item => item.id === id);if (item) {item.quantity++;}},// 减少商品数量decreaseQuantity(id) {const item = this.cartItems.find(item => item.id === id);if (item && item.quantity > 1) {item.quantity--;}},// 删除商品removeItem(id) {if (confirm('确定要删除这个商品吗?')) {// 根据id查找商品索引const index = this.cartItems.findIndex(item => item.id === id);if (index !== -1) {// 从数组中删除商品this.cartItems.splice(index, 1);}}},// 结算方法checkout() {// 检查是否有选中的商品if (this.selectedCount === 0) {alert('请至少选择一件商品');return;}// 显示结算信息alert(`您已选择${this.selectedCount}件商品,总计:¥${this.totalPrice},感谢购买!`);// 实际应用中这里应该跳转到结算页面或发送请求到后端// 为了演示,我们仅移除已选中的商品this.cartItems = this.cartItems.filter(item => !item.checked);}},watch: {// 深度监听购物车数据变化cartItems: {handler(newValue) {// 将购物车数据保存到本地存储localStorage.setItem('vue-cart', JSON.stringify(newValue));},deep: true // 深度监听对象内部的变化}},created() {// 从本地存储加载购物车数据const savedCart = localStorage.getItem('vue-cart');if (savedCart) {this.cartItems = JSON.parse(savedCart);}}});</script>
</body>
</html>
8. Vue知识点解析
通过这个项目,我们学习了以下Vue的知识点:
8.1 数据渲染与绑定
- 插值表达式
{{ }}
:显示变量内容 v-model
:双向数据绑定,用于表单输入和应用数据之间的绑定v-model.number
:自动将输入值转换为数字类型
8.2 条件渲染与列表渲染
v-if
/v-else
:根据条件决定是否渲染元素v-for
:遍历数组渲染列表项:key
:为列表项提供唯一标识符,帮助Vue优化渲染
8.3 类与样式绑定
:class
:动态绑定CSS类,根据条件添加不同的类- 在本项目中,我们根据商品是否选中来添加不同的背景样式
8.4 事件处理
@click
:监听点击事件- 事件传参:如
@click="increaseQuantity(item.id)"
,可以向事件处理方法传递参数
8.5 计算属性
computed
:基于响应式依赖进行缓存,只有依赖更新时才重新计算- 计算属性的getter和setter:如
selectAll
,可以通过getter获取值,通过setter设置值
8.6 侦听器
watch
:监听数据变化,执行相应操作deep
选项:深度监听对象内部的变化- 在本项目中,我们使用侦听器将购物车数据保存到本地存储
8.7 生命周期钩子
created
:实例创建后执行代码- 在本项目中,我们在
created
钩子中从本地存储加载购物车数据
8.8 属性绑定
:src
、:alt
、:class
、:disabled
等:动态绑定DOM元素的属性
9. 功能扩展思路
以下是一些可以进一步扩展购物车功能的思路:
-
商品分类:按照商品分类进行分组显示。
-
商品筛选:添加筛选功能,如按价格筛选、按品牌筛选等。
-
优惠券功能:添加优惠券选择和应用功能。
-
库存检查:添加库存数量限制,防止超出库存购买。
-
收藏功能:允许用户将商品移动到收藏夹。
-
推荐商品:在购物车底部显示相关推荐商品。
-
批量操作:支持选中多个商品后批量删除。
-
价格变动提醒:当商品价格变动时提醒用户。
-
商品详情链接:点击商品名称可以跳转到商品详情页。
-
登录注册功能:添加用户登录注册功能,将购物车与用户账号关联。
10. 总结
通过本教程,我们使用Vue.js构建了一个功能完整的购物车应用。虽然这是一个小型应用,但它涵盖了Vue的核心概念和实践技巧。作为新手,你可以通过这个项目了解Vue的数据驱动和响应式特性,以及如何使用计算属性、方法和侦听器处理数据和用户交互。
重点是,我们详细介绍了以下关键功能的实现:
-
渲染功能:使用
v-for
高效渲染商品列表,通过:class
动态绑定样式,使选中的商品具有不同的背景色。 -
删除功能:使用
@click
绑定删除事件,获取当前行的id,使用splice
方法删除数组中的元素。 -
修改数量:使用加减按钮和输入框控制商品数量,通过
v-model.number
确保输入的是数字类型。 -
全选/反选:通过计算属性的getter和setter实现全选/反选功能,使用
every
方法检查是否所有商品都被选中。 -
统计功能:使用计算属性自动计算选中商品的总数量和总价,保持数据的响应式更新。
-
本地存储:使用侦听器监听购物车数据变化,自动保存到本地存储,确保刷新页面后数据不丢失。
希望这个教程能帮助你更好地理解Vue的基础概念和应用开发流程。随着你对Vue的深入学习,可以考虑使用Vue CLI、Vue Router、Vuex等进阶技术来构建更复杂的应用。