文章目录
- 前言
- 发现宝藏
- 一、指令补充
- 1. 指令修饰符
- 2. v-bind对于样式操作的增强 - class
- 3. 案例 - 京东秒杀 tab 导航高亮
- 4. v-bind对于样式操作的增强 - style
- 5. v-model应用于其他表单元素
- 二、computed计算属性
- 1. 基础语法
- 2. 计算属性 vS method 方法
- 3. 完整写法
- 4. 成绩案例
- 三、watch 侦听器
-
- 四、综合案例:水果购物车
- 1. 渲染
- 2. 删除和修改数量
- 3. 全选反选
- 4. 统计总价
- 5. 本地持久化
- 总结
前言
为了巩固所学的知识,作者尝试着开始发布一些学习笔记类的博客,方便日后回顾。当然,如果能帮到一些萌新进行新技术的学习那也是极好的。作者菜菜一枚,文章中如果有记录错误,欢迎读者朋友们批评指正。
发现宝藏
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。【宝藏入口】。
一、指令补充
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> |
| </head> |
| <body> |
| <div id="app"> |
| <h3>@keyup.enter → 监听键盘回车事件</h3> |
| <input @keyup.enter="fn" v-model="username" type="text"> |
| </div> |
| <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> |
| <script> |
| const app = new Vue({ |
| el: '#app', |
| data: { |
| username: '' |
| }, |
| methods: { |
| fn (e) { |
| |
| |
| |
| |
| console.log('键盘回车的时候触发', this.username) |
| } |
| } |
| }) |
| </script> |
| </body> |
| </html> |
复制

- .trim .number .stop .prevent
| <!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> |
| .father { |
| width: 200px; |
| height: 200px; |
| background-color: pink; |
| margin-top: 20px; |
| } |
| .son { |
| width: 100px; |
| height: 100px; |
| background-color: skyblue; |
| } |
| </style> |
| </head> |
| <body> |
| <div id="app"> |
| <h3>v-model修饰符 .trim .number</h3> |
| 姓名:<input v-model.trim="username" type="text"><br> |
| 年纪:<input v-model.number="age" type="text"><br> |
| |
| |
| <h3>@事件名.stop → 阻止冒泡</h3> |
| <div @click="fatherFn" class="father"> |
| <div @click.stop="sonFn" class="son">儿子</div> |
| </div> |
| |
| <h3>@事件名.prevent → 阻止默认行为</h3> |
| <a @click.prevent href="http://www.baidu.com">阻止默认行为</a> |
| </div> |
| <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> |
| <script> |
| const app = new Vue({ |
| el: '#app', |
| data: { |
| username: '', |
| age: '', |
| }, |
| methods: { |
| fatherFn () { |
| alert('老父亲被点击了') |
| }, |
| sonFn (e) { |
| |
| alert('儿子被点击了') |
| } |
| } |
| }) |
| </script> |
| </body> |
| </html> |
复制

2. v-bind对于样式操作的增强 - class

| <!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> |
| .box { |
| width: 200px; |
| height: 200px; |
| border: 3px solid #000; |
| font-size: 30px; |
| margin-top: 10px; |
| } |
| .pink { |
| background-color: pink; |
| } |
| .big { |
| width: 300px; |
| height: 300px; |
| } |
| </style> |
| </head> |
| <body> |
| |
| <div id="app"> |
| <div class="box" :class="{ pink: true, big: true }">黑马程序员</div> |
| <div class="box" :class="['pink', 'big']">黑马程序员</div> |
| </div> |
| <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> |
| <script> |
| const app = new Vue({ |
| el: '#app', |
| data: { |
| |
| } |
| }) |
| </script> |
| </body> |
| </html> |
复制

3. 案例 - 京东秒杀 tab 导航高亮

| <!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> |
| * { |
| margin: 0; |
| padding: 0; |
| } |
| ul { |
| display: flex; |
| border-bottom: 2px solid #e01222; |
| padding: 0 10px; |
| } |
| li { |
| width: 100px; |
| height: 50px; |
| line-height: 50px; |
| list-style: none; |
| text-align: center; |
| } |
| li a { |
| display: block; |
| text-decoration: none; |
| font-weight: bold; |
| color: #333333; |
| } |
| li a.active { |
| background-color: #e01222; |
| color: #fff; |
| } |
| |
| </style> |
| </head> |
| <body> |
| |
| <div id="app"> |
| <ul> |
| <li v-for="(item, index) in list" :key="item.id" @click="activeIndex = index"> |
| <a :class="{ active: index === activeIndex }" href="#">{{ item.name }}</a> |
| </li> |
| </ul> |
| </div> |
| <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> |
| <script> |
| const app = new Vue({ |
| el: '#app', |
| data: { |
| activeIndex: 2, |
| list: [ |
| { id: 1, name: '京东秒杀' }, |
| { id: 2, name: '每日特价' }, |
| { id: 3, name: '品类秒杀' } |
| ] |
| } |
| }) |
| </script> |
| </body> |
| </html> |
复制

4. v-bind对于样式操作的增强 - style

| <!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> |
| .progress { |
| height: 25px; |
| width: 400px; |
| border-radius: 15px; |
| background-color: #272425; |
| border: 3px solid #272425; |
| box-sizing: border-box; |
| margin-bottom: 30px; |
| } |
| .inner { |
| width: 50%; |
| height: 20px; |
| border-radius: 10px; |
| text-align: right; |
| position: relative; |
| background-color: #409eff; |
| background-size: 20px 20px; |
| box-sizing: border-box; |
| transition: all 1s; |
| } |
| .inner span { |
| position: absolute; |
| right: -20px; |
| bottom: -25px; |
| } |
| </style> |
| </head> |
| <body> |
| <div id="app"> |
| |
| <div class="progress"> |
| |
| <div class="inner" :style="{ width: percent + '%' }"> |
| <span>{{ percent }}%</span> |
| </div> |
| </div> |
| <button @click="percent = 25">设置25%</button> |
| <button @click="percent = 50">设置50%</button> |
| <button @click="percent = 75">设置75%</button> |
| <button @click="percent = 100">设置100%</button> |
| </div> |
| <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> |
| <script> |
| const app = new Vue({ |
| el: '#app', |
| data: { |
| percent: 30 |
| } |
| }) |
| </script> |
| </body> |
| </html> |
复制

