系统定制开发Vue实现手机端界面的购物车案例

目录


今天用Vue系统定制开发实现的一个手机端的购物车案例,系统定制开发着重阐述思想的优势,系统定制开发将页面的内容分成各个系统定制开发模块进行书写,系统定制开发然后数据传输,父传子、子传父、系统定制开发兄弟数据共享等,系统定制开发这方面知识不牢固的话系统定制开发可以关注一下右方vue的专栏: 系统定制开发里面详解介绍了vue的知识,系统定制开发今天的案例也是借助黑系统定制开发马的相关案例及其接口,系统定制开发既是分享也是回顾。

前期准备

系统定制开发因为本案例是借助vue系统定制开发的框架进行实现的,系统定制开发所以我们需要先搭建vue-cli脚手架,系统定制开发具体的搭建过程请看右方链接  ,系统定制开发搭建完成后,需要根据案例的具体的图片功能点要求给components文件下新建子组件文件,案例实现结果以及父子组件的框架如下:

写项目之前先选择好自己想要的手机版本是什么,我这里就用最常见的iPhone 6/7/8 这个手机尺寸了,如果想更换手机版本,可以自行在浏览器进行更换。

为了语义化,这边我把components文件名更改为shopping,不该也没关系。

根据上文图片案例,我们先从上面也是最简单的Header来写,为了便于数据的管理,我们把标题设置为自定义属性,允许使用者自定义标题的内容。

Header.vue子组件代码

  1. <template>
  2. <div class="header-container">{{title}}</div>
  3. </template>
  4. <script>
  5. export default {
  6. props:{
  7. // 声明 title 自定义属性,允许使用者自定义标题的内容
  8. title:{
  9. default:'',
  10. type:String
  11. }
  12. }
  13. }
  14. </script>
  15. <style lang="less" scoped>
  16. .header-container{
  17. font-size: 12px;
  18. height: 45px;
  19. width: 100%;
  20. background-color: #008c8c;
  21. display: flex;
  22. justify-content: center;
  23. align-items: center;
  24. color: #fff;
  25. position: fixed;
  26. top: 0;
  27. z-index: 999;
  28. }
  29. </style>

App.vue父组件代码

  1. <template>
  2. <div class="app-container">
  3. <!-- 头部区域 -->
  4. <Header title="购物车案例"></Header>
  5. </div>
  6. </template>
  7. <script>
  8. // 导入需要的组件
  9. import Header from '@/shopping/Header/Header.vue'
  10. export default {
  11. components:{
  12. Header
  13. }
  14. }
  15. </script>
  16. <style lang="less" scoped>
  17. .app-container{
  18. padding-top: 45px;
  19. padding-bottom:50px
  20. }
  21. </style>

Goods

现在开始从项目的内容入手,因为子组件的内容肯定不能写死了,所以需要将父组件的值传入到子组件,而父组件的值从哪来?这里需要借助接口来获取自己要渲染的商品列表数据,而借助接口传值需要使用ajax或者是axios,所以我们需要先在当前项目下安装 axios,命令如下:

npm install axios -S

安装完成之后,在App.vue 父组件下 使用axios调用接口进行使用:很明显,我们先在data里面定义一个空数组,如果接收到接口里面的数据状态为200,就把接口里面的数据传递到我们定义的list里面,具体方法如下:

在控制台打印的接口数据如下,可以方便的查看接口里面的属性:

Goods.vue子组件代码

