├── 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:包版本控制文件
1. vue.js与vue.runtime.xxx.js的区别:
1. vue.js是完整版的Vue,包含:核心功能 + 模板解析器。
2. vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。
2. 因为vue.runtime.xxx.js没有模板解析器,所以不能使用template这个配置项,需要使用render函数接收到的createElement函数去指定具体内容。
vue.config.js配置文件
1. 使用vue inspect > output.js可以查看到Vue脚手架的默认配置。
2. 使用vue.config.js可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh
1. 被用来给元素或子组件注册引用信息(id的替代者) 2. 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc) 3. 使用方式: 1. 打标识:<h1 ref="xxx">.....</h1> 或 <School ref="xxx"></School> 2. 获取:this.$refs.xxx

1 <template> 2 <div> 3 <h1 v-text="msg" ref="title"></h1> 4 <button ref="btn" @click="showDOM">点我输出上方DOM元素</button> 5 <school ref="sch"></school> 6 </div> 7 </template> 8 9 10 <script> 11 import School from "./components/School"; 12 13 export default { 14 name:'App', 15 components:{ 16 School, 17 }, 18 data(){ 19 return { 20 msg:"你好啊" 21 } 22 }, 23 methods:{ 24 showDOM(){ 25 console.log(this.$refs.title) 26 console.log(this.$refs.btn) 27 console.log(this.$refs.sch) 28 } 29 } 30 } 31 </script>

1 <template> 2 <div class="school"> 3 <h2>学校名称:{{name}}</h2> 4 <h2>学校地址:{{address}}</h2> 5 </div> 6 </template> 7 8 <script> 9 export default { 10 name:'School', 11 data(){ 12 return { 13 name:"京东", 14 address:"北京" 15 } 16 } 17 } 18 </script> 19 20 <style> 21 .school{ 22 background-color: gray; 23 } 24 </style>
props配置项
1. 功能:让组件接收外部传过来的数据 2. 传递数据:<Demo name="xxx"/> 3. 接收数据: 1. 第一种方式(只接收):props:['name'] 2. 第二种方式(限制类型):props:{name:String} 3. 第三种方式(限制类型、限制必要性、指定默认值):
props:{
name:{
type:String, //类型
required:true, //必要性
default:'老王' //默认值
}
}
备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。

1 <template> 2 <div> 3 <Student name="李四" sex="女" :age="18"></Student> 4 </div> 5 </template> 6 7 <script> 8 import Student from "./components/Student"; 9 10 export default { 11 name:'App', 12 components:{ 13 Student, 14 } 15 } 16 </script>

