0


从零开始打造vue---购物车案例

购物车案例

1. 案例效果

2. 实现步骤

  • ① 初始化项目基本结构
  • ② 封装 EsHeader 组件
  • ③ 基于 axios 请求商品列表数据
  • ④ 封装 EsFooter 组件
  • ⑤ 封装 EsGoods 组件
  • ⑥ 封装 EsCounter 组件

1. 初始化项目结构

运行如下的命令,初始化 vite 项目:

  1. npm init vite-app code-cart
  2. cd code-cart
  3. npm install
  1. 清理项目结构:
  • 把 bootstrap 相关的文件放入 src/assets 目录下
  • 在 main.js 中导入 bootstrap.css
  • 清空 App.vue 组件
  • 删除 components 目录下的 HelloWorld.vue 组件
  1. 为组件的样式启用 less 语法
  1. npm i less -D

4.初始化 index.css 全局样式如下:

  1. :root {
  2. font-size: 12px;
  3. }

2. **封装 **es-header 组件

2.1 **创建并注册 **EsHeader **组件 **

  1. 在 src/components/es-header/ 目录下新建 EsHeader.vue 组件:
  1. <template>
  2. <div>EsHeader 组件</div>
  3. </template> <script>
  4. export default {
  5. name: 'EsHeader', }
  6. </script> <style lang="less" scoped></style>
  1. 在 App.vue 组件中导入并注册 EsHeader.vue 组件:
  1. // 导入 header 组件
  2. import EsHeader from './components/es-header/EsHeader.vue'
  3. export default {
  4. name: 'MyApp',
  5. components: {
  6. // 注册 header 组件
  7. EsHeader,
  8. },
  9. }
  1. 在 App.vue 的 template 模板结构中使用 EsHeader 组件:\
    1. <template>
    2. <div>
    3. <h1>App 根组件</h1>
    4. <!-- 使用 es-header 组件 -->
    5. <es-header></es-header>
    6. </div>
    7. </template>

2.2 **封装 **es-header **组件 **

  1. 封装需求:
  • 允许用户自定义 title 标题内容
  • 允许用户自定义 color 文字颜色
  • 允许用户自定义 bgcolor 背景颜色
  • 允许用户自定义 fsize 字体大小
  • es-header 组件必须固定定位到页面顶部的位置,高度为 45px,文本居中,z-index 为 999
  1. 在 es-header 组件中封装以下的 props 属性:
  1. export default {
  2. name: 'EsHeader',
  3. props: {
  4. title: { // 标题内容
  5. type: String,
  6. default: 'es-header',
  7. },
  8. bgcolor: { // 背景颜色
  9. type: String,
  10. default: '#007BFF',
  11. },
  12. color: { // 文字颜色
  13. type: String,
  14. default: '#ffffff',
  15. },
  16. fsize: { // 文字大小
  17. type: Number,
  18. default: 12,
  19. },
  20. },
  21. }
  1. 渲染标题内容,并动态为 DOM 元素绑定行内的 style 样式对象:
  1. <template>
  2. <div :style="{ color: color, backgroundColor: bgcolor, fontSize:
  3. fsize + 'px' }">{{ title }}</div>
  4. </template>
  1. 为 DOM 节点添加 header-container 类名,进一步美化 es-header 组件的样式:
  1. <template>
  2. <div class="header-container" :style="{ color: color,
  3. backgroundColor: bgcolor, fontSize: fsize + 'px' }">
  4. {{ title }}
  5. </div>
  6. </template> <style lang="less" scoped>
  7. .header-container {
  8. height: 45px;
  9. line-height: 45px;
  10. text-align: center;
  11. position: fixed;
  12. top: 0;
  13. left: 0;
  14. width: 100%;
  15. z-index: 999;
  16. }
  17. </style>
  1. 在 App 根组件中使用 es-header 组件时,通过 title 属性 指定 标题内容
  1. <template>
  2. <div class="app-container">
  3. <h1>App 根组件</h1>
  4. <!-- 为 es-header 组件指定 title 属性的值 -->
  5. <es-header title="购物车案例"></es-header>
  6. </div>
  7. </template>

3. **基于 **axios **请求商品列表数据 **

1.运行如下的命令安装 axios

npm i axios -S

2.在 main.js 入口文件中导入并全局配置 axios:

  1. import { createApp } from 'vue'
  2. import App from './App.vue'
  3. import './assets/css/bootstrap.css'
  4. import './index.css'
  5. // 导入 axios
  6. import axios from 'axios'
  7. const app = createApp(App)
  8. // 配置请求的根路径
  9. axios.defaults.baseURL = 'https://www.escook.cn'
  10. // 将 axios 挂载为全局的 $http 自定义属性
  11. app.config.globalProperties.$http = axios
  12. app.mount('#app')