因为子组件的复选框的数据是没有和父组件的数据进行联通的,如果不把子组件修改复选框的状态的值传到App.vue父组件上,父组件上的goods_state是不会发生变化的,所以要通过自定义事件进行子向父传值,将复选框的修改状态传递到父组件上面。

  1. <template>
  2. <div class="goods-container">
  3. <!-- 左侧图片 -->
  4. <div class="thumb">
  5. <!-- 复选框 -->
  6. <div class="custom-control custom-checkbox">
  7. <input type="checkbox" class="custom-control-input" :id="'cb'+id" :checked="state" @change="stateChange" />
  8. <label :for="'cb'+id" class="custom-control-label">
  9. <!-- 商品的缩略图 -->
  10. <img :src="pic" alt="">
  11. </label>
  12. </div>
  13. </div>
  14. <!-- 右侧信息区域 -->
  15. <div class="goods-infos">
  16. <!-- 商品标题 -->
  17. <h6 class="goods-title">{{title}}</h6>
  18. <div class="goods-info-bottom">
  19. <!-- 商品价格 -->
  20. <span class="goods-price">¥{{price}}</span>
  21. <!-- 商品的数量 -->
  22. </div>
  23. </div>
  24. </div>
  25. </template>
  26. <script>
  27. export default {
  28. props:{
  29. // 商品的id,将来子组件中商品的勾选状态变化之后,需要通过子 -> 父的形式,通知父组件根据id修改对应商品的修改状态
  30. id:{
  31. require:true,
  32. type:Number
  33. },
  34. // 要渲染的商品的标题
  35. title:{
  36. default:'',
  37. type:String
  38. },
  39. // 要渲染的商品的图片
  40. pic:{
  41. default:'',
  42. type:String
  43. },
  44. // 商品的单价
  45. price:{
  46. default:0,
  47. type:Number
  48. },
  49. // 商品的勾选状态
  50. state:{
  51. default:true,
  52. type:Boolean
  53. },
  54. },
  55. methods:{
  56. // 只有复选框的选中状态发生了变化就会调用这个处理函数
  57. stateChange(e){
  58. const newState = e.target.checked;
  59. this.$emit('state-change', {id:this.id,value:newState});
  60. }
  61. },
  62. }
  63. </script>
  64. <style lang="less" scoped>
  65. .goods-container{
  66. + .goods-container{
  67. border-top:1px solid #efefef
  68. }
  69. padding: 10px;
  70. display: flex;
  71. .thumb{
  72. display: flex;/*display:flex 意思是弹性布局,它能够扩展和收缩 flex 容器内的元素,以最大限度地填充可用空间。*/
  73. align-items: center;/* 设置项目交叉轴方向上的对齐方式 */
  74. img{
  75. width: 80px;
  76. height: 80px;
  77. margin: 10px;
  78. }
  79. .custom-control{
  80. width: 114px;
  81. height: 105px;
  82. }
  83. }
  84. .goods-infos{
  85. display: flex;
  86. flex-direction: column;/*灵活的项目将垂直显示,正如一个列一样。在这里插入图片描述*/
  87. justify-content: space-between;/* 均匀排列每个元素首个元素放置于起点,末尾元素放置于终点 */
  88. height: 100px;
  89. flex: 1;
  90. .goods-title{
  91. font-size: 12px;
  92. font-weight: bold;
  93. }
  94. .goods-info-bottom{
  95. display: flex;
  96. justify-content: space-between;
  97. .goods-price{
  98. font-weight: bold;
  99. color: red;
  100. font-size: 13px;
  101. }
  102. }
  103. }
  104. }
  105. </style>

App.vue父组件代码

父组件通过接收子组件自定义的事件名,通过将函数methods里面的方法判断,来进行动态的改变 list.goods_state 里面的值。

  1. <template>
  2. <div class="app-container">
  3. <!-- 头部区域 -->
  4. <Header title="购物车案例"></Header>
  5. <!-- 循环渲染每一个商品的信息 -->
  6. <Goods
  7. v-for="item in list"
  8. :key="item.id"
  9. :id="item.id"
  10. :title="item.goods_name"
  11. :pic="item.goods_img"
  12. :price="item.goods_price"
  13. :state="item.goods_state"
  14. @state-change="getNewState"
  15. >
  16. </Goods>
  17. </div>
  18. </template>
  19. <script>
  20. // 导入 axios 请求库
  21. import axios from 'axios'
  22. // 导入需要的组件
  23. import Header from '@/shopping/Header/Header.vue'
  24. import Goods from '@/shopping/Goods/Goods.vue'
  25. export default {
  26. data(){
  27. return {
  28. // 用来存储购物车的列表数据,默认为空数组
  29. list:[]
  30. }
  31. },
  32. methods:{
  33. // 封装请求列表数据的方法
  34. async initCarList(){
  35. // 调用 axios 的 get 方法,请求列表数据
  36. const {data:res} = await axios.get("https://www.escook.cn/api/cart")
  37. console.log(res);
  38. if(res.status === 200){
  39. this.list = res.list
  40. }
  41. },
  42. // 接收子组件传递过来的数据
  43. getNewState(val){
  44. this.list.some(item => {
  45. if(item.id === val.id){
  46. item.goods_state = val.value
  47. // 终止后续循环
  48. return true
  49. }
  50. })
  51. },
  52. },
  53. components:{
  54. Header,Goods
  55. },
  56. created(){
  57. // 调用请求数据的方法
  58. this.initCarList()
  59. }
  60. }
  61. </script>
  62. <style lang="less" scoped>
  63. .app-container{
  64. padding-top: 45px;
  65. padding-bottom:50px
  66. }
  67. </style>