<template> <div class="school"> <h1>{{msg}}</h1> <h2>学生姓名:{{name}}</h2> <h2>学生性别:{{sex}}</h2> <h2>学生年龄:{{myAge}}</h2> <button @click="updateAge">尝试修改收到的年龄</button> </div> </template> <script> export default { name:'Student', data(){ return { msg:"你好啊", myAge:this.age } }, methods:{ updateAge(){ return this.myAge++ } }, //简单声明接收 // props:['name','age','sex'] //接收的同时对数据进行类型限制 // props:{ // name:String, // age:Number, // sex:String // } //接收的同时对数据:进行类型限制+默认值的指定+必要性的限制 props:{ name:{ type:String, required:true }, age:{ type:Number, default:20 }, sex:{ type:String, required:true } } } </script> <style> .school{ background-color: gray; } </style>
1. 功能:可以把多个组件共用的配置提取成一个混入对象
2. 使用方式:
第一步定义混合:
{
data(){....},
methods:{....}
....
}
第二步使用混入:
全局混入:Vue.mixin(xxx)
局部混入:mixins:['xxx']

1 import Vue from 'vue' 2 import App from './App.vue' 3 4 Vue.config.productionTip = false 5 6 //全局 7 import {mixin,mixin2} from "@/mixin"; 8 Vue.mixin(mixin) 9 Vue.mixin(mixin2) 10 11 new Vue({ 12 el:"#app", 13 render: h => h(App), 14 })

1 export const mixin = { 2 methods:{ 3 showName(){ 4 alert(this.name) 5 } 6 }, 7 mounted() { 8 console.log("你好") 9 } 10 } 11 12 export const mixin2 = { 13 data(){ 14 return { 15 x:100, 16 y:200 17 } 18 } 19 }

1 <template> 2 <div> 3 <Student></Student> 4 <hr> 5 <School></School> 6 </div> 7 </template> 8 9 <script> 10 import Student from "./components/Student"; 11 import School from "./components/School"; 12 13 export default { 14 name:'App', 15 components:{ 16 Student, 17 School 18 } 19 } 20 </script>

1 <template> 2 <div class="school"> 3 <h2 @click="showName">学生姓名:{{name}}</h2> 4 <h2>学生性别:{{sex}}</h2> 5 </div> 6 </template> 7 8 <script> 9 // import {mixin,mixin2} from '../mixin' 10 11 export default { 12 name:'Student', 13 data(){ 14 return { 15 name:"张三", 16 sex:"男" 17 } 18 }, 19 // mixins:[mixin,mixin2] 20 } 21 </script>

1 <template> 2 <div class="school"> 3 <h2 @click="showName">学校名称:{{name}}</h2> 4 <h2>学校地址:{{address}}</h2> 5 </div> 6 </template> 7 8 <script> 9 // import {mixin,mixin2} from '../mixin' 10 11 export default { 12 name:'School', 13 data(){ 14 return { 15 name:"京东", 16 address:"北京" 17 } 18 }, 19 // mixins:[mixin,mixin2] 20 } 21 </script>
4、插件
1. 功能:用于增强Vue
2. 本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。
3. 定义插件:
对象.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()

1 import Vue from 'vue' 2 import App from './App.vue' 3 4 //插件:增强Vue 5 import plugins from "./plugins" 6 7 Vue.config.productionTip = false 8 9 Vue.use(plugins,1,2,3) 10 11 new Vue({ 12 el:"#app", 13 render: h => h(App), 14 })

1 export default { 2 install(Vue,x,y,z){ 3 console.log(x,y,z) 4 //全局过滤器 5 Vue.filter('mySlice',function(value){ 6 return value.slice(0,4) 7 }) 8 9 //定义全局指令 10 Vue.directive('fbind',{ 11 //指令与元素成功绑定时(一上来) 12 bind(element,binding){ 13 element.value = binding.value 14 }, 15 //指令所在元素被插入页面时 16 inserted(element){ 17 element.focus() 18 }, 19 //指令所在的模板被重新解析时 20 update(element,binding){ 21 element.value = binding.value 22 } 23 }) 24 25 //定义混入 26 Vue.mixin({ 27 data() { 28 return { 29 x:100, 30 y:200 31 } 32 }, 33 }) 34 35 //给Vue原型上添加一个方法(vm和vc就都能用了) 36 Vue.prototype.hello = ()=>{alert('你好啊')} 37 } 38 }

1 <template> 2 <div> 3 <Student></Student> 4 <hr> 5 <School></School> 6 </div> 7 </template> 8 9 <script> 10 import Student from "./components/Student"; 11 import School from "./components/School"; 12 13 export default { 14 name:'App', 15 components:{ 16 Student, 17 School 18 } 19 } 20 </script>

1 <template> 2 <div class="school"> 3 <h2>学生姓名:{{name}}</h2> 4 <h2>学生性别:{{sex}}</h2> 5 <input type="text" v-fbind:value="name"> 6 </div> 7 </template> 8 9 <script> 10 export default { 11 name:'Student', 12 data(){ 13 return { 14 name:"张三", 15 sex:"男" 16 } 17 }, 18 } 19 </script>

1 <template> 2 <div class="school"> 3 <h2>学校名称:{{name | mySlice}}</h2> 4 <h2>学校地址:{{address}}</h2> 5 <button @click="test">点我测试一下hello方法</button> 6 </div> 7 </template> 8 9 <script> 10 export default { 11 name:'School', 12 data(){ 13 return { 14 name:"京东avcsd", 15 address:"北京" 16 } 17 }, 18 methods:{ 19 test(){ 20 alert(this.name) 21 } 22 } 23 } 24 </script>
5、scoped样式
1. 作用:让样式在局部生效,防止冲突。 2. 写法:<style scoped>

1 <template> 2 <div> 3 <h1 class="title">你好啊</h1> 4 <hr> 5 <Student></Student> 6 <hr> 7 <School></School> 8 </div> 9 </template> 10 11 <script> 12 import Student from "./components/Student"; 13 import School from "./components/School"; 14 15 export default { 16 name:'App', 17 components:{ 18 Student, 19 School 20 } 21 } 22 </script> 23 24 <style scoped> 25 .title{ 26 color: red; 27 } 28 </style>

1 <template> 2 <div class="demo"> 3 <h2 class="title">学生姓名:{{name}}</h2> 4 <h2 class="at">学生性别:{{sex}}</h2> 5 </div> 6 </template> 7 8 <script> 9 export default { 10 name:'Student', 11 data(){ 12 return { 13 name:"张三", 14 sex:"男" 15 } 16 }, 17 } 18 </script> 19 20 <style lang="less" scoped> 21 .demo{ 22 background-color: pink; 23 .at{ 24 font-size: 40px; 25 } 26 } 27 </style>

1 <template> 2 <div class="demo"> 3 <h2 class="title">学校名称:{{name}}</h2> 4 <h2>学校地址:{{address}}</h2> 5 </div> 6 </template> 7 8 <script> 9 export default { 10 name:'School', 11 data(){ 12 return { 13 name:"京东avcsd", 14 address:"北京" 15 } 16 }, 17 methods:{ 18 19 } 20 } 21 </script> 22 23 <style scoped> 24 .demo{ 25 background-color: skyblue; 26 } 27 </style>
1. 组件化编码流程: (1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。 (2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用: 1).一个组件在用:放在组件自身即可。 2). 一些组件在用:放在他们共同的父组件上(状态提升)。 (3).实现交互:从绑定事件开始。 2. props适用于: (1).父组件 ==> 子组件 通信 (2).子组件 ==> 父组件 通信(要求父先给子一个函数) 3. 使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的! 4. props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。

1 <template> 2 <div id="root"> 3 <div class="todo-container"> 4 <div class="todo-wrap"> 5 <MyHeader :addTodo="addTodo"></MyHeader> 6 <MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></MyList> 7 <MyFooter :todos="todos" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"></MyFooter> 8 </div> 9 </div> 10 </div> 11 </template> 12 13 <script> 14 import MyHeader from "./components/MyHeader" 15 import MyList from "./components/MyList" 16 import MyFooter from "./components/MyFooter" 17 18 export default { 19 name: 'App', 20 components: { 21 MyHeader, 22 MyList, 23 MyFooter 24 }, 25 data(){ 26 return { 27 todos:[ 28 {id:"001",title:"唱",done:true}, 29 {id:"002",title:"跳",done:true}, 30 {id:"003",title:"rap",done:false}, 31 {id:"004",title:"打篮球",done:true} 32 ] 33 } 34 }, 35 methods:{ 36 // 添加一个todo 37 addTodo(todoObj){ 38 this.todos.unshift(todoObj) 39 }, 40 // 勾选or取消勾选一个todo 41 checkTodo(id){ 42 this.todos.forEach((todo)=>{ 43 if (todo.id === id) todo.done = !todo.done 44 }) 45 }, 46 //删除一个todo 47 deleteTodo(id){ 48 this.todos = this.todos.filter((todo) => todo.id !== id) 49 }, 50 //全选or取消全选 51 checkAllTodo(done){ 52 this.todos.forEach((todo)=>{ 53 todo.done = done 54 }) 55 }, 56 //清除所有已经完成的todo 57 clearAllTodo(){ 58 this.todos = this.todos.filter((todo)=>{ 59 return !todo.done 60 }) 61 } 62 } 63 } 64 </script> 65 66 <style> 67 /*base*/ 68 body { 69 background: #fff; 70 } 71 72 .btn { 73 display: inline-block; 74 padding: 4px 12px; 75 margin-bottom: 0; 76 font-size: 14px; 77 line-height: 20px; 78 text-align: center; 79 vertical-align: middle; 80 cursor: pointer; 81 box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); 82 border-radius: 4px; 83 } 84 85 .btn-danger { 86 color: #fff; 87 background-color: #da4f49; 88 border: 1px solid #bd362f; 89 } 90 91 .btn-danger:hover { 92 color: #fff; 93 background-color: #bd362f; 94 } 95 96 .btn:focus { 97 outline: none; 98 } 99 100 .todo-container { 101 width: 600px; 102 margin: 0 auto; 103 } 104 105 .todo-container .todo-wrap { 106 padding: 10px; 107 border: 1px solid #ddd; 108 border-radius: 5px; 109 } 110 111 </style>

1 <template> 2 <div class="todo-header"> 3 <input 4 type="text" placeholder="请输入你的任务名称,按回车键确认" v-model = "title" @keyup.enter="add"/> 5 </div> 6 </template> 7 8 <script> 9 import {nanoid} from 'nanoid' 10 11 export default { 12 name: "MyHeader", 13 props:['addTodo'], 14 data(){ 15 return { 16 title:' ' 17 } 18 }, 19 methods:{ 20 add(){ 21 if(!this.title.trim()) return alert("输入不能为空") 22 const todoObj = {id:nanoid(),title:this.title,done:false} 23 this.addTodo(todoObj) 24 this.title = '' 25 } 26 } 27 } 28 </script> 29 30 <style scoped> 31 /*header*/ 32 .todo-header input { 33 width: 560px; 34 height: 28px; 35 font-size: 14px; 36 border: 1px solid #ccc; 37 border-radius: 4px; 38 padding: 4px 7px; 39 } 40 41 .todo-header input:focus { 42 outline: none; 43 border-color: rgba(82, 168, 236, 0.8); 44 box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); 45 } 46 </style>

1 <template> 2 <div class="todo-footer" v-show="total"> 3 <label> 4 <!-- <input type="checkbox" :checked="isAll" @change="checkAll"/>--> 5 <input type="checkbox" v-model="isAll"/> 6 </label> 7 <span> 8 <span>已完成{{ doneTotal }}</span> / 全部{{ total }} 9 </span> 10 <button class="btn btn-danger" @click="clearAll">清除已完成任务</button> 11 </div> 12 </template> 13 14 <script> 15 export default { 16 name: "MyFooter", 17 props: ['todos','checkAllTodo','clearAllTodo'], 18 computed: { 19 //总数 20 total() { 21 return this.todos.length 22 }, 23 //已完成数 24 doneTotal() { 25 // return this.todos.filter((todo)=>todo.done===true).length 26 //此处使用reduce方法做条件统计 27 return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0) 28 }, 29 //控制全选框 30 isAll:{ 31 //全选框是否勾选 32 get(){ 33 return this.doneTotal === this.total && this.total > 0 34 }, 35 //isAll被修改时set被调用 36 set(value){ 37 this.checkAllTodo(value) 38 } 39 } 40 }, 41 methods:{ 42 // checkAll(e){ 43 // this.checkAllTodo(e.target.checked) 44 // }, 45 //清空所有已完成 46 clearAll(){ 47 this.clearAllTodo() 48 } 49 } 50 } 51 </script> 52 53 <style scoped> 54 /*footer*/ 55 .todo-footer { 56 height: 40px; 57 line-height: 40px; 58 padding-left: 6px; 59 margin-top: 5px; 60 } 61 62 .todo-footer label { 63 display: inline-block; 64 margin-right: 20px; 65 cursor: pointer; 66 } 67 68 .todo-footer label input { 69 position: relative; 70 top: -1px; 71 vertical-align: middle; 72 margin-right: 5px; 73 } 74 75 .todo-footer button { 76 float: right; 77 margin-top: 5px; 78 } 79 </style>

1 <template> 2 <ul class="todo-main"> 3 <MyItem 4 v-for="todoObj in todos" 5 :key="todoObj.id" 6 :todo="todoObj" 7 :checkTodo="checkTodo" 8 :deleteTodo="deleteTodo" 9 ></MyItem> 10 </ul> 11 </template> 12 13 <script> 14 import MyItem from "./MyItem" 15 16 export default { 17 name: "MyList", 18 components:{ 19 MyItem 20 }, 21 props:['todos','checkTodo','deleteTodo'] 22 23 } 24 </script> 25 26 <style scoped> 27 /*main*/ 28 .todo-main { 29 margin-left: 0px; 30 border: 1px solid #ddd; 31 border-radius: 2px; 32 padding: 0px; 33 } 34 35 .todo-empty { 36 height: 40px; 37 line-height: 40px; 38 border: 1px solid #ddd; 39 border-radius: 2px; 40 padding-left: 5px; 41 margin-top: 10px; 42 } 43 44 </style>

1 <template> 2 <li> 3 <label> 4 <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/> 5 <!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了props --> 6 <!-- <input type="checkbox" v-model="todo.done"/> --> 7 <span>{{ todo.title }}</span> 8 </label> 9 <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button> 10 </li> 11 </template> 12 13 <script> 14 export default { 15 name: "MyItem", 16 props:['todo','checkTodo','deleteTodo'], 17 methods:{ 18 handleCheck(id){ 19 this.checkTodo(id) 20 }, 21 handleDelete(id){ 22 if (confirm('确认删除吗?')){ 23 this.deleteTodo(id) 24 } 25 } 26 27 } 28 } 29 </script> 30 31 <style scoped> 32 /*item*/ 33 li { 34 list-style: none; 35 height: 36px; 36 line-height: 36px; 37 padding: 0 5px; 38 border-bottom: 1px solid #ddd; 39 } 40 41 li label { 42 float: left; 43 cursor: pointer; 44 } 45 46 li label li input { 47 vertical-align: middle; 48 margin-right: 6px; 49 position: relative; 50 top: -1px; 51 } 52 53 li button { 54 float: right; 55 display: none; 56 margin-top: 3px; 57 } 58 59 li:before { 60 content: initial; 61 } 62 63 li:last-child { 64 border-bottom: none; 65 } 66 67 li:hover{ 68 background-color: #ddd; 69 } 70 71 li:hover button{ 72 display: block; 73 } 74 </style>
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。

1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="UTF-8" /> 5 <title>sessionStorage</title> 6 </head> 7 <body> 8 <h2>sessionStorage</h2> 9 <button onclick="saveData()">点我保存一个数据</button> 10 <button onclick="readData()">点我读取一个数据</button> 11 <button onclick="deleteData()">点我删除一个数据</button> 12 <button onclick="deleteAllData()">点我清空一个数据</button> 13 14 <script type="text/javascript" > 15 let p = {name:'张三',age:18} 16 17 function saveData(){ 18 sessionStorage.setItem('msg','hello!!!') 19 sessionStorage.setItem('msg2',666) 20 sessionStorage.setItem('person',JSON.stringify(p)) 21 } 22 function readData(){ 23 console.log(sessionStorage.getItem('msg')) 24 console.log(sessionStorage.getItem('msg2')) 25 26 const result = sessionStorage.getItem('person') 27 console.log(JSON.parse(result)) 28 29 // console.log(sessionStorage.getItem('msg3')) 30 } 31 function deleteData(){ 32 sessionStorage.removeItem('msg2') 33 } 34 function deleteAllData(){ 35 sessionStorage.clear() 36 } 37 </script> 38 </body> 39 </html>

1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="UTF-8" /> 5 <title>localStorage</title> 6 </head> 7 <body> 8 <h2>localStorage</h2> 9 <button onclick="saveData()">点我保存一个数据</button> 10 <button onclick="readData()">点我读取一个数据</button> 11 <button onclick="deleteData()">点我删除一个数据</button> 12 <button onclick="deleteAllData()">点我清空一个数据</button> 13 14 <script type="text/javascript" > 15 let p = {name:'张三',age:18} 16 17 function saveData(){ 18 localStorage.setItem('msg','hello!!!') 19 localStorage.setItem('msg2',666) 20 localStorage.setItem('person',JSON.stringify(p)) 21 } 22 function readData(){ 23 console.log(localStorage.getItem('msg')) 24 console.log(localStorage.getItem('msg2')) 25 26 const result = localStorage.getItem('person') 27 console.log(JSON.parse(result)) 28 29 // console.log(localStorage.getItem('msg3')) 30 } 31 function deleteData(){ 32 localStorage.removeItem('msg2') 33 } 34 function deleteAllData(){ 35 localStorage.clear() 36 } 37 </script> 38 </body> 39 </html>

1 <template> 2 <div id="root"> 3 <div class="todo-container"> 4 <div class="todo-wrap"> 5 <MyHeader :addTodo="addTodo"></MyHeader> 6 <MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></MyList> 7 <MyFooter :todos="todos" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"></MyFooter> 8 </div> 9 </div> 10 </div> 11 </template> 12 13 <script> 14 import MyHeader from "./components/MyHeader" 15 import MyList from "./components/MyList" 16 import MyFooter from "./components/MyFooter" 17 18 export default { 19 name: 'App', 20 components: { 21 MyHeader, 22 MyList, 23 MyFooter 24 }, 25 data(){ 26 return { 27 // {id:"001",title:"唱",done:true}, 28 // {id:"002",title:"跳",done:true}, 29 // {id:"003",title:"rap",done:false}, 30 // {id:"004",title:"打篮球",done:true} 31 todos:JSON.parse(localStorage.getItem('todos')) || [] 32 } 33 }, 34 methods:{ 35 // 添加一个todo 36 addTodo(todoObj){ 37 this.todos.unshift(todoObj) 38 }, 39 // 勾选or取消勾选一个todo 40 checkTodo(id){ 41 this.todos.forEach((todo)=>{ 42 if (todo.id === id) todo.done = !todo.done 43 }) 44 }, 45 //删除一个todo 46 deleteTodo(id){ 47 this.todos = this.todos.filter((todo) => todo.id !== id) 48 }, 49 //全选or取消全选 50 checkAllTodo(done){ 51 this.todos.forEach((todo)=>{ 52 todo.done = done 53 }) 54 }, 55 //清除所有已经完成的todo 56 clearAllTodo(){ 57 this.todos = this.todos.filter((todo)=>{ 58 return !todo.done 59 }) 60 } 61 }, 62 watch:{ 63 // todos(value){ 64 // localStorage.setItem('todos',JSON.stringify(value)) 65 // } 66 todos:{ 67 deep:true, 68 handler(value){ 69 localStorage.setItem('todos',JSON.stringify(value)) 70 } 71 } 72 } 73 } 74 </script> 75 76 <style> 77 /*base*/ 78 body { 79 background: #fff; 80 } 81 82 .btn { 83 display: inline-block; 84 padding: 4px 12px; 85 margin-bottom: 0; 86 font-size: 14px; 87 line-height: 20px; 88 text-align: center; 89 vertical-align: middle; 90 cursor: pointer; 91 box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); 92 border-radius: 4px; 93 } 94 95 .btn-danger { 96 color: #fff; 97 background-color: #da4f49; 98 border: 1px solid #bd362f; 99 } 100 101 .btn-danger:hover { 102 color: #fff; 103 background-color: #bd362f; 104 } 105 106 .btn:focus { 107 outline: none; 108 } 109 110 .todo-container { 111 width: 600px; 112 margin: 0 auto; 113 } 114 115 .todo-container .todo-wrap { 116 padding: 10px; 117 border: 1px solid #ddd; 118 border-radius: 5px; 119 } 120 121 </style>
1. 一种组件间通信的方式,适用于:子组件 ===> 父组件 2. 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。 3. 绑定自定义事件: 1. 第一种方式,在父组件中:<Demo @at="test"/> 或 <Demo v-on:at="test"/> 2. 第二种方式,在父组件中: <Demo ref="demo"/> ...... mounted(){ this.$refs.xxx.$on('at',this.test) } 3. 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。 4. 触发自定义事件:this.$emit('at',数据) 5. 解绑自定义事件this.$off('at') 6. 组件上也可以绑定原生DOM事件,需要使用native修饰符。 7. 注意:通过this.$refs.xxx.$on('at',回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!

1 <template> 2 <div class="app"> 3 <h1>{{msg}},学生姓名是:{{studentName}}</h1> 4 <!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 --> 5 <School :getSchoolName="getSchoolName"></School> 6 7 <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v-on) --> 8 <Student @getStudentNameEvent="getStudentName" @demo="m1"></Student> 9 10 <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref) --> 11 <!-- <Student ref="student" @click.native="show"></Student>--> 12 </div> 13 </template> 14 15 <script> 16 import Student from "./components/Student"; 17 import School from "./components/School"; 18 19 export default { 20 name:'App', 21 components:{ 22 Student, 23 School 24 }, 25 data(){ 26 return { 27 msg:"你好啊", 28 studentName:'' 29 } 30 }, 31 methods:{ 32 getSchoolName(name){ 33 console.log('App收到了学校名:',name) 34 }, 35 getStudentName(name,...params){ 36 console.log('App收到了学生名:',name,params) 37 this.studentName = name 38 }, 39 m1(){ 40 console.log('demo事件被触发了!') 41 }, 42 show(){ 43 alert(123) 44 } 45 }, 46 mounted() { 47 // this.$refs.student.$on("getStudentNameEvent",this.getStudentName) 48 // this.$refs.student.$once("getStudentNameEvent",this.getStudentName) 49 } 50 } 51 </script> 52 53 <style scoped> 54 .app{ 55 background-color: gray; 56 padding: 5px; 57 } 58 </style>

1 <template> 2 <div class="student"> 3 <h2>学生姓名:{{name}}</h2> 4 <h2>学生性别:{{sex}}</h2> 5 <h2>当前求和为:{{number}}</h2> 6 <button @click="add">点我number++</button> 7 8 <button @click="sendStudentlName">把学生名给App</button> 9 <button @click="unbind">解绑getStudentNameEvent事件</button> 10 11 <button @click="death">销毁当前Student组件的实例(vc)</button> 12 </div> 13 </template> 14 15 <script> 16 export default { 17 name:'Student', 18 data(){ 19 return { 20 name:"张三", 21 sex:"男", 22 number:0 23 } 24 }, 25 methods:{ 26 add(){ 27 console.log('add回调被调用了') 28 this.number++ 29 }, 30 sendStudentlName(){ 31 //触发Student组件实例身上的getStudentNameEvent事件 32 this.$emit("getStudentNameEvent",this.name,300,400,500) 33 // this.$emit("demo") 34 35 // this.$emit('click') 36 }, 37 unbind(){ 38 this.$off("getStudentNameEvent") //解绑一个自定义事件 39 // this.$off(['getStudentNameEvent','demo']) //解绑多个自定义事件 40 // this.$off() //解绑所有的自定义事件 41 }, 42 death(){ 43 //销毁了当前Student组件的实例,销毁后所有Student实例的自定义事件全都不奏效。 44 this.$destroy() 45 } 46 } 47 } 48 </script> 49 50 <style lang="less" scoped> 51 .student{ 52 background-color: pink; 53 padding: 5px; 54 margin-top: 30px; 55 } 56 </style>

1 <template> 2 <div class="school"> 3 <h2>学校名称:{{name}}</h2> 4 <h2>学校地址:{{address}}</h2> 5 <button @click="sendSchoolName">把学校名给App</button> 6 </div> 7 </template> 8 9 <script> 10 export default { 11 name:'School', 12 props:['getSchoolName'], 13 data(){ 14 return { 15 name:"京东", 16 address:"北京" 17 } 18 }, 19 methods:{ 20 sendSchoolName(){ 21 this.getSchoolName(this.name) 22 } 23 } 24 } 25 </script> 26 27 <style scoped> 28 .school{ 29 background-color: skyblue; 30 padding: 5px; 31 } 32 </style>
TodoList_自定义事件

1 <template> 2 <div id="root"> 3 <div class="todo-container"> 4 <div class="todo-wrap"> 5 <MyHeader @addTodo="addTodo"></MyHeader> 6 <MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></MyList> 7 <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"></MyFooter> 8 </div> 9 </div> 10 </div> 11 </template> 12 13 <script> 14 import MyHeader from "./components/MyHeader" 15 import MyList from "./components/MyList" 16 import MyFooter from "./components/MyFooter" 17 18 export default { 19 name: 'App', 20 components: { 21 MyHeader, 22 MyList, 23 MyFooter 24 }, 25 data(){ 26 return { 27 // {id:"001",title:"唱",done:true}, 28 // {id:"002",title:"跳",done:true}, 29 // {id:"003",title:"rap",done:false}, 30 // {id:"004",title:"打篮球",done:true} 31 todos:JSON.parse(localStorage.getItem('todos')) || [] 32 } 33 }, 34 methods:{ 35 // 添加一个todo 36 addTodo(todoObj){ 37 this.todos.unshift(todoObj) 38 }, 39 // 勾选or取消勾选一个todo 40 checkTodo(id){ 41 this.todos.forEach((todo)=>{ 42 if (todo.id === id) todo.done = !todo.done 43 }) 44 }, 45 //删除一个todo 46 deleteTodo(id){ 47 this.todos = this.todos.filter((todo) => todo.id !== id) 48 }, 49 //全选or取消全选 50 checkAllTodo(done){ 51 this.todos.forEach((todo)=>{ 52 todo.done = done 53 }) 54 }, 55 //清除所有已经完成的todo 56 clearAllTodo(){ 57 this.todos = this.todos.filter((todo)=>{ 58 return !todo.done 59 }) 60 } 61 }, 62 watch:{ 63 // todos(value){ 64 // localStorage.setItem('todos',JSON.stringify(value)) 65 // } 66 todos:{ 67 deep:true, 68 handler(value){ 69 localStorage.setItem('todos',JSON.stringify(value)) 70 } 71 } 72 } 73 } 74 </script> 75 76 <style> 77 /*base*/ 78 body { 79 background: #fff; 80 } 81 82 .btn { 83 display: inline-block; 84 padding: 4px 12px; 85 margin-bottom: 0; 86 font-size: 14px; 87 line-height: 20px; 88 text-align: center; 89 vertical-align: middle; 90 cursor: pointer; 91 box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); 92 border-radius: 4px; 93 } 94 95 .btn-danger { 96 color: #fff; 97 background-color: #da4f49; 98 border: 1px solid #bd362f; 99 } 100 101 .btn-danger:hover { 102 color: #fff; 103 background-color: #bd362f; 104 } 105 106 .btn:focus { 107 outline: none; 108 } 109 110 .todo-container { 111 width: 600px; 112 margin: 0 auto; 113 } 114 115 .todo-container .todo-wrap { 116 padding: 10px; 117 border: 1px solid #ddd; 118 border-radius: 5px; 119 } 120 121 </style>

1 <template> 2 <div class="todo-header"> 3 <input 4 type="text" placeholder="请输入你的任务名称,按回车键确认" v-model = "title" @keyup.enter="add"/> 5 </div> 6 </template> 7 8 <script> 9 import {nanoid} from 'nanoid' 10 11 export default { 12 name: "MyHeader", 13 // props:['addTodo'], 14 data(){ 15 return { 16 title:' ' 17 } 18 }, 19 methods:{ 20 add(){ 21 if(!this.title.trim()) return alert("输入不能为空") 22 const todoObj = {id:nanoid(),title:this.title,done:false} 23 // this.addTodo(todoObj) 24 this.$emit('addTodo',todoObj) 25 this.title = '' 26 } 27 } 28 } 29 </script> 30 31 <style scoped> 32 /*header*/ 33 .todo-header input { 34 width: 560px; 35 height: 28px; 36 font-size: 14px; 37 border: 1px solid #ccc; 38 border-radius: 4px; 39 padding: 4px 7px; 40 } 41 42 .todo-header input:focus { 43 outline: none; 44 border-color: rgba(82, 168, 236, 0.8); 45 box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); 46 } 47 </style>

1 <template> 2 <div class="todo-footer" v-show="total"> 3 <label> 4 <!-- <input type="checkbox" :checked="isAll" @change="checkAll"/>--> 5 <input type="checkbox" v-model="isAll"/> 6 </label> 7 <span> 8 <span>已完成{{ doneTotal }}</span> / 全部{{ total }} 9 </span> 10 <button class="btn btn-danger" @click="clearAll">清除已完成任务</button> 11 </div> 12 </template> 13 14 <script> 15 export default { 16 name: "MyFooter", 17 // props: ['todos','checkAllTodo','clearAllTodo'], 18 props: ['todos'], 19 computed: { 20 //总数 21 total() { 22 return this.todos.length 23 }, 24 //已完成数 25 doneTotal() { 26 // return this.todos.filter((todo)=>todo.done===true).length 27 //此处使用reduce方法做条件统计 28 return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0) 29 }, 30 //控制全选框 31 isAll:{ 32 //全选框是否勾选 33 get(){ 34 return this.doneTotal === this.total && this.total > 0 35 }, 36 //isAll被修改时set被调用 37 set(value){ 38 // this.checkAllTodo(value) 39 this.$emit("checkAllTodo",value) 40 } 41 } 42 }, 43 methods:{ 44 // checkAll(e){ 45 // this.checkAllTodo(e.target.checked) 46 // }, 47 //清空所有已完成 48 clearAll(){ 49 // this.clearAllTodo() 50 this.$emit("clearAllTodo") 51 } 52 } 53 } 54 </script> 55 56 <style scoped> 57 /*footer*/ 58 .todo-footer { 59 height: 40px; 60 line-height: 40px; 61 padding-left: 6px; 62 margin-top: 5px; 63 } 64 65 .todo-footer label { 66 display: inline-block; 67 margin-right: 20px; 68 cursor: pointer; 69 } 70 71 .todo-footer label input { 72 position: relative; 73 top: -1px; 74 vertical-align: middle; 75 margin-right: 5px; 76 } 77 78 .todo-footer button { 79 float: right; 80 margin-top: 5px; 81 } 82 </style>

1 <template> 2 <ul class="todo-main"> 3 <MyItem 4 v-for="todoObj in todos" 5 :key="todoObj.id" 6 :todo="todoObj" 7 :checkTodo="checkTodo" 8 :deleteTodo="deleteTodo" 9 ></MyItem> 10 </ul> 11 </template> 12 13 <script> 14 import MyItem from "./MyItem" 15 16 export default { 17 name: "MyList", 18 components:{ 19 MyItem 20 }, 21 props:['todos','checkTodo','deleteTodo'] 22 23 } 24 </script> 25 26 <style scoped> 27 /*main*/ 28 .todo-main { 29 margin-left: 0px; 30 border: 1px solid #ddd; 31 border-radius: 2px; 32 padding: 0px; 33 } 34 35 .todo-empty { 36 height: 40px; 37 line-height: 40px; 38 border: 1px solid #ddd; 39 border-radius: 2px; 40 padding-left: 5px; 41 margin-top: 10px; 42 } 43 44 </style>

1 <template> 2 <li> 3 <label> 4 <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/> 5 <!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了props --> 6 <!-- <input type="checkbox" v-model="todo.done"/> --> 7 <span>{{ todo.title }}</span> 8 </label> 9 <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button> 10 </li> 11 </template> 12 13 <script> 14 export default { 15 name: "MyItem", 16 props:['todo','checkTodo','deleteTodo'], 17 methods:{ 18 handleCheck(id){ 19 this.checkTodo(id) 20 }, 21 handleDelete(id){ 22 if (confirm('确认删除吗?')){ 23 this.deleteTodo(id) 24 } 25 } 26 27 } 28 } 29 </script> 30 31 <style scoped> 32 /*item*/ 33 li { 34 list-style: none; 35 height: 36px; 36 line-height: 36px; 37 padding: 0 5px; 38 border-bottom: 1px solid #ddd; 39 } 40 41 li label { 42 float: left; 43 cursor: pointer; 44 } 45 46 li label li input { 47 vertical-align: middle; 48 margin-right: 6px; 49 position: relative; 50 top: -1px; 51 } 52 53 li button { 54 float: right; 55 display: none; 56 margin-top: 3px; 57 } 58 59 li:before { 60 content: initial; 61 } 62 63 li:last-child { 64 border-bottom: none; 65 } 66 67 li:hover{ 68 background-color: #ddd; 69 } 70 71 li:hover button{ 72 display: block; 73 } 74 </style>
9、全局事件总线(GlobalEventBus)
1. 一种组件间通信的方式,适用于任意组件间通信。 2. 安装全局事件总线: new Vue({ ...... beforeCreate() { Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm }, ...... }) 3. 使用事件总线: 1. 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。 methods(){ demo(data){......} } ...... mounted() { this.$bus.$on('xxxx',this.demo) } 2. 提供数据:this.$bus.$emit('xxxx',数据) 4. 最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。

1 import Vue from 'vue' 2 import App from './App.vue' 3 4 Vue.config.productionTip = false 5 6 new Vue({ 7 el:"#app", 8 render: h => h(App), 9 beforeCreate() { 10 //安装全局事件总线 11 Vue.prototype.$bus = this 12 } 13 })

1 <template> 2 <div class="app"> 3 <h1>{{msg}}</h1> 4 <School></School> 5 <Student></Student> 6 </div> 7 </template> 8 9 <script> 10 import Student from "./components/Student"; 11 import School from "./components/School"; 12 13 export default { 14 name:'App', 15 components:{ 16 Student, 17 School 18 }, 19 data(){ 20 return { 21 msg:"你好啊", 22 } 23 }, 24 methods:{ 25 26 } 27 } 28 </script> 29 30 <style scoped> 31 .app{ 32 background-color: gray; 33 padding: 5px; 34 } 35 </style>

1 <template> 2 <div class="student"> 3 <h2>学生姓名:{{name}}</h2> 4 <h2>学生性别:{{sex}}</h2> 5 <button @click="sendStudentName">把学生名给School组件</button> 6 </div> 7 </template> 8 9 <script> 10 export default { 11 name:'Student', 12 data(){ 13 return { 14 name:"张三", 15 sex:"男", 16 } 17 }, 18 methods:{ 19 sendStudentName(){ 20 this.$bus.$emit('hello',this.name) 21 } 22 } 23 } 24 </script> 25 26 <style lang="less" scoped> 27 .student{ 28 background-color: pink; 29 padding: 5px; 30 margin-top: 30px; 31 } 32 </style>

1 <template> 2 <div class="school"> 3 <h2>学校名称:{{name}}</h2> 4 <h2>学校地址:{{address}}</h2> 5 </div> 6 </template> 7 8 <script> 9 export default { 10 name:'School', 11 data(){ 12 return { 13 name:"京东", 14 address:"北京" 15 } 16 }, 17 mounted() { 18 this.$bus.$on('hello',(data)=>{ 19 console.log('我是School组件,收到了数据',data) 20 }) 21 }, 22 beforeDestroy() { 23 this.$bus.$off('hello') 24 } 25 } 26 </script> 27 28 <style scoped> 29 .school{ 30 background-color: skyblue; 31 padding: 5px; 32 } 33 </style>
TodoList_事件总线

1 import Vue from 'vue' 2 import App from './App.vue' 3 4 Vue.config.productionTip = false 5 6 new Vue({ 7 el:"#app", 8 render: h => h(App), 9 beforeCreate() { 10 //安装全局事件总线 11 Vue.prototype.$bus = this 12 } 13 })

1 <template> 2 <div id="root"> 3 <div class="todo-container"> 4 <div class="todo-wrap"> 5 <MyHeader @addTodo="addTodo"></MyHeader> 6 <!-- <MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></MyList>--> 7 <MyList :todos="todos"></MyList> 8 <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"></MyFooter> 9 </div> 10 </div> 11 </div> 12 </template> 13 14 <script> 15 import MyHeader from "./components/MyHeader" 16 import MyList from "./components/MyList" 17 import MyFooter from "./components/MyFooter" 18 19 export default { 20 name: 'App', 21 components: { 22 MyHeader, 23 MyList, 24 MyFooter 25 }, 26 data(){ 27 return { 28 // {id:"001",title:"唱",done:true}, 29 // {id:"002",title:"跳",done:true}, 30 // {id:"003",title:"rap",done:false}, 31 // {id:"004",title:"打篮球",done:true} 32 todos:JSON.parse(localStorage.getItem('todos')) || [] 33 } 34 }, 35 methods:{ 36 // 添加一个todo 37 addTodo(todoObj){ 38 this.todos.unshift(todoObj) 39 }, 40 // 勾选or取消勾选一个todo 41 checkTodo(id){ 42 this.todos.forEach((todo)=>{ 43 if (todo.id === id) todo.done = !todo.done 44 }) 45 }, 46 //删除一个todo 47 deleteTodo(id){ 48 this.todos = this.todos.filter((todo) => todo.id !== id) 49 }, 50 //全选or取消全选 51 checkAllTodo(done){ 52 this.todos.forEach((todo)=>{ 53 todo.done = done 54 }) 55 }, 56 //清除所有已经完成的todo 57 clearAllTodo(){ 58 this.todos = this.todos.filter((todo)=>{ 59 return !todo.done 60 }) 61 } 62 }, 63 watch:{ 64 // todos(value){ 65 // localStorage.setItem('todos',JSON.stringify(value)) 66 // } 67 todos:{ 68 deep:true, 69 handler(value){ 70 localStorage.setItem('todos',JSON.stringify(value)) 71 } 72 } 73 }, 74 mounted() { 75 this.$bus.$on('checkTodo',this.checkTodo) 76 this.$bus.$on('deleteTodo',this.deleteTodo) 77 }, 78 beforeDestroy() { 79 this.$bus.$off('checkTodo') 80 this.$bus.$off('deleteTodo') 81 } 82 } 83 </script> 84 85 <style> 86 /*base*/ 87 body { 88 background: #fff; 89 } 90 91 .btn { 92 display: inline-block; 93 padding: 4px 12px; 94 margin-bottom: 0; 95 font-size: 14px; 96 line-height: 20px; 97 text-align: center; 98 vertical-align: middle; 99 cursor: pointer; 100 box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); 101 border-radius: 4px; 102 } 103 104 .btn-danger { 105 color: #fff; 106 background-color: #da4f49; 107 border: 1px solid #bd362f; 108 } 109 110 .btn-danger:hover { 111 color: #fff; 112 background-color: #bd362f; 113 } 114 115 .btn:focus { 116 outline: none; 117 } 118 119 .todo-container { 120 width: 600px; 121 margin: 0 auto; 122 } 123 124 .todo-container .todo-wrap { 125 padding: 10px; 126 border: 1px solid #ddd; 127 border-radius: 5px; 128 } 129 130 </style>

1 <template> 2 <div class="todo-header"> 3 <input 4 type="text" placeholder="请输入你的任务名称,按回车键确认" v-model = "title" @keyup.enter="add"/> 5 </div> 6 </template> 7 8 <script> 9 import {nanoid} from 'nanoid' 10 11 export default { 12 name: "MyHeader", 13 // props:['addTodo'], 14 data(){ 15 return { 16 title:' ' 17 } 18 }, 19 methods:{ 20 add(){ 21 if(!this.title.trim()) return alert("输入不能为空") 22 const todoObj = {id:nanoid(),title:this.title,done:false} 23 // this.addTodo(todoObj) 24 this.$emit('addTodo',todoObj) 25 this.title = '' 26 } 27 } 28 } 29 </script> 30 31 <style scoped> 32 /*header*/ 33 .todo-header input { 34 width: 560px; 35 height: 28px; 36 font-size: 14px; 37 border: 1px solid #ccc; 38 border-radius: 4px; 39 padding: 4px 7px; 40 } 41 42 .todo-header input:focus { 43 outline: none; 44 border-color: rgba(82, 168, 236, 0.8); 45 box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); 46 } 47 </style>

1 <template> 2 <div class="todo-footer" v-show="total"> 3 <label> 4 <!-- <input type="checkbox" :checked="isAll" @change="checkAll"/>--> 5 <input type="checkbox" v-model="isAll"/> 6 </label> 7 <span> 8 <span>已完成{{ doneTotal }}</span> / 全部{{ total }} 9 </span> 10 <button class="btn btn-danger" @click="clearAll">清除已完成任务</button> 11 </div> 12 </template> 13 14 <script> 15 export default { 16 name: "MyFooter", 17 // props: ['todos','checkAllTodo','clearAllTodo'], 18 props: ['todos'], 19 computed: { 20 //总数 21 total() { 22 return this.todos.length 23 }, 24 //已完成数 25 doneTotal() { 26 // return this.todos.filter((todo)=>todo.done===true).length 27 //此处使用reduce方法做条件统计 28 return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0) 29 }, 30 //控制全选框 31 isAll:{ 32 //全选框是否勾选 33 get(){ 34 return this.doneTotal === this.total && this.total > 0 35 }, 36 //isAll被修改时set被调用 37 set(value){ 38 // this.checkAllTodo(value) 39 this.$emit("checkAllTodo",value) 40 } 41 } 42 }, 43 methods:{ 44 // checkAll(e){ 45 // this.checkAllTodo(e.target.checked) 46 // }, 47 //清空所有已完成 48 clearAll(){ 49 // this.clearAllTodo() 50 this.$emit("clearAllTodo") 51 } 52 } 53 } 54 </script> 55 56 <style scoped> 57 /*footer*/ 58 .todo-footer { 59 height: 40px; 60 line-height: 40px; 61 padding-left: 6px; 62 margin-top: 5px; 63 } 64 65 .todo-footer label { 66 display: inline-block; 67 margin-right: 20px; 68 cursor: pointer; 69 } 70 71 .todo-footer label input { 72 position: relative; 73 top: -1px; 74 vertical-align: middle; 75 margin-right: 5px; 76 } 77 78 .todo-footer button { 79 float: right; 80 margin-top: 5px; 81 } 82 </style>

1 <template> 2 <ul class="todo-main"> 3 <MyItem 4 v-for="todoObj in todos" 5 :key="todoObj.id" 6 :todo="todoObj" 7 ></MyItem> 8 </ul> 9 </template> 10 11 <script> 12 import MyItem from "./MyItem" 13 14 export default { 15 name: "MyList", 16 components:{ 17 MyItem 18 }, 19 // props:['todos','checkTodo','deleteTodo'] 20 props:['todos'] 21 22 } 23 </script> 24 25 <style scoped> 26 /*main*/ 27 .todo-main { 28 margin-left: 0px; 29 border: 1px solid #ddd; 30 border-radius: 2px; 31 padding: 0px; 32 } 33 34 .todo-empty { 35 height: 40px; 36 line-height: 40px; 37 border: 1px solid #ddd; 38 border-radius: 2px; 39 padding-left: 5px; 40 margin-top: 10px; 41 } 42 43 </style>

1 <template> 2 <li> 3 <label> 4 <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/> 5 <!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了props --> 6 <!-- <input type="checkbox" v-model="todo.done"/> --> 7 <span>{{ todo.title }}</span> 8 </label> 9 <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button> 10 </li> 11 </template> 12 13 <script> 14 export default { 15 name: "MyItem", 16 props:['todo'], 17 methods:{ 18 handleCheck(id){ 19 // this.checkTodo(id) 20 this.$bus.$emit('checkTodo',id) 21 }, 22 handleDelete(id){ 23 if (confirm('确认删除吗?')){ 24 // this.deleteTodo(id) 25 this.$bus.$emit('deleteTodo',id) 26 } 27 } 28 29 } 30 } 31 </script> 32 33 <style scoped> 34 /*item*/ 35 li { 36 list-style: none; 37 height: 36px; 38 line-height: 36px; 39 padding: 0 5px; 40 border-bottom: 1px solid #ddd; 41 } 42 43 li label { 44 float: left; 45 cursor: pointer; 46 } 47 48 li label li input { 49 vertical-align: middle; 50 margin-right: 6px; 51 position: relative; 52 top: -1px; 53 } 54 55 li button { 56 float: right; 57 display: none; 58 margin-top: 3px; 59 } 60 61 li:before { 62 content: initial; 63 } 64 65 li:last-child { 66 border-bottom: none; 67 } 68 69 li:hover{ 70 background-color: #ddd; 71 } 72 73 li:hover button{ 74 display: block; 75 } 76 </style>
10、消息订阅与发布(pubsub)
1. 一种组件间通信的方式,适用于任意组件间通信</span>。 2. 使用步骤: 1. 安装pubsub:npm i pubsub-js 2. 引入: import pubsub from 'pubsub-js' 3. 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。 methods(){ demo(data){......} } ...... mounted() { this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息 } 4. 提供数据:pubsub.publish('xxx',数据) 5. 最好在beforeDestroy钩子中,用PubSub.unsubscribe(pid)去取消订阅。

1 <template> 2 <div class="app"> 3 <h1>{{msg}}</h1> 4 <School/> 5 <Student/> 6 </div> 7 </template> 8 9 <script> 10 import Student from './components/Student' 11 import School from './components/School' 12 13 export default { 14 name:'App', 15 components:{School,Student}, 16 data() { 17 return { 18 msg:'你好啊!', 19 } 20 } 21 } 22 </script> 23 24 <style scoped> 25 .app{ 26 background-color: gray; 27 padding: 5px; 28 } 29 </style>

1 <template> 2 <div class="student"> 3 <h2>学生姓名:{{name}}</h2> 4 <h2>学生性别:{{sex}}</h2> 5 <button @click="sendStudentName">把学生名给School组件</button> 6 </div> 7 </template> 8 9 <script> 10 import pubsub from 'pubsub-js' 11 export default { 12 name:'Student', 13 data() { 14 return { 15 name:'张三', 16 sex:'男', 17 } 18 }, 19 mounted() { 20 // console.log('Student',this.x) 21 }, 22 methods: { 23 sendStudentName(){ 24 // this.$bus.$emit('hello',this.name) 25 pubsub.publish('hello',666) 26 } 27 }, 28 } 29 </script> 30 31 <style lang="less" scoped> 32 .student{ 33 background-color: pink; 34 padding: 5px; 35 margin-top: 30px; 36 } 37 </style>

1 <template> 2 <div class="school"> 3 <h2>学校名称:{{name}}</h2> 4 <h2>学校地址:{{address}}</h2> 5 </div> 6 </template> 7 8 <script> 9 import pubsub from 'pubsub-js' 10 export default { 11 name:'School', 12 data() { 13 return { 14 name:'尚硅谷', 15 address:'北京', 16 } 17 }, 18 mounted() { 19 // console.log('School',this) 20 /* this.$bus.$on('hello',(data)=>{ 21 console.log('我是School组件,收到了数据',data) 22 }) */ 23 this.pubId = pubsub.subscribe('hello',(msgName,data)=>{ 24 console.log(this) 25 // console.log('有人发布了hello消息,hello消息的回调执行了',msgName,data) 26 }) 27 }, 28 beforeDestroy() { 29 // this.$bus.$off('hello') 30 pubsub.unsubscribe(this.pubId) 31 }, 32 } 33 </script> 34 35 <style scoped> 36 .school{ 37 background-color: skyblue; 38 padding: 5px; 39 } 40 </style>
TodoList_pubsub

1 <template> 2 <div id="root"> 3 <div class="todo-container"> 4 <div class="todo-wrap"> 5 <MyHeader @addTodo="addTodo"></MyHeader> 6 <!-- <MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></MyList>--> 7 <MyList :todos="todos"></MyList> 8 <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"></MyFooter> 9 </div> 10 </div> 11 </div> 12 </template> 13 14 <script> 15 import MyHeader from "./components/MyHeader" 16 import MyList from "./components/MyList" 17 import MyFooter from "./components/MyFooter" 18 import pubsub from 'pubsub-js' 19 20 export default { 21 name: 'App', 22 components: { 23 MyHeader, 24 MyList, 25 MyFooter 26 }, 27 data(){ 28 return { 29 // {id:"001",title:"唱",done:true}, 30 // {id:"002",title:"跳",done:true}, 31 // {id:"003",title:"rap",done:false}, 32 // {id:"004",title:"打篮球",done:true} 33 todos:JSON.parse(localStorage.getItem('todos')) || [] 34 } 35 }, 36 methods:{ 37 // 添加一个todo 38 addTodo(todoObj){ 39 this.todos.unshift(todoObj) 40 }, 41 // 勾选or取消勾选一个todo 42 checkTodo(id){ 43 this.todos.forEach((todo)=>{ 44 if (todo.id === id) todo.done = !todo.done 45 }) 46 }, 47 //删除一个todo 48 deleteTodo(_,id){ 49 this.todos = this.todos.filter((todo) => todo.id !== id) 50 }, 51 //全选or取消全选 52 checkAllTodo(done){ 53 this.todos.forEach((todo)=>{ 54 todo.done = done 55 }) 56 }, 57 //清除所有已经完成的todo 58 clearAllTodo(){ 59 this.todos = this.todos.filter((todo)=>{ 60 return !todo.done 61 }) 62 } 63 }, 64 watch:{ 65 // todos(value){ 66 // localStorage.setItem('todos',JSON.stringify(value)) 67 // } 68 todos:{ 69 deep:true, 70 handler(value){ 71 localStorage.setItem('todos',JSON.stringify(value)) 72 } 73 } 74 }, 75 mounted() { 76 this.$bus.$on('checkTodo',this.checkTodo) 77 // this.$bus.$on('deleteTodo',this.deleteTodo) 78 this.pubId = pubsub.subscribe('deleteTodo',this.deleteTodo) 79 }, 80 beforeDestroy() { 81 this.$bus.$off('checkTodo') 82 // this.$bus.$off('deleteTodo') 83 pubsub.unsubscribe(this.pubId) 84 } 85 } 86 </script> 87 88 <style> 89 /*base*/ 90 body { 91 background: #fff; 92 } 93 94 .btn { 95 display: inline-block; 96 padding: 4px 12px; 97 margin-bottom: 0; 98 font-size: 14px; 99 line-height: 20px; 100 text-align: center; 101 vertical-align: middle; 102 cursor: pointer; 103 box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); 104 border-radius: 4px; 105 } 106 107 .btn-danger { 108 color: #fff; 109 background-color: #da4f49; 110 border: 1px solid #bd362f; 111 } 112 113 .btn-danger:hover { 114 color: #fff; 115 background-color: #bd362f; 116 } 117 118 .btn:focus { 119 outline: none; 120 } 121 122 .todo-container { 123 width: 600px; 124 margin: 0 auto; 125 } 126 127 .todo-container .todo-wrap { 128 padding: 10px; 129 border: 1px solid #ddd; 130 border-radius: 5px; 131 } 132 133 </style>

1 <template> 2 <div class="todo-header"> 3 <input 4 type="text" placeholder="请输入你的任务名称,按回车键确认" v-model = "title" @keyup.enter="add"/> 5 </div> 6 </template> 7 8 <script> 9 import {nanoid} from 'nanoid' 10 11 export default { 12 name: "MyHeader", 13 // props:['addTodo'], 14 data(){ 15 return { 16 title:' ' 17 } 18 }, 19 methods:{ 20 add(){ 21 if(!this.title.trim()) return alert("输入不能为空") 22 const todoObj = {id:nanoid(),title:this.title,done:false} 23 // this.addTodo(todoObj) 24 this.$emit('addTodo',todoObj) 25 this.title = '' 26 } 27 } 28 } 29 </script> 30 31 <style scoped> 32 /*header*/ 33 .todo-header input { 34 width: 560px; 35 height: 28px; 36 font-size: 14px; 37 border: 1px solid #ccc; 38 border-radius: 4px; 39 padding: 4px 7px; 40 } 41 42 .todo-header input:focus { 43 outline: none; 44 border-color: rgba(82, 168, 236, 0.8); 45 box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); 46 } 47 </style>

1 <template> 2 <div class="todo-footer" v-show="total"> 3 <label> 4 <!-- <input type="checkbox" :checked="isAll" @change="checkAll"/>--> 5 <input type="checkbox" v-model="isAll"/> 6 </label> 7 <span> 8 <span>已完成{{ doneTotal }}</span> / 全部{{ total }} 9 </span> 10 <button class="btn btn-danger" @click="clearAll">清除已完成任务</button> 11 </div> 12 </template> 13 14 <script> 15 export default { 16 name: "MyFooter", 17 // props: ['todos','checkAllTodo','clearAllTodo'], 18 props: ['todos'], 19 computed: { 20 //总数 21 total() { 22 return this.todos.length 23 }, 24 //已完成数 25 doneTotal() { 26 // return this.todos.filter((todo)=>todo.done===true).length 27 //此处使用reduce方法做条件统计 28 return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0) 29 }, 30 //控制全选框 31 isAll:{ 32 //全选框是否勾选 33 get(){ 34 return this.doneTotal === this.total && this.total > 0 35 }, 36 //isAll被修改时set被调用 37 set(value){ 38 // this.checkAllTodo(value) 39 this.$emit("checkAllTodo",value) 40 } 41 } 42 }, 43 methods:{ 44 // checkAll(e){ 45 // this.checkAllTodo(e.target.checked) 46 // }, 47 //清空所有已完成 48 clearAll(){ 49 // this.clearAllTodo() 50 this.$emit("clearAllTodo") 51 } 52 } 53 } 54 </script> 55 56 <style scoped> 57 /*footer*/ 58 .todo-footer { 59 height: 40px; 60 line-height: 40px; 61 padding-left: 6px; 62 margin-top: 5px; 63 } 64 65 .todo-footer label { 66 display: inline-block; 67 margin-right: 20px; 68 cursor: pointer; 69 } 70 71 .todo-footer label input { 72 position: relative; 73 top: -1px; 74 vertical-align: middle; 75 margin-right: 5px; 76 } 77 78 .todo-footer button { 79 float: right; 80 margin-top: 5px; 81 } 82 </style>

1 <template> 2 <ul class="todo-main"> 3 <MyItem 4 v-for="todoObj in todos" 5 :key="todoObj.id" 6 :todo="todoObj" 7 ></MyItem> 8 </ul> 9 </template> 10 11 <script> 12 import MyItem from "./MyItem" 13 14 export default { 15 name: "MyList", 16 components:{ 17 MyItem 18 }, 19 // props:['todos','checkTodo','deleteTodo'] 20 props:['todos'] 21 22 } 23 </script> 24 25 <style scoped> 26 /*main*/ 27 .todo-main { 28 margin-left: 0px; 29 border: 1px solid #ddd; 30 border-radius: 2px; 31 padding: 0px; 32 } 33 34 .todo-empty { 35 height: 40px; 36 line-height: 40px; 37 border: 1px solid #ddd; 38 border-radius: 2px; 39 padding-left: 5px; 40 margin-top: 10px; 41 } 42 43 </style>

1 <template> 2 <li> 3 <label> 4 <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/> 5 <!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了props --> 6 <!-- <input type="checkbox" v-model="todo.done"/> --> 7 <span>{{ todo.title }}</span> 8 </label> 9 <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button> 10 </li> 11 </template> 12 13 <script> 14 import pubsub from 'pubsub-js' 15 16 export default { 17 name: "MyItem", 18 props:['todo'], 19 methods:{ 20 handleCheck(id){ 21 // this.checkTodo(id) 22 this.$bus.$emit('checkTodo',id) 23 }, 24 handleDelete(id){ 25 if (confirm('确认删除吗?')){ 26 // this.deleteTodo(id) 27 // this.$bus.$emit('deleteTodo',id) 28 pubsub.publish('deleteTodo',id) 29 } 30 } 31 } 32 } 33 </script> 34 35 <style scoped> 36 /*item*/ 37 li { 38 list-style: none; 39 height: 36px; 40 line-height: 36px; 41 padding: 0 5px; 42 border-bottom: 1px solid #ddd; 43 } 44 45 li label { 46 float: left; 47 cursor: pointer; 48 } 49 50 li label li input { 51 vertical-align: middle; 52 margin-right: 6px; 53 position: relative; 54 top: -1px; 55 } 56 57 li button { 58 float: right; 59 display: none; 60 margin-top: 3px; 61 } 62 63 li:before { 64 content: initial; 65 } 66 67 li:last-child { 68 border-bottom: none; 69 } 70 71 li:hover{ 72 background-color: #ddd; 73 } 74 75 li:hover button{ 76 display: block; 77 } 78 </style>
1. 语法:this.$nextTick(回调函数)
2. 作用:在下一次 DOM 更新结束后执行其指定的回调。
3. 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。

1 <template> 2 <li> 3 <label> 4 <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/> 5 <!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了props --> 6 <!-- <input type="checkbox" v-model="todo.done"/> --> 7 <span v-show="!todo.isEdit">{{ todo.title }}</span> 8 <input 9 type="text" 10 v-show="todo.isEdit" 11 :value="todo.title" 12 @blur="handleBlur(todo,$event)" 13 ref="inputTitle" 14 > 15 </label> 16 <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button> 17 <button v-show="!todo.isEdit" class="btn btn-edit" @click="handleEdit(todo)">编辑</button> 18 </li> 19 </template> 20 21 <script> 22 import pubsub from 'pubsub-js' 23 24 export default { 25 name: "MyItem", 26 props:['todo'], 27 methods:{ 28 handleCheck(id){ 29 // this.checkTodo(id) 30 this.$bus.$emit('checkTodo',id) 31 }, 32 33 handleDelete(id){ 34 if (confirm('确认删除吗?')){ 35 // this.deleteTodo(id) 36 // this.$bus.$emit('deleteTodo',id) 37 pubsub.publish('deleteTodo',id) 38 } 39 }, 40 41 handleEdit(todo){ 42 if (todo.hasOwnProperty.call('isEdit')){ 43 todo.isEdit = true 44 }else { 45 this.$set(todo,'isEdit',true) 46 } 47 // 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行 48 this.$nextTick(function () { 49 this.$refs.inputTitle.focus() 50 }) 51 }, 52 //失去焦点回调(修改) 53 handleBlur(todo,e){ 54 todo.isEdit = false 55 if (!e.target.value.trim()) return alert("输入不能为空") 56 this.$bus.$emit('updateTodo',todo.id,e.target.value) 57 } 58 } 59 } 60 </script> 61 62 <style scoped> 63 /*item*/ 64 li { 65 list-style: none; 66 height: 36px; 67 line-height: 36px; 68 padding: 0 5px; 69 border-bottom: 1px solid #ddd; 70 } 71 72 li label { 73 float: left; 74 cursor: pointer; 75 } 76 77 li label li input { 78 vertical-align: middle; 79 margin-right: 6px; 80 position: relative; 81 top: -1px; 82 } 83 84 li button { 85 float: right; 86 display: none; 87 margin-top: 3px; 88 } 89 90 li:before { 91 content: initial; 92 } 93 94 li:last-child { 95 border-bottom: none; 96 } 97 98 li:hover{ 99 background-color: #ddd; 100 } 101 102 li:hover button{ 103 display: block; 104 } 105 </style>
1. 作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。 2. 写法: 1. 准备好样式: - 元素进入的样式: 1. v-enter:进入的起点 2. v-enter-active:进入过程中 3. v-enter-to:进入的终点 - 元素离开的样式: 1. v-leave:离开的起点 2. v-leave-active:离开过程中 3. v-leave-to:离开的终点 2. 使用<transition>包裹要过度的元素,并配置name属性: <transition name="hello"> <h1 v-show="isShow">你好啊!</h1> </transition> 3. 备注:若有多个元素需要过度,则需要使用:<transition-group>,且每个元素都要指定key值。

1 <template> 2 <div> 3 <Test/> 4 <Test2/> 5 <Test3/> 6 </div> 7 </template> 8 9 <script> 10 import Test from './components/Test' 11 import Test2 from './components/Test2' 12 import Test3 from './components/Test3' 13 14 export default { 15 name:'App', 16 components:{Test,Test2,Test3}, 17 } 18 </script>

1 <template> 2 <div> 3 <button @click="isShow = !isShow">显示/隐藏</button> 4 <transition name="hello" appear> 5 <h1 v-show="isShow">你好啊!</h1> 6 </transition> 7 </div> 8 </template> 9 10 <script> 11 export default { 12 name:'Test', 13 data() { 14 return { 15 isShow:true 16 } 17 }, 18 } 19 </script> 20 21 <style scoped> 22 h1{ 23 background-color: orange; 24 } 25 26 .hello-enter-active{ 27 animation: at 0.5s linear; 28 } 29 30 .hello-leave-active{ 31 animation: at 0.5s linear reverse; 32 } 33 34 @keyframes at { 35 from{ 36 transform: translateX(-100%); 37 } 38 to{ 39 transform: translateX(0px); 40 } 41 } 42 </style>

1 <template> 2 <div> 3 <button @click="isShow = !isShow">显示/隐藏</button> 4 <transition-group name="hello" appear> 5 <h1 v-show="!isShow" key="1">你好啊!</h1> 6 <h1 v-show="isShow" key="2">京东!</h1> 7 </transition-group> 8 </div> 9 </template> 10 11 <script> 12 export default { 13 name:'Test', 14 data() { 15 return { 16 isShow:true 17 } 18 }, 19 } 20 </script> 21 22 <style scoped> 23 h1{ 24 background-color: orange; 25 } 26 /* 进入的起点、离开的终点 */ 27 .hello-enter,.hello-leave-to{ 28 transform: translateX(-100%); 29 } 30 .hello-enter-active,.hello-leave-active{ 31 transition: 0.5s linear; 32 } 33 /* 进入的终点、离开的起点 */ 34 .hello-enter-to,.hello-leave{ 35 transform: translateX(0); 36 } 37 38 </style>

1 <template> 2 <div> 3 <button @click="isShow = !isShow">显示/隐藏</button> 4 <transition-group 5 appear 6 name="animate__animated animate__bounce" 7 enter-active-class="animate__swing" 8 leave-active-class="animate__backOutUp" 9 > 10 <h1 v-show="!isShow" key="1">你好啊!</h1> 11 <h1 v-show="isShow" key="2">京东!</h1> 12 </transition-group> 13 </div> 14 </template> 15 16 <script> 17 import 'animate.css' 18 export default { 19 name:'Test', 20 data() { 21 return { 22 isShow:true 23 } 24 }, 25 } 26 </script> 27 28 <style scoped> 29 h1{ 30 background-color: orange; 31 } 32 33 34 </style>
TodoList_动画

1 <template> 2 <transition name="todo" appear> 3 <li> 4 <label> 5 <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/> 6 <!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了props --> 7 <!-- <input type="checkbox" v-model="todo.done"/> --> 8 <span v-show="!todo.isEdit">{{ todo.title }}</span> 9 <input 10 type="text" 11 v-show="todo.isEdit" 12 :value="todo.title" 13 @blur="handleBlur(todo,$event)" 14 ref="inputTitle" 15 > 16 </label> 17 <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button> 18 <button v-show="!todo.isEdit" class="btn btn-edit" @click="handleEdit(todo)">编辑</button> 19 </li> 20 </transition> 21 </template> 22 23 <script> 24 import pubsub from 'pubsub-js' 25 26 export default { 27 name: "MyItem", 28 props:['todo'], 29 methods:{ 30 handleCheck(id){ 31 // this.checkTodo(id) 32 this.$bus.$emit('checkTodo',id) 33 }, 34 35 handleDelete(id){ 36 if (confirm('确认删除吗?')){ 37 // this.deleteTodo(id) 38 // this.$bus.$emit('deleteTodo',id) 39 pubsub.publish('deleteTodo',id) 40 } 41 }, 42 43 handleEdit(todo){ 44 if (todo.hasOwnProperty.call('isEdit')){ 45 todo.isEdit = true 46 }else { 47 this.$set(todo,'isEdit',true) 48 } 49 // 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行 50 this.$nextTick(function () { 51 this.$refs.inputTitle.focus() 52 }) 53 }, 54 //失去焦点回调(修改) 55 handleBlur(todo,e){ 56 todo.isEdit = false 57 if (!e.target.value.trim()) return alert("输入不能为空") 58 this.$bus.$emit('updateTodo',todo.id,e.target.value) 59 } 60 } 61 } 62 </script> 63 64 <style scoped> 65 /*item*/ 66 li { 67 list-style: none; 68 height: 36px; 69 line-height: 36px; 70 padding: 0 5px; 71 border-bottom: 1px solid #ddd; 72 } 73 74 li label { 75 float: left; 76 cursor: pointer; 77 } 78 79 li label li input { 80 vertical-align: middle; 81 margin-right: 6px; 82 position: relative; 83 top: -1px; 84 } 85 86 li button { 87 float: right; 88 display: none; 89 margin-top: 3px; 90 } 91 92 li:before { 93 content: initial; 94 } 95 96 li:last-child { 97 border-bottom: none; 98 } 99 100 li:hover{ 101 background-color: #ddd; 102 } 103 104 li:hover button{ 105 display: block; 106 } 107 108 .todo-enter-active{ 109 animation: at 0.5s linear; 110 } 111 112 .todo-leave-active{ 113 animation: at 0.5s linear reverse; 114 } 115 116 @keyframes at { 117 from{ 118 transform: translateX(100%); 119 } 120 to{ 121 transform: translateX(0px); 122 } 123 } 124 </style>
13、vue脚手架配置代理
方法一:
在vue.config.js中添加如下配置:
devServer:{
proxy:"http://localhost:5000"
}
说明:
1. 优点:配置简单,请求资源时直接发给前端(8080)即可。
2. 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
3. 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)
方法二:
编写vue.config.js配置具体代理规则:
module.exports = {
devServer: {
proxy: {
'/api1': {// 匹配所有以 '/api1'开头的请求路径
target: 'http://localhost:5000',// 代理目标的基础路径
changeOrigin: true,
pathRewrite: {'^/api1': ''}
},
'/api2': {// 匹配所有以 '/api2'开头的请求路径
target: 'http://localhost:5001',// 代理目标的基础路径
changeOrigin: true,
pathRewrite: {'^/api2': ''}
}
}
}
}
/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
changeOrigin默认值为true
*/
说明:
1. 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
2. 缺点:配置略微繁琐,请求资源时必须加前缀。

1 const { defineConfig } = require('@vue/cli-service') 2 module.exports = defineConfig({ 3 transpileDependencies: true, 4 lintOnSave:false, 5 // 开启代理服务器(方式一) 6 // devServer:{ 7 // proxy:'http://localhost:5000' 8 // }, 9 // 开启代理服务器(方式二) 10 devServer:{ 11 proxy:{ 12 '/at':{ 13 target:'http://localhost:5000', 14 pathRewrite:{'^/at':''}, 15 ws:true, 16 changeOrigin:true 17 }, 18 '/demo':{ 19 target:'http://localhost:5001', 20 pathRewrite:{'^/demo':''}, 21 ws:true, 22 changeOrigin:true 23 } 24 } 25 } 26 })

1 <template> 2 <div> 3 <button @click="getStudents">获取学生信息</button> 4 <button @click="getCars">获取汽车信息</button> 5 </div> 6 </template> 7 8 <script> 9 import axios from 'axios' 10 export default { 11 name:'App', 12 methods: { 13 getStudents(){ 14 axios.get('http://localhost:8080/at/students').then( 15 response => { 16 console.log('请求成功了',response.data) 17 }, 18 error => { 19 console.log('请求失败了',error.message) 20 } 21 ) 22 }, 23 getCars(){ 24 axios.get('http://localhost:8080/demo/cars').then( 25 response => { 26 console.log('请求成功了',response.data) 27 }, 28 error => { 29 console.log('请求失败了',error.message) 30 } 31 ) 32 } 33 }, 34 } 35 </script>
github搜索案例

1 <template> 2 <div class="container"> 3 <Search></Search> 4 <List></List> 5 </div> 6 </template> 7 8 <script> 9 import Search from "./components/Search" 10 import List from "./components/List" 11 12 export default { 13 name: 'App', 14 components: { 15 Search, 16 List 17 } 18 } 19 </script>

1 <template> 2 <section class="jumbotron"> 3 <h3 class="jumbotron-heading">Search Github Users</h3> 4 <div> 5 <input type="text" placeholder="enter the name you search" v-model="keyWord"/> 6 <button @click="searchUsers">Search</button> 7 </div> 8 </section> 9 </template> 10 11 <script> 12 import axios from 'axios' 13 14 export default { 15 name: "Search", 16 data(){ 17 return { 18 keyWord:'' 19 } 20 }, 21 methods:{ 22 searchUsers(){ 23 //请求前更新List的数据 24 this.$bus.$emit('updateListData',{isFirst:false,isLoading:true,errMsg:'',users:[]}) 25 axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then( 26 response => { 27 // console.log('请求成功了',response.data.items) 28 //请求成功后更新List的数据 29 this.$bus.$emit('updateListData',{isLoading:false,errMsg:'',users:response.data.items}) 30 }, 31 error => { 32 //请求后更新List的数据 33 this.$bus.$emit('updateListData',{isLoading:false,errMsg:error.message,users:[]}) 34 } 35 ) 36 } 37 } 38 } 39 </script>

1 <template> 2 <div class="row"> 3 <!-- 展示用户列表 --> 4 <div v-show="info.users.length" class="card" v-for="user in info.users" :key="user.login"> 5 <a :href="user.html_url" target="_blank"> 6 <img :src="user.avatar_url" style='width: 100px'/> 7 </a> 8 <p class="card-text">{{user.login}}</p> 9 </div> 10 <!-- 展示欢迎词 --> 11 <h1 v-show="info.isFirst">欢迎使用!</h1> 12 <!-- 展示加载中 --> 13 <h1 v-show="info.isLoading">加载中....</h1> 14 <!-- 展示错误信息 --> 15 <h1 v-show="info.errMsg">{{info.errMsg}}</h1> 16 </div> 17 </template> 18 19 <script> 20 export default { 21 name: "List", 22 data(){ 23 return { 24 info:{ 25 isFirst:true, 26 isLoading:false, 27 errMsg:'', 28 users:[] 29 } 30 } 31 }, 32 mounted() { 33 this.$bus.$on('updateListData',(dataObj)=>{ 34 // console.log(users) 35 this.info = {...this.info,...dataObj} // 合并(防止isFirst数据丢失) 36 }) 37 } 38 39 } 40 </script> 41 42 <style scoped> 43 .album { 44 min-height: 50rem; /* Can be removed; just added for demo purposes */ 45 padding-top: 3rem; 46 padding-bottom: 3rem; 47 background-color: #f7f7f7; 48 } 49 50 .card { 51 float: left; 52 width: 33.333%; 53 padding: .75rem; 54 margin-bottom: 2rem; 55 border: 1px solid #efefef; 56 text-align: center; 57 } 58 59 .card > img { 60 margin-bottom: .75rem; 61 border-radius: 100px; 62 } 63 64 .card-text { 65 font-size: 85%; 66 } 67 </style>
github搜索案例_vue-resource

1 //引入Vue 2 import Vue from 'vue' 3 //引入App 4 import App from './App.vue' 5 //引入插件 6 import vueResource from 'vue-resource' 7 //关闭Vue的生产提示 8 Vue.config.productionTip = false 9 //使用插件 10 Vue.use(vueResource) 11 12 //创建vm 13 new Vue({ 14 el:'#app', 15 render: h => h(App), 16 beforeCreate() { 17 Vue.prototype.$bus = this 18 }, 19 })

1 <template> 2 <div class="container"> 3 <Search/> 4 <List/> 5 </div> 6 </template> 7 8 <script> 9 import Search from './components/Search' 10 import List from './components/List' 11 export default { 12 name:'App', 13 components:{Search,List} 14 } 15 </script>

1 <template> 2 <section class="jumbotron"> 3 <h3 class="jumbotron-heading">Search Github Users</h3> 4 <div> 5 <input type="text" placeholder="enter the name you search" v-model="keyWord"/> 6 <button @click="searchUsers">Search</button> 7 </div> 8 </section> 9 </template> 10 11 <script> 12 export default { 13 name:'Search', 14 data() { 15 return { 16 keyWord:'' 17 } 18 }, 19 methods: { 20 searchUsers(){ 21 //请求前更新List的数据 22 this.$bus.$emit('updateListData',{isLoading:true,errMsg:'',users:[],isFirst:false}) 23 this.$http.get(`https://api.github.com/search/users?q=${this.keyWord}`).then( 24 response => { 25 console.log('请求成功了') 26 //请求成功后更新List的数据 27 this.$bus.$emit('updateListData',{isLoading:false,errMsg:'',users:response.data.items}) 28 }, 29 error => { 30 //请求后更新List的数据 31 this.$bus.$emit('updateListData',{isLoading:false,errMsg:error.message,users:[]}) 32 } 33 ) 34 } 35 }, 36 } 37 </script>

1 <template> 2 <div class="row"> 3 <!-- 展示用户列表 --> 4 <div v-show="info.users.length" class="card" v-for="user in info.users" :key="user.login"> 5 <a :href="user.html_url" target="_blank"> 6 <img :src="user.avatar_url" style='width: 100px'/> 7 </a> 8 <p class="card-text">{{user.login}}</p> 9 </div> 10 <!-- 展示欢迎词 --> 11 <h1 v-show="info.isFirst">欢迎使用!</h1> 12 <!-- 展示加载中 --> 13 <h1 v-show="info.isLoading">加载中....</h1> 14 <!-- 展示错误信息 --> 15 <h1 v-show="info.errMsg">{{info.errMsg}}</h1> 16 </div> 17 </template> 18 19 <script> 20 export default { 21 name:'List', 22 data() { 23 return { 24 info:{ 25 isFirst:true, 26 isLoading:false, 27 errMsg:'', 28 users:[] 29 } 30 } 31 }, 32 mounted() { 33 this.$bus.$on('updateListData',(dataObj)=>{ 34 this.info = {...this.info,...dataObj} 35 }) 36 }, 37 } 38 </script> 39 40 <style scoped> 41 .album { 42 min-height: 50rem; /* Can be removed; just added for demo purposes */ 43 padding-top: 3rem; 44 padding-bottom: 3rem; 45 background-color: #f7f7f7; 46 } 47 48 .card { 49 float: left; 50 width: 33.333%; 51 padding: .75rem; 52 margin-bottom: 2rem; 53 border: 1px solid #efefef; 54 text-align: center; 55 } 56 57 .card > img { 58 margin-bottom: .75rem; 59 border-radius: 100px; 60 } 61 62 .card-text { 63 font-size: 85%; 64 } 65 </style>
14、插槽
1. 作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于父组件 ===> 子组件。 2. 分类:默认插槽、具名插槽、作用域插槽 3. 使用方式: 1. 默认插槽: 父组件中: <Category> <div>html结构1</div> </Category> 子组件中: <template> <div> <!-- 定义插槽 --> <slot>插槽默认内容...</slot> </div> </template> 2. 具名插槽: 父组件中: <Category> <template slot="center"> <div>html结构1</div> </template> <template v-slot:footer> <div>html结构2</div> </template> </Category> 子组件中: <template> <div> <!-- 定义插槽 --> <slot name="center">插槽默认内容...</slot> <slot name="footer">插槽默认内容...</slot> </div> </template> 3. 作用域插槽: 1. 理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定) 2. 具体编码: 父组件中: <Category> <template scope="scopeData"> <!-- 生成的是ul列表 --> <ul> <li v-for="g in scopeData.games" :key="g">{{g}}</li> </ul> </template> </Category> <Category> <template slot-scope="scopeData"> <!-- 生成的是h4标题 --> <h4 v-for="g in scopeData.games" :key="g">{{g}}</h4> </template> </Category> 子组件中: <template> <div> <slot :games="games"></slot> </div> </template> <script> export default { name:'Category', props:['title'], //数据在子组件自身 data() { return { games:['红色警戒','穿越火线','劲舞团','超级玛丽'] } }, } </script>
默认插槽

1 <template> 2 <div class="container"> 3 <Category title="美食" > 4 <img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt=""> 5 </Category> 6 7 <Category title="游戏" > 8 <ul> 9 <li v-for="(g,index) in games" :key="index">{{g}}</li> 10 </ul> 11 </Category> 12 13 <Category title="电影"> 14 <video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video> 15 </Category> 16 </div> 17 </template> 18 19 <script> 20 import Category from "./components/Category" 21 22 export default { 23 name: 'App', 24 components: { 25 Category, 26 }, 27 data() { 28 return { 29 foods:['火锅','烧烤','小龙虾','牛排'], 30 games:['红色警戒','穿越火线','劲舞团','超级玛丽'], 31 films:['《教父》','《拆弹专家》','《你好,李焕英》','《尚硅谷》'] 32 } 33 }, 34 } 35 </script> 36 37 <style scoped> 38 .container{ 39 display: flex; 40 justify-content: space-around; 41 } 42 video{ 43 width: 100%; 44 } 45 </style>

1 <template> 2 <div class="category"> 3 <h3>{{title}}分类</h3> 4 <!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) --> 5 <slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot> 6 </div> 7 </template> 8 9 <script> 10 export default { 11 name: "Category", 12 props:['title'] 13 } 14 </script> 15 16 <style scoped> 17 .category{ 18 background-color: skyblue; 19 width: 200px; 20 height: 300px; 21 } 22 h3{ 23 text-align: center; 24 background-color: orange; 25 } 26 video{ 27 width: 100%; 28 } 29 img{ 30 width: 100%; 31 } 32 </style>
具名插槽

1 <template> 2 <div class="container"> 3 <Category title="美食" > 4 <img slot="center" src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt=""> 5 <a class="foot" slot="footer" href="http://baidu.com">更多美食</a> 6 </Category> 7 8 <Category title="游戏" > 9 <ul slot="center"> 10 <li v-for="(g,index) in games" :key="index">{{g}}</li> 11 </ul> 12 <div class="foot" slot="footer"> 13 <a href="http://baidu.com">单机游戏</a> 14 <a href="http://baidu.com">网络游戏</a> 15 </div> 16 </Category> 17 18 <Category title="电影"> 19 <video slot="center" controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video> 20 <template v-slot:footer> 21 <div class="foot"> 22 <a href="http://baidu.com">经典</a> 23 <a href="http://baidu.com">热门</a> 24 <a href="http://baidu.com">推荐</a> 25 </div> 26 <h4>欢迎前来观影</h4> 27 </template> 28 </Category> 29 </div> 30 </template> 31 32 <script> 33 import Category from "./components/Category" 34 35 export default { 36 name: 'App', 37 components: { 38 Category, 39 }, 40 data() { 41 return { 42 foods:['火锅','烧烤','小龙虾','牛排'], 43 games:['红色警戒','穿越火线','劲舞团','超级玛丽'], 44 films:['《教父》','《拆弹专家》','《你好,李焕英》','《尚硅谷》'] 45 } 46 }, 47 } 48 </script> 49 50 <style scoped> 51 .container,.foot{ 52 display: flex; 53 justify-content: space-around; 54 } 55 video{ 56 width: 100%; 57 } 58 h4{ 59 text-align: center; 60 } 61 </style>

1 <template> 2 <div class="category"> 3 <h3>{{title}}分类</h3> 4 <!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) --> 5 <slot name="center">我是一些默认值,当使用者没有传递具体结构时,我会出现1</slot> 6 <slot name="footer">我是一些默认值,当使用者没有传递具体结构时,我会出现2</slot> 7 </div> 8 </template> 9 10 <script> 11 export default { 12 name: "Category", 13 props:['title'] 14 } 15 </script> 16 17 <style scoped> 18 .category{ 19 background-color: skyblue; 20 width: 200px; 21 height: 300px; 22 } 23 h3{ 24 text-align: center; 25 background-color: orange; 26 } 27 video{ 28 width: 100%; 29 } 30 img{ 31 width: 100%; 32 } 33 </style>
作用域插槽

1 <template> 2 <div class="container"> 3 4 <Category title="游戏"> 5 <template scope="at"> 6 <ul> 7 <li v-for="(g,index) in at.games" :key="index">{{ g }}</li> 8 </ul> 9 </template> 10 </Category> 11 12 <Category title="游戏"> 13 <template scope="{games}"> 14 <ol> 15 <li style="color:red" v-for="(g,index) in games" :key="index">{{ g }}</li> 16 </ol> 17 </template> 18 </Category> 19 20 <Category title="游戏"> 21 <template slot-scope="{games}"> 22 <h4 v-for="(g,index) in games" :key="index">{{ g }}</h4> 23 </template> 24 </Category> 25 26 </div> 27 </template> 28 29 <script> 30 import Category from './components/Category' 31 32 export default { 33 name: 'App', 34 components: {Category}, 35 } 36 </script> 37 38 <style scoped> 39 .container, .foot { 40 display: flex; 41 justify-content: space-around; 42 } 43 44 h4 { 45 text-align: center; 46 } 47 </style>

1 <template> 2 <div class="category"> 3 <h3>{{title}}分类</h3> 4 <slot :games="games" msg="hello">我是默认的一些内容</slot> 5 </div> 6 </template> 7 8 <script> 9 export default { 10 name:'Category', 11 props:['title'], 12 data() { 13 return { 14 games:['红色警戒','穿越火线','劲舞团','超级玛丽'], 15 } 16 }, 17 } 18 </script> 19 20 <style scoped> 21 .category{ 22 background-color: skyblue; 23 width: 200px; 24 height: 300px; 25 } 26 h3{ 27 text-align: center; 28 background-color: orange; 29 } 30 video{ 31 width: 100%; 32 } 33 img{ 34 width: 100%; 35 } 36 </style>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?