vue2[黑马程序员]

一、前端工程化与webpack

1.前端工程化

1. 小白眼中的前端开发 vs 实际的前端开发

 2. 什么是前端工程化

 3. 前端工程化的解决方案

2.webpack的基本使用

1. 什么是 webpack:是前端项目工程化的具体解决方案

 2. 创建列表隔行变色项目

 

3. 在全局项目中安装 webpack 

【这里注意:后面使用到webpack-dev-server这个插件的时候,会报错,需要将webpack-cli进行修改,将重新安装为最新版本】:npm i webpack-cli 

4. 创建名为 webpack.config.js 的 webpack 配置文件:在项目中配置 webpack

① 在项目根目录中,创建名为 webpack.config.js 的 webpack 配置文件,并初始化如下的基本配置:

 ② 在 package.json 的 scripts 节点下,新增 dev 脚本如下:("dev”是脚本的名字,是可以修改的,“webpack”是固定写法)

 ③ 在终端中运行 npm run dev 命令,启动 webpack 进行项目的打包构建(创建了一个dist的文件夹,里面有一个main.js)

4.1 mode 的可选值mode:development、production(对代码进行压缩)

节点的可选值有两个,分别是:

① development(项目开发过程中使用)

⚫ 开发环境

⚫ 不会对打包生成的文件进行代码压缩和性能优化

⚫ 打包速度快,适合在开发阶段使用

② production(项目上线的时候使用)

⚫ 生产环境

⚫ 会对打包生成的文件进行代码压缩和性能优化

⚫ 打包速度很慢,仅适合在项目发布阶段使用

    mode 用来指定构建模式 可以选中有development 和 production

    结论:开发的时候一定使用development ,因为追求的是打包的速度,而不是体积

    反过来,发布上线的时候一定要用production ,因为上线追求的是体积小,而不是打包速度。

 4.2 webpack.config.js 文件的作用

webpack.config.js 是 webpack 的配置文件。webpack 在真正开始打包构建之前,会先读取这个配置文件, 从而基于给定的配置,对项目进行打包。

【在npm run dev(这个dev是在package.json文件中的script脚本中的,它会先去读取webpack.config.js这个文件,然后再进行配置】

注意:由于 webpack 是基于 node.js 开发出来的打包工具,因此在它的配置文件中,支持使用 node.js 相关 的语法和模块进行 webpack 的个性化配置。

 4.3 webpack 中的默认约定:默认打包入口文件为src->index.js,打包输出文件为dist->main.js

 4.4 自定义打包的入口与出口

在 webpack.config.js 配置文件中,通过 entry 节点指定打包的入口。通过 output 节点指定打包的出口。 示例代码如下:(记得先将前面生成的dist文件夹删除)


const path=require('path')


//使用Node.js 中的导出语法,向外导出一个webpack的配置对象
module.exports={
    // mode 用来指定构建模式 可以选中有development 和 production
    // 结论:开发的时候一定使用development ,因为追求的是打包的速度,而不是体积
    // 反过来,发布上线的时候一定要用production ,因为上线追求的是体积小,而不是打包速度。
    mode:'development',
    entry:path.join(__dirname,'./src/index.js'),
    //输出文件的名称
    //指定生成的文件要存放的位置
    output:{
        // 存放的目录
       path: path.join(__dirname,'dist'),
       // 生成的文件名
       filename:'bundle.js'
    }

}

    <!-- 加载和引用内存中的main.js 因为每一次一修改代码,run一下后,实际上dist中的main.js文件是存放再内存中 -->
    <!-- 并且是存放再根目录下的内存中 -->
    <script src="../dist/bundle.js"></script>

3.webpack中的插件【都是将产生的文件存放在内存中】

1. webpack 插件的作用

 2. webpack-dev-server:自动更新dist文件夹中的bundle.js

webpack-dev-server 可以让 webpack 监听项目源代码的变化,从而进行自动打包构建。

        2.1 安装 webpack-dev-server

         2.2 配置 webpack-dev-server(3.11.2版本的)

① 修改 package.json -> scripts 中的 dev 命令如下:

    

 ② 再次运行 npm run dev 命令,重新进行项目的打包

③ 在浏览器中访问 http://localhost:8080 地址,查看自动打包效果(记得再这个网址才可以看到)

 实际上,代码修改后,执行run,dist中的bundle.js并没有被修改,而是再根目录下的bundle.js被修改,所以我们要使用的是根目录下的bundle.js而且不是dist中的bundle.js。

    <!-- 加载和引用内存中的bundle.js 因为每一次一修改代码,run一下后,实际上dist中的bundle.js文件是存放再内存中 -->
    <!-- 并且是存放再根目录下的内存中 -->
  <!-- 一定要加上“/” 如果不加就错误 -->
    <script src="/bundle.js"></script>//表示根目录下的bundle.js

     2.3 打包生成的文件哪儿去了?

不配置 webpack-dev-server 的情况下,webpack 打包生成的文件,会存放到实际的物理磁盘上

        ⚫ 严格遵守开发者在 webpack.config.js 中指定配置

        ⚫ 根据 output 节点指定路径进行存放

配置了 webpack-dev-server 之后,打包生成的文件存放到了内存中

        ⚫ 不再根据 output 节点指定的路径,存放到实际的物理磁盘上

        ⚫ 提高了实时打包输出的性能,因为内存比物理磁盘速度快很多

         2.4 生成到内存中的文件该如何访问?

webpack-dev-server 生成到内存中的文件,默认放到了项目的根目录中,而且是虚拟的、不可见的。

        ⚫ 可以直接用 / 表示项目根目录,后面跟上要访问的文件名称,即可访问内存中的文件

        ⚫ 例如 /bundle.js 就表示要访问 webpack-dev-server 生成到内存中的 bundle.js 文件

 3. html-webpack-plugin:将页面文件放在根目录中,才不用每次进入src中才可以查看页面

         3.1 安装 html-webpack-plugin(版本为5.3.2)

         3.2 配置 html-webpack-plugin

// 1. 导入 html-webpack-plugin 这个插件,得到插件的构造函数
const HtmlPlugin = require('html-webpack-plugin')
// 2. new 构造函数,创建插件的实例对象
const htmlPlugin = new HtmlPlugin({
  // 指定要复制哪个页面
  template: './src/index.html',
  // 指定复制出来的文件名和存放路径
  filename: './index.html'
})

module.exports = {
  plugins:[htmlPlugin],
}

         3.3 解惑 html-webpack-plugin

① 通过 HTML 插件复制到项目根目录中的 index.html 页面,也被放到了内存中

HTML 插件在生成的 index.html 页面,自动注入了打包的 bundle.js 文件【所以可以不用在index.html文件中引入script文件路径】

4. devServer 节点 【自动打开浏览器】

在 webpack.config.js 配置文件中,可以通过 devServer 节点对 webpack-dev-server 插件进行更多的配置, 示例代码如下:

module.exports = {
  devServer:{
    // 首次打包成功后,自动打开浏览器
    open:true,
    //原来打开浏览器的时候,地址是:--http://localhost:8080/
    // 在 http 协议中,如果端口号是 80,则可以被省略
    port:80,
    // 指定运行的主机地址
    //地址栏出现为:http://127.0.0.1/
    host: '127.0.0.1'

   }
}

 注意:凡是修改了 webpack.config.js 配置文件,或修改了 package.json 配置文件,必须重启实时打包的服 务器,否则最新的配置文件无法生效!

4.webpack中的loader(加载器)

1. loader 概述

在实际开发过程中,webpack 默认只能打包处理以 .js 后缀名结尾的模块。其他非 .js 后缀名结尾的模块, webpack 默认处理不了,需要调用 loader 加载器才可以正常打包,否则会报错!

loader 加载器的作用:协助 webpack 打包处理特定的文件模块。

比如:

⚫ css-loader 可以打包处理 .css 相关的文件

⚫ less-loader 可以打包处理 .less 相关的文件

⚫ babel-loader 可以打包处理 webpack 无法处理的高级 JS 语法

 2. loader 的调用过程

在导入css文件并且没有导包前:

修改后面 run 一下:报错 

3. 打包处理 css 文件

① 运行 npm i style-loader@3.0.0 css-loader@5.2.6 -D 命令,安装处理 css 文件的 loader ② 在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则如下:

 

module.exports = {
   module:{
        rules:[
            // 定义了不同模块对应的 loader
            //use指定的顺序不能改变,要按顺序(从后往前调用)
            {test:/\.less$/,use:['style-loader','css-loader']}
        ]
   }
}

 4. 打包处理 less 文件

① 运行 npm i less-loader@10.0.1 less@4.1.1 -D 命令

② 在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则如下:

 


// 导入样式(在 webpack 中,一切皆模块,都可以通过 ES6 导入语法进行导入和使用)
// 如果某个模块中,使用 from 接收到的成员为 undefined,则没必要进行接收
//因为index1.js文件是模块导入的入口文件,并且bundle.js是由本文件(index1.js)产生的
//所以在这个文件导入样式
import'./css/index.css';


module.exports = {
   module:{
        rules:[
            // 处理 .less 文件的 loader
            {test:/\.less$/,use:['style-loader','css-loader','less-loader']}
        ]
   }
}

其中,test 表示匹配的文件类型, use 表示对应要调用的 loader 注意:

⚫ use 数组中指定的 loader 顺序是固定的

⚫ 多个 loader 的调用顺序是:从后往前调用

5. 打包处理样式表中与 url 路径相关的文件

① 运行 npm i url-loader@4.1.1 file-loader@6.2.0 -D 命令

② 在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则如下:

如果需要调用的 loader 只有一个,则只传递一个字符串也行,如果有多个loader,则必须指定数组

其中 ? 之后的是 loader 的参数项:

⚫ limit 用来指定图片的大小,单位是字节(byte)

只有 ≤ limit 大小的图片,才会被转为 base64 格式的图片

图片base64:将图片转换为base64的形式:console.log(logo)


// 导入样式(在 webpack 中,一切皆模块,都可以通过 ES6 导入语法进行导入和使用)
// 如果某个模块中,使用 from 接收到的成员为 undefined,则没必要进行接收
//因为index1.js文件是模块导入的入口文件,并且bundle.js是由本文件(index1.js)产生的
//所以在这个文件导入样式
import'./css/index.less';

module.exports = {
   module:{
        rules:[
            // 处理图片文件的 loader
            // 如果需要调用的 loader 只有一个,则只传递一个字符串也行,如果有多个loader,则必须指定数组
            // 在配置 url-loader 的时候,多个参数之间,使用 & 符号进行分隔
            { test: /\.jpg|png|gif$/, use: 'url-loader' },
        ]
   }
}

 6. 打包处理 js 文件中的高级语法

         6.1 安装 babel-loader 相关的包

运行如下的命令安装对应的依赖包: npm i babel-loader@8.2.2 @babel/core@7.14.6 @babel/plugin-proposal-decorators@7.14.5 -D

在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则如下:

         6.2 配置 babel-loade【创建名为bable.config.js跟webpack.config.js同级】

在项目根目录下,创建名为 babel.config.js 的配置文件,定义 Babel 的配置项如下:

module.exports = {
    // 声明 babel 可用的插件
    // 将来,webpack 在调用 babel-loader 的时候,会先加载 plugins 插件来使用
    plugins: [['@babel/plugin-proposal-decorators', { legacy: true }]]
  }

5.打包发布

1. 为什么要打包发布

 2. 配置 webpack 的打包发布

在 package.json 文件的 scripts 节点下,新增 build 命令如下:

 --model 是一个参数项,用来指定 webpack 的运行模式。

production 代表生产环境,会对打包生成的文件 进行代码压缩和性能优化。

注意:通过 --model 指定的参数项,会覆盖 webpack.config.js 中的 model 选项。

  "scripts": {
    "dev": "webpack serve",
    "bulid":"webpack --mode production"
  },

 3. 把 JavaScript 文件统一生成到 js 目录中

//   __dirname--当前文件所处的根目录
  // entry: '指定要处理哪个文件'
    entry:path.join(__dirname,'./src/index1.js'),
    // 指定生成的文件要存放到哪里
    output:{
        // 存放的目录
        path:path.join(__dirname,'./dist'),
        // 生成的文件名
        //表示新产生的文件存放在dist文件夹下的js文件夹目录下
        filename:'js/bundle.js'
    },

 4. 把图片文件统一生成到 image 目录中

   module:{
        rules:[
            // 处理图片文件的 loader
            // 如果需要调用的 loader 只有一个,则只传递一个字符串也行,如果有多个loader,则必须指定数组
            // 在配置 url-loader 的时候,多个参数之间,使用 & 符号进行分隔
            // &outputPath=images--表示生成文件的存放路径
            { test: /\.jpg|png|gif$/, use: 'url-loader?limit=470&outputPath=images' },

        ]
   }

 5. 自动清理 dist 目录下的旧文件

为了在每次打包发布时自动清理掉 dist 目录中的旧文件,可以安装并配置 clean-webpack-plugin 插件:


// 注意:左侧的 { } 是解构赋值
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

  // 3. 插件的数组,将来 webpack 在运行时,会加载并调用这些插件
  plugins:[htmlPlugin, new CleanWebpackPlugin()],

6.Source Map

1. 生产环境遇到的问题

 2. 什么是 Source Map:一个信息文件,里面储存着位置信息

 3. webpack 开发环境下的 Source Map

         3.1 默认 Source Map 的问题:记录是生成后的代码位置

              3.2 解决默认 Source Map 的问题:在webpack.config.js文件中进行配置

开发环境下,推荐在 webpack.config.js 中添加如下的配置,即可保证运行时报错的行数与源代码的行数 保持一致:

module.exports = {
  // 在开发调试阶段,建议大家都把 devtool 的值设置为 eval-source-map
  // devtool: 'eval-source-map',
  // 在实际发布的时候,建议大家把 devtool 的值设置为 nosources-source-map 或直接关闭 SourceMap
  devtool:'nosources-source-map',
    // mode 代表 webpack 运行的模式,可选值有两个 development 和 production
    // 结论:开发时候一定要用 development,因为追求的是打包的速度,而不是体积;
    // 反过来,发布上线的时候一定能要用 production,因为上线追求的是体积小,而不是打包速度快!
  mode: 'development',
}

4. webpack 生产环境下的 Source Map【要关闭Source Map】

在生产环境下,如果省略了 devtool 选项,则最终生成的文件中不包含 Source Map。这能够防止原始代码通 过 Source Map 的形式暴露给别有所图之人。

         4.1 只定位行数不暴露源码: devtool:'nosources-source-map'

在生产环境下,如果只想定位报错的具体行数,且不想暴露源码。此时可以将 devtool 的值设置为 nosources-source-map。实际效果如图所示:

         4.2 定位行数且暴露源码:devtool:'source-map'

在生产环境下,如果想在定位报错行数的同时,展示具体报错的源码。此时可以将 devtool 的值设置为 source-map。实际效果如图所示:

采用此选项后:你应该将你的服务器配置为,不允许普通用户访问 source map 文件! 

5. Source Map 的最佳实践 

① 开发环境下:

建议把 devtool 的值设置为 eval-source-map

⚫ 好处:可以精准定位到具体的错误行

② 生产环境下:

建议关闭 Source Map 或将 devtool 的值设置为 nosources-source-map

⚫ 好处:防止源码泄露,提高网站的安全性

 实际开发中需要自己配置 webpack 吗?

@的使用:代表src这个根目录【使用前要先进行配置】

配置代码:在webpack.config.js这个文件中进行配置的

 module.exports = {
  resolve:{
    alias:{
      // 告诉 webpack,程序员写的代码中,@ 符号表示 src 这一层目录
      '@':path.join(__dirname,'./src/')
    }
  }
}

演示代码:

// import'./css/index.css';
//跟下面等价
import'@/css/index.css';

// import'./css/index.less';
//跟下面等价
import'@/css/index.less';


// 导入 src/js/test/info.js
import '@/js/test/info.js'

 总结

二、vue基础入门

1.vue简介

1. 什么是 vue

 1. 构建用户界面
    用 vue 往 html 页面中填充数据,非常的方便
2. 框架
     框架是一套现成的解决方案,程序员只能遵守框架的规范,去编写自己的业务功能!
     要学习 vue,就是在学习 vue 框架中规定的用法!
      vue 的指令、组件(是对 UI 结构的复用)、路由、Vuex、vue 组件库
     只有把上面老师罗列的内容掌握以后,才有开发 vue 项目的能力!

2. vue 的特性

         2.1 数据驱动视图(单向数据绑定-从服务器到客户端)

数据驱动视图:

   + 数据的变化会驱动视图自动更新
   + 好处:程序员只管把数据维护好,那么页面结构会被 vue 自动渲染出来!

         2.2 双向数据绑定

在填写表单时,双向数据绑定可以辅助开发者在不操作 DOM 的前提下,自动把用户填写的内容同步到数据源 中。示意图如下:

 双向数据绑定:

   > 在网页中,form 表单负责**采集数据**,Ajax 负责**提交数据**。

   + js 数据的变化,会被自动渲染到页面上
   + 页面上表单采集的数据发生变化的时候,会被 vue 自动获取到,并更新到 js 数据中开发者不再需要手动操作 DOM 元素,来获取表单元素最新的值)

> 注意:数据驱动视图和双向数据绑定的底层原理是 MVVM(Mode 数据源、View 视图、ViewModel 就是 vue 的实例)

         2.3 MVVM:是 vue 实现数据驱动视图双向数据绑定的核心原理

MVVM 是 vue 实现数据驱动视图和双向数据绑定的核心原理。MVVM 指的是 Model、View 和 ViewModel, 它把每个 HTML 页面都拆分成了这三个部分,如图所示:

         2.4 MVVM 的工作原理:把当前页面的数据源(Model)页面的结构(View)连接在了一起

ViewModel 作为 MVVM 的核心,是它把当前页面的数据源(Model)和页面的结构(View)连接在了一起。

注意:数据驱动视图和双向数据绑定的底层原理是 MVVM(Mode 数据源【data】、View 视图【el】、ViewModel 【vm】就是 vue 的实例) 

 3. vue 的版本

2.vue的基本使用

1. 基本使用步骤

① 导入 vue.js 的 script 脚本文件

② 在页面中声明一个将要被 vue 所控制的 DOM 区域

③ 创建 vm 实例对象(vue 实例对象)

        “el”---是指定当前要控制页面的哪一块(最好先用div将整个括起来,然后给div加上一个id,然后将el赋值给这个id)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
<!-- 希望Vue能够控制下面的这个div 帮我们把数据填充到div内部 -->
<div id="app">{{ username }}</div>

    <!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 -->
    <script src="./lib/vue-2.6.12.js"></script>

    <script>
        //创建Vue实例对象
        const vm=new Vue({
      // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
            el:'#app',
      // data 对象就是要渲染到页面上的数据
            data:{
                username:'zhangsan'
            }
        })
    </script>
</body>
</html>

 2. 基本代码与 MVVM 的对应关系

3.vue的调试工具

1. 安装 vue-devtools 调试工具

 2. 配置 Chrome 浏览器中的 vue-devtools

 3. 使用 vue-devtools 调试 vue 页面

4.vue的指令与过滤器

1. 指令的概念

         1.1 内容渲染指令

 v-text:会覆盖元素内默认的值

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
<!-- 希望Vue能够控制下面的这个div 帮我们把数据填充到div内部 -->
<div id="app">
    <p v-text="username"></p>
    <!-- “女”将“性别”覆盖住了 -->
    <p v-text="gender">性别</p>
</div>

    <!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 -->
    <script src="./lib/vue-2.6.12.js"></script>

    <script>
        //创建Vue实例对象
        const vm=new Vue({
      // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
            el:'#app',
      // data 对象就是要渲染到页面上的数据
            data:{
                username:'zhangsan',
                gender:'女'
            }
        })
    </script>
</body>
</html>

 {{ }} 语法插值表达式】:不会将默认值覆盖【开发中常用】

vue 提供的 {{ }} 语法,专门用来解决 v-text 会覆盖默认文本内容的问题。这种 {{ }} 语法的专业名称是插值表达 式(英文名为:Mustache)。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
<!-- 希望Vue能够控制下面的这个div 帮我们把数据填充到div内部 -->
<div id="app">



    <hr>

    <p>姓名:{{ username }}</p>
    <p>性别:{{ gender }}</p>

    <hr>

    

</div>

    <!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 -->
    <script src="./lib/vue-2.6.12.js"></script>

    <script>
        //创建Vue实例对象
        const vm=new Vue({
      // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
            el:'#app',
      // data 对象就是要渲染到页面上的数据
            data:{
                username:'zhangsan',
                gender:'女'
            }
        })
    </script>
</body>
</html>

注意:相对于 v-text 指令来说,插值表达式在开发中更常用一些!因为它不会覆盖元素中默认的文本内容。

 v-html

v-text 指令和插值表达式只能渲染纯文本内容。如果要把包含 HTML 标签的字符串渲染为页面的 HTML 元素, 则需要用到 v-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>Document</title>
</head>
<body>
<!-- 希望Vue能够控制下面的这个div 帮我们把数据填充到div内部 -->
<div id="app">

    
    <div v-text="info"></div>
    <div>{{ info }}</div>
    <div v-html="info"></div>

</div>

    <!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 -->
    <script src="./lib/vue-2.6.12.js"></script>

    <script>
        //创建Vue实例对象
        const vm=new Vue({
      // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
            el:'#app',
      // data 对象就是要渲染到页面上的数据
            data:{
                username:'zhangsan',
                gender:'女',
        info: '<h4 style="color: red; font-weight: bold;">欢迎大家来学习 vue.js</h4>'
            }
        })
    </script>
</body>
</html>

总结

1. `v-text` 指令的缺点:会覆盖元素内部原有的内容!
2. `{{ }}` 插值表达式:在实际开发中用的最多,只是内容的占位符,不会覆盖原有的内容!
3. `v-html` 指令的作用:可以把带有标签的字符串,渲染成真正的 HTML 内容!

   1.2 属性绑定指令:v-bind:【记得加上冒号】

如果需要为元素的属性动态绑定属性值,则需要用到 v-bind 属性绑定指令。用法示例如下:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
  <div id="app">
    <input type="text" v-bind:placeholder="tips">
    <hr>
    <!-- vue 规定 v-bind: 指令可以简写为 : -->
    <img v-bind:src="photo" alt="">
  </div>

  <!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 -->
  <script src="./lib/vue-2.6.12.js"></script>
  <!-- 2. 创建 Vue 的实例对象 -->
  <script>
    // 创建 Vue 的实例对象
    const vm = new Vue({
      // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
      el: '#app',
      // data 对象就是要渲染到页面上的数据
      data: {
        tips: '请输入用户名',
        photo:'https://www.h5w3.com/wp-content/uploads/2020/05/1460000022734939.png'
      }
    })
  </script>
</body>

</html>

         属性绑定指令的简写形式

  vue 规定 v-bind: 指令可以简写为 ":"

 <img v-bind:src="photo" alt="">   等价于    <img :src="photo" alt="">

 使用 Javascript 表达式

在 vue 提供的模板渲染语法中,除了支持绑定简单的数据值之外,还支持 Javascript 表达式的运算,例如:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
  <div id="app">

    <input type="text" v-bind:placeholder="tips">


    <hr>
    <!-- vue 规定 v-bind: 指令可以简写为 : -->
    <img v-bind:src="photo" alt="">

    <hr>
    <div>1+2 的结果是:{{ 1+2 }}</div>
    <div>{{ tips }}反转的结果是:{{ tips.split(' ').reverse().join() }}</div>
    <!-- 记得给box加上单引号 -->
    <!-- box相当于一个字符串 -->
    <div :title="'box' + index">这是一个div</div>

  </div>

  <!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 -->
  <script src="./lib/vue-2.6.12.js"></script>
  <!-- 2. 创建 Vue 的实例对象 -->
  <script>
    // 创建 Vue 的实例对象
    const vm = new Vue({
      // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
      el: '#app',
      // data 对象就是要渲染到页面上的数据
      data: {
        tips: '请输入用户名',
        photo:'https://www.h5w3.com/wp-content/uploads/2020/05/1460000022734939.png',
        index:3
      }
    })
  </script>
</body>

</html>

 总结:

>  注意:插值表达式只能用在元素的**内容节点**中,不能用在元素的**属性节点**中

+ 在 vue 中,可以使用 `v-bind:` 指令,为元素的属性动态绑定值;

+ 简写是英文的 `:`

+ 在使用 v-bind 属性绑定期间,如果绑定内容需要进行动态拼接,则字符串的外面应该包裹单引号,例如:


  <div :title=" 'box' + index">这是一个 div</div>

        1.3 事件绑定指令:v-on【methods节点中进行声明】

vue 提供了 v-on 事件绑定指令,用来辅助程序员为 DOM 元素绑定事件监听。语法格式如下:

 注意:原生 DOM 对象有 onclick、oninput、onkeyup 等原生事件,替换为 vue 的事件绑定形式后, 分别为:v-on:click(@click)v-on:input(@input)v-on:keyup(@keyup)

 通过 v-on 绑定的事件处理函数,需要在 methods 节点中进行声明

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
  <div id="app">
    <p>count 的值是 {{ count }}</p>
    <!-- <button v-on:click="add">+1</button> -->
    <button @click="add">+1</button>

    <!-- <button v-on:cilck="sub">-1</button> -->
    <button @cilck="sub">-1</button>

  </div>

  <!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 -->
  <script src="./lib/vue-2.6.12.js"></script>
  <!-- 2. 创建 Vue 的实例对象 -->
  <script>
    // 创建 Vue 的实例对象
    const vm = new Vue({
      // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
      el: '#app',
      // data 对象就是要渲染到页面上的数据
      data: {
        count: 0
      },
      // methods 的作用,就是给v-on定义事件的处理函数
      methods:{
        add(){//add:function(){
          console.log('ok');
        },
        sub(){//sub:function()
          console.log('触发了sub处理函数');
        }
      }
    })
  </script>
</body>

</html>

         事件绑定的简写形式:@

由于 v-on 指令在开发中使用频率非常高,因此,vue 官方为其提供了简写形式(简写为英文的 @ )。

     

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
  <div id="app">
    <p>count 的值是 {{ count }}</p>
    <!-- <button v-on:click="add">+1</button> -->
    <button @click="add">+1</button>

    <!-- <button v-on:cilck="sub">-1</button> -->
    <button @cilck="sub">-1</button>

  </div>

  <!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 -->
  <script src="./lib/vue-2.6.12.js"></script>
  <!-- 2. 创建 Vue 的实例对象 -->
  <script>
    // 创建 Vue 的实例对象
    const vm = new Vue({
      // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
      el: '#app',
      // data 对象就是要渲染到页面上的数据
      data: {
        count: 0
      },
      // methods 的作用,就是给v-on定义事件的处理函数
      methods:{
        add(){//add:function(){
          console.log('ok');
        },
        sub(){//sub:function()
          console.log('触发了sub处理函数');
        }
      }
    })
  </script>
</body>

</html>

   事件参数对象:实例对象===this【使用this来调用】

在原生的 DOM 事件绑定中,可以在事件处理函数的形参处,接收事件参数对象 event。同理,在 v-on 指令 (简写为 @ )所绑定的事件处理函数中,同样可以接收到事件参数对象 event,示例代码如下:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
  <div id="app">
    <p>count 的值是 {{ count }}</p>
    <!-- <button v-on:click="add">+1</button> -->
    <button @click="add">+1</button>

  </div>

  <!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 -->
  <script src="./lib/vue-2.6.12.js"></script>
  <!-- 2. 创建 Vue 的实例对象 -->
  <script>
    // 创建 Vue 的实例对象
    const vm = new Vue({
      // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
      el: '#app',
      // data 对象就是要渲染到页面上的数据
      data: {
        count: 0
      },
      // methods 的作用,就是给v-on定义事件的处理函数
      methods:{
        add(){//add:function(){
          console.log(vm);
          // console.log(vm===this);//true
          // vm.count+=1;
          //当点击add的时候,count+1
          this.count+=1;
        }
        }
      }
    })
  </script>