现在实现购物车底部的全选、总计、以及结算的功能样式,因为数据也不能写死了,所以需要我们进行数据绑定,然后通过父组件获取的数据进行传值。

Footer.vue子组件代码

  1. <template>
  2. <div class="footer-container">
  3. <!-- 左侧的全选 -->
  4. <div class="custom-control custom-checkbox">
  5. <input type="checkbox" class="custom-control-input" id="cbFull" :checked="isfull" @change="fullchange">
  6. <label for="cbFull" class="custom-control-label">全选</label>
  7. </div>
  8. <!-- 中间的合计 -->
  9. <div>
  10. <span>合计:</span>
  11. <span class="total-price">¥{{amount.toFixed(2)}}</span>
  12. </div>
  13. <!-- 结算按钮 -->
  14. <button type="button" class="btn btn-primary btn-settle">结算({{all}})</button>
  15. </div>
  16. </template>
  17. <script>
  18. export default {
  19. props:{
  20. // 全选的状态
  21. isfull:{
  22. type:Boolean,
  23. default:true
  24. },
  25. // 总价格
  26. amount:{
  27. type:Number,
  28. default:0
  29. },
  30. // 已勾选的商品的总数量
  31. all:{
  32. type:Number,
  33. default:0
  34. }
  35. },
  36. methods:{
  37. // 监听到了全选的状态变化
  38. fullchange(e){
  39. this.$emit('full-change',e.target.checked)
  40. }
  41. }
  42. }
  43. </script>
  44. <style lang="less" scoped>
  45. .footer-container{
  46. font-size: 12px;
  47. height: 60px;
  48. width: 100%;
  49. border-top: 1px solid #efefef;
  50. position: fixed;
  51. bottom: 0;
  52. background-color: #fff;
  53. display: flex;
  54. justify-content: space-between;
  55. align-items: center;
  56. padding: 0 10px;
  57. .custom-checkbox{
  58. font-size: 13px;
  59. display: flex;
  60. align-items: center;
  61. .custom-control-label{
  62. margin-bottom: -5px;
  63. }
  64. #cbFull{
  65. margin-right: 5px;
  66. }
  67. }
  68. .total-price{
  69. font-weight: bold;
  70. font-size: 14px;
  71. color: red;
  72. }
  73. .btn-settle{
  74. height: 70%;
  75. min-width: 110px;
  76. border-radius: 25px;
  77. font-size: 12px;
  78. }
  79. }
  80. </style>

