文章目录
组件化编程
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../js/vue.js"></script>
</head>
<body>
<!-- 非单文件组件:一个文件中包含有n个组件
单文件组件:一个文件中包含有n个组件
-->
<div id="root">
<!-- 3.编写组件标签 -->
<school></school>
<student2></student2>
<hello2></hello2>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
// 1.创建school组件
const school = Vue.extend({
//template 中必须要有一个根元素包含
template: `
<div>
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址{{address}}</h2>
<button @click="showName">点我显示学校名</button>
</div>
`,
// el: '#root',组件定义时,一定不要写el,因为最终所有的组件都要被一个Vm管理,由vm决定服务哪个容器。
data() {
return {
schoolName: 'qut',
address: "huangdao",
}
},
methods: {
showName() {
console.log(this.schoolName);
}
}
});
//1.创建student组件
const student = Vue.extend({
template: `
<div>
<h2>学生名称:{{studentName}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>`,
data() {
//改了某一个,不会影响到同样包含到这个组件的元素,改变是只改变自身
return {
studentName: 'liuyue',
age: 18,
}
}
});
const hello = Vue.extend({
template: `
<div>
<h2>你好啊</h2>
</div>
`,
data() {
return{
name:"hhh",
}
}
})
//全局注册组件
Vue.component('hello2', hello);
// 创建vm
new Vue({
el: '#root',
//2.注册组件(局部注册)
components: {
// school:school,可以用下方的简写形式
school,
student2: student,
// hello:hello,
},
})
</script>
</html>
组件的几个注意点
v-for遍历组件时,使用自闭合或者第一种写法都可以
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>组件注意事项</title>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<h1>{{msg}}</h1>
<school-na></school-na>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
//定义组件
const school = Vue.extend({
//只要使用这个属性了,开发者工具中显示的就是这个名字,但是注册的时候教school,引用的时候就得写<school></school>
name: 'atguigu',
template: `
<div>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
`,
data() {
return {
name: '尚硅谷',
address: '北京'
}
}
});
// const school={...xxx...}不写extend,也可以正常执行,因为会补充
new Vue({
el: '#root',
data: {
msg: '欢迎学习Vue!'
},
//注册组件
components: {
'school-na': school
//SchoolNa 脚手架(wepack那一套流程)环境才能这么写
// 'al-sch'
}
})
</script>
</html>
组件的嵌套
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>组件注意事项</title>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<!-- <hello></hello>
<school></school> -->
<!-- <student></student> -->
<!-- <app></app> 或者这个地方不写,写在后面 -->
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
//定义student组件
const student = Vue.extend({
template: `
<div>
<h2>学生名字:{{StudentName}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
`,
data() {
return {
StudentName: 'zhaozhe ',
age: 18,
}
}
})
//定义school组件
const school = Vue.extend({
//只要使用这个属性了,开发者工具中显示的就是这个名字
name: 'atguigu',
template: `
<div>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<student></student>
</div>
`,
data() {
return {
name: '尚硅谷',
address: '北京'
}
},
components:{
student//局部注册(父子关系)注册给谁,就在谁的结构里写
}
});
//定义hello组件
const hello = Vue.extend({
template:`
<h1>{{msg}}</h1>
`,
data(){
return {
msg:"欢迎学习尚硅谷Vue教程!"
}
}
})
//定义app组件
const app=Vue.extend({
template:`
<div>
<hello></hello>
<school></school>
</div>
`,
components:{
school,
hello
}
});
new Vue({
el: '#root',
template:`<app></app>`,
//注册组件(局部)
components: {
// school,//简写
// // 'al-sch'
// // student
// hello,
app
}
})
</script>
</html>
Vuecomponents构造函数
在组件中,this就是当前的组件,
在new Vue(),一般在main.js中,this是vue
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>一个重要的内置关系</title>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<school></school>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
Vue.prototype.x = 99
const school = Vue.extend({
name: 'school',
template: `
<div>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showX">点我输出x</button>
</div>
`,
data() {
return {
name: '尚硅谷',//通过一系列操作,放在了Vuecomponents实例对象上
address: '北京'
}
},
methods: {
showX() {
console.log(this.x)//这个地方的this是Vuecomponents实例对象
}
},
})
// school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的
console.log(school);//查看一个school,是一个构造函数
const vm = new Vue({
el: '#root',
data: {
msg: '你好'
},
components: { school }
})
</script>
</html>
vc和vm,99%都是一样的,但是比如像el,或者data函数的写法等等,还是有些许不一样的
一个重要的内置关系:VueComponent.prototype.proto === Vue.prototype
为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue 原型上的属性、方法
单文件组件
创建一个.vue 文件,下载vetur插件,60集
Cannot find name ‘template’.Vetur 问题解决
“ Vetur 插件是一个 VS Code 上的 Vue 插件,提供了代码补全、高亮等功能。”
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>练习单文件语法</title>
</head>
<body>
<div id="root"></div>
<script type="text/javascript" src="./js/vue.js"></script>
<script type="text/javascript" src="./main.js"></script>
</body>
</html>
main.js
import App from "./App.vue"
new Vue({
el:'#root',
template:`<App></App>`,
components:{App}
})
App.vue
<template>
<div>
<School />
<Student></Student>
</div>
</template>
<script>
//引入组件
import school from "./School.vue";
import Student from "./Student.vue";
export default {
name: "App",
components: {
School,
Student,
},
};
</script>
Student.vue
<template>
<!--组件的结构 -->
<div id="Demo">
<h2>学校名称:{{ name }}</h2>
<h2>学校地址:{{ age }}</h2>
</div>
</template>
<script>
// 组件交互相关的代码;暴露
// export const school = Vue.extend({
// export default Vue.extend({
export default {
name: "Student",//与文件名保持一致
data() {
return {
name: "liuyue",
age: 16,
};
},
};
//不加前面的, 统一暴露 export{school}
</script>
<style>
/* 组件的样式 */
</style>
school.vue
<template>
<!--组件的结构 -->
<div id="Demo">
<h2>学校名称:{{ name }}</h2>
<h2>学校地址:{{ address }}</h2>
<button @click="showName">点我提示学校名</button>
</div>
</template>
<script>
// 组件交互相关的代码;暴露
// export const school = Vue.extend({
// export default Vue.extend({
export default {
name: "School",//与文件名保持一致
data() {
return {
name: "尚硅谷",
address: "北京",
};
},
methods: {
showName() {
alert(this.name);
},
},
};
//不加前面的, 统一暴露 export{school}
</script>
<style>
/* 组件的样式 */
#Demo {
background: orange;
}
</style>
脚手架
.文件目录
├── node_modules
├── public
│ ├── favicon.ico: 页签图标
│ └── index.html: 主页面
├── src
│ ├── assets: 存放静态资源
│ │ └── logo.png
│ │── component: 存放组件
│ │ └── HelloWorld.vue
│ │── App.vue: 汇总所有组件
│ └── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件
├── README.md: 应用描述文件
└── package-lock.json: 包版本控制文件
Vue 脚手架是 Vue 官方提供的标准化开发工具(开发平台)
最新的版本是 4.x
文档:Vue CLI
具体步骤
- 如果下载缓慢请配置 npm 淘宝镜像:
npm config set registry http://registry.npm.taobao.org
- 全局安装@vue/cli:
npm install -g @vue/cli
- 切换到你要创建项目的目录,然后使用命令创建项目:vue create vuecli
- 选择使用vue的版本,一开始是二版本
- 切换到新创建的目录下面,启动项目:npm run serve
- 暂停项目:Ctrl+C(暂停项目之后,创建的本地网站无法访问)
render函数
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
el: "#app",
// render: h => h(App),
/* 引入的是残缺版的vue,不能使用template
template:`<app></app>`,
components:{app}
render(createElement){
console.log('render111');
return createElement('h1','您好啊');
}
*/
render: createElement => createElement('h1', '您好啊')
})
Vue修改默认配置
npx vue inspect > output.js
- 要记得加npx,并且要在生成的对象前面加个名字
- vue.config.js 是一个可选的配置文件,如果项目的(和 package.json 同级的)根目录中存在这个文件,那么它会被 @vue/cli-service 自动加载
- 使用 vue.config.js 可以对脚手架进行个性化定制,详见配置参考 | Vue CLI
ref属性
02refs
SChool.vue
<template>
<!--组件的结构 -->
<div class="demo">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showName">点我提示学校名</button>
</div>
</template>
<script>
export default {
name: "School",//与文件名保持一致
data() {
return {
name: "尚硅谷",
address: "北京",
};
},
methods: {
showName() {
alert(this.name);
},
},
};
//不加前面的, 统一暴露 export{school}
</script>
<style>
/* 组件的样式 */
#Demo {
background: orange;
}
</style>
APP.vue
<template>
<div>
<!-- <h1 v-text="msg" id="title"></h1> -->
<h1 v-text="msg" ref="title"></h1>
<button @click="showDom">点我输出上方的Dom元素</button>
<School ref="sch"></School>
<!-- <School id="sch"></School> -->
</div>
</template>
<script>
//引入组件
import School from './components/School.vue'
export default {
name: "App",
data() {
return {
msg: '欢迎学习vue',
};
},
components: {
School,
},
methods:{
showDom(){
// console.log(document.getElementById("title"));
// console.log(this);//这个地方是APP.vue的vc
console.log(this.$refs.title);//真实dom元素
console.log(this.$refs.sch);//得到school的组件实例对象
// console.log(document.getElementById("sch"));//输出之后,会发现输出的是school的html,并且会加上一个sch的id
}
}
};
</script>
props属性
关于props中一些属性前面是否需要加冒号
数字前面加了冒号,传入过去的就是Number类型的值,不加冒号,穿入过去的就是字符串类型的值
props配置项:
-
功能:让组件接收外部传过来的数据
-
传递数据:
<Demo name="xxx"/>
或者<Demo :age="xxx"/>
加上冒号,传递的就是一个表达式 -
接收数据:
-
第一种方式(只接收):
props:['name']
-
第二种方式(限制数据类型):
props:{name:String}
-
第三种方式(限制类型、限制必要性、指定默认值):
-
props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据
可以参照一下警告(Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop’s value. Prop being mutated: “age”
)
student.vue
<template>
<!--组件的结构 -->
<div id="Demo">
<h1>{{ msg }}</h1>
<h2>学校名称:{{ name }}</h2>
<!--<h2>学校地址:{{ age*1+1 }}</h2> 强制类型转化,要不然得到的是181,而不是18+1=19-->
<!-- <h2>学校地址:{{ age + 1 }}</h2> -->
<h2>学校地址:{{ myAge+ 1 }}</h2>
<h2>学校地址:{{ sex }}</h2>
<button @click="updateAge">修改年龄</button>
</div>
</template>
<script>
// 组件交互相关的代码;暴露
// export const school = Vue.extend({
// export default Vue.extend({
export default {
name: "Student", //与文件名保持一致
data() {
// console.log(this);
return {
msg: "我是学生",
myAge:this.age,//props中的age是优先被接受的,也就是优先级更高
};
},
methods:{
updateAge(){
this.myAge++;
}
},
props:['name','age','sex']//加上之后,才会使用
//接受的同时,对数据进行限制
/*
props:{
name:String,
age:Number,
sex:String,
}
*/
//接收的同时,对类型进行限制+默认值的指定+必要性的限制
/* props: {
name: {
type: String, //name的类型是字符串
require: true, //name是必须的
},
age: {
type: Number,
default: 99, //默认值
},
sex: {
type: String,
require: true,
},
},*/
};
</script>
App.vue
<template>
<div>
<!-- <Student ref="sch"></Student> -->
<hr />
<Student name="李四" sex="女" :age="18" />
<!-- 加一个冒号,代表是表达式 -->
<hr />
</div>
</template>
<script>
//引入组件
import Student from "./components/Student.vue";
export default {
name: "App",
data() {
return {
msg: "欢迎学习vue",
};
},
components: {
Student,
},
};
</script>
mixin混入
可以把多个组件共用的配置提取成一个混入对象
局部混入:,可以重点看一下钩子,以及有相同的属性时,谁的优先级高
组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”,在发生冲突时以组件优先
参考理解: 混入就是在一个公共的实例中写入公共的数据或者方法,这样的话vue会自动注入到(全局混入会注入到每一个实例组件中)、(单页面引入的会注入到引入的组件中)。混入的对象也就是mixin中可以写入的内容包含任意组件选项(也就是说你在export default { } 里边的内容都是可以混入的)有一种类似公共方法的感觉,但是可以写公共的data里面的数据、
4.import 导入加不加{},如果要导入的文件是默认暴露,也就是export default,那么不加,而且命名随意,
如果不是默认暴露,那么需要加{},命名也不能随意
import 导入加不加{}的详细解释
src/mixin.js:
export const mixin={//分别暴露
methods: {
showName() {
alert(this.name)
}
},
mounted() {
console.log("我是混合中的钩子,我的优先级高,先输出");
},
data(){
return{
name:"备选"//如果没有,启用备选,如果有的话,就不启用了
}
}
}
src/components/School.vue
<template>
<!--组件的结构 -->
<div id="Demo">
<h2 @click="showName">学校名称:{{ name }}</h2>
<h2>学校地址:{{ address }}</h2>
</div>
</template>
<script>
import { mixin } from "../mixin";
//import {mixin,mixin2} from '../mixin'
export default {
name: "School", //与文件名保持一致
data() {
return {
// name: "qut",//试一试启用备选
address: "hd",
};
},
mixins: [mixin],
//挂载完成之后输出
mounted() {
console.log("我是School中的钩子,我的优先级低");
},
};
</script>
src/components/Student.vue:
<template>
<!--组件的结构 -->
<div id="Demo">
<h2 @click="showName">学生姓名:{{ name }}</h2>
<h2>学生性别:{{ sex }}</h2>
</div>
</template>
<script>
import { mixin } from "../mixin";
export default {
// 引入一个混合
name: "Student", //与文件名保持一致
data() {
return {
name: "zhangsan",
sex: "n",
};
},
mixins: [mixin],
};
</script>
src/App.vue:
<template>
<div>
<School/>
<hr/>
<Student/>
</div>
</template>
<script>
import Student from './components/Student.vue'
import School from './components/School.vue'
export default {
name:'App',
components: { Student,School },
}
</script>
全局引入
在全局注册混入对象的时候是Vue.mixin(mixin);不是Vue.use(mixin);
这是第二种方式,全局注入到每一个组件实例中,就不需要再每一个组件实例中写一遍了
import Vue from 'vue'
import App from './App.vue'
import {mixin} from './mixin'
Vue.config.productionTip = false
Vue.mixin(mixin)
new Vue({
el:"#app",
render: h => h(App)
})
插件
-
功能:用于增强Vue
-
本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据
-
定义插件:
plugin.install = function (Vue, options) {
// 1. 添加全局过滤器
Vue.filter(....)
// 2. 添加全局指令
Vue.directive(....)
// 3. 配置全局混入
Vue.mixin(....)
// 4. 添加实例方法
Vue.prototype.$myMethod = function () {...}
Vue.prototype.$myProperty = xxxx
}
4.使用插件:Vue.use(plugin)
plugins.js
//,默认暴露
export default {
install(Vue) {
//全局过滤器
Vue.filter('mySlice',function(value){
return value.slice(0,5)
})
//定义全局指令
Vue.directive('fbind',{
//指令与元素成功绑定时(一上来)
bind(element,binding){
element.value = binding.value
},
//指令所在元素被插入页面时
inserted(element,binding){
element.focus()
},
//指令所在的模板被重新解析时
update(element,binding){
element.value = binding.value
}
})
//定义混入
Vue.mixin({
data(){
return{
x:100,
y:100,
}
}
})
//给vue原形上添加一个方法(vm vc都可以用了)
Vue.prototype.hello=()=>console.log("best");
}
}
School.vue
<template>
<!--组件的结构 -->
<div id="Demo">
<h2 @click="showName">学校名称:{{ name | mySlice}}</h2>
<h2>学校地址:{{ address }}</h2>
<button @click="hello">点我测试hello方法</button>
</div>
</template>
<script>
export default {
name: "School", //与文件名保持一致
data() {
return {
name: "qut@青岛理工",
address: "hd",
};
},
methods:{
showName(){
console.log(this.name);
}
},
};
</script>
Student.vue
<template>
<!--组件的结构 -->
<div id="Demo">
<h2 @click="showName">学生姓名:{{ name }}</h2>
<h2>学生性别:{{ sex }}</h2>
<input type="text" v-fbind:value="name">
</div>
</template>
<script>
export default {
name: "Student", //与文件名保持一致
data() {
return {
name: "zhangsan",
sex: "n",
};
},
methods:{
showName(){
console.log(this.name);
}
},
};
//不加前面的, 统一暴露 export{school}
</script>
App.vue相同
main.js
import Vue from 'vue'
import App from './App.vue'
//引入插件
import plugins from './plugins'
Vue.config.productionTip=false
//使用插件
Vue.use(plugins)
new Vue({
el:"#app",
render:h=>h(App),
})
scoped
让样式在局部生效,防止冲突
下载使用less时的插件 69集 npm i less-loader
<template>
<!--组件的结构 -->
<div class="Demo">
<h2 >学校名称:{{ name }}</h2>
<h2>学校地址:{{ address }}</h2>
</div>
</template>
<script>
export default {
name: "School",
data() {
return {
name: "qut@青岛理工",
address: "hd",
};
},
};
</script>
<style lang="less" scoped>
.Demo{
background-color: antiquewhite;
}
</style>
TODOlist实例
安装库 72集 npm i nanoid
1.把组件引入,以及html css 分配组件
2.遍历数据,把数据遍历显示静态页面
3. 添加一个todo(获取内容,转化为todoObj,加入,APP中添加加入方法,header调用,因为没有办法直接让list获取header中输入的内容)
4.判断是否被勾选
5.勾选 取消勾选的切换,看false是否切换
6.删除元素
7.myFooter中完成/全部 /是否勾选/清除已经完成 的任务/没有任务的不显示
8.本地储存
9.子给父传事件,都可以改为自定义事件
10.addTodo改为@,myHeader也不用接收了,:todo不嫩改,那是数据,
11.item和app,item给app传数据,勾选和删除的时候传入id
安装关于.md文件的东西 77集
App.vue
<template>
<div class="root">
<div class="todo-container">
<div class="todo-wrap">
<MyHeader :addTodo="addTodo"></MyHeader>
<List :todos="todos" :checkTodo="checkTodo":deleteTodo="deleteTodo" ></List>
<MyFooter :todos="todos" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"></MyFooter>
</div>
</div>
</div>
</template>
<script>
//关键在与引入的顺序
import MyHeader from "./components/MyHeader.vue";
import MyFooter from "./components/MyFooter.vue";
import List from "./components/List.vue";
export default {
name: "App",
// 注册
components: {
MyHeader,
MyFooter,
List,
},
//List组件用数据,所有把data给List,后来又移动给APP
data() {
return {
//存一组数据,用数据或者对象
todos: [
{ id: "001", title: "吃饭", done: false },
{ id: "002", title: "睡觉", done: true },
],
};
},
methods: {
//添加一个todo
addTodo(todoObj) {
console.log("我是APP组件,我收到了数据", todoObj);
this.todos.unshift(todoObj);
},
//选择或者取消选择,看开发者工具的true/false是否改变(爷爷传孙子)
checkTodo(id) {
this.todos.forEach((todo) => {
if (todo.id === id) todo.done = !todo.done;
});
},
//删除一个todo
deleteTodo(id) {
this.todos = this.todos.filter((todo) => {
return todo.id !== id;
});
},
//全选or全不选
checkAllTodo(isdone){
this.todos.forEach(todo=> {
todo.done=isdone;
});
},
//清除所有已经完成的todo
clearAllTodo(){
this.todos=this.todos.filter(todo=>{
if(todo.done)return false;
else return true;
})
}
},
};
</script>
<style>
/* App里面一般不写scoped */
.btn {
display: inline-block;
padding: 4px 12px;
/*上下左右*/
margin-bottom: 0;
font-size: 14px;
line-height: 20px;
text-align: center;
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>
MyHeader.vue
<template>
<div class="todo-header">
<input
type="text"
placeholder="请输入你的任务,按回车键确认"
@keyup.enter="add"
/>
<!-- 怎么获取input当中的数据,
第一种 通过给add方法中传入event获取
add(event) {
console.log(event.target.value);
},
第二种,使用v-model="title",(需要得在data中添加一个title)
data(){
return(){
title:"",
}
}
-->
</div>
</template>
<script>
import { nanoid } from "nanoid";
export default {
name: "MyHeader",
props: ["addTodo"],
methods: {
add(event) {
//1.什么也不输入的时候,校验数据
if (!event.target.value.trim()) return alert("输入不能为空");
//2.将用户的输入包装成为todo对象
const todoObj = { id: nanoid(), title: event.target.value, done: false };
// console.log(todoObj); //查看todoObj是啥样的
this.addTodo(todoObj); //调用的是app中的方法
event.target.value = ""; //回车完毕之后清空
//(上面这种方法操作的是dom),也可以在data那加一个title,this.title='';
},
},
};
</script>
<style scoped>
.todo-header input {
width: 560px;
height: 28px;
font-size: 14px;
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>
MyFooter.vue
*
<template>
<div class="todo-footer" v-show="todos.length">
<!-- v-show的意思就是,当没有任务的时候,也不展示下方Footer -->
<label for="">
<!-- <input type="checkbox" :checked="todos.length===doneTotal"/> -->
<input type="checkbox" :checked="isAll" @change="checkAll" />
</label>
<span>
<span>已完成{{ doneTotal }}</span
>/全部{{ todos.length }}
</span>
<button class="btn btn-danger" @click="clearTodo">清除已完成任务</button>
</div>
</template>
<script>
export default {
name: "MyFooter",
props: ["todos", "checkAllTodo","clearAllTodo"],
methods: {
checkAll(event) {
// console.log(event.target.checked);//获取是否被选中的布尔值
/*
需要把对todo操作写在APP中
this.todos.forEach(todo=> {
todo.done=event.target.checked;
});*/
this.checkAllTodo(event.target.checked);
},
clearTodo(){
this.clearAllTodo();
}
},
computed: {
doneTotal() {
/*let i = 0;
this.todos.forEach((todo) => {
if (todo.done) i++;
});
return i;*/
/* reduce专门用来做条件统计的,
第一个参数传一个函数,第二个参数可以传入一个统计值
函数里第一个参数表示上一次的返回值,第二个表示当前的todo项
todos有几个值,函数就会被调用几次
第一次调用pre是0,第一次的值就是pre+1=1
第二次调用pre是1,第二次的值就是pre+1=2
最后一次调用reduce函数的返回值就是整个函数 的返回值
*/
const x = this.todos.reduce((pre, current) => {
//第二次的值是第一次的返回值
return pre + (current.done ? 1 : 0);
}, 0);
return x;
},
isAll() {
//排除0=0的时候的勾选
return this.doneTotal === this.todos.length && this.todos.length > 0;
},
/*或者用另一种方法
把这两个合并,但是这样的话,需要修改isAll函数
<input type="checkbox" v-model="isAll"/>
isAll: {
get() {
return this.doneTotal === this.todos.length && this.todos.length > 0;
},
set(value) {
// console.log(value);//这个地方的value就是true/false
this.checkAllTodo(value);
},
},
*/
},
};
</script>
<style scoped>
.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>
List.vue
<template>
<ul class="todo-main">
<item v-for=" todoObj in todos" :todo="todoObj" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></item> <!-- 遍历 用v-for 为了让大家明白,todoObj是个todo对象-->
</ul>
</template>
<script>
import item from "./item.vue";
export default {
name: "List",
components: {
item,
},
props:["todos","checkTodo","deleteTodo"],
};
</script>
<style scoped>
.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>
item.vue
<template>
<li>
<label for="">
<input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/> <!-- 要加上冒号,后面的内容才会变成表达式 -->
<!-- 如果v-model绑定一个布尔值,并且input类型是勾选,那么绑定的布尔值就会决定是勾选还是不勾选
<input type="checkbox" v-model="todo.done"/>
就不需要那些方法了
不建议
-->
<span>{{ todo.title }}</span>
</label>
<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
</li>
</template>
<script>
export default {
name: "item",
//声明接收todo对象
props: ["todo","checkTodo","deleteTodo"],
methods:{
//勾选 取消勾选
handleCheck(id){
//通知APP组件,将对应的done取反
this.checkTodo(id);
},
//删除
handleDelete(id){
// console.log(id);
if(confirm("确认删除吗?")){
this.deleteTodo(id);
}
}
}
};
</script>
<style scoped>
li {
list-style: none;
height: 36px;
line-height: 36px;
padding: 0 5px;
border-bottom: 1px solid #ddd;
}
li label {
// float: left;加上会有一点点bug
cursor: pointer;
}
li label li input {
vertical-align: center;
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: aqua;
}
li:hover button{
display: block;
}
</style>
TODOLIST注意事项
1.起文件名的时候一定要合法
引入的时候,item是 被引入到list当中 的
把整个div带走
关于label标签的使用和说明
cursor:pointer将鼠标变成一只手
box-shadow: inset 内阴影的一些说明
Vue属性前加:和不加:
怎么获取input当中的数据,
第一种 通过给add方法中传入event获取
add(event) {
console.log(event.target.value);
},
第二种,使用v-model=“title”,(需要得在data中添加一个title
data(){
return(){
title:“”,
}
}
有click事件,也有change事件
父给子传,用props方便,子给父传 props那个方法/自定义事件
需要数据的地方,订阅消息(组件挂载完毕),有数据的地方,发布消息
网站本地的存储
localStorage
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>localStorage</title>
</head>
<body>
<h2>localStorage</h2>
<button onclick="saveDate()">点我保存数据</button><br/>
<button onclick="readDate()">点我读数据</button><br/>
<button onclick="deleteDate()">点我删除数据</button><br/>
<button onclick="deleteAllDate()">点我清空数据</button><br/>
<script>
let person = {name:"JOJO",age:20}
function saveDate(){
localStorage.setItem('我是key-->msg','我是value-->localStorage')
localStorage.setItem('person',JSON.stringify(person))
}
function readDate(){
console.log(localStorage.getItem('我是key-->msg'))
const person = localStorage.getItem('person')
console.log(JSON.parse(person))
}
function deleteDate(){
localStorage.removeItem('我是key-->msg')
localStorage.removeItem('person')
}
function deleteAllDate(){
localStorage.clear()
}
</script>
</body>
</html>
sessionStorage
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>sessionStorage</title>
</head>
<body>
<h2>sessionStorage</h2>
<button onclick="saveDate()">点我保存数据</button><br/>
<button onclick="readDate()">点我读数据</button><br/>
<button onclick="deleteDate()">点我删除数据</button><br/>
<button onclick="deleteAllDate()">点我清空数据</button><br/>
<script>
let person = {name:"JOJO",age:20}
function saveDate(){
sessionStorage.setItem('msg','sessionStorage')
sessionStorage.setItem('person',JSON.stringify(person))
}
function readDate(){
console.log(sessionStorage.getItem('msg'))
const person = sessionStorage.getItem('person')
console.log(JSON.parse(person))
}
function deleteDate(){
sessionStorage.removeItem('msg')
sessionStorage.removeItem('person')
}
function deleteAllDate(){
sessionStorage.clear()
}
</script>
</body>
</html>
- localStorage生命周期是永久,这意味着除非用户显示在浏览器提供的UI上清除localStorage信息,否则这些信息将永远存在。
- sessionStorage生命周期为当前窗口或标签页,一旦窗口或标签页被永久关闭了,那么所有通过sessionStorage存储的数据也就被清空了。
- 两者都不允许存对象,要存字符串,转化为JSON
自定义事件&props子父传值
APP.vue
<template>
<div>
<h1>{{ msg }} 学生姓名shi:{{ StudentName }}</h1>
<!-- 通过父组件给子组件传递函数类型的props 实现子给父传递数据,需要父组件先给子组件一个参数 -->
<School :getSchoolName="getSchoolName"></School>
<hr />
<!-- v-on在谁身上,就是给谁绑定事件
给Student的实例对象vc绑定了一个atguigu事件
v-on:xxx等同于@xxx-->
<Student v-on:atguigu="getStudentName" @demo="m1" />
<!-- 通过父组件给子组件绑定一个自定义事件实现子给父传递数据(第二种写法,使用ref) -->
<!-- <Student ref="student" /> -->
</div>
</template>
<script>
//引入组件
import Student from "./components/Student.vue";
import School from "./components/School.vue";
export default {
name: "App",
data() {
return {
msg: "欢迎学习vue",
StudentName: "",
};
},
components: {
Student,
School,
},
methods: {
getSchoolName(name) {
console.log("已收到学校的名称:" + name);
},
getStudentName(name, x, y, z) {
console.log(this); //app实例对象
console.log("已收到学生的名称:" + name, x, y, z);
this.StudentName = name;
},
m1() {
console.log("demo事件被触发了");
},
},
// mounted() {
// //更灵活一点,3s之后点击才有反应
// setTimeout(() => {
// //this.$refs.student 是student 的组件实例对象
// this.$refs.student.$on("atguigu", this.getStudentName); //绑定自定义事件
// // this.$refs.student.$once("atguigu", this.getStudentName);//(一次)绑定自定义事件
// }, 1000);
// console.log(this); //this 不是app实例对象,是学生那个实例对象(谁触发的就是谁)
// },
};
</script>
Student.vue
<template>
<!--组件的结构 -->
<div class="student">
<h2>学生姓名:{{ name }}</h2>
<h2>学生性别:{{ sex }}</h2>
<h2>当前的值是:{{n}}</h2>
<button @click="add">n++</button>
<button @click="sendStudentName">把学生名给APP</button>
<button @click="unbind">解绑atguigu事件</button>
<button @click="death">销毁当前组件实例(vc)</button>
</div>
</template>
<script>
export default {
// 引入一个混合
name: "Student", //与文件名保持一致
data() {
return {
name: "zhangsan",
sex: "n",
n:1,
};
},
methods:{
add(){
this.n++;
},
sendStudentName(){
//this是找到vc,触发Student组件实例身上的atguigu事件
this.$emit('atguigu', this.name,666,88,99);
// console.log(this.name);
// this.$emit('demo', this.name,666,88,99);//可以同时绑定多个
},
unbind(){
this.$off("atguigu");//解绑一个自定义事件
// this.$off(['atguigu',"demo"])//用数组解绑多个
// this.$off()//所有的绑定事件都解绑
},
death(){
//销毁了当前的student组件实例,销毁后,所有student实例的自定义事件都失效
this.$destroy()
}
}
};
//不加前面的, 统一暴露 export{school}
</script>
School.vue
<template>
<!--组件的结构 -->
<div class="Demo">
<h2 >学校名称:{{ name }}</h2>
<h2>学校地址:{{ address }}</h2>
<button @click="sendSchoolName">把学校名给APP</button>
</div>
</template>
<script>
export default {
name: "School",
props:['getSchoolName'],
data() {
return {
name: "qut@青岛理工",
address: "hd",
};
},
methods:{
sendSchoolName(){
// this.getSchoolName(this.name);
this.getSchoolName("fd");
}
}
};
</script>
<style lang="less" scoped>
.Demo{
background-color: antiquewhite;
}
</style>
事件总线
School.vue
<template>
<div class="school">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
</template>
<script>
export default {
name:'School',
data() {
return {
name:'尚硅谷',
address:'北京',
}
},
mounted() {
this.$bus.$on('hello',(data)=>{
console.log("我是School组件,收到了数据",data);
})
},
}
</script>
<style scoped>
.school{
background-color: skyblue;
padding: 5px;
}
</style>
Student.vue
<template>
<div class="student">
<h2>学生姓名:{{ name }}</h2>
<h2>学生性别:{{ sex }}</h2>
<button @click="sendStudentName">把学生名给School组件</button>
</div>
</template>
<script>
export default {
name: "Student",
data() {
return {
name: "张三",
sex: "男",
};
},
methods: {
sendStudentName() {
this.$bus.$emit("hello", 666);
},
},
beforeDestroy(){
this.$bus.$off("hello");
},
};
</script>
<style scoped>
.student {
background-color: pink;
padding: 5px;
margin-top: 30px;
}
</style>
App.vue
<template>
<div class="app">
<School/>
<Student/>
</div>
</template>
<script>
import Student from './components/Student'
import School from './components/School'
export default {
name:'App',
components:{School,Student},
data(){
return{
msg:"你好啊!",
// 所有的组件都能看的到,也就是所有的vc都能看到,所以我在VueComponent上放
}
}
}
</script>
<style scoped>
.app{
background-color: gray;
padding: 5px;
}
</style>
main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
/*
这样其实也可以
const demo=Vue.extend({});//demo其实是VueComponent
const d=new demo()
Vue.prototype.x=d
*/
const vm = new Vue({
el: '#app',
render: h => h(App),
//安装全局事件总线
beforeCreate() {
Vue.prototype.$bus = this//this就是当前这个vue实例
},
})
消息订阅与发布
/安装插件npm i pubsub-js
只需要更改以下两个
需要数据的地方,订阅消息(组件挂载完毕),有数据的地方,发布消息
Student.vue
<template>
<div class="student">
<h2>学生姓名:{{ name }}</h2>
<h2>学生性别:{{ sex }}</h2>
<!-- Student 组件提供消息, School需要消息 -->
<button @click="sendStudentName">把学生名给School组件</button>
</div>
</template>
<script>
import pubsub from "pubsub-js";
export default {
name: "Student",
data() {
return {
name: "张三",
sex: "男",
};
},
methods: {
sendStudentName() {
// this.$bus.$emit("hello", 666);
//消息名字,消息
pubsub.publish("hello",666);
},
},
};
</script>
<style scoped>
.student {
background-color: pink;
padding: 5px;
margin-top: 30px;
}
</style>
School.vue
<template>
<div class="school">
<h2>学校名称:{{ name }}</h2>
<h2>学校地址:{{ address }}</h2>
</div>
</template>
<script>
import pubsub from "pubsub-js";
export default {
name: "School",
data() {
return {
name: "尚硅谷",
address: "北京",
};
},
mounted() {
// this.$bus.$on('hello',(data)=>{
// console.log("我是School组件,收到了数据",data);
// })
this.pubId = pubsub.subscribe("hello", function (msgName, data) {
//这个地方是undefined,因为是第三方库,所以可以使用箭头函数,或者在method里面写一个函数。这个地方去调用那个函数
console.log(this);
console.log("有人发布了hello消息,并且发送成功", msgName, data);
});
},
beforeDestory() {
pubsub.unsubscribe(this.pubId);
},
};
</script>
<style scoped>
.school {
background-color: skyblue;
padding: 5px;
}
</style>
$nextTick
$nextTick(回调函数)可以将回调延迟到下次 DOM 更新循环之后执行
二次编辑之后的todo
main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
/*
这样其实也可以
const demo=Vue.extend({});//demo其实是VueComponent
const d=new demo()
Vue.prototype.x=d
*/
const vm = new Vue({
el: '#app',
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus=this
},
})
App
<template>
<div class="root">
<div class="todo-container">
<div class="todo-wrap">
<MyHeader @addTodo="addTodo"></MyHeader>
<List
:todos="todos"
:checkTodo="checkTodo"
:deleteTodo="deleteTodo"
></List>
<MyFooter
:todos="todos"
@checkAllTodo="checkAllTodo"
@clearAllTodo="clearAllTodo"
></MyFooter>
</div>
</div>
</div>
</template>
<script>
//关键在与引入的顺序
import pubsub from "pubsub-js";
import MyHeader from "./components/MyHeader.vue";
import MyFooter from "./components/MyFooter.vue";
import List from "./components/List.vue";
export default {
name: "App",
// 注册
components: {
MyHeader,
MyFooter,
List,
},
//List组件用数据,所有把data给List,后来又移动给APP
data() {
return {
/*存一组数据,用数据或者对象
todos:[ {id:'001',title:"吃饭饭",done:false} ] */
// 我们通过set把数据已经存在了网页上,现在在这个地方,我们把数据读起来
todos: JSON.parse(localStorage.getItem("todos")) || [],
};
},
methods: {
//添加一个todo
addTodo(todoObj) {
// console.log("我是APP组件,我收到了数据", todoObj);
this.todos.unshift(todoObj);
},
//选择或者取消选择,看开发者工具的true/false是否改变(爷爷传孙子)
checkTodo(id) {
this.todos.forEach((todo) => {
if (todo.id === id) todo.done = !todo.done;
});
},
//更新一个todo
updateTodo(id,title) {
this.todos.forEach((todo) => {
if (todo.id === id) todo.title = title;
});
},
//删除一个todo,可以用下划线占位,因为不使用这个参数
deleteTodo(id) {
this.todos = this.todos.filter((todo) => {
return todo.id !== id;
});
},
//全选or全不选
checkAllTodo(isdone) {
this.todos.forEach((todo) => {
todo.done = isdone;
});
},
//清除所有已经完成的todo
clearAllTodo() {
this.todos = this.todos.filter((todo) => {
if (todo.done) return false;
else return true;
});
},
},
watch: {
todos: {
deep: true, //开启深度监视,网页中的true和false才能曹村
handler(value) {
localStorage.setItem("todos", JSON.stringify(value));
},
},
},
mounted() {
this.$bus.$on("checkTodo", this.checkTodo);
this.$bus.$on("updateTodo", this.updateTodo);
this.$bus.$on("deleteTodo", this.deleteTodo);
// this.pubId=pubsub.subscribe('deleteTodo',this.deleteTodo)
},
beforeDestory() {
this.$bus.$off("checkTodo");
this.$bus.$off("deleteTodo");
this.$bus.$off("updateTodo");
// pubsub.unsubscribe(this.pubId)
},
};
</script>
<style>
/* App里面一般不写scoped */
.btn {
display: inline-block;
padding: 4px 12px;
/*上下左右*/
margin-bottom: 0;
font-size: 14px;
line-height: 20px;
text-align: center;
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-edit {
color: #fff;
background-color: skyblue;
border: 1px solid skyblue;
margin-right: 5px;
}
.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>
MyHeader
<template>
<div class="todo-header">
<input
type="text"
placeholder="请输入你的任务,按回车键确认"
@keyup.enter="add"
/>
<!-- 怎么获取input当中的数据,
第一种 通过给add方法中传入event获取
add(event) {
console.log(event.target.value);
},
第二种,使用v-model="title",(需要得在data中添加一个title)
data(){
return(){
title:"",
}
}
-->
</div>
</template>
<script>
import { nanoid } from "nanoid";
export default {
name: "MyHeader",
methods: {
add(event) {
//1.什么也不输入的时候,校验数据
if (!event.target.value.trim()) return alert("输入不能为空");
//2.将用户的输入包装成为todo对象
const todoObj = { id: nanoid(), title: event.target.value, done: false };
// console.log(todoObj); //查看todoObj是啥样的
// this.addTodo(todoObj); //调用的是app中的方法
this.$emit('addTodo', todoObj);
event.target.value = ""; //回车完毕之后清空
//(上面这种方法操作的是dom),也可以在data那加一个title,this.title='';
},
},
};
</script>
<style scoped>
.todo-header input {
width: 560px;
height: 28px;
font-size: 14px;
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>
MyFooter
*
<template>
<div class="todo-footer" v-show="todos.length">
<!-- v-show的意思就是,当没有任务的时候,也不展示下方Footer -->
<label for="">
<!-- <input type="checkbox" :checked="todos.length===doneTotal"/> -->
<input type="checkbox" :checked="isAll" @change="checkAll" />
</label>
<span>
<span>已完成{{ doneTotal }}</span
>/全部{{ todos.length }}
</span>
<button class="btn btn-danger" @click="clearTodo">清除已完成任务</button>
</div>
</template>
<script>
export default {
name: "MyFooter",
props: ["todos"],
methods: {
//主动勾选
checkAll(event) {
// console.log(event.target.checked);//获取是否被选中的布尔值
/*
需要把对todo操作写在APP中
this.todos.forEach(todo=> {
todo.done=event.target.checked;
});*/
// this.checkAllTodo(event.target.checked);
this.$emit('checkAllTodo', event.target.checked);
},
clearTodo(){
// this.clearAllTodo();
this.$emit('clearAllTodo');
}
},
computed: {
doneTotal() {
/*let i = 0;
this.todos.forEach((todo) => {
if (todo.done) i++;
});
return i;*/
/* reduce专门用来做条件统计的,
第一个参数传一个函数,第二个参数可以传入一个统计值
函数里第一个参数表示上一次的返回值,第二个表示当前的todo项
todos有几个值,函数就会被调用几次
第一次调用pre是0,第一次的值就是pre+1=1
第二次调用pre是1,第二次的值就是pre+1=2
最后一次调用reduce函数的返回值就是整个函数 的返回值
*/
const x = this.todos.reduce((pre, current) => {
//第二次的值是第一次的返回值
return pre + (current.done ? 1 : 0);
}, 0);//pre的初始值
return x;
},
//
isAll() {
//排除0=0的时候的勾选
return this.doneTotal === this.todos.length && this.todos.length > 0;
},
/*或者用另一种方法
把这两个合并,但是这样的话,需要修改isAll函数
<input type="checkbox" v-model="isAll"/>
isAll: {
get() {
return this.doneTotal === this.todos.length && this.todos.length > 0;
},
set(value) {
// console.log(value);//这个地方的value就是true/false
this.checkAllTodo(value);
},
},
*/
},
};
</script>
<style scoped>
.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>
List
<template>
<ul class="todo-main">
<item v-for="todoObj in todos" :todo="todoObj"></item>
<!-- 遍历 用v-for 为了让大家明白,todoObj是个todo对象-->
</ul>
</template>
<script>
import item from "./item.vue";
export default {
name: "List",
components: {
item,
},
props: ["todos"],
};
</script>
<style scoped>
.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>
item
<template>
<transition name="todo" appear>
<li>
<label for="">
<input
type="checkbox"
:checked="todo.done"
@change="handleCheck(todo.id)"
/>
<!-- 要加上冒号,后面的内容才会变成表达式 -->
<!-- 如果v-model绑定一个布尔值,并且input类型是勾选,那么绑定的布尔值就会决定是勾选还是不勾选
<input type="checkbox" v-model="todo.done"/>
就不需要那些方法了
不建议
-->
<span v-show="!todo.isEdit">{{ todo.title }}</span>
<input
v-show="todo.isEdit"
type="text"
:value="todo.title"
@blur="handleBlur(todo, $event)"
ref="inputTitle"
/>
</label>
<button class="btn btn-danger" @click="handleDelete(todo.id)">
删除
</button>
<button
v-show="!todo.isEdit"
class="btn btn-edit"
@click="handleEdit(todo)"
>
编辑
</button>
</li>
</transition>
</template>
<script>
import pubsub from "pubsub-js";
export default {
name: "item",
//声明接收todo对象
props: ["todo", "checkTodo", "deleteTodo"],
methods: {
//勾选 取消勾选
handleCheck(id) {
//通知APP组件,将对应的done取反
// this.checkTodo(id);
this.$bus.$emit("checkTodo", id);
},
//删除
handleDelete(id) {
// console.log(id);
if (confirm("确认删除吗?")) {
// this.deleteTodo(id);
this.$bus.$emit("deleteTodo", id);
// pubsub.publish("deleteToDo", id);
}
},
//编辑
handleEdit(todo) {
// todo.isEdit = true;不可以
//把文字变成输入框
if (todo.hasOwnProperty("isEdit")) {
todo.isEdit = true;
} else {
this.$set(todo, "isEdit", true);
}
// this.$refs.inputTitle.focus();不行,因为执行这句话的时候可以还没有变成输入框
this.$nextTick(function () {
this.$refs.inputTitle.focus();
}); //指定的回调函数会在dom节点结束之后再执行
// console.log(todo);
},
//失去焦点回调(真正执行修改逻辑)
handleBlur(todo, e) {
todo.isEdit = false;
if (!e.target.value.trim()) return; //如果是空白,毙掉
this.$bus.$emit("updateTodo", todo.id, e.target.value);
// console.log(todo.id, e.target.value);
},
},
};
</script>
<style scoped>
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: center;
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: aqua;
}
li:hover button {
display: block;
}
.todo-enter-active {
animation: atguigu 1s;
}
.todo-leave-active {
animation: atguigu 1s reverse; /*翻转 */
}
@keyframes atguigu {
from {
transform: translateX(100%); /*从左边来 */
}
to {
transform: translateX(0px);
}
}
</style>
动画过渡
需要带有v-show v-if才能使用动画
安装第三方库npm install animate.css
94集
引入import 'animate.css'
name="animate__animated animate__bounce"
animate.style网站网址
在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名
APP
<template>
<div class="root">
<Test />
<Test2 />
<Test3 />
</div>
</template>
<script>
import Test from "./components/Test.vue";
import Test2 from "./components/Test2.vue";
import Test3 from "./components/Test3.vue";
export default {
name: "App",
// 注册
components: {
Test,
Test2,
Test3,
},
};
</script>
Test
<template>
<div class="root">
<button @click="isShow = !isShow">显示/隐藏</button>
<transition name="liuyue" appear>
<!--让谁发生动画效果,就把他包裹起来
没起名字要用v -->
<h1 v-show="isShow">你好啊!</h1>
</transition>
</div>
</template>
<script>
export default {
name: "Test",
data() {
return {
isShow: true,
};
},
};
</script>
<style>
h1 {
background-color: orange;
}
.liuyue-enter-active {
animation: atguigu 1s;
}
.liuyue-leave-active {
animation: atguigu 1s reverse; /*翻转 */
}
@keyframes atguigu {
from {
transform: translateX(-100%); /*从左边来 */
}
to {
transform: translateX(0px);
}
}
</style>
Test2
<template>
<div class="root">
<button @click="isShow = !isShow">显示/隐藏</button>
<!--单个元素
让谁发生动画效果,就把他包裹起来,,没起名字要用v -->
<!-- <transition name="liuyue" appear>
<h1 v-show="isShow">你好啊!</h1>
</transition>-->
<!-- 多个元素 -->
<transition-group name="liuyue" appear>
<h1 v-show="isShow" key="1">你好啊!</h1>
<h1 v-show="! isShow" key="2">尚硅谷!</h1>
</transition-group>
</div>
</template>
<script>
export default {
name: "Test",
data() {
return {
isShow: true,
};
},
};
</script>
<style>
h1 {
background-color: orange;
width: 160px;
/* transition: 0.5s linear; 可以不写在这个地方*/
}
/*进入的起点 离开的终点*/
.liuyue-enter,
.liuyue-leave-to {
transform: translateX(-100%);
}
/*进入的终点 离开的起点*/
.liuyue-enter-to,
.liuyue-leave {
transform: translateX(0);
}
.liuyue-enter-active,
.liuyue-leave-active {
transition: 0.5s linear;
}
</style>
Test3
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<transition-group
appear
name="animate__animated animate__bounce"
enter-active-class="animate__backInUp"
leave-active-class="animate__backOutUp"
>
<h1 v-show="isShow" key="1">你好啊!</h1>
<h1 v-show="!isShow" key="2">尚硅谷!</h1>
</transition-group>
</div>
</template>
<script>
import "animate.css";
export default {
name: "Test",
data() {
return {
isShow: true,
};
},
};
</script>
<style>
h1 {
background-color: orange;
width: 160px;
}
</style>