</body>

</html>

         绑定事件并传参:可以使用(参数)进行传参

    <!-- 当点击add的时候,一起将参数进行传入 -->
    <button @click="add(4)">+4</button>


  <script>
    // 创建 Vue 的实例对象
    const vm = new Vue({
      // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
      el: '#app',
      // data 对象就是要渲染到页面上的数据
      data: {
        count: 0
      },
      // methods 的作用,就是给v-on定义事件的处理函数
      methods:{
        add(n){//add:function(){
          // 在 methods 处理函数中,this 就是 new 出来的 vm 实例对象
          console.log(vm);
          // console.log(vm===this);//true
          // vm.count+=1;
          //当点击add的时候,count+1
          this.count+=1;
          //当点击add的时候,一起将参数进行传入
          this.count+=n;
        }
      }
    })
  </script>

      $event:表示原生的事件参数对象event

$event 是 vue 提供的特殊变量,用来表示原生的事件参数对象 event。$event 可以解决事件参数对象 event 被覆盖的问题。示例用法如下:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
  <div id="app">
    <p>count 的值是:{{ count }}</p>
    <!-- 如果 count 是偶数,则 按钮背景变成红色,否则,取消背景颜色 -->
    <!-- <button @click="add(1)">+N</button> -->
    <!-- vue 提供了内置变量,名字叫做 $event,它就是原生 DOM 的事件对象 e -->
    <button @click="add($event, 1)">+N</button>
  </div>

  <!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 -->
  <script src="./lib/vue-2.6.12.js"></script>
  <!-- 2. 创建 Vue 的实例对象 -->
  <script>
    // 创建 Vue 的实例对象
    const vm = new Vue({
      // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
      el: '#app',
      // data 对象就是要渲染到页面上的数据
      data: {
        count: 0
      },
      methods: {
        add(e,n){
          this.count+=n;
          console.log(e);
          // 判断 this.count 的值是否为偶数
          if(this.count%2==0){
            //偶数
            // e.target---鼠标点击事件
            e.target.style.backgroundColor = 'red'
          }else{
            //奇数
            e.target.style.backgroundColor = ''
          }
        }
      },
    })
  </script>
</body>

</html>

         事件修饰符:【prevent,stop】@click.stop="divHandler"

在事件处理函数中调用 event.preventDefault() 或 event.stopPropagation() 是非常常见的需求。因此, vue 提供了事件修饰符的概念,来辅助程序员更方便的对事件的触发进行控制。常用的 5 个事件修饰符如下:

 

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
  <div id="app">
    <a href="http://www.baidu.com" @click.prevent="show">跳转到百度页面</a>

    <hr>
    <div style="height: 150px;background-color: orange;padding-left: 100px;line-height: 150px;" @click.stop="divHandler">
      <button @click.stop="btnHandler">按钮</button>
    </div>
  </div>

  <!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 -->
  <script src="./lib/vue-2.6.12.js"></script>
  <!-- 2. 创建 Vue 的实例对象 -->
  <script>
    // 创建 Vue 的实例对象
    const vm = new Vue({
      // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
      el: '#app',
      // data 对象就是要渲染到页面上的数据
      data: {},
      methods:{
        show(e){
          // 阻止事件e的默认行为
          // e.preventDefault()
          console.log('点击了a');
        },
        btnHandler(){//btnHandler(e){e.stop();//阻止冒泡}
          console.log('btnHandler');
        },
        divHandler(){
          console.log('divHandler');
        }
      }
    })
  </script>
</body>

</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>Document</title>
</head>

<body>
  <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
  <div id="app">
    <!-- @keyup.esc="clearInput"---表示当我们按下键盘中的“esc”的时候,就触发clearInput事件 -->
    <input type="text" @keyup.esc="clearInput" @keyup.enter="commitAjax" >
  </div>

  <!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 -->
  <script src="./lib/vue-2.6.12.js"></script>
  <!-- 2. 创建 Vue 的实例对象 -->
  <script>
    // 创建 Vue 的实例对象
    const vm = new Vue({
      // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
      el: '#app',
      // data 对象就是要渲染到页面上的数据
      data: {},
      methods:{
        clearInput(e){//e--接收事件
          console.log('触发了clearInput事件');
          e.target.value='';
        },
        commitAjax(){
          console.log('触发了commitAjax请求');
        }
      }
    })
  </script>
</body>

</html>

 1.4 双向绑定指令:v-model

vue 提供了 v-model 双向数据绑定指令,用来辅助开发者在不操作 DOM 的前提下,快速获取表单的数据。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
  <div id="app">
    <p>用户的名字是:{{ username }}</p>
    <!-- 当在页面对v-model中的文字进行修改,vm中的data也会被修改【双向数据修改】 -->
    <input type="text" v-model="username">
    <hr>
    <!-- value--是文本框中的默认值,如果这个value中的值被修改,数据源并不会改变【单向数据修改】 -->
    <input type="text" :value="username">
    <hr>
    <!-- v-model只能跟表单元素进行交互:input,textarea,select -->
    <select v-model="city">
      <option value="">请选择城市</option>
      <option value="1">北京</option>
      <option value="2">上海</option>
      <option value="3">广州</option>
    </select>
  </div>

  <!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 -->
  <script src="./lib/vue-2.6.12.js"></script>
  <!-- 2. 创建 Vue 的实例对象 -->
  <script>
    // 创建 Vue 的实例对象
    const vm = new Vue({
      // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
      el: '#app',
      // data 对象就是要渲染到页面上的数据
      data: {
        username: 'zhangsan',
        // 默认选中value为2的
        city: '2'
      }
    })
  </script>
</body>

</html>

 v-model 指令的修饰符:v-model.number="n1"

为了方便对用户输入的内容进行处理,vue 为 v-model 指令提供了 3 个修饰符,分别是:

 

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <!-- 希望 Vue 能够控制下面的这个 div,帮我们把数据填充到 div 内部 -->
  <div id="app">
    <!-- v-model.number=""  以防在进行数值计算的时候输入的是字符串 -->
    <input type="text" v-model.number="n1">+<input type="text" v-model.number="n2">=<span>{{n1+n2 }}</span>
    <hr>
    <!-- v-model.trim=""   去除username中前后的空格  中间的空格不去除 -->
    <input type="text" v-model.trim="username">
    <button @click="showName">获取用户名</button>
    <hr>
    <!-- v-model.lazy=""   防抖,不会每一次删除文本框中的文字的时候就进行更新,而是在删除结束后(就是失去焦点的时候)才更新 -->
    <input type="text" v-model.lazy="username">
  </div>

  <!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 -->
  <script src="./lib/vue-2.6.12.js"></script>
  <!-- 2. 创建 Vue 的实例对象 -->
  <script>
    // 创建 Vue 的实例对象
    const vm = new Vue({
      // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
      el: '#app',
      // data 对象就是要渲染到页面上的数据
      data: {
        username: 'zhangsan',
        n1: 1,
        n2: 2
      },
      methods:{
        showName(){
          // 里面用的是模板字符串
        console.log(`用户名是"${this.username}"`)
        }
      }
      
    })
  </script>
</body>

</html>

 1.5 条件渲染指令:v-if/v-show

条件渲染指令用来辅助开发者按需控制 DOM 的显示与隐藏。条件渲染指令有如下两个,分别是: ⚫ v-if

⚫ v-show

 v-if 和 v-show 的区别

1. `v-show` 的原理是:动态为元素添加或移除 `display: none` 样式,来实现元素的显示和隐藏
     如果要频繁的切换元素的显示状态,用 v-show 性能会更好
2. `v-if` 的原理是:每次动态创建或移除元素,实现元素的显示和隐藏
     如果刚进入页面的时候,某些元素默认不需要被展示,而且后期这个元素很可能也不需要被展示出来,此时 v-if 性能更好

>  在实际开发中,绝大多数情况,不用考虑性能问题,直接使用 v-if 就好了!!!

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <!-- 希望 Vue 能够控制下面的这个 div,帮我们把数据填充到 div 内部 -->
  <div id="app">
    <!-- 当flag=false的时候  v-if  会被动态删除,v-show 会添加上style="none" -->
    <p v-if="flag">这是被 v-if 控制的元素</p>
    <p v-show="flag">这是被 v-show 控制的元素</p>
  </div>

  <!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 -->
  <script src="./lib/vue-2.6.12.js"></script>
  <!-- 2. 创建 Vue 的实例对象 -->
  <script>
    // 创建 Vue 的实例对象
    const vm = new Vue({
      // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
      el: '#app',
      // data 对象就是要渲染到页面上的数据
      data: {
        // 如果 flag 为 true,则显示被控制的元素;如果为 false 则隐藏被控制的元素
        flag: false,
      }
    })
  </script>
</body>

</html>

 v-else:后面不用加条件

 v-else-if:一定要跟v-if一起使用

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <!-- 希望 Vue 能够控制下面的这个 div,帮我们把数据填充到 div 内部 -->

    <hr>
    <div v-if="type === 'A'">优秀</div>
    <div v-else-if="type === 'B'">良好</div>
    <div v-else-if="type === 'C'">一般</div>
    <div v-else>差</div>
  </div>

  <!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 -->
  <script src="./lib/vue-2.6.12.js"></script>
  <!-- 2. 创建 Vue 的实例对象 -->
  <script>
    // 创建 Vue 的实例对象
    const vm = new Vue({
      // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
      el: '#app',
      // data 对象就是要渲染到页面上的数据
      data: {
        type: 'A'
      }
    })
  </script>
</body>

</html>

 1.6 列表渲染指令:v-for【data:{list:[]}】

vue 提供了 v-for 列表渲染指令,用来辅助开发者基于一个数组来循环渲染一个列表结构。

v-for 指令需要使 用 item in items 形式的特殊语法,其中:

⚫ items 是待循环的数组

⚫ item 是被循环的每一项

 v-for 中的索引:(item, index) in items

v-for 指令还支持一个可选的第二个参数,即当前项的索引。语法格式为 (item, index) in items,示例代码如下:

 注意:v-for 指令中的 item 项和 index 索引都是形参,可以根据需要进行重命名。例如 (user, i) in userlist

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link rel="stylesheet" href="./lib/bootstrap.css">
</head>

<body>
  <!-- 希望 Vue 能够控制下面的这个 div,帮我们把数据填充到 div 内部 -->
  <div id="app">
    <table class="table table-bordered table-hover table-striped">
      <thead>
        <th>索引</th>
        <th>Id</th>
        <th>姓名</th>
      </thead>
      <tbody>
        <!-- item in list---item可以自己定 -->
        <tr v-for="(item, index) in list" >
          <!-- index--索引号 -->
          <td>{{ index }}</td>
          <td>{{ item.id }}</td>
          <td>{{ item.name }}</td>
        </tr>
      </tbody>
    </table>
  </div>

  <!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 -->
  <script src="./lib/vue-2.6.12.js"></script>
  <!-- 2. 创建 Vue 的实例对象 -->
  <script>
    // 创建 Vue 的实例对象
    const vm = new Vue({
      // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
      el: '#app',
      // data 对象就是要渲染到页面上的数据
      data: {
        list: [
          { id: 1, name: '张三' },
          { id: 2, name: '李四' },
          { id: 3, name: '王五' },
          { id: 4, name: '张三' },
        ]
      }
    })
  </script>
</body>

</html>

使用 key 维护列表的状态:要用到了 v-for 指令,那么一定要绑定一个 :key 属性(尽量使用id)

当列表的数据变化时,默认情况下,vue 会尽可能的复用已存在的 DOM 元素,从而提升渲染的性能。但这种 默认的性能优化策略,会导致有状态的列表无法被正确更新。 为了给 vue 一个提示,以便它能跟踪每个节点的身份,从而在保证有状态的列表被正确更新的前提下,提升渲 染的性能。此时,需要为每项提供一个唯一的 key 属性:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link rel="stylesheet" href="./lib/bootstrap.css">
</head>

<body>
  <!-- 希望 Vue 能够控制下面的这个 div,帮我们把数据填充到 div 内部 -->
  <div id="app">
    <table class="table table-bordered table-hover table-striped">
      <thead>
        <th>索引</th>
        <th>Id</th>
        <th>姓名</th>
      </thead>
      <tbody>
        <!-- 官方建议:只要用到了 v-for 指令,那么一定要绑定一个 :key 属性 -->
        <!-- 而且,尽量把 id 作为 key 的值 -->
        <!-- 官方对 key 的值类型,是有要求的:字符串或数字类型 -->
        <!-- key 的值是千万不能重复的,否则会终端报错:Duplicate keys detected -->
        <!-- item in list---item可以自己定 -->
        <tr v-for="(item, index) in list" :key="item.id">
          <!-- index--索引号 -->
          <td>{{ index }}</td>
          <td>{{ item.id }}</td>
          <td>{{ item.name }}</td>
        </tr>
      </tbody>
    </table>
  </div>

  <!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 -->
  <script src="./lib/vue-2.6.12.js"></script>
  <!-- 2. 创建 Vue 的实例对象 -->
  <script>
    // 创建 Vue 的实例对象
    const vm = new Vue({
      // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
      el: '#app',
      // data 对象就是要渲染到页面上的数据
      data: {
        list: [
          { id: 1, name: '张三' },
          { id: 2, name: '李四' },
          { id: 3, name: '王五' },
          { id: 4, name: '张三' },
        ]
      }
    })
  </script>
</body>

</html>

 key 的注意事项

key 的值只能是字符串或数字类型

② key 的值必须具有唯一性(即:key 的值不能重复)

③ 建议把数据项 id 属性的值作为 key 的值(因为 id 属性的值具有唯一性)

使用 index 的值当作 key 的值没有任何意义(因为 index 的值不具有唯一性)--因为index跟数据没有强制的绑定关系

建议使用 v-for 指令时一定要指定 key 的值(既提升性能、又防止列表状态紊乱)

key不能选索引值为key值

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <!-- 在页面中声明一个将要被 vue 所控制的 DOM 区域 -->
  <div id="app">

    <!-- 添加用户的区域 -->
    <div>
      <input type="text" v-model="name">
      <button @click="addNewUser">添加</button>
    </div>

    <!-- 用户列表区域 -->
    <ul>
      <li v-for="(user, index) in userlist" :key="user.index">
        <input type="checkbox" />
        姓名:{{user.name}}
      </li>
    </ul>
  </div>

  <script src="./lib/vue-2.6.12.js"></script>
  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        // 用户列表
        userlist: [
          { id: 1, name: 'zs' },
          { id: 2, name: 'ls' }
        ],
        // 输入的用户名
        name: '',
        // 下一个可用的 id 值
        nextId: 3
      },
      methods: {
        // 点击了添加按钮
        addNewUser() {
          this.userlist.unshift({ id: this.nextId, name: this.name })
          this.name = ''
          this.nextId++
        }
      },
    })
  </script>
</body>

</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>
  <link rel="stylesheet" href="./lib/bootstrap.css">
  <link rel="stylesheet" href="./css/brandlist.css">
</head>

<body>

  <div id="app">
    <!-- 卡片区域 -->
    <div class="card">
      <div class="card-header">
        添加品牌
      </div>
      <div class="card-body">
        <!-- 添加品牌的表单区域 -->
        <!-- form 表单元素有 submit 事件 -->
        <!-- 阻止按钮的默认提交行为 并触发add这个行为 -->
        <form @submit.prevent="add">
          <div class="form-row align-items-center">
            <div class="col-auto">
              <div class="input-group mb-2">
                <div class="input-group-prepend">
                  <div class="input-group-text">品牌名称</div>
                </div>
                <!-- 给添加按钮添加点击事件 -->
                <!-- 因为我们点击按钮要获取文本框中用户输入的内容,最快的方法就是直接使用v-model双向获取数据 -->
                <input type="text" class="form-control" placeholder="请输入品牌名称" v-model.trim="brand">
              </div>
            </div>
            <div class="col-auto">
              <button type="submit" class="btn btn-primary mb-2">添加</button>
            </div>
          </div>
        </form>
      </div>
    </div>

    <!-- 表格区域 -->
    <table class="table table-bordered table-hover table-striped">
      <thead>
        <tr>
          <th scope="col">#</th>
          <th scope="col">品牌名称</th>
          <th scope="col">状态</th>
          <th scope="col">创建时间</th>
          <th scope="col">操作</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="item in list" :key="item.id">
          <td>{{  item.id }}</td>
          <td>{{item.name}}</td>
          <td>
            <div class="custom-control custom-switch">
              <!-- 因为表单中的禁用和启动是双向的数据交互,所以使用v-model -->
              <!-- 使用 v-model 实现双向数据绑定 -->
              <!-- 给id添加动态绑定,让id后面跟着item.id -->
              <input type="checkbox" class="custom-control-input" :id="'cb' + item.id" v-model="item.status">
              <!-- 当statue为true的时候,就为启动,为false的时候为禁用,所以要使用v-if和v-else -->
              <!-- 使用 v-if 结合 v-else 实现按需渲染 -->
              <label class="custom-control-label" :for="'cb' + item.id" v-if="item.status">已启用</label>
              <label class="custom-control-label" :for="'cb' + item.id" v-else>已禁用</label>
            </div>
          </td>
          <td>{{item.time}}</td>
          <td>
            <!-- 给删除按钮绑定一个点击事件 并且传入要进行删除的id -->
            <a href="javascript:;" @click="remove(item.id)">删除</a>
          </td>
        </tr>
      </tbody>
    </table>
  </div>

  <script src="./lib/vue-2.6.12.js"></script>
  <script>
    //1.创建实例对象
    const vm=new Vue({
      el:'#app',
      data:{
        //用户输入的匹配名称
        brand:'',
        // nextId 是下一个,可用的 id
        nextId: 4,
        //品牌的列表数据
        list:[
          {id:1, name:'宝马', status:true, time:new Date()},
          {id:2, name:'奔驰', status:true, time:new Date()},
          {id:3, name:'奥迪', status:true, time:new Date()},
        ]
      },
      methods:{
        //点击链接,删除对应的数据
          remove(id){
            // console.log(id);
            // filter--过滤,返回一个数组
            // filter--返回不满足条件的数据
            this.list=this.list.filter(item=>item.id!==id)
          },
          //阻止表单的默认提交行为之后,触发add行为
          add(){
            // console.log(this.brand);
            // 如果判断brand的值为空字符串,则return出去
            if(this.brand===''){
              alert('必须填写汽车名称')
              return
            }
          // 如果没有被 return 出去,应该执行添加的逻辑
          // 1. 先把要添加的品牌对象,整理出来
            const obj={
              id:this.nextId,
              name:this.brand,
              status:true,
              time:new Date()
            }
            //2.把this.list数组中push步骤1中得到的对象
          this.list.push(obj)

            // 3.清空this.brand,让this.nextId自增1
            this.brand=''
            this.nextId++;
          }
        }
    })

  </script>