3.2 **请求商品列表数据 **

  1. 在 App.vue 根组件中声明如下的 data 数据:
  1. data() {
  2. return {
  3. // 商品列表的数据
  4. goodslist: [],
  5. }
  6. },
  1. 在 App.vue 根组件的 created 生命周期函数中,**预调用 **获取商品列表数据 的 methods 方法
  1. / 组件实例创建完毕之后的生命周期函数
  2. created() {
  3. // 调用 methods 中的 getGoodsList 方法,请求商品列表的数据
  4. this.getGoodsList()
  5. },
  1. 在 Ap.vue 根组件的 methods 节点中,声明刚才预调用的 getGoodsList 方法:
  1. methods: {
  2. // 请求商品列表的数据
  3. async getGoodsList() {
  4. // 1. 通过组件实例 this 访问到全局挂载的 $http 属性,并发起
  5. Ajax 数据请求
  6. const { data: res } = await this.$http.get('/api/cart')
  7. // 2. 判断请求是否成功
  8. if (res.status !== 200) return alert('请求商品列表数据失败!')
  9. // 3. 将请求到的数据存储到 data 中,供页面渲染期间使用
  10. this.goodslist = res.list
  11. },
  12. },

4. **封装 **es-footer 组件

4.1 **创建并注册 **EsFooter **组件 **

1.在 src/components/es-footer/ 目录下新建 EsFooter.vue 组件:

  1. <template>
  2. <div>EsFooter 组件</div>
  3. </template> <script>
  4. export default {
  5. name: 'EsFooter', }
  6. </script> <style lang="less" scoped></style>
  1. 在 App.vue 组件中导入并注册 EsFooter.vue 组件:
  1. // 导入 header 组件
  2. import EsHeader from './components/es-header/EsHeader.vue'
  3. // 导入 footer 组件
  4. import EsFooter from './components/es-footer/EsFooter.vue'
  5. export default {
  6. name: 'MyApp',
  7. components: {
  8. // 注册 header 组件
  9. EsHeader,
  10. // 注册 footer 组件
  11. EsFooter,
  12. },
  13. } 6
  1. 在 App.vue 的 template 模板结构中使用 EsFooter 组件:
    1. <template>
    2. <div>
    3. <h1>App 根组件</h1>
    4. <!-- 使用 es-header 组件 -->
    5. <es-header></es-header>
    6. <!-- 使用 es-footer 组件 -->
    7. <es-footer></es-footer>
    8. </div>
    9. </template>

4.2 **封装 **es-footer **组件 **

  • es-footer 组件必须固定定位到 页面底部 的位置 高度为 50px **内容两端贴边对齐 **z-index :999
  • 允许用户自定义 amount 总价格(单位是元),并在渲染时 保留两位小数
  • 允许用户自定义 total 总数量 并渲染到 结算按钮中 如果要结算的商品数为0 则 禁用结算按钮
  • 允许用户自定义 isfull 全选按钮的选中状态
  • 允许用户通过 自定义事件 的形式,监听全选按钮 选中状态的变化 并获取到 最新的选中状态
  1. <!-- Footer 组件 -->
  2. <my-footer :isfull="false" :total="1" :amount="98"
  3. @fullChange="onFullStateChange"></my-footer>

4.2.1 **渲染组件的基础布局 **

  1. 将 EsFooter.vue 组件在页面底部进行固定定位
  1. <template>
  2. <div class="footer-container">EsFooter 组件</div>
  3. </template> <script>
  4. export default {
  5. name: 'EsFooter', }
  6. </script> <style lang="less" scoped>
  7. .footer-container {
  8. // 设置宽度和高度
  9. height: 50px;
  10. width: 100%;
  11. // 设置背景颜色和顶边框颜色
  12. background-color: white;
  13. border-top: 1px solid #efefef;
  14. // 底部固定定位
  15. position: fixed;
  16. bottom: 0;
  17. left: 0;
  18. // 内部元素的对齐方式
  19. display: flex;
  20. justify-content: space-between;
  21. align-items: center;
  22. // 设置左右 padding
  23. padding: 0 10px; }
  24. </style>
  1. 根据 bootstrap 提供的 Checkboxes https://v4.bootcss.com/docs/components/forms/#check

boxes 渲染左侧的 全选 按钮:

  1. <template>
  2. <div class="footer-container">
  3. <!-- 全选按钮 -->
  4. <div class="custom-control custom-checkbox">
  5. <input type="checkbox" class="custom-control-input"
  6. id="fullCheck" />
  7. <label class="custom-control-label" for="fullCheck">全选
  8. </label>
  9. </div>
  10. </div>
  11. </template>

并在全局样式表 index.css 中覆盖 全选 按钮的圆角样式:

  1. .custom-checkbox .custom-control-label::before {
  2. border-radius: 10px; }
  1. 渲染合计对应的价格区域:
  1. <template>
  2. <div class="footer-container">
  3. <!-- 全选按钮 -->
  4. <div class="custom-control custom-checkbox">
  5. <input type="checkbox" class="custom-control-input"
  6. id="fullCheck" />
  7. <label class="custom-control-label" for="fullCheck">全选
  8. </label>
  9. </div>
  10. <!-- 合计 -->
  11. <div>
  12. <span>合计:</span>
  13. <span class="amount">¥0.00</span>
  14. </div>
  15. </div>
  16. </template>

并在当前组件的 <style> 节点中美化总价格的样式

  1. .amount {
  2. color: red;
  3. font-weight: bold; }

4.根据 bootstrap 提供的渲染结算按钮

