一、案例
现在我们来做一个相对综合一点的练习:书籍购物车
案例说明:
- 1.在界面上以表格的形式,显示一些书籍的数据;
- 2.在底部显示书籍的总价格;
- 3.点击+或者-可以增加或减少书籍数量(如果为1,那么不能继续-);
- 4.点击移除按钮,可以将书籍移除(当所有的书籍移除完毕时,显示:购物车为空~);
我们看下下面代码 目前我们只是实现了 1 2 部分。
<!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" />
<title>Document</title>
<style>
table {
border-collapse: collapse;
text-align: center;
}
thead {
background-color: #f5f5f5;
}
th,
td {
border: 1px solid #aaa;
padding: 8px 16px;
}
</style>
</head>
<body>
<div id="app">
<!-- 1. 搭建界面内容 -->
<table>
<thead>
<tr>
<th>序号</th>
<th>书籍名称</th>
<th>出版日期</th>
<th>价格</th>
<th>购买数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- 如果key绑定的值是一个变量,需要使用冒号来进行绑定, 否则Vue会将该值作为字符串进行处理,从而无法实现正确的渲染和响应式。 -->
<tr v-for="(item, index) in books" :key="item.id">
<td>{{ index + 1 }}</td>
<td>{{ item.name }}</td>
<td>{{ item.date }}</td>
<td>{{ formatPrice(item.price) }}</td>
<td>
<button @click="decrement">-</button>
{{ item.count }}
<button @click="increment">+</button>
</td>
<td><button>移除</button></td>
</tr>
</tbody>
</table>
<h2>总价: {{ formatPrice(totalPrice) }}</h2>
</div>
<script src="../lib/vue.js"></script>
<script src="./data/data.js"></script>
<script>
const app = Vue.createApp({
// data: option api
data() {
return {
books: books,
}
},
computed: {
totalPrice() {
// 1. 直接遍历books
// let price = 0
// for (const item of this.books) {
// price += item.price * item.count
// }
// return price
// 2. reduce(后面再用这种方法)
return this.books.reduce((preValue, item) => {
return preValue + item.price * item.count
}, 0)
},
},
methods: {
formatPrice(price) {
return "¥" + price
},
decrement() {
console.log("点击-")
},
increment() {
console.log("点击+")
},
},
})
app.mount("#app")
</script>
</body>
</html>
(1) 问题1:我每一行都有购买数量的加减号,当我点加减号按钮的时候,我怎么获取我想要加的具体是哪一本书呢?我点击移除我怎么区分出来我想移除的是哪一本书呢?
思路:我只要能拿到我点击的这本书的索引我就能区分。
那我们看下数据books的结构。 数组里面嵌套对象,所以我们在点击加减号的时候,我们把索引传入到那个函数中,那函数是不是知道是书的id了?我们通过这个索引去books中获取。
this.books[index].count--
下面附源码
<!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" />
<title>Document</title>
<style>
table {
border-collapse: collapse;
text-align: center;
}
thead {
background-color: #f5f5f5;
}
th,
td {
border: 1px solid #aaa;
padding: 8px 16px;
}
</style>
</head>
<body>
<div id="app">
<!-- 1. 搭建界面内容 -->
<table>
<thead>
<tr>
<th>序号</th>
<th>书籍名称</th>
<th>出版日期</th>
<th>价格</th>
<th>购买数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- 如果key绑定的值是一个变量,需要使用冒号来进行绑定, 否则Vue会将该值作为字符串进行处理,从而无法实现正确的渲染和响应式。 -->
<tr v-for="(item, index) in books" :key="item.id">
<td>{{ index + 1 }}</td>
<td>{{ item.name }}</td>
<td>{{ item.date }}</td>
<td>{{ formatPrice(item.price) }}</td>
<td>
<button @click="decrement(index)">-</button>
{{ item.count }}
<button @click="increment(item)">+</button>
</td>
<td><button >移除</button></td>
</tr>
</tbody>
</table>
<h2>总价: {{ formatPrice(totalPrice) }}</h2>
</div>
<script src="../lib/vue.js"></script>
<script src="./data/data.js"></script>
<script>
const app = Vue.createApp({
// data: option api
data() {
return {
books: books,
}
},
computed: {
// 计算属性会监听依赖的响应式数据,如果依赖的数据发生变化,他也会重新计算。
totalPrice() {
// 1. 直接遍历books
// let price = 0
// for (const item of this.books) {
// price += item.price * item.count
// }
// return price
// 2. reduce(后面再用这种方法)
return this.books.reduce((preValue, item) => {
return preValue + item.price * item.count
}, 0)
},
},
methods: {
formatPrice(price) {
return "¥" + price
},
decrement(index) {
this.books[index].count--
},
// 也可以传item,直接把整条数据拿过来
increment(item) {
console.log("点击+")
item.count++
},
},
})
app.mount("#app")
</script>
</body>
</html>
(2)问题2:当数量为1的时候,减号按钮➖置灰
之前我是想过button按钮的置灰不置灰是有一个display属性,我将他的属性值通过data里面一个displayed属性去绑定,发现不能这么玩。因为我改变了data里面的displayed之后,界面所有的减号全部变成了置灰,因为我绑定的是data里面的displayed。
这让我考虑到不能绑定同一个值,所以我想办法让他各自绑定各自的。
思路来了,那我该怎么绑定呢?我可以根据每一行数据的count值来判断他要不要置灰不是吗?
<!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" />
<title>Document</title>
<style>
table {
border-collapse: collapse;
text-align: center;
}
thead {
background-color: #f5f5f5;
}
th,
td {
border: 1px solid #aaa;
padding: 8px 16px;
}
</style>
</head>
<body>
<div id="app">
<!-- 1. 搭建界面内容 -->
<table>
<thead>
<tr>
<th>序号</th>
<th>书籍名称</th>
<th>出版日期</th>
<th>价格</th>
<th>购买数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- 如果key绑定的值是一个变量,需要使用冒号来进行绑定, 否则Vue会将该值作为字符串进行处理,从而无法实现正确的渲染和响应式。 -->
<tr v-for="(item, index) in books" :key="item.id">
<td>{{ index + 1 }}</td>
<td>{{ item.name }}</td>
<td>{{ item.date }}</td>
<td>{{ formatPrice(item.price) }}</td>
<td>
<button @click="decrement(index)" :disabled="item.count <= 1">-</button>
{{ item.count }}
<button @click="increment(item)">+</button>
</td>
<td><button >移除</button></td>
</tr>
</tbody>
</table>
<h2>总价: {{ formatPrice(totalPrice) }}</h2>
</div>
<script src="../lib/vue.js"></script>
<script src="./data/data.js"></script>
<script>
const app = Vue.createApp({
// data: option api
data() {
return {
books: books,
}
},
computed: {
// 计算属性会监听依赖的响应式数据,如果依赖的数据发生变化,他也会重新计算。
totalPrice() {
// 1. 直接遍历books
// let price = 0
// for (const item of this.books) {
// price += item.price * item.count
// }
// return price
// 2. reduce(后面再用这种方法)
return this.books.reduce((preValue, item) => {
return preValue + item.price * item.count
}, 0)
},
},
methods: {
formatPrice(price) {
return "¥" + price
},
decrement(index) {
this.books[index].count--
},
// 也可以传item,直接把整条数据拿过来
increment(item) {
console.log("点击+")
item.count++
},
},
})
app.mount("#app")
</script>
</body>
</html>
(3)移除操作
使用array.splice(index, 1)去删除数组里面的元素,index 是要删除元素的下标,1 表示删除的元素个数。
直接附上源码
<!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" />
<title>Document</title>
<style>
table {
border-collapse: collapse;
text-align: center;
}
thead {
background-color: #f5f5f5;
}
th,
td {
border: 1px solid #aaa;
padding: 8px 16px;
}
</style>
</head>
<body>
<div id="app">
<!-- 1. 搭建界面内容 -->
<table>
<thead>
<tr>
<th>序号</th>
<th>书籍名称</th>
<th>出版日期</th>
<th>价格</th>
<th>购买数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- 如果key绑定的值是一个变量,需要使用冒号来进行绑定, 否则Vue会将该值作为字符串进行处理,从而无法实现正确的渲染和响应式。 -->
<tr v-for="(item, index) in books" :key="item.id">
<td>{{ index + 1 }}</td>
<td>{{ item.name }}</td>
<td>{{ item.date }}</td>
<td>{{ formatPrice(item.price) }}</td>
<td>
<button @click="decrement(index)" :disabled="item.count <= 1">-</button>
{{ item.count }}
<button @click="increment(item)">+</button>
</td>
<td><button @click="removeBook(index)">移除</button></td>
</tr>
</tbody>
</table>
<h2>总价: {{ formatPrice(totalPrice) }}</h2>
</div>
<script src="../lib/vue.js"></script>
<script src="./data/data.js"></script>
<script>
const app = Vue.createApp({
// data: option api
data() {
return {
books: books,
}
},
computed: {
// 计算属性会监听依赖的响应式数据,如果依赖的数据发生变化,他也会重新计算。
totalPrice() {
// 1. 直接遍历books
// let price = 0
// for (const item of this.books) {
// price += item.price * item.count
// }
// return price
// 2. reduce(后面再用这种方法)
return this.books.reduce((preValue, item) => {
return preValue + item.price * item.count
}, 0)
},
},
methods: {
formatPrice(price) {
return "¥" + price
},
decrement(index) {
this.books[index].count--
},
// 也可以传item,直接把整条数据拿过来
increment(item) {
console.log("点击+")
item.count++
},
removeBook(index) {
this.books.splice(index, 1)
},
},
})
app.mount("#app")
</script>
</body>
</html>
(4)删到最后一个,显示”购物车没有已选择的商品啦“
<!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" />
<title>Document</title>
<style>
table {
border-collapse: collapse;
text-align: center;
}
thead {
background-color: #f5f5f5;
}
th,
td {
border: 1px solid #aaa;
padding: 8px 16px;
}
</style>
</head>
<body>
<div id="app">
<!-- 1. 搭建界面内容 -->
<template v-if="books.length">
<table>
<thead>
<tr>
<th>序号</th>
<th>书籍名称</th>
<th>出版日期</th>
<th>价格</th>
<th>购买数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- 如果key绑定的值是一个变量,需要使用冒号来进行绑定, 否则Vue会将该值作为字符串进行处理,从而无法实现正确的渲染和响应式。 -->
<tr v-for="(item, index) in books" :key="item.id">
<td>{{ index + 1 }}</td>
<td>{{ item.name }}</td>
<td>{{ item.date }}</td>
<td>{{ formatPrice(item.price) }}</td>
<td>
<button @click="decrement(index)" :disabled="item.count <= 1">-</button>
{{ item.count }}
<button @click="increment(item)">+</button>
</td>
<td><button @click="removeBook(index)">移除</button></td>
</tr>
</tbody>
</table>
<h2>总价: {{ formatPrice(totalPrice) }}</h2>
</template>
<template v-else>
<h1>购物车为空,请添加喜欢的书籍~</h1>
</template>
</div>
<script src="../lib/vue.js"></script>
<script src="./data/data.js"></script>
<script>
const app = Vue.createApp({
// data: option api
data() {
return {
books: books,
}
},
computed: {
// 计算属性会监听依赖的响应式数据,如果依赖的数据发生变化,他也会重新计算。
totalPrice() {
// 1. 直接遍历books
// let price = 0
// for (const item of this.books) {
// price += item.price * item.count
// }
// return price
// 2. reduce(后面再用这种方法)
return this.books.reduce((preValue, item) => {
return preValue + item.price * item.count
}, 0)
},
},
methods: {
formatPrice(price) {
return "¥" + price
},
decrement(index) {
this.books[index].count--
},
// 也可以传item,直接把整条数据拿过来
increment(item) {
console.log("点击+")
item.count++
},
removeBook(index) {
this.books.splice(index, 1)
},
},
})
app.mount("#app")
</script>
</body>
</html>
(5)有一些数据,实现功能:点击A选中A,点击B选中B,A变成未选中状态。
<!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" />
<title>Document</title>
</head>
<body>
<div id="app">
<ul>
<li v-for="item in movies">{{ item }}</li>
</ul>
</div>
<script src="../lib/vue.js"></script>
<script>
const app = Vue.createApp({
// data: option api
data() {
return {
movies: ["星际穿越", "阿凡达", "大话西游", "黑客帝国"],
}
},
})
app.mount("#app")
</script>
</body>
</html>
思路:控制active放在哪个元素上面。
我直接这样子写的话 每个元素都会被active
我们先来一个简单的需求,将索引值为1的li添加上active
<!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" />
<title>Document</title>
<style>
.active {
color: red;
}
</style>
</head>
<body>
<div id="app">
<ul>
<!-- <h2 :class="{title: true}"></h2> -->
<!-- 对active的class进行动态绑定 -->
<!-- 需求:将索引值为1的li添加上active -->
<li :class="{active: index === 1}" v-for="(item, index) in movies">{{ item }}</li>
</ul>
</div>
<script src="../lib/vue.js"></script>
<script>
const app = Vue.createApp({
// data: option api
data() {
return {
movies: ["星际穿越", "阿凡达", "大话西游", "黑客帝国"],
}
},
})
app.mount("#app")
</script>
</body>
</html>
功能是不是实现了?哈哈
那怎么 选中哪就给哪一个添加active呢
思路:用一个变量(属性)记录当前点击的位置
<!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" />
<title>Document</title>
<style>
.active {
color: red;
}
</style>
</head>
<body>
<div id="app">
<ul>
<!-- <h2 :class="{title: true}"></h2> -->
<!-- 对active的class进行动态绑定 -->
<!-- 需求1:将索引值为1的li添加上active -->
<!-- 需求2 选中哪就给哪一个添加active 思路:用一个变量(属性)记录当前点击的位置-->
<li :class="{active: index === currentIndex}" v-for="(item, index) in movies" @click="liClick(index)">{{ item }}</li>
</ul>
</div>
<script src="../lib/vue.js"></script>
<script>
const app = Vue.createApp({
// data: option api
data() {
return {
movies: ["星际穿越", "阿凡达", "大话西游", "黑客帝国"],
currentIndex: -1,
}
},
methods: {
liClick(index) {
this.currentIndex = index
},
},
})
app.mount("#app")
</script>
</body>
</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" />
<title>Document</title>
<style>
table {
border-collapse: collapse;
text-align: center;
}
thead {
background-color: #f5f5f5;
}
th,
td {
border: 1px solid #aaa;
padding: 8px 16px;
}
.active {
background: skyblue;
}
</style>
</head>
<body>
<div id="app">
<!-- 1. 搭建界面内容 -->
<template v-if="books.length">
<table>
<thead>
<tr>
<th>序号</th>
<th>书籍名称</th>
<th>出版日期</th>
<th>价格</th>
<th>购买数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- 如果key绑定的值是一个变量,需要使用冒号来进行绑定, 否则Vue会将该值作为字符串进行处理,从而无法实现正确的渲染和响应式。 -->
<tr v-for="(item, index) in books" :key="item.id" @click="rowClick(index)" :class="{ active: index === currentIndex }">
<td>{{ index + 1 }}</td>
<td>{{ item.name }}</td>
<td>{{ item.date }}</td>
<td>{{ formatPrice(item.price) }}</td>
<td>
<button @click="decrement(index)" :disabled="item.count <= 1">-</button>
{{ item.count }}
<button @click="increment(item)">+</button>
</td>
<td><button @click="removeBook(index)">移除</button></td>
</tr>
</tbody>
</table>
<h2>总价: {{ formatPrice(totalPrice) }}</h2>
</template>
<template v-else>
<h1>购物车为空,请添加喜欢的书籍~</h1>
</template>
</div>
<script src="../lib/vue.js"></script>
<script src="./data/data.js"></script>
<script>
const app = Vue.createApp({
// data: option api
data() {
return {
books: books,
currentIndex: 0,
}
},
computed: {
// 计算属性会监听依赖的响应式数据,如果依赖的数据发生变化,他也会重新计算。
totalPrice() {
// 1. 直接遍历books
// let price = 0
// for (const item of this.books) {
// price += item.price * item.count
// }
// return price
// 2. reduce(后面再用这种方法)
return this.books.reduce((preValue, item) => {
return preValue + item.price * item.count
}, 0)
},
},
methods: {
formatPrice(price) {
return "¥" + price
},
decrement(index) {
this.books[index].count--
},
// 也可以传item,直接把整条数据拿过来
increment(item) {
console.log("点击+")
item.count++
},
removeBook(index) {
this.books.splice(index, 1)
},
rowClick(index) {
this.currentIndex = index
},
},
})
app.mount("#app")
</script>
</body>
</html>