</body>

</html>

2. 过滤器:过滤器本质上是函数

过滤器(Filters)是 vue 为开发者提供的功能,常用于文本的格式化。过滤器可以用在两个地方:插值表达式 和 v-bind 属性绑定。

过滤器应该被添加在 JavaScript 表达式的尾部,由“管道符”进行调用,示例代码如下:

 2.1 定义过滤器:在filters节点中定义【过滤器中,一定要有一个返回值】

在创建 vue 实例期间,可以在 filters 节点中定义过滤器,示例代码如下:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div id="app">
    <!-- “| ”:管道符 -->
    <!-- 将message的值传递给capi,然后capi将新得到的值return出来 -->
    <p>message 的值是:{{ message | capi }}</p>
  </div>

  <script src="./lib/vue-2.6.12.js"></script>
  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        message: 'hello vue.js'
      },
      // 过滤器函数,必须被定义到 filters 节点之下
      // 过滤器本质上是函数
      filters: {
        // 注意:过滤器函数形参中的 val,永远都是“管道符”前面的那个值
        capi(val) {
          // 字符串有 charAt 方法,这个方法接收索引值,表示从字符串中把索引对应的字符,获取出来
          // val.charAt(0)--获取第一个字母
          const first = val.charAt(0).toUpperCase()
          // 字符串的 slice 方法,可以截取字符串,从指定索引往后截取
          const other = val.slice(1)
          // 强调:过滤器中,一定要有一个返回值
          return first + other
        }
      }
    })
  </script>
</body>

</html>

1. 要定义到 filters 节点下,本质是一个函数
2. 在过滤器函数中,一定要有 return 值
3. 在过滤器的形参中,可以获取到“管道符”前面待处理的那个值

 2.2 私有过滤器全局过滤器(Vue.filter('函数名',构造函数))【注意:是filter不是filters】

在 filters 节点下定义的过滤器,称为“私有过滤器”,因为它只能在当前 vm 实例所控制的 el 区域内使用。 如果希望在多个 vue 实例之间共享过滤器,则可以按照如下的格式定义全局过滤器2.3 连续调用多个过滤器

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div id="app">
    <!-- 如果自己有过滤器,择调用自己的过滤器 -->
    <p>message 的值是:{{ message | capi }}</p>
  </div>

  <div id="app2">
    <p>message 的值是:{{ message | capi }}</p>
  </div>

  <script src="./lib/vue-2.6.12.js"></script>
  <script>
    // 使用 Vue.filter() 定义全局过滤器
    Vue.filter('capi', function (str) {
      const first = str.charAt(0).toUpperCase()
      const other = str.slice(1)
      return first + other + '~~~'
    })

    const vm = new Vue({
      el: '#app',
      data: {
        message: 'hello vue.js'
      },
      // 过滤器函数,必须被定义到 filters 节点之下
      // 过滤器本质上是函数
      // 这个filters是私有过滤器,外部不能使用
      filters: {
        // 注意:过滤器函数形参中的 val,永远都是“管道符”前面的那个值
        capi(val) {
          // 字符串有 charAt 方法,这个方法接收索引值,表示从字符串中把索引对应的字符,获取出来
          // val.charAt(0)
          const first = val.charAt(0).toUpperCase()
          // 字符串的 slice 方法,可以截取字符串,从指定索引往后截取
          const other = val.slice(1)
          // 强调:过滤器中,一定要有一个返回值
          return first + other
        }
      }
    })

    // ----------------------------------

    const vm2 = new Vue({
      el: '#app2',
      data: {
        message: 'heima'
      }
    })
  </script>
</body>

</html>

 如果全局过滤器和私有过滤器名字一致,此时按照“就近原则”,调用的是”私有过滤器“

使用全局过滤器对时间进行格式化


          <!-- 使用名为dateFormat这个过滤器,使得时间进行格式化 -->
          <td>{{ item.time | dateFormat }}</td>



  <!-- 只要导入了 dayjs 的库文件,在 window 全局,就可以使用 dayjs() 方法了 -->
  <script src="./lib/dayjs.min.js"></script>

  <script>
    // 声明格式化时间的全局过滤器
    Vue.filter('dateFormat', function (time) {
      // 1. 对 time 进行格式化处理,得到 YYYY-MM-DD HH:mm:ss
      // 2. 把 格式化的结果,return 出去

      // 直接调用 dayjs() 得到的是当前时间
      // dayjs(给定的日期时间) 得到指定的日期
      const dtStr = dayjs(time).format('YYYY-MM-DD HH:mm:ss')
      return dtStr
    })
  </script>

2.3 连续调用多个过滤器 :过滤器可以串联地进行调用

 2.4 过滤器传参:本质是 JavaScript 函数,因此可以接收参数

 


 2.5 过滤器的兼容性:vue3.x中没有过滤器

5.品牌列表案例

1. 案例效果

 3. 整体实现步骤

总结

 

6.侦听器

1. 什么是 watch 侦听器:允许开发者监视数据的变化,从而针对数据的变化做特定的操作

 2.监听器定义:应该被定义到 watch 节点下

本质上是一个函数,要监视哪个数据的变化,就把数据名作为方法名即可

新值在前,旧值在后 username(newVal,oldVal)

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div id="app">
    <input type="text" v-model="username">
  </div>

  <script src="./lib/vue-2.6.12.js"></script>
  <script src="./lib/jquery-v3.6.0.js"></script>

  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        username: 'admin'
      },
      // 所有的侦听器,都应该被定义到 watch 节点下
      watch: {
        // 侦听器本质上是一个函数,要监视哪个数据的变化,就把数据名作为方法名即可
        // 新值在前,旧值在后 username(newVal,oldVal)
        username(newVal,oldVal) {
          console.log('username进行监听',newVal,oldVal);
        }
      }
    })
  </script>
</body>

</html>

3. 使用 watch 检测用户名是否可用

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div id="app">
    <input type="text" v-model="username">
  </div>

  <script src="./lib/vue-2.6.12.js"></script>
  <script src="./lib/jquery-v3.6.0.js"></script>

  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        username: 'admin'
      },
      // 所有的侦听器,都应该被定义到 watch 节点下
      watch: {
        // 侦听器本质上是一个函数,要监视哪个数据的变化,就把数据名作为方法名即可
        // 新值在前,旧值在后
        username(newVal) {
          if (newVal === '') return
          // 1. 调用 jQuery 中的 Ajax 发起请求,判断 newVal 是否被占用!!!
          $.get('https://www.escook.cn/api/finduser/' + newVal, function (result) {
            console.log(result)
          })
        }
      }
    })
  </script>
</body>

</html>

 4. 对象格式监听器:immediate 选项(immediate 选项的默认值是 false)

默认情况下,组件在初次加载完毕后不会调用 watch 侦听器。如果想让 watch 侦听器立即被调用,则需要使 用 immediate 选项。示例代码如下:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div id="app">
    <input type="text" v-model="username">
  </div>

  <script src="./lib/vue-2.6.12.js"></script>
  <script src="./lib/jquery-v3.6.0.js"></script>

  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        username: 'admin'
      },
      // 所有的侦听器,都应该被定义到 watch 节点下
      watch: {
        // 定义对象格式的侦听器
        username: {
          // 侦听器的处理函数
          // 当监听到username的时候,会自动触发handler这个函数
          handler(newVal, oldVal) {
            console.log(newVal, oldVal)
          },
          // immediate 选项的默认值是 false
          // immediate为true表示进入的时候自动触发监听
          // immediate 的作用是:控制侦听器是否自动触发一次!
          immediate: true
        }
      }
    })
  </script>
</body>

</html>

 5. 对象格式监听器:deep 选项(监听对象中的属性值)

如果 watch 侦听的是一个对象,如果对象中的属性值发生了变化,则无法被监听到。此时需要使用 deep 选 项,代码示例如下:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div id="app">
    <!-- 因为data数据中的info是一个对象,要进行深度拷贝才可以访问到 -->
    <input type="text" v-model="info.username">
    <input type="text" v-model="info.address.city">
  </div>

  <script src="./lib/vue-2.6.12.js"></script>
  <script src="./lib/jquery-v3.6.0.js"></script>

  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        // 用户的信息对象
        info: {
          username: 'admin',
          address: {
            city: '北京'
          }
        }
      },
      // 所有的侦听器,都应该被定义到 watch 节点下
      watch: {

       /* 并不会触发侦听器,因为属性是一个对象
        info(newVal){
          console.log(newVal);
        }*/

        //这个是侦听整个对象
         info: {
          handler(newVal) {
            console.log(newVal)
          },
          // 开启深度监听,只要对象中任何一个属性变化了,都会触发“对象的侦听器”
          deep: true
        } 
      }
    })
  </script>
</body>

</html>

6.对象监听器和方法监听器的优缺点

1. 方法格式的侦听器
   + 缺点1:无法在刚进入页面的时候,自动触发!!!
   + 缺点2:如果侦听的是一个对象,如果对象中的属性发生了变化,不会触发侦听器!!!
2. 对象格式的侦听器
   + 好处1:可以通过 **immediate** 选项,让侦听器自动触发!!!
   + 好处2:可以通过 **deep** 选项,让侦听器深度监听对象中每个属性的变化!!!

 7. 监听对象单个属性的变化

如果只想监听对象中单个属性的变化,则可以按照如下的方式定义 watch 侦听器:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div id="app">
    <!-- 因为data数据中的info是一个对象,要进行深度拷贝才可以访问到 -->
    <input type="text" v-model="info.username">
    <input type="text" v-model="info.address.city">
  </div>

  <script src="./lib/vue-2.6.12.js"></script>
  <script src="./lib/jquery-v3.6.0.js"></script>

  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        // 用户的信息对象
        info: {
          username: 'admin',
          address: {
            city: '北京'
          }
        }
      },
      // 所有的侦听器,都应该被定义到 watch 节点下
      watch: {

       /* 并不会触发侦听器,因为属性是一个对象
        info(newVal){
          console.log(newVal);
        }*/

        // 如果要侦听的是对象的子属性的变化,则必须包裹一层单引号
        'info.username'(newVal) {
          console.log(newVal)
        }
      }
    })
  </script>
</body>

</html>

7.计算属性

1. 什么是计算属性:computed:{}

计算属性指的是通过一系列运算之后,最终得到一个属性值。

这个动态计算出来的属性值可以被模板结构或 methods 方法使用。示例代码如下:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <script src="./lib/vue-2.6.12.js"></script>
  <style>
    .box {
      width: 200px;
      height: 200px;
      border: 1px solid #ccc;
    }
  </style>
</head>

<body>
  <div id="app">
    <div>
      <span>R:</span>
      <input type="text" v-model.number="r">
    </div>
    <div>
      <span>G:</span>
      <input type="text" v-model.number="g">
    </div>
    <div>
      <span>B:</span>
      <input type="text" v-model.number="b">
    </div>
    <hr>

    <!-- 专门用户呈现颜色的 div 盒子 -->
    <!-- 在属性身上,: 代表  v-bind: 属性绑定 -->
    <!-- :style 代表动态绑定一个样式对象,它的值是一个 {  } 样式对象 -->
    <!-- 当前的样式对象中,只包含 backgroundColor 背景颜色 -->
    <div class="box" :style="{ backgroundColor: `rgb(${r}, ${g}, ${b})` }">
      {{ `rgb(${r}, ${g}, ${b})` }}
    </div>
    <button @click="show">按钮</button>
  </div>

  <script>
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
      el: '#app',
      data: {
        // 红色
        r: 0,
        // 绿色
        g: 0,
        // 蓝色
        b: 0
      },
      methods: {
        // 点击按钮,在终端显示最新的颜色
        show() {
          console.log(`rgb(${this.r}, ${this.g}, ${this.b})`)
        }
      },
    });
  </script>
</body>

</html>

2. 计算属性的特点 

① 虽然计算属性在声明的时候被定义为方法,但是计算属性的本质是一个属性

② 计算属性会缓存计算的结果,只有计算属性依赖的数据变化时,才会重新进行运算

特点:

1. 定义的时候,要被定义为“方法”
2. 在使用计算属性的时候,当普通的属性使用即可

好处:

1. 实现了代码的复用
2. 只要计算属性中依赖的数据源变化了,则计算属性会自动重新求值!

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <script src="./lib/vue-2.6.12.js"></script>
  <style>
    .box {
      width: 200px;
      height: 200px;
      border: 1px solid #ccc;
    }
  </style>
</head>

<body>
  <div id="app">
    <div>
      <span>R:</span>
      <input type="text" v-model.number="r">
    </div>
    <div>
      <span>G:</span>
      <input type="text" v-model.number="g">
    </div>
    <div>
      <span>B:</span>
      <input type="text" v-model.number="b">
    </div>
    <hr>

    <!-- 专门用户呈现颜色的 div 盒子 -->
    <!-- 在属性身上,: 代表  v-bind: 属性绑定 -->
    <!-- :style 代表动态绑定一个样式对象,它的值是一个 {  } 样式对象 -->
    <!-- 当前的样式对象中,只包含 backgroundColor 背景颜色 -->
    <div class="box" :style="{ backgroundColor: rgb }">
      {{ rgb }}
    </div>
    <button @click="show">按钮</button>
  </div>

  <script>
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
      el: '#app',
      data: {
        // 红色
        r: 0,
        // 绿色
        g: 0,
        // 蓝色
        b: 0
      },
      methods: {
        // 点击按钮,在终端显示最新的颜色
        show() {
          console.log(this.rgb)
        }
      },
      // 所有的计算属性,都要定义到 computed 节点之下
      // 计算属性在定义的时候,要定义成“方法格式”
      computed: {
        // rgb 作为一个计算属性,被定义成了方法格式,
        // 最终,在这个方法中,要返回一个生成好的 rgb(x,x,x) 的字符串
        // rgb在声明的时候是一个函数,但是实际在vm对象中是一个属性,所以可以直接vm.rgb来调用
        rgb() {
          return `rgb(${this.r}, ${this.g}, ${this.b})`
        }
      }
    });

    console.log(vm)
  </script>
</body>

</html>

8.axios数据请求

1. 什么是axios:是专注于网络数据请求的库

2. axios发起GET请求

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

  <!-- 导入axios -->
  <script src="./lib/axios.js"></script>
  <script>
    // http://www.liulongbin.top:3006/api/getbooks

    // 1. 调用 axios 方法得到的返回值是 Promise 对象
    axios({
      // 请求方式
      method: 'GET',
      // 请求的地址
      url: 'http://www.liulongbin.top:3006/api/getbooks',
      // URL 中的查询参数
    }).then(function (result) {
      // result是套完数据之后的
      console.log(result)
    })
  </script>
</body>

</html>

 3. axios传参

1. 直接使用axios发起GET请求:使用params属性

    // 1. 调用 axios 方法得到的返回值是 Promise 对象
    axios({
      // 请求方式
      method: 'GET',
      // 请求的地址
      url: 'http://www.liulongbin.top:3006/api/getbooks',
      // URL 中的查询参数
      params: {
        id: 1
      },
    }).then(function (result) {
      console.log(result)
    })

2. 直接使用axios发起POST请求:使用data属性

    // 1. 调用 axios 方法得到的返回值是 Promise 对象
    axios({
      // 请求方式
      method: 'GET',
      // 请求的地址
      url: 'http://www.liulongbin.top:3006/api/getbooks',
      // 请求体参数
      data: {}
    }).then(function (result) {
      console.log(result)
    })
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
<button id="btnPost">发起post请求</button>
<script src="./lib/axios.js"></script>
<script>
  document.querySelector('#btnPost').addEventListener('click',function(){
    axios:({
      method:'POST',
      url:'http://www.liulongin.top:3006/api/post',
      data:{
        name:'zs',
        age:20
      }
    }).then(function(result){
      console.log(result);
    })
  })
</script>

</body>
</html>

 4. 结合async和await调用axios

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

  <button id="btnPost">发起POST请求</button>
  <button id="btnGet">发起GET请求</button>

  <script src="./lib/axios.js"></script>
  <script>
    document.querySelector('#btnPost').addEventListener('click', async function () {
      // 如果调用某个方法的返回值是 Promise 实例,则前面可以添加 await!
      // await 只能用在被 async “修饰”的方法中
      //const result=await axios---其中result不是真正的数据 真正的数据是result.data()
      const { data } = await axios({
        method: 'POST',
        url: 'http://www.liulongbin.top:3006/api/post',
        data: {
          name: 'zs',
          age: 20
        }
      })

      console.log(data)
    })

    document.querySelector('#btnGet').addEventListener('click', async function () {
      // 解构赋值的时候,使用 : 进行重命名
      // 1. 调用 axios 之后,使用 async/await 进行简化
      // 2. 使用解构赋值,从 axios 封装的大对象中,把 data 属性解构出来
      // 3. 把解构出来的 data 属性,使用 冒号 进行重命名,一般都重命名为 { data: res }
      const { data: res } = await axios({
        method: 'GET',
        url: 'http://www.liulongbin.top:3006/api/getbooks'
      })

      console.log(res.data)
    })

    // $.ajax()   $.get()  $.post()
    // axios()    axios.get()    axios.post()    axios.delete()   axios.put()
  </script>
</body>

</html>

5.axios.get

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <button id="btnGET">GET</button>

  <script src="./lib/axios.js"></script>
  <script>
    document.querySelector('#btnGET').addEventListener('click', async function () {
      /* axios.get('url地址', {
        // GET 参数
        params: {}
      }) */

      // 将data重命名为res
      const { data: res } = await axios.get('http://www.liulongbin.top:3006/api/getbooks', {
        params: { id: 1 }
      })
      console.log(res)
    })
  </script>
</body>

</html>

6.axios.post

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <button id="btnPOST">POST</button>

  <script src="./lib/axios.js"></script>
  <script>

    document.querySelector('#btnPOST').addEventListener('click', async function () {
      // axios.post('url', { /* POST 请求体数据 */ })
      const { data: res } = await axios.post('http://www.liulongbin.top:3006/api/post', { name: 'zs', gender: '女' })
      console.log(res)
    })
  </script>
</body>

</html>

9.vue-cli

1. 什么是单页面应用程序(SPA)

2. 什么是 vue-cli:自动搭建Webpack

 3. 安装和使用:全局变量

 4. vue 项目的运行流程

 1.快速生成vue项目:vue create 项目名称(记得再要创建的地址(再当前地址上cmd)上进行执行,并且项目的名字不要有中文和空格)

 

 4.1 了解src目录的构建

    assets 文件夹:存放项目中用到的静态资源文件,例如:css 样式表、图片资源
   components 文件夹:程序员封装的、可复用的组件,都要放到 components 目录下
   main.js 是项目的入口文件。整个项目的运行,要先执行 main.js
   App.vue 是项目的根组件。

4.2 vue项目运行的过程

在工程化的项目中,vue 要做的事情很单纯:通过 main.js(渲染的方式记录) 把 App.vue (要渲染的UI结构)渲染到 index.html (将app.vue渲染到index.html)的指定区域中。

 4.3 vue-cli组件的基本使用

 4.4 .$mount():作用和el属性完全一样

//创建Vue实例对象
new Vue({
  //将写的这个实例挂载到app上
  // el:'#app',
  // 把render函数指定的组件,渲染到HTML页面中
  render: h => h(Test),
}).$mount('#app')//跟上面的el:'#app'   效果一样

// Vue实例的$mount()方法,作用和el属性完全一样
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div id="app">{{username}}</div>

  <script src="./lib/vue-2.6.12.js"></script>
  <script>
    const vm = new Vue({
      data: {
        username: 'admin'
      }
    })

    // 指定调用app这个位置的元素
    vm.$mount('#app')
  </script>
</body>

</html>

10.vue组件

1. 什么是组件化开发

组件化开发指的是:根据封装的思想,把页面上可重用的 UI 结构封装为组件,从而方便项目的开发和维护.

2. vue 中的组件化开发

 3. vue 组件(对UI结构的复用)的三个组成部分

 3.1 template:声明组件的UI结构

vue 规定:每个组件对应的模板结构,需要定义到 <template> 节点中。

注意:

 template 是 vue 提供的容器标签,只起到包裹性质的作用,它不会被渲染为真正的 DOM 元素

template 中只能包含唯一的根节点 【一个template只能包含唯一一个div】

<!-- 声明组件的UI结构 -->
<template>
    <div class="test-box">
        <h3>这是用户自定义的组件  -- {{ username }}</h3>
    </div>

</template>

 3.2 script:定义组件的行为(数据,调用方法)

vue 规定:开发者可以在<script>节点中封装组件的JavaScript业务逻辑

1. 必须写::默认导出。固定写法--exprot default{}

2.vue 组件中的 data 必须是一个函数,不能直接指向一个数据对象

 .vue 组件中的 data 必须是函数

<!-- 定义组件的行为(数据,调用方法) -->
<script>
    // 默认导出。固定写法!!!!!!!!
    export default{
        // datat数据源
        // 注意:.vue组件中的data不能像之前一样,不能指向对象
        // 注意:组件中的data必须是一个函数
        // data:{
        //     username:'zs'
        // }

        // data:function(){}
        data(){
            // 这个return出去的{}中 可以定义数据
            return {
                username:'admin'
            }
        }
    }
</script>

3.3 style :定义样式结构

vue 规定:组件内的<style>节点是可选的,开发者可以再<style>节点中编写样式美化当前组件的UI结构

<!-- 定义样式结构 -->
<style>
 .test-box{
    background-color: pink;
 }
</style>

让 style 中支持 less 语法 :在style属性上加上[lang="less"]

<!-- 定义样式结构 -->
<style lang="less">
 .test-box{
    background-color: pink;
 }
 h3 {
 color: red;}
</style>

4.组件中定义methods方法

5.组件之间的父子关系

 4.1 使用组件的三个步骤

 4.2 通过 components 注册的是私有子组件

4.3 注册全局组件:在main.js文件中进行注册【自己不能调用自己】

在 vue 项目的 main.js 入口文件中,通过 Vue.component() 方法,可以注册全局组件。

