【精讲】vue框架 利用脚手架实现购物车(含添加、删除、存储、清空数据、全选or单选、tap栏切换)内含详细注释

目录

简介

第一部分:子组件内容及详细注释

第二部分:最终实现效果

 第三部分:动画知识剖析


简介

该项目(购物车)制作主要采用了tap栏切换、删除、添加、存储、清空数据、全选or单选及其它动态效果为一体的实现购物车功能。每一个子组件内部都包含了三部分:HTML ,js,css样式。 在下面的内容中,博主会以组件实现的形式展现效果部分。每一部分都有详细的注释

第一部分:子组件内容及详细注释

子组件1:MyHeader.vue

<template>

  <div class="todo-header">

      <!-- @keyup.enter="add" 创建keyup键盘事件 回车即添加数据 -->

      <!-- 这里可以使用v-model="titelle"采用 双向绑定,让data内部的变量收集数据 -->

    <input type="text" placeholder="请输入你的任务名称,按回车键确认"  @keyup.enter="add($event)"/>

  </div>

</template>

<script>

// 这里引入 单向操作数据id值不同

import {nanoid} from 'nanoid'

export default {

  name: "MyHeader",

//   data(){

//    return{

    // titelle:'',

//    }

//   },

// 接收App父组件传来的数据

  props:['addtodo'],

  methods:{

    //对数据回车列入li标签中的数据进行回车事件绑定  将回车后的数据创建成对象的形式

      add(e) {

     // 将用户的输入包装成一个todo对象     第一个id值   第二个获取的是输入的内容   第三个四判断是否选中了

      const todoObj = {id:nanoid(),title:e.target.value,done:false}

    //将创建好的对象传给App

      this.addtodo(todoObj)

    //   创建好并且也传了,后面就让输入框内清空

      e.target.value=''

      }

  }

};

</script>

<style scoped>

/*header*/

.todo-header input {

  width: 560px;

  height: 28px;

  font-size: 14px;

  border: 1px solid #ccc;

  border-radius: 4px;

  padding: 4px 7px;

}

.todo-header input:focus {

  outline: none;

  border-color: rgba(82, 168, 236, 0.8);

  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),

    0 0 8px rgba(82, 168, 236, 0.6);

}

</style>

子组件2:MyList.vue

<template>

  <ul class="todo-main">

    <!-- 下面是实现动态效果的内容  第一块是动画的总名称   第二部分是初始过渡的作用     第三部分是出现的效果  第四部分是删除结束的效果 -->

  <transition-group

   name="animate__animated animate__bounce"

   appear

   enter-active-class="animate__backInRight"

   leave-active-class="animate__backOutLeft"

 >

    <!-- 将获取的MyItem数据  改成标签,并遍历todo中的数据 给定key值 并传给子组件数据 -->

      <MyItem

        v-for="item in todo"

        :key="item.id"

        :proe="item"

        :checktodo="checktodo"

        :deletTodo="deletTodo"

      />

    </transition-group>

  </ul>

</template>

<script>

import 'animate.css'

import MyItem from "./MyItem.vue";

export default {

  name: "MyList",

  components: { MyItem },

  data() {

    return {};

  },

  // App父组件传给子组件数据  在props中接收

  //下面接收值是第一种写法

  props: ["todo", "checktodo", "deletTodo"],

  // 下面的接收值是第二种写法

  //   props:{

  //   todo:{

  //  type:Array,

  //   },

  //   checktodo:{

  //    type:Array,

  //   }

  //   }

};

</script>

<style scoped>

/*main*/

.todo-main {

  margin-left: 0px;

  border: 1px solid #ddd;

  border-radius: 2px;

  padding: 0px;

}

.todo-empty {

  height: 40px;

  line-height: 40px;

  border: 1px solid #ddd;

  border-radius: 2px;

  padding-left: 5px;

  margin-top: 10px;

}

</style>

子组件3:MyItem.vue

<template>

  <li>

    <label>

      <!-- 这里是默认勾选  选项 动态生成是否勾选     这里除了使用绑定点击事件@click  还有一个便是@change改变-->

      <input

        type="checkbox"

        :checked="proe.done"

        @click="handlecheck(proe.id)"

      />

       <!-- 如下代码也能实现判定是勾选还是没有勾选,但是不建议使用 因为有违反原则  修改了props数据  知识vue没有监测到-->

      <!-- <input type="checkbox" v-model="proe.done"> -->

      <!-- 将获取的数据进行添加  该数据是由MyList父组件传来的 -->

      <span>{{ proe.title }}</span>

    </label>

    <!-- 这里添加点击事件  返回一个todo内部的id值-->

    <button class="btn btn-danger" @click="delet(proe.id)">删除</button>

  </li>

