随笔 - 81  文章 - 0  评论 - 0  阅读 - 7151 

脚手架文件结构

复制代码
├── 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

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、ref属性

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>
App.vue
复制代码
复制代码
 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>
School.vue
复制代码

2、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>
App.vue
复制代码
复制代码
<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>
Student.vue
复制代码

3、mixin(混入)

复制代码
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 })
main.js
复制代码
复制代码
 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 }
mixin.js
复制代码
复制代码
 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>
App.vue
复制代码
复制代码
 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>
Student.vue
复制代码
复制代码
 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>
School.vue
复制代码

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 })
main.js
复制代码
复制代码
 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 }
plugins.js
复制代码
复制代码
 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>
App.vue
复制代码
复制代码
 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>
Student.vue
复制代码
复制代码
 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>
School.vue
复制代码

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>
App.vue
复制代码
复制代码
 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>
Student.vue
复制代码
复制代码
 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>
School.vue
复制代码

6、TodoList案例

复制代码
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>
App.vue
复制代码
复制代码
 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>
MyHeader.vue
复制代码
复制代码
 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>
MyFooter.vue
复制代码
复制代码
 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>
MyList.vue
复制代码
复制代码
 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>
MyItem.vue
复制代码

7、webStorage

复制代码
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>
sessionStorage.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>
localStorage.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>
App.vue
复制代码

8、组件的自定义事件

复制代码
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>
App.vue
复制代码
复制代码
 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>
Student.vue
复制代码
复制代码
 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>
School.vue
复制代码

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>
App.vue
复制代码
复制代码
 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>
MyHeader.vue
复制代码
复制代码
 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>
MyFooter.vue
复制代码
复制代码
 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>
MyList.vue
复制代码
复制代码
 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>
MyItem.vue
复制代码

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 })
main.js
复制代码
复制代码
 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>
App.vue
复制代码
复制代码
 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>
Student.vue
复制代码
复制代码
 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>
School.vue
复制代码

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 })
main.js
复制代码
复制代码
  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>
App.vue
复制代码
复制代码
 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>
MyHeader.vue
复制代码
复制代码
 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>
MyFooter.vue
复制代码
复制代码
 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>
MyList.vue
复制代码
复制代码
 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>
MyItem.vue
复制代码

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>
App.vue
复制代码
复制代码
 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>
Student.vue
复制代码
复制代码
 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>
School.vue
复制代码

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>
App.vue
复制代码
复制代码
 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>
MyHeader.vue
复制代码
复制代码
 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>
MyFooter.vue
复制代码
复制代码
 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>
MyList.vue
复制代码
复制代码
 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>
MyItem.vue
复制代码

11、nextTick

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>
MyItem.vue
复制代码

12、过度与动画

复制代码
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>
App.vue
复制代码
复制代码
 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>
Test.vue
复制代码
复制代码
 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>
Test2.vue
复制代码
复制代码
 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>
Test3.vue
复制代码

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>
MyItem.vue
复制代码

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 })
vue.config.js
复制代码
复制代码
 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>
App.vue
复制代码

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>
App.vue
复制代码
复制代码
 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"/>&nbsp;
 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>
Search.vue
复制代码
复制代码
 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>
List.vue
复制代码

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 })
main.js
复制代码
复制代码
 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>
App.vue
复制代码
复制代码
 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"/>&nbsp;
 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>
Search.vue
复制代码
复制代码
 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>
List.vue
复制代码

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>
App.vue
复制代码
复制代码
 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>
Category.vue
复制代码

具名插槽

复制代码
 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>
App.vue
复制代码
复制代码
 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>
Category.vue
复制代码

作用域插槽

复制代码
 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>
App.vue
复制代码
复制代码
 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>
Category.vue
复制代码

 

posted on   晨曦生辉耀匕尖  阅读(23)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示