// 导入需要被全局注册的那个组件
import Count from '@/components/Count.vue'
// 注册
// 参数1:字符串格式,表示组件的“注册名称”,可以自己取
// 参数2:需要被全局注册的那个组件
Vue.component('MyCount',Count)

    <!-- 因为在main.js文件中自己定义的名字就是MyCount所以标签名就为MyCount -->
    <MyCount></MyCount>

 5. 组件的 props:自定义属性

props 是组件的自定义属性,在封装通用组件的时候,合理地使用 props 可以极大的提高组件的复用性!

 

<script>

export default {
    //props是自定义属性,允许使用者通过自定义属性,为当前组件指定初始值
    // 这个"init"可以自己取
    props:['init'],
    data(){
        return {
            count:0
        }
    },
    methods:{
        add(){
            this.count+=1
        }
    }

}
</script>

5.1 props 是只读的:不能修改【可以将值转存到data中】

vue 规定:组件中封装的自定义属性是只读的,程序员不能直接修改 props 的值。否则会直接报错:

 要想修改 props 的值,可以把 props 的值转存到 data 中,因为 data 中的数据都是可读可写的!

<template>
  <div>
    <h5>Count 组件</h5>
    <p>count的值是:{{ count }}</p>
    <button @click="count+=1">+1</button>
  </div>
</template>




<script>

export default {
    //props是自定义属性,允许使用者通过自定义属性,为当前组件指定初始值
    // 这个"init"可以自己取
    // props中的数据,可以直接在模板结构中被使用
    props:['init'],
    data(){
        return {
            // 将props中的属性转存到count上
            count:this.init
        }
    },
    methods:{
        show(){
            console.log(this);
        }
    }

}
</script>

 5.2 props 的 default 默认值:default:0

<template>
  <div class="right-container">
    <!-- 传入数字9 -->
    <!-- <MyCount :init="9"></MyCount> -->
    <!-- 用户没有输入数值,则使用props的默认值 -->
    <MyCount ></MyCount>
  </div>
</template>




<script>

export default {
    //props是自定义属性,允许使用者通过自定义属性,为当前组件指定初始值
    // 这个"init"可以自己取
    // props中的数据,可以直接在模板结构中被使用
    // 注意:props是只读,必要直接修改props的值,否则终端报错
    // props:['init'],


    //数组格式的props不能指定默认值
    // props:{
    //     自定义属性A:{/*配置选项*/},
    //     自定义属性A:{/*配置选项*/},
    //     自定义属性A:{/*配置选项*/},
    // }
        props:{
            init:{
                // 如果外界使用Count组件的时候,没有传递init属性,则默认值生效
                default:0
            }
        }

}
</script>

5.3 props 的 type 值类型:记得在标签中的init前加上冒号

 

    <!-- 因为在main.js文件中自己定义的名字就是MyCount所以标签名就为MyCount -->
    <MyCount :init="9"></MyCount>


<script>

export default {
    //props是自定义属性,允许使用者通过自定义属性,为当前组件指定初始值
    // 这个"init"可以自己取
    // props中的数据,可以直接在模板结构中被使用
    // 注意:props是只读,必要直接修改props的值,否则终端报错
    // props:['init'],


    //数组格式的props不能指定默认值
    // props:{
    //     自定义属性A:{/*配置选项*/},
    //     自定义属性A:{/*配置选项*/},
    //     自定义属性A:{/*配置选项*/},
    // }
        props:{
            init:{
                // 如果外界使用Count组件的时候,没有传递init属性,则默认值生效
                default:0,
                // init的值类型必须是Number数字
                // type:Number

            }
        }

}
</script>

 5.4 props 的 required 必填项:如果用户没有输入属性值,则控制台报错


    <!-- 因为在main.js文件中自己定义的名字就是MyCount所以标签名就为MyCount -->
    <MyCount :init="9"></MyCount>

<script>

export default {
    //props是自定义属性,允许使用者通过自定义属性,为当前组件指定初始值
    // 这个"init"可以自己取
    // props中的数据,可以直接在模板结构中被使用
    // 注意:props是只读,必要直接修改props的值,否则终端报错
    // props:['init'],


    //数组格式的props不能指定默认值
    // props:{
    //     自定义属性A:{/*配置选项*/},
    //     自定义属性A:{/*配置选项*/},
    //     自定义属性A:{/*配置选项*/},
    // }
        props:{
            init:{
                // 如果外界使用Count组件的时候,没有传递init属性,则默认值生效
                default:0,
                // init的值类型必须是Number数字
                type:Number,
                // 必填项校验,如果用户不传入init属性值,则强制报错
                required:true

            }
        }
}
</script>

5.5 props和v-bind(“:”)使用自定义属性

 6. 组件之间的样式冲突问题

默认情况下,写在 .vue 组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题。

导致组件之间样式冲突的根本原因是:

① 单页面应用程序中,所有组件的 DOM 结构,都是基于唯一的 index.html 页面进行呈现的 ② 每个组件中的样式,都会影响整个 index.html 页面中的 DOM 元素

 6.1 思考:如何解决组件样式冲突的问题:【分配唯一的自定义属性】

为每个组件分配唯一的自定义属性,在编写组件样式时,通过属性选择器来控制样式的作用域

6.2 style 节点的 scoped 属性:会自动添加上类选择器【用于解决组件之间的样式冲突】

为了提高开发效率和开发体验,vue 为 style 节点提供了 scoped 属性,从而防止组件之间的样式冲突问题

<style lang="less" scoped>
.left-container {
  padding: 0 20px 20px;
  background-color: orange;
  min-height: 250px;
  flex: 1;
}
h3 {
  color: red;
}
</style>

 6.3 /deep/ 样式穿透

如果给当前组件的 style 节点添加了 scoped 属性,则当前组件的样式对其子组件是不生效的。如果想让某些样 式对子组件生效,可以使用 /deep/ 深度选择器

当使用第三方组件的时候,如果有修改组件默认样式的需求,需要用到/deep/

<style lang="less" scoped>

//没有加/deep/前:h5[data-v-3c83f0b7]
// 加上/deep/后:[data-v-3c83f0b7] h5 --->给父亲加上data-v-3c83f0b7这个类
// 当使用第三方组件的时候,如果有修改组件默认样式的需求,需要用到/deep/
// 这个/deep/就是通过父组件修改子组件
/deep/ h5 {
  color: pink;
}
</style>

7.vue组件的实例对象

 生成的.vue代码并不是直接到浏览器中进行实现,而且是通过package.json文件中的插件进行转换为js代码

在.vue文件中使用标签相当于new一个实例对象 

<MyCount></MyCount>-->相当于创建了MyCounr这个实例

三、生命周期 & 数据共享

1.组件的生命周期

1. 生命周期 & 生命周期函数

 注意:生命周期强调的是时间段,生命周期函数强调的是时间点

 3. 组件生命周期函数的分类

4. 生命周期图示 

Vue 实例 — Vue.js (vuejs.org)

5.生命周期的实现过程

5.1 初步了解组件创建的过程

 5.2 【组件的创建阶段】了解beforeCreate生命周期函数:组件中的props/data/methods不可用

<script>
export default {
    props:['info'],
    data(){
        return {
            message:'hello vue.js',
            // 定义books数组,存储的是所有图书的列表数据,默认为空数组
            books:[]

        }
    },
    methods:{
        show(){
            console.log('调用了Test组件的show方法');
        },
        // 使用Ajax请求图书列表的数据
        initBookList() {
      const xhr = new XMLHttpRequest()
      xhr.addEventListener('load', () => {
        const result = JSON.parse(xhr.responseText)
        // 获取对象
        console.log(result)
        this.books = result.data
      })
      xhr.open('GET', 'http://www.liulongbin.top:3006/api/getbooks')
      xhr.send()
    }
    },
    // 创建阶段的第一个生命周期---props,data,methods都处于不可用状态
    beforeCreate(){
        // console.log(this.info);//拿不到
        // console.log(this.message);//拿不到
        // this.show()//拿不到
    }

}
</script>

5.3 【组件的创建阶段】了解created生命周期函数:组件中的props/data/methods可用


<template>
  <div class="test-container">
    <h3>Test.vue 组件 一共有{{ books.length }}本书</h3>
  </div>
</template>



<script>
export default {
    props:['info'],
    data(){
        return {
            message:'hello vue.js',
            // 定义books数组,存储的是所有图书的列表数据,默认为空数组
            books:[]

        }
    },
    methods:{
        show(){
            console.log('调用了Test组件的show方法');
        },
        // 使用Ajax请求图书列表的数据
        initBookList() {
      const xhr = new XMLHttpRequest()
      xhr.addEventListener('load', () => {
        const result = JSON.parse(xhr.responseText)
        // 获取对象
        console.log(result)
        this.books = result.data
      })
      xhr.open('GET', 'http://www.liulongbin.top:3006/api/getbooks')
      xhr.send()
    }
    },
    //创建阶段的第二个生命周期---props,data,methods都处于可用状态
    // 在这个状态下可以调用Ajax请求拿数据
    // created生命周期函数,非常常用
    // 经常在它里面,调用methods中的方法,请求服务器的数据
    // 并且,把请求到的数据,转存到data中,供template模板渲染的时候使用
    created(){
        console.log(this.info);
        console.log(this.message);
        this.show(),
        //可以访问到data数据
        this.initBookList()
    }

}
</script>

5.4【组件的创建阶段】 了解beforeMount生命周期函数: 未拿到DOM结构

<script>
export default {
    props:['info'],
    data(){
        return {
            message:'hello vue.js',
            // 定义books数组,存储的是所有图书的列表数据,默认为空数组
            books:[]

        }
    },
    methods:{
        show(){
            console.log('调用了Test组件的show方法');
        },
        // 使用Ajax请求图书列表的数据
        initBookList() {
      const xhr = new XMLHttpRequest()
      xhr.addEventListener('load', () => {
        const result = JSON.parse(xhr.responseText)
        // 获取对象
        console.log(result)
        this.books = result.data
      })
      xhr.open('GET', 'http://www.liulongbin.top:3006/api/getbooks')
      xhr.send()
    }
    },
    // 将要渲染DOM结构,但是没有拿到DOM元素
    beforeMount(){
        console.log('beforeMount');
        const dom =document.querySelector('#myh3')
        console.log(dom);//拿不到dom元素
    }

}
</script>

5.5【组件的创建阶段】 了解mounted生命周期函数: 将DOM结构渲染到页面上了

<script>
export default {
    props:['info'],
    data(){
        return {
            message:'hello vue.js',
            // 定义books数组,存储的是所有图书的列表数据,默认为空数组
            books:[]

        }
    },
    methods:{
        show(){
            console.log('调用了Test组件的show方法');
        },
        // 使用Ajax请求图书列表的数据
        initBookList() {
      const xhr = new XMLHttpRequest()
      xhr.addEventListener('load', () => {
        const result = JSON.parse(xhr.responseText)
        // 获取对象
        console.log(result)
        this.books = result.data
      })
      xhr.open('GET', 'http://www.liulongbin.top:3006/api/getbooks')
      xhr.send()
    }
    },
    mounted(){
        console.log(this.$el);
    }

}
</script>

 5.6 【组件的创建阶段】生命周期的示意图

5.7 【组件的运行阶段】生命周期函数 

beforeupdate

<script>
export default {
    props:['info'],
    data(){
        return {
            message:'hello vue.js',
            // 定义books数组,存储的是所有图书的列表数据,默认为空数组
            books:[]

        }
    },
    methods:{
        show(){
            console.log('调用了Test组件的show方法');
        },
        // 使用Ajax请求图书列表的数据
        initBookList() {
      const xhr = new XMLHttpRequest()
      xhr.addEventListener('load', () => {
        const result = JSON.parse(xhr.responseText)
        // 获取对象
        console.log(result)
        this.books = result.data
      })
      xhr.open('GET', 'http://www.liulongbin.top:3006/api/getbooks')
      xhr.send()
    }
    },
    // 要触发beforeUpdata就要数据发生改变的时候
    // 这个时候数据是新的,但是UI结构是旧的
    beforeUpdate(){
        console.log('beforeUpdate');

        console.log(this.message);
        const dom=document.querySelector('#pppp')
        console.log(dom.innerHTML);//跟上面获取的message数据不一样
    }

}
</script>

updated

<script>
export default {
    props:['info'],
    data(){
        return {
            message:'hello vue.js',
            // 定义books数组,存储的是所有图书的列表数据,默认为空数组
            books:[]

        }
    },
    methods:{
        show(){
            console.log('调用了Test组件的show方法');
        },
        // 使用Ajax请求图书列表的数据
        initBookList() {
      const xhr = new XMLHttpRequest()
      xhr.addEventListener('load', () => {
        const result = JSON.parse(xhr.responseText)
        // 获取对象
        console.log(result)
        this.books = result.data
      })
      xhr.open('GET', 'http://www.liulongbin.top:3006/api/getbooks')
      xhr.send()
    }
    },
    // 创建阶段的第一个生命周期---props,data,methods都处于不可用状态
    beforeCreate(){
        // console.log(this.info);
        // console.log(this.message);
        // this.show()
    },
    //创建阶段的第二个生命周期---props,data,methods都处于可用状态
    // 在这个状态下可以调用Ajax请求拿数据
    // created生命周期函数,非常常用
    // 经常在它里面,调用methods中的方法,请求服务器的数据
    // 并且,把请求到的数据,转存到data中,供template模板渲染的时候使用
    created(){
        // console.log(this.info);
        // console.log(this.message);
        // this.show(),
        //可以访问到data数据
        this.initBookList()
    },
    // 将要渲染DOM结构,但是没有拿到DOM元素
    beforeMount(){
        // console.log('beforeMount');
        // const dom =document.querySelector('#myh3')
        // console.log(dom);//拿不到dom元素
    },
    mounted(){
        // console.log(this.$el);
    },
    // 当数据变化之后,为了能够操作到最新的 DOM 结构,必须把代码写到 updated 生命周期函数中
    updated(){
    
        console.log('beforeUpdate');

        console.log(this.message);
        const dom=document.querySelector('#dddd')
        console.log(dom.innerHTML);//跟上面获取的message数据一样
    }

}
</script>

5.8 【组件的销毁阶段】生命周期函数 

<script>
export default {
    props:['info'],
    data(){
        return {
            message:'hello vue.js',
            // 定义books数组,存储的是所有图书的列表数据,默认为空数组
            books:[]

        }
    },
    methods:{
        show(){
            console.log('调用了Test组件的show方法');
        },
        // 使用Ajax请求图书列表的数据
        initBookList() {
      const xhr = new XMLHttpRequest()
      xhr.addEventListener('load', () => {
        const result = JSON.parse(xhr.responseText)
        // 获取对象
        console.log(result)
        this.books = result.data
      })
      xhr.open('GET', 'http://www.liulongbin.top:3006/api/getbooks')
      xhr.send()
    }
    },
    beforeDestroy() {
    console.log('beforeDestroy')
    this.message = 'aaa'
    console.log(this.message)
  },
  destroyed() {
    console.log('destroyed')
    // this.message = 'aaa'
  }

}
</script>

2.组件之间的数据共享

1. 组件之间的关系

 2. 父子组件之间的数据共享

 2.1 父组件向子组件共享数据:自定义属性  (:msg="messsage")

父组件向子组件共享数据需要使用自定义属性。示例代码如下:

 父组件

<template>
  <div class="app-container">
    <h1>App 根组件</h1>
    <hr />

    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <!-- 3. 以标签形式,使用注册好的组件 -->
      <!-- 注意:如果没有使用v-bind绑定属性的话,则输出的是“msg 的值为message” -->
      <!-- 表示将message这个字符串传入,而我们要接收的是message这个对应数据 -->
      <Left :msg="message" :user="userinfo"></Left>
    </div>
  </div>
</template>

<script>
// 1.将其他组件导入
import Left from '@/components/Left.vue'
import { userInfo } from 'os';
// import Right from '@/components/Right.vue'



export default {
  data(){
    return {
      message:'hello',
      userinfo:{ name: 'wsc',age:'18'}
    }
  },
  // 2. 注册组件
  components:{
    Left,
  }
}
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}
.box {
  display: flex;
}
</style>

子组件

<template>
  <div class="left-container">
    <h3>Left 组件</h3>
    <p>msg 的值为:{{ msg }}</p>
    <p>user的值为:{{ user }}</p>
  </div>
</template>

<script>
export default {
  props:['msg','user']
}
</script>

<style lang="less">
.left-container {
  padding: 0 20px 20px;
  background-color: orange;
  min-height: 250px;
  flex: 1;
}
</style>

注意:不要修改props属性里面的值

对于简单数据类型来说:子组件是将父组件的传入的内容复制了一份,所以如果修改子组件中的内容,则父组件不会变

对于复杂数据类型来说:子组件指向父组件,当子组件发生改变的时候,父组件中的值也发生改变

2.2 子组件向父组件共享数据 :使用自定义事件

 子组件向父组件共享数据使用自定义事件

父组件

<template>
  <div class="app-container">
    <h1>App 根组件 --- {{ countFromSon }}</h1>
    <hr />

    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <!-- 3. 以标签形式,使用注册好的组件 -->
      <!-- 注意:如果没有使用v-bind绑定属性的话,则输出的是“msg 的值为message” -->
      <!-- 表示将message这个字符串传入,而我们要接收的是message这个对应数据 -->
      <Left :msg="message" :user="userinfo"></Left>
      <Right @numchange="getNewCount"></Right>
    </div>
  </div>
</template>

<script>
// 1.将其他组件导入
import Left from '@/components/Left.vue'
import Right from '@/components/Right.vue'



export default {
  data(){
    return {
      message:'hello',
      userinfo:{ name: 'wsc',age:'18'},
      // 接收子组件传递过来的count
      countFromSon:0
    }
  },
  methods:{
  // 获取子组件传递过来的数据
  // val--是子组件传递过来的值
    getNewCount(val){
      console.log('numchange事件被触发了');
      // 将子组件传递过来的数据重新赋值给父组件
      this.countFromSon=val
    
    }
  },
  // 2. 注册组件
  components:{
    Left,
    Right
  }
}
</script>

子组件

<template>
  <div class="right-container">
    <h3>Right 组件----{{ count }}</h3>
    <button @click="add">+1</button>
  </div>
</template>

<script>
export default {
  data(){
    return {
      // 子组件自己的数据 将来希望把count的值传递给父组件
      count:0
    }
  },
  methods:{
    add(){
      // 让子组件的count值自增1
      this.count+=1
      // 把自增的结果,传给父组件
      // 这个numchange是自己取的
      // 自定义事件
      this.$emit('numchange',this.count)
    }
  }
}
</script>

 3. 兄弟组件之间/组件之间相离较远  的数据共享:EventBus

在 vue2.x 中,兄弟组件之间数据共享的方案是 EventBus。

EventBus 的使用步骤 

1.在数据发送方data写入数据,然后再接收方data定义一个变量接收来自发送方发来的数据

2.再components文件中创建eventBus.js文件

import Vue from 'vue'


// 将新创建的Vue对象向外暴露
export default new Vue()

3.再发送方创建一个DOM实例

<template>
  <div class="left-container">

    <button @click="send">把诗歌发送出去</button>
  </div>
</template>

4.将eventBus模块导入发送方和接收方

//1.导入eventBus.js模块
import bus from '@/components/eventBus.js'


import bus from '@/components/eventBus.js'

5.在发送方中声明触发事件

<script>
//1.导入eventBus.js模块
import bus from '@/components/eventBus.js'

export default {
  props:['msg','user'],
  data(){
    return {
      str:'鹅鹅鹅'
    }
  },
  methods:{
    send(){
      //2.通过eventBus发送数据
      bus.$emit('share',this.str)
      
    }
  }
}
</script>

6.在接收方中绑定自定义事件(跟data同级的)

<script>

export default {
  data(){
    return {
      // 子组件自己的数据 将来希望把count的值传递给父组件
      count:0,
      //创建一个对象接收从Left组件接收过来的数据
      msgfFromLeft:''
    }
  },
  
  created(val) {
      //2.为bus绑定自定义事件
      bus.$on('share',(val)=>{
        console.log('在Right组件中定义的share被触发了',val);
        // 接收从发送方发过来的数据
        this.msgfFromLeft=val
      })
    },
  methods:{
    add(){
      // 让子组件的count值自增1
      this.count+=1
      // 把自增的结果,传给父组件
      // 这个numchange是自己取的
      // 自定义事件
      this.$emit('numchange',this.count)
    },
  }
}
</script>

3.ref引用

1. 什么是 ref 引用:在不依赖jQuery的作用下,使用DOM

ref 用来辅助开发者在不依赖于 jQuery 的情况下,获取 DOM 元素或组件的引用。

每个 vue 的组件实例上,都包含一个 $refs 对象,里面存储着对应的 DOM 元素或组件的引用。默认情况下, 组件的 $refs 指向一个空对象

<script>
export default {
  methods:{
    showThis(){
      //指向空对象
      console.log(this);
    }
  }
}
</script>

 2. 使用 $ref 引用 DOM 元素

如果想要使用 ref 引用页面上的 DOM 元素,则可以按照如下的方式进行操作:

<template>
  <div class="app-container">
    <h1 ref="myh12">App 根组件</h1>
    <button @click="showThis">打印this</button>
    <hr />

    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->
    </div>
  </div>
</template>

<script>
export default {
  methods:{
    showThis(){
      //指向空对象
      console.log(this);
      // 拿到DOM元素
      console.log(this.$refs.myh12);
      // 给DOM添加样式
      this.$refs.myh12.style.color='red'
    }
  }
}
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}
.box {
  display: flex;
}
</style>

3. 使用 ref 引用组件实例

如果想要使用 ref 引用页面上的组件实例,则可以按照如下的方式进行操作:

 

使用组件

<template>
  <div class="app-container">
    <h1 ref="myh12">App 根组件</h1>
    <button @click="showThis">打印this</button>
    <!-- 这里调用到Left组件中的this -->
    <button @click="onReset" >重置Left组件的count值为0</button>
    <hr />

    <!-- 3.将组件渲染出来 -->
    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->

      <!-- 给Left组件添加ref可以拿到Left组件中的引用 -->
      <!-- 因为ref是拿DOM的方法,所以通过这个方法就可以拿到Left元素(这里将Left看作是DOM元素) -->
      <Left ref="comLeft"></Left>
    </div>
  </div>
</template>

<script>
//1.导入组件
import Left from '@/components/Left.vue'
export default {
  methods:{
    showThis(){
      //指向空对象
      console.log(this);
      // 拿到DOM元素
      console.log(this.$refs.myh12);
      // 给DOM添加样式
      this.$refs.myh12.style.color='red'
    },
    onReset(){
      // this.$refs.comLeft.resetCount()
      // 等价于
      this.$refs.comLeft.count=0
    }
  },
  //2.注册组件
  components:{
    Left
  }
}
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}
.box {
  display: flex;
}
</style>