5. v-model应用于其他表单元素

| <!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> |
| textarea { |
| display: block; |
| width: 240px; |
| height: 100px; |
| margin: 10px 0; |
| } |
| </style> |
| </head> |
| <body> |
| |
| <div id="app"> |
| <h3>小黑学习网</h3> |
| |
| 姓名: |
| <input type="text" v-model="username"> |
| <br><br> |
| |
| 是否单身: |
| <input type="checkbox" v-model="isSingle"> |
| <br><br> |
| |
| |
| |
| |
| |
| |
| |
| 性别: |
| <input v-model="gender" type="radio" name="gender" value="1">男 |
| <input v-model="gender" type="radio" name="gender" value="2">女 |
| <br><br> |
| |
| |
| |
| |
| |
| |
| |
| 所在城市: |
| <select v-model="cityId"> |
| <option value="101">北京</option> |
| <option value="102">上海</option> |
| <option value="103">成都</option> |
| <option value="104">南京</option> |
| </select> |
| <br><br> |
| |
| 自我描述: |
| <textarea v-model="desc"></textarea> |
| |
| <button>立即注册</button> |
| </div> |
| <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> |
| <script> |
| const app = new Vue({ |
| el: '#app', |
| data: { |
| username: '', |
| isSingle: false, |
| gender: "2", |
| cityId: '102', |
| desc: "" |
| } |
| }) |
| </script> |
| </body> |
| </html> |
复制

二、computed计算属性
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: 1px solid #000; |
| text-align: center; |
| width: 240px; |
| } |
| th,td { |
| border: 1px solid #000; |
| } |
| h3 { |
| position: relative; |
| } |
| </style> |
| </head> |
| <body> |
| |
| <div id="app"> |
| <h3>小黑的礼物清单</h3> |
| <table> |
| <tr> |
| <th>名字</th> |
| <th>数量</th> |
| </tr> |
| <tr v-for="(item, index) in list" :key="item.id"> |
| <td>{{ item.name }}</td> |
| <td>{{ item.num }}个</td> |
| </tr> |
| </table> |
| |
| |
| <p>礼物总数:{{ totalCount }} 个</p> |
| </div> |
| <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> |
| <script> |
| const app = new Vue({ |
| el: '#app', |
| data: { |
| |
| list: [ |
| { id: 1, name: '篮球', num: 1 }, |
| { id: 2, name: '玩具', num: 2 }, |
| { id: 3, name: '铅笔', num: 5 }, |
| ] |
| }, |
| computed: { |
| totalCount () { |
| |
| |
| |
| |
| |
| let total = this.list.reduce((sum, item) => sum + item.num, 0) |
| return total |
| } |
| } |
| }) |
| </script> |
| </body> |
| </html> |
复制

2. 计算属性 vS method 方法

| <!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: 1px solid #000; |
| text-align: center; |
| width: 300px; |
| } |
| th,td { |
| border: 1px solid #000; |
| } |
| h3 { |
| position: relative; |
| } |
| span { |
| position: absolute; |
| left: 145px; |
| top: -4px; |
| width: 16px; |
| height: 16px; |
| color: white; |
| font-size: 12px; |
| text-align: center; |
| border-radius: 50%; |
| background-color: #e63f32; |
| } |
| </style> |
| </head> |
| <body> |
| |
| <div id="app"> |
| <h3>小黑的礼物清单🛒<span>{{ totalCountFn() }}</span></h3> |
| <h3>小黑的礼物清单🛒<span>{{ totalCountFn() }}</span></h3> |
| <h3>小黑的礼物清单🛒<span>{{ totalCountFn() }}</span></h3> |
| <h3>小黑的礼物清单🛒<span>{{ totalCountFn() }}</span></h3> |
| <table> |
| <tr> |
| <th>名字</th> |
| <th>数量</th> |
| </tr> |
| <tr v-for="(item, index) in list" :key="item.id"> |
| <td>{{ item.name }}</td> |
| <td>{{ item.num }}个</td> |
| </tr> |
| </table> |
| |
| <p>礼物总数:{{ totalCountFn() }} 个</p> |
| </div> |
| <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> |
| <script> |
| const app = new Vue({ |
| el: '#app', |
| data: { |
| |
| list: [ |
| { id: 1, name: '篮球', num: 3 }, |
| { id: 2, name: '玩具', num: 2 }, |
| { id: 3, name: '铅笔', num: 5 }, |
| ] |
| }, |
| |
| methods: { |
| totalCountFn () { |
| console.log('methods方法执行了') |
| let total = this.list.reduce((sum, item) => sum + item.num, 0) |
| return total |
| } |
| }, |
| |
| computed: { |
| |
| |
| |
| |
| |
| |
| |
| } |
| }) |
| </script> |
| </body> |
| </html> |
复制

3. 完整写法