Buttons https://v4.bootcss.com/docs/components/buttons/#examples

  1. <template>
  2. <div class="footer-container">
  3. <!-- 全选按钮 -->
  4. <div class="custom-control custom-checkbox">
  5. <input type="checkbox" class="custom-control-input"
  6. id="fullCheck" />
  7. <label class="custom-control-label" for="fullCheck">全选
  8. </label>
  9. </div>
  10. <!-- 合计 -->
  11. <div>
  12. <span>合计:</span>
  13. <span class="amount">¥0.00</span>
  14. </div>
  15. <!-- 结算按钮 -->
  16. <button type="button" class="btn btn-primary">结算(0)</button>
  17. </div>
  18. </template>

并在当前组件的 <style> 节点中美化结算按钮的样式

  1. .btn-primary {
  2. // 设置固定高度
  3. height: 38px;
  4. // 设置圆角效果
  5. border-radius: 19px;
  6. // 设置最小宽度
  7. min-width: 90px; }

4.2.2 **封装自定义属性 **amount

amount 是已勾选商品的总价格

  1. 在 EsFooter.vue 组件的 props 节点中,声明如下的自定义属性
  1. export default {
  2. name: 'EsFooter',
  3. props: {
  4. // 已勾选商品的总价格
  5. amount: {
  6. type: Number,
  7. default: 0,
  8. },
  9. },
  10. }
  1. 在 EsFooter.vue 组件的 DOM 结构中渲染 amount 的值:
  1. <!-- 合计 -->
  2. <div>
  3. <span>合计:</span>
  4. <!-- 将 amount 的值保留两位小数 -->
  5. <span class="amount">¥{{ amount.toFixed(2) }}</span>
  6. </div>

4.2.3 **封装自定义属性 **total

total 为已勾选商品的总数量

  1. 在 EsFooter.vue 组件的 props 节点中,声明如下的自定义属性
  1. export default {
  2. name: 'EsFooter',
  3. props: {
  4. // 已勾选商品的总价格
  5. amount: {
  6. type: Number,
  7. default: 0,
  8. },
  9. // 已勾选商品的总数量
  10. total: {
  11. type: Number,
  12. default: 0,
  13. },
  14. },
  15. }
  1. 在 EsFooter.vue 组件的 DOM 结构中渲染 total 的值
  1. <!-- 结算按钮 -->
  2. <button type="button" class="btn btn-primary">结算({{total}})
  3. </button>
  1. 动态控制结算按钮的禁用状态:
  1. <!-- disabled 的值为 true,表示禁用按钮 -->
  2. <button type="button" class="btn btn-primary" :disabled="total ===
  3. 0">结算({{ total }})</button>

4.2.4 **封装自定义属性 **isfull

isfull 是全选按钮的选中状态,true 表示选中,false 表示未选中

  1. 在 EsFooter.vue 组件的 props 节点中,声明如下的自定义属性:
  1. export default {
  2. name: 'EsFooter',
  3. props: {
  4. // 已勾选商品的总价格
  5. amount: {
  6. type: Number,
  7. default: 0,
  8. },
  9. // 已勾选商品的总数量
  10. total: {
  11. type: Number,
  12. default: 0,
  13. },
  14. // 全选按钮的选中状态
  15. isfull: {
  16. type: Boolean,
  17. default: false,
  18. },
  19. },
  20. }
  1. 为复选框动态绑定 ckecked 属性的值:
  1. <!-- 全选按钮 -->
  2. <div class="custom-control custom-checkbox">
  3. <input type="checkbox" class="custom-control-input"
  4. id="fullCheck" :checked="isfull" />
  5. <label class="custom-control-label" for="fullCheck">全选</label>
  6. </div>

4.2.5 **封装自定义事件 **fullChange

通过自定义事件 fullChange,把最新的选中状态传递给组件的使用者

  1. 监听复选框选中状态变化的 change 事件:
  1. <!-- 全选按钮 -->
  2. <div class="custom-control custom-checkbox">
  3. <input type="checkbox" class="custom-control-input"
  4. id="fullCheck" :checked="isfull" @change="onCheckBoxChange" />
  5. <label class="custom-control-label" for="fullCheck">全选</label>
  6. </div>
  1. 在 methods 中声明 onCheckBoxChange ,并通过事件对象 e 获取到最新的选中状态:
  1. methods: {
  2. // 监听复选框选中状态的变化
  3. onCheckBoxChange(e) {
  4. // e.target.checked 是复选框最新的选中状态
  5. console.log(e.target.checked)
  6. },
  7. },
  1. 在 emits 中声明自定义事件:
  1. // 声明自定义事件
  2. emits: ['fullChange'],

在 onCheckBoxChange 事件处理函数中,通过 $emit() 触发自定义事件,把最新的选中

状态传递给当前组件的使用者:

  1. methods: {
  2. onCheckBoxChange(e) {
  3. // 触发自定义事件
  4. this.$emit('fullChange', e.target.checked)
  5. },
  6. },
  1. 在 App.vue 根组件中测试 EsFooter.vue 组件:
  1. <!-- 使用 footer 组件 -->
  2. <es-footer :total="0" :amount="0" @fullChange="onFullStateChange">
  3. </es-footer>