App.vue父组件代码

  1. <template>
  2. <div class="app-container">
  3. <!-- 头部区域 -->
  4. <Header title="购物车案例"></Header>
  5. <!-- 循环渲染每一个商品的信息 -->
  6. <Goods
  7. v-for="item in list"
  8. :key="item.id"
  9. :id="item.id"
  10. :title="item.goods_name"
  11. :pic="item.goods_img"
  12. :price="item.goods_price"
  13. :state="item.goods_state"
  14. @state-change="getNewState"
  15. >
  16. </Goods>
  17. <!-- Footer区域 -->
  18. <Footer :isfull="fullState" :amount="amt" :all="total" @full-change="getFullState"></Footer>
  19. </div>
  20. </template>
  21. <script>
  22. // 导入 axios 请求库
  23. import axios from 'axios'
  24. // 导入需要的组件
  25. import Header from '@/shopping/Header/Header.vue'
  26. import Goods from '@/shopping/Goods/Goods.vue'
  27. import Footer from '@/shopping/Footer/Footer.vue'
  28. export default {
  29. data(){
  30. return {
  31. // 用来存储购物车的列表数据,默认为空数组
  32. list:[]
  33. }
  34. },
  35. methods:{
  36. // 封装请求列表数据的方法
  37. async initCarList(){
  38. // 调用 axios 的 get 方法,请求列表数据
  39. const {data:res} = await axios.get("https://www.escook.cn/api/cart")
  40. console.log(res);
  41. if(res.status === 200){
  42. this.list = res.list
  43. }
  44. },
  45. // 接收子组件传递过来的数据
  46. getNewState(val){
  47. this.list.some(item => {
  48. if(item.id === val.id){
  49. item.goods_state = val.value
  50. // 终止后续循环
  51. return true
  52. }
  53. })
  54. },
  55. // 接收 Footer 子组件传递过来的全选按钮的状态
  56. getFullState(val){
  57. this.list.forEach(item => item.goods_state = val)
  58. }
  59. },
  60. computed:{
  61. // 动态计算出全选的状态是 true 还是 false
  62. fullState(){
  63. return this.list.every(item => item.goods_state)
  64. },
  65. // 已勾选的商品总价格
  66. amt(){
  67. // 1.先filter过滤
  68. // 2.再reduce累加
  69. return this.list.filter(item=>item.goods_state).reduce((total,item)=>{
  70. return total+=item.goods_price * item.goods_count
  71. },0)
  72. },
  73. // 已勾选商品的总数量
  74. total(){
  75. return this.list.filter(item => item.goods_state).reduce((t,item)=>{
  76. return t+=item.goods_count
  77. },0)
  78. }
  79. },
  80. components:{
  81. Header,Goods,Footer
  82. },
  83. created(){
  84. // 调用请求数据的方法
  85. this.initCarList()
  86. }
  87. }
  88. </script>
  89. <style lang="less" scoped>
  90. .app-container{
  91. padding-top: 45px;
  92. padding-bottom:50px
  93. }
  94. </style>

Counter

因为count是修改商品的数量的,所以修改的数量要直接修改到父组件的App.vue里面的数据,而要想直接修改App.vue数据是不可能的,因为Counter组件外面还嵌套一层Goods组件,App与Counter相当于爷孙的关系,所以我们可以通过eventBus让Counter直接去修改App里面的值。

eventBus.js文件

  1. import Vue from 'vue'
  2. export default new Vue()

 Counter.vue子组件

因为Counter是嵌套在Goods组件里面的,所以我们还需要在Goods组件去引用Counter子组件

给Goods的props属性在设置一个count,用来表明商品的数量。

  1. <template>
  2. <div class="number-container d-flex justify-content-center align-items-center">
  3. <!-- 减 1 的按钮 -->
  4. <button type="button" class="btn btn-light bnt-sm" @click="sub">-</button>
  5. <!-- 购买的数量 -->
  6. <span class="number-box">{{num}}</span>
  7. <!-- 加 1 的按钮 -->
  8. <button type="button" class="btn btn-light bnt-sm" @click="add">+</button>
  9. </div>
  10. </template>
  11. <script>
  12. // 导入eventBus文件
  13. import bus from '@/shopping/eventBus.js'
  14. export default {
  15. props:{
  16. // 接收商品的id值,将来使用 EventBus 方案,把数量传递到 App.vue 的时候,需要通知 App 组件,更新哪个商品的数量
  17. id:{
  18. type:Number,
  19. required:true
  20. },
  21. // 接收到的 num 数量值
  22. num:{
  23. type:Number,
  24. default:1
  25. }
  26. },
  27. methods:{
  28. // 点击按钮,数值+1
  29. add(){
  30. // 要发送给 App 的数据格式为 {id,value}
  31. // 其中,id是商品的id;value是商品最新的购买数量
  32. const obj = {id:this.id,value:this.num+1}
  33. // 要做的事:通过 EventBus 把 obj 对象,发送给 App.vue 组件
  34. bus.$emit('share',obj)
  35. },
  36. sub(){
  37. if(this.num-1 == 0) return
  38. // 要发送给 App 的数据格式为 {id,value}
  39. // 其中,id是商品的id;value是商品最新的购买数量
  40. const obj = {id:this.id,value:this.num-1}
  41. // 要做的事:通过 EventBus 把 obj 对象,发送给 App.vue 组件
  42. bus.$emit('share',obj)
  43. }
  44. }
  45. }
  46. </script>
  47. <style>
  48. </style>

