17_购物车案例
1、组件化布局
-
把静态页面转换成组件化模式
-
把组件渲染到页面上
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style type="text/css"> .container .cart { width: 300px; margin: auto; } .container .title { background-color: lightblue; height: 40px; line-height: 40px; text-align: center; /*color: #fff;*/ } .container .total { background-color: #FFCE46; height: 50px; line-height: 50px; text-align: right; } .container .total button { margin: 0 10px; background-color: #DC4C40; height: 35px; width: 80px; border: 0; } .container .total span { color: red; font-weight: bold; } .container .item { height: 55px; line-height: 55px; position: relative; border-top: 1px solid #ADD8E6; } .container .item img { width: 45px; height: 45px; margin: 5px; } .container .item .name { position: absolute; width: 90px; top: 0; left: 55px; font-size: 16px; } .container .item .change { width: 100px; position: absolute; top: 0; right: 50px; } .container .item .change a { font-size: 20px; width: 30px; text-decoration: none; background-color: lightgray; vertical-align: middle; } .container .item .change .num { width: 40px; height: 25px; } .container .item .del { position: absolute; top: 0; right: 0px; width: 40px; text-align: center; font-size: 40px; cursor: pointer; color: red; } .container .item .del:hover { background-color: orange; } </style> </head> <body> <div id="app"> <div class="container"> <my-cart></my-cart> </div> </div> <script type="text/javascript" src="js/vue.js"></script> <script type="text/javascript"> // 1、 把静态页面转换成组件化模式 // 1.1 标题组件 var CartTitle = { template: ` <div class="title">我的商品</div> ` } // 1.2 商品列表组件 var CartList = { template: ` <div> <div class="item"> <img src="img/a.jpg"/> <div class="name"></div> <div class="change"> <a href="">-</a> <input type="text" class="num" /> <a href="">+</a> </div> <div class="del">×</div> </div> <div class="item"> <img src="img/b.jpg"/> <div class="name"></div> <div class="change"> <a href="">-</a> <input type="text" class="num" /> <a href="">+</a> </div> <div class="del">×</div> </div> <div class="item"> <img src="img/c.jpg"/> <div class="name"></div> <div class="change"> <a href="">-</a> <input type="text" class="num" /> <a href="">+</a> </div> <div class="del">×</div> </div> <div class="item"> <img src="img/d.jpg"/> <div class="name"></div> <div class="change"> <a href="">-</a> <input type="text" class="num" /> <a href="">+</a> </div> <div class="del">×</div> </div> <div class="item"> <img src="img/e.jpg"/> <div class="name"></div> <div class="change"> <a href="">-</a> <input type="text" class="num" /> <a href="">+</a> </div> <div class="del">×</div> </div> </div> ` } // 1.3 商品结算组件 var CartTotal = { template: ` <div class="total"> <span>总价:123</span> <button>结算</button> </div> ` } // 1.4 定义一个全局组件 my-cart Vue.component('my-cart', { // 1.6 引入子组件 template: ` <div class='cart'> <cart-title></cart-title> <cart-list></cart-list> <cart-total></cart-total> </div> `, // 1.5 注册子组件 components: { 'cart-title': CartTitle, 'cart-list': CartList, 'cart-total': CartTotal } }); var vm = new Vue({ el: '#app', data: { } }); </script> </body> </html>
-
标题组件实现动态渲染
-
从父组件把标题数据传递过来 即 父向子组件传值
-
把传递过来的数据渲染到页面上
-
-
结算功能组件
-
从父组件把商品列表list 数据传递过来 即 父向子组件传值
-
把传递过来的数据计算最终价格渲染到页面上
-
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style type="text/css"> .container .cart { width: 300px; margin: auto; } .container .title { background-color: lightblue; height: 40px; line-height: 40px; text-align: center; /*color: #fff;*/ } .container .total { background-color: #FFCE46; height: 50px; line-height: 50px; text-align: right; } .container .total button { margin: 0 10px; background-color: #DC4C40; height: 35px; width: 80px; border: 0; } .container .total span { color: red; font-weight: bold; } .container .item { height: 55px; line-height: 55px; position: relative; border-top: 1px solid #ADD8E6; } .container .item img { width: 45px; height: 45px; margin: 5px; } .container .item .name { position: absolute; width: 90px; top: 0; left: 55px; font-size: 16px; } .container .item .change { width: 100px; position: absolute; top: 0; right: 50px; } .container .item .change a { font-size: 20px; width: 30px; text-decoration: none; background-color: lightgray; vertical-align: middle; } .container .item .change .num { width: 40px; height: 25px; } .container .item .del { position: absolute; top: 0; right: 0px; width: 40px; text-align: center; font-size: 40px; cursor: pointer; color: red; } .container .item .del:hover { background-color: orange; } </style> </head> <body> <div id="app"> <div class="container"> <my-cart></my-cart> </div> </div> <script type="text/javascript" src="js/vue.js"></script> <script type="text/javascript"> // 2.2 标题组件子组件通过props形式接收父组件传递过来的uname数据 var CartTitle = { props: ['uname'], template: ` <div class="title">{{uname}}的商品</div> ` } var CartList = { template: ` <div> <div class="item"> <img src="img/a.jpg"/> <div class="name"></div> <div class="change"> <a href="">-</a> <input type="text" class="num" /> <a href="">+</a> </div> <div class="del">×</div> </div> <div class="item"> <img src="img/b.jpg"/> <div class="name"></div> <div class="change"> <a href="">-</a> <input type="text" class="num" /> <a href="">+</a> </div> <div class="del">×</div> </div> <div class="item"> <img src="img/c.jpg"/> <div class="name"></div> <div class="change"> <a href="">-</a> <input type="text" class="num" /> <a href="">+</a> </div> <div class="del">×</div> </div> <div class="item"> <img src="img/d.jpg"/> <div class="name"></div> <div class="change"> <a href="">-</a> <input type="text" class="num" /> <a href="">+</a> </div> <div class="del">×</div> </div> <div class="item"> <img src="img/e.jpg"/> <div class="name"></div> <div class="change"> <a href="">-</a> <input type="text" class="num" /> <a href="">+</a> </div> <div class="del">×</div> </div> </div> ` } // 2.3 商品结算组件子组件通过props形式接收父组件传递过来的list数据 var CartTotal = { props: ['list'], template: ` <div class="total"> <span>总价:{{total}}</span> <button>结算</button> </div> `, // 2.4计算商品的总价并渲染到页面上 computed: { total: function () { // 计算商品的总价 var t = 0; this.list.forEach(item => { t += item.price * item.num; }); return t; } } } Vue.component('my-cart', { data: function () { return { uname: '张三', list: [{ id: 1, name: 'TCL彩电', price: 1000, num: 1, img: 'img/a.jpg' }, { id: 2, name: '机顶盒', price: 1000, num: 1, img: 'img/b.jpg' }, { id: 3, name: '海尔冰箱', price: 1000, num: 1, img: 'img/c.jpg' }, { id: 4, name: '小米手机', price: 1000, num: 1, img: 'img/d.jpg' }, { id: 5, name: 'PPTV电视', price: 1000, num: 2, img: 'img/e.jpg' }] } }, // 2.1 父组件向子组件以属性传递的形式 传递数据,向 标题组件 传递 uname 属性,向 商品结算组件 传递list属性 template: ` <div class='cart'> <cart-title :uname='uname'></cart-title> <cart-list></cart-list> <cart-total :list='list'></cart-total> </div> `, components: { 'cart-title': CartTitle, 'cart-list': CartList, 'cart-total': CartTotal } }); var vm = new Vue({ el: '#app', data: { } }); </script> </body> </html>
-
从父组件把商品列表list 数据传递过来 即 父向子组件传值
-
把传递过来的数据渲染到页面上
-
点击删除按钮的时候删除对应的数据
-
给按钮添加点击事件把需要删除的id传递过来
-
子组件中不推荐操作父组件的数据有可能多个子组件使用父组件的数据 我们需要把数据传递给父组件让父组件操作数据
-
父组件删除对应的数据
-
-
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style type="text/css"> .container .cart { width: 300px; margin: auto; } .container .title { background-color: lightblue; height: 40px; line-height: 40px; text-align: center; /*color: #fff;*/ } .container .total { background-color: #FFCE46; height: 50px; line-height: 50px; text-align: right; } .container .total button { margin: 0 10px; background-color: #DC4C40; height: 35px; width: 80px; border: 0; } .container .total span { color: red; font-weight: bold; } .container .item { height: 55px; line-height: 55px; position: relative; border-top: 1px solid #ADD8E6; } .container .item img { width: 45px; height: 45px; margin: 5px; } .container .item .name { position: absolute; width: 90px; top: 0; left: 55px; font-size: 16px; } .container .item .change { width: 100px; position: absolute; top: 0; right: 50px; } .container .item .change a { font-size: 20px; width: 30px; text-decoration: none; background-color: lightgray; vertical-align: middle; } .container .item .change .num { width: 40px; height: 25px; } .container .item .del { position: absolute; top: 0; right: 0px; width: 40px; text-align: center; font-size: 40px; cursor: pointer; color: red; } .container .item .del:hover { background-color: orange; } </style> </head> <body> <div id="app"> <div class="container"> <my-cart></my-cart> </div> </div> <script type="text/javascript" src="js/vue.js"></script> <script type="text/javascript"> var CartTitle = { props: ['uname'], template: ` <div class="title">{{uname}}的商品</div> ` } // 3.2 把列表数据动态渲染到页面上 var CartList = { props: ['list'], // 3.3 给按钮添加点击事件把需要删除的id传递过来 template: ` <div> <div :key='item.id' v-for='item in list' class="item"> <img :src="item.img"/> <div class="name">{{item.name}}</div> <div class="change"> <a href="">-</a> <input type="text" class="num" /> <a href="">+</a> </div> <div class="del" @click='del(item.id)'>×</div> </div> </div> `, methods: { del: function (id) { // 3.4 子组件中不推荐操作父组件的数据有可能多个子组件使用父组件的数据,我们需要把数据传递给父组件 让父组件操作数据 // 把id传递给父组件 this.$emit('cart-del', id); } } } var CartTotal = { props: ['list'], template: ` <div class="total"> <span>总价:{{total}}</span> <button>结算</button> </div> `, computed: { total: function () { // 计算商品的总价 var t = 0; this.list.forEach(item => { t += item.price * item.num; }); return t; } } } Vue.component('my-cart', { data: function () { return { uname: '张三', list: [{ id: 1, name: 'TCL彩电', price: 1000, num: 1, img: 'img/a.jpg' }, { id: 2, name: '机顶盒', price: 1000, num: 1, img: 'img/b.jpg' }, { id: 3, name: '海尔冰箱', price: 1000, num: 1, img: 'img/c.jpg' }, { id: 4, name: '小米手机', price: 1000, num: 1, img: 'img/d.jpg' }, { id: 5, name: 'PPTV电视', price: 1000, num: 2, img: 'img/e.jpg' }] } }, // 3.1 从父组件把商品列表list 数据传递过来即父向子组件传值 // 3.5 父组件通过事件绑定 接收子组件传递过来的数据 template: ` <div class='cart'> <cart-title :uname='uname'></cart-title> <cart-list :list='list' @cart-del='delCart($event)'></cart-list> <cart-total :list='list'></cart-total> </div> `, components: { 'cart-title': CartTitle, 'cart-list': CartList, 'cart-total': CartTotal }, methods: { // 3.6 根据id删除list中对应的数据 delCart: function (id) { // 根据id删除list中对应的数据 // 1、找到id所对应数据的索引 var index = this.list.findIndex(item => { return item.id == id; }); // 2、根据索引删除对应数据 this.list.splice(index, 1); } } }); var vm = new Vue({ el: '#app', data: { } }); </script> </body> </html>
-
将输入框中的默认数据动态渲染出来
-
输入框失去焦点的时候 更改商品的数量
-
子组件中不推荐操作数据 把这些数据传递给父组件 让父组件处理这些数据
-
父组件中接收子组件传递过来的数据并处理
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style type="text/css"> .container .cart { width: 300px; margin: auto; } .container .title { background-color: lightblue; height: 40px; line-height: 40px; text-align: center; /*color: #fff;*/ } .container .total { background-color: #FFCE46; height: 50px; line-height: 50px; text-align: right; } .container .total button { margin: 0 10px; background-color: #DC4C40; height: 35px; width: 80px; border: 0; } .container .total span { color: red; font-weight: bold; } .container .item { height: 55px; line-height: 55px; position: relative; border-top: 1px solid #ADD8E6; } .container .item img { width: 45px; height: 45px; margin: 5px; } .container .item .name { position: absolute; width: 90px; top: 0; left: 55px; font-size: 16px; } .container .item .change { width: 100px; position: absolute; top: 0; right: 50px; } .container .item .change a { font-size: 20px; width: 30px; text-decoration: none; background-color: lightgray; vertical-align: middle; } .container .item .change .num { width: 40px; height: 25px; } .container .item .del { position: absolute; top: 0; right: 0px; width: 40px; text-align: center; font-size: 40px; cursor: pointer; color: red; } .container .item .del:hover { background-color: orange; } </style> </head> <body> <div id="app"> <div class="container"> <my-cart></my-cart> </div> </div> <script type="text/javascript" src="js/vue.js"></script> <script type="text/javascript"> var CartTitle = { props: ['uname'], template: ` <div class="title">{{uname}}的商品</div> ` } var CartList = { props: ['list'], // 1. 将输入框中的默认数据动态渲染出来 // 2. 输入框失去焦点的时候,更改商品的数量需要将当前商品的id传递过来 template: ` <div> <div :key='item.id' v-for='item in list' class="item"> <img :src="item.img"/> <div class="name">{{item.name}}</div> <div class="change"> <a href="">-</a> <input type="text" class="num" :value='item.num' @blur='changeNum(item.id, $event)'/> <a href="">+</a> </div> <div class="del" @click='del(item.id)'>×</div> </div> </div> `, methods: { // 3、子组件中不推荐操作数据 因为别的组件可能也引用了这些数据 // 把这些数据传递给父组件让父组件处理这些数据 changeNum: function (id, event) { this.$emit('change-num', { id: id, num: event.target.value }); }, del: function (id) { // 把id传递给父组件 this.$emit('cart-del', id); } } } var CartTotal = { props: ['list'], template: ` <div class="total"> <span>总价:{{total}}</span> <button>结算</button> </div> `, computed: { total: function () { // 计算商品的总价 var t = 0; this.list.forEach(item => { t += item.price * item.num; }); return t; } } } Vue.component('my-cart', { data: function () { return { uname: '张三', list: [{ id: 1, name: 'TCL彩电', price: 1000, num: 1, img: 'img/a.jpg' }, { id: 2, name: '机顶盒', price: 1000, num: 1, img: 'img/b.jpg' }, { id: 3, name: '海尔冰箱', price: 1000, num: 1, img: 'img/c.jpg' }, { id: 4, name: '小米手机', price: 1000, num: 1, img: 'img/d.jpg' }, { id: 5, name: 'PPTV电视', price: 1000, num: 2, img: 'img/e.jpg' }] } }, // 4、父组件中接收子组件传递过来的数据 template: ` <div class='cart'> <cart-title :uname='uname'></cart-title> <cart-list :list='list' @change-num='changeNum($event)' @cart-del='delCart($event)'></cart-list> <cart-total :list='list'></cart-total> </div> `, components: { 'cart-title': CartTitle, 'cart-list': CartList, 'cart-total': CartTotal }, methods: { changeNum: function (val) { // 5、根据子组件传递过来的数据,跟新list中对应的数据 this.list.some(item => { if (item.id == val.id) { item.num = val.num; // 终止遍历 return true; } }); }, delCart: function (id) { // 根据id删除list中对应的数据 // 1、找到id所对应数据的索引 var index = this.list.findIndex(item => { return item.id == id; }); // 2、根据索引删除对应数据 this.list.splice(index, 1); } } }); var vm = new Vue({ el: '#app', data: { } }); </script> </body> </html>
5. 实现组件更新数据功能-下
-
子组件通过一个标识符来标记对用的用户点击 + - 或者输入框输入的内容
-
父组件拿到标识符更新对应的组件
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style type="text/css"> .container .cart { width: 300px; margin: auto; } .container .title { background-color: lightblue; height: 40px; line-height: 40px; text-align: center; /*color: #fff;*/ } .container .total { background-color: #FFCE46; height: 50px; line-height: 50px; text-align: right; } .container .total button { margin: 0 10px; background-color: #DC4C40; height: 35px; width: 80px; border: 0; } .container .total span { color: red; font-weight: bold; } .container .item { height: 55px; line-height: 55px; position: relative; border-top: 1px solid #ADD8E6; } .container .item img { width: 45px; height: 45px; margin: 5px; } .container .item .name { position: absolute; width: 90px; top: 0; left: 55px; font-size: 16px; } .container .item .change { width: 100px; position: absolute; top: 0; right: 50px; } .container .item .change a { font-size: 20px; width: 30px; text-decoration: none; background-color: lightgray; vertical-align: middle; } .container .item .change .num { width: 40px; height: 25px; } .container .item .del { position: absolute; top: 0; right: 0px; width: 40px; text-align: center; font-size: 40px; cursor: pointer; color: red; } .container .item .del:hover { background-color: orange; } </style> </head> <body> <div id="app"> <div class="container"> <my-cart></my-cart> </div> </div> <script type="text/javascript" src="js/vue.js"></script> <script type="text/javascript"> var CartTitle = { props: ['uname'], template: ` <div class="title">{{uname}}的商品</div> ` } var CartList = { props: ['list'], // 1. "+-" 按钮绑定事件 template: ` <div> <div :key='item.id' v-for='item in list' class="item"> <img :src="item.img"/> <div class="name">{{item.name}}</div> <div class="change"> <a href="" @click.prevent='sub(item.id)'>-</a> <input type="text" class="num" :value='item.num' @blur='changeNum(item.id, $event)'/> <a href="" @click.prevent='add(item.id)'>+</a> </div> <div class="del" @click='del(item.id)'>×</div> </div> </div> `, methods: { changeNum: function (id, event) { this.$emit('change-num', { id: id, type: 'change', num: event.target.value }); }, // 2、数量的增加和减少通过父组件来计算,每次都是加1和减1不需要传递数量 // 父组件需要一个类型来判断是"加1"还是"减1"以及是输入框输入的数据,我们通过type标识符来标记不同的操作 sub: function (id) { this.$emit('change-num', { id: id, type: 'sub' }); }, // 2、数量的增加和减少通过父组件来计算,每次都是加1和减1不需要传递数量 // 父组件需要一个类型来判断是"加1"还是"减1"以及是输入框输入的数据,我们通过type标识符来标记不同的操作 add: function (id) { this.$emit('change-num', { id: id, type: 'add' }); }, del: function (id) { // 把id传递给父组件 this.$emit('cart-del', id); } } } var CartTotal = { props: ['list'], template: ` <div class="total"> <span>总价:{{total}}</span> <button>结算</button> </div> `, computed: { total: function () { // 计算商品的总价 var t = 0; this.list.forEach(item => { t += item.price * item.num; }); return t; } } } Vue.component('my-cart', { data: function () { return { uname: '张三', list: [{ id: 1, name: 'TCL彩电', price: 1000, num: 1, img: 'img/a.jpg' }, { id: 2, name: '机顶盒', price: 1000, num: 1, img: 'img/b.jpg' }, { id: 3, name: '海尔冰箱', price: 1000, num: 1, img: 'img/c.jpg' }, { id: 4, name: '小米手机', price: 1000, num: 1, img: 'img/d.jpg' }, { id: 5, name: 'PPTV电视', price: 1000, num: 2, img: 'img/e.jpg' }] } }, // 3、父组件通过事件监听接收子组件的数据 template: ` <div class='cart'> <cart-title :uname='uname'></cart-title> <cart-list :list='list' @change-num='changeNum($event)' @cart-del='delCart($event)'></cart-list> <cart-total :list='list'></cart-total> </div> `, components: { 'cart-title': CartTitle, 'cart-list': CartList, 'cart-total': CartTotal }, methods: { changeNum: function (val) { // 4、分为三种情况:输入域变更、加号变更、减号变更 if (val.type == 'change') { // 根据子组件传递过来的数据,跟新list中对应的数据 this.list.some(item => { if (item.id == val.id) { item.num = val.num; // 终止遍历 return true; } }); } else if (val.type == 'sub') { // 减一操作 this.list.some(item => { if (item.id == val.id) { item.num -= 1; // 终止遍历 return true; } }); } else if (val.type == 'add') { // 加一操作 this.list.some(item => { if (item.id == val.id) { item.num += 1; // 终止遍历 return true; } }); } }, delCart: function (id) { // 根据id删除list中对应的数据 // 1、找到id所对应数据的索引 var index = this.list.findIndex(item => { return item.id == id; }); // 2、根据索引删除对应数据 this.list.splice(index, 1); } } }); var vm = new Vue({ el: '#app', data: { } }); </script> </body> </html>