| <!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> |
| input { |
| width: 30px; |
| } |
| </style> |
| </head> |
| <body> |
| |
| <div id="app"> |
| 姓:<input type="text" v-model="firstName"> + |
| 名:<input type="text" v-model="lastName"> = |
| <span>{{ fullName }}</span><br><br> |
| |
| <button @click="changeName">改名卡</button> |
| </div> |
| <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> |
| <script> |
| const app = new Vue({ |
| el: '#app', |
| data: { |
| firstName: '刘', |
| lastName: '备', |
| }, |
| methods: { |
| changeName () { |
| this.fullName = '黄忠' |
| } |
| }, |
| computed: { |
| |
| |
| |
| |
| |
| |
| fullName: { |
| |
| |
| get () { |
| return this.firstName + this.lastName |
| }, |
| |
| |
| set (value) { |
| |
| |
| this.firstName = value.slice(0, 1) |
| this.lastName = value.slice(1) |
| } |
| } |
| } |
| }) |
| </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" /> |
| <link rel="stylesheet" href="./styles/index.css" /> |
| <title>Document</title> |
| </head> |
| <body> |
| <div id="app" class="score-case"> |
| <div class="table"> |
| <table> |
| <thead> |
| <tr> |
| <th>编号</th> |
| <th>科目</th> |
| <th>成绩</th> |
| <th>操作</th> |
| </tr> |
| </thead> |
| |
| <tbody v-if="list.length > 0"> |
| <tr v-for="(item, index) in list" :key="item.id"> |
| <td>{{ index + 1 }}</td> |
| <td>{{ item.subject }}</td> |
| |
| <td :class="{ red: item.score < 60 }">{{ item.score }}</td> |
| <td><a href="#">删除</a></td> |
| </tr> |
| </tbody> |
| |
| <tbody v-else> |
| <tr> |
| <td colspan="5"> |
| <span class="none">暂无数据</span> |
| </td> |
| </tr> |
| </tbody> |
| |
| <tfoot> |
| <tr> |
| <td colspan="5"> |
| <span>总分:246</span> |
| <span style="margin-left: 50px">平均分:79</span> |
| </td> |
| </tr> |
| </tfoot> |
| </table> |
| </div> |
| <div class="form"> |
| <div class="form-item"> |
| <div class="label">科目:</div> |
| <div class="input"> |
| <input |
| type="text" |
| placeholder="请输入科目" |
| /> |
| </div> |
| </div> |
| <div class="form-item"> |
| <div class="label">分数:</div> |
| <div class="input"> |
| <input |
| type="text" |
| placeholder="请输入分数" |
| /> |
| </div> |
| </div> |
| <div class="form-item"> |
| <div class="label"></div> |
| <div class="input"> |
| <button class="submit" >添加</button> |
| </div> |
| </div> |
| </div> |
| </div> |
| <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> |
| |
| <script> |
| const app = new Vue({ |
| el: '#app', |
| data: { |
| list: [ |
| { id: 1, subject: '语文', score: 62 }, |
| { id: 7, subject: '数学', score: 39 }, |
| { id: 12, subject: '英语', score: 70 }, |
| ], |
| subject: '', |
| score: '' |
| } |
| }) |
| </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" /> |
| <link rel="stylesheet" href="./styles/index.css" /> |
| <title>Document</title> |
| </head> |
| <body> |
| <div id="app" class="score-case"> |
| <div class="table"> |
| <table> |
| <thead> |
| <tr> |
| <th>编号</th> |
| <th>科目</th> |
| <th>成绩</th> |
| <th>操作</th> |
| </tr> |
| </thead> |
| |
| <tbody v-if="list.length > 0"> |
| <tr v-for="(item, index) in list" :key="item.id"> |
| <td>{{ index + 1 }}</td> |
| <td>{{ item.subject }}</td> |
| |
| <td :class="{ red: item.score < 60 }">{{ item.score }}</td> |
| <td><a @click.prevent="del(item.id)" href="http://www.baidu.com">删除</a></td> |
| </tr> |
| </tbody> |
| |
| <tbody v-else> |
| <tr> |
| <td colspan="5"> |
| <span class="none">暂无数据</span> |
| </td> |
| </tr> |
| </tbody> |
| |
| <tfoot> |
| <tr> |
| <td colspan="5"> |
| <span>总分:246</span> |
| <span style="margin-left: 50px">平均分:79</span> |
| </td> |
| </tr> |
| </tfoot> |
| </table> |
| </div> |
| <div class="form"> |
| <div class="form-item"> |
| <div class="label">科目:</div> |
| <div class="input"> |
| <input |
| type="text" |
| placeholder="请输入科目" |
| v-model.trim="subject" |
| /> |
| </div> |
| </div> |
| <div class="form-item"> |
| <div class="label">分数:</div> |
| <div class="input"> |
| <input |
| type="text" |
| placeholder="请输入分数" |
| v-model.number="score" |
| /> |
| </div> |
| </div> |
| <div class="form-item"> |
| <div class="label"></div> |
| <div class="input"> |
| <button @click="add" class="submit" >添加</button> |
| </div> |
| </div> |
| </div> |
| </div> |
| <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> |
| |
| <script> |
| const app = new Vue({ |
| el: '#app', |
| data: { |
| list: [ |
| { id: 1, subject: '语文', score: 62 }, |
| { id: 7, subject: '数学', score: 39 }, |
| { id: 12, subject: '英语', score: 70 }, |
| ], |
| subject: '', |
| score: '' |
| }, |
| methods: { |
| del (id) { |
| |
| this.list = this.list.filter(item => item.id !== id) |
| }, |
| add () { |
| if (!this.subject) { |
| alert('请输入科目') |
| return |
| } |
| if (typeof this.score !== 'number') { |
| alert('请输入正确的成绩') |
| return |
| } |
| this.list.unshift({ |
| id: +new Date(), |
| subject: this.subject, |
| score: this.score |
| }) |
| |
| this.subject = '' |
| this.score = '' |
| } |
| } |
| }) |
| </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" /> |
| <link rel="stylesheet" href="./styles/index.css" /> |
| <title>Document</title> |
| </head> |
| <body> |
| <div id="app" class="score-case"> |
| <div class="table"> |
| <table> |
| <thead> |
| <tr> |
| <th>编号</th> |
| <th>科目</th> |
| <th>成绩</th> |
| <th>操作</th> |
| </tr> |
| </thead> |
| |
| <tbody v-if="list.length > 0"> |
| <tr v-for="(item, index) in list" :key="item.id"> |
| <td>{{ index + 1 }}</td> |
| <td>{{ item.subject }}</td> |
| |
| <td :class="{ red: item.score < 60 }">{{ item.score }}</td> |
| <td><a @click.prevent="del(item.id)" href="http://www.baidu.com">删除</a></td> |
| </tr> |
| </tbody> |
| |
| <tbody v-else> |
| <tr> |
| <td colspan="5"> |
| <span class="none">暂无数据</span> |
| </td> |
| </tr> |
| </tbody> |
| |
| <tfoot> |
| <tr> |
| <td colspan="5"> |
| <span>总分:{{ totalScore }}</span> |
| <span style="margin-left: 50px">平均分:{{ averageScore }}</span> |
| </td> |
| </tr> |
| </tfoot> |
| </table> |
| </div> |
| <div class="form"> |
| <div class="form-item"> |
| <div class="label">科目:</div> |
| <div class="input"> |
| <input |
| type="text" |
| placeholder="请输入科目" |
| v-model.trim="subject" |
| /> |
| </div> |
| </div> |
| <div class="form-item"> |
| <div class="label">分数:</div> |
| <div class="input"> |
| <input |
| type="text" |
| placeholder="请输入分数" |
| v-model.number="score" |
| /> |
| </div> |
| </div> |
| <div class="form-item"> |
| <div class="label"></div> |
| <div class="input"> |
| <button @click="add" class="submit" >添加</button> |
| </div> |
| </div> |
| </div> |
| </div> |
| <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> |
| |
| <script> |
| const app = new Vue({ |
| el: '#app', |
| data: { |
| list: [ |
| { id: 1, subject: '语文', score: 62 }, |
| { id: 7, subject: '数学', score: 89 }, |
| { id: 12, subject: '英语', score: 70 }, |
| ], |
| subject: '', |
| score: '' |
| }, |
| computed: { |
| totalScore() { |
| return this.list.reduce((sum, item) => sum + item.score, 0) |
| }, |
| averageScore () { |
| if (this.list.length === 0) { |
| return 0 |
| } |
| return (this.totalScore / this.list.length).toFixed(2) |
| } |
| }, |
| methods: { |
| del (id) { |
| |
| this.list = this.list.filter(item => item.id !== id) |
| }, |
| add () { |
| if (!this.subject) { |
| alert('请输入科目') |
| return |
| } |
| if (typeof this.score !== 'number') { |
| alert('请输入正确的成绩') |
| return |
| } |
| this.list.unshift({ |
| id: +new Date(), |
| subject: this.subject, |
| score: this.score |
| }) |
| |
| this.subject = '' |
| this.score = '' |
| } |
| } |
| }) |
| </script> |
| </body> |
| </html> |
| |
复制