并在 methods 中声明 onFullStateChange 处理函数,通过形参获取到全选按钮最新的

选中状态:

  1. methods: {
  2. // 监听全选按钮状态的变化
  3. onFullStateChange(isFull) {
  4. // 打印全选按钮最新的选中状态
  5. console.log(isFull)
  6. },
  7. },

5. **封装 **es-goods 组件

5.1 **创建并注册 **EsGoods **组件 **

  1. 在 src/components/es-goods/ 目录下新建 EsGoods.vue 组件:
  1. <template>
  2. <div>EsGoods 组件</div>
  3. </template> <script>
  4. export default {
  5. name: 'EsGoods', }
  6. </script> <style lang="less" scoped></style>
  1. 在 App.vue 组件中导入并注册 EsGoods.vue 组件:
  1. // 导入 header 组件
  2. import EsHeader from './components/es-header/EsHeader.vue'
  3. // 导入 footer 组件
  4. import EsFooter from './components/es-footer/EsFooter.vue'
  5. // 导入 goods 组件
  6. import EsGoods from './components/es-goods/EsGoods.vue'
  7. export default {
  8. name: 'MyApp',
  9. components: {
  10. // 注册 header 组件
  11. EsHeader,
  12. // 注册 footer 组件
  13. EsFooter,
  14. // 注册 goods 组件
  15. EsGoods,
  16. },
  17. }
  1. 在 App.vue 的 template 模板结构中使用 EsGoods 组件
  1. <template>
  2. <div class="app-container">
  3. <!-- 使用 header 组件 -->
  4. <es-header title="购物车案例"></es-header>
  5. <!-- 使用 goods 组件 -->
  6. <es-goods></es-goods>
  7. <!-- 使用 footer 组件 -->
  8. <es-footer :total="0" :amount="0"
  9. @fullChange="onFullStateChange"></es-footer>
  10. </div>
  11. </template>

5.2 **封装 **es-goods **组件 **

5.2.0 **封装需求 **

    1. 实现 EsGoods 组件的基础布局
    1. 封装组件的 6 个自定义属性(id, thumb,title,price,count,checked)
    1. 封装组件的自定义事件 stateChange ,允许外界监听组件选中状态的变
  1. <!-- 使用 goods 组件 -->
  2. <es-goods
  3. v-for="item in goodslist"
  4. :key="item.id"
  5. :id="item.id"
  6. :thumb="item.goods_img"
  7. :title="item.goods_name"
  8. :price="item.goods_price"
  9. :count="item.goods_count"
  10. :checked="item.goods_state"
  11. @stateChange="onGoodsStateChange"
  12. ></es-goods>

5.2.1 **渲染组件的基础布局 **

  1. 渲染 EsGoods 组件的基础 DOM 结构:
  1. <template>
  2. <div class="goods-container">
  3. <!-- 左侧图片区域 -->
  4. <div class="left">
  5. <!-- 商品的缩略图 -->
  6. <img src="" alt="商品图片" class="thumb" />
  7. </div>
  8. <!-- 右侧信息区域 -->
  9. <div class="right">
  10. <!-- 商品名称 -->
  11. <div class="top">xxxx</div>
  12. <div class="bottom">
  13. <!-- 商品价格 -->
  14. <div class="price">¥0.00</div>
  15. <!-- 商品数量 -->
  16. <div class="count">数量</div>
  17. </div>
  18. </div>
  19. </div>
  20. </template>
  1. 美化组件的布局样式
  1. .goods-container {
  2. display: flex;
  3. padding: 50px,10px;
  4. // 左侧图片的样式
  5. .left {
  6. margin-right: 10px;
  7. // 商品图片
  8. .thumb {
  9. display: block;
  10. width: 100px;
  11. height: 100px;
  12. background-color: #efefef;
  13. }
  14. }
  15. // 右侧商品名称、单价、数量的样式
  16. .right {
  17. display: flex;
  18. flex-direction: column;
  19. justify-content: space-between;
  20. flex: 1;
  21. .top {
  22. font-weight: bold;
  23. }
  24. .bottom {
  25. display: flex;
  26. justify-content: space-between;
  27. align-items: center;
  28. .price {
  29. color: red;
  30. font-weight: bold;
  31. }
  32. }
  33. }
  34. }