被使用的组件 

<template>
  <div class="left-container">
    <h3>Left 组件----{{ count }}</h3>
    <button @click="count+=1">+1</button>
    <button @click="resetCount">重置</button>
  </div>
</template>

<script>
export default {
  data(){
    return {
      count:0
    }
  },
  methods:{
    resetCount(){
     this.count=0
    }
  }
}
</script>

<style lang="less">
.left-container {
  padding: 0 20px 20px;
  background-color: orange;
  min-height: 250px;
  flex: 1;
}
</style>

 4. 控制文本框和按钮的按需切换

通过布尔值 inputVisible 来控制组件中的文本框与按钮的按需切换。示例代码如下:

 

<template>
  <div class="app-container">
    <h1 ref="myh12">App 根组件</h1>
    <button @click="showThis">打印this</button>
    <!-- 这里调用到Left组件中的this -->
    <button @click="onReset" >重置Left组件的count值为0</button>
    <hr />


    <!-- 当v-if为true的时候,展示输入框 -->
    <!-- 当文本框失去焦点的时候,展示按钮  使用了@blur -->
    <input type="text" v-if="inputVisible" @blur="showButton">
    <!-- 当点击按钮后,按钮消失,出现文本框 -->
    <button v-else @click="showInput">展示输入框</button>



    <hr>
    <!-- 3.将组件渲染出来 -->
    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->

      <!-- 给Left组件添加ref可以拿到Left组件中的引用 -->
      <!-- 因为ref是拿DOM的方法,所以通过这个方法就可以拿到Left元素(这里将Left看作是DOM元素) -->
      <Left ref="comLeft"></Left>
    </div>
  </div>
</template>

<script>
//1.导入组件
import Left from '@/components/Left.vue'
export default {
  data(){
    return {
      // 控制输入框和按钮的按需切换
      // 默认值为false,表示默认展示按钮,隐藏输入框
      inputVisible:false
    }
  },
  methods:{
    showInput(){
      this.inputVisible=true
    },
    showButton(){
      this.inputVisible=false
    },
    showThis(){
      //指向空对象
      console.log(this);
      // 拿到DOM元素
      console.log(this.$refs.myh12);
      // 给DOM添加样式
      this.$refs.myh12.style.color='red'
    },
    onReset(){
      // this.$refs.comLeft.resetCount()
      // 等价于
      this.$refs.comLeft.count=0
    }
  },
  //2.注册组件
  components:{
    Left
  }
}
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}
.box {
  display: flex;
}
</style>

5. 让文本框自动获得焦点

当文本框展示出来之后,如果希望它立即获得焦点,则可以为其添加 ref 引用,并调用原生 DOM 对象的 .focus() 方法即可

 6. this.$nextTick(cb) 方法

组件的 $nextTick(cb) 方法,会把 cb 回调推迟到下一个 DOM 更新周期之后执行。通俗的理解是:等组件的 DOM 更新完成之后,再执行 cb 回调函数。从而能保证 cb 回调函数可以操作到最新的 DOM 元素

4.购物车案例

1. 案例效果

 2. 实现步骤

1.导入Header组件

<template>
  <div class="app-container">
    <!-- 3.使用组件 -->
    <!-- Header头部区域 -->
    <Header></Header>
  </div>
</template>


<script>
// 1.导入Header组件
// 导入需要的组件
import Header from '@/components/Header/Header.vue'
export default {
  // 2.注册组件
  components:{
    Header
  }
}
</script>

2.基于axios请求列表数据

1.安装axios,并在App.vue中导入axios

2.在methods方法中,定义initCartList函数请求列表数据

3.在created生命周期函数中,调用步骤2封装的initCartList函数

<template>
  <div class="app-container">
    <!-- 3.使用组件 -->
    <!-- Header头部区域 -->
    <Header></Header>
    <h1>App 根组件</h1>
  </div>
</template>

<script>
// 导入axios请求库
import axios from 'axios'


// 1.导入Header组件
// 导入需要的组件
import Header from '@/components/Header/Header.vue'

export default {
  created(){
    //调用请求数据的方法
    // 当数据一旦创建好就调用下面的这个方法
    this.initCartList();
  },
  methods:{
    // 封装请求列表数据的方法--发起数据请求都是在生命周期created中进行请求的
    async initCartList(){
      // 调用axios的get方法,请求列表数据
      // axios.get--得到的是一个对象
      // 如果使用了await,则记得在函数名前面加上async
      // data:res--是将获得的对象中,实际获得的数据是:对象.data
      // data:res--表示对data这个属性进行重命名
      const {data:res}=await axios.get('https://www.escook.cn/api/cart')
      console.log(res);
    }
  },
  // 2.注册组件
  components:{
    Header
  }
}
</script>

<style lang="less" scoped>
.app-container {
  padding-top: 45px;
  padding-bottom: 50px;
}
</style>

 3.请求回来的数据存放在data中

1.先在data中声明一个空的数组,将获取得到的数据存入

2.在initCartList函数中判断是否获取数据成功

3.获取成功后,将真正的数据存入

<script>
// 导入axios请求库
import axios from 'axios'


// 1.导入Header组件
// 导入需要的组件
import Header from '@/components/Header/Header.vue'

export default {
  data(){
    return {
      list:[]
    }
  },
  created(){
    //调用请求数据的方法
    // 当数据一旦创建好就调用下面的这个方法
    this.initCartList();
  },
  methods:{
    // 封装请求列表数据的方法--发起数据请求都是在生命周期created中进行请求的
    async initCartList(){
      // 调用axios的get方法,请求列表数据
      // axios.get--得到的是一个对象
      // 如果使用了await,则记得在函数名前面加上async
      // data:res--是将获得的对象中,实际获得的数据是:对象.data
      // data:res--表示对data这个属性进行重命名
      const {data:res}=await axios.get('https://www.escook.cn/api/cart')
      // 只要请求回来的数据,在页面中渲染期间需要用到,必须转存到data中
      console.log(res);
      if(res.status === 200){
        // 如果数据请求成功,则将res中的list(存放数据的)赋值给data中list数组
        this.list=res.list
      }

    }
  },
  // 2.注册组件
  components:{
    Header
  }
}
</script>

 4.循环渲染Goods组件

1.先将Goods组件引入App.vue文件中

2.组件的循环使用v-for,记得后面加上:key

    <!-- 循环渲染每一个商品的信息 -->
    <!-- 组件的循环使用v-for -->
    <!-- 使用v-for记得加上key,key指的是id(唯一性) -->
    <Goods v-for="item in list" :key="item.id"></Goods>




//2.导入Goods组件
import Goods from '@/components/Goods/Goods.vue'


  // 2.注册组件
  components:{
    Header,
    Goods,
  }

5.为Goods组件封装title、pic属性、prices属性和state属性:【父组件向子组件传值】

主要思路:父向子传值(就是在App.vue文件中通过axios获得的数据传递给Goods组件进行渲染)

1.在Goods组件中声明props属性

2.在标签中(App.vue和Goods.vue中)进行动态绑定

注意点:

插值表达式"{{}}"不能用在属性中 只能使用动态绑定来实现 ":"

      <h6 class="goods-title">{{title}}</h6>


          <!-- 插值表达式"{{}}"不能用在属性中 只能使用动态绑定来实现 ":" -->
          <!-- <img src="../../assets/logo.png" alt="" /> -->
          <img :src="pic" alt="" />

 App.vue

<template>
  <div class="app-container">
    <!-- 3.使用组件 -->
    <!-- Header头部区域 -->
    <Header></Header>


    <!-- 循环渲染每一个商品的信息 -->
    <!-- 组件的循环使用v-for -->
    <!-- 使用v-for记得加上key,key指的是id(唯一性) -->
    <!-- 记得在title前面加上 ":"  要不然传入的是字符串 -->
    <Goods v-for="item in list" :key="item.id" :title="item.goods_name"  :pic="item.goods_img" :price="item.goods_price" :state="item.goods_state"></Goods>


  </div>
</template>

Goods.vue

<template>
  <div class="goods-container">
    <!-- 左侧图片 -->
    <div class="thumb">
      <div class="custom-control custom-checkbox">
        <!-- 复选框 -->
        <input type="checkbox" class="custom-control-input" id="cb1" :checked="state" />
        <label class="custom-control-label" for="cb1">
          <!-- 商品的缩略图 -->
          <!-- 插值表达式"{{}}"不能用在属性中 只能使用动态绑定来实现 ":" -->
          <!-- <img src="../../assets/logo.png" alt="" /> -->
          <img :src="pic" alt="" />
        </label>
      </div>
    </div>
    <!-- 右侧信息区域 -->
    <div class="goods-info">
      <!-- 商品标题 -->
      <!-- 这里需要用到动态绑定 -->
      <!-- <h6 class="goods-title">图片图片</h6> -->
      <h6 class="goods-title">{{title}}</h6>
      <div class="goods-info-bottom">
        <!-- 商品价格 -->
        <span class="goods-price">{{price}}</span>
        <!-- 商品的数量 -->
      </div>
    </div>
  </div>
</template>

<script>
export default {
  props:{
    // 要渲染的商品的标题
    title:{
      default:'',
      type:String
    },
    pic:{
      default:'',
      // 图片是字符串类型
      type:String
    },
    price:{
      // 商品的单价
      default:0,
      type:Number
    },
    state:{
      // 商品的勾选状态
      // 默认为选中状态
      default:true,
      type:Boolean
    }
  }
}
</script>

6.关于自定义组件的属性传递问题【分析封装props两种方案的优缺点】

 7.如何修改商品勾选状态【子组件向父组件传值】

8.自定义state-change事件 

 父组件:App.vue

<template>
  <div class="app-container">
    <!-- 3.使用组件 -->
    <!-- Header头部区域 -->
    <Header></Header>


    <!-- 循环渲染每一个商品的信息 -->
    <!-- 组件的循环使用v-for -->
    <!-- 使用v-for记得加上key,key指的是id(唯一性) -->
    <!-- 记得在title前面加上 ":"  要不然传入的是字符串 -->
    <Goods v-for="item in list" :key="item.id" :title="item.goods_name"  :pic="item.goods_img" :price="item.goods_price" :state="item.goods_state" :id="item.id" @state-change="getNewState"></Goods>


  </div>
</template>


  methods:{

    },
// 接收子组件
getNewState(val){
  console.log('父组件接收到数据了');
  console.log(val);
}

子组件:Count.vue

        <!-- 复选框 -->
        <!-- 这里的checked不能使用v-model,因为props是只读属性,而v-model是双向数据绑定的 -->
        <input type="checkbox" class="custom-control-input" id="cb1" :checked="state" @change="stateChange"/>



methods:{
    // 只要复选框的选中状态发生了改变,就会调用这个出来函数
    stateChange(e){
      // console.log('ok');
      // console.log(e);
      // 获得最新的状态
      const newState=e.target.checked
      // console.log(newState);
      // 触发自定义事件
      this.$emit('state-change',{id:this.id,value:newState})
    }
  }

9.修改对应商品的勾选状态

父组件

// 接收子组件传递过来的数据
// e的格式是{id,value}
getNewState(e){
  console.log('父组件接收到数据了');
  // console.log(e);
  // 遍历传入的数组,判断是否有符合条件的
  this.list.some(item=>{
    if(item.id===e.id){
      //将商品的状态进行修改
      item.goods_state=e.value
      // 终止后续的状态
      return true
    }
  })
}

10.底部的“全选”按钮是否选中:【计算属性:computed/父组件向子组件传递】

因为全选按钮要被其他按钮影响到,所以要使用计算属性

1.先判断是否将复选框勾选上---->计算属性:computed

2.【当小按钮全部选中后,大按钮也被选中】将在App.vue文件中获得步骤1的结果渲染到Footer组件中---->父组件向子组件传递(自定义事件)

3.【当大按钮被选中后,小按钮全部被选中】将在Footer组件中触发到的事件渲染到App.vue组件中-->子组件向父组件传递(自定义属性)

  // 计算属性,用于底部"总计"复选框是否选中
  computed:{
    // 动态计算出全选的状态是true还是false
    fullState(){
      // 数组.every返回的是布尔值
      return this.list.every(item=>item.goods_state===true)
    }
  },

    <!-- Footer区域 -->
    <Footer :isFull="fullState" ></Footer>



  // 计算属性,用于底部"总计"复选框是否选中
  computed:{
    // 动态计算出全选的状态是true还是false
    fullState(){
      // 数组.every返回的是布尔值
      return this.list.every(item=>item.goods_state===true)
    }
  },


      <input type="checkbox" class="custom-control-input" id="cbFull" :checked="isFull"  />


    <!-- Footer区域 -->
    <Footer :isFull="fullState" @full-change="getFullState"></Footer>


      // 接收Footer子组件传递过来的全选按钮的状态
      getFullState(e){
        console.log('在App中拿到了全选的按钮');
        console.log(e);
        // 因为是要遍历小复选框中的每一项所以使用forEach
        this.list.forEach(item=>item.goods_state=e)
      },      


<input type="checkbox" class="custom-control-input" id="cbFull" :checked="isFull" @change="fullChange" />

  methods:{
    // 监听到了全选状态的变化
    fullChange(e){
      // console.log(e.target.checked);
      this.$emit('full-change',e.target.checked)
    },
  }

 11.计算商品的总价格

1.先在父组件App.vue组件中先写出计算属性

2.然后将计算出来的结果渲染到Footer组件(子组件)

 父组件

<template>
  <div class="app-container">
    <!-- 3.使用组件 -->
    <!-- Header头部区域 -->
    <Header></Header>
    <p>{{ amt }}</p>


    <!-- 循环渲染每一个商品的信息 -->
    <!-- 组件的循环使用v-for -->
    <!-- 使用v-for记得加上key,key指的是id(唯一性) -->
    <!-- 记得在title前面加上 ":"  要不然传入的是字符串 -->
    <Goods v-for="item in list" :key="item.id" :title="item.goods_name"  :pic="item.goods_img" :price="item.goods_price" :state="item.goods_state" :id="item.id" @state-change="getNewState"></Goods>

    <!-- Footer区域 -->
    <Footer :isFull="fullState" :amount="amt" @full-change="getFullState"></Footer>

  </div>
</template>

<script>
// 导入axios请求库
import axios from 'axios'


// 导入需要的组件
// 1.导入Header组件
import Header from '@/components/Header/Header.vue'
//2.导入Goods组件
import Goods from '@/components/Goods/Goods.vue'
//3.导入Footer组件
import Footer from '@/components/Footer/Footer.vue'


export default {
  data(){
    return {
      list:[]
    }
  },
  // 计算属性,用于底部"总计"复选框是否选中
  computed:{
    // 动态计算出全选的状态是true还是false
    fullState(){
      // 数组.every返回的是布尔值
      return this.list.every(item=>item.goods_state===true)
    },
    // 已勾选商品的总价格
    amt(){
      // 1.先filter过滤
      // 2.在reduce累加
      return this.list
      .filter(item=>item.goods_state)
      .reduce((total,item)=>{
        return total+=item.goods_price*item.goods_count
      },0)
    }
  },
  created(){
    //调用请求数据的方法
    // 当数据一旦创建好就调用下面的这个方法
    this.initCartList();
  },
  methods:{


    // 封装请求列表数据的方法--发起数据请求都是在生命周期created中进行请求的
    async initCartList(){
      // 调用axios的get方法,请求列表数据
      // axios.get--得到的是一个对象
      // 如果使用了await,则记得在函数名前面加上async
      // data:res--是将获得的对象中,实际获得的数据是:对象.data
      // data:res--表示对data这个属性进行重命名
      const {data:res}=await axios.get('https://www.escook.cn/api/cart')
      // 只要请求回来的数据,在页面中渲染期间需要用到,必须转存到data中
      console.log(res);
      if(res.status === 200){
        // 如果数据请求成功,则将res中的list(存放数据的)赋值给data中list数组
        this.list=res.list
      }

    },
// 接收子组件传递过来的数据
// e的格式是{id,value}
getNewState(e){
  console.log('父组件接收到数据了');
  // console.log(e);
  // 遍历传入的数组,判断是否有符合条件的
  this.list.some(item=>{
    if(item.id===e.id){
      //将商品的状态进行修改
      item.goods_state=e.value
      // 终止后续的状态
      return true
    }
  })
},
      // 接收Footer子组件传递过来的全选按钮的状态
      getFullState(e){
        console.log('在App中拿到了全选的按钮');
        console.log(e);
        // 因为是要遍历小复选框中的每一项所以使用forEach
        this.list.forEach(item=>item.goods_state=e)
      },

  },
  // 2.注册组件
  components:{
    Header,
    Goods,
    Footer,
  }
}
</script>

<style lang="less" scoped>
.app-container {
  padding-top: 45px;
  padding-bottom: 50px;
}
</style>

子组件

<template>
  <div class="footer-container">
    <!-- 左侧的全选 -->
    <div class="custom-control custom-checkbox">
      <input type="checkbox" class="custom-control-input" id="cbFull" :checked="isFull" @change="fullChange" />
      <label class="custom-control-label" for="cbFull">全选</label>
    </div>

    <!-- 中间的合计 -->
    <div>
      <span>合计:</span>
      <!-- toFixed---表示显示2位小数 -->
      <span class="total-price">¥{{ amount.toFixed(2) }}</span>
    </div>

    <!-- 结算按钮 -->
    <button type="button" class="btn btn-primary btn-settle">结算({{ 0 }})</button>
  </div>
</template>

<script>
export default {
  // 定义一个属性,用于接收父组件传递过来的复选框的勾选状态
  props:{
    // 全选状态
    isFull:{
      type:Boolean,
      default:true
    },
    // 总价格
    amount:{
      type:Number,
      default:0
    },
  },
  methods:{
    // 监听到了全选状态的变化
    fullChange(e){
      // console.log(e.target.checked);
      this.$emit('full-change',e.target.checked)
    },
  }
}
</script>

<style lang="less" scoped>
.footer-container {
  font-size: 12px;
  height: 50px;
  width: 100%;
  border-top: 1px solid #efefef;
  position: fixed;
  bottom: 0;
  background-color: #fff;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 10px;
}

.custom-checkbox {
  display: flex;
  align-items: center;
}

#cbFull {
  margin-right: 5px;
}

.btn-settle {
  height: 80%;
  min-width: 110px;
  border-radius: 25px;
  font-size: 12px;
}

.total-price {
  font-weight: bold;
  font-size: 14px;
  color: red;
}
</style>

12.把购买数量传给counter组件

1.Counter组件是Goods的子组件

2.要从Goods组件中获得的数量传递给Counter组件,但是在Goods组件中得不到(因为没有数量属性),所以要先从App组件中获得,才能给Counter组件

 2.1 将App组件中的数据传递给Goods组件

2.2 将Goods组件中的数据传递给Counter组件中

13.将在增删商品数量的结果传递给App组件

1.向App组件传递数据记得要传入id,则先在Counter中获得id属性[记得在Goods中将属性传递给Counter]

 2.给Counter组件添加点击事件

 3.创建EventBus.js文件,然后再发送方和接收方上加上EventBus

13.动态计算已勾选上商品数量

 总结

 四、动态组件 & 插槽 & 自定义指令

1.动态组件

1. 什么是动态组件:动态切换组件的显示与隐藏

2. 如何实现动态组件渲染:<components :is="要渲染的组件名">组件

vue 提供了一个内置的<components >组件专门用来实现动态组件的渲染。【相当于一个占位符】

      1.components标签是vue内置的,作用:组件的占位符

      2.is属性的值,表示要渲染的组件的名字

      3.is属性的,应该是组件再components节点下注册的名称

 

 3.动态切换组件的展示与隐藏

<template>
  <div class="app-container">
    <h1>App 根组件</h1>
    <hr />

    <button @click="comName='Left'">展示Left</button>
    <button @click="comName='Right'">展示Right</button>

    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <!-- 1.components标签是vue内置的,作用:组件的占位符 -->
      <!-- 2.is属性的值,表示要渲染的组件的名字 -->
      <!-- 3.is属性的,应该是组件再components节点下注册的名称 -->
      <!-- 写死了 -->
      <!-- <components is="Left"></components> -->
      <!-- 动态绑定 -->
      <components :is="comName"></components>
      

    </div>
  </div>
</template>

<script>
import Left from '@/components/Left.vue'
import Right from '@/components/Right.vue'

export default {

  data(){
    return {
      // comName表示要进行展示的组件名字
      comName:Left
    }
  },
  components:{
    Left,
    Right,
  }
}
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}
.box {
  display: flex;
}
</style>

 4. 使用 keep-alive 保持状态

keep-alive可以把内部的组件进行缓存,而不是销毁组件.

默认情况下,切换动态组件时无法保持组件的状态。此时可以使用 vue 内置的<keep-alive> 组件保持动态组 件的状态。

      <keep-alive>
      <components :is="comName"></components>
      </keep-alive>

 

5. keep-alive 对应的生命周期函数:被缓存的时候才可以使用

当组件被缓存时,会自动触发组件的 deactivated 生命周期函数。

当组件被激活时,会自动触发组件的 activated 生命周期函数。 

  1.当组件第一次被创建的时候,既会执行created生命周期,也会执行activated生命周期

  2. 但是,当组件被激活的时候,只会触发activated生命周期,不会触发created,因为组件没有重新被创建

 6. keep-alive 的 include/exclude 属性:指定哪一些组件可以/不被被缓存

include 属性用来指定:只有名称匹配的组件会被缓存。多个组件名之间使用英文的逗号分隔:

include:指定要进行缓存的组件 这里指定就缓存Left组件

 exclude:指定不进行缓存的

 include和exclude不能同时使用

7.了解组件注册名称和组件声明时name区别

当提供了name属性之后,组件的名称,就是name属性的值

  对比

   1.组件的"注册名称" components:{ Left,Right,} 的主要应用常见是:以标签的形式,把注册好的组件,渲染和使用到页面结构之中

   2.组件声明时候的"name"名称的主要应用场景"结合<keep-alive>标签实现组件缓存功能;以及再调试工具中看到组件的name名称

2.插槽

1. 什么是插槽<slot>:占位符

插槽(Slot)是 vue 为组件的封装者提供的能力。允许开发者在封装组件时,把不确定的、希望由用户指定的 部分定义为插槽。

可以把插槽认为是组件封装期间,为用户预留的内容的占位符。

2. 体验插槽的基础用法

在封装组件时,可以通过<slot> 元素定义插槽,从而为用户预留内容占位符。

 2.1 没有预留插槽的内容会被丢弃

如果在封装组件时没有预留任何<slot> 插槽,则用户提供的任何自定义内容都会被丢弃

 2.2 后备内容:如果用户输入内容则会将默认内容进行覆盖

 封装组件时,可以为预留的<slot> 插槽提供后备内容(默认内容)。如果组件的使用者没有为插槽提供任何 内容,则后备内容会生效。

 3. 具名插槽:v-slot

