视频链接:尚硅谷Vue2.0+Vue3.0全套教程丨vuejs从入门到精通_哔哩哔哩_bilibili


P1-50:尚硅谷Vue2.0+Vue3.0全套教程视频笔记 + 代码 [P001-050]_小白桶子的博客-CSDN博客

P51-100:当前页面

P101-135:尚硅谷Vue2.0+Vue3.0全套教程视频笔记 + 代码 [P101-135]_小白桶子的博客-CSDN博客

P51-60:

- P51 - 生命周期_销毁流程

课堂笔记:

(1)vm.$destroy() 问题:

(官方文档说明如下图)

①为什么vm销毁了,页面上还有内容?

vm确实销毁了,但是vm销毁之前的工作成果还在。只不过vm销毁之后,并没有东西去管理页面上的内容了。

②官网中 “清理它与其它实例的连接” 这句话并没有问题。因为Vue的官方文档始终站在组件化编码的思维基础上,给予提示,而目前还没接触到组件,所以还无法印证。

③为什么destroy之后,add事件还能打印?

官网中所谓的 “移除了所有的事件监听器” ,这个事件指的是自定义事件,而不是原生的DOM事件。

(2)在beforeDestroy中,data、methods、指令等都处于可用状态。虽然能访问到数据,也能调用到方法,但是所有对数据的修改(如本节中的 add )不会再触发更新了。

