项目初始化:
需求1:从0新建项目
需求2:分拆组件,创建组件文件
分析:
①:vue命令创建项目(在根目录下运行)
vue create shopcar-deom
②:下载需要得配置:
yarn add less less-loader bootstrap
③:main.js - 引入bootst 样式
// 直接引入 bootstrap 的css文件样式
import 'bootstrap/dist/css/bootstrap.css'
补充:下载时候可能会因为网络出问题
//查看当前源
yarn config get registry
//设置淘宝源或内网源
yarn config set registry https://registry.npm.taobao.org --global
//恢复源
yarn config set registry https://registry.yarnpkg.com --global
准备代码:
MyHeader:
<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:
<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';
export default {
components: {
MyCount
}
}
</script>
<style lang="less">
.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:
<template>
<div class="my-counter">
<button type="button" class="btn btn-light">-</button>
<input type="" class="form-control inp" placeholder="10">
<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:
<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">¥ 100</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>
App.vue盒子优化:
<template>
<div>
<MyHeader></MyHeader>
<div class="main">
<MyGoods></MyGoods>
</div>
<MyFooter></MyFooter>
</div>
</template>
<script>
import MyHeader from './components/MyHeader'
import MyGoods from './components/MyGoods';
import MyFooter from './components/MyFooter';
export default {
data() {
return {}
},
components: {
MyHeader,
MyGoods,
MyFooter,
}
}
</script>
<style scoped>
.main {
padding: 45px 0 50px;
}
</style>
头部自定义
我们的组件要自定义(大致样式一样,颜色,字体我们要变化的来适应不同场景)
分析:
①:背景色定义props里变量接收使用
②:文字颜色定义props里变量接收-设默认值
③:文字内容定义props里变量接收-设必传值
④:使用组件传入具体值(父传子)
props有两种定义方式:
props: [ ] - 只声明变量,不能类型校验
props: { } - 声明变量和校验类型规则 - 不对就会报错
数据获取
分析:
①:下载axios,在main,jsl入(以前是单独引入一个vue文件里面)
yarn add axios
②:配置基础地址,https:/www.escook.cn
③:axios挂载到Vue原型上,作为全局属性
④:App.vue的created中使用全局属性axios
⑤:接口地址为/api/cart
// 目标:请求数据 - 打印
// 1. 下载axios库,main.js - 全局绑定属性(所有 .vue 文件可以访问到axios方法)
import axios from 'axios'
// 2. 设置基础地址
axios.defaults.baseURL = 'https://www.escook.cn'
// 3. axios方法添加到原型上($axios这个名字是自定义的)
Vue.prototype.$axios = axios
数据铺设
需求:把数据使用MyGoods组件展示
分析:
①:App.vue把数据保存到data定义的变量上
②:使用v-for循环数组使用组件
③:分别给商品组件传入数据对象
④:商品组件接收后,使用对象里字段数据展示
商品选中
分析:
①:给v-model关联到小选框上,数据关联对象的goods_state
②:注意点图片用label关联的小选框
③:label的for值要和表单标签id一致,点label才触发小选框
④:id属性分别使用商品的id即可
- label标签关联表单,点击label标签上,相当于点击表单
- 对象传值赋值传的是引用类型的堆内存地址,多处使用共同的这个对象/数组
数量控制
分析:
①:给MyCount组件传入数据对象关联
②:输入框v-model关联对象里数量属性
③:增加或减少修改对象数量属性
④:控制商品最少1件
先控制按钮,然后侦听输入框值,强制覆盖
全选
分析:
①:v-model用计算属性,关联全选框
②:在set方法里,获取全选状态
③:回传到App.vue再同步给所有小选框
④:在get方法里,统计小选框给全选复制
全选和小选框,互相影响的思路:
- 全选-关联计算属性-set方法-同步所有小选
- 小选-触发计算属性的get方法-统计后返回全选状态
总数量
分析:
①:计算属性allCount变量
②:在统计数组里数据时,要判断勾选状态才累加数量(判断)
③:把累加好数量返回给allCount变量使用显示
// 核心代码-计算属性
allCount() {
// reduce() 的计算方法
return this.arr.reduce((sum, obj) => { //整体返回计算的结果值
if (obj.goods_state == true) {// 选中商品才累加数量
sum += obj.goods_count;
}
return sum //内部返回供下次使用
}, 0)
},
总价
分析:
①:计算属性allPrice变量
②:在统计数组里数据时,要判断勾选状态才累加
③:把价格和数量先想乘,再累加
④:累加好数据返回给allPrice变量使用显示
// 核心代码-计算属性
allPrice() {
return this.arr.reduce((sum, obj) => {
if (obj.goods_state == true) {
sum += obj.goods_count * obj.goods_price
}
return sum
}, 0)
}
最终代码
App.vue:
<template>
<div>
<!-- 标签内属性加冒号是变量,不加是固定字符串 -->
<MyHeader background="skyblue" title="HB的购物车"></MyHeader>
<div class="main">
<MyGoods v-for="obj in list" :key="obj.id" :obj="obj"></MyGoods>
</div>
<MyFooter style="boredr:10px" @changeAllFn="allFn" :arr="list"></MyFooter>
</div>
</template>
<script>
//目标:数据铺设到MyGoods组件上
//l.数据在data保存一下(页面只能用data里值)
//2.页面v-for循环MyGoods:组件
//3.分别传入obj数据对象(一对一关系)I
//4.内部使用数据对象值
import MyHeader from './components/MyHeader'
import MyGoods from './components/MyGoods';
import MyFooter from './components/MyFooter';
export default {
data() {
return {
list: [] // 商品所有数据
}
},
components: {
MyHeader,
MyGoods,
MyFooter,
},
created() {
// 思考这个用法这是调用函数
this.$axios({
url: '/api/cart'
}).then(res => {
this.list = res.data.list //这里必须得用箭头函数才能这样用 this
console.log(this.list);
})
},
methods: {
allFn(val) {
// this.list.goods_state = val
this.list.forEach(obj => obj.goods_state = val)
// 把MyFooter内的全选状态值同步给所有小选框的关联属性上
}
}
}
</script>
<style scoped>
.main {
padding: 45px 0 50px;
}
</style>
MyCount.vue:
<template>
<div class="my-counter">
<!-- obj.goods_count > 1 && obj.goods_count-- 大于一才能减一操作 -->
<button type="button" class="btn btn-light" @click="obj.goods_count > 1 && obj.goods_count--"
:disabled='obj.goods_count==1'>-</button>
<input class="form-control inp" v-model.number="obj.goods_count">
<button type="button" class="btn btn-light" @click="obj.goods_count++">+</button>
</div>
</template>
<script>
// 目标:商品数量 - 控制
// 1. 外部传入数据对象
// 2. v-model关联对象的goods_count属性和输入框(双向绑定!)
// 3. 商品按钮 +和- ,商品数量最小一件, 直接用表达式
// 4. 侦听数量改变,小于1,直接强制覆盖1
export default {
props: {
// 因为数量控制要通过对象“互相引用的关系”来影响外面对象里的数量值,所以最好传 对象进来
obj: Object // 商品对象
},
watch: {
obj: {
deep: true,
handler() {
// 为啥三元表达不可?
// this.obj.goods_count < 1 ? 1 : this.obj.goods_count
if (this.obj.goods_count < 1) {
this.obj.goods_count = 1
}
console.log(this.obj.goods_count);
}
}
}
}
</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" 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>
// 目标:全选
// 1. v-model 关联全选复选框(v-model后变量计算属性)
// 2. 页面(视图层)v(true) → 数据层(变量-) → 计算属性(完整写法)
// 3. 把全选的选中状态同步给所有的小选框的选中状态上
// 小选
// 从App里面把数组传过来 ,
// 看数组里面的 选中状态是不是全部满足条件,用 every()方法
// 在计算属性的 get() 里面
// 目标:总数量的统计
export default {
props: {
arr: Array,
},
computed: {
isAll: {
set(val) { // 所关联表单的值(true/false)
console.log(val);
this.$emit('changeAllFn', val)
},
get() {
// 查找小选框关联的属性有没有不符合勾选的条件 读下面语句
return this.arr.every(obj => obj.goods_state == true)
}
},
// 只需要统计的话,不用完整写法也可以
allCount() {
// reduce() 的计算方法
return this.arr.reduce((sum, obj) => { //整体返回计算的结果值
if (obj.goods_state == true) {// 选中商品才累加数量
sum += obj.goods_count;
}
return sum //内部返回供下次使用
}, 0)
},
allPrice() {
return this.arr.reduce((sum, obj) => {
if (obj.goods_state == true) {
sum += obj.goods_count * obj.goods_price
}
return sum
}, 0)
}
}
}
</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>
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="obj.id" v-model="obj.goods_state">
<label class="custom-control-label" :for="obj.id">
<img :src="obj.goods_img" alt="">
</label>
</div>
</div>
<div class="right">
<div class="top">{{ obj.goods_name }}</div>
<div class="bottom">
<span class="price">¥{{ obj.goods_price
}}</span>
<span>
<MyCount :obj="obj"></MyCount>
</span>
</div>
</div>
</div>
</template>
<script>
import MyCount from './MyCount';
export default {
components: {
MyCount
},
props: {
obj: Object
}
}
</script>
<style lang="less">
.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>
MyHeader.vue:
<template>
<!-- 像color,这样值和名一样,可以简写 color: color => color-->
<div class="my-header" :style="{ backgroundColor: background, color }">{{ title }}</div>
</template>
<script>
// 目标:让Header组件支持不同的项目 - 自定义
// 1. 分析那些可以自定义(背景色、颜色、文字内容)
// 2. ❤️可以对props的变量值,进行校验
// 3. 内部使用props变量值
// 4. 外部使用时,遵守变量名作为属性名,值得类型也要遵守
export default {
props: {
background: String,// 外部插入此变量的值,必须是字符串类型,否则报错
color: {
type: String,// 约束color的插入值类型
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.js创建一个购物车应用,包括项目初始化、组件划分、数据获取、商品展示、选中状态控制、数量增减、全选功能、总数量与总价计算等关键步骤,提供完整的代码示例。
1214

被折叠的 条评论
为什么被折叠?