App.vue父组件代码

导入eventBus.js文件,通过bus.$on()方法,调用Counter传来的share里面的数据,通过传来的数据,来修改list里面的goods_count里面的值。

  1. <template>
  2. <div class="app-container">
  3. <!-- 头部区域 -->
  4. <Header title="购物车案例"></Header>
  5. <!-- 循环渲染每一个商品的信息 -->
  6. <Goods
  7. v-for="item in list"
  8. :key="item.id"
  9. :id="item.id"
  10. :title="item.goods_name"
  11. :pic="item.goods_img"
  12. :price="item.goods_price"
  13. :state="item.goods_state"
  14. :count="item.goods_count"
  15. @state-change="getNewState"
  16. >
  17. </Goods>
  18. <!-- Footer区域 -->
  19. <Footer :isfull="fullState" :amount="amt" :all="total" @full-change="getFullState"></Footer>
  20. </div>
  21. </template>
  22. <script>
  23. // 导入 axios 请求库
  24. import axios from 'axios'
  25. // 导入需要的组件
  26. import Header from '@/shopping/Header/Header.vue'
  27. import Goods from '@/shopping/Goods/Goods.vue'
  28. import Footer from '@/shopping/Footer/Footer.vue'
  29. // 导入eventBus文件
  30. import bus from '@/shopping/eventBus.js'
  31. export default {
  32. data(){
  33. return {
  34. // 用来存储购物车的列表数据,默认为空数组
  35. list:[]
  36. }
  37. },
  38. methods:{
  39. // 封装请求列表数据的方法
  40. async initCarList(){
  41. // 调用 axios 的 get 方法,请求列表数据
  42. const {data:res} = await axios.get("https://www.escook.cn/api/cart")
  43. console.log(res);
  44. if(res.status === 200){
  45. this.list = res.list
  46. }
  47. },
  48. // 接收子组件传递过来的数据
  49. getNewState(val){
  50. this.list.some(item => {
  51. if(item.id === val.id){
  52. item.goods_state = val.value
  53. // 终止后续循环
  54. return true
  55. }
  56. })
  57. },
  58. // 接收 Footer 子组件传递过来的全选按钮的状态
  59. getFullState(val){
  60. this.list.forEach(item => item.goods_state = val)
  61. }
  62. },
  63. computed:{
  64. // 动态计算出全选的状态是 true 还是 false
  65. fullState(){
  66. return this.list.every(item => item.goods_state)
  67. },
  68. // 已勾选的商品总价格
  69. amt(){
  70. // 1.先filter过滤
  71. // 2.再reduce累加
  72. return this.list.filter(item=>item.goods_state).reduce((total,item)=>{
  73. return total+=item.goods_price * item.goods_count
  74. },0)
  75. },
  76. // 已勾选商品的总数量
  77. total(){
  78. return this.list.filter(item => item.goods_state).reduce((t,item)=>{
  79. return t+=item.goods_count
  80. },0)
  81. }
  82. },
  83. components:{
  84. Header,Goods,Footer
  85. },
  86. created(){
  87. // 调用请求数据的方法
  88. this.initCarList()
  89. bus.$on('share',val=>{
  90. this.list.some(item=>{
  91. if(item.id === val.id){
  92. item.goods_count = val.value
  93. return true
  94. }
  95. })
  96. })
  97. }
  98. }
  99. </script>
  100. <style lang="less" scoped>
  101. .app-container{
  102. padding-top: 45px;
  103. padding-bottom:50px
  104. }
  105. </style>

这个案例对初学vue者还是有很大的借鉴意义,通过此案例了解组件之间的数据共享的各种方式,不了解的可以先看下右边这篇文章 。通过项目案例将自己所学知识融会贯通这一点非常重要,多做项目对成长的帮助非常大,希望与诸位共

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发