目录
简介
第一部分:子组件内容及详细注释
第二部分:最终实现效果
第三部分:动画知识剖析
简介
该项目(购物车)制作主要采用了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也有相似的问题