本节代码(直接复制到空白html页面即可使用):

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title></title><script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.1/vue.js"></script>
</head>
<body><div id="root"><h2 v-text="n"></h2><h2> 当前的n值是:{{n}}</h2><button @click="add">点我n+1</button><button @click="bye">点我销毁vm</button></div><script type="text/javascript">Vue.config.productionTip = falseconst vm = new Vue({el:'#root',// template:`// <div>//     <h2> 当前的n值是:{{n}}</h2>//     <button @click="add">点我n+1</button> // </div>// `,data:{n:1},methods: {add(){console.log('add')this.n ++},bye(){console.log('bye')this.$destroy()}},watch: {n(){console.log('n变了')}},beforeCreate() {console.log('beforeCreate')},created() {console.log('created')},beforeMount() {console.log('beforeMount')},mounted() {console.log('mounted')},beforeUpdate() {console.log('beforeUpdate')},updated() {console.log('updated')},beforeDestroy() {console.log('beforeDestroy')},destroyed() {console.log('destroyed')},})</script>
</body>
</html>

- P52 - 生命周期_总结

课堂笔记:

(1)生命周期类比图

(2)如果多个地方需要用到同个东西(如本节中,mounted和methods中都需要用到定时器),可以直接用 this.xxx 。

老师总结:

常用的生命周期钩子:

1.mounted:发送Ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。

2.beforeDestroy:清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。

关于销毁Vue实例

1.销毁后借助Vue开发者工具看不到任何信息。

2.销毁后自定义事件会失效,但原生DOM事件依然有效。

3.一般不会再beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。

本节代码(直接复制到空白html页面即可使用):

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title></title><script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.1/vue.js"></script>
</head>
<body><div id="root"><h2 :style="{opacity}">欢迎学习Vue</h2><button @click="opacity = 1">透明度设置为1</button><button @click="stop">点我停止变换</button></div><script type="text/javascript">Vue.config.productionTip = falseconst vm = new Vue({el:'#root',data:{opacity:1},methods: {stop(){this.$destroy()}},// Vue完成模板的解析并把真实的DOM元素放入页面后(挂载完毕)调用mountedmounted(){console.log('mounted')this.timer = setInterval(() => {console.log('setInterval')this.opacity -= 0.01if(this.opacity <= 0) this.opacity = 1},16) },beforeDestroy() {console.log('vm即将驾鹤西游了')clearInterval(this.timer)},})</script>
</body>
</html>

- P53 - 对组件的理解

课堂笔记:

(1)老师的PPT图:

老师总结:

模块与组件、模块化与组件化:

1.模块:

(1)理解:向外提供特定功能的js程序,一般就是一个js文件

(2)为什么:js文件很多很复杂

(3)作用:复用js,简化js的编写,提高js运行效率

2.组件:

(1)理解:用来实现局部(特定)功能效果的代码集合(html/css/js/image...)

(2)为什么:一个界面的功能很复杂

(3)作用:复用编码,简化项目编码,提高运行效率

3.模块化:

当应用中的js都以模块来编写,那这个应用就是一个模块化的应用。

4.组件化:

当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用。

- P54 - 非单文件组件

老师总结:

Vue中使用组件的三大步骤:

一、定义组件(创建组件)

二、注册组件

三、使用组件(写组件标签)

一、如何定义一个组件?

使用Vue.extend(options)创建,其中 options 和 new Vue(options)时传入的那个options几乎一样,但区别如下:

1.el不要写,为什么?——最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。

2.data必须写成函数,为什么?——避免组件被复用时,数据存在引用关系。

备注:使用template可以配置组件结构。

二、如何注册组件?

1.局部注册:靠 new Vue的时候传入components选项

2.全局注册:靠Vue.component('组件名',组件)

三、编写组件标签:

<school></school>

本节代码(直接复制到空白html页面即可使用):

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title></title><script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.1/vue.js"></script>
</head>
<body><div id="root"><hello></hello><hr><h1>{{msg}}</h1><hr><!-- 第三步:编写组件标签 --><school></school><hr><!-- 第三步:编写组件标签 --><student></student></div><div id="root2"><hello></hello></div><script type="text/javascript">Vue.config.productionTip = false// 第一步:创建school组件const school = Vue.extend({// el:'#root', //组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器。template:`<div><h2>学校名称:{{shcoolName}}</h2><h2>学校地址:{{address}}</h2><button @click="showName">点我提示学校名</button></div>`,data(){return{shcoolName:'尚硅谷',address:'北京昌平'}},methods: {showName(){alert(this.shcoolName)}},})// 第一步:创建student组件const student = Vue.extend({template:`<div><h2>学生姓名:{{studentName}}</h2><h2>学生年龄:{{age}}</h2></div>`,data(){return{studentName:'张三',age:18}}})// 第一步:创建hello组件const hello = Vue.extend({template:`<div><h2>你好啊!{{name}}</h2></div>`,data(){return{name:'Tom'}}})// 第二步:全局注册组件Vue.component('hello',hello)// 创建vmnew Vue({el:'#root',data:{msg:'你好啊!'},// 第二步:注册组件(局部注册)components: {school,student}})new Vue({el:'#root2'})</script>
</body>
</html>

- P55 - 组件的几个注意点

课堂笔记:

(1)本节中,有个简写方式。为什么简写成对象了,Vue依旧能访问到?

不是说简写中没有调用 Vue.extend ,最终就没有调用。而是在Vue底层(components)中写了判断,如果传入的值是对象(如本节的 s ),那么在components中会帮你执行 Vue.extend 。也就是说,写与不写都行,Vue最终会有判断,写了Vue就不管,没写会补上。

老师总结:

几个注意点:

1.关于组件名:

一个单词组成:

第一种写法(首字母小写):school

第二种写法(首字母大写):School

多个单词组成:

第一种写法(kebab-case命名):my-school

第二种写法(CamelCase命名):MySchool(需要Vue脚手架支持)

备注:

(1)组件名尽可能回避HTML中已有的9元素名称,例如:h2、H2都不行。

(2)可以使用name配置项指定组件在开发者工具中呈现的名字。

2.关于组件标签:

第一种写法:<school></school>

第二种写法:<school/>

备注:不适用脚手架时,<school/>会导致后续组件不能渲染。

3.一个简写方式:

const school = Vue.extend(options) 可简写为:const school = options

本节代码(直接复制到空白html页面即可使用):

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title></title><script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.1/vue.js"></script>
</head>
<body><div id="root"><h1>{{msg}}</h1><school></school><!-- <school/> --></div><script type="text/javascript">Vue.config.productionTip = false// 定义组件const s = {name:'atguigu',template:`<div><h2>学校名称:{{name}}</h2><h2>学校地址:{{address}}</h2></div>`,data(){return{name:'尚硅谷',address:'北京'}}}new Vue({el:'#root',data:{msg:'欢迎学习Vue!'},components:{// 'my-school':sschool:s}})</script>
</body>
</html>

- P56 - 组件的嵌套

本节代码(直接复制到空白html页面即可使用):

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title></title><script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.1/vue.js"></script>
</head>
<body><div id="root"></div><script type="text/javascript">Vue.config.productionTip = false// 定义student组件const student  = Vue.extend({name:'student',template:`<div><h2>学生姓名:{{name}}</h2><h2>学生年龄:{{age}}</h2></div>`,data(){return{name:'尚硅谷',age:18}}})// 定义school组件const school  = Vue.extend({name:'school',template:`<div><h2>学校名称:{{name}}</h2><h2>学校地址:{{address}}</h2><student></student></div>`,data(){return{name:'尚硅谷',address:'北京'}},// 注册组件(局部)components:{student}})// 定义hello组件const hello = Vue.extend({template:`<h1>{{msg}}</h1>`,data(){return{msg:'欢迎来到尚硅谷学习!'}}})// 定义app组件const app = Vue.extend({template:`<div><hello></hello><school></school></div>`,components:{school,hello}})// 创建vmnew Vue({template:`<app></app>`,el:'#root',// 注册组件(局部)components:{app}})</script>
</body>
</html>

- P57 - VueComponent构造函数

课堂笔记:

(1)关于老师说的,每次调用返回的都是新的VueComponent,弹幕中看到有人说的不错:

就是东西都是一样的,但由于你所给的参数不同,最终所拿到的东西也不同。

老师总结:

关于VueComponent:

1.school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.exend生成的。

2.我们只需要写或,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)。

3.特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!

4.关于this指向:

(1)组件配置中:

data函数、methods中的函数、watch中的函数、computed中的函数,他们的this均是【VueComponent实例对象】

(2)new Vue()配置中:

data函数、methods中的函数、watch中的函数、computed中的函数,他们的this均是【Vue实例对象】

5.VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。

Vue的实例对象,以后简称vm。

(这节代码跟上面两节差不多,就不放上来了。自己一定要去验证、写写看)

- P58 - Vue实例与组件实例

课堂笔记:

(1)既然vc和vm中的内容一样,为什么不能把vc和vm直接划上等号?

vc是由VueComponent缔造的,vm是由Vue缔造的。这两个个缔造的过程肯定不可能完全一样。

比如说在创建组件实例对象、传入配置项的时候,是不可以写el的(回顾可看P54),但是在创建vm的时候,配置对象中就可以写el。也就是说vm可以写自己为哪个容器服务,但是vc不能指定服务容器,只能跟着vm走。

再比如说,vc的数据配置项(data)就必须写成函数。

可以说,它们俩身上99%的东西一样,但是有这1%就是不一样。就像是两个双胞胎,长得一模一样,但是不能说他们是同个人。

打开官网——学习——教程——组件基础——基本实例中,也有说到:组件是可复用的Vue实例......因为组件是可复用的Vue实例,所以它们与new Vue接收相同的选项......

- P59 - 一个重要的内置关系

课堂笔记:

(1)本节学习中,需要有一定原型的基础。如果这里不太理解,可以去补一下原型和原型链的理解。可以看这个:js原型和原型链的理解(透彻)_lixiaonaaa的博客-CSDN博客_js原型和原型链的理解

(2)分析Vue与VueComponent的关系图。

(3)实例的隐式原型属性,永远指向自己缔造者的原型对象。

老师总结:

1.一个重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype

2.为什么要有这个关系:让组件实例对象(vc)可以访问到Vue原型上的属性、方法。

本节代码(直接复制到空白html页面即可使用):

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title></title><script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.1/vue.js"></script>
</head>
<body><div id="root"><school></school></div><script type="text/javascript">Vue.config.productionTip = falseVue.prototype.x = 99// 定义school组件const school  = Vue.extend({name:'school',template:`<div><h2>学校名称:{{name}}</h2><h2>学校地址:{{address}}</h2><button @click="showX">点我输出x</button></div>`,data(){return{name:'尚硅谷',address:'北京'}},methods: {showX(){console.log(this.x)}},})// 创建一个vmnew Vue({el:'#root',data:{msg:'你好'},components:{school}})// console.log(school.prototype.__proto__ === Vue.prototype)// 定义一个构造函数/* function Demo(){this.a = 1this.b = 2}// 创建一个Demo的实例对象const d = new Demo()console.log(Demo.prototype) //显示原型属性console.log(d.__proto__) //隐式原型属性console.log(Demo.prototype === d.__proto__)//程序员通过显示原型属性操作原型对象,追加一个x属性,值为99Demo.prototype.x = 99console.log('@',d.x) */</script>
</body>
</html>

- P60 - 单文件组件

课堂笔记:

(1)给 .vue 文件起名字的规则,和给组件起名的规则是一样的。

(2)老师安装的插件:Vetur,版本自己选择。

(3)如果ES6模块化、模块暴露不熟的同学,需要去补一下。可以看这个:es6中的模块化_我不是你不是我的博客-CSDN博客_es6模块化

(4)看到弹幕有一条说的不错,可以帮助理解结构:

main:领导,App:包工头,其他组件:工人。

(本节代码在不放入脚手架前都是无用的,目前老师只是展示一个简单的结构,所以这里不贴代码了。)

P61-70:

- P61 - 创建Vue脚手架

课堂笔记:

(1)CLI就是 command line interface 的缩写。Vue CLI官网:Vue CLI

(2)安装过程:

(PS:

①老师已经提前安装过node.js了,没有安装的可以打开这个:Download | Node.js

②已经安装的可以检查node.js和npm版本及安装情况:

检查nodejs : node -v

检查npm : npm -v (这两个指令均出现版本号为安装成功)

①配置npm淘宝镜像:npm config set registryhttps://registry.npm.taobao.org

(这一步可以让vue的安装更加快速,可有可无,但是建议还是安装一下)

②第一步(仅第一次执行),全局安装@vue/cli : npm install -g @vue/cli

③第二步,切换到你要创建项目的目录,然后使用命令创建项目 : vue create xxxx (xxxx为项目名,最好不要取vue、jQuery这种名字)

④第三步,启动项目 : npm run serve

- P62 - 分析脚手架结构

课堂笔记:

(1)目录结构:

┌── node_modules (依赖包)

|

├── public ──┬── favicon.ico(网站的页签图标)

|                      └── index.html(vue是单页面应用,这就是总的入口文件)

|

|                 ┌── assets(一般用来放静态资源)

├── src ──┼── components(所有组件都往这里放)

|                 ├── App.vue(大哥)

|                 └── main.js (非常重要!该文件是整个项目的入口文件)

|

├── .gitignore (git的配置文件)

├── babel.config.js (babel的控制文件,虽然很重要,但是和我们没啥关系,不需要动,主要工作于 ES6 => ES5,如果想要配置的话,可以参考babel官网)

├── package-lock.json (包版本控制文件)

├── package.json (只要打开的功能是符合npm规范的,那么就一定会有这个文件,类似于包的说明书。其中会配置包的名字(name)、版本(version)、采用的依赖(dependencies)等。还有常用的命令:serve开始的时候配置全部;build所有工程完成之后转换;lint语法检查,几乎不用)

└── README.md (对整个工程进行说明、描述)

(2)整个流程:

执行npm run serve,随后来到src中,找到main.js,这个页面中引入了Vue、App.vue、关闭了提示等。

继续找到app.vue页面,看到这个页面中引入了school和student,于是就到components文件夹中找到这两个并执行,执行最终汇总到了App.vue页面。

再回到main.js页面,把App组件放入容器中。

再找到index.html,把东西放到这个里面。

至此完成了整个运行流程。

(3)我自己这边报了一个错:

Component name "School" should always be multi-word

看到弹幕上很多人也有这个问题,这个是说组件名最好由多个单词组成,可以找到vue.config.js文件,在module.exports里面添加 lintOnSave:false (关闭语法检查)即可。

完整的可以看:关于Vue报错“Component name “School“ should always be multi-word”的解决方法_指尖世界 £的博客-CSDN博客

或者自己按照规范重新命名。

- P63 - render函数

课堂笔记:

(1)在main.js中引入的vue是残缺版的。

(2)理解render这行:

render(createElement){return createElement('h1','你好啊')
}
// ↑ 也就可以简写成 ↓
render:q => q('h1','你好啊')

(3)为什么不直接引入vue.js,反而要vue文件夹那么多东西?

vue中包含了两种东西,核心(比如说生命周期、处理事件等)和模板解析器。如果没有其它的文件,只把vue放到一个页面中,可能会出现问题。因为vue中有三分之一都是模板解析器,之后webpack打包完成后,会生成一个非常大的文件,这个时候vue的模板解析器就不适合出现在这里,没有作用。(vue文件中,带有runtime的都表示运行时的vue,带有common的就是走commonJS)

简而言之,没有了模板解析器的vue体积很小,打包之后能够更加轻量,代价就是写的时候要用那行render去写。

老师的比喻:

老师总结:

关于不同版本的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函数接收到的createrElement函数去指定具体内容。

- P64 - 修改默认配置

课堂笔记:

(1)Vue 脚手架隐藏了所有 webpack 相关的配置,即使不填写,也会有默认的配置。

(2)五个默认不能改的:public文件夹、favicon.ico、index.html、src文件夹、main.js

(3)如果还是要修改的话,打开Vue CLI官网——配置参考——左侧栏都是能改的。

(4)项目开发中需要关闭语法检查(具体的根据公司要求定),在 module.exports 里面添加 lintOnSave:false (详细的可以看62节我也有说)

(5)看md文件推荐使用Typroa。

老师总结:

1.脚手架文件结构:

(我在62节写了,老师这个会更精简一点,我写的那个包含了老师课上说的其它内容,如果要直接参考查看的话还是老师这个好,补充知识可以看我那个)

2.关于不同版本的Vue

(这个我也在63节写了,这里不弄上去了)

3. vue.config.js 配置文件

使用 vue inspect > output.js 可以查看到Vue脚手架的默认配置。

使用 vue.config.js 可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh

- P65 - ref属性

课堂笔记:

(1)对于传统的HTML而言,id和ref确实没有什么差别,但是对于组件来说就不一样了。

给组件加id,打印出获取的结果为组件所对应的完整DOM结构。

给组件加ref,打印出获取的结果就是VueComponent实例。

老师总结:

ref属性:

1.被用来给元素或子组件注册引用信息(id的代替者)

2.应用在html标签上获取的是真实的DOM元素,应用在组件标签上是组件实例对象(vc)

3.使用方式:

打标识:<h1 ref="xxx">  ... </h1>

获取: this.$refs.xxx

本节部分代码:

main.js页面:

// 引入Vue
import Vue from 'vue'
// 引入App
import App from './App.vue'
// 关闭Vue的生产提示
// Vue.config.productionTip = false// 创建vm
new Vue({el:'#app',render: h => h(App),
})

App.vue页面:

<template><div><h1 v-text="msg" ref="title"></h1><button ref="btn" @click="showDOM">点我输出上方的DOM元素</button><School ref="sch"/></div>
</template><script>
// 引入School组件
import School from './components/School.vue'
export default {name: "App",components: { School },data() {return {msg:'欢迎学习Vue!'}},methods: {showDOM(){console.log(this.$refs.title) //真实DOM元素console.log(this.$refs.btn) //真实DOM元素console.log(this.$refs.sch) //School组件的实例对象(vc)}},
}
</script>

- P66 - props配置

老师总结:

配置项propos

功能:让组件接收外部传过来的数据

(1)传递数据:

<Demo name="xxx"/>

(2)接收数据:

第一种方式(只接收):

props:['name']

第二种方式(限制类型):

props:{

name:Number

}

第三种方式(限制类型、限制必要性、指定默认值):

props:{

name:{

type:String, // 类型

required:true, // 必要性

default:'老王' // 默认值

}

}

备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。

本节部分代码:

App.vue页面:

<template><div><Student name="李四" sex="女" :age="18"/></div>
</template><script>
import Student from './components/Student.vue'
export default {name: "App",components: { Student },
}
</script>

Student.vue页面:

<template><div><h1>{{msg}}</h1><h2>学生姓名:{{name}}</h2><h2>学生性别:{{sex}}</h2><h2>学生年龄:{{myAge + 1}}</h2><button @click="updateAge">尝试修改收到的年龄</button></div>
</template><script>
export default {name:'Student',data() {return {msg:'我是一个尚硅谷的学生',myAge:this.age}},methods: {updateAge(){this.myAge++}},// 简单声明接收props:['name','sex','age'] // 接收的同时对数据进行类型限制/* props:{name:String,age:Number,sex:String} */// 接收的同时对数据进行类型限制 + 默认值的指定 + 必要性的限制/* props:{name:{ type:String, // name的类型是字符串required:true // name是必要的},age:{type:Number,default:99 // 默认值},sex:{ type:String, required:true },} */
}
</script>

- P67 - mixin混入

课堂笔记:

(1)mixin.js页面中用了ES6模块化的分别暴露,可以看下这个:es6中的模块化_我不是你不是我的博客-CSDN博客_es6模块化

(2)mixin中的data数据会与页面中的data数据进行整合,如若有冲突的话,则以页面为准。

(3)mixin和页面中的生命周期钩子不以任何为主,都要。

老师总结:

mixin(混入)

功能:可以把多个组件共用的配置提取成一个混入对象

使用方式:

第一步定义混合,例如:

{

data(){ ... }

methods:{ ... }

...

}

第二步使用混入,例如:

(1)全局混入:Vue.mixin(xxx)

(2)局部混入:mixins:['xxx']

本节部分代码:

mixin.js页面:

export const mixin = {methods: {showName(){alert(this.name)}},mounted() {console.log('你好啊!')},
}export const mixin2 = {data(){return{x:100,y:200}}
}

main.js页面:

// 引入Vue
import Vue from 'vue'
// 引入App
import App from './App.vue'
import {mixin,mixin2} from './mixin'
// 关闭Vue的生产提示
Vue.config.productionTip = falseVue.mixin(mixin)
Vue.mixin(mixin2)// 创建vm
new Vue({el:'#app',render: h => h(App),
})

Student.vue页面:

<template><div><h2 @click="showName">学生姓名:{{name}}</h2><h2>学生性别:{{sex}}</h2></div>
</template><script>
// 引入了一个混合
import {mixin,mixin2} from '../mixin'
export default {name:'Student',data() {return {name:'张三',sex:'男'}},mixins:[mixin,mixin2]
}
</script>

- P68 - 插件

老师总结:

插件:

功能:用于增强Vue

本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。

定义插件:

对象.install = fuction (Vue, options){

// 1.添加全局过滤器

Vue.filter( ... )

// 2.添加全局指令

Vue.directive( ... )

}

本节部分代码:

pugins.js页面:

export default {install(Vue){console.log('111',Vue)// 全局过滤器Vue.filter('mySlice',function(value){return value.slice(0,4)})// 定义全局指令Vue.directive('fbind',{// 指令与元素成功绑定时(一上来)bind(element,binding){element.value = binding.value},// 指令所在元素被插入页面时inserted(element,binding){element.focus()},// 指令所在模板被重新解析时update(element,binding){element.value = binding.value}})// 定义混入Vue.mixin({data(){return{x:100,y:200}}})// 给Vue原型上添加一个方法(vm和vc就都能用了)Vue.prototype.hello = ()=>{alert('你好啊!')}}
}

main.js页面:

// 引入Vue
import Vue from 'vue'
// 引入App
import App from './App.vue'
// 引入插件
import plugins from './pugins'
// 关闭Vue的生产提示
Vue.config.productionTip = false
// 应用插件
Vue.use(plugins)
// 创建vm
new Vue({el:'#app',render: h => h(App),
})

School.vue页面(我将两个页面的引用插件都整合在一起了,比较方便看):

<template><div><h2>学校名称:{{name | mySlice}}</h2><h2>学校地址:{{address}}</h2><input type="text" v-fbind:value="name"><button @click="test">点我测试一个hello方法</button></div>
</template><script>
export default {name:'School',data() {return {name:'尚硅谷atguigu',address:'北京'}},methods: {test(){this.hello()}}
}
</script>

- P69 - scoped样式

课堂笔记:

(1)各个组件虽然样式是在自己的页面上写的,可是最后编译起来都会汇总到一起。

(2)脚手架在解析Vue文件的时候,顺序是:最先扫描import引入、然后再读取配置项、最后才解析模板。

(3)scoped原理:

其实就是在外层的div中,加了一个特殊的标签(随机生成值),通过标签+属性选择器,完成控制指定的div。

(4)App组件不适合用。

(5)安装less指令:npm i less-loader

查看less版本指令:npm view less-loader versions (less-loader可替换成别的,查看别的版本)

安装指定版本的less:npm i less-loader@7

(我自己是没指定版本就安装好了,依据个人情况定)

老师总结:

scoped样式

作用:让样式在局部生效,放置冲突。

写法:<style scoped>

- P70 - 静态

课堂笔记:

(1)老师的工作经验:如果你在拆分组件取名字的时候,发现很难取,说不定是因为你拆的不合理。

比如说课上的,将input框和list中的item放一起,就难命名,显然就是拆分不合理。

(2)把旧代码改成组件的流程:

①一个个div折叠,通过折叠判断哪个是一块儿的,然后剪切到新建组件中。

②剪切完之后,马上在剪切的地方写上新建组件,以免到时候忘记、搞混了。

③一个个组件分好之后,确认哪些样式是需要公用的,哪些样式是单独的,分配好。

(老师讲的流程虽然比较理想化,现实中需要整理的代码可能会更乱,但是这个流程确实是比较靠谱的,实际工作中就在这个流程上调整即可,多写注释造福你我他)

老师总结:

组件化编码流程(通用)

1.实现静态组件:抽取组件,使用组件实现静态页面效果

2.展示动态数据:

(1)数据的类型、名称是什么?

(2)数据保存在哪个组件?

3.交互——从绑定事件监听开始

P71-80:

- P71 - TodoList案例_初始化列表

课堂笔记:

(1)如何在Vue中,让一个标签动态拥有某个属性?

如本节中的checked,通过 v-bind:cheked=" "(true or false)完成动态展示。

(全部代码在77节)

- P72 - TodoList案例_添加

课堂笔记:

(1)uuid标准:用于生成全球唯一的字符串编码。

(因为这个包太大了,所以本节中用的是uuid的精简版,nanoid)

(2)nanoid安装指令:npm i nanoid

nanoid的库用了分别暴露,所以需要这样引用:

import {nanoid} from 'nanoid'

(3)子组件传值父组件简单原理:

①父亲先传给儿子一个函数。

②儿子在合适的时候调用,调用的时候父亲就能收到参数。

(4)整个添加的流程:

首先在MyHeader中,添加用了add方法,add方法调用 this.addTodo() 将 todoObj的值传给了App。

传给App后,addTodo方法获取值,操作data中的todos。

Vue收到todos变了,就开始重新解析模板。

重新解析的时候,显然todos已经变了,则MyList中收到的也会跟着变化。

MyList收到数据变化,也开始重新解析模板,通过比较数据,虚拟DOM增加,于是MyItem增加。

(全部代码在77节)

- P73 - TodoList案例_勾选

课堂笔记:

(1)数据在哪个页面,那么对数据的操作就应该在哪个页面。

(2)本节中的第二个做法:

<input type="checkbox" v-model="todo.done"/>

①因为v-model是双向绑定数据,所以修改有效。

②什么叫props被修改了?

本节中的举例:

Vue不能监测到的修改:let obj = {a:1,b:2},修改成 obj.a = 666

Vue能监测到的修改:let obj = {a:1,b:2},修改成 obj = {x:100,y:200}

也就是说Vue监视的是浅层次的修改。

③不建议这样做,因为这样会违反原则修改props,虽然并没有被Vue监测到。

(全部代码在77节)

- P74 - TodoList案例_删除

课堂笔记:

(1)在浏览器的Console中,可能会有未展示的消息。

在Default levels中选择需要看到的。老师建议勾选:Info、Warnings、Errors。

(全部代码在77节)

- P75 - TodoList案例_底部统计

课堂笔记:

(1)ES6中的reduce,条件统计,不熟的可以看:Array.prototype.reduce() - JavaScript | MDN

(全部代码在77节)

- P76 - TodoList案例_底部交互

课堂笔记:

(1)计算属性是允许套娃的。一个计算属性可以通过其它的两个或以上计算属性再进行计算。

(全部代码在77节)

- P77 - TodoList案例_总结

课堂笔记:

(1)看md文件,除了使用Typroa外,还可以在VScode中有个预览按钮可以查看,或者安装VScode插件,老师推荐的是Open in External App。

老师总结:

总结TodoList案例

1.组件化编码流程:

(1)拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。

(2)实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:

①一个组件在用:放在组件自身即可。

②一些组件在用:放在他们共同的父组件上(状态提升)

(3)实现交互:从绑定事件开始。

2.props适用于:

(1)父组件 ==> 子组件 通信

(2)子组件 ==> 父组件 通信 (要求父先给子一个函数)

3.使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!

4.props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。

本节部分代码:

App.vue页面:

<template><div class="todo-container"><div class="todo-wrap"><MyHeader :addTodo="addTodo"/><MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/><MyFooter :todos="todos" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"/></div></div>
</template><script>
import MyHeader from './components/MyHeader'
import MyList from './components/MyList'
import MyFooter from './components/MyFooter'export default {name: "App",components: {MyHeader,MyList,MyFooter},data() {return {todos:[{id:'001', title:'抽烟', done:true},{id:'002', title:'喝酒', done:false},{id:'003', title:'开车', done:true}]}},methods: {// 添加一个todoaddTodo(todoObj){this.todos.unshift(todoObj)},// 勾选or取消勾选一个todocheckTodo(id){this.todos.forEach((todo) => {if(todo.id === id) todo.done = !todo.done});},// 删除一个tododeleteTodo(id){this.todos = this.todos.filter(todo=> todo.id !== id )},// 全选or取消全选checkAllTodo(done){this.todos.forEach((todo)=>{todo.done = done})},// 清楚所有已经完成的todoclearAllTodo(){this.todos = this.todos.filter((todo)=>{return !todo.done})}},
}
</script><style>
/* base */
body {background-color: #fff;
}
.btn {display: inline-block;padding: 4px 12px;margin-bottom: 0;font-size: 14px;line-height: 20px;text-align: center;vertical-align: middle;cursor: pointer;box-shadow: inset 0 1px 0 rgba(255,255,255,0.2), 0 1px 2px rgba(0, 0,0,0.05);border-radius: 4px;
}
.btn-danger{color: #fff;background-color: #da4f49;border: 1px solid #bd362f;
}
.btn-danger:hover{color: #fff;background-color: #bd362f;
}
.btn:focus{outline: none;
}
.todo-container{width: 600px;margin: 0 auto;
}
.todo-container .todo-wrap{padding: 10px;border: 1px solid #ddd;border-radius: 5px;
}
</style>

MyHeader.vue页面:

<template><div class="todo-header"><input type="text" placeholder="请输入你的任务名称,按回车键确认" @keyup.enter="add"v-model="title"></div></template><script>
import {nanoid} from 'nanoid'
export default {name:'MyHeader',props:['addTodo'],data() {return {title:''}},methods: {add(){// 校验数据if(!this.title.trim()) return alert('输入不能为空')// 将用户的输入包装成一个todo对象const todoObj = {id:nanoid(),title:this.title,done:false}// 通知App组件去添加一个todo对象this.addTodo(todoObj)// 清空输入this.title = ''}},
}
</script><style scoped>
/* header */
.todo-header input{width: 560px;height: 28px;font-size: 14px;border: 1px solid #ccc;border-radius: 4px;padding: 4px 7px;
}
.todo-header input:focus{outline: none;border-color: rgba(82,168,236,0.8);box-shadow: inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);
}</style>

MyItem.vue页面:

<template><li><label><input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/><!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,修改了props --><!-- <input type="checkbox" v-model="todo.done"/> --><span>{{todo.title}}</span></label><button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button></li>
</template><script>
export default {name:'MyItem',// 声明接收todo对象props:['todo','checkTodo','deleteTodo'],methods: {// 勾选or取消勾选handleCheck(id){// 通知App组件将对应的todo对象的done值取反this.checkTodo(id)},// 删除handleDelete(id){if(confirm('确定删除吗?')){this.deleteTodo(id)}}},
}
</script><style scoped>
/* item */
li {list-style: none;height: 36px;line-height: 36px;padding: 0 5px;border-bottom: 1px solid #ddd;
}
li label {float: left;cursor: pointer;
}
li label li input {vertical-align: middle;margin-right: 6px;position: relative;top: -1px;
}
li button {float: right;display: none;margin-top: 3px;
}
li:before {content: initial;
}
li:last-child {border-bottom: none;
}
li:hover {background-color: #ddd;
}
li:hover button {display: block;
}
</style>

MyList.vue页面:

<template><ul class="todo-main"><MyItem v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj" :checkTodo="checkTodo":deleteTodo="deleteTodo"/></ul>
</template><script>
import MyItem from './MyItem'
export default {name:'MyList',components:{MyItem},props:['todos','checkTodo','deleteTodo']
}
</script><style scoped>
/* main */
.todo-main{margin-left: 0px;border: 1px solid #ddd;border-radius: 2px;padding: 0px;
}
.todo-empty{height: 40px;line-height: 40px;border: 1px solid #ddd;border-radius: 2px;padding-left: 5px;margin-top: 10px;
}
</style>

MyFooter.vue页面:

<template><div class="todo-footer" v-show="total > 0"><label><!-- 全选的第一种做法 --><!-- <input type="checkbox" :checked="isAll" @change="checkAll" /> --><!-- 全选的第二种做法 --><input type="checkbox" v-model="isAll" /></label><span><span>已完成{{doneTotal}}</span>  /  全部 {{total}}</span><button class="btn btn-danger" @click="clearAll">清楚已完成任务</button></div>
</template><script>
export default {name:'MyFooter',props:['todos','checkAllTodo','clearAllTodo'],computed:{total(){return this.todos.length},doneTotal(){// 老师写的第一种土方法/* let i =0this.todos.forEach((todo) => {if(todo.done) i++});return i */// 高端方法/* const x = this.todos.reduce((pre,current)=>{console.log('@',pre,current)return pre + (current.done ? 1 : 0)},0) */return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0)},isAll:{get(){return this.doneTotal === this.total && this.total > 0},// 全选的第二种做法set(value){this.checkAllTodo(value)}}},methods: {// 全选的第一种做法/* checkAll(e){this.checkAllTodo(e.target.checked)} */clearAll(){this.clearAllTodo()}},}
</script><style scoped>
/* footer */
.todo-footer {height: 40px;line-height: 40px;padding-left: 6px;margin-top: 5px;
}
.todo-footer label {display: inline-block;margin-right: 20px;cursor: pointer;
}
.todo-footer label input{position: relative;top: -1px;vertical-align: middle;margin-right: 5px;
}
.todo-footer button{float: right;margin-top: 5px;
}
</style>

- P78 - 浏览器本地存储

老师总结:

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。

- P79 - TodoList_本地存储

本节App.vue页面部分代码:

export default {......data() {return {todos:JSON.parse(localStorage.getItem('todos')) || []}},......watch: {todos:{deep:true,handler(value){localStorage.setItem('todos',JSON.stringify(value))}}}
}

- P80 - 组件自定义事件_绑定

课堂笔记:

(1)本节在接收多个参数时,用到了ES6的解构赋值,可以看这个:解构赋值_永远的大白的博客-CSDN博客_解构赋值

(全部代码在82节)

P81-90:

- P81 - 组件自定义事件_解绑

(全部代码在82节)

- P82 - 组件自定义事件_总结

课堂笔记:

(1)this.$refs.student.$on问题:

①为什么在App.vue页面中,写如下的内容,打印出来的this却是Student组件的实例呢?

this.$refs.student.$on('atguigu', function(name,...params){console.log('App收到了学生名:', name, params)console.log(this)
})

因为Vue底层逻辑设计了,谁触发的那个atguigu事件,那么这个事件回调中的this就是谁。

②那为什么在写 this.$refs.student.$on('atguigu',this.getStudentName) 的时候,this能获取到App的方法呢?

因为Vue给过承诺,如果这个函数写在methods里面,并且是普通函数,那么这个this就会是App的实例。

老师总结:

组件的自定义事件

1.一种组件间通信的方式,适用于:子组件 ==> 父组件。

2.使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。

3.绑定自定义事件:

(1)第一种方式,在父组件中:

<Demo @atguigu="test"/> 或 <Demo v-on:atguigu="test"/>

(2)第二种方式,在父组件中:

<Demo ref="demo">......mounted(){this.$refs.xxx.$on('atguigu',this.test)}

(3)若想让自定义事件只能触发一次,可以使用 once 修饰符或 $once 方法。

4.触发自定义事件:this.$emit('atguigu',数据)

5.解绑自定义事件:this.$off('atguigu')

6.组件上也可以绑定原生DOM事件,需要使用 native 修饰符。

7.注意:通过 this.$refs.xxx.$on('atguigu',回调) 绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!

本节部分代码:

App.vue页面:

<template><div class="app"><h1>{{msg}} 学生姓名是:{{stdentName}}</h1><!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 --><School :getSchoolName="getSchoolName"/><!-- 通过父组件给子组件绑定一个自定义实现:子给父传递数据(第一种写法,使用@或v-on) --><!-- <Student @atguigu="getStudentName" @demo="m1"/> --><!-- 通过父组件给子组件绑定一个自定义实现:子给父传递数据(第二种写法,使用ref) --><Student ref="student" @click.native="show"/></div>
</template><script>
import { NONAME } from 'dns'
import School from './components/School.vue'
import Student from './components/Student.vue'
export default {name: "App",components: { Student, School },data() {return {msg:'你好啊!',stdentName:''}},methods: {getSchoolName(name){console.log('App收到了学校名:', name)},getStudentName(name,...params){console.log('App收到了学生名:', name, params)this.stdentName = name},m1(){console.log('demo事件被触发了')},show(){alert(123)}},mounted() {// this.$refs.student.$on('atguigu', this.getStudentName) //绑定自定义事件this.$refs.student.$on('atguigu', (name,...params)=>{console.log('App收到了学生名:', name, params)this.stdentName = NONAME})// this.$refs.student.$once('atguigu', this.getStudentName) //绑定自定义事件(一次性)},
}
</script><style scoped>
.app{background-color: gray;
}
</style>

Student.vue页面:

<template><div class="student"><h2>学生姓名:{{name}}</h2><h2>学生性别:{{sex}}</h2><h2>当前求和为:{{number}}</h2><button @click="add">点我number++</button><button @click="sendStudentName">把学生名给App</button><button @click="unbind">解绑atguigu事件</button><button @click="death">销毁当前Student组件的实例(vc)</button></div>
</template><script>
export default {name:'Student',data() {return {name:'张三',sex:'男',number:0}},methods: {add(){console.log('add回调被调用了')this.number++},sendStudentName(){// 触发Student组件实例身上的atguigu事件this.$emit('atguigu',this.name,666,888,999)// this.$emit('demo')},unbind(){this.$off('atguigu') //解绑一个自定义事件// this.$off(['atguigu','demo']) //解绑多个自定义事件// this.$off() //解绑所有的自定义事件},death(){this.$destroy() //销毁了当前Student组件的实例,销毁后所有Student实例的自定义事件全都不奏效。}},
}
</script><style scoped>.student{background-color: pink;padding: 5px;margin-top: 30px;}
</style>

- P83 - TodoList案例_自定义事件

(这节太简单了不贴代码,最后所有的TodoList统一放在P90)

- P84 - 全局事件总线1

- P85 - 全局事件总线2

课堂笔记:

(1)对以下代码的理解:

const Demo = Vue.extend({})
const d = new Demo()
Vue.prototype.x = d

第一行(回顾54节)创建Demo全局组件。

第二行,获取组件的实例对象。

因为需要写了标签才能使用组件,然而不能在main.js中引用标签。所以这一行亲自new一个。

第三行(回顾59节)获得了原型vc。

(2)对以下代码的理解:

new Vue({......beforeCreate() {Vue.prototype.$bus = this //安装全局事件总线}
})

第三行(回顾49节)此时beforeCreate时无法访问到data中的数据的,只是初始化。

第四行,已知这个时候vm原型还未生成,所以不能直接等于vm,但是Vue中有个this本身就是vue实例。$bus为总线。

(3)为什么bus需要销毁,自定义事件不用销毁?

举个例子,比如说Student组件被销毁了,那么整个vc都没有了,所以自定义事件就直接没有了。

但是bus不是这样的,就算Student组件被销毁了,bus也会一直在,所以bus用完了最好要销毁。

老师总结:

全局事件总线(GlobalEventBus)

1.一种组件通信的方式,适用于任意组件间通信。

2.安装全局事件总线。

new Vue({......beforeCreate() {Vue.prototype.$bus = this //安装全局事件总线}
})

3.使用事件总线:

(1)接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。

methods(){demo(data){......}
}
......
moutnted(){this.$bus.$on('xxx',数据)
}

(2)提供数据:this.$bus.$emit( 'xxx', 数据 )

4.最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。

本节部分代码:

main.js页面:

// 引入Vue
import Vue from 'vue'
// 引入App
import App from './App.vue'
// 关闭Vue的生产提示
Vue.config.productionTip = false// 创建vm
new Vue({el:'#app',render: h => h(App),beforeCreate() {Vue.prototype.$bus = this //安装全局事件总线}
})

School.vue页面:

<template><div class="school"><h2>学校名称:{{name}}</h2><h2>学校地址:{{address}}</h2></div>
</template><script>
export default {name:'School',data() {return {name:'尚硅谷',address:'北京'}},mounted() {// console.log('School',this)this.$bus.$on('hello',(data)=>{console.log('我是School组件,收到了数据',data)})},beforeDestroy() {this.$bus.$off('hello')},
}
</script><style scoped>.school{background-color: skyblue;padding: 5px;}
</style>

Student.vue页面:

<template><div class="student"><h2>学生姓名:{{name}}</h2><h2>学生性别:{{sex}}</h2><button @click="sendStudentName">把学生名给School组件</button></div>
</template><script>
export default {name:'Student',data() {return {name:'张三',sex:'男'}},methods: {sendStudentName(){this.$bus.$emit('hello',this.name)}},
}
</script><style scoped>.student{background-color: pink;padding: 5px;margin-top: 30px;}
</style>

- P86 - TodoList案例_事件总线

课堂笔记:

(1)如果在Vue控制台的事件中,看到触发事件是 by 的话,那么多半意味着这是个事件总线。

(这节太简单了不贴代码,最后所有的TodoList统一放在P90)

- P87 - 消息订阅与发布_pubsub

课堂笔记:

(1)老师的比喻:

(2)对以下代码的理解:

mounted() {this.pubId = pubsub.subscribe('hello',(msgName,data)=>{console.log('有人发布了hello消息,hello消息的回调执行了',msgName,data)})
},
beforeDestroy() {pubsub.unsubscribe(this.pubId)
},

pubsub在取消订阅的时候,需要通过Id去取消。

但是beforeDestroy不能直接获取到methods中的id,所以这里需要用this.id。

又因为pubsub是外部函数,所以这里使用箭头函数。

老师总结:

消息订阅与发布(pubsub)

1.一种组件通信的方式,适用于任意组件间通信。

2.使用步骤:

(1)安装pubsub: npm i pubsub-js

(2)引入:import pubsub from 'pubsub-js'

(3)接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。

methods: {demo(msgName,data){......}
},
mounted() {this.pubId = pubsub.subscribe('xxx',this.demo) //订阅消息
},

(4)提供数据:pubsub.publish( 'xxx',数据 )

(5)最好在beforeDestroy钩子中,用 pubsub.unsubscribe(pid) 去取消订阅。

本节部分代码:

School.vue页面:

<template><div class="school"><h2>学校名称:{{name}}</h2><h2>学校地址:{{address}}</h2></div>
</template><script>
import pubsub from 'pubsub-js'
export default {name:'School',data() {return {name:'尚硅谷',address:'北京'}},methods: {demo(msgName,data){console.log('hello消息收到了',data)}},mounted() {// this.$bus.$on('hello',(data)=>{//     console.log('我是School组件,收到了数据',data)// })this.pubId = pubsub.subscribe('hello',this.demo)},beforeDestroy() {// this.$bus.$off('hello')pubsub.unsubscribe(this.pubId)},
}
</script><style scoped>.school{background-color: skyblue;padding: 5px;}
</style>

Student.vue页面部分代码:

    methods: {sendStudentName(){// this.$bus.$emit('hello',this.name)pubsub.publish('hello',666)}},

- P88 - TodoList案例_pubsub

(这节太简单了不贴代码,最后所有的TodoList统一放在P90)

- P89 - TodoList案例_编辑

课堂笔记:

(1)hasOwnProperty,判断一个属性定义在对象本身而不是继承原型链的方法,主要用于判断某个对象中是否有某个属性,返回值为布尔值 。

(全部代码在P90)

- P90 - $nextTick

课堂笔记:

(1)为什么直接写无效?

因为Vue需要等整个函数走完,才去重新渲染DOM,这就和之前一样(回顾46节),还没渲染就focus,显然是无效的。

老师总结:

nextTick

1.语法:this.$nextTick( 回调函数 )

2.作用:在下一次DOM更新结束后执行其指定的回调。

3.什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。

本节部分代码:

main.js页面:

// 引入Vue
import Vue from 'vue'
// 引入App
import App from './App.vue'
// 关闭Vue的生产提示
Vue.config.productionTip = false// 创建vm
new Vue({el:'#app',render: h => h(App),beforeCreate() {Vue.prototype.$bus = this}
})

App.vue页面:

<template><div class="todo-container"><div class="todo-wrap"><MyHeader @addTodo="addTodo"/><MyList :todos="todos"/><MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/></div></div>
</template><script>
import pubsub from 'pubsub-js'
import MyHeader from './components/MyHeader'
import MyList from './components/MyList'
import MyFooter from './components/MyFooter'export default {name: "App",components: {MyHeader,MyList,MyFooter},data() {return {todos:JSON.parse(localStorage.getItem('todos')) || []}},methods: {// 添加一个todoaddTodo(todoObj){this.todos.unshift(todoObj)},// 勾选or取消勾选一个todocheckTodo(id){this.todos.forEach((todo) => {if(todo.id === id) todo.done = !todo.done});},// 更新一个todoupdateTodo(id,title){this.todos.forEach((todo) => {if(todo.id === id) todo.title = title});},// 删除一个tododeleteTodo(_,id){this.todos = this.todos.filter(todo=> todo.id !== id )},// 全选or取消全选checkAllTodo(done){this.todos.forEach((todo)=>{todo.done = done})},// 清楚所有已经完成的todoclearAllTodo(){this.todos = this.todos.filter((todo)=>{return !todo.done})}},watch: {todos:{deep:true,handler(value){localStorage.setItem('todos',JSON.stringify(value))}}},mounted() {this.$bus.$on('checkTodo',this.checkTodo)this.$bus.$on('updateTodo',this.updateTodo)// this.$bus.$on('deleteTodo',this.deleteTodo)this.pubId = pubsub.subscribe('deleteTodo',this.deleteTodo)},beforeDestroy() {this.$bus.$off('checkTodo')this.$bus.$off('updateTodo')// this.$bus.$off('deleteTodo')pubsub.unsubscribe(this.pubId)},
}
</script><style>
/* base */
body {background-color: #fff;
}
.btn {display: inline-block;padding: 4px 12px;margin-bottom: 0;font-size: 14px;line-height: 20px;text-align: center;vertical-align: middle;cursor: pointer;box-shadow: inset 0 1px 0 rgba(255,255,255,0.2), 0 1px 2px rgba(0, 0,0,0.05);border-radius: 4px;
}
.btn-danger{color: #fff;background-color: #da4f49;border: 1px solid #bd362f;
}
.btn-danger:hover{color: #fff;background-color: #bd362f;
}
.btn-edit{color: #fff;background-color: skyblue;border: 1px solid rgb(103,159,180);margin-right: 5px;
}
.btn-edit:hover{color: #fff;background-color: rgb(103,159,180);
}
.btn:focus{outline: none;
}
.todo-container{width: 600px;margin: 0 auto;
}
.todo-container .todo-wrap{padding: 10px;border: 1px solid #ddd;border-radius: 5px;
}
</style>

MyHeader.vue页面:

<template><div class="todo-header"><input type="text" placeholder="请输入你的任务名称,按回车键确认" @keyup.enter="add"v-model="title"></div></template><script>
import {nanoid} from 'nanoid'
export default {name:'MyHeader',data() {return {title:''}},methods: {add(){// 校验数据if(!this.title.trim()) return alert('输入不能为空')// 将用户的输入包装成一个todo对象const todoObj = {id:nanoid(),title:this.title,done:false}// 通知App组件去添加一个todo对象this.$emit('addTodo',todoObj)// 清空输入this.title = ''}},
}
</script><style scoped>
/* header */
.todo-header input{width: 560px;height: 28px;font-size: 14px;border: 1px solid #ccc;border-radius: 4px;padding: 4px 7px;
}
.todo-header input:focus{outline: none;border-color: rgba(82,168,236,0.8);box-shadow: inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);
}</style>

MyList.vue页面:

<template><ul class="todo-main"><MyItem v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj" /></ul>
</template><script>
import MyItem from './MyItem'
export default {name:'MyList',components:{MyItem},props:['todos']
}
</script><style scoped>
/* main */
.todo-main{margin-left: 0px;border: 1px solid #ddd;border-radius: 2px;padding: 0px;
}
.todo-empty{height: 40px;line-height: 40px;border: 1px solid #ddd;border-radius: 2px;padding-left: 5px;margin-top: 10px;
}
</style>

MyItem.vue页面:

<template><li><label><input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/><!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,修改了props --><!-- <input type="checkbox" v-model="todo.done"/> --><span v-show="!todo.isEdit">{{todo.title}}</span><input type="text"v-show="todo.isEdit" :value="todo.title" @blur="handleBlur(todo,$event)"ref="inputTitle"></label><button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button><button v-show="!todo.isEdit" class="btn btn-edit" @click="handleEdit(todo)">编辑</button></li>
</template><script>
import pubsub from 'pubsub-js'
export default {name:'MyItem',// 声明接收todo对象props:['todo'],methods: {// 勾选or取消勾选handleCheck(id){// 通知App组件将对应的todo对象的done值取反this.$bus.$emit('checkTodo',id)},// 删除handleDelete(id){if(confirm('确定删除吗?')){// 通知App组件将对应的todo对象删除// this.deleteTodo(id) //其中的deleteTodo是父组件传来的函数// this.$bus.$emit('deleteTodo',id) //其中的deleteTodo是自定义事件的时间名pubsub.publish('deleteTodo',id) //其中的deleteTodo是订阅消息的消息名}},// 编辑handleEdit(todo){if(todo.hasOwnProperty('isEdit')){todo.isEdit = true}else{this.$set(todo,'isEdit',true)}this.$nextTick(function(){this.$refs.inputTitle.focus() })},// 失去焦点回调(真正执行修改逻辑)handleBlur(todo,e){todo.isEdit = falseif(!e.target.value.trim()) return alert('输入不能为空')this.$bus.$emit('updateTodo',todo.id,e.target.value)}},
}
</script><style scoped>
/* item */
li {list-style: none;height: 36px;line-height: 36px;padding: 0 5px;border-bottom: 1px solid #ddd;
}
li label {float: left;cursor: pointer;
}
li label li input {vertical-align: middle;margin-right: 6px;position: relative;top: -1px;
}
li button {float: right;display: none;margin-top: 3px;
}
li:before {content: initial;
}
li:last-child {border-bottom: none;
}
li:hover {background-color: #ddd;
}
li:hover button {display: block;
}
</style>

MyFooter.vue页面:

<template><div class="todo-footer" v-show="total > 0"><label><!-- 全选的第一种做法 --><!-- <input type="checkbox" :checked="isAll" @change="checkAll" /> --><!-- 全选的第二种做法 --><input type="checkbox" v-model="isAll" /></label><span><span>已完成{{doneTotal}}</span>  /  全部 {{total}}</span><button class="btn btn-danger" @click="clearAll">清楚已完成任务</button></div>
</template><script>
export default {name:'MyFooter',props:['todos'],computed:{total(){return this.todos.length},doneTotal(){// 老师写的第一种土方法/* let i =0this.todos.forEach((todo) => {if(todo.done) i++});return i */// 高端方法/* const x = this.todos.reduce((pre,current)=>{console.log('@',pre,current)return pre + (current.done ? 1 : 0)},0) */return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0)},isAll:{get(){return this.doneTotal === this.total && this.total > 0},// 全选的第二种做法set(value){this.$emit('checkAllTodo',value)}}},methods: {// 全选的第一种做法/* checkAll(e){this.checkAllTodo(e.target.checked)} */clearAll(){this.$emit('clearAllTodo')}},}
</script><style scoped>
/* footer */
.todo-footer {height: 40px;line-height: 40px;padding-left: 6px;margin-top: 5px;
}
.todo-footer label {display: inline-block;margin-right: 20px;cursor: pointer;
}
.todo-footer label input{position: relative;top: -1px;vertical-align: middle;margin-right: 5px;
}
.todo-footer button{float: right;margin-top: 5px;
}
</style>

P91-100:

- P91 - 动画效果

本节部分代码:

Test.vue页面:

<template><div><button @click="isShow = !isShow">显示/隐藏</button><transition name="hello" appear><h1 v-show="isShow">你好啊!</h1></transition></div>
</template><script>
export default {name:'Test',data() {return {isShow:true}},
}
</script><style scoped>
h1{background-color: orange;
}
.hello-enter-active{animation: atguigu 0.5s linear;
}
.hello-leave-active{animation: atguigu 0.5s linear reverse;
}
@keyframes atguigu {from{transform: translateX(-100%);}to{transform: translateX(0px);}
}
</style>

- P92 - 过度效果

(代码在P93)

- P93 - 多个元素过度

本节部分代码:

Test2.vue页面:

<template><div><button @click="isShow = !isShow">显示/隐藏</button><transition-group name="hello" appear><h1 v-show="!isShow" key="1">你好啊!</h1><h1 v-show="isShow" key="2">尚硅谷!</h1></transition-group></div>
</template><script>
export default {name:'Test',data() {return {isShow:true}},
}
</script><style scoped>
h1{background-color: orange;transition: 0.5s linear;
}
/* 进入的起点、离开的终点 */
.hello-enter , .hello-leave-to{transform: translateX(-100%);
}
/* .hello-enter-active , .hello-leave-active{transition: 0.5s linear;
} */
/* 进入的终点、离开的起点 */
.hello-enter-to , .hello-leave{transform: translateX(0);
}
</style>

- P94 - 集成第三方动画

课堂笔记:

(1)npm官网:npm

Animate官网:Animate.css | A cross-browser library of CSS animations.

(2)Animate安装指令:npm i animate (我从npm上复制来的,跟老师的不太一样,哪种都行)

本节部分代码:

Tset3.vue页面:

<template><div><button @click="isShow = !isShow">显示/隐藏</button><transition-groupappearname="animate__animated animate__bounce"enter-active-class="animate__swing"leave-active-class="animate__backOutUp"><h1 v-show="!isShow" key="1">你好啊!</h1><h1 v-show="isShow" key="2">尚硅谷!</h1></transition-group></div>
</template><script>
import 'animate'
export default {name:'Test',data() {return {isShow:true}},
}
</script><style scoped>
h1{background-color: orange;transition: 0.5s linear;
}
</style>

- P95 - 总结过度与动画

老师总结:

Vue封装的过度与动画

1.作用:在插入、更新或移除DOM元素时,在合适的时候给元素添加样式类名。

2.图示:

3.写法:

(1)准备好样式:

• 元素进入的样式:

①v-enter:进入的起点

②v-enter-active:进入的过程

③v-enter-to:进入的终点

• 元素离开的样式:

①v-leave:进入的起点

②v-leave-active:进入的过程

③v-leave-to:进入的终点

(2)使用<transition>包裹要过度的元素,并配置name属性:

<transition name="hello"><h1 v-show="isShow">你好啊!</h1>
</transition>

(3)备注:若有多个元素需要过度,则需要使用:,且每个元素都要指定key值。

(MyList.vue页面的代码不贴了,跟Test2的一样,稍微修改一下就好了)

- P96 - 配置代理_方式一

课堂笔记:

(1)回顾几种请求:

① xhr:也就是 new XMLHttpRequest() ,用于与服务器交互数据,是ajax功能实现所依赖的对象。

② JQuery:是对 xhr 的封装:$.get 、$.post。

③ axios:也是对 xhr 的封装。

④ fetch:在window的内置对象中直接有的方法,兼容性稍差。

(2)axios安装指令:npm i axios

(3)常见的跨域解决:

① cors:其实就是给你加几个特殊的响应头。拓展链接:CORS解决跨域问题_亦木丶的博客-CSDN博客_cors跨域

② jsonp:借助了script标签的src属性,在引入外部资源的时候,不受同源限制的特点。需要前后端都调整,且只能解决get请求(平时很少用,但是面试常问到)。拓展链接:使用jsonp解决跨域问题_是啥也不会酱的博客-CSDN博客_jsonp解决跨域问题

③配置代理服务器

(4)Vue CLI官网——配置参考——devServer.proxy:配置参考 | Vue CLI

(5)public文件夹就相当于8080服务器的根路径。public文件夹里有的东西都算是8080中有的。

- P97 - 配置代理_方式二

课堂笔记:

(1)方法二部分分析:

'/foo' 是在 '/api' 的基础上精简而来的。

pathRewrite:官网上没有加,需要自己加上去,重写路径。

ws:用于支持websocket。

老师总结:

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:5000changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080changeOrigin默认值为true
*/

说明:

1.优点:可以配置多个代理,且可以灵活的控制请求是否走代理。

2.缺点:配置略微繁琐,请求资源时必须加前缀。

本节部分代码:

vue.config.js页面:

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({transpileDependencies: true,lintOnSave:false, //关闭语法检测// 开启代理服务器(方式一)/* devServer: {proxy: 'http://localhost:5000'} */// 开启代理服务器(方式二)devServer: {proxy: {'/atguigu': {target: 'http://localhost:5000',pathRewrite:{'^/atguigu':''},// ws: true, //用于支持websocket// changeOrigin: true //用于控制请求头中host值},'/demo': {target: 'http://localhost:5001',pathRewrite:{'^/demo':''},}}}
})

- P98 - github案例_静态组件

课堂笔记:

(1)bootstrap官网:Bootstrap中文网

(2)vue中引入样式的两种方法:

①在src文件夹中新建assets文件夹,再新建css文件夹,放入css文件,在.vue页面中import。缺点就是,Vue会逐条检查,如果有本节类似缺失字体部分,就很麻烦。

②在public文件夹中新建css文件夹,放入css文件,在index.html页面中用link引入。

<!-- 引入第三方样式 -->
<link rel="stylesheet" href="<%= BASE_URL %>css/bootstrap.css">

(代码在P100)

- P99 - github案例_列表展示

课堂笔记:

(1)课件提供的接口地址:

https://api.github.com/search/users?q=xxx

(2)写请求的时候提到了ES6中的模板字符串,可以看下:什么是模板字符串?_紫微前端的博客-CSDN博客_模板字符串

(代码在P100)

- P100 - github案例_完善案例

本节部分代码:

App.vue页面:

<template><div class="container"><Search/><List/></div>
</template><script>
import Search from './components/Search.vue'
import List from './components/List.vue'
export default {name:'App',components:{Search,List}
}
</script>

Search.vue页面:

<template><section class="jumbotron"><h3 class="jumbotron-heading">Search Github Users</h3><div><input type="text" placeholder="enter the name you search"v-model="keyWord"/>&nbsp;<button @click="searchUsers">Search</button></div></section>
</template><script>
import axios from 'axios'
export default {name:'Search',data() {return {keyWord:''}},methods: {searchUsers(){// 请求前更新List的数据this.$bus.$emit('updateListData',{isFirst:false, //是否为第一次展示isLoading:true, //是否处于加载中errMsg:'', //报错信息users:[]})axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(response => {console.log('请求成功了')// 请求成功后更新List的数据this.$bus.$emit('updateListData',{ isLoading:false, errMsg:'', users:response.data.items})},error => {// 请求失败后更新List的数据console.log('请求失败了',error.message)this.$bus.$emit('updateListData',{isLoading:false, errMsg:error.message, users:[]})})}}
}
</script><style></style>

List.vue页面:

<template><div class="row"><!-- 展示用户列表 --><div v-show="info.users.length" class="card" v-for="user in info.users" :key="user.login"><a :href="user.html_url" target="_blank"><img :src="user.avatar_url" style="width:100px" /></a><p class="card-text">{{user.login}}</p></div><!-- 展示欢迎词 --><h1 v-show="info.isFirst">欢迎使用!</h1><!-- 展示加载中 --><h1 v-show="info.isLoading">Loading···</h1><!-- 展示错误信息 --><h1 v-show="info.errMsg">{{info.errMsg}}</h1></div>
</template><script>
export default {name:'List',data() {return {info:{isFirst:true, //是否为第一次展示isLoading:false, //是否处于加载中errMsg:'', //报错信息users:[]}}},mounted() {this.$bus.$on('updateListData',(dataObj)=>{this.info = {...this.info,...dataObj}})},
}
</script><style scoped>
.album {min-height: 50rem; /* Can be removed */padding-top: 3rem;padding-bottom: 3rem;background-color: #f7f7f7;
}
.card {float: left;width: 33.333%;padding: .75rem;margin-bottom: 2rem;border: 1px solid #efefef;text-align: center;
}
.card > img {margin-bottom:  .75rem;border-radius: 100px;
}
.card-text {font-size: 85%;
}
</style>

——————————————————————————————————————

——————————————原创不易,转载请声明——————————————

尚硅谷Vue2.0+Vue3.0全套教程视频笔记 + 代码 [P051-100]相关推荐

  1. 尚硅谷Vue2.0+Vue3.0全套教程视频笔记 + 代码 [P001-050]

    视频链接:尚硅谷Vue2.0+Vue3.0全套教程丨vuejs从入门到精通_哔哩哔哩_bilibili P1-50:当前页面.  P51-100:尚硅谷Vue2.0+Vue3.0全套教程视频笔记 + ...

  2. 尚硅谷最新版JavaScript基础全套教程完整版(p79-p90)

    尚硅谷最新版JavaScript基础全套教程完整版(140集实战教学,JS从入门到精通) 一.函数的方法 1.call()和 apply()方法 -这两个方法都是函数对象方法,需要通过函数对象来调用 ...

  3. 【Vue学习笔记】尚硅谷Vue2.0+Vue3.0全套教程丨vue.js从入门到精通

    尚硅谷Vue2.0+Vue3.0全套教程丨vue.js从入门到精通 1.Vue核心部分 1.1 Vue简介 1.1.1 Vue是什么? Vue是一套用于构建用户界面的渐进式JavaScript框架. ...

  4. [Vue]学习笔记目录 【Vue2与Vue3完结】 (尚硅谷Vue2.0+Vue3.0全套教程丨vuejs从入门到精通)

    文章目录 前言 遇见的问题及其解决方案 之前笔记 Vue2 Vue3 前言 本笔记根据如下笔记和视频进行整理 老师的课件笔记,不含视频 https://www.aliyundrive.com/s/B8 ...

  5. vue2.0,vue3.0 v-model数据双向绑定

    vue2.0,vue3.0 v-model数据双向绑定 vue.2.0 vue2.0 vue-property-decorator vue3.0 vue.2.0 <base-checkbox v ...

  6. vue2.0 vue3.0 打包二级项目-如何部署二级目录

    看了很多其他大佬的文章,我这边做了一个笔记记录了一下vue打包二级目录的方法 我们想要的效果是什么 我们想www.taobao.com/web二级目录来访问我们的页面 如果我们没有做任何的配置,直接将 ...

  7. 尚硅谷Vue2学习笔记分享

    前言 这里是尚硅谷Vue2的学习笔记分享. 原视频是尚硅谷Vue2.0+Vue3.0全套教程丨vuejs从入门到精通 Vue3的笔记链接 文章目录 前言 初识Vue 模板语法 数据绑定 el和data ...

  8. 关于KINECT V2.0 C++ SDK 基础教程的笔记 EP2

    最近忙着搞老师的任务,没来得及更新点云系列. 目前在做Kinect,在这里接着做个笔记. 原文地址: Kinect Tutorials 这仅仅是做一个笔记以及自己的实际操作记录 关于KINECT V2 ...

  9. 前端 | ( 九)尚品汇实操练习 | 尚硅谷前端html+css零基础教程2023最新

    学习来源:尚硅谷前端html+css零基础教程,2023最新前端开发html5+css3视频 系列笔记: [HTML4](一)前端简介 [HTML4](二)各种各样的常用标签 [HTML4](三)表单 ...

  10. 前端 | ( 十一)CSS3简介及基本语法(上) | 尚硅谷前端html+css零基础教程2023最新

    学习来源:尚硅谷前端html+css零基础教程,2023最新前端开发html5+css3视频 系列笔记: [HTML4](一)前端简介 [HTML4](二)各种各样的常用标签 [HTML4](三)表单 ...

最新文章

  1. java命令执行类,这里设置了classpath,系统变量里的classpath将失效
  2. 替换软连接导致的问题
  3. 【財務会計】固定資産の除却と廃棄の違い
  4. 截取字符串slice(),substring() ,substr()。
  5. Kubernetes网络一年发展动态与未来趋势
  6. PHP 分页类 高洛峰 细说PHP
  7. 如何订阅MQTT服务器历史消息,mqtt集群订阅如何只消费一个(一次)消息?
  8. java 持续集成工具_Jenkins集成式项目控件下载
  9. IP层:尽力交付,可能丢包,可能重包,可能无序
  10. 数据库设计笔记——MySQL基础知识(四)
  11. 用python做数据分析流程图_使用Pyecharts进行高级数据可视化
  12. 线性地址到物理地址的映射
  13. 什么是TensorBoard?
  14. 国内maven镜像,快的飞起
  15. pc端滚动去掉滚动条scroll
  16. JanusGraph组件对应版本
  17. 用渐变工具绘制七色彩虹(每天一个PS小项目)
  18. 【每日一短语】夜长梦多
  19. Softmax回归及损失函数(李沐深度学习课程、自用)
  20. python基础(一)完结

热门文章

  1. 矩阵按键原理和BUG
  2. 半导体物理学——(二)半导体中杂志和能级缺陷
  3. 高校实验室安全隐患及安全建设-LIMS2
  4. 【hexo】fluid中文乱码问题解决
  5. Android Kotlin关于新增本地数据库对象表字段问题
  6. Visio安装失败问题解决
  7. 基于PHP的在线聊天室(网页版)
  8. SpringCloud第一章 Euraka服务注册与发现组件
  9. 不到100行代码制作各种证件照
  10. js/vue 动态获取浏览器宽度/高度