如果在封装组件时需要预留多个插槽节点,则需要为每个<slot> 插槽指定具体的 name 名称。这种带有具体 名称的插槽叫做“具名插槽”。

  vue官方规定:每一个slot插槽,都要有一个name名称

  如果省略了slot的name属性,则有一个默认名称叫做default

  默认情况下,在使用组件的时候,提供的内容会被填充到名字为default的插槽中

3.1 为具名插槽提供内容 

在向具名插槽提供内容的时候,我们可以在一个<template> 元素上使用 v-slot 指令,并以 v-slot 的参数的 形式提供其名称。

        1.如果要把内容填充到指定名称的插槽中,需要使用v-slot这个指令

        2.v-slot:后面要跟上插槽的名字

        3.v-slot:指令不能直接用在元素身上,必须用在template标签上,或者组件

        4.template这个标签,它是一个虚拟标签,只起到包裹性质作用,但是,不会被渲染为任何实际性质的html元素

         5.v-slot简写形式为:#

        <template v-slot:default>
        <!-- 如果再Left组件中没有定义插槽,则下面的p标签会被忽略 -->
        <!-- 默认情况下,在使用组件的时候,提供的内容会被填充到名字为default的插槽中 -->
        <p>这是在Left组件的内容中声明的p标签</p>
        </template>

3.2 具名插槽的简写形式:"#"

跟 v-on 和 v-bind 一样,v-slot 也有缩写,即把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header 可以被重写为 #header:

 

    <Article>

      <template #title>
        <h3>一首歌</h3>
      </template>

      <template #content> 
        <div>
          <p>啊,大海,全是水。</p>
          <p>啊,蜈蚣,全是腿。</p>
          <p>啊,辣椒,净辣嘴。</p>
        </div>
      </template>
      
      <template #author>
        <div>作者</div>
      </template>
    </Article>
<template>
  <div class="article-container">

    <!-- 文章标题 -->
    <div class="header-box">
        <slot name="title"></slot>
    </div>


    <!-- 文章的内容 -->
    <div class="content-box">
      <!-- 在封装组件时,为预留的 <slot> 提供属性对应的值,这种用法,叫做 “作用域插槽” -->
      <slot name="content"></slot>
    </div>

    <!-- 文章的作者 -->
    <div class="footer-box">
        <slot name="author"></slot>
    </div>
  </div>
</template>

 4. 作用域插槽

在封装组件的过程中,可以为预留的 <slot> 插槽绑定 props 数据这种带有 props 数据的 叫做“作用 域插槽”。

4.1 使用作用域插槽 

可以使用 v-slot: 的形式,接收作用域插槽对外提供的数据。

在封装组件时,为预留的 <slot> 提供属性对应的值,这种用法,叫做 “作用域插槽” 

      <!-- 这个obj得到的是一个对象 -->
      <!-- 用scope接收作用域插槽中的数据 -->
      <template #content="scope"> 
        <div>
          <p>啊,大海,全是水。</p>
          <p>啊,蜈蚣,全是腿。</p>
          <p>啊,辣椒,净辣嘴。</p>
          <!-- 拿到的是:{ "msg": "hello vue.js" } -->
          <p>{{ scope }}</p>
        </div>
      </template>


    <!-- 文章的内容 -->
    <div class="content-box">
      <!-- 在封装组件时,为预留的 <slot> 提供属性对应的值,这种用法,叫做 “作用域插槽” -->
      <slot name="content" msg="hello vue.js"></slot>
    </div>

 4.2 解构插槽 Prop

作用域插槽对外提供的数据对象,可以使用解构赋值简化数据的接收过程。

      <!-- <template #content="scope">  -->
      <template #content="{msg,user}"> 
        <div>
          <p>啊,大海,全是水。</p>
          <p>啊,蜈蚣,全是腿。</p>
          <p>啊,辣椒,净辣嘴。</p>
          <!-- 拿到的是:{ "msg": "hello vue.js" } -->
          <p>{{ user.name }}</p>
        </div>
      </template>


    <div class="content-box">
      <!-- 在封装组件时,为预留的 <slot> 提供属性对应的值,这种用法,叫做 “作用域插槽” -->
        <!-- msg="hello vue.js"--表示写入一个固定的值 -->
        <!-- :user="userinfo"---表示动态绑定一个属性 -->
      <slot name="content" msg="hello vue.js" :user="userinfo"></slot>
    </div>

5.购物车案例改造

5.1 基于slot插槽改造购物车案例--在App组件中直接获得Counter组件

 

 5.2 基于slot插槽改造购物车案例--在App组件中直接获得Counter组件中的数值

3.自定义指令

1. 什么是自定义指令

vue 官方提供了 v-text、v-for、v-model、v-if 等常用的指令。除此之外 vue 还允许开发者自定义指令。

2. 自定义指令的分类

 3. 私有自定义指令:directives 节点

在每个 vue 组件中,可以在 directives 节点下声明私有自定义指令

 4. 使用自定义指令

在使用自定义指令时,需要加上 v- 前缀

<template>
  <div class="app-container">
    <h1 v-color>App 根组件</h1>
  </div>
</template>

<script>

export default {

  // 私有自定义指令的节点
  directives:{
    // v-color--(v-)是固定写法,实际名字为:color    
    // 定义名为color的自定义指令,指向一个配置对象
    color:{
      // 当指令第一次被绑定到元素的时候,会立即触发bind函数
      // 形参中el表示当前指令所绑定到的那个DOM对象
      bind(el){
        // console.log('触发了v-color的bind 函数')
        el.style.color='red'
      }

    }
  }
}
</script>

5. 为自定义指令动态绑定参数值 

在 template 结构中使用自定义指令时,可以通过等号(=)的方式,为当前指令动态绑定参数值


    <h1 v-color="color">App 根组件</h1>


  data(){
    return {
      color:'blue'
    }
  },

 6. 通过 binding 获取指令的参数:bind(el,binding){}

在声明自定义指令时,可以通过形参中的第二个参数,来接收指令的参数值:



    <h1 v-color="color">App 根组件</h1>


  data(){
    return {
      color:'blue'
    }
  },


  // 私有自定义指令的节点
  directives:{
    // v-color--(v-)是固定写法,实际名字为:color    
    // 定义名为color的自定义指令,指向一个配置对象
    color:{
      // 当指令第一次被绑定到元素的时候,会立即触发bind函数
      // 形参中el表示当前指令所绑定到的那个DOM对象
      bind(el,binding){
        // console.log('触发了v-color的bind 函数')
        // el.style.color='red'

        // 当自定义指令中动态绑定了值
        // binding---是一个对象
        // console.log(binding);
        el.style.color=binding.value
      }

    }
  }

 7.单独在属性值后面添加属性值

8. update 函数 

bind 函数只调用 1 次:当指令第一次绑定到元素时调用,当 DOM 更新时 bind 函数不会被触发 update 函 数会在每次 DOM 更新时被调用。

9. 函数简写 

如果binding 和update 函数中的逻辑完全相同,则对象格式的自定义指令可以简写成函数格式:

 10. 全局自定义指令:Vue.directive()【写在main.js中】

全局共享的自定义指令需要通过“Vue.directive()”进行声明

1.参数1:字符串,表示全局变量自定义指令的名字

2.参数2:对象,用l来接收指令的参数值

// 全局自定义指令
/*Vue.directive('color',{
  bind(el,binding){
    el.style.color=binding.value
  },
  update(el,binding){
    el.style.color=binding.value
  }
})*/

Vue.directive('color',function(el,binging){
  el.style.color=binding.value
})

 总结

 五、ESLint:代码约束

1.了解eslintrc.js配置文件中rules规则

 2.初步了解ESLint的语法规则

当报错的时候,去官网查找:ESLint - Pluggable JavaScript linter - ESLint中文

3.配置VsCode 

3.1 ESlint

    // ESLint组件的配置

    "editor.codeActionsOnSave": {

        "source.fixAll": true

    },

3.2 prettier

    "eslint.alwaysShowStatus":true,
    "prettier.trailingComma": "none",
    "prettier.semi":false,
    // 每行文字个数超出此限制见过会被迫换行
    "prettier.printWidth":300,
    // 使用单引号替换双引号
    "prettier.singleQuote": true,
    "prettier.arrowParens": "avoid",
    // 设置.vue文件中,HTML代码的格式化插件
    "vetur.format.defaultFormatter.html": "js-beautify-html",
    "vetur.ignoreProjectWarning": true,
    "vetur.format.defaultFormatterOptions": {
        "prettier": {
            "trailingComma":"none",
            "semi":false,
            "singleQuote":true,
            "arrowParens":"avoid",
            "printWidth":300
        },
        "js-beautify-html": {
            "wrap_attributes": "false"
        }
    },

3.3 在电脑上配置一个文件

 

文件内容为: 

 {"semi": false, "singleQuote": true, "printWidth": 300}

在setting.json文件中加上

"prettier.configPath": "C:\\Users\\小林\\.prettierrc",

 4.配置默认格式化文档的方式

在vue文件下

在js文件下 

 出现保存自动加逗号,则在setting.json文件中将

5.演示axios的基本使用并发现问题 

5.1 在装axios包的时候出现错误

则应该修改为:

npm i axios -S --legacy-peer-deps

 5. 2 在components文件夹中起名字为Left-vue和Right-vue的两个组件

   【注意点:一定要在文件名后面加上-vue要不然报错】

5.3 如果不想将名字修改为-vue,则修改.eslintrc.js文件

module.exports = {
  root: true,
  env: {
    node: true
  },
  extends: [
    'plugin:vue/essential',
    '@vue/standard'
  ],
  parserOptions: {
    parser: '@babel/eslint-parser'
  },
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    // 在方法的形参 () 之前,是否必须有空格
    'space-before-function-paren': ['warn', 'never'],
    'vue/multi-word-component-names': 'off'
  }
}

 6.Right.vue和Left.vue

<template>
  <div class="right-container">
    <h3>Right组件</h3>
    <button @click="postInfo">发起 POST 请求</button>
  </div>

</template>

<script>
import axios from 'axios'
export default {
  methods: {
    async postInfo() {
      const { data: res } = await axios.post('http://www.liulongbin.top:3006/api/post', { name: 'zs', age: 20 })
      console.log(res)
    }
  }
}
</script>

<style lang="less" scoped>
.right-container {
  background-color: pink;
  min-height: 200px;
  flex:1;
}
</style>
<template>
  <div class="left-container">
    <h3>Left组件</h3>
    <button @click="getInfo">发起 GET 请求</button>
  </div>
</template>

<script>

import axios from 'axios'

export default {

  methods: {
    async getInfo() {
      const { data: res } = await axios.get('http://www.liulongbin.top:3006/api/get')
      console.log(res)
    }
  }

}
</script>

<style lang="less" scoped>
.left-container {
  background-color: orange;
  min-height: 200px;
  flex:1;
}
</style>

7.把axios挂载到Vue的原型上并配置请求根路径

把axios挂载到Vue的原型上

把 axios 挂载到 Vue 原型上,有一个缺点:不利于 API 接口的复用!!!

 配置请求根路径

main.js文件

import Vue from 'vue'
import App from './App.vue'

import axios from 'axios'
Vue.config.productionTip = false

// 全局配置 axios 的请求根路径
axios.defaults.baseURL = 'http://www.liulongbin.top:3006'

// 将axios属性挂载在Vue的原型上
// Vue.prototype.axios = axios
Vue.prototype.$http = axios

// 今后,在每个 .vue 组件中要发起请求,直接调用 this.$http.xxx
// 但是,把 axios 挂载到 Vue 原型上,有一个缺点:不利于 API 接口的复用!!!

new Vue({
  render: (h) => h(App)
}).$mount('#app')

六、路由

1.前端路由的概念与原理

1. 什么是路由:路由(英文:router)就是对应关系

3. SPA (单页面开发)与前端路由

4. 什么是前端路由:#Hash 地址(锚链接)组件之间的对应关系

 5. 前端路由的工作方式

① 用户点击了页面上的路由链接

② 导致了 URL 地址栏中的 Hash 值发生了变化

前端路由监听了到 Hash 地址的变化

④ 前端路由把当前 Hash 地址对应的组件渲染都浏览器中

 前端路由,指的是 Hash 地址与组件之间的对应关系

 6. 手动实现简易的前端路由

步骤1:通过 <component> 标签,结合 comName 动态渲染组件。示例代码如下:

 步骤2:在 App.vue 组件中,为 <a>链接添加对应的hash值

 步骤3:在 created 生命周期函数中,监听浏览器地址栏中 hash 地址的变化,动态切换要展示的组件的名称:

  created(){
    // 只要当前App组件一被创建,就立即监听window对象的onhashchange事件
    // onhashchange---是拿到当前的哈希地址
    window.onhashchange=()=>{
      // location.hash---拿到的是根地址后面的具体地址===#/hmoe   #/movie
      console.log('监听到了hash地址的变化',location.hash);
      switch(location.hash){
        case '#/home':
            this.comName='home'
            break
        case '#/movie':
          this.comName = 'movie'
          break
        case '#/about':
          this.comName = 'about'
          break
      }
    }

2.vue-router的基本使用

1. 什么是 vue-router

 2. vue-router 安装和配置的步骤

① 安装 vue-router 包

② 创建路由模块

③ 导入并挂载路由模块

④ 声明路由链接和占位符

2.1 在项目中安装 vue-router

 2.2 创建路由模块:导入,调用,创建,共享router/index.js

在 src 源代码目录下,新建 router/index.js 路由模块

// src/router/index.js 就是当前项目的路由模块
import Vue from 'vue'
import VueRouter from 'vue-router'


// 把 VueRouter 安装为 Vue 项目的插件
// Vue.use() 函数的作用,就是来安装插件的
Vue.use(VueRouter)

// 创建路由的实例对象
const router=new VueRouter()

// 将创建好的实例对象向外暴露
export default router

 2.3 导入并挂载路由模块(在 src/main.js)

在 src/main.js 入口文件中,导入并挂载路由模块。

import Vue from 'vue'
import App from './App.vue'
// 导入路由模块,目的:拿到路由的实例对象
import router from '@/router/index.js'


// 导入 bootstrap 样式
import 'bootstrap/dist/css/bootstrap.min.css'
// 全局样式
import '@/assets/global.css'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  // 在VUe项目中,想要把路由用起来,必须把路由实例对象,通过下面的方式进行挂载
  // router:路由的实例对象
  router:router
}).$mount('#app')

2.4 路由的基本用法

在进行模块化导入的时候,如果给定的是文件夹,则默认导入这个文件夹下,名字叫做index.js的文件

 2.5 声明路由占位符

在 src/App.vue 组件中,使用 vue-router 提供的 <router-view> 声明路由占位符

<template>
  <div class="app-container">
    <h1>App2 组件</h1>

    <a href="#/home">首页</a>
    <a href="#/movie">电影</a>
    <a href="#/about">关于</a>

    <hr />

    <!-- 只要在项目中安装和配置了vue-router,就可以使用router-view这个组件了 -->
    <!-- 它的作用很单纯:占位符 -->
    <router-view></router-view>
  </div>
</template>

2.6 使用router-link替代a链接

 

    <!-- 当安装和配置了vue-router后,就可以使用router-link来替代普通的a链接 -->

    //<a href="#/home">首页</a>
    <router-link to="/home">首页</router-link>
    //<a href="#/movie">电影</a>
    <router-link to="movie">电影</router-link>
    //<a href="#/about">关于</a>
    <router-link to="/about">关于</router-link>

3. 声明路由的匹配规则 

在 src/router/index.js 路由模块中,通过 routes 数组声明路由的匹配规则。

// src/router/index.js 就是当前项目的路由模块
import Vue from 'vue'
import VueRouter from 'vue-router'


// 导入想要的组件
import Home from '@/components/Home.vue'
import Movie from '@/components/Movie.vue'
import About from '@/components/About.vue'


// 把 VueRouter 安装为 Vue 项目的插件
// Vue.use() 函数的作用,就是来安装插件的
Vue.use(VueRouter)

// 创建路由的实例对象
const router=new VueRouter({
  // routes是一个数组,作用:定义”hash地址“与”组件“之间的对应关系
  // routes:[{path:'/具体的组件地址(不能再前面加”#“)',component:要展示的组件}]
  routes:[
    {path:'/home',component:Home},
    {path:'/movie',component:Movie},
    {path:'/about',component:About}
  ]
})

// 将创建好的实例对象向外暴露
export default router

3.vue-router的常见用法

1. 路由重定向:链接的强制跳转(redirect 属性)【在index.js文件中写的对应关系】

路由重定向指的是:用户在访问地址 A 的时候,强制用户跳转到地址 C ,从而展示特定的组件页面。 通过路由规则的 redirect 属性,指定一个新的路由地址,可以很方便地设置路由的重定向:

// 创建路由的实例对象
const router=new VueRouter({
  // routes是一个数组,作用:定义”hash地址“与”组件“之间的对应关系
  // routes:[{path:'/具体的组件地址(不能再前面加”#“)',component:要展示的组件}]
  routes:[
    // 重定向的路由规则
    {path:'/',redirect:'/home'},
    // 路由规则
    {path:'/home',component:Home},
    {path:'/movie',component:Movie},
    {path:'/about',component:About}
  ]
})

 2. 嵌套路由:实现组件的嵌套展示

3.1 声明子路由链接子路由占位符 (在About组件中写的)

子级路由链接

子级路由占位符

 

<template>
  <div class="about-container">
    <h3>About 组件</h3>

    <!-- 子级路由链接 -->
    <router-link to="/about/tab1">tab1</router-link>
    <router-link to="/about/tab2">tab2</router-link>
    <hr>
    <!-- 子级路由占位符 -->
    <router-view></router-view>
    
  </div>
</template>

3.2 通过 children数组 属性声明子路由规则 

在 src/router/index.js 路由模块中,导入需要的组件,并使用 children 属性声明子路由规则:

使用children属性中包裹的子路由path路径中不能加”/“

父路由的path必须加”/“ 


import Tab1 from '@/components/tabs/Tab1.vue'
import Tab2 from '@/components/tabs/Tab2.vue'


// 创建路由的实例对象
const router=new VueRouter({
  // routes是一个数组,作用:定义”hash地址“与”组件“之间的对应关系
  // routes:[{path:'/具体的组件地址(不能再前面加”#“)',component:要展示的组件}]
  routes:[
    // 重定向的路由规则
    {path:'/',redirect:'/home'},
    // 路由规则
    {path:'/home',component:Home},
    {path:'/movie',component:Movie},
    {path:'/about',
    component:About,
    children:[
      // 子路由规则
      {path:'tab1',component:Tab1},
      {path:'tab2',component:Tab2}
    ]}
  ]
})

3.3 默认子路由

1.使用redirect属性

// 创建路由的实例对象
const router=new VueRouter({
  // routes是一个数组,作用:定义”hash地址“与”组件“之间的对应关系
  // routes:[{path:'/具体的组件地址(不能再前面加”#“)',component:要展示的组件}]
  routes:[
    // 重定向的路由规则
    {path:'/',redirect:'/home'},
    // 路由规则
    {path:'/home',component:Home},
    {path:'/movie',component:Movie},
    {path:'/about',
    component:About,
    redirect:'/about/tab1',
    children:[
      // 子路由规则
      {path:'tab1',component:Tab1},
      {path:'tab2',component:Tab2}
    ]}
  ]
})

 2. 默认子路由

默认子路由:如果children数组中,某一个路由规则path值为空字符串,则这条路由规则,叫做“默认子路由”

    <!-- 子级路由链接 -->
    <router-link to="/about/">tab1</router-link>

// 创建路由的实例对象
const router=new VueRouter({
  // routes是一个数组,作用:定义”hash地址“与”组件“之间的对应关系
  // routes:[{path:'/具体的组件地址(不能再前面加”#“)',component:要展示的组件}]
  routes:[
    // 重定向的路由规则
    {path:'/',redirect:'/home'},
    // 路由规则
    {path:'/home',component:Home},
    {path:'/movie',component:Movie},
    {path:'/about',
    component:About,
    redirect:'/about/tab1',
    children:[
      // 子路由规则
      // 默认子路由:如果children数组中,某一个路由规则path值为空字符串,则这条路由规则,叫做“默认子路由”
      // {path:'tab1',component:Tab1},
      {path:'',component:Tab1},
      {path:'tab2',component:Tab2}
    ]}
  ]
})

4. 动态路由匹配

 4.1 动态路由的概念

动态路由指的是:把 Hash 地址中可变的部分定义为参数项,从而提高路由规则的复用性。 在 vue-router 中使用英文的冒号(:)来定义路由的参数项。示例代码如下

 

 4.2 $route.params 参数对象

在动态路由渲染出来的组件中,可以使用 this.$route.params.id名 对象访问到动态匹配的参数值。

    注意:在hash地址中,/后面的参数项,叫做”路径参数“

    在路由”参数对象中,需要使用this.$route.parms来访问路径参数

 4.3 使用 props 接收路由参数

为了简化路由参数的获取形式,vue-router 允许在路由规则中开启 props 传参(index.js)

    // props:true---表示为Movie这个组件开启自定义属性
    // 可以为路由规则开启props传参,从而方便的拿到动态参数的值
    {path:'/movie/:id',component:Movie,props:true},


  // 接收props数组
  props:['id']


    <h3>Movie 组件---{{ id }}</h3>

 4.4 query和fullPath

 

    注意1:在hash地址中,/后面的参数项,叫做”路径参数“

    在路由”参数对象“中,需要使用this.$route.parms来访问路径参数

    注意2:在hash地址中,?后面的参数项,叫做”查询参数“

    在路由”参数对象“中,需要使用this.$route.query来访问查询参数

    注意3:在this.$route中,path只是路径部分,fullPath是完整的地址

    例如:

    /movie/2?name=zs&age=20  是fullPath的值

    /movie/2  是fullPath的值


    <router-link to="/movie/2?name=zs&age=20">雷神</router-link>

 5. 声明式导航 & 编程式导航

 5.1 vue-router 中的编程式导航 API

