案例 - 购物车
1.0 案例-购物车-项目初始化
目标: 初始化新项目, 清空不要的东西, 下载bootstrap库, 下载less模块
vue create shopcar
yarn add bootstrap
yarn add less less-loader@5.0.0 -D
图示:
- 按照需求, 把项目页面拆分成几个组件, 在components下创建
-
MyHeader组件 -
MyFooter组件 -
MyGoods组件 - 商品 -
MyCount组件
-
然后引入到App.vue上注册 -
在main.js中引入bootStrap库
import "bootstrap/dist/css/bootstrap.css"
素材
MyHeader.vue
<template>
<div class="my-header">购物车案例</div>
</template>
<script>
export default {
}
</script>
<style lang="less" scoped>
.my-header {
height: 45px;
line-height: 45px;
text-align: center;
background-color: #1d7bff;
color: #fff;
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 2;
}
</style>
MyGoods.vue
<template>
<div class="my-goods-item">
<div class="left">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="input"
>
<label class="custom-control-label" for="input">
<img src="http://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg" alt="">
</label>
</div>
</div>
<div class="right">
<div class="top">商品名字</div>
<div class="bottom">
<span class="price">¥ 100</span>
<span>
数量组件
</span>
</div>
</div>
</div>
</template>
<script>
export default {
}
</script>
<style lang="less" scoped>
.my-goods-item {
display: flex;
padding: 10px;
border-bottom: 1px solid #ccc;
.left {
img {
width: 120px;
height: 120px;
margin-right: 8px;
border-radius: 10px;
}
.custom-control-label::before,
.custom-control-label::after {
top: 50px;
}
}
.right {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
.top{
font-size: 14px;
font-weight: 700;
}
.bottom {
display: flex;
justify-content: space-between;
padding: 5px 0;
align-items: center;
.price {
color: red;
font-weight: bold;
}
}
}
}
</style>
目标: 完成商品组件右下角商品组件的开发
components/MyCount.vue
<template>
<div class="my-counter">
<button type="button" class="btn btn-light" >-</button>
<input type="number" class="form-control inp" >
<button type="button" class="btn btn-light">+</button>
</div>
</template>
<script>
export default {
}
</script>
<style lang="less" scoped>
.my-counter {
display: flex;
.inp {
width: 45px;
text-align: center;
margin: 0 10px;
}
.btn, .inp{
transform: scale(0.9);
}
}
</style>
components/MyFooter.vue
<template>
<div class="my-footer">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="footerCheck">
<label class="custom-control-label" for="footerCheck">全选</label>
</div>
<div>
<span>合计:</span>
<span class="price">¥ 0</span>
</div>
<button type="button" class="footer-btn btn btn-primary">结算 ( 0 )</button>
</div>
</template>
<script>
export default {
}
</script>
<style lang="less" scoped>
.my-footer {
position: fixed;
z-index: 2;
bottom: 0;
width: 100%;
height: 50px;
border-top: 1px solid #ccc;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 10px;
background: #fff;
.price {
color: red;
font-weight: bold;
font-size: 15px;
}
.footer-btn {
min-width: 80px;
height: 30px;
line-height: 30px;
border-radius: 25px;
padding: 0;
}
}
</style>
示例
01-案例_购物车.vue
<template>
<div>
<MyHeader></MyHeader>
<div class="main">
<MyGoods></MyGoods>
</div>
<MyFooter></MyFooter>
</div>
</template>
<script>
import MyHeader from "./components/MyHeader.vue"
import MyGoods from "./components/MyGoods.vue"
import MyFooter from "./components/MyFooter.vue"
export default {
components: {
MyHeader,
MyGoods,
MyFooter,
}
}
</script>
<style scoped>
.main{
padding-top: 45px;
padding-bottom: 50px;
}
</style>
MyHeader.vue
<template>
<div class="my-header">购物车案例</div>
</template>
<script>
export default {
}
</script>
<style lang="less" scoped>
.my-header {
height: 45px;
line-height: 45px;
text-align: center;
background-color: #1d7bff;
color: #fff;
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 2;
}
</style>
MyGoods.vue
<template>
<div class="my-goods-item">
<div class="left">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="input"
>
<label class="custom-control-label" for="input">
<img src="http://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg" alt="">
</label>
</div>
</div>
<div class="right">
<div class="top">商品名字</div>
<div class="bottom">
<span class="price">¥ 100</span>
<span>
<MyCount></MyCount>
</span>
</div>
</div>
</div>
</template>
<script>
import MyCount from "./MyCount.vue"
export default {
components: {
MyCount,
}
}
</script>
<style lang="less" scoped>
.my-goods-item {
display: flex;
padding: 10px;
border-bottom: 1px solid #ccc;
.left {
img {
width: 120px;
height: 120px;
margin-right: 8px;
border-radius: 10px;
}
.custom-control-label::before,
.custom-control-label::after {
top: 50px;
}
}
.right {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
.top{
font-size: 14px;
font-weight: 700;
}
.bottom {
display: flex;
justify-content: space-between;
padding: 5px 0;
align-items: center;
.price {
color: red;
font-weight: bold;
}
}
}
}
</style>
MyCount.vue
<template>
<div class="my-counter">
<button type="button" class="btn btn-light" >-</button>
<input type="number" class="form-control inp" >
<button type="button" class="btn btn-light">+</button>
</div>
</template>
<script>
export default {
}
</script>
<style lang="less" scoped>
.my-counter {
display: flex;
.inp {
width: 45px;
text-align: center;
margin: 0 10px;
}
.btn, .inp{
transform: scale(0.9);
}
}
</style>
MyFooter.vue
<template>
<div class="my-footer">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="footerCheck">
<label class="custom-control-label" for="footerCheck">全选</label>
</div>
<div>
<span>合计:</span>
<span class="price">¥ 0</span>
</div>
<button type="button" class="footer-btn btn btn-primary">结算 ( 0 )</button>
</div>
</template>
<script>
export default {
}
</script>
<style lang="less" scoped>
.my-footer {
position: fixed;
z-index: 2;
bottom: 0;
width: 100%;
height: 50px;
border-top: 1px solid #ccc;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 10px;
background: #fff;
.price {
color: red;
font-weight: bold;
font-size: 15px;
}
.footer-btn {
min-width: 80px;
height: 30px;
line-height: 30px;
border-radius: 25px;
padding: 0;
}
}
</style>
1.1 案例-购物车-头部自定义
目的: 头部的标题, 颜色, 背景色可以随便修改, props类型的校验
思路
- 在MyHeader.vue中准备props里变量, 然后使用
- 在使用MyHeader.vue组件时, 传入相应的值 (color和backgroundColor)
MyHeader.vue
<template>
<div class="my-header"
:style="{
backgroundColor:background,
color:color,
}"
>{{ title }}</div>
</template>
<script>
export default {
props: {
background:{
type:String
},
color:{
type:String,
default:'#fff',
},
title:{
type:String,
required:true,
}
}
}
</script>
<style lang="less" scoped>
.my-header {
height: 45px;
line-height: 45px;
text-align: center;
background-color: #1d7bff;
color: #fff;
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 2;
}
</style>
购物车.vue
<template>
<div>
<MyHeader
title="购物车案例64"
background="#0d6efd"
></MyHeader>
<div class="main">
<MyGoods></MyGoods>
</div>
<MyFooter></MyFooter>
</div>
</template>
<script>
import MyHeader from "./components/MyHeader.vue"
import MyGoods from "./components/MyGoods.vue"
import MyFooter from "./components/MyFooter.vue"
export default {
components: {
MyHeader,
MyGoods,
MyFooter,
}
}
</script>
<style scoped>
.main{
padding-top: 45px;
padding-bottom: 50px;
}
</style>
App.vue传入相应自定义的值
<MyHeader title="购物车案例"></MyHeader>
总结:
props: [] - 只能声明变量和接收, 不能类型校验
props: {} - 声明变量和校验类型规则 - 外部传入值不对则报错
1.2 案例-购物车-请求数据
目标: 使用axios把数据请求回来
数据地址: https://www.escook.cn/api/cart (get方式)
- 下载axios
yarn add axios
- main.js - 原型上挂载
import axios from 'axios'
axios.defaults.baseURL = "https://www.escook.cn"
Vue.prototype.$axios = axios
- App.vue请求使用
<script>
export default {
data(){
return {
list: []
}
},
created(){
console.log(this);
this.$axios({
url: "/api/cart"
}).then(res => {
this.list = res.data.list
})
}
}
</script>
1.3 案例-购物车-数据渲染
目标: 把上面请求的数据, 铺设到页面上
App.vue
<div class="main">
<MyGoods
v-for="item in list"
:key="item.id"
:item="item"
></MyGoods>
</div>
MyGoods.vue
<template>
<div class="my-goods-item">
<div class="left">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="input" v-model="item.goods_state"
>
<label class="custom-control-label" for="input">
<img :src="item.goods_img" alt="">
</label>
</div>
</div>
<div class="right">
<div class="top">{{item.goods_name}}</div>
<div class="bottom">
<span class="price">¥ {{item.goods_price}} </span>
<span>
<MyCount :item="item"></MyCount>
</span>
</div>
</div>
</div>
</template>
<script>
import MyCount from "./MyCount.vue"
export default {
props: {
item:Object,
},
components: {
MyCount,
}
}
</script>
MyCount.vue
<template>
<div class="my-counter">
<button type="button" class="btn btn-light" >-</button>
<input type="number" class="form-control inp" v-model="item.goods_count">
<button type="button" class="btn btn-light">+</button>
</div>
</template>
<script>
export default {
props: {
item:Object,
}
}
</script>
总结: 把各个组件关联起来, 把数据都铺设到页面上
1.4 案例-购物车-商品选中
问题: 点击发现总是第一个被选中
原来id和for都是"input"
但是id是唯一的啊, 所以用数据的id来作为标签的id, 分别独立, 为了兼容label点击图片也能选中的效果
MyGoods.vue
<input type="checkbox" class="custom-control-input" :id="item.id" v-model="item.goods_state"
>
<label class="custom-control-label" :for="item.id">
<img :src="item.goods_img" alt="">
</label>
总结: lable的for值对应input的id, 点击label就能让对应input处于激活
1.5 案例-购物车-数量控制
目标: 点击+和-或者直接修改输入框的值影响商品购买的数量
MyCount.vue
<template>
<div class="my-counter">
<button type="button" class="btn btn-light" @click="item.goods_count-- " :disabled=" item.goods_count === 1">-</button>
<input type="number" class="form-control inp" v-model.number="item.goods_count">
<button type="button" class="btn btn-light" @click="item.goods_count++ ">+</button>
</div>
</template>
<script>
export default {
props: {
item:Object,
},
watch: {
item:{
deep:true,
handler(){
if(this.item.goods_count < 1){
this.item.goods_count = 1
}
}
}
}
}
</script>
1.6 案例-购物车-全选功能
目标: 在底部组件上, 完成全选功能
思路:
- 点击获取它的选中状态
- 同步给上面每个小选框 - 而小选框的选中状态又在数组里
- 把数组传给MyFooter, 然后更新即可 - 因为对象都是引用关系的
MyFooter.vue
<template>
<div class="my-footer">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="footerCheck" v-model="isAll">
<label class="custom-control-label" for="footerCheck">全选</label>
</div>
<div>
<span>合计:</span>
<span class="price">¥ 0</span>
</div>
<button type="button" class="footer-btn btn btn-primary">结算 ( 0 )</button>
</div>
</template>
<script>
export default {
props: {
arr:Array
},
computed:{
isAll:{
get(){
return this.arr.every( (item) => item.goods_state === true )
},
set(val){
this.arr.forEach( (item) => (item.goods_state = val))
},
},
},
}
</script>
App.vue
<MyFooter :arr="list"></MyFooter>
总结: 全选的v-model的值, 使用计算属性完整写法
1.7 案例-购物车-总数量
目标: 完成底部组件, 显示选中的商品的总数量
MyFooter.vue
<template>
<div class="my-footer">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="footerCheck" v-model="isAll">
<label class="custom-control-label" for="footerCheck">全选</label>
</div>
<div>
<span>合计:</span>
<span class="price">¥ 0</span>
</div>
<button type="button" class="footer-btn btn btn-primary">结算 ( {{allCount}} )</button>
</div>
</template>
<script>
export default {
props: {
arr:Array
},
computed:{
isAll:{
get(){
return this.arr.every( (item) => item.goods_state === true )
},
set(val){
this.arr.forEach( (item) => (item.goods_state = val))
},
},
allCount(){
return this.arr.reduce((sum,item) => {
if(item.goods_state){
sum += item.goods_count
}
return sum
},0)
},
},
}
</script>
1.8 案例-购物车-总价
目标: 完成选中商品计算价格
MyFooter.vue
<template>
<div class="my-footer">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="footerCheck" v-model="isAll">
<label class="custom-control-label" for="footerCheck">全选</label>
</div>
<div>
<span>合计:</span>
<span class="price">¥ {{allPrice}}</span>
</div>
<button type="button" class="footer-btn btn btn-primary">结算 ( {{ allCount }} )</button>
</div>
</template>
<script>
export default {
props: {
arr:Array
},
computed:{
isAll:{
get(){
return this.arr.every( (item) => item.goods_state === true )
},
set(val){
this.arr.forEach( (item) => (item.goods_state = val))
},
},
allCount(){
return this.arr.reduce((sum,item) => {
if(item.goods_state){
sum += item.goods_count
}
return sum
},0)
},
allPrice(){
return this.arr.reduce((sum,item) => {
if(item.goods_state){
sum += item.goods_count * item.goods_price
}
return sum
},0)
}
},
}
</script>
总结: 把数组传给了MyFooter组件, 统计总价
|