在商品缩略图之外包裹复选框( https://v4.bootcss.com/docs/components/forms/#checkboxes )

  1. <!-- 左侧图片和复选框区域 -->
  2. <div class="left">
  3. <!-- 复选框 -->
  4. <div class="custom-control custom-checkbox">
  5. <input type="checkbox" class="custom-control-input"
  6. id="customCheck1" />
  7. <!-- 将商品图片包裹于 label 之中,点击图片可以切换“复选框”的选
  8. 中状态 -->
  9. <label class="custom-control-label" for="customCheck1">
  10. <img src="" alt="商品图片" class="thumb" />
  11. </label>
  12. </div>
  13. <!-- <img src="" alt="商品图片" class="thumb" /> -->
  14. </div>
  1. 覆盖复选框的默认样式:
  1. .custom-control-label::before,
  2. .custom-control-label::after {
  3. top: 3.4rem; }
  1. 在 App.vue 组件中循环渲染 EsGoods.vue 组件
  1. <!-- 使用 goods 组件 -->
  2. <es-goods v-for="item in goodslist" :key="item.id"></es-goods> 12
  1. 为 EsGoods.vue 添加顶边框:
  1. .goods-container {
  2. display: flex;
  3. padding: 10px;
  4. // 最终生成的选择器为 .goods-container + .goods-container
  5. // 在 css 中,(+)是“相邻兄弟选择器”,表示:选择紧连着另一元素后
  6. 的元素,二者具有相同的父元素。
  7. + .goods-container {
  8. border-top: 1px solid #efefef;
  9. }
  10. // ...省略其他样式
  11. }

5.2.2 **封装自定义属性 **id

id 是每件商品的唯一标识符

  1. 在 EsGoods.vue 组件的 props 节点中,声明如下的自定义属性:
  1. export default {
  2. name: 'EsGoods',
  3. props: {
  4. // 唯一的 key 值
  5. id: {
  6. type: [String, Number], // id 的值可以是“字符串”也可以是“数 值”
  7. required: true,
  8. },
  9. },
  10. }
  1. 在渲染复选框时动态绑定 input 的 id 属性和 label 的 for 属性值
  1. <!-- 复选框 -->
  2. <div class="custom-control custom-checkbox">
  3. <input type="checkbox" class="custom-control-input" :id="id" />
  4. <label class="custom-control-label" :for="id">
  5. <img src="" alt="商品图片" class="thumb" />
  6. </label>
  7. </div>
  1. 在 App.vue 中使用 EsGoods.vue 组件时,动态绑定 id 属性的值:
  1. <!-- 使用 goods 组件 -->
  2. <es-goods v-for="item in goodslist" :id="item.id"></es-goods> 12

5.2.3 封装其它属性

除了 id 属性之外,EsGoods 组件还需要封装:

缩略图(thumb)、商品名称(title)、单价(price)、数量(count)、**勾选状态 **

(checked)这 5 个属性

  1. 在 EsGoods.vue 组件的 props 节点中,声明如下的自定义属性:
  1. export default {
  2. name: 'EsGoods',
  3. props: {
  4. // 唯一的 key 值
  5. id: {
  6. type: [String, Number],
  7. required: true,
  8. },
  9. // 1. 商品的缩略图
  10. thumb: {
  11. type: String,
  12. required: true,
  13. },
  14. // 2. 商品的名称
  15. title: {
  16. type: String,
  17. required: true,
  18. },
  19. // 3. 单价
  20. price: {
  21. type: Number,
  22. required: true,
  23. },
  24. // 4. 数量
  25. count: {
  26. type: Number,
  27. required: true,
  28. },
  29. // 5. 商品的勾选状态
  30. checked: {
  31. type: Boolean,
  32. required: true,
  33. },
  34. },
  35. }
  1. 在 EsGoods.vue 组件的 DOM 结构中渲染商品的信息数据:
  1. <template>
  2. <div class="goods-container">
  3. <!-- 左侧图片和复选框区域 -->
  4. <div class="left">
  5. <!-- 复选框 -->
  6. <div class="custom-control custom-checkbox">
  7. <input type="checkbox" class="custom-control-input"
  8. :id="id" :checked="checked" />
  9. <label class="custom-control-label" :for="id">
  10. <img :src="thumb" alt="商品图片" class="thumb" />
  11. </label>
  12. </div>
  13. </div>
  14. <!-- 右侧信息区域 -->
  15. <div class="right">
  16. <!-- 商品名称 -->
  17. <div class="top">{{ title }}</div>
  18. <div class="bottom">
  19. <!-- 商品价格 -->
  20. <div class="price">¥{{ price.toFixed(2) }}</div>
  21. <!-- 商品数量 -->
  22. <div class="count">数量:{{ count }}</div>
  23. </div>
  24. </div>
  25. </div>
  26. </template>

在 App.vue 组件中使用 EsGoods.vue 组件时,动态绑定对应属性的值:

  1. <!-- 使用 goods 组件 -->
  2. <es-goods
  3. v-for="item in goodslist"
  4. :key="item.id"
  5. :id="item.id"
  6. :thumb="item.goods_img"
  7. :title="item.goods_name"
  8. :price="item.goods_price"
  9. :count="item.goods_count"
  10. :checked="item.goods_state"
  11. ></es-goods> 123456789
  12. 10
  13. 11

5.2.4 **封装自定义事件 **stateChange

点击复选框时,可以把最新的勾选状态,通过自定义事件的方式传递给组件的使用者

  1. 在 EsGoods.vue 组件中,监听 checkbox 选中状态变化的事件:
  1. <!-- 监听复选框的 change 事件 -->
  2. <input type="checkbox" class="custom-control-input" :id="id"
  3. :checked="checked" @change="onCheckBoxChange" />
  1. 在 EsGoods.vue 组件的 methods 中声明对应的事件处理函数
  1. methods: {
  2. // 监听复选框选中状态变化的事件
  3. onCheckBoxChange(e) {
  4. // e.target.checked 是最新的勾选状态
  5. console.log(e.target.checked)
  6. },
  7. },
  8. 1234567

在 EsGoods.vue 组件中声明自定义事件:

  1. emits: ['stateChange'],
  1. 完善 onCheckBoxChange 函数的处理逻辑,调用 $emit() 函数触发自定义事件
  1. methods: {
  2. // 监听复选框选中状态变化的事件
  3. onCheckBoxChange(e) {
  4. // 向外发送的数据是一个对象,包含了 { id, value } 两个属性
  5. this.$emit('stateChange', {
  6. id: this.id,
  7. value: e.target.checked,
  8. })
  9. },
  10. },
  1. 在 App.vue 根组件中使用 EsGoods.vue 组件时,监听它的 stateChange 事件:
  1. <!-- 使用 goods 组件 -->
  2. <es-goods
  3. v-for="item in goodslist"
  4. :key="item.id"
  5. :id="item.id"
  6. :thumb="item.goods_img"
  7. :title="item.goods_name"
  8. :price="item.goods_price"
  9. :count="item.goods_count"
  10. :checked="item.goods_state"
  11. @stateChange="onGoodsStateChange"
  12. ></es-goods>

并在 App.vue 的 methods 中声明如下的事件处理函数

  1. methods: {
  2. // 监听商品选中状态变化的事件
  3. onGoodsStateChange(e) {
  4. // 1. 根据 id 进行查找(注意:e 是一个对象,包含了 id 和 value
  5. 两个属性)
  6. const findResult = this.goodslist.find(x => x.id === e.id)
  7. // 2. 找到了对应的商品,则更新其选中状态
  8. if (findResult) {
  9. findResult.goods_state = e.value
  10. }
  11. },
  12. }

