1 静态组件
App.vue
<template>
<div class="todo-container">
<div class="todo-wrap">
<UserHeader/>
<UserList/>
<UserFooter/>
</div>
</div>
</template>
<script>
import UserHeader from "@/components/UserHeader"
import UserFooter from "@/components/UserFooter"
import UserList from "@/components/UserList"
export default {
name: 'App',
data() {
return {}
},
methods: {},
components: {UserHeader, UserList, UserFooter}
}
</script>
<style>
/* base */
body {
background-color: #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>
UserHeader.vue
<template>
<div class="todo-header">
<input type="text"
placeholder="请输入你的任务名称,按回车键确认">
</div>
</template>
<script>
export default {
name: `UserList`
}
</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>
UserList.vue
<template>
<ul class="todo-main">
<UserItem/>
<UserItem/>
<UserItem/>
</ul>
</template>
<script>
import UserItem from "@/components/UserItem";
export default {
name: `UserList`,
components: {UserItem}
}
</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>
UserItem.vue
<template>
<li>
<label>
<input type="checkbox"/>
<span>xxxxx</span>
</label>
<button class="btn btn-danger" style="display: none">删除</button>
</li>
</template>
<script>
export default {
name: `UserItem`
}
</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>
UserFooter.vue
<template>
<div class="todo-footer">
<label>
<input type="checkbox"/>
</label>
<span>
<span>已完成0</span> / 全部2
</span>
<button class="btn btn-danger">清除已完成任务</button>
</div>
</template>
<script>
export default {
name: `UserFooter`
}
</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>
2 TodoList案例_初始化列表
UserList.vue
<template>
<ul class="todo-main">
<UserItem v-for="todo in todoList" :key="todoList.id" :todo="todo"></UserItem>
</ul>
</template>
<script>
import UserItem from "@/components/UserItem";
export default {
name: `UserList`,
components: {UserItem},
data() {
return {
todoList: [
{id: '0001', title: '抽烟', done: true},
{id: '0002', title: '喝酒', done: false},
{id: '0003', title: '烫头', done: true},
]
}
}
}
</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>
UserItem.vue
<template>
<li>
<label>
<input type="checkbox" :checked="todo.done"/>
<span>{{ todo.title }}</span>
</label>
<button class="btn btn-danger" style="display: none">删除</button>
</li>
</template>
<script>
export default {
name: `UserItem`,
// 声明接受todo
props: ['todo']
}
</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>
3 TodoList案例_添加
1)uuid标准:用于生成全球唯一的字符串编码。
(因为这个包太大了,所以本节中用的是uuid的精简版,nanoid)
2)nanoid安装指令:npm i nanoid
nanoid的库用了分别暴露,所以需要这样引用:
import {nanoid} from 'nanoid'
3)子组件传值父组件简单原理:
父亲先传给儿子一个函数
儿子在合适的时候调用,调用的时候父亲就能收到参数
4)整个添加的流程:
首先在MyHeader中,添加用了add方法,add方法调用 this.addTodo() 将 todoObj的值传给了App。
传给App后,addTodo方法获取值,操作data中的todos。
Vue收到todos变了,就开始重新解析模板。
重新解析的时候,显然todos已经变了,则MyList中收到的也会跟着变化。
MyList收到数据变化,也开始重新解析模板,通过比较数据,虚拟DOM增加,于是MyItem增加。
App.vue
<template>
<div class="todo-container">
<div class="todo-wrap">
<UserHeader :addTodo="addTodo"/>
<UserList :todoList="todoList"/>
<UserFooter/>
</div>
</div>
</template>
<script>
import UserHeader from "@/components/UserHeader"
import UserFooter from "@/components/UserFooter"
import UserList from "@/components/UserList"
export default {
name: 'App',
data() {
return {
todoList: [
{id: '0001', title: '抽烟', done: true},
{id: '0002', title: '喝酒', done: false},
{id: '0003', title: '烫头', done: true},
]
}
},
methods: {
addTodo(data) {
this.todoList.unshift(data)
}
},
components: {UserHeader, UserList, UserFooter}
}
</script>
<style>
/* base */
body {
background-color: #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>
UserHeader.vue
<template>
<div class="todo-header">
<input type="text"
placeholder="请输入你的任务名称,按回车键确认"
@keyup.enter="add"
v-model="inputText">
</div>
</template>
<script>
import {nanoid} from "nanoid"
export default {
name: `UserList`,
data() {
return {
inputText: ''
}
},
methods: {
add() {
// 将用户的输入包装成一个对象
if (this.inputText.trim()) {
const todo = {id: nanoid(), title: this.inputText, done: false}
this.addTodo(todo)
this.inputText = ''
}
}
},
props: ['addTodo']
}
</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>
UserList.vue
<template>
<ul class="todo-main">
<UserItem v-for="todo in todoList" :key="todo.id" :todo="todo"></UserItem>
</ul>
</template>
<script>
import UserItem from "@/components/UserItem";
export default {
name: `UserList`,
components: {UserItem},
data() {
return {}
},
props: ['todoList']
}
</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>
4 TodoList案例_勾选
App.vue
<template>
<div class="todo-container">
<div class="todo-wrap">
<UserHeader :addTodo="addTodo"/>
<UserList :todoList="todoList" :checkTodo="checkTodo"/>
<UserFooter/>
</div>
</div>
</template>
<script>
import UserHeader from "@/components/UserHeader"
import UserFooter from "@/components/UserFooter"
import UserList from "@/components/UserList"
export default {
name: 'App',
data() {
return {
todoList: [
{id: '0001', title: '抽烟', done: true},
{id: '0002', title: '喝酒', done: false},
{id: '0003', title: '烫头', done: true},
]
}
},
methods: {
addTodo(data) {
this.todoList.unshift(data)
},
checkTodo(id) {
this.todoList.forEach(todo => {
if (todo.id === id) {
todo.done = !todo.done
}
})
}
},
components: {UserHeader, UserList, UserFooter}
}
</script>
<style>
/* base */
body {
background-color: #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>
UserList.vue
<template>
<ul class="todo-main">
<UserItem
v-for="todo in todoList"
:key="todo.id" :todo="todo"
:checkTodo="checkTodo"></UserItem>
</ul>
</template>
<script>
import UserItem from "@/components/UserItem";
export default {
name: `UserList`,
components: {UserItem},
data() {
return {}
},
props: ['todoList', 'checkTodo']
}
</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>
UserItem.vue
<template>
<li>
<label>
<!-- <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>-->
<input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
<span>{{ todo.title }}</span>
</label>
<button class="btn btn-danger" style="display: none">删除</button>
</li>
</template>
<script>
export default {
name: `UserItem`,
// 声明接受todo
props: ['todo', 'checkTodo'],
methods: {
handleCheck(id) {
// 通知App组件将对应的todo 的 done 取反
this.checkTodo(id)
}
}
}
</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>
提出一个新的问题:
<input type="checkbox" v-model="todo.done"/>
App.vue
<template>
<div class="todo-container">
<div class="todo-wrap">
<UserHeader :addTodo="addTodo"/>
<UserList :todoList="todoList"/>
<UserFooter/>
</div>
</div>
</template>
<script>
import UserHeader from "@/components/UserHeader"
import UserFooter from "@/components/UserFooter"
import UserList from "@/components/UserList"
export default {
name: 'App',
data() {
return {
todoList: [
{id: '0001', title: '抽烟', done: true},
{id: '0002', title: '喝酒', done: false},
{id: '0003', title: '烫头', done: true},
]
}
},
methods: {
addTodo(data) {
this.todoList.unshift(data)
},
},
components: {UserHeader, UserList, UserFooter}
}
</script>
<style>
/* base */
body {
background-color: #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>
UserList.vue
<template>
<ul class="todo-main">
<UserItem
v-for="todo in todoList"
:key="todo.id" :todo="todo"></UserItem>
</ul>
</template>
<script>
import UserItem from "@/components/UserItem";
export default {
name: `UserList`,
components: {UserItem},
data() {
return {}
},
props: ['todoList']
}
</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>
UserItem.vue
<template>
<li>
<label>
<!-- <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>-->
<input type="checkbox" v-model="todo.done"/>
<span>{{ todo.title }}</span>
</label>
<button class="btn btn-danger" style="display: none">删除</button>
</li>
</template>
<script>
export default {
name: `UserItem`,
// 声明接受todo pros是只读的、不可以修改
props: ['todo'],
methods: {}
}
</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>
1)数据在哪个页面,那么对数据的操作就应该在哪个页面。
2)本节中的第二个做法:
<input type="checkbox" v-model="todo.done"/>
因为v-model是双向绑定数据,所以修改有效。
什么叫props被修改了?
本节中的举例:
Vue不能监测到的修改:let obj = {a:1,b:2},修改成 obj.a = 666
Vue能监测到的修改:let obj = {a:1,b:2},修改成 obj = {x:100,y:200}
也就是说Vue监视的是浅层次的修改。
不建议这样做,因为这样会违反原则修改props,虽然并没有被Vue监测到。
5 TodoList案例_删除
App.vue
<template>
<div class="todo-container">
<div class="todo-wrap">
<UserHeader :addTodo="addTodo"/>
<UserList :todoList="todoList" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
<UserFooter/>
</div>
</div>
</template>
<script>
import UserHeader from "@/components/UserHeader"
import UserFooter from "@/components/UserFooter"
import UserList from "@/components/UserList"
export default {
name: 'App',
data() {
return {
todoList: [
{id: '0001', title: '抽烟', done: true},
{id: '0002', title: '喝酒', done: false},
{id: '0003', title: '烫头', done: true},
]
}
},
methods: {
addTodo(data) {
this.todoList.unshift(data)
},
checkTodo(id) {
this.todoList.forEach(todo => {
if (todo.id === id) {
todo.done = !todo.done
}
})
},
deleteTodo(id) {
this.todoList = this.todoList.filter(todo => {
return todo.id !== id
})
}
},
components: {UserHeader, UserList, UserFooter}
}
</script>
<style>
/* base */
body {
background-color: #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>
UserList.vue
<template>
<ul class="todo-main">
<UserItem
v-for="todo in todoList"
:key="todo.id" :todo="todo"
:checkTodo="checkTodo"
:deleteTodo="deleteTodo"></UserItem>
</ul>
</template>
<script>
import UserItem from "@/components/UserItem";
export default {
name: `UserList`,
components: {UserItem},
data() {
return {}
},
props: ['todoList', 'checkTodo', 'deleteTodo']
}
</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>
UserItem.vue
<template>
<li>
<label>
<!-- <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>-->
<input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
<span>{{ todo.title }}</span>
</label>
<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
</li>
</template>
<script>
export default {
name: `UserItem`,
// 声明接受todo
props: ['todo', 'checkTodo', 'deleteTodo'],
methods: {
handleCheck(id) {
// 通知App组件将对应的todo 的 done 取反
this.checkTodo(id)
},
handleDelete(id) {
if (confirm('确定删除吗?')) {
this.deleteTodo(id)
}
}
}
}
</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>
6 TodoList案例_底部统计
App.vue
<template>
<div class="todo-container">
<div class="todo-wrap">
<UserHeader :addTodo="addTodo"/>
<UserList :todoList="todoList" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
<UserFooter :todoList="todoList"/>
</div>
</div>
</template>
<script>
import UserHeader from "@/components/UserHeader"
import UserFooter from "@/components/UserFooter"
import UserList from "@/components/UserList"
export default {
name: 'App',
data() {
return {
todoList: [
{id: '0001', title: '抽烟', done: true},
{id: '0002', title: '喝酒', done: false},
{id: '0003', title: '烫头', done: true},
]
}
},
methods: {
addTodo(data) {
this.todoList.unshift(data)
},
checkTodo(id) {
this.todoList.forEach(todo => {
if (todo.id === id) {
todo.done = !todo.done
}
})
},
deleteTodo(id) {
this.todoList = this.todoList.filter(todo => {
return todo.id !== id
})
}
},
components: {UserHeader, UserList, UserFooter}
}
</script>
<style>
/* base */
body {
background-color: #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>
UserFooter.vue
<template>
<div class="todo-footer">
<label>
<input type="checkbox"/>
</label>
<span>
<span>已完成{{ doneTotal }}</span> / 全部{{ todoList.length }}
</span>
<button class="btn btn-danger">清除已完成任务</button>
</div>
</template>
<script>
export default {
name: `UserFooter`,
props: ['todoList'],
computed: {
doneTotal: function () {
// return this.todoList.filter(todo => todo.done).length
return this.todoList.reduce((pre, current) => pre + (current.done ? 1 : 0), 0)
}
}
}
</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>
7 TodoList案例_底部交互
App.vue
<template>
<div class="todo-container">
<div class="todo-wrap">
<UserHeader :addTodo="addTodo"/>
<UserList :todoList="todoList" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
<UserFooter :todoList="todoList" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"/>
</div>
</div>
</template>
<script>
import UserHeader from "@/components/UserHeader"
import UserFooter from "@/components/UserFooter"
import UserList from "@/components/UserList"
export default {
name: 'App',
data() {
return {
todoList: [
{id: '0001', title: '抽烟', done: true},
{id: '0002', title: '喝酒', done: false},
{id: '0003', title: '烫头', done: true},
]
}
},
methods: {
addTodo(data) {
this.todoList.unshift(data)
},
checkTodo(id) {
this.todoList.forEach(todo => {
if (todo.id === id) {
todo.done = !todo.done
}
})
},
deleteTodo(id) {
this.todoList = this.todoList.filter(todo => {
return todo.id !== id
})
},
checkAllTodo(done) {
this.todoList.forEach(todo => {
todo.done = done
})
},
clearAllTodo() {
this.todoList = this.todoList.filter(todo => !todo.done)
}
},
components: {UserHeader, UserList, UserFooter}
}
</script>
<style>
/* base */
body {
background-color: #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>
UserFooter.vue
<template>
<div class="todo-footer" v-show="total">
<label>
<!-- <input type="checkbox" :checked="isAll" @change="checkAll"/>-->
<input type="checkbox" v-model="isAll"/>
</label>
<span>
<span>已完成{{ doneTotal }}</span> / 全部{{ total }}
</span>
<button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
</div>
</template>
<script>
export default {
name: `UserFooter`,
props: ['todoList', 'checkAllTodo', 'clearAllTodo'],
computed: {
doneTotal() {
// return this.todoList.filter(todo => todo.done).length
return this.todoList.reduce((pre, current) => pre + (current.done ? 1 : 0), 0)
},
total() {
return this.todoList.length
},
// 只被读取、不被修改的方式-函数式
/*isAll() {
return this.doneTotal === this.total && this.total > 0
},*/
isAll: {
get() {
return this.doneTotal === this.total && this.total > 0
},
set(value) {
this.checkAllTodo(value)
}
},
},
methods: {
checkAll(event) {
this.checkAllTodo(event.target.checked)
},
clearAll(event) {
this.checkAllTodo()
}
}
}
</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>
8 TodoList案例_总结
1.组件化编码流程:
1)拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
2)实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
一个组件在用:放在组件自身即可。
一些组件在用:放在他们共同的父组件上(状态提升)
3)实现交互:从绑定事件开始。
2.props适用于:
1)父组件 ==> 子组件 通信
2)子组件 ==> 父组件 通信 (要求父先给子一个函数)
3.使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
4.props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。
9 浏览器本地存储
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="localStorage">
<button onclick="save()">点击保存一个数据</button>
<button onclick="read()">点击读取一个数据</button>
<button onclick="deleteOne()">点击删除一个数据</button>
<button onclick="clearAll()">点击清空所有数据</button>
</div>
<script type="text/javascript">
let person = {name: 'zhaoshuai-lc', age: 27}
let name = 'zhaoshuai-lc'
let age = '27'
function save() {
localStorage.setItem('name', name)
localStorage.setItem('age', 'age')
localStorage.setItem('person', JSON.stringify(person))
}
function read() {
console.log(localStorage.getItem('person'));
console.log(localStorage.getItem('name'));
console.log(localStorage.getItem('age'));
}
function deleteOne() {
localStorage.removeItem('name')
}
function clearAll() {
localStorage.clear()
}
</script>
</body>
</html>
清空之后 再读取数据:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="sessionStorage">
<button onclick="save()">点击保存一个数据</button>
<button onclick="read()">点击读取一个数据</button>
<button onclick="deleteOne()">点击删除一个数据</button>
<button onclick="clearAll()">点击清空所有数据</button>
</div>
<script type="text/javascript">
let person = {name: 'zhaoshuai-lc', age: 27}
let name = 'zhaoshuai-lc'
let age = '27'
function save() {
sessionStorage.setItem('name', name)
sessionStorage.setItem('age', 'age')
sessionStorage.setItem('person', JSON.stringify(person))
}
function read() {
console.log(sessionStorage.getItem('person'));
console.log(sessionStorage.getItem('name'));
console.log(sessionStorage.getItem('age'));
}
function deleteOne() {
sessionStorage.removeItem('name')
}
function clearAll() {
sessionStorage.clear()
}
</script>
</body>
</html>
1.存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
2.浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。
3.相关API:
1)xxxxxStorage.setItem( 'key', 'value' );
该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。
2)xxxxxStorage.getItem( 'person' );
该方法接受一个键名作为参数,返回键名对应的值。
3)xxxxxStorage.removeItem( 'key' );
该方法接受一个键名作为参数,并把该键名从存储中删除。
4)xxxxxStorage.clear()
该方法会清空存储中的所有数据。
4.备注:
1)SessionStorage存储的内容会随着浏览器窗口关闭而消失。
2)LocalStorage存储的内容,需要手动清楚才会消失。
3)xxxxxStorage.getItem( xxx ) 如果xxx对应的value获取不到,那么getItem的返回值是null。
4)JSON.parse(null)的结果依然是null。
10 TodoList_本地存储
<template>
<div class="todo-container">
<div class="todo-wrap">
<UserHeader :addTodo="addTodo"/>
<UserList :todoList="todoList" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
<UserFooter :todoList="todoList" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"/>
</div>
</div>
</template>
<script>
import UserHeader from "@/components/UserHeader"
import UserFooter from "@/components/UserFooter"
import UserList from "@/components/UserList"
export default {
name: 'App',
data() {
return {
todoList: JSON.parse(localStorage.getItem('todoList')) || []
}
},
methods: {
addTodo(data) {
this.todoList.unshift(data)
},
checkTodo(id) {
this.todoList.forEach(todo => {
if (todo.id === id) {
todo.done = !todo.done
}
})
},
deleteTodo(id) {
this.todoList = this.todoList.filter(todo => {
return todo.id !== id
})
},
checkAllTodo(done) {
this.todoList.forEach(todo => {
todo.done = done
})
},
clearAllTodo() {
this.todoList = this.todoList.filter(todo => !todo.done)
}
},
watch: {
// 默认监视的是数组层次-浅监视
/* todoList(value) {
localStorage.setItem('todoList', JSON.stringify(value))
},*/
todoList: {
// 深度监视
deep: true,
handler(newValue, oldValue) {
localStorage.setItem('todoList', JSON.stringify(newValue))
}
},
},
components: {UserHeader, UserList, UserFooter}
}
</script>
<style>
/* base */
body {
background-color: #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>
11 组件自定义事件_绑定
子组件给父组件传值:
School.vue
<template>
<!-- 组件的结构-->
<div class="sc">
<h2>学校名称:{{ name }}</h2>
<h2>学校地址:{{ address }}</h2>
<button @click="sendSchoolName">传值给App-SchoolName</button>
</div>
</template>
<script>
export default {
name: `School`, // 跟文件名保持一致
data() {
return {
name: '北京大学',
address: '北京'
}
},
methods: {
sendSchoolName() {
return this.getSchoolName(this.name)
}
},
props: ['getSchoolName']
}
</script>
<style>
/*组件的样式*/
.sc {
background-color: aquamarine;
padding: 5px;
}
</style>
App.vue
<template>
<div class="app">
<h1>{{ msg }}</h1>
<School :getSchoolName="getSchoolName"/>
<Student/>
</div>
</template>
<script>
import School from "./components/School"
import Student from './components/Student'
export default {
name: "App",
components: {Student, School},
data() {
return {
msg: 'Hello-zhaoshuai-lc',
}
},
methods: {
getSchoolName(name) {
console.log(name)
}
},
mounted() {
},
}
</script>
<style scoped>
.app {
background-color: gray;
}
</style>
自定义事件-1:通过父组件给子组件绑定一个自定义事件:子给父传递数据(第一种写法,使用@或v-on)
App.vue
<template>
<div class="app">
<h1>{{ msg }}</h1>
<!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
<School :getSchoolName="getSchoolName"/>
<!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第一种写法,使用@或v-on) -->
<Student v-on:inspur="getStudentName"/>
</div>
</template>
<script>
import School from "./components/School"
import Student from './components/Student'
export default {
name: "App",
components: {Student, School},
data() {
return {
msg: 'Hello-zhaoshuai-lc',
}
},
methods: {
getSchoolName(name) {
console.log('getSchoolName', name)
},
getStudentName(name) {
console.log('getStudentName', name)
}
},
mounted() {
},
}
</script>
<style scoped>
.app {
background-color: gray;
}
</style>
Student.vue
<template>
<!-- 组件的结构-->
<div class="student">
<h2>学生名字:{{ name }}</h2>
<h2>学生性别:{{ sex }}</h2>
<button @click="sendStudentName">传值给App-StudentName</button>
</div>
</template>
<script>
export default {
name: `Student`,
data() {
return {
name: 'zhaoshuai-lc',
sex: 'male'
}
},
methods: {
sendStudentName() {
// 触发Student组件身上的inspur事件
this.$emit('inspur', this.name)
}
}
}
</script>
<style>
/*组件的样式*/
.student {
background-color: pink;
padding: 5px;
}
</style>
自定义事件-2 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第二种写法,使用ref)
App.vue
<template>
<div class="app">
<h1>{{ msg }}</h1>
<!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
<School :getSchoolName="getSchoolName"/>
<!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第一种写法,使用@或v-on) -->
<!-- <Student v-on:inspur.once="getStudentName"/>-->
<!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第二种写法,使用ref) -->
<Student ref="student"/>
</div>
</template>
<script>
import School from "./components/School"
import Student from './components/Student'
export default {
name: "App",
components: {Student, School},
data() {
return {
msg: 'Hello-zhaoshuai-lc',
}
},
methods: {
getSchoolName(name) {
console.log('getSchoolName', name)
},
getStudentName(name, ...params) {
console.log('getStudentName', name, params)
}
},
mounted() {
setTimeout(() => {
// this.$refs.student.$on('inspur', this.getStudentName)
// 触发一次
this.$refs.student.$once('inspur', this.getStudentName)
}, 0)
},
}
</script>
<style scoped>
.app {
background-color: gray;
}
</style>
Student.vue
<template>
<!-- 组件的结构-->
<div class="student">
<h2>学生名字:{{ name }}</h2>
<h2>学生性别:{{ sex }}</h2>
<button @click="sendStudentName">传值给App-StudentName</button>
</div>
</template>
<script>
export default {
name: `Student`,
data() {
return {
name: 'zhaoshuai-lc',
sex: 'male'
}
},
methods: {
sendStudentName() {
// 触发Student组件身上的inspur事件
this.$emit('inspur', this.name, this.sex)
}
}
}
</script>
<style>
/*组件的样式*/
.student {
background-color: pink;
padding: 5px;
}
</style>
12 组件自定义事件_绑定
销毁了当前student组件的实例vc,销毁后所有student实例的自定义事件全都不奏效。
App.vue
<template>
<div class="app">
<h1>{{ msg }}</h1>
<!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
<School :getSchoolName="getSchoolName"/>
<!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第一种写法,使用@或v-on) -->
<Student @inspur.once="getStudentName"/>
<!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第二种写法,使用ref) -->
<!-- <Student ref="student"/>-->
</div>
</template>
<script>
import School from "./components/School"
import Student from './components/Student'
export default {
name: "App",
components: {Student, School},
data() {
return {
msg: 'Hello-zhaoshuai-lc',
}
},
methods: {
getSchoolName(name) {
console.log('getSchoolName', name)
},
getStudentName(name, ...params) {
console.log('getStudentName', name, params)
}
},
mounted() {
setTimeout(() => {
// this.$refs.student.$on('inspur', this.getStudentName)
// 触发一次
this.$refs.student.$once('inspur', this.getStudentName)
}, 0)
},
}
</script>
<style scoped>
.app {
background-color: gray;
}
</style>
Student.vue
<template>
<!-- 组件的结构-->
<div class="student">
<h2>学生名字:{{ name }}</h2>
<h2>学生性别:{{ sex }}</h2>
<button @click="sendStudentName">传值给App-StudentName</button>
<button @click="unbind">解绑inspur事件</button>
</div>
</template>
<script>
export default {
name: `Student`,
data() {
return {
name: 'zhaoshuai-lc',
sex: 'male'
}
},
methods: {
sendStudentName() {
// 触发Student组件身上的inspur事件
this.$emit('inspur', this.name, this.sex)
},
unbind() {
// 解绑一个自定义事件
// this.$off('inspur')
// 解绑多个自定义事件 用一个数组传值
// this.$off(['inspur'])
// 所有的自定义事件全都解绑
this.$off([])
}
}
}
</script>
<style>
/*组件的样式*/
.student {
background-color: pink;
padding: 5px;
}
</style>
13 组件自定义事件_总结
App.vue
<template>
<div class="app">
<h1>{{ msg }} {{ studentName }}</h1>
<!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
<School :getSchoolName="getSchoolName"/>
<!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第一种写法,使用@或v-on) -->
<Student @inspur.once="getStudentName"/>
<!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第二种写法,使用ref) -->
<!-- <Student ref="student"/>-->
</div>
</template>
<script>
import School from "./components/School"
import Student from './components/Student'
export default {
name: "App",
components: {Student, School},
data() {
return {
msg: 'Hello-',
studentName: ''
}
},
methods: {
getSchoolName(name) {
console.log('getSchoolName', name)
},
getStudentName(name, ...params) {
this.studentName = name
console.log('getStudentName', name, params)
}
},
mounted() {
setTimeout(() => {
// this.$refs.student.$on('inspur', this.getStudentName)
// 触发一次
this.$refs.student.$once('inspur', this.getStudentName)
}, 0)
},
}
</script>
<style scoped>
.app {
background-color: gray;
}
</style>
引出问题1:
<template>
<div class="app">
<h1>{{ msg }} {{ studentName }}</h1>
<!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
<School :getSchoolName="getSchoolName"/>
<!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第一种写法,使用@或v-on) -->
<!-- <Student @inspur.once="getStudentName"/>-->
<!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第二种写法,使用ref) -->
<Student ref="student"/>
</div>
</template>
<script>
import School from "./components/School"
import Student from './components/Student'
export default {
name: "App",
components: {Student, School},
data() {
return {
msg: 'Hello-',
studentName: ''
}
},
methods: {
getSchoolName(name) {
console.log('getSchoolName', name)
},
getStudentName(name, ...params) {
this.studentName = name
console.log('getStudentName', name, params)
}
},
mounted() {
setTimeout(() => {
// this.$refs.student.$on('inspur', this.getStudentName)
// 改写方式一:
this.$refs.student.$on('inspur', function () {
this.studentName = name
})
}, 0)
},
}
</script>
<style scoped>
.app {
background-color: gray;
}
</style>
正确方式1:
this.$refs.student.$on('inspur', this.getStudentName)
正确方式2:箭头函数 this->App.vue
<template>
<div class="app">
<h1>{{ msg }} {{ studentName }}</h1>
<!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
<School :getSchoolName="getSchoolName"/>
<!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第一种写法,使用@或v-on) -->
<!-- <Student @inspur.once="getStudentName"/>-->
<!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第二种写法,使用ref) -->
<Student ref="student"/>
</div>
</template>
<script>
import School from "./components/School"
import Student from './components/Student'
export default {
name: "App",
components: {Student, School},
data() {
return {
msg: 'Hello-',
studentName: ''
}
},
methods: {
getSchoolName(name) {
console.log('getSchoolName', name)
},
getStudentName(name, ...params) {
this.studentName = name
console.log('getStudentName', name, params)
}
},
mounted() {
setTimeout(() => {
// this.$refs.student.$on('inspur', this.getStudentName)
// 改写方式一:
this.$refs.student.$on('inspur', (name) => {
console.log(this)
this.studentName = name
})
}, 0)
},
}
</script>
<style scoped>
.app {
background-color: gray;
}
</style>
引出问题2: 自定义组件绑定原生事件 (原生事件为自定义事件)
<Student ref="student" @click.native="showAlert"/>
<template>
<div class="app">
<h1>{{ msg }} {{ studentName }}</h1>
<!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
<School :getSchoolName="getSchoolName"/>
<!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第一种写法,使用@或v-on) -->
<!-- <Student @inspur.once="getStudentName"/>-->
<!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第二种写法,使用ref) -->
<Student ref="student" @click.native="showAlert"/>
</div>
</template>
<script>
import School from "./components/School"
import Student from './components/Student'
export default {
name: "App",
components: {Student, School},
data() {
return {
msg: 'Hello-',
studentName: ''
}
},
methods: {
getSchoolName(name) {
console.log('getSchoolName', name)
},
getStudentName(name, ...params) {
this.studentName = name
console.log('getStudentName', name, params)
},
showAlert() {
alert('@click.native="showAlert"')
}
},
mounted() {
setTimeout(() => {
// this.$refs.student.$on('inspur', this.getStudentName)
// 改写方式一:
this.$refs.student.$on('inspur', (name) => {
console.log(this)
this.studentName = name
})
}, 0)
},
}
</script>
<style scoped>
.app {
background-color: gray;
}
</style>
组件的自定义事件
1.一种组件间通信的方式,适用于:子组件 ==> 父组件。
2.使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)
3.绑定自定义事件:
(1)第一种方式,在父组件中:
<Demo @inpsur="test"/> 或 <Demo v-on:inspur="test"/>
(2)第二种方式,在父组件中:
<Demo ref="demo">
......
mounted(){
this.$refs.xxx.$on('inspur',this.test)
}
(3)若想让自定义事件只能触发一次,可以使用 once 修饰符或 $once 方法。
4.触发自定义事件:this.$emit('inspur',数据)
5.解绑自定义事件:this.$off('inspur')
6.组件上也可以绑定原生DOM事件,需要使用 native 修饰符。
7.注意:通过 this.$refs.xxx.$on('inspur',回调) 绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!
14 TodoList案例_自定义事件
App.vue
<template>
<div class="todo-container">
<div class="todo-wrap">
<UserHeader @addTodo="addTodo"/>
<UserList :todoList="todoList" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
<UserFooter :todoList="todoList" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/>
</div>
</div>
</template>
<script>
import UserHeader from "@/components/UserHeader"
import UserFooter from "@/components/UserFooter"
import UserList from "@/components/UserList"
export default {
name: 'App',
data() {
return {
todoList: JSON.parse(localStorage.getItem('todoList')) || []
}
},
methods: {
addTodo(data) {
this.todoList.unshift(data)
},
checkTodo(id) {
this.todoList.forEach(todo => {
if (todo.id === id) {
todo.done = !todo.done
}
})
},
deleteTodo(id) {
this.todoList = this.todoList.filter(todo => {
return todo.id !== id
})
},
checkAllTodo(done) {
this.todoList.forEach(todo => {
todo.done = done
})
},
clearAllTodo() {
this.todoList = this.todoList.filter(todo => !todo.done)
}
},
watch: {
todoList: {
// 深度监视
deep: true,
handler(newValue, oldValue) {
localStorage.setItem('todoList', JSON.stringify(newValue))
}
},
},
components: {UserHeader, UserList, UserFooter}
}
</script>
<style>
/* base */
body {
background-color: #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>
UserHeader.vue
<template>
<div class="todo-header">
<input type="text"
placeholder="请输入你的任务名称,按回车键确认"
@keyup.enter="add"
v-model="inputText">
</div>
</template>
<script>
import {nanoid} from "nanoid"
export default {
name: `UserHeader`,
data() {
return {
inputText: ''
}
},
methods: {
add() {
// 将用户的输入包装成一个对象
if (this.inputText.trim()) {
const todo = {id: nanoid(), title: this.inputText, done: false}
this.$emit('addTodo', todo)
this.inputText = ''
}
}
},
}
</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>
UserFooter.vue
<template>
<div class="todo-footer" v-show="total">
<label>
<input type="checkbox" :checked="isAll" @change="checkAll"/>
<!-- <input type="checkbox" v-model="isAll"/>-->
</label>
<span>
<span>已完成{{ doneTotal }}</span> / 全部{{ total }}
</span>
<button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
</div>
</template>
<script>
export default {
name: `UserFooter`,
props: ['todoList'],
computed: {
doneTotal() {
// return this.todoList.filter(todo => todo.done).length
return this.todoList.reduce((pre, current) => pre + (current.done ? 1 : 0), 0)
},
total() {
return this.todoList.length
},
// 只被读取、不被修改的方式-函数式
/*isAll() {
return this.doneTotal === this.total && this.total > 0
},*/
isAll: {
get() {
return this.doneTotal === this.total && this.total > 0
},
set(value) {
// this.checkAllTodo(value)
this.$emit('checkAllTodo', value)
}
},
},
methods: {
checkAll(event) {
// this.checkAllTodo(event.target.checked)
this.$emit('checkAllTodo', event.target.checked)
},
clearAll(event) {
// this.clearAllTodo()
this.$emit('clearAllTodo')
}
}
}
</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>
UserList.vue
<template>
<ul class="todo-main">
<UserItem
v-for="todo in todoList"
:key="todo.id" :todo="todo"
:checkTodo="checkTodo"
:deleteTodo="deleteTodo"></UserItem>
</ul>
</template>
<script>
import UserItem from "@/components/UserItem";
export default {
name: `UserList`,
components: {UserItem},
data() {
return {}
},
props: ['todoList', 'checkTodo', 'deleteTodo']
}
</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>
UserItem.vue
<template>
<li>
<label>
<!-- <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>-->
<input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
<span>{{ todo.title }}</span>
</label>
<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
</li>
</template>
<script>
export default {
name: `UserItem`,
// 声明接受todo
props: ['todo', 'checkTodo', 'deleteTodo'],
methods: {
handleCheck(id) {
// 通知App组件将对应的todo 的 done 取反
this.checkTodo(id)
},
handleDelete(id) {
if (confirm('确定删除吗?')) {
this.deleteTodo(id)
}
}
}
}
</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>