</template>

<script>

export default {

  name: "MyItem",

  data() {

    return {};

  },

  // 父传子 收集数据

  props: ["proe", "checktodo","deletTodo"],

  methods: {

    handlecheck(id) {

      //通知App组件将对应的todo(proe)对象的done取反

      this.checktodo(id)

    },

    //删除

    delet(id){

        // confirm 与 alert 都是弹窗,其区别:confirm有返回值  alert 没有返回值

     if(confirm('你确定删除吗?')){

      //  在上面的判断结束后,若用户点击确定,确认要删除, 那么执行删除语句   该删除语句是从父组件调用来得,所以关于数据处理得去父组件

      //进行设置添加

       this.deletTodo(id)

     }

    }

  },

  // 声明接收proe对象

};

</script>

<style scoped>

/*item*/

li {

  list-style: none;

  height: 36px;

  line-height: 36px;

  padding: 0 5px;

  border-bottom: 1px solid #ddd;

}

li label {

  float: left;

  cursor: pointer;

}

li label li input {

  vertical-align: middle;

  margin-right: 6px;

  position: relative;

  top: -1px;

}

li button {

  float: right;

  display: none;

  margin-top: 3px;

}

li:before {

  content: initial;

}

li:last-child {

  border-bottom: none;

}

/* 鼠标悬浮效果 */

li:hover{

  background-color:#ddd;

}

li:hover  button{

    display:block;

}

</style>

子组件4:MyFooter.vue

<template>

  <div class="todo-footer" v-show="total">

    <label>

      <!-- 第一种写法是   :checked="doneTodo === todo.length"  整体写法  :checked="isAllTodo" :change="checkall"-->

      <input type="checkbox" v-model="isAll"/>

    </label>                            

           <!--全部  这里也可以写todo.length -->

    <span> <span>已完成{{doneTodo}}</span> / 全部{{total}} </span>

      <!--当用户点击清除已完成的数据时,其实是删除(已勾选过得)  在这里我们设置一个点击事件 -->

    <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>

  </div>

</template>

<script>

export default {

  name: "MyFooter",

  props:['todo','checkdone','clearAlltodo'],

  computed:{

    doneTodo(){

      // 第一种写法

    // let tode = 0

    // this.todo.forEach((to)=>{

      //  if(to.done){

        // tode++

      //  }

    // })

    //  return tode

    // 第二种写法

  //  return  this.todo.reduce((pre,current)=>{

        // console.log(pre,current)

        // return pre+(current.done?1:0)

    // },0)

    // 第三种写法(较为高级xie)

    // 第一个参数值是上一级的返回值   第二个参数是当前的数据值

    return  this.todo.reduce((pre,current)=> pre + (current.done?1:0),0)

    },

    // 这里的 total是方法 在计算属性中操作主要是为了获取全部内部的数据的长度

    total(){

    return  this.todo.length

    },

    // 这里是勾选框,当用户点击后,那么所有的均都选中了

    isAll:{

      get(){

      return this.doneTodo == this.total  && this.total>0

      },

      // 这一部分是输入 因为点击按钮所出现的数据无非就两个一个是true 一个是false 所以在这边,用户输入一个数据返回给父组件,那么就是输入是否全选

      set(value){

       this.checkdone(value)

      }

    }

  },

  methods:{

    clearAll(){

      if(confirm('你确定删除吗?')){

         this.clearAlltodo()  

      }

    }

    // checkall(e){

      // this.checkdone(e.target.checked)

    //  }

  }

};

</script>

<style scoped>

/*footer*/

.todo-footer {

  height: 40px;

  line-height: 40px;

  padding-left: 6px;

  margin-top: 5px;

}

.todo-footer label {

  display: inline-block;

  margin-right: 20px;

  cursor: pointer;

}

.todo-footer label input {

  position: relative;

  top: -1px;

  vertical-align: middle;

  margin-right: 5px;

}

.todo-footer button {

  float: right;

  margin-top: 5px;

}

</style>

父组件1:App.vue