5.2 this.$router.push('hash地址):增加一条记录

调用 this.$router.push() 方法,可以跳转到指定的 hash 地址,从而展示对应的组件页面。

  <button @click="gotoLK">跳转到”洛基“页面</button>

  methods:{
    gotoLK(){
      // 通过编程式导航API,导航跳转到指定的页面
      this.$router.push('/movie/1')
    }
  }

 5.3 this.$router.replace('hash地址'):替换掉当前的历史记录

调用 this.$router.replace() 方法,可以跳转到指定的 hash 地址,从而展示对应的组件页面。

push 和 replace 的区别:

⚫ push 会增加一条历史记录

⚫ replace 不会增加历史记录,而是替换掉当前的历史记录


    <button @click="gotoLK2">通过replace跳转到”洛基“页面</button>



    gotoLK2(){
      // replace---替换当前的页面,没办法回之前的页面
      this.$router.replace('/movie/1')
    }

 5.4  this.$router.go

调用 this.$router.go() 方法,可以在浏览历史中前进和后退。


    <button @click="goback">后退</button>


    goback(){
      // 返回前一个页面
      // 如果后退的层数超过上限,则原地不动
      // this.$router.go(-1);
      this.$router.go(-2);
    }

 5.5 $router.go 的简化用法


    <!-- 在行内使用编程式导航跳转的时候,this必须要省略,否则报错 --> 
   <!-- back()--后退一层 -->
    <button @click="$router.back()">back 后退</button>
    <!-- forward()--前进一层 -->
    <button @click="$router.forward()">forward 前进</button>

 6. 导航守卫:以控制路由的访问权限

 

 6.1 全局前置守卫:全局生效

每次发生路由的导航跳转时,都会触发全局前置守卫。因此,在全局前置守卫中,程序员可以对每个路由进行 访问权限的控制:

 

6.2 守卫方法的 3 个形参

全局前置守卫的回调函数中接收 3 个形参


// 创建路由的实例对象
const router=new VueRouter()

// 为router实例对象,声明全局前置导航守卫
// 只要发生了路由的跳转,必然会触发beforEach指定的function回调函数
router.beforeEach(function(to,from,next){
  // to-表示要访问的路由信息对象
  // from-表示将要离开的路由信息对象
  // next()函数表示放行,如果被调用就会报错
  next()
})

6.3 next 函数的 3 种调用方式

当前用户拥有后台主页的访问权限,直接放行:next()

当前用户没有后台主页的访问权限,强制其跳转到登录页面:next('/login')

当前用户没有后台主页的访问权限,不允许跳转到后台主页:next(false)

6.4 控制后台主页的访问权限 

// 为router实例对象,声明全局前置导航守卫
// 只要发生了路由的跳转,必然会触发beforEach指定的function回调函数
router.beforeEach(function(to,from,next){
  // to-表示要访问的路由信息对象
  // from-表示将要离开的路由信息对象
  // next()函数表示放行,如果被调用就会报错
  // 分析:
  // 1. 要拿到用户将要访问的 hash 地址
  // 2. 判断 hash 地址是否等于 /main。
  // 2.1 如果等于 /main,证明需要登录之后,才能访问成功
  // 2.2 如果不等于 /main,则不需要登录,直接放行  next()
  // 3. 如果访问的地址是 /main。则需要读取 localStorage 中的 token 值
  // 3.1 如果有 token,则放行
  // 3.2 如果没有 token,则强制跳转到 /login 登录页
  if (to.path === '/main') {
    // 要访问后台主页,需要判断是否有 token
    const token = localStorage.getItem('token')
    if (token) {//表示拿到token值
      next()
    } else {
      // 没有登录,强制跳转到登录页
      next('/login')
    }
  }//表示不是要进入后台主页
   else {
    next()
  }
})

4.后台管理案例

1. 案例效果

 2. 案例用到的知识

 2.1 配置路由

1.安装vue-router的包

2.在src中创建一个router的文件夹,在此文件夹中创建一个index.js

3.在main.js中导入路由

index.js

import Vue from 'vue'
import VueRouter from 'vue-router'

// 将插件装上
Vue.use(VueRouter)

const router=new VueRouter()

export default router

main.js


// 导入路由模块
import router from '@/router/index.js'

new Vue({
  render: h => h(App),
  router:router
}).$mount('#app')

2.2 基于路由渲染登录组件

1.在inedx.js文件中导入需要进行链接的hash地址和组件

2.在App.vue中输入占位符

3.将进入“/”地址页面的时候,强制跳转到login页面(redirect)

{path:'/',redirect:'/login'}

index.js


// 导入需要的组件
import Login from '@/components/MyLogin.vue'

const router=new VueRouter({
  // 路由规则,建立hash地址与组件的对应关系
  routes:[
    // 当进入/地址的时候强制跳转到login页面
    {path:'/',redirect:'/login'},
    {path:'/login',component:Login},
  ]
})

App.vue

<template>
  <div>
    <!-- 路由占位符 -->
    <router-view></router-view>
  </div>
</template>

2.3 模拟登录功能

1.给MyLogin组件输入data数据

2.分别给登录名和登录密码加上v-molde双向数据绑定

3.判断输入的是否与设置的一致

        3.1 如果登录成功则记录token并且跳转页面

        3.2 如果登录失败,则移除token

MyLogin.vue


          <!-- 给登录名进行数据的双向绑定:v-modle -->
 <input type="text" class="form-control ml-2" id="username" placeholder="请输入登录名称" autocomplete="off" v-model.trim="username">
<input type="password" class="form-control ml-2" id="password" placeholder="请输入登录密码" v-model.trim="passward">
<button type="button" class="btn btn-secondary mr-2" @click="reset">重置</button>
<button type="button" class="btn btn-primary" @click="login">登录</button>

<script>
export default {
  name: 'MyLogin',
  data(){
    return {
      username:'',
      passward:''
    }
  },
  methods:{
    // 重置按钮
    reset(){
      this.username='',
      this.passward=''
    },
    login(){
      if(this.username==='admin' && this.passward==='666666'){
      // 登录成功
      // 1.存储token
      localStorage.setItem('token','Bearer xxxx')
      // 2.跳转到后台主页
      this.$router.push('/home')
    }else{
      //登录失败
      // 清除token
      localStorage.removeItem('token')
    }
   }
  }
}
</script>

2.4 实现后台主页的基础布局

1.在index.js文件中声明hash地址与组件的对应关系

// 导入需要的组件
import Home from '@/components/MyHome.vue'

// 将插件装上
Vue.use(VueRouter)

const router=new VueRouter({
  // 路由规则,建立hash地址与组件的对应关系
  routes:[
    // 后台主页的路由规则
    {path:'/home',component:Home}
  ]
})

2.在MyHome.vue组件中编写页面的形式

<template>
  <div class="home-container">
    <!-- 头部区域 -->
    <MyHeader></MyHeader>

    <!-- 页面主体区 -->
    <div class="home-main-box">
      <!-- 左侧边栏 -->
      <MyAside></MyAside>

      <!-- 右侧内容主题区域 -->
      <div class="home-main-body">
        123
      </div>

    </div>
  </div>
</template>


<script>
// 头部区域组件
import MyHeader from './subcomponents/MyHeader.vue'
// 左侧边栏组件
import MyAside from './subcomponents/MyAside.vue'

export default {
  name: 'MyHome',
  // 注册组件
  components: {
    MyHeader,
    MyAside,
  },
}
</script>

<style lang="less" scoped>
.home-container {
  height: 100%;
  display: flex;
  flex-direction: column;

  .home-main-box {
    height: 100%;
    display: flex;
    .home-main-body {
      padding: 15px;
      flex: 1;
    }
  }
}
</style>

2.5 退出登录并控制访问权限

1.在退出登录组件中添加带点击事件的时候,删除token


 <button type="button" class="btn btn-light" @click="loginout">退出登录</button>


  methods:{
    loginout(){
      // 1.清空token
      localStorage.removeItem('token')
      // 2.跳转到登录页面
      this.$router.push('/login')
    }
  }

2.因为当未登录的时候可以访问home组件,所以要在index.js文件中使用全局守卫保护数据

// 全局前置守卫
router.beforeEach(function(to,from,next){
  if(to.path==='/home'){
    // 如果要访问的页面是home,则先进行判断是否已经登录
    const token=localStorage.getItem('token')
    if(token){
      next()
    }else{
      next('/login')
    }
  }else{
    next()
  }
})

2.6 实现子路由的嵌套展示

1.给左侧添加<router-link>链接,并添加to属性【MyAside组件】

    <!-- 左侧边栏列表 -->
    <ul class="user-select-none menu">
      <li class="menu-item">
        <router-link to="/home/users">用户管理</router-link>
      </li>
      <li class="menu-item">
        <router-link to="home/rights">权限管理</router-link>
      </li>
      <li class="menu-item">
        <router-link to="/home/goods">商品管理</router-link>
      </li>
      <li class="menu-item">
        <router-link to="/home/orders">订单管理</router-link>
      </li>
      <li class="menu-item">
        <router-link to="/home/setting">系统设置</router-link>
      </li>
    </ul>

 2.在右侧组件中添加占位符【MyHome组件】

      <!-- 右侧内容主题区域 -->
      <div class="home-main-body">
        <router-view></router-view>
      </div>

3.给地址为“/home”的地址添加子路由嵌套【children添加子路由】

const router=new VueRouter({
  // 路由规则,建立hash地址与组件的对应关系
  routes:[
    // 当进入/地址的时候强制跳转到login页面
    {path:'/',redirect:'/login'},
    {path:'/login',component:Login},
    // 后台主页的路由规则
    {path:'/home',component:Home,children:[
      // children中的path路径中不能加“/”
      { path: 'users', component: Users },
      { path: 'rights', component: Rights },
      { path: 'goods', component: Goods },
      { path: 'orders', component: Orders },
      { path: 'settings', component: Settings },
    ]}
  ]
})

2.7 点击进入用户详情页面

1.先将data数渲染到页面

      <tbody>
        <!-- 使用循环拿到data中的数据进行渲染 -->
        <tr v-for="item in userlist" :key="item.id">
          <td>{{ item.id }}</td>
          <td>{{ item.name }}</td>
          <td>{{ item.age }}</td>
          <td>{{ item.position }}</td>
          <td>
            <a href="#">详情</a>
          </td>
        </tr>
      </tbody>

<script>
export default {
  name: 'MyUser',
  data() {
    return {
      // 用户列表数据
      userlist: [
        { id: 1, name: '嬴政', age: 18, position: '始皇帝' },
        { id: 2, name: '李斯', age: 35, position: '丞相' },
        { id: 3, name: '吕不韦', age: 50, position: '商人' },
        { id: 4, name: '赵姬', age: 48, position: '王太后' }
      ]
    }
  }
}
</script>

 2.当点击“详情”的时候进行跳转

          <td>
            <!-- 阻止默认行为 -->
            <a href="#" @click.prevent="gotoDetail">详情</a>
          </td>


3.右边的“详情”应该跟左边的“用户管理”是兄弟,所以要设置路由规则,跟其平级【在index.js中】


// 详情页
import UserDetail from '@/components/user/MyUserDetail.vue'

    // 后台主页的路由规则
    {path:'/home',component:Home,children:[
      // children中的path路径中不能加“/”
      { path: 'users', component: Users },
      { path: 'rights', component: Rights },
      { path: 'goods', component: Goods },
      { path: 'orders', component: Orders },
      { path: 'settings', component: Settings },
      // 用户详情页的路由规则
      {path:'userinfo',component:UserDetail}
    ]}

4.补充gotoDetail函数

  methods:{
    gotoDetail(){
      // console.log('ok');
      this.$router.push('/home/userinfo')
    },
  }

5. 设置后退按钮功能【在MyUserDetaile】


    <button type="button" class="btn btn-light btn-sm" @click="$router.back()">后退</button>

2.8 升级用户详情页面的路由规则

1.因为如果单纯写路径不够完整,应该将id传入

          <td>
            <!-- 阻止默认行为 -->
            <!-- 往gotoDetail中加入要进行跳转的id -->
            <a href="#" @click.prevent="gotoDetail(item.id)">详情</a>
          </td>


  methods:{
    gotoDetail(id){
      // console.log('ok');
      this.$router.push('/home/userinfo/'+id)
    },
  }

2.给路由规则添加动态绑定的id

      // 用户详情页的路由规则
      {path:'userinfo/:id',component:UserDetail}

3.在第1个页面拿到第一个页面应该出现的组件

2.9 当登录进去后要直接跳转到users页面【redirect】

    // 后台主页的路由规则
    {path:'/home',
    component:Home,
    // 添加重定向,强制从登录页面一进来直接进入/home/users页面
    redirect:'/home/users',
    children:[
      // children中的path路径中不能加“/”
      { path: 'users', component: Users },
      { path: 'rights', component: Rights },
      { path: 'goods', component: Goods },
      { path: 'orders', component: Orders },
      { path: 'settings', component: Settings },
      // 用户详情页的路由规则
      {path:'userinfo/:id',component:UserDetail,props:true}
    ]}

 2.10 如何控制多页面的权限【多写一个数组存放】

// 全局前置守卫
router.beforeEach(function(to,from,next){
  const pathArr=['/home','/home/users','.home/rights']
  if(pathArr.indexOf(to.path)!==-1){
    // 如果要访问的页面是home,则先进行判断是否已经登录
    const token=localStorage.getItem('token')
    if(token){
      next()
    }else{
      next('/login')
    }
  }else{
    next()
  }
})

总结

5. 完整的页面项目:Headline | 黑马头条 - 移动端

1.创建(vue create demo-toutiao)并梳理项目结构

1.先将自动生成的App组件中生成的代码删除

<template>
  <div>
    <h1>App 根组件</h1>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

<style lang="less" scoped>

</style>

 2.将views和components文件夹中的所有文件删除,并且将router文件夹中的index.js文件中的router中的内容清除

import Vue from 'vue'
import VueRouter from 'vue-router'

// 把VueRouter安装为Vue的插件
Vue.use(VueRouter)

// 路由规则的数组
const routes = [

]

// 创建路由实例对象
const router = new VueRouter({
  routes
})

export default router

 2.初始化-安装和配置Vant组件库

Vant 2 - 轻量、可靠的移动端组件库 (gitee.io)

1.使用Vant步骤:安装,配置,使用

npm i vant@latest-v2 -S --legacy-peer-deps

 2.导入组件【在main.js文件中导入】

// 导入并安装Vant组件库
import Vant from 'vant'
import 'vant/lib/index.css'

Vue.use(Vant)

3.使用Tabbar组件并开启路由模式【因为实现了路由切换所以放在view文件夹下面

 

 1.初始化页面

<template>
<div>
  <h1>Home 组件</h1>
</div>
</template>

<script>
export default {
  name: 'Home'
}
</script>

<style lang="less" scoped>

</style>

<template>
  <div>
  <h1>User 组件</h1>
  </div>
</template>

<script>
export default {
  name: 'User'
}
</script>

<style lang="less" scoped>

</style>

2.在App组件中创建占位符

3.将Tabbar组件放入【放在App组件中】

<template>
  <div>

    <!-- 路由占位符 -->

    <!-- Tabbar区域 -->
    <van-tabbar v-model="active">
      <van-tabbar-item icon="home-o" >首页</van-tabbar-item>
      <van-tabbar-item icon="user-o" >我的</van-tabbar-item>
  </van-tabbar>

  </div>
</template>

<script>
export default {
  data () {
    return {
      active: 0
    }
  }
}
</script>

4.添加路由模式

<template>
  <div>

    <!-- 路由占位符 -->

    <!-- Tabbar区域 -->
    <van-tabbar v-model="active">
      <van-tabbar-item icon="home-o" to="/">首页</van-tabbar-item>
      <van-tabbar-item icon="user-o" to="/user">我的</van-tabbar-item>
  </van-tabbar>

  </div>
</template>

<script>
export default {
  data () {
    return {
      active: 0
    }
  }
}
</script>

4.通过路由展示对应的Tabbar页面

1.将页面上定一个占位符【在App组件中定义】

    <!-- 路由占位符 -->
    <router-view></router-view>

2.将组件和hash地址的对应关系写在router文件加下index.js文件中

import Vue from 'vue'
import VueRouter from 'vue-router'

// 导入需要的组件
import Home from '@/views/Home/Home.vue'
import User from '@/views/User/User.vue'

Vue.use(VueRouter)

const routes = [
  // 定义首页的路由规则
  { path: '/', component: Home },
  // 定义我的路由规则
  { path: '/user', component: User }
]

const router = new VueRouter({
  routes
})

export default router

5.使用Navbar导航栏组件

1.导入组件中的的模块【应该放在Home组件中】

<template>
  <div>
    <van-nav-bar
  title="标题"
  left-text="返回"
  right-text="按钮"
  left-arrow
/>
  </div>
</template>

 

2.设置标题为固定定位【:fixed="true"】

<template>
  <div>
    <van-nav-bar title="标题" :fixed="true"/>
  </div>
</template>

 3.将被标题遮挡住的内容进行显示【分别给div加上下边框】

6.覆盖Navbar的默认样式

1.使用调试工具进行修改

 2.注意点:

7.了解获取列表数据的API接口

 8.封装utils目录下的request模块【使用axios】

1.安装axios包

 npm i axios -S -legacy-peer-deps

2.配置axois【多创建一个request.js文件】

 

import axios from 'axios'

// request 就是小axios
const request = axios.create({
  // 指定请求的根路径
  baseURL: 'https://applet-base-api-t.itheima.net'
})

export default request

9.在Home组件中封装initArticleList方法【当进入Home页面的时候自动请求数据显示】

1.先在Home组件中导入request.js文件

// 导入request.js
import reuqest from '@/utils/request.js'

2.先在data中声明数据

  data () {
    return {
      // 页码值
      page: 1,
      // 每一页显示多少条数据
      limit: 10
    }
  },

3.封装函数

  methods: {
    // 封装获取文章列表数据的方法
    async initArticleList () {
      // 发起GET请求,获取文章的列表数据
      // 获取到的对象是Promise,所以要使用await和async
      const { data: res } = await reuqest.get('/articles', {
        // 请求参数
        params: {
          _page: this.page,
          _limit: this.limit
        }
      })

      console.log(res)
    }
  }

4.调用该函数【created要写在函数的声明前】

  created () {
    this.initArticleList()
  },

10.文章列表--封装articleAPI模块

1.先在User组件中声明一个函数,当进入User页面的时候,获取5条数据

<script>

// 导入
import request from '@/utils/request.js'

export default {
  data () {
    return {
      page: 1,
      limit: 5
    }
  },
  created () {
    this.initArticleList()
  },
  name: 'User',
  methods: {
    async initArticleList () {
      const { data: res } = await request.get('/articles', {
        params: {
          _page: this.page,
          _limit: this.limit
        }
      })

      console.log(res)
    }
  }
}
</script>

2.因为重复写了调用接口的Promise所以直接写成一个js文件然后进行调用

3.在src文件夹下新建一个文件夹为api,然后再这个文件夹中新建一个articleAPI.js文件

 

4.因为获取到的是一个Promise对象,所以将这个Promise对象放在articleAPI.js文件中,如果需要则直接再外调用

 

 api/articleAPI.js

// 文章相关的API接口,都封装到这个模块中

// 导入request组件
import request from '@/utils/request'

// 向外按需导出一个API函数
/*   这是一个Promise对象
   request.get('/articles', {
    params: {
      _page: this.page,
      _limit: this.limit
    }
  })
  */
export const getArticleListAPI = function (_page, _limit) {
  // console.log('调用了getArticleListAPI这个函数')

  return request.get('/articles', {
    params: {
      _page: _page,
      _limit: _limit
    }
  })
}

User.vue

<script>

// 导入
// import request from '@/utils/request.js'

// 按需导入API接口
import { getArticleListAPI } from '@/api/articleAPI.js'

// 此时获取到的result是一个Promise对象
// const result = getArticleListAPI(1, 5)
// console.log(result)

export default {
  data () {
    return {
      page: 1,
      limit: 5
    }
  },
  created () {
    this.initArticleList()
  },
  name: 'User',
  methods: {
    async initArticleList () {
      // const { data: res } = await Promise对象
      const { data: res } = await getArticleListAPI(this.page, this.limit)

      console.log(res)
    }
  }
}
</script>

Home.vue

<script>

// 导入request.js
// import reuqest from '@/utils/request.js'

// 按需导入API接口
import { getArticleListAPI } from '@/api/articleAPI'

export default {
  name: 'Home',
  data () {
    return {
      // 页码值
      page: 1,
      // 每一页显示多少条数据
      limit: 10
    }
  },
  created () {
    this.initArticleList()
  },
  methods: {
    // 封装获取文章列表数据的方法
    async initArticleList () {
      // 发起GET请求,获取文章的列表数据
      // 获取到的对象是Promise,所以要使用await和async
      const { data: res } = await getArticleListAPI(this.page, this.limit)

      console.log(res)
    }
  }
}
</script>

11.文章列表--封装ArticleInfo组件

1.当我们拿到一份数据要再页面上进行渲染,则应该将这份数据存放再data里面

 

2.创建一个ArticleInfo的组件【存放再components文件夹下的】

 

<template>
  <div>
    <h1>ArticleInfo 组件</h1>
  </div>
</template>

<script>
export default {
  name: 'ArticleInfo'
}
</script>

<style lang="less" scoped>

</style>

3.在Home组件中导入,注册使用ArticleInfo组件

<template>
  <div class="home-container">

    <!-- 导入,注册,并使用ArticleInfo组件 -->
    <h1> {{ artlist.length }}</h1>
    <!-- 3.使用 ArticleInfo组件 -->
    <ArticleInfo></ArticleInfo>

  </div>
</template>



// 1.导入ArticleInfo组件
import ArticleInfo from '@/components/Article/ArticleInfo.vue'

  // 2.注册组件
  components: {
    ArticleInfo
  }

4.动态循环ArticleInfo组件

    <ArticleInfo v-for="item in artlist" :key="item.id"></ArticleInfo>

12.文章列表-为ArticleInfo组件封装props属性

1.在ArticleInfo组件中封装属性

ArticleInof组件

          <!-- 标题 -->
          <span>{{title}}</span>

        <div class="label-box">
          <span>作者{{ author}}&nbsp;&nbsp; {{cmtCount}}评论 &nbsp;&nbsp; 发布日期 {{ time }}</span>


<script>
export default {
  name: 'ArticleInfo',
  // 自定义属性
  props: {
    // 标题
    title: {
      type: String,
      default: ''
    },
    // 作者名字
    author: {
      type: String,
      default: ''
    },
    // 评论数
    cmtCount: {
      // 通过数组形式,为当前属性定义多个可能的类型
      type: [Number, String],
      default: 0
    },
    // 发布日期
    time: {
      type: String,
      default: ''
    }
  }
}
</script>

 Home组件

    <!-- 导入,注册,并使用ArticleInfo组件 -->
    <!-- 3.使用 ArticleInfo组件 -->
    <ArticleInfo v-for="item in artlist" :key="item.id"
    :title="item.title"
    :author="item.aut_name"
    :cmtCount="item.comm_count"
    :time="item.pubdate"
    ></ArticleInfo>

13.文章列表--为ArticleInfo组件封装cover属性(封面图片)

1.定义cover属性

    // 封面的信息对象
    cover: {
      type: Object,
      // 通过default函数,返回cover属性的默认值
      default: function () {
        //这个return的对象就是cover属性的默认值
        return { type: 0 }
      }
    }

2.动态添加属性

3.在ArticleInfo组件进行判断是显示几张照片

 

4.给图片动态绑定

 

 

14.上拉加载更多--了解List组件的基本使用

Vant 2 - 轻量、可靠的移动端组件库 (gitee.io)

 15.上拉加载更多--初步使用List组件

1.先将代码封装在<van-list></van-list>

2.初始化默认值和onLoad函数

 

      // 是否正在加载下一页数据,如果loading为true 则不会反复触发load事件
      // 每当下一页数据请求回来之后,千万要记得,把loading从true改为false
      // 因为刚刚进入页面的时候,不需要触发onLoad事件,所以应该将loading初始化为true,才不会进行数据请求
      loading: true,
      // 所有数据是否加载完毕了,如果没有更多数据了,一定要把finished改为true
      finished: false

    // 只要onLoad被调用,就应该请求下一页数据
    onLoad () {
      console.log('触发了load事件')
    }

3.因为初始化loading为true,当第一页数据渲染完之后,就应该将loading改为false,才可与进行访问第二页数据

    async initArticleList () {
      // 发起GET请求,获取文章的列表数据
      // 获取到的对象是Promise,所以要使用await和async
      const { data: res } = await getArticleListAPI(this.page, this.limit)

      // console.log(res)
      this.artlist = res
      // 当第一页渲染完,应该将loading改为false,等一下才可以访问第二页
      this.loading = false
    },

 

16.上拉加载更多--实现上拉加载更多的效果

1.上拉触发事件

    // 只要onLoad被调用,就应该请求下一页数据
    onLoad () {
      // console.log('触发了load事件')
      // 1.让页码值+1
      this.page++
      // 2.重新请求接口获取数据
      this.initArticleList()
    }

2.上面的写法会将第一页的数据进行覆盖,所以要使用将旧数据中的数据记录下来

    async initArticleList () {
      // 发起GET请求,获取文章的列表数据
      // 获取到的对象是Promise,所以要使用await和async
      const { data: res } = await getArticleListAPI(this.page, this.limit)

      // console.log(res)
      // this.artlist = res
      // 如果上拉加载更多,那么应该是
      // this.artlist=[旧数据在前,新数据在后]
      this.artlist = [...this.artlist, ...res]

      // 当第一页渲染完,应该将loading改为false,等一下才可以访问第二页
      this.loading = false
    },

 3.页数的判断条件

 

    // 封装获取文章列表数据的方法
    async initArticleList () {
      // 发起GET请求,获取文章的列表数据
      // 获取到的对象是Promise,所以要使用await和async
      const { data: res } = await getArticleListAPI(this.page, this.limit)

      // console.log(res)
      // this.artlist = res
      // 如果上拉加载更多,那么应该是
      // this.artlist=[旧数据在前,新数据在后]
      this.artlist = [...this.artlist, ...res]

      // 当第一页渲染完,应该将loading改为false,等一下才可以访问第二页
      this.loading = false
      if (res.length === 0) {
        // 证明没有下一页数据了,直接把finished改为true,表示数据加载完了
        this.finished = true
      }

 过程:

1.在触发o'n'Load的时候先让页码值+1

2.重新请求接口获取数据

3.对获取回来的数据进行拼接

4.判断是否有下一页

17.下拉刷新--实现下拉刷新的功能

1.将下拉刷新的模块导入

2.初始化onRefresh和onRefresh函数

 

      // 是否下拉刷新
      refreshing: false

    // 下拉刷新的处理函数
    onRefresh () {
      // console.log('触发了下拉刷新')
      // 1.让页码值+1
      this.page++
      // 2.重新请求接口获取数据
      this.initArticleList()
    }

3.在initArticleList函数内判断是要上拉刷新还是下拉刷新(因为数据的拼接顺序不一样)

      if (isRefresh) {
        // 证明是下拉刷新:新数据在前,旧数据在后
        this.artlist = [...res, ...this.artlist]
      } else {
      // 如果上拉加载更多,那么应该是
      // this.artlist=[旧数据在前,新数据在后]
        this.artlist = [...this.artlist, ...res]
      }


      // 2.重新请求接口获取数据
      // 只有当下拉刷新的时候才需要在调用该函数的时候传参
      // async initArticleList (isRefresh) {
      this.initArticleList(true)

 4.当我们下拉请求数据后应该将isLoading改为false,要不然没办法进行第二次数据请求

      if (isRefresh) {
        // 证明是下拉刷新:新数据在前,旧数据在后
        this.artlist = [...res, ...this.artlist]
        this.isLoading = false
      } else {
      // 如果上拉加载更多,那么应该是
      // this.artlist=[旧数据在前,新数据在后]
        this.artlist = [...this.artlist, ...res]
      }

 5.判断数据是否已经渲染完毕

 

    <van-pull-refresh v-model="isLoading" :disabled="finished" @refresh="onRefresh">
    <van-list v-model="loading" :finished="finished" finished-text="没有更多了" @load="onLoad" >
    <!-- 导入,注册,并使用ArticleInfo组件 -->
    <!-- 3.使用 ArticleInfo组件 -->
    <ArticleInfo v-for="item in artlist" :key="item.id"
    :title="item.title"
    :author="item.aut_name"
    :cmtCount="item.comm_count"
    :time="item.pubdate"
    :cover="item.cover"
    ></ArticleInfo>
  </van-list>
</van-pull-refresh>

 

18.定制主题-说明Vant定制主题的核心原理【less变量的覆盖】

Vant 2 - 轻量、可靠的移动端组件库 (gitee.io)

1.在main.js文件中导入less文件

 

// 切记:为了能够覆盖默认的less变量,这里一定要把后缀名改为.less
import 'vant/lib/index.less'

2.修改样式变量

 


// 这个文件是 vue-cli 创建出来的项目的配置文件
// 在 vue.config.js 这个配置文件中,可以对整个项目的打包、构建进行全局性的配置

// webpack 在进行打包的时候,底层用到了 node.js
// 因此,在 vue.config.js 配置文件中,可以导入并使用 node.js 中的核心模块

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  css: {
    loaderOptions: {
      less: {
        modifyVars: {
          // 直接覆盖变量
          // 或者可以通过 less 文件覆盖(文件路径为绝对路径)
          hack: 'true; @import "your-less-file-path.less";'
        }
      }
    }
  }
})

 3.通过theme.less定制主题【在src文件夹根目录下创建一个theme.less文件】

 