三、watch 侦听器
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> |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| font-size: 18px; |
| } |
| #app { |
| padding: 10px 20px; |
| } |
| .query { |
| margin: 10px 0; |
| } |
| .box { |
| display: flex; |
| } |
| textarea { |
| width: 300px; |
| height: 160px; |
| font-size: 18px; |
| border: 1px solid #dedede; |
| outline: none; |
| resize: none; |
| padding: 10px; |
| } |
| textarea:hover { |
| border: 1px solid #1589f5; |
| } |
| .transbox { |
| width: 300px; |
| height: 160px; |
| background-color: #f0f0f0; |
| padding: 10px; |
| border: none; |
| } |
| .tip-box { |
| width: 300px; |
| height: 25px; |
| line-height: 25px; |
| display: flex; |
| } |
| .tip-box span { |
| flex: 1; |
| text-align: center; |
| } |
| .query span { |
| font-size: 18px; |
| } |
| |
| .input-wrap { |
| position: relative; |
| } |
| .input-wrap span { |
| position: absolute; |
| right: 15px; |
| bottom: 15px; |
| font-size: 12px; |
| } |
| .input-wrap i { |
| font-size: 20px; |
| font-style: normal; |
| } |
| </style> |
| </head> |
| <body> |
| <div id="app"> |
| |
| <div class="query"> |
| <span>翻译成的语言:</span> |
| <select> |
| <option value="italy">意大利</option> |
| <option value="english">英语</option> |
| <option value="german">德语</option> |
| </select> |
| </div> |
| |
| |
| <div class="box"> |
| <div class="input-wrap"> |
| <textarea v-model="obj.words"></textarea> |
| <span><i>⌨️</i>文档翻译</span> |
| </div> |
| <div class="output-wrap"> |
| <div class="transbox">mela</div> |
| </div> |
| </div> |
| </div> |
| <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> |
| <script> |
| |
| |
| |
| |
| |
| |
| |
| const app = new Vue({ |
| el: '#app', |
| data: { |
| |
| obj: { |
| words: '' |
| } |
| }, |
| |
| watch: { |
| |
| |
| |
| |
| |
| |
| 'obj.words' (newValue) { |
| console.log('变化了', newValue) |
| } |
| } |
| }) |
| </script> |
| </body> |
| </html> |
| |
复制

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> |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| font-size: 18px; |
| } |
| #app { |
| padding: 10px 20px; |
| } |
| .query { |
| margin: 10px 0; |
| } |
| .box { |
| display: flex; |
| } |
| textarea { |
| width: 300px; |
| height: 160px; |
| font-size: 18px; |
| border: 1px solid #dedede; |
| outline: none; |
| resize: none; |
| padding: 10px; |
| } |
| textarea:hover { |
| border: 1px solid #1589f5; |
| } |
| .transbox { |
| width: 300px; |
| height: 160px; |
| background-color: #f0f0f0; |
| padding: 10px; |
| border: none; |
| } |
| .tip-box { |
| width: 300px; |
| height: 25px; |
| line-height: 25px; |
| display: flex; |
| } |
| .tip-box span { |
| flex: 1; |
| text-align: center; |
| } |
| .query span { |
| font-size: 18px; |
| } |
| |
| .input-wrap { |
| position: relative; |
| } |
| .input-wrap span { |
| position: absolute; |
| right: 15px; |
| bottom: 15px; |
| font-size: 12px; |
| } |
| .input-wrap i { |
| font-size: 20px; |
| font-style: normal; |
| } |
| </style> |
| </head> |
| <body> |
| <div id="app"> |
| |
| <div class="query"> |
| <span>翻译成的语言:</span> |
| <select> |
| <option value="italy">意大利</option> |
| <option value="english">英语</option> |
| <option value="german">德语</option> |
| </select> |
| </div> |
| |
| |
| <div class="box"> |
| <div class="input-wrap"> |
| <textarea v-model="obj.words"></textarea> |
| <span><i>⌨️</i>文档翻译</span> |
| </div> |
| <div class="output-wrap"> |
| <div class="transbox">{{ result }}</div> |
| </div> |
| </div> |
| </div> |
| <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> |
| <script> |
| |
| |
| |
| |
| |
| |
| |
| const app = new Vue({ |
| el: '#app', |
| data: { |
| |
| obj: { |
| words: '' |
| }, |
| result: '', |
| |
| }, |
| |
| watch: { |
| |
| |
| |
| |
| |
| |
| 'obj.words' (newValue) { |
| |
| |
| clearTimeout(this.timer) |
| this.timer = setTimeout(async () => { |
| const res = await axios({ |
| url: 'https://applet-base-api-t.itheima.net/api/translate', |
| params: { |
| words: newValue |
| } |
| }) |
| this.result = res.data.data |
| console.log(res.data.data) |
| }, 300) |
| } |
| } |
| }) |
| </script> |
| </body> |
| </html> |
| |
复制

