前言
Vue.js 是一个用于构建用户界面的渐进式 JavaScript 框架。它的设计目标是通过采用易于上手的结构和强大的功能,使前端开发变得更加简便和高效。以下是 Vue.js 的一些关键特性和优点:
核心特性
-
声明式渲染
- Vue.js 使用声明式语法来描述用户界面,数据和视图是双向绑定的。当数据变化时,视图会自动更新。
-
组件系统
- Vue.js 提供了一个灵活的组件系统,允许开发者将 UI 分解成可复用的组件,每个组件都有自己的逻辑和样式。
-
单文件组件 (SFC)
- Vue.js 允许开发者将 HTML、CSS 和 JavaScript 放在同一个
.vue
文件中,这样可以更容易地管理组件。
- Vue.js 允许开发者将 HTML、CSS 和 JavaScript 放在同一个
-
虚拟 DOM
- Vue.js 使用虚拟 DOM 来优化更新过程,通过最小化实际 DOM 操作来提高性能。
-
反应式数据绑定
- Vue.js 提供了响应式的数据绑定系统,使得数据与 DOM 之间的同步变得简单和高效。
-
指令
- Vue.js 提供了一组内置指令(如
v-bind
、v-model
和v-for
),帮助开发者轻松地操作 DOM。
- Vue.js 提供了一组内置指令(如
优点
-
易于上手
- Vue.js 的学习曲线较低,适合新手入门,并且文档详细、社区活跃。
-
灵活性
- Vue.js 可以与现有项目集成,也可以用于构建复杂的单页面应用(SPA)。
-
性能高效
- 得益于虚拟 DOM 和响应式系统,Vue.js 在处理大量数据更新时表现出色。
-
生态系统
- Vue.js 拥有丰富的生态系统,包括 Vue Router、Vuex、Vue CLI 等工具和库,支持开发者在不同场景下使用。
-
强大的社区支持
- Vue.js 有一个全球活跃的社区,提供了大量的插件、教程和支持资源。
知识点
1. v-if v-else 条件渲染,如果没有数据则显示空车
2. v-for 循环遍历渲染数组
3. v-bind 绑定 class 设置条件
4. v-model 绑定属性
5. v-bind 绑定 src
6. 插值语法
7. @click 点击事件实现数量加减
8. filter() 方法更新数组达到删除元素的效果
9. find() 方法通过 id 找到对应的水果
10. :disabled 动态属性绑定限制用户
11. 计算属性完整写法实现全选的两个功能
12. every() 方法检查每一个元素
13. foreach() 方法设置每一个元素
14. some() 方法实现如果有任意一个按钮不是 true,则全选,反之取消所有选择
15. reduce() 方法计算总价与数量
16. 判断条件实现没有选中的则不添加
项目效果
初始页面
添加水果
全选按钮
删除水果
本地缓存
源代码
index.css
.app-container {
padding-bottom: 300px;
width: 800px;
margin: 0 auto;
}
@media screen and (max-width: 800px) {
.app-container {
width: 600px;
}
}
.app-container .banner-box {
border-radius: 20px;
overflow: hidden;
margin-bottom: 10px;
}
.app-container .banner-box img {
width: 100%;
}
.app-container .nav-box {
background: #ddedec;
height: 60px;
border-radius: 10px;
padding-left: 20px;
display: flex;
align-items: center;
}
.app-container .nav-box .my-nav {
display: inline-block;
background: #5fca71;
border-radius: 5px;
width: 90px;
height: 35px;
color: white;
text-align: center;
line-height: 35px;
margin-right: 10px;
}
.breadcrumb {
font-size: 16px;
color: gray;
}
.table {
width: 100%;
text-align: left;
border-radius: 2px 2px 0 0;
border-collapse: separate;
border-spacing: 0;
}
.th {
color: rgba(0, 0, 0, 0.85);
font-weight: 500;
text-align: left;
background: #fafafa;
border-bottom: 1px solid #f0f0f0;
transition: background 0.3s ease;
}
.th.num-th {
flex: 1.5;
}
.th {
text-align: center;
}
.th:nth-child(4),
.th:nth-child(5),
.th:nth-child(6),
.th:nth-child(7) {
text-align: center;
}
.th.th-pic {
flex: 1.3;
}
.th:nth-child(6) {
flex: 1.3;
}
.th,
.td {
position: relative;
padding: 16px 16px;
overflow-wrap: break-word;
flex: 1;
}
.pick-td {
font-size: 14px;
}
.main,
.empty {
border: 1px solid #f0f0f0;
margin-top: 10px;
}
.tr {
display: flex;
cursor: pointer;
border-bottom: 1px solid #ebeef5;
}
.tr.active {
background-color: #f5f7fa;
}
.td {
display: flex;
justify-content: center;
align-items: center;
}
.table img {
width: 100px;
height: 100px;
}
button {
outline: 0;
box-shadow: none;
color: #fff;
background: #d9363e;
border-color: #d9363e;
color: #fff;
background: #d9363e;
border-color: #d9363e;
line-height: 1.5715;
position: relative;
display: inline-block;
font-weight: 400;
white-space: nowrap;
text-align: center;
background-image: none;
border: 1px solid transparent;
box-shadow: 0 2px 0 rgb(0 0 0 / 2%);
cursor: pointer;
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
touch-action: manipulation;
height: 32px;
padding: 4px 15px;
font-size: 14px;
border-radius: 2px;
}
button.pay {
background-color: #3f85ed;
margin-left: 20px;
}
.bottom {
height: 60px;
display: flex;
align-items: center;
justify-content: space-between;
padding-right: 20px;
border: 1px solid #f0f0f0;
border-top: none;
padding-left: 20px;
}
.right-box {
display: flex;
align-items: center;
}
.check-all {
cursor: pointer;
}
.price {
color: hotpink;
font-size: 30px;
font-weight: 700;
}
.price-box {
display: flex;
align-items: center;
}
.empty {
padding: 20px;
text-align: center;
font-size: 30px;
color: #909399;
}
.my-input-number {
display: flex;
}
.my-input-number button {
height: 40px;
color: #333;
border: 1px solid #dcdfe6;
background-color: #f5f7fa;
}
.my-input-number button:disabled {
cursor: not-allowed!important;
}
.my-input-number .my-input__inner {
height: 40px;
width: 50px;
padding: 0;
border: none;
border-top: 1px solid #dcdfe6;
border-bottom: 1px solid #dcdfe6;
inputnumber.css
.my-input-number {
position: relative;
display: inline-block;
width: 140px;
line-height: 38px;
}
.my-input-number span {
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}
.my-input-number .my-input {
display: block;
position: relative;
font-size: 14px;
width: 100%;
}
.my-input-number .my-input__inner {
-webkit-appearance: none;
background-color: #fff;
background-image: none;
border-radius: 4px;
border: 1px solid #dcdfe6;
box-sizing: border-box;
color: #606266;
display: inline-block;
font-size: inherit;
height: 40px;
line-height: 40px;
outline: none;
padding: 0 15px;
transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
width: 100%;
padding-left: 50px;
padding-right: 50px;
text-align: center;
}
.my-input-number .my-input-number__decrease,
.my-input-number .my-input-number__increase {
position: absolute;
z-index: 1;
top: 1px;
width: 40px;
height: auto;
text-align: center;
background: #f5f7fa;
color: #606266;
cursor: pointer;
font-size: 13px;
}
.my-input-number .my-input-number__decrease {
left: 1px;
border-radius: 4px 0 0 4px;
border-right: 1px solid #dcdfe6;
}
.my-input-number .my-input-number__increase {
right: 1px;
border-radius: 0 4px 4px 0;
border-left: 1px solid #dcdfe6;
}
.my-input-number .my-input-number__decrease.is-disabled,
.my-input-number .my-input-number__increase.is-disabled {
color: #c0c4cc;
cursor: not-allowed;
}
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./css/inputnumber.css" />
<link rel="stylesheet" href="./css/index.css" />
<title>购物车</title>
</head>
<body>
<div class="app-container" id="app">
<!-- 顶部banner -->
<div class="banner-box"><img src="./img/fruit.jpg" alt="Fruit"></div>
<!-- 面包屑 -->
<div class="breadcrumb">
<span>🏠</span>
/
<span>购物车</span>
</div>
<!-- 购物车主体 -->
<!-- v-if v-else 条件渲染,如果没有数据则显示空车 -->
<div v-if="fruitList.length > 0" class="main">
<div class="table">
<!-- 头部 -->
<div class="thead">
<div class="tr">
<div class="th">选中</div>
<div class="th th-pic">图片</div>
<div class="th">单价</div>
<div class="th num-th">个数</div>
<div class="th">小计</div>
<div class="th">操作</div>
</div>
</div>
<!-- 身体 -->
<!-- v-for 循环遍历渲染数组 -->
<div v-for="(item, index) in fruitList" :key="item.id" class="tbody">
<!-- v-bind 绑定 class 设置条件-->
<div class="tr" :class="{ active: item.isChecked }">
<!-- v-model 绑定属性 -->
<div class="td"><input type="checkbox" v-model="item.isChecked" /></div>
<!-- v-bind 绑定 src -->
<div class="td"><img :src="item.icon" alt="" /></div>
<div class="td">{{ item.price }}</div>
<div class="td">
<div class="my-input-number">
<!-- @click 点击事件实现数量加减 -->
<!-- :disabled 是一个动态属性绑定,它允许你根据表达式的结果动态地设置 HTML 元素的 disabled 属性 -->
<button :disabled="item.num <= 1" class="decrease" @click="item.num--"> - </button>
<span class="my-input__inner">{{ item.num }}</span>
<button class="increase" @click="item.num++"> + </button>
</div>
</div>
<div class="td">{{ item.price * item.num }}</div>
<div @click="del(item.id)" class="td"><button>删除</button></div>
</div>
</div>
</div>
<!-- 底部 -->
<div class="bottom">
<!-- 全选 -->
<label class="check-all">
<!-- v-model 绑定计算属性实现全选功能 -->
<input type="checkbox" v-model="isAll" @click=""/>
全选
</label>
<div class="right-box">
<!-- 所有商品总价 -->
<span class="price-box">总价 : ¥ <span class="price">{{ totalPrice }}</span></span>
<!-- 结算按钮 -->
<button class="pay">结算{{ totalCount }}</button>
</div>
</div>
</div>
<!-- 空车 -->
<div v-else class="empty">🛒空空如也</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
// 初始页面给出默认数组
const defultArr = [
{
id: 1,
icon: './img/火龙果.png',
isChecked: true,
num: 2,
price: 6,
},
{
id: 2,
icon: './img/荔枝.png',
isChecked: false,
num: 7,
price: 20,
},
{
id: 3,
icon: './img/榴莲.png',
isChecked: false,
num: 3,
price: 40,
},
{
id: 4,
icon: './img/鸭梨.png',
isChecked: true,
num: 10,
price: 3,
},
{
id: 5,
icon: './img/樱桃.png',
isChecked: false,
num: 20,
price: 34,
},
]
const app = new Vue({
el: '#app',
data: {
// localStorage.getItem 是 Web 存储 API 的一部分,用于从本地存储中获取键名为 'list' 的数据项
// JSON.parse 函数用于将 JSON 格式的字符串转换为 JavaScript 对象。如果从 localStorage 获取的字符串是有效的 JSON 字符串,它将被转换为对应的 JavaScript 对象
// || 是逻辑或运算符。如果 JSON.parse(localStorage.getItem('list')) 的结果为 null 或 undefined(即本地存储中没有 'list' 键或其值为 null),则使用 defaultArr 作为默认值
fruitList: JSON.parse(localStorage.getItem('list')) || defultArr ,
},
computed: {
// 默认计算属性只能获取不能设置
// 通过 every 检查每个水果的选中状态,如果都选中则全选按钮选中
// isAll () {
// return this.fruitList.every(item => item.isChecked === true)
// }
// 完整写法 get + set
isAll: {
// get 用于获取
get () {
return this.fruitList.every(item => item.isChecked === true)
},
// set 用于设置
set (value) {
// 点击按钮时先判断
// some() 方法实现如果有任意一个按钮不是 true,则全选,反之取消所有选择
if (this.fruitList.some(item => item.isChecked !== true)) {
this.fruitList.forEach(item => item.isChecked = true);
} else {
this.fruitList.forEach(item => item.isChecked = false);
}
}
},
totalCount () {
return this.fruitList.reduce((sum, item) => {
// 判断实现选中状态的才添加
if (item.isChecked === true) {
return sum + item.num
} else {
// 没选中则不干,返回 sum
return sum
}
}, 0)
},
totalPrice () {
return this.fruitList.reduce((sum, item) => {
// 判断实现选中状态的才添加
if (item.isChecked === true) {
return sum + item.price * item.num
} else {
return sum
}
}, 0)
}
},
methods: {
del (id) {
// filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素
// 这是一个箭头函数,它接收数组中的每个元素作为参数(在这里是 item)。
// item.id !== id:这是一个比较操作,检查 item 的 id 属性是否不等于传递给 filter 方法的 id 参数。如果不等于,返回 true;如果等于,返回 false
this.fruitList = this.fruitList.filter(item => item.id !== id)
},
},
// 使用 watch 监视数据保存到本地实现持久化
// watch 是 Vue 实例的一个选项,用于定义一个或多个观察者,观察者可以观察 Vue 实例的数据变化
watch: {
fruitList: {
deep: true,
handler (newValue) {
// 使用 $nextTick 确保在 DOM 更新完成后保存数据
this.$nextTick(() => {
// 需要将变化后的数据(newValue)存储本地(转 JSON)
// localStorage.setItem('list', JSON.stringify(newValue)) 将 fruitList 数组的新值转换为 JSON 字符串,并存储到浏览器的本地存储中。
// localStorage 是 Web 存储 API 的一部分,允许存储键值对数据,数据在页面会话结束时会被清除
// JSON.stringify(newValue) 将 newValue(一个数组)转换为一个 JSON 格式的字符串,以便存储在本地存储中
localStorage.setItem('list', JSON.stringify(newValue));
});
}
}
}
})
</script>
</body>
</html>