// 在theme.less文件中,覆盖Vant官方的less 变量值
@blue:#007bff;


// 覆盖Navbar的less样式
@nav-bar-background-color:@blue;

 4.导入less文件【在vue.config.js文件中导入】


// 这个文件是 vue-cli 创建出来的项目的配置文件
// 在 vue.config.js 这个配置文件中,可以对整个项目的打包、构建进行全局性的配置

// webpack 在进行打包的时候,底层用到了 node.js
// 因此,在 vue.config.js 配置文件中,可以导入并使用 node.js 中的核心模块

const path = require('path')
// __dirname--当前的根目录
const themePath = path.join(__dirname, './src/theme.less')

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  css: {
    loaderOptions: {
      less: {
        modifyVars: {
          // 直接覆盖变量
          // 或者可以通过 less 文件覆盖(文件路径为绝对路径)
          //  ../    ./    theme.less
          // 从盘符开始的路径,叫做绝对路径   C:\\Users\liulongbin\\theme.less
          // hack: 'true; @import "绝对路径";'(外面是使用模板字符串)
          hack: `true; @import "${themePath}";`
        }
      }
    }
  }
})

打包发布

 1.npm run build【生成dist文件夹】

2.查看vue-cli文档

 3.在vue.config.js文件中添加

 

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  publicPath: '',
  transpileDependencies: true
})

 4.重新run一下

六、ES6模块化与异步编程高级用法

1.ES6模块化

1. 回顾:node.js 中如何实现模块化

 2. 前端模块化规范的分类

 3. 什么是 ES6 模块化规范

 ES6 模块化规范中定义:

⚫ 每个 js 文件都是一个独立的模块

⚫ 导入其它模块成员使用 import 关键字

⚫ 向外共享模块成员使用 export 关键字

4. 在 node.js 中体验 ES6 模块化

① 确保安装了 v14.15.1 或更高版本的 node.js

② 在 package.json 的根节点中添加 "type": "module" 节点

5. ES6 模块化的基本语法:默认导出【export default 默认导出的成员】

let n1=10;
let n2=20;
function show(){}

// 外界只能访问到n1和show,访问不到n2
export default{
    n1,
    show
}

注意事项

每个模块中,只允许使用唯一的一次 export default,否则会报错!

 5. ES6 模块化的基本语法:默认导入【import 接收名称 from '模块标识符'】

// 记得加上后缀名
import m1 from './01.默认导出.js'

console.log(m1);

注意事项

默认导入时的接收名称可以任意名称,只要是合法的成员名称即可:(数字开头报错)

 

 5.2 按需导出:【export 按需导出的成员

export let s1 = 'aaa'
export let s2 = 'ccc'
export function say() {}

export default {
  a: 20
}

 5.2 按需导入:【import { s1 } from '模块标识符'

import {s1, s2 , say} from './03.按需导出.js'

console.log(s1)
console.log(s2)
console.log(say)

 5.2 按需导出与按需导入的注意事项

① 每个模块中可以使用多次按需导出(但是默认导出只能有一个)

按需导入的成员名称必须和按需导出的名称保持一致

③ 按需导入时,可以使用 as 关键字进行重命名

④ 按需导入可以和默认导入一起使用

 5.3 直接导入并执行模块中的代码

如果只想单纯地执行某个模块中的代码,并不需要得到模块中向外共享的成员。此时,可以直接导入并执行模 块代码,示例代码如下:

2.Promise

1. 回调地狱:多层回调函数的相互嵌套

 回调地狱的缺点:

⚫ 代码耦合性太强,牵一发而动全身,难以维护

⚫ 大量冗余的代码相互嵌套,代码的可读性变差

1.1 如何解决回调地狱的问题

为了解决回调地狱的问题,ES6(ECMAScript 2015)中新增了 Promise 的概念

1.2 Promise 的基本概念

        ① Promise 是一个构造函数

⚫ 我们可以创建 Promise 的实例 const p = new Promise()

⚫ new 出来的 Promise 实例对象,代表一个异步操作

        ② Promise.prototype 上包含一个 .then() 方法

⚫ 每一次 new Promise() 构造函数得到的实例对象,

⚫ 都可以通过原型链的方式访问到 .then() 方法,例如 p.then()

        ③ .then() 方法用来预先指定成功和失败的回调函数

⚫ p.then(成功的回调函数,失败的回调函数)

p.then(result => { }, error => { })

⚫ 调用 .then() 方法时,成功的回调函数是必选的、失败的回调函数是可选的

 2. 基于回调函数按顺序读取文件内容

 3. 基于 then-fs 读取文件内容

由于 node.js 官方提供的 fs 模块仅支持以回调函数的方式读取文件不支持 Promise 的调用方式。因此,需 要先运行如下的命令,安装 then-fs 这个第三方包,从而支持我们基于 Promise 的方式读取文件的内容:

3.1 then-fs 的基本使用 

调用 then-fs 提供的 readFile() 方法,可以异步地读取文件的内容,它的返回值是 Promise 的实例对象。因 此可以调用 .then() 方法为每个 Promise 异步操作指定成功和失败之后的回调函数。示例代码如下:

 注意:上述的代码无法保证文件的读取顺序,需要做进一步的改进!

import thenFs from 'then-fs'

// .then()中的失败回调函数可以不写
// thenFs.readFile('./files/1.txt', 'utf8')--返回的是一个Promise对象【是异步的,所以每一次输出的数值顺序不一样】
thenFs.readFile('./files/1.txt', 'utf8').then((r1) => {console.log(r1)})
thenFs.readFile('./files/2.txt', 'utf8').then((r2) => {console.log(r2)})
thenFs.readFile('./files/3.txt', 'utf8').then((r3) => {console.log(r3)})

3.2 .then() 方法的特性

如果上一个 .then() 方法中返回了一个新的 Promise 实例对象,则可以通过下一个 .then() 继续进行处理。通 过 .then() 方法的链式调用,就解决了回调地狱的问题。

3.3 基于 Promise 按顺序读取文件的内容

Promise 支持链式调用,从而来解决回调地狱的问题。示例代码如下:

import thenFs from 'then-fs'

thenFs
  .readFile('./files/1.txt', 'utf8')
  .then((r1) => {
    console.log(r1)
    //将第二个Promise对象作为第一个对象成功的回调函数的return返回值返回出去,然后再用.then()进行接收
    return thenFs.readFile('./files/2.txt', 'utf8')
  })
  .then((r2) => {
    console.log(r2)
    return thenFs.readFile('./files/3.txt', 'utf8')
  })
  .then((r3) => {
    console.log(r3)
  })

3.4 通过 .catch 捕获错误

在 Promise 的链式操作中如果发生了错误,可以使用 Promise.prototype.catch 方法进行捕获和处理:

如果不希望前面的错误导致后续的 .then 无法正常执行,则可以将 .catch 的调用提前(放在最前面)

import thenFs from 'then-fs'

thenFs
  .readFile('./files/11.txt', 'utf8')
  // 把.catch()放再最前面,则就算出现错误,但是错误后面的then()不受到影响,还是可以继续执行
  .catch((err) => {
    console.log(err.message)
  })
  .then((r1) => {
    console.log(r1)
    //将第二个Promise对象作为第一个对象成功的回调函数的return返回值返回出去,然后再用.then()进行接收
    return thenFs.readFile('./files/2.txt', 'utf8')
  })
  .then((r2) => {
    console.log(r2)
    return thenFs.readFile('./files/3.txt', 'utf8')
  })
  .then((r3) => {
    console.log(r3)
  })

3.5 Promise.all() 方法【等所有的异步操作全部结束后才会执行下一步的 .then 操作】

Promise.all() 方法会发起并行的 Promise 异步操作,等所有的异步操作全部结束后才会执行下一步的 .then 操作(等待机制)。

注意:数组中 Promise 实例的顺序, 就是最终结果的顺序!

import thenFs from 'then-fs'

const promiseArr = [
  thenFs.readFile('./files/3.txt', 'utf8'),
  thenFs.readFile('./files/2.txt', 'utf8'),
  thenFs.readFile('./files/1.txt', 'utf8'),
]

// 数组中 Promise 实例的顺序, 就是最终结果的顺序
Promise.all(promiseArr).then(result => {
  console.log(result)//333,222,111
})

 3.6 Promise.race() 方法只要任何一个异步操作完成,就立即执行下一步的 .then 操作

Promise.race() 方法会发起并行的 Promise 异步操作,只要任何一个异步操作完成,就立即执行下一步的 .then 操作(赛跑机制)。

import thenFs from 'then-fs'

const promiseArr = [
  thenFs.readFile('./files/3.txt', 'utf8'),
  thenFs.readFile('./files/2.txt', 'utf8'),
  thenFs.readFile('./files/1.txt', 'utf8'),
]

// 数组中 Promise 实例的顺序, 就是最终结果的顺序
Promise.race(promiseArr).then(result => {
  console.log(result)//输出结果取决于哪一个文件读取较快
})

4. 基于 Promise 封装读文件的方法 

方法的封装要求:

方法的名称要定义为 getFile

② 方法接收一个形参 fpath,表示要读取的文件的路径

③ 方法的返回值为 Promise 实例对象

4.1 getFile 方法的基本定义 

 4.2 创建具体的异步操作

如果想要创建具体的异步操作,则需要在 new Promise() 构造函数期间,传递一个 function 函数,将具体的 异步操作定义到 function 函数内部。示例代码如下:

function getFile(fpath) {
  // return new Promise--创建了形式上的异步
  return new Promise(function (resolve, reject) {
    fs.readFile(fpath, 'utf8', (err, dataStr) => {
      


    })
  })
}

 4.3 获取 .then 的两个实参

通过 .then() 指定的成功和失败的回调函数,可以在 function 的形参中进行接收,

 4.4 调用 resolve(读取成功回调函数) reject(读取失败回调函数) 回调函数

Promise 异步操作的结果,可以调用 resolve 或 reject 回调函数进行处理

import fs from 'fs'

function getFile(fpath) {
  // return new Promise--创建了形式上的异步
  return new Promise(function (resolve, reject) {
    fs.readFile(fpath, 'utf8', (err, dataStr) => {
      // 如果读取失败,则调用失败的回调函数
      if (err) return reject(err)
      // 如果读取成功,则调用成功的回调函数
      resolve(dataStr)
    })
  })
}

getFile('./files/11.txt')
  .then((r1) => {
    console.log(r1)
  })
  .catch((err) => console.log(err.message))

3.async/await

1. 什么是 async/await

async/await 是 ES8(ECMAScript 2017)引入的新语法,用来简化 Promise 异步操作。在 async/await 出 现之前,开发者只能通过链式 .then() 的方式处理 Promise 异步操作。

.then 链式调用的优点: 解决了回调地狱的问题

.then 链式调用的缺点: 代码冗余、阅读性差、 不易理解

2. async/await 的基本使用 

import thenFs from 'then-fs'


async function getAllFile() {
  // thenFs.readFile--Promise 对象
  // 被await修饰过的Promise对象就变成了一个普通变量
  // 当函数内部有await修饰过的对象,构造函数外部要加上async
  const r1 = await thenFs.readFile('./files/1.txt', 'utf8')
  console.log(r1)
  const r2 = await thenFs.readFile('./files/2.txt', 'utf8')
  console.log(r2)
  const r3 = await thenFs.readFile('./files/3.txt', 'utf8')
  console.log(r3)
}

getAllFile()

 3. async/await 的使用注意事项

① 如果在 function 中使用了 await,则 function 必须被 async 修饰

② 在 async 方法中,第一个 await 之前的代码会同步执行,await 之后的代码会异步执行

 

import thenFs from 'then-fs'

console.log('A')
async function getAllFile() {
  //同步执行,因为下面是异步的,所以先跳出这个函数
  console.log('B')
  // thenFs.readFile--Promise 对象
  // 被await修饰过的Promise对象就变成了一个普通变量
  // 当函数内部有await修饰过的对象,构造函数外部要加上async
  const r1 = await thenFs.readFile('./files/1.txt', 'utf8')
  console.log(r1)
  const r2 = await thenFs.readFile('./files/2.txt', 'utf8')
  console.log(r2)
  const r3 = await thenFs.readFile('./files/3.txt', 'utf8')
  console.log(r3)
  console.log('D')
}

getAllFile()
console.log('C')
//输出结果为A B C 111 222 333 D

4.EventLoop

1. JavaScript 是单线程的语言:同一时间只能做一件事情

JavaScript 是一门单线程执行的编程语言。也就是说,同一时间只能做一件事情。

 2. 同步任务和异步任务

为了防止某个耗时任务导致程序假死的问题,JavaScript 把待执行的任务分为了两类:

        ① 同步任务(synchronous)

⚫ 又叫做非耗时任务,指的是在主线程上排队执行的那些任务

⚫ 只有前一个任务执行完毕,才能执行后一个任务

        ② 异步任务(asynchronous)

⚫ 又叫做耗时任务,异步任务由 JavaScript 委托给宿主环境进行执行

⚫ 当异步任务执行完成后,会通知 JavaScript 主线程执行异步任务的回调函数

3. 同步任务和异步任务的执行过程

 ① 同步任务由 JavaScript 主线程次序执行

异步任务委托给宿主环境执行

已完成的异步任务对应的回调函数,会被 加入到任务队列中等待执行

④ JavaScript 主线程的执行栈被清空后,会 读取任务队列中的回调函数,次序执行

⑤ JavaScript 主线程不断重复上面的第 4 步

4. EventLoop 的基本概念

JavaScript 主线程从“任务队列”中读取异步 任务的回调函数,放到执行栈中依次执行。这 个过程是循环不断的,所以整个的这种运行机 制又称为 EventLoop(事件循环)

 4. 结合 EventLoop 分析输出的顺序

 

5.宏任务和微任务(异步任务的划分)

1. 什么是宏任务和微任务

JavaScript 把异步任务又做了进一步的划分,异步任务又分为两类,分别是:

 

 2. 宏任务和微任务的执行顺序:宏任务-->微任务(交替执行)

 每一个宏任务执行完之后,都会检查是否存在待执行的微任务, 如果有,则执行完所有微任务之后,再继续执行下一个宏任务。

3. 去银行办业务的场景

4. 分析以下代码输出的顺序 

5. 经典面试题

console.log('1');
setTimeout(function(){
  console.log('2');
  new Promise(function(resolve){
    console.log('3');
    resolve()
  }).then(function(){
    console.log('4');
  })
})

new Promise(function(resolve){
  console.log('5');
  resolve()
}).then(function(){
  console.log('6');
})

setTimeout(function(){
  console.log('7');
  new Promise(function(resolve){
    console.log('8');
    resolve();
  }).then(function(){
    console.log('9');
  })
})


6.API接口案例

1. 案例需求

 2. 主要的实现步骤

 3. 搭建项目的基本结构

 4. 创建基本的服务器


//搭建一个express服务器
import express from 'express'
const app=express();

app.listen(20,()=>{
    console.log('server running at http://127.0.0.1');
})

 5. 创建 db 数据库操作模块

//数据库操作模块
import mysql from 'mysql2'

const pool=mysql.createPool({
    host:'127.0.0.1',
    port:3306,
    database:'my_db_01',
    user:'root',
    password:'111111'
})


// 将pool以Promise的形式向外暴露
export default pool.promise()

 6. 创建 user_ctrl 模块

//查询数据库中的数据
import db from '../db/index.js'

// 使用ES6的按需导出语法,将getAllUser方法导出去
async function getAllUser(req,res){
    // db.query()--输入查询语句,并且返回的是一个Promise对象
    // 索引为0是我们要的数据
    const [rows]= await db.query('select id,username,nickname from ev_users')
    // console.log(rows);
    //将数据发送给客户端
    res.send({
        status:0,
        message:'获取用户列表数据成功',
        data:rows
    })
}

getAllUser();

7. 创建 user_router 模块 

//因为要使用路由对象,所以要导入express
import { express } from "express";
//因为要使用到user_ctrl中的函数,所以要导入
import { getAllUser } from '../controller/user_ctr.js'


//创建路由对象
const router=new express.Router();
//为路由挂载一个get
router.get('/user',getAllUser);

//将路由向外暴露
export default router

 8. 导入并挂载路由模块


//搭建一个express服务器
import express from 'express'

//导入路由模块
import userRouter from './router/user_router.js'

const app=express();


//路由挂载
app.use('./api',userRouter);

app.listen(20,()=>{
    console.log('server running at http://127.0.0.1');
})

 9. 使用 try…catch 捕获异常

//查询数据库中的数据
import db from '../db/index.js'

// 使用ES6的按需导出语法,将getAllUser方法导出去
async function getAllUser(req,res){
    // db.query()--输入查询语句,并且返回的是一个Promise对象
    // 索引为0是我们要的数据
   try{
        const [rows]= await db.query('select id,username,nickname from ev_users')
        // console.log(rows);
        //将数据发送给客户端
        res.send({
            status:0,
            message:'获取用户列表数据成功',
            data:rows
        })
        // 使用catch要获取错误
    }catch(err){
        res.send({
            status:1,
            message:'获取用户数据失败',
            desc:err.message
        })
    }
}

getAllUser();

 总结

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值