3. 完整写法

| <!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> |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| font-size: 18px; |
| } |
| #app { |
| padding: 10px 20px; |
| } |
| .query { |
| margin: 10px 0; |
| } |
| .box { |
| display: flex; |
| } |
| textarea { |
| width: 300px; |
| height: 160px; |
| font-size: 18px; |
| border: 1px solid #dedede; |
| outline: none; |
| resize: none; |
| padding: 10px; |
| } |
| textarea:hover { |
| border: 1px solid #1589f5; |
| } |
| .transbox { |
| width: 300px; |
| height: 160px; |
| background-color: #f0f0f0; |
| padding: 10px; |
| border: none; |
| } |
| .tip-box { |
| width: 300px; |
| height: 25px; |
| line-height: 25px; |
| display: flex; |
| } |
| .tip-box span { |
| flex: 1; |
| text-align: center; |
| } |
| .query span { |
| font-size: 18px; |
| } |
| |
| .input-wrap { |
| position: relative; |
| } |
| .input-wrap span { |
| position: absolute; |
| right: 15px; |
| bottom: 15px; |
| font-size: 12px; |
| } |
| .input-wrap i { |
| font-size: 20px; |
| font-style: normal; |
| } |
| </style> |
| </head> |
| <body> |
| <div id="app"> |
| |
| <div class="query"> |
| <span>翻译成的语言:</span> |
| <select v-model="obj.lang"> |
| <option value="italy">意大利</option> |
| <option value="english">英语</option> |
| <option value="german">德语</option> |
| </select> |
| </div> |
| |
| |
| <div class="box"> |
| <div class="input-wrap"> |
| <textarea v-model="obj.words"></textarea> |
| <span><i>⌨️</i>文档翻译</span> |
| </div> |
| <div class="output-wrap"> |
| <div class="transbox">{{ result }}</div> |
| </div> |
| </div> |
| </div> |
| <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> |
| <script> |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const app = new Vue({ |
| el: '#app', |
| data: { |
| obj: { |
| words: '小黑', |
| lang: 'italy' |
| }, |
| result: '', |
| }, |
| watch: { |
| obj: { |
| deep: true, |
| immediate: true, |
| handler (newValue) { |
| clearTimeout(this.timer) |
| this.timer = setTimeout(async () => { |
| const res = await axios({ |
| url: 'https://applet-base-api-t.itheima.net/api/translate', |
| params: newValue |
| }) |
| this.result = res.data.data |
| console.log(res.data.data) |
| }, 300) |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| } |
| }) |
| </script> |
| </body> |
| </html> |
| |
复制


四、综合案例:水果购物车

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" /> |
| <link rel="stylesheet" href="./css/inputnumber.css" /> |
| <link rel="stylesheet" href="./css/index.css" /> |
| <title>购物车</title> |
| </head> |
| <body> |
| <div class="app-container" id="app"> |
| |
| <div class="banner-box"><img src="http://autumnfish.cn/static/fruit.jpg" alt="" /></div> |
| |
| <div class="breadcrumb"> |
| <span>🏠</span> |
| / |
| <span>购物车</span> |
| </div> |
| |
| <div class="main" v-if="fruitList.length > 0"> |
| <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> |
| |
| <div class="tbody"> |
| <div v-for="(item, index) in fruitList" :key="item.id" class="tr" :class="{ active: item.isChecked }"> |
| <div class="td"><input type="checkbox" v-model="item.isChecked" /></div> |
| <div class="td"><img :src="item.icon" alt="" /></div> |
| <div class="td">{{ item.price }}</div> |
| <div class="td"> |
| <div class="my-input-number"> |
| <button class="decrease"> - </button> |
| <span class="my-input__inner">{{ item.num }}</span> |
| <button class="increase"> + </button> |
| </div> |
| </div> |
| <div class="td">{{ item.num * item.price }}</div> |
| <div class="td"><button>删除</button></div> |
| </div> |
| </div> |
| </div> |
| |
| <div class="bottom"> |
| |
| <label class="check-all"> |
| <input type="checkbox" /> |
| 全选 |
| </label> |
| <div class="right-box"> |
| |
| <span class="price-box">总价 : ¥ <span class="price">24</span></span> |
| |
| <button class="pay">结算( 6 )</button> |
| </div> |
| </div> |
| </div> |
| |
| <div class="empty" v-else>🛒空空如也</div> |
| </div> |
| <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> |
| <script> |
| const app = new Vue({ |
| el: '#app', |
| data: { |
| |
| fruitList: [ |
| { |
| id: 1, |
| icon: 'http://autumnfish.cn/static/火龙果.png', |
| isChecked: true, |
| num: 2, |
| price: 6, |
| }, |
| { |
| id: 2, |
| icon: 'http://autumnfish.cn/static/荔枝.png', |
| isChecked: false, |
| num: 7, |
| price: 20, |
| }, |
| { |
| id: 3, |
| icon: 'http://autumnfish.cn/static/榴莲.png', |
| isChecked: false, |
| num: 3, |
| price: 40, |
| }, |
| { |
| id: 4, |
| icon: 'http://autumnfish.cn/static/鸭梨.png', |
| isChecked: true, |
| num: 10, |
| price: 3, |
| }, |
| { |
| id: 5, |
| icon: 'http://autumnfish.cn/static/樱桃.png', |
| isChecked: false, |
| num: 20, |
| price: 34, |
| }, |
| ], |
| }, |
| }) |
| </script> |
| </body> |
| </html> |
| |
复制
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" /> |
| <link rel="stylesheet" href="./css/inputnumber.css" /> |
| <link rel="stylesheet" href="./css/index.css" /> |
| <title>购物车</title> |
| </head> |
| <body> |
| <div class="app-container" id="app"> |
| |
| <div class="banner-box"><img src="http://autumnfish.cn/static/fruit.jpg" alt="" /></div> |
| |
| <div class="breadcrumb"> |
| <span>🏠</span> |
| / |
| <span>购物车</span> |
| </div> |
| |
| <div class="main" v-if="fruitList.length > 0"> |
| <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> |
| |
| <div class="tbody"> |
| <div v-for="(item, index) in fruitList" :key="item.id" class="tr" :class="{ active: item.isChecked }"> |
| <div class="td"><input type="checkbox" v-model="item.isChecked" /></div> |
| <div class="td"><img :src="item.icon" alt="" /></div> |
| <div class="td">{{ item.price }}</div> |
| <div class="td"> |
| <div class="my-input-number"> |
| <button :disabled="item.num <= 1" class="decrease" @click="sub(item.id)"> - </button> |
| <span class="my-input__inner">{{ item.num }}</span> |
| <button class="increase" @click="add(item.id)"> + </button> |
| </div> |
| </div> |
| <div class="td">{{ item.num * item.price }}</div> |
| <div class="td"><button @click="del(item.id)">删除</button></div> |
| </div> |
| </div> |
| </div> |
| |
| <div class="bottom"> |
| |
| <label class="check-all"> |
| <input type="checkbox" /> |
| 全选 |
| </label> |
| <div class="right-box"> |
| |
| <span class="price-box">总价 : ¥ <span class="price">24</span></span> |
| |
| <button class="pay">结算( 6 )</button> |
| </div> |
| </div> |
| </div> |
| |
| <div class="empty" v-else>🛒空空如也</div> |
| </div> |
| <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> |
| <script> |
| const app = new Vue({ |
| el: '#app', |
| data: { |
| |
| fruitList: [ |
| { |
| id: 1, |
| icon: 'http://autumnfish.cn/static/火龙果.png', |
| isChecked: true, |
| num: 2, |
| price: 6, |
| }, |
| { |
| id: 2, |
| icon: 'http://autumnfish.cn/static/荔枝.png', |
| isChecked: false, |
| num: 7, |
| price: 20, |
| }, |
| { |
| id: 3, |
| icon: 'http://autumnfish.cn/static/榴莲.png', |
| isChecked: false, |
| num: 3, |
| price: 40, |
| }, |
| { |
| id: 4, |
| icon: 'http://autumnfish.cn/static/鸭梨.png', |
| isChecked: true, |
| num: 10, |
| price: 3, |
| }, |
| { |
| id: 5, |
| icon: 'http://autumnfish.cn/static/樱桃.png', |
| isChecked: false, |
| num: 20, |
| price: 34, |
| }, |
| ], |
| }, |
| methods: { |
| del (id) { |
| this.fruitList = this.fruitList.filter(item => item.id !== id) |
| }, |
| add (id) { |
| |
| const fruit = this.fruitList.find(item => item.id === id) |
| |
| fruit.num++ |
| }, |
| sub (id) { |
| |
| const fruit = this.fruitList.find(item => item.id === id) |
| |
| fruit.num-- |
| } |
| } |
| }) |
| </script> |
| </body> |
| </html> |
| |
复制
3. 全选反选
| <!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"> |
| |
| <div class="banner-box"><img src="http://autumnfish.cn/static/fruit.jpg" alt="" /></div> |
| |
| <div class="breadcrumb"> |
| <span>🏠</span> |
| / |
| <span>购物车</span> |
| </div> |
| |
| <div class="main" v-if="fruitList.length > 0"> |
| <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> |
| |
| <div class="tbody"> |
| <div v-for="(item, index) in fruitList" :key="item.id" class="tr" :class="{ active: item.isChecked }"> |
| <div class="td"><input type="checkbox" v-model="item.isChecked" /></div> |
| <div class="td"><img :src="item.icon" alt="" /></div> |
| <div class="td">{{ item.price }}</div> |
| <div class="td"> |
| <div class="my-input-number"> |
| <button :disabled="item.num <= 1" class="decrease" @click="sub(item.id)"> - </button> |
| <span class="my-input__inner">{{ item.num }}</span> |
| <button class="increase" @click="add(item.id)"> + </button> |
| </div> |
| </div> |
| <div class="td">{{ item.num * item.price }}</div> |
| <div class="td"><button @click="del(item.id)">删除</button></div> |
| </div> |
| </div> |
| </div> |
| |
| <div class="bottom"> |
| |
| <label class="check-all"> |
| <input type="checkbox" v-model="isAll"/> |
| 全选 |
| </label> |
| <div class="right-box"> |
| |
| <span class="price-box">总价 : ¥ <span class="price">24</span></span> |
| |
| <button class="pay">结算( 6 )</button> |
| </div> |
| </div> |
| </div> |
| |
| <div class="empty" v-else>🛒空空如也</div> |
| </div> |
| <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> |
| <script> |
| const app = new Vue({ |
| el: '#app', |
| data: { |
| |
| fruitList: [ |
| { |
| id: 1, |
| icon: 'http://autumnfish.cn/static/火龙果.png', |
| isChecked: true, |
| num: 2, |
| price: 6, |
| }, |
| { |
| id: 2, |
| icon: 'http://autumnfish.cn/static/荔枝.png', |
| isChecked: false, |
| num: 7, |
| price: 20, |
| }, |
| { |
| id: 3, |
| icon: 'http://autumnfish.cn/static/榴莲.png', |
| isChecked: false, |
| num: 3, |
| price: 40, |
| }, |
| { |
| id: 4, |
| icon: 'http://autumnfish.cn/static/鸭梨.png', |
| isChecked: true, |
| num: 10, |
| price: 3, |
| }, |
| { |
| id: 5, |
| icon: 'http://autumnfish.cn/static/樱桃.png', |
| isChecked: false, |
| num: 20, |
| price: 34, |
| }, |
| ], |
| }, |
| computed: { |
| |
| |
| |
| |
| |
| |
| |
| isAll: { |
| get () { |
| return this.fruitList.every(item => item.isChecked) |
| }, |
| set (value) { |
| |
| this.fruitList.forEach(item => item.isChecked = value) |
| } |
| } |
| }, |
| methods: { |
| del (id) { |
| this.fruitList = this.fruitList.filter(item => item.id !== id) |
| }, |
| add (id) { |
| |
| const fruit = this.fruitList.find(item => item.id === id) |
| |
| fruit.num++ |
| }, |
| sub (id) { |
| |
| const fruit = this.fruitList.find(item => item.id === id) |
| |
| fruit.num-- |
| } |
| } |
| }) |
| </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" /> |
| <link rel="stylesheet" href="./css/inputnumber.css" /> |
| <link rel="stylesheet" href="./css/index.css" /> |
| <title>购物车</title> |
| </head> |
| <body> |
| <div class="app-container" id="app"> |
| |
| <div class="banner-box"><img src="http://autumnfish.cn/static/fruit.jpg" alt="" /></div> |
| |
| <div class="breadcrumb"> |
| <span>🏠</span> |
| / |
| <span>购物车</span> |
| </div> |
| |
| <div class="main" v-if="fruitList.length > 0"> |
| <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> |
| |
| <div class="tbody"> |
| <div v-for="(item, index) in fruitList" :key="item.id" class="tr" :class="{ active: item.isChecked }"> |
| <div class="td"><input type="checkbox" v-model="item.isChecked" /></div> |
| <div class="td"><img :src="item.icon" alt="" /></div> |
| <div class="td">{{ item.price }}</div> |
| <div class="td"> |
| <div class="my-input-number"> |
| <button :disabled="item.num <= 1" class="decrease" @click="sub(item.id)"> - </button> |
| <span class="my-input__inner">{{ item.num }}</span> |
| <button class="increase" @click="add(item.id)"> + </button> |
| </div> |
| </div> |
| <div class="td">{{ item.num * item.price }}</div> |
| <div class="td"><button @click="del(item.id)">删除</button></div> |
| </div> |
| </div> |
| </div> |
| |
| <div class="bottom"> |
| |
| <label class="check-all"> |
| <input type="checkbox" v-model="isAll"/> |
| 全选 |
| </label> |
| <div class="right-box"> |
| |
| <span class="price-box">总价 : ¥ <span class="price">{{ totalPrice }}</span></span> |
| |
| <button class="pay">结算( {{ totalCount }} )</button> |
| </div> |
| </div> |
| </div> |
| |
| <div class="empty" v-else>🛒空空如也</div> |
| </div> |
| <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> |
| <script> |
| const app = new Vue({ |
| el: '#app', |
| data: { |
| |
| fruitList: [ |
| { |
| id: 1, |
| icon: 'http://autumnfish.cn/static/火龙果.png', |
| isChecked: true, |
| num: 2, |
| price: 6, |
| }, |
| { |
| id: 2, |
| icon: 'http://autumnfish.cn/static/荔枝.png', |
| isChecked: false, |
| num: 7, |
| price: 20, |
| }, |
| { |
| id: 3, |
| icon: 'http://autumnfish.cn/static/榴莲.png', |
| isChecked: false, |
| num: 3, |
| price: 40, |
| }, |
| { |
| id: 4, |
| icon: 'http://autumnfish.cn/static/鸭梨.png', |
| isChecked: true, |
| num: 10, |
| price: 3, |
| }, |
| { |
| id: 5, |
| icon: 'http://autumnfish.cn/static/樱桃.png', |
| isChecked: false, |
| num: 20, |
| price: 34, |
| }, |
| ], |
| }, |
| computed: { |
| |
| |
| |
| |
| |
| |
| |
| isAll: { |
| get () { |
| return this.fruitList.every(item => item.isChecked) |
| }, |
| set (value) { |
| |
| this.fruitList.forEach(item => item.isChecked = value) |
| } |
| }, |
| |
| totalCount () { |
| return this.fruitList.reduce((sum, item) => { |
| if (item.isChecked) { |
| |
| return sum + item.num |
| } else { |
| |
| return sum |
| } |
| }, 0) |
| }, |
| |
| totalPrice () { |
| return this.fruitList.reduce((sum, item) => { |
| if (item.isChecked) { |
| return sum + item.num * item.price |
| } else { |
| return sum |
| } |
| }, 0) |
| } |
| |
| }, |
| methods: { |
| del (id) { |
| this.fruitList = this.fruitList.filter(item => item.id !== id) |
| }, |
| add (id) { |
| |
| const fruit = this.fruitList.find(item => item.id === id) |
| |
| fruit.num++ |
| }, |
| sub (id) { |
| |
| const fruit = this.fruitList.find(item => item.id === id) |
| |
| fruit.num-- |
| } |
| } |
| }) |
| </script> |
| </body> |
| </html> |
| |
复制
5. 本地持久化
| <!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"> |
| |
| <div class="banner-box"><img src="http://autumnfish.cn/static/fruit.jpg" alt="" /></div> |
| |
| <div class="breadcrumb"> |
| <span>🏠</span> |
| / |
| <span>购物车</span> |
| </div> |
| |
| <div class="main" v-if="fruitList.length > 0"> |
| <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> |
| |
| <div class="tbody"> |
| <div v-for="(item, index) in fruitList" :key="item.id" class="tr" :class="{ active: item.isChecked }"> |
| <div class="td"><input type="checkbox" v-model="item.isChecked" /></div> |
| <div class="td"><img :src="item.icon" alt="" /></div> |
| <div class="td">{{ item.price }}</div> |
| <div class="td"> |
| <div class="my-input-number"> |
| <button :disabled="item.num <= 1" class="decrease" @click="sub(item.id)"> - </button> |
| <span class="my-input__inner">{{ item.num }}</span> |
| <button class="increase" @click="add(item.id)"> + </button> |
| </div> |
| </div> |
| <div class="td">{{ item.num * item.price }}</div> |
| <div class="td"><button @click="del(item.id)">删除</button></div> |
| </div> |
| </div> |
| </div> |
| |
| <div class="bottom"> |
| |
| <label class="check-all"> |
| <input type="checkbox" v-model="isAll"/> |
| 全选 |
| </label> |
| <div class="right-box"> |
| |
| <span class="price-box">总价 : ¥ <span class="price">{{ totalPrice }}</span></span> |
| |
| <button class="pay">结算( {{ totalCount }} )</button> |
| </div> |
| </div> |
| </div> |
| |
| <div class="empty" v-else>🛒空空如也</div> |
| </div> |
| <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> |
| <script> |
| const defaultArr = [ |
| { |
| id: 1, |
| icon: 'http://autumnfish.cn/static/火龙果.png', |
| isChecked: true, |
| num: 2, |
| price: 6, |
| }, |
| { |
| id: 2, |
| icon: 'http://autumnfish.cn/static/荔枝.png', |
| isChecked: false, |
| num: 7, |
| price: 20, |
| }, |
| { |
| id: 3, |
| icon: 'http://autumnfish.cn/static/榴莲.png', |
| isChecked: false, |
| num: 3, |
| price: 40, |
| }, |
| { |
| id: 4, |
| icon: 'http://autumnfish.cn/static/鸭梨.png', |
| isChecked: true, |
| num: 10, |
| price: 3, |
| }, |
| { |
| id: 5, |
| icon: 'http://autumnfish.cn/static/樱桃.png', |
| isChecked: false, |
| num: 20, |
| price: 34, |
| }, |
| ] |
| const app = new Vue({ |
| el: '#app', |
| data: { |
| |
| fruitList: JSON.parse(localStorage.getItem('list')) || defaultArr, |
| }, |
| computed: { |
| |
| |
| |
| |
| |
| |
| |
| isAll: { |
| get () { |
| return this.fruitList.every(item => item.isChecked) |
| }, |
| set (value) { |
| |
| this.fruitList.forEach(item => item.isChecked = value) |
| } |
| }, |
| |
| totalCount () { |
| return this.fruitList.reduce((sum, item) => { |
| if (item.isChecked) { |
| |
| return sum + item.num |
| } else { |
| |
| return sum |
| } |
| }, 0) |
| }, |
| |
| totalPrice () { |
| return this.fruitList.reduce((sum, item) => { |
| if (item.isChecked) { |
| return sum + item.num * item.price |
| } else { |
| return sum |
| } |
| }, 0) |
| } |
| }, |
| methods: { |
| del (id) { |
| this.fruitList = this.fruitList.filter(item => item.id !== id) |
| }, |
| add (id) { |
| |
| const fruit = this.fruitList.find(item => item.id === id) |
| |
| fruit.num++ |
| }, |
| sub (id) { |
| |
| const fruit = this.fruitList.find(item => item.id === id) |
| |
| fruit.num-- |
| } |
| }, |
| watch: { |
| fruitList: { |
| deep: true, |
| handler (newValue) { |
| |
| localStorage.setItem('list', JSON.stringify(newValue)) |
| } |
| } |
| } |
| }) |
| </script> |
| </body> |
| </html> |
| |
复制


总结
欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下。
(博客的参考源码可以在我主页的资源里找到,如果在学习的过程中有什么疑问欢迎大家在评论区向我提出)