<template>

  <div class="todo-container">

    <div class="todo-wrap">

    <!-- 由App去支配数据 分别将数据传给MyHeader -->

      <MyHeader :addtodo="addtodo"/>

       <!-- 由App去支配数据 分别将数据传给MyList -->

      <MyList :todo="todo" :checktodo="checktodo" :deletTodo="deletTodo"/>

       <!-- 全选or取消全选将数据传给MyFooter    清除数据内容   -->

      <MyFooter :todo="todo" :checkdone="checkdone"  :clearAlltodo="clearAlltodo"/>

    </div>

  </div>

</template>

<script>

import MyHeader from "./components/MyHeader.vue";

import MyList from "./components/MyList.vue";

import MyFooter from "./components/MyFooter.vue";

export default {

  name: "App",

  data() {

    return {

      // 这里采用的是本地存储,在watch中进行监听事件 若刷新后,需要的时候 从控制台中获取数据  起初数据是空  会报错,所以我们可以

      //再添加一个判断  那就是[] 空数组,当控制台中是空时,那么就执行空数组,不会报错了

    todo:JSON.parse(localStorage.getItem('todo'))||[]

    }

  },

  methods:{

    // 这里采用的是父传给子,然后子通过父传的路径再按照原路径传回  所以这里建立了方法,内部是数据是头部input框传的

     addtodo(todoObj){

      //  将传入的数据放在todo数组的最前面

     this.todo.unshift(todoObj);

     },

     //勾选或取消勾选       将值处理过之后把数据传给MyList 随后由MyList把数据传给MyListItem(逐层传递)

     checktodo(id){

      this.todo.forEach((todo)=>{

        // 若该todo里的id和传入的id值是相同的

      if(todo.id === id){

       //那么就取反,并将取反的值赋给原todo中

       todo.done =!todo.done;

      }

      })

     },

    //删除一个todo 做好数据的处理之后   需要将数据传给MyItem那么就必须经过MyList

     deletTodo(id){

       //由于filter是不改变原数组的,所以我们需要将过滤出的新数组返回给原数组中

      this.todo = this.todo.filter((to)=>{

      //函数体

       return to.id !== id

       })

     },

     //全选or取消全选

     checkdone(done){

      this.todo.forEach((to)=>{

       to.done=done;

      })

     },

     //清除所有已经完成的数据

     clearAlltodo(){

      this.todo = this.todo.filter((to)=>{

       return  !to.done

       })

     }

  },

  watch:{

    // todo(value){

      // localStorage.setItem('todo',JSON.stringify(value))

    // }

    todo:{

      // 深度监视  必须完整编写  否则就会报错(错误原因是:将todo内部的内容转为数组的格式存储)

      deep: true, //这里设置true 初始值是真  随后触发选项按钮就为false

      handler(val){

      localStorage.setItem('todo',JSON.stringify(val))

      }

    }

  },

  components: {

    MyHeader,

    MyList,

    MyFooter,

  },

};

</script>

<style>

/*base*/

body {

  background: #fff;

}

.btn {

  display: inline-block;

  padding: 4px 12px;

  margin-bottom: 0;

  font-size: 14px;

  line-height: 20px;

  text-align: center;

  vertical-align: middle;

  cursor: pointer;

  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),

    0 1px 2px rgba(0, 0, 0, 0.05);

  border-radius: 4px;

}

.btn-danger {

  color: #fff;

  background-color: #da4f49;

  border: 1px solid #bd362f;

}

.btn-danger:hover {

  color: #fff;

  background-color: #bd362f;

}

.btn:focus {

  outline: none;

}

.todo-container {

  width: 600px;

  margin: 0 auto;

}

.todo-container .todo-wrap {

  padding: 10px;

  border: 1px solid #ddd;

  border-radius: 5px;

}

</style>

第二部分:最终实现效果

进入时:

 进入后:

 删除时:

 

删除后:

 选or非选:

 第三部分:动画知识剖析

四种情况:(与他们在style中的排列顺序有关系)
1、appear-class、 appear-to-class、 appear-active-class或者 appear-to-class、appear-class、 appear-active-class的排列顺序,此时只有appear-active-class的属性起作用。
2、appear-active-class、appear-class、 appear-to-class
此时appear-active-class的不起作用,由appear-class过渡到appear-to-class属性。
3、appear-class、appear-active-class、 appear-to-class
此时appear-class属性不起作用,由appear-active-class过渡到 appear-to-class属性。
4、 appear-to-class、 appear-active-class、appear-class
此时appear-to-class不起作用,由appear-class过渡到 appear-active-class属性。
enter也有相似的问题

 

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

共创splendid--与您携手

鼓励会让我为您创造出更优质作品

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值