实现合计、结算数量、全选功能

6.1 动态统计已勾选商品的总价格

需求分析: 合计的商品总价格,依赖于 goodslist 数组中每一件商品信息的变化,此场景下适合使用**计算属 **

  1. 在 App.vue 中声明如下的计算属性:
  1. computed: {
  2. // 已勾选商品的总价
  3. amount() {
  4. // 1. 定义商品总价格
  5. let a = 0
  6. // 2. 循环累加商品总价格
  7. this.goodslist
  8. .filter(x => x.goods_state)
  9. .forEach(x => {
  10. a += x.goods_price * x.goods_count
  11. })
  12. // 3. 返回累加的结果
  13. return a
  14. },
  15. },
  1. 在 App.vue 中使用 EsFooter.vue 组件时,动态绑定已勾选商品的总价格
  1. <!-- 使用 footer 组件 -->
  2. <es-footer :total="0" :amount="amount"
  3. @fullChange="onFullStateChange"></es-footer> 1

6.2 动态统计已勾选商品的总数量

需求分析:

已勾选商品的总数量依赖项 goodslist 中商品勾选状态的变化,此场景下适合使用计算属性。

  1. 在 App.vue 中声明如下的计算属性:
  1. computed: {
  2. // 已勾选商品的总数量
  3. total() {
  4. // 1. 定义已勾选的商品总数量
  5. let t = 0
  6. // 2. 循环累加
  7. this.goodslist
  8. .filter(x => x.goods_state)
  9. .forEach(x => (t += x.goods_count))
  10. // 3. 返回计算的结果
  11. return t
  12. },
  13. },
  1. 在 App.vue 中使用 EsFooter.vue 组件时,动态绑定已勾选商品的总数量
  1. <!-- 使用 footer 组件 -->
  2. <es-footer :total="total" :amount="amount"
  3. @fullChange="onFullStateChange"></es-footer>

6.3 **实现全选功能 **

  1. 在 App.vue 组件中监听到 EsFooter.vue 组件的选中状态发生变化时,立即更新 goodslist 中每件商品的选中状态即可:
  1. <!-- 使用 footer 组件 -->
  2. <es-footer :total="total" :amount="amount"
  3. @fullChange="onFullStateChange"></es-footer>
  1. 在 onFullStateChange 的事件处理函数中修改每件商品的选中状态:
  1. methods: {
  2. // 监听全选按钮状态的变化
  3. onFullStateChange(isFull) {
  4. this.goodslist.forEach(x => x.goods_state = isFull)
  5. },
  6. }

7. **封装 **es-counter 组件

7.1 **创建并注册 **EsCounter **组件 **

在 src/components/es-counter/ 目录下新建 EsCounter.vue 组件

  1. <template>
  2. <div>EsCounter 组件</div>
  3. </template> <script>
  4. export default {
  5. name: 'EsCounter',
  6. }
  7. </script> <style lang="less" scoped></style>
  1. 在 EsGoods.vue 组件中导入并注册 EsCounter.vue 组件
  1. // 导入 counter 组件
  2. import EsCounter from '../es-counter/EsCounter.vue'
  3. export default {
  4. name: 'EsGoods',
  5. components: {
  6. // 注册 counter 组件
  7. EsCounter,
  8. }
  9. }
  1. 在 EsGoods.vue 的 template 模板结构中使用 EsCounter.vue 组件:
  1. <div class="bottom">
  2. <!-- 商品价格 -->
  3. <div class="price">¥{{ price.toFixed(2) }}</div>
  4. <!-- 商品数量 -->
  5. <div class="count">
  6. <!-- 使用 es-counter 组件 -->
  7. <es-counter></es-counter>
  8. </div>
  9. </div>

7.2 **封装 **es-counter 组件

7.2.0 **封装需求 **

    1. 渲染组件的 基础布局
    1. 实现数量值的 加减操作
    1. 处理 min 最小值
    1. 使用 watch 侦听器处理文本框输入的结果
    1. 封装 numChange 自定义事件
  1. 1 <es-counter :num="count" :min="1" @numChange="getNumber"></es-counter>

7.2.1 **渲染组件的基础布局 **

  1. 基于 bootstrap 提供的 Buttons https://v4.bootcss.com/docs/components/buttons/#exampl

es 和 form-control 渲染组件的基础布局:

  1. <template>
  2. <div class="counter-container">
  3. <!-- 数量 -1 按钮 -->
  4. <button type="button" class="btn btn-light btn-sm">-</button>
  5. <!-- 输入框 -->
  6. <input type="number" class="form-control form-control-sm ipt-num" />
  7. <!-- 数量 +1 按钮 -->
  8. <button type="button" class="btn btn-light btn-sm">+</button>
  9. </div>
  10. </template>
  1. 美化当前组件的样式:
  1. counter-container {
  2. display: flex;
  3. // 按钮的样式
  4. .btn {
  5. width: 25px;
  6. }
  7. // 输入框的样式
  8. .ipt-num {
  9. width: 34px;
  10. text-align: center;
  11. margin: 0 4px;
  12. }
  13. }

7.2.2 实现数值的渲染及加减操作

思路分析:

    1. 加减操作需要依赖于 EsCounter 组件的 data 数据
    1. 初始数据依赖于父组件通过 props 传递进来 将父组件传递进来的 props 初始值转存到 data 中,形成 EsCounter 组件的内部状态!
  1. 在 EsCounter.vue 组件中声明如下的 props:
  1. props: {
  2. // 数量值
  3. num: {
  4. type: Number,
  5. default: 0,
  6. },
  7. },
  1. 在 EsGoods.vue 组件中通过属性绑定的形式,将数据传递到 EsCounter.vue 组件中:
  1. <!-- 商品数量 -->
  2. <div class="count">
  3. <es-counter :num="count"></es-counter>
  4. </div> 1234

注意:不要直接把 num 通过 v-model 指令双向绑定到 input 输入框,因为 vue 规定:props 的值只读的!例如下面的做法是错误的:

  1. <!-- Warning 警告:不要模仿下面的操作 -->
  2. <input type="number" class="form-control form-control-sm ipt-num"
  3. v-model.number="num" />
  1. 正确的做法:将 props 的初始值转存到 data 中,因为 data 中的数据是可读可写的!示例代

码如下:

  1. export default {
  2. name: 'EsCounter',
  3. props: {
  4. // 初始数量值【只读数据】
  5. num: {
  6. type: Number,
  7. default: 0,
  8. },
  9. },
  10. data() {
  11. return {
  12. // 内部状态值【可读可写的数据】
  13. // 通过 this 可以访问到 props 中的初始值
  14. number: this.num,
  15. }
  16. },
  17. }

并且把 data 中的 number 双向绑定到 input 输入框:

  1. <input type="number" class="form-control form-control-sm ipt-num"
  2. v-model.number="number" />
  1. 为 -1 和 +1 按钮绑定响应的点击事件处理函数:
  1. <button type="button" class="btn btn-light btn-sm"
  2. @click="onSubClick">-</button> <input type="number" class="form-control form-control-sm ipt-num"
  3. v-model.number="number" />
  4. <button type="button" class="btn btn-light btn-sm"
  5. @click="onAddClick">+</button>

并在 methods 中声明对应的事件处理函数如下

  1. methods: {
  2. // -1 按钮的事件处理函数
  3. onSubClick() {
  4. this.number -= 1
  5. },
  6. // +1 按钮的事件处理函数
  7. onAddClick() {
  8. this.number += 1
  9. },
  10. },

7.2.3 **实现 **min 最小值的处理

需求分析:

购买商品时,购买的数量最小值为 1

  1. 在 EsCounter.vue 组件中封装如下的 props:
  1. export default {
  2. name: 'EsCounter',
  3. props: {
  4. // 数量值
  5. num: {
  6. type: Number,
  7. default: 0,
  8. },
  9. // 最小值
  10. min: {
  11. type: Number,
  12. // min 属性的值默认为 NaN,表示不限制最小值
  13. default: NaN,
  14. },
  15. },
  16. }

在 -1 按钮的事件处理函数中,对 min 的值进行判断和处理:

  1. methods: {
  2. // -1 按钮的事件处理函数
  3. onSubClick() {
  4. // 判断条件:min 的值存在,且 number - 1 之后小于 min
  5. if (!isNaN(this.min) && this.number - 1 < this.min) return
  6. this.number -= 1
  7. },
  8. }
  1. 在 EsGoods.vue 组件中使用 EsCounter.vue 组件时指定 min 最小值:
  1. <!-- 商品数量 -->
  2. <div class="count">
  3. <!-- 指定数量的最小值为 1 -->
  4. <es-counter :num="count" :min="1"></es-counter>
  5. </div> 12345

7.2.4 处理输入框的输入结果

思路分析:

    1. 将输入的新值转化为整数
    1. 如果转换的结果不是数字,或小于1,则强制 number 的值等于1
    1. 如果新值为小数,则把转换的结果赋值给 number
  1. 为输入框的 v-model 指令添加 .lazy 修饰符(当输入框触发 change 事件时更新 v

model 所绑定到的数据源):

  1. <input type="number" class="form-control form-control-sm ipt-num"
  2. v-model.number.lazy="number" />
  1. 通过 watch 侦听器监听 number 数值的变化,并按照分析的步骤实现代码
  1. export default {
  2. name: 'EsCounter',
  3. watch: {
  4. // 监听 number 数值的变化
  5. number(newVal) {
  6. // 1. 将输入的新值转化为整数
  7. const parseResult = parseInt(newVal)
  8. // 2. 如果转换的结果不是数字,或小于1,则强制 number 的值等于
  9. if (isNaN(parseResult) || parseResult < 1) {
  10. this.number = 1
  11. return
  12. }
  13. // 3. 如果新值为小数,则把转换的结果赋值给 number
  14. if (String(newVal).indexOf('.') !== -1) {
  15. this.number = parseResult
  16. return
  17. }
  18. console.log(this.number)
  19. },
  20. },
  21. }

7.2.5 把最新的数据传递给使用者

需求分析:

当 EsGoods 组件使用 EsCounter 组件时,期望能够监听到商品数量的变化,此时需要使用**自定 **

义事件的方式,把最新的数据传递给组件的使用者

在 EsCounter.vue 组件中声明自定义事件如下:

emits: ['numChange'],

  1. 在 EsCounter.vue 组件的 watch 侦听器中触发自定义事件:
  1. watch: {
  2. number(newVal) {
  3. // 1. 将输入的新值转化为整数
  4. const parseResult = parseInt(newVal)
  5. // 2. 如果转换的结果不是数字,或小于1,则强制 number 的值等于1
  6. if (isNaN(parseResult) || parseResult < 1) {
  7. this.number = 1
  8. return
  9. }
  10. // 3. 如果新值为小数,则把转换的结果赋值给 number
  11. if (String(newVal).indexOf('.') !== -1) {
  12. this.number = parseResult
  13. return
  14. }
  15. // 触发自定义事件,把最新的 number 数值传递给组件的使用者
  16. this.$emit('numChange', this.number)
  17. },
  18. },
  1. 在 EsGoods.vue 组件中监听 EsCounter.vue 组件的自定义事件:
  1. <!-- 商品数量 -->
  2. <div class="count">
  3. <es-counter :num="count" :min="1" @numChange="getNumber"></escounter>
  4. </div> 1234

并声明对应的事件处理函数如下:

  1. methods: {
  2. // 监听数量变化的事件
  3. getNumber(num) {
  4. console.log(num)
  5. },
  6. }

7.2.6 **更新购物车中商品的数量 **

思路分析:

    1. 在 EsGoods 组件中**获取到最新的商品数量 **
    1. 在 EsGoods 组件中声明自定义事件
    1. 在 EsGoods 组件中触发自定义事件,向外传递数据对象 { id, value }
    1. 在 App 根组件中监听 EsGoods 组件的自定义事件,并根据 id 更新对应商品的数量
  1. 在 EsGoods.vue 组件中声明自定义事件 countChange :

emits: ['stateChange', 'countChange'],

  1. 在 EsCounter.vue 组件的 numChange 事件处理函数中,触发步骤1声明的自定义事件
  1. <es-counter :num="count" :min="1" @numChange="getNumber"></escounter>
  2. methods: {
  3. // 监听数量变化的事件
  4. getNumber(num) {
  5. // 触发自定义事件,向外传递数据对象 { id, value }
  6. this.$emit('countChange', {
  7. // 商品的 id
  8. id: this.id,
  9. // 最新的数量
  10. value: num,
  11. })
  12. },
  13. }

在 App.vue 根组件中使用 EsGoods.vue 组件时,监听它的自定义事件 countChange

  1. <!-- 使用 goods 组件 -->
  2. <es-goods
  3. v-for="item in goodslist"
  4. :key="item.id"
  5. :id="item.id"
  6. :thumb="item.goods_img"
  7. :title="item.goods_name"
  8. :price="item.goods_price"
  9. :count="item.goods_count"
  10. :checked="item.goods_state"
  11. @stateChange="onGoodsStateChange"
  12. @countChange="onGoodsCountChange"
  13. ></es-goods>

并在 methods 中声明对应的事件处理函数:

  1. // 监听商品数量变化的事件
  2. onGoodsCountChange(e) {
  3. // 根据 id 进行查找
  4. const findResult = this.goodslist.find(x => x.id === e.id)
  5. // 找到了对应的商品,则更新其数量
  6. if (findResult) {
  7. findResult.goods_count = e.value
  8. }
  9. }
  10. }

本文转载自: https://blog.csdn.net/qq_63358859/article/details/122759838
版权归原作者 前端 贾公子 所有, 如有侵权,请联系我们删除。

“从零开始打造vue---购物车案例”的评论:

还没有评论