VUE3从入门到精通

第一章=>

1、前端工程化是什么

2、webpack的作用

3、plugin的基本使用

4、loader的基本使用

5、SourceMap的作用

第二章=>

1、VUE基本使用步骤

2、各种指令的使用

3、过滤器

4、实际案例

第三章=>

1、单页面应用与组件化开发

2、vue三个组成部分

3、注册vue组件

4、如何声明props

5、如何进行样式绑定

6、组件封装案例

第四章=>

1、对props进行验证

2、使用计算属性

3、为组件自定义事件

4、在组件使用v-model

5、任务列表案例

第五章=>

1、watch侦听器使用

2、常用的生命周期函数

3、实现组件间数据共享

4、vue3.x配置axios

5、购物车案例

第六章=>

1、ref引用dom与组件实例

2、$nextTick调用时机

3、keep-alive的作用

4、插槽的基本用法

5、自定义指令

6、Table案例

第七章=>

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

2、vue-router的基本使用

3、vue-router高级用法

4、后台管理案例

第八章=>

1、vue-cli创建vue项目

2、安装配置element-ui

3、element-ui常见组件

4、axios拦截器的使用

5、配proxy接口代理

6、用户列表案例

****************************************************************************************************************************************************************************

复制代码
第一章
1、前端工程化
【1】模块化、组件化、规范化、自动化
【2】优点:让前端开发自有体系、最大程度提高开发效率
【3】解决方案:webpack https://www.webpackjs.com/

****************************************************************************************************************************************************************************

复制代码
2、webpack的基础使用
【0】优点:把开发者中心放在代码开发上,无需关心兼容性等问题。
【1】基本步骤
*************************************************************************
npm init -y 初始化配置package.json
*************************************************************************
新建src->index.html/index.js
*************************************************************************
npm i jquery -S并导入
【2】index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Simple</title>
</head>
<body>
<ul>
    <li>这是第1个li</li>
    <li>这是第2个li</li>
    <li>这是第3个li</li>
    <li>这是第4个li</li>
    <li>这是第5个li</li>
    <li>这是第6个li</li>
    <li>这是第7个li</li>
    <li>这是第8个li</li>
</ul>
</body>
</html>
【3】index.js
import $ from 'jquery'

$(function () {
  $('li:odd').css('backgroundColor', 'red')
  $('li:even').css('backgroundColor', 'blue')
})
【4】发现了兼容性问题
*************************************************************************安装包!!!!!!!!!!!!!
npm i webpack@5.5.1 -D
npm i webpack-cli@4.2.0 -D
*************************************************************************配置webpack.config.js!!!!!!!!!!!!!!!!
/*配置文件*/
module.exports = {
  mode: 'development' // production 上线  development开发
}
*************************************************************************package.json
{
  "name": "day",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "jquery": "^3.6.3",
    "webpack": "^5.5.1",
    "webpack-cli": "^4.2.0"
  }
}
*************************************************************************
set NODE_OPTIONS=--openssl-legacy-provider
npm run dev
生成了dist/main.js
*************************************************************************引入兼容的main.js
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Simple</title>
</head>
<body>
<ul>
    <li>这是第1个li</li>
    <li>这是第2个li</li>
    <li>这是第3个li</li>
    <li>这是第4个li</li>
    <li>这是第5个li</li>
    <li>这是第6个li</li>
    <li>这是第7个li</li>
    <li>这是第8个li</li>
</ul>
</body>
<script src="../dist/main.js"></script>
</html>
【5】mode节点的可选值
development:开发环境
***********************************************************************
不会对打包文件进行压缩和性能优化、打包速度快,适合在开发阶段使用
production:生产环境
***********************************************************************
会压缩、性能优化。打包速度慢,适合在项目发布阶段使用
production明显体积小
/*配置文件*/
module.exports = {
  mode: 'production' // production 上线  development开发
}
【6】webpack.config.js的作用
webpack在打包之前,会先读取这个文件。
***********************************************************************
注意:支持用node.js相关的语法进行配置。比如module.exports就是common的规则使用。
【7】webpack默认约定
打包默认入口文件为src/index.js
***********************************************************************
默认输出文件路径为dist/main.js
***********************************************************************
可以在webpack.config.js中修改打包的默认约定
********************************************************************通过entry入口,output出口
/*配置文件*/
const path = require('path')

module.exports = {
  mode: 'production', // production 上线  development开发
  entry: path.join(__dirname, './src/index.js'), // 入口
  // 出口
  output: {
    path: path.join(__dirname, './dist'),
    filename: "bundle.js"
  }
}

****************************************************************************************************************************************************************************

复制代码
3、插件的作用 set NODE_OPTIONS=--openssl-legacy-provider
【1】webpack-dev-server  html-webpack-plugin
【2】实时打包与构建
npm i webpack-dev-server@3.11.0 -D
npm i webpack-cli -D
*********************************************************************************package.json
  "scripts": {
     "dev": "set NODE_OPTIONS=--openssl-legacy-provider && webpack serve"
  }
*********************************************************************************
http://localhost:8080/
*********************************************************************************
访问内存中的文件才能实时更新。默认放到项目的根目录中,是虚拟的看不见的bundle.js
<script src="/main.js"></script>
注意:这里webpack.config.js使用的是默认配置
【3】html-webpack-plugin使得localhost:8080可以直接访问index.html
localhost:8080/src/index.js复制一份放到根目录
********************************************************************************
npm i html-webpack-plugin@4.5.0 -D
********************************************************************************
/*配置文件webpack.config.js*/
const HtmlPlugin = require('html-webpack-plugin')

const htmlPlugin = new HtmlPlugin({
  template: './src/index.html', // 源文件
  filename: './index.html' // 生成文件
})

module.exports = {
  mode: 'production', // production 上线  development开发
  plugins: [htmlPlugin]  // 通过plugins节点使得htmlPlugin生效
}
********************************************************************************
访问localhost:8080直接看到index.html
********************************************************************************
复制出来的index.html也是放到了内存中
********************************************************************************
而且会自动注入打包好的xxx.js自动注入的index.html,所以index.html就不用额外引入xxx.js了
【4】删除dist目录。如果开启实时打包,可以删除dist目录,不影响运行。
【5】webpack插件-devServer节点
对webpack.config.js进行更多配置
******************************************************************************
/*配置文件*/
const HtmlPlugin = require('html-webpack-plugin')

const htmlPlugin = new HtmlPlugin({
  template: './src/index.html', // 源文件
  filename: './index.html' // 生成文件
})

module.exports = {
  mode: 'production', // production 上线  development开发
  plugins: [htmlPlugin],  // 通过plugins节点使得htmlPlugin生效
  devServer: {
    open: true,
    host: 'localhost',
    port: 80
  }
}
******************************************************************************修改配置需要重启
http://localhost/ 即可访问

****************************************************************************************************************************************************************************

复制代码
4、loader加载器处理打包其他类型文件
【1】目的,打包更多文件或者更高级的语法,打包处理.css
**********************************************************************
import $ from 'jquery'
import './index.css' // 导致报错,因为没有合适loader

$(function () {
  $('li:odd').css('backgroundColor', 'red')
  $('li:even').css('backgroundColor', 'orange')
})
**********************************************************************
npm i style-loader@2.0.0 css-loader@5.0.1 -D
**********************************************************************在webpack.config.js配置
/*配置文件*/
const HtmlPlugin = require('html-webpack-plugin')

const htmlPlugin = new HtmlPlugin({
  template: './src/index.html', // 源文件
  filename: './index.html' // 生成文件
})

module.exports = {
  mode: 'production', // production 上线  development开发
  plugins: [htmlPlugin],  // 通过plugins节点使得htmlPlugin生效
  devServer: {
    open: true,
    host: 'localhost',
    port: 80
  },
  module: { // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    rules: [
      {test: /.css$/, use: ['style-loader', 'css-loader']}
    ]
  }
}
**********************************************************************
这就解决了css打包的问题。
【2】处理less打包
引入less后的处理
****************************************************************************
import $ from 'jquery'
import './index.css' // 导致报错,因为没有合适loader
import './index.less' // 有报错了

$(function () {
  $('li:odd').css('backgroundColor', 'red')
  $('li:even').css('backgroundColor', 'orange')
})
****************************************************************************
npm i less-loader@7.1.0 less@3.12.2 -D
****************************************************************************
/*配置文件*/
const HtmlPlugin = require('html-webpack-plugin')

const htmlPlugin = new HtmlPlugin({
  template: './src/index.html', // 源文件
  filename: './index.html' // 生成文件
})

module.exports = {
  mode: 'production', // production 上线  development开发
  plugins: [htmlPlugin],  // 通过plugins节点使得htmlPlugin生效
  devServer: {
    open: true,
    host: 'localhost',
    port: 80
  },
  module: {
    rules: [
      {test: /.css$/, use: ['style-loader', 'css-loader']},
      {test: /.less$/, use: ['style-loader', 'css-loader', 'less-loader']}, // !!!!!!!!!!
    ]
  }
}
【3】打包处理url路径相关文件
url报错
****************************************************************************
html, body, ul {
  margin: 0;
  padding: 0;

  li {
    line-height: 35px;
    padding-left: 10px;
    font-size: 12px;
  }
}

#box {
  width: 380px;
  height: 114px;
  background-color: dodgerblue;
  background: url("1.jpg"); // 报错了
}
****************************************************************************
npm i url-loader@4.1.1 file-loader@6.2.0 -D
****************************************************************************
/*配置文件*/
const HtmlPlugin = require('html-webpack-plugin')

const htmlPlugin = new HtmlPlugin({
  template: './src/index.html', // 源文件
  filename: './index.html' // 生成文件
})

module.exports = {
  mode: 'production', // production 上线  development开发
  plugins: [htmlPlugin],  // 通过plugins节点使得htmlPlugin生效
  devServer: {
    open: true,
    host: 'localhost',
    port: 80
  },
  module: {
    rules: [
      {test: /.css$/, use: ['style-loader', 'css-loader']},
      {test: /.less$/, use: ['style-loader', 'css-loader', 'less-loader']},
      {test: /.jpg|png|gif$/, use: ['url-loader?limit=22229']}, // !!!!!!!!!!!
    ]
  }
}
【4】添加参数项 url-loader?limit=22229
把小的图片转成base64,所以要加限制。
*********************************************************************************
limit=22229即为小于等于22229字节
【5】loader另一种配置方式
webpack.config.js/*配置文件*/
const HtmlPlugin = require('html-webpack-plugin')

const htmlPlugin = new HtmlPlugin({
  template: './src/index.html', // 源文件
  filename: './index.html' // 生成文件
})

module.exports = {
  mode: 'production', // production 上线  development开发
  plugins: [htmlPlugin],  // 通过plugins节点使得htmlPlugin生效
  devServer: {
    open: true,
    host: 'localhost',
    port: 80
  },
  module: {
    rules: [
      {test: /.css$/, use: ['style-loader', 'css-loader']},
      {test: /.less$/, use: ['style-loader', 'css-loader', 'less-loader']},
      {
        test: /.jpg|png|gif$/, use: { // !!!!!!!!!!!!!!!!
          loader: "url-loader",
          options: {
            limit: 22229
          }
        }
      },
    ]
  }
}
【6】babel-loader处理高级js语法打包
安装配置包
*********************************************************************
npm i babel-loader@8.2.1
npm i @babel/core@7.12.3
npm i @babel/plugin-proposal-class-properties@7.12.1
*********************************************************************配置
/*配置文件*/
const HtmlPlugin = require('html-webpack-plugin')

const htmlPlugin = new HtmlPlugin({
  template: './src/index.html', // 源文件
  filename: './index.html' // 生成文件
})

module.exports = {
  mode: 'production', // production 上线  development开发
  plugins: [htmlPlugin],  // 通过plugins节点使得htmlPlugin生效
  devServer: {
    open: true,
    host: 'localhost',
    port: 80
  },
  module: {
    rules: [
      {test: /.css$/, use: ['style-loader', 'css-loader']},
      {test: /.less$/, use: ['style-loader', 'css-loader', 'less-loader']},
      {
        test: /.jpg|png|gif$/, use: {
          loader: "url-loader",
          options: {
            limit: 22229
          }
        }
      },
      { // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        test: /.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            plugins: ['@babel/plugin-proposal-class-properties']
          }
        }
      }
    ]
  }
}
【7】打包发布,获取到最终打包后的通用文件、对代码进行压缩和性能优化
配置webpack的打包发布
************************************************************************build命令
{
  "name": "day",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "set NODE_OPTIONS=--openssl-legacy-provider && webpack serve",
	// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    "bulid": "set NODE_OPTIONS=--openssl-legacy-provider && webpack --mode production"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@babel/core": "^7.12.3",
    "@babel/plugin-proposal-class-properties": "^7.12.1",
    "babel-loader": "^8.2.1",
    "jquery": "^3.6.3",
    "webpack": "^5.5.1",
    "webpack-cli": "^4.10.0"
  },
  "devDependencies": {
    "css-loader": "^5.0.1",
    "file-loader": "^6.2.0",
    "html-webpack-plugin": "^4.5.0",
    "less": "^3.12.2",
    "less-loader": "^7.1.0",
    "style-loader": "^2.0.0",
    "url-loader": "^4.1.1",
    "webpack-dev-server": "^3.11.0"
  }
}
************************************************************************
npm run build 执行打包命令
************************************************************************
生成dist放到nginx下面即可
【8】整理dist目录。在exports={output节点}进行操作,修改url-loader可以指定图片的存放路径
【9】配置自动清理dist目录
安装插件
npm i clean-webpack-plugin@3.0.0 -D
************************************************************************
/*配置文件*/
const HtmlPlugin = require('html-webpack-plugin')

const htmlPlugin = new HtmlPlugin({
  template: './src/index.html', // 源文件
  filename: './index.html' // 生成文件
})

const {CleanWebpackPlugin} = require('clean-webpack-plugin')
const cleanWebpackPlugin = new CleanWebpackPlugin()

module.exports = {
  mode: 'development', // production 上线  development开发
  plugins: [htmlPlugin, cleanWebpackPlugin],  // 通过plugins节点使得htmlPlugin生效.. options.output.path not defined. Plugin disabled... 需要配置outputPath
  devServer: {
    open: true,
    host: 'localhost',
    port: 80
  },
  module: {
    rules: [
      {test: /.css$/, use: ['style-loader', 'css-loader']},
      {test: /.less$/, use: ['style-loader', 'css-loader', 'less-loader']},
      {
        test: /.jpg|png|gif$/, use: {
          loader: "url-loader",
          options: {
            limit: 22229
          }
        }
      },
      {
        test: /.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            plugins: ['@babel/plugin-proposal-class-properties']
          }
        }
      }
    ]
  }
}

****************************************************************************************************************************************************************************

复制代码
5、SourceMap概述
【1】如何对压缩后的代码进行DEBUG?
【2】SourceMap是一个信息文件,里面存储着位置信息。有了它,可以直接显示原始代码。
********************************************************************************
import $ from 'jquery'
import './index.css' // 导致报错,因为没有合适loader
import './index.less' // 有报错了

$(function () {
  $('li:odd').css('backgroundColor', 'red')
  $('li:even').css('backgroundColor', 'orange')
})

class Person {
  static info = 'person info'
}

consle.log(Person.info) // !!!!!!!!!!!!!!!!!!!!
********************************************************************************
Uncaught ReferenceError: consle is not defined    index.js:19 
而源代码实际是14行,如何解决呢?默认开启的问题
********************************************************************************webpack.config.js
module.exports = {
  mode: 'development', // production 上线  development开发
  devtool: 'eval-source-map', // !!!!!!!!!!!但是仅限开发环境,开发环境强列推荐
【3】生产环境下怎么操作解决呢?
********************************************************************************
生产环境不包含source map,能否防止原始代码暴露给别人。
********************************************************************************正式环境的提示
Uncaught ReferenceError: consle is not defined  main.js:2 
点进去发现毛都看不懂,怎么办?
********************************************************************************只定位行数解决
/*配置文件*/
const HtmlPlugin = require('html-webpack-plugin')

const htmlPlugin = new HtmlPlugin({
  template: './src/index.html', // 源文件
  filename: './index.html' // 生成文件
})

const {CleanWebpackPlugin} = require('clean-webpack-plugin')
const cleanWebpackPlugin = new CleanWebpackPlugin()

module.exports = {
  mode: 'development', // production 上线  development开发
  devtool: 'nosources-source-map', // !!!!!!!!!!!!!!!!!!!!!
  plugins: [htmlPlugin, cleanWebpackPlugin],  // 通过plugins节点使得htmlPlugin生效.. options.output.path not defined. Plugin disabled... 需要配置outputPath
  devServer: {
    open: true,
    host: 'localhost',
    port: 80
  },
  module: {
    rules: [
      {test: /.css$/, use: ['style-loader', 'css-loader']},
      {test: /.less$/, use: ['style-loader', 'css-loader', 'less-loader']},
      {
        test: /.jpg|png|gif$/, use: {
          loader: "url-loader",
          options: {
            limit: 22229
          }
        }
      },
      {
        test: /.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            plugins: ['@babel/plugin-proposal-class-properties']
          }
        }
      }
    ]
  }
}
********************************************************************************生产环境强烈推荐
Uncaught ReferenceError: consle is not defined index.js:14 这个就是安全的,且能定位源文件
【4】总结
********************************************************************************
显示开发中需要程序员配置吗?不需要,可以使用cli一键生成。
********************************************************************************
webpack基本使用、常用插件、常用loader、source map的使用

****************************************************************************************************************************************************************************

复制代码
第二章
1、VUE相关知识
【1】VUE概述
构建用户界面的前端框架、构建出美观、舒适、好用的网页
*************************************************************************************
传统方式:jquery+模板引擎。有不需要拼接的优点,缺点:不高亮、适配差
*************************************************************************************
使用VUE的好处。结构:指令。美化样式:CSS。交互:事件绑定。解放程序员
*************************************************************************************
框架:一整套解决方案,VUE全家桶。包括:vue核心库。vue-router路由方案。vuex状态管理方案。vue组件库快速搭建UI。
*************************************************************************************辅助开发工具
vue-cli。vite。vue-devtools。
*************************************************************************************
指令、数据驱动视图、事件绑定
【2】VUE特性
数据驱动视图
***********************************************************************
vue自动监听数据变化,从而自动重新渲染页面结构。
***********************************************************************
数据->监听数据变化->页面结构叫做"单向数据绑定"。
双向数据绑定:在不操作DOM的前提下,自动把用户填写内容同步到数据源中。
MVVM,是vue实现数据驱动视图与双向数据绑定的核心原理。
***********************************************************************
view(dom页面)         viewmodel(监听、绑定)          model(js对象)
***********************************************************************vue版本
2.x 1-2年会被淘汰
3.x 20200919发布  这个将是未来企业级项目开发的趋势
***********************************************************************比对
3.x大部分都支持2.x。3.x还有新增,废弃了一些旧功能。
【3】vue的基本使用
导入vue.js
******************************************************************
在页面生命vue控制的dom区域
******************************************************************
创建vm示例对象(vue的实例对象)
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Simple体验vue</title>
</head>

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

<script src="../src/vue.js"></script>
<script>
  const vm = new Vue({
    el: '#app',
    data: {
      name: '陈翔'
    }
  })
</script>
</html>
******************************************************************
【4】vue-devtools工具的安装使用(用最传统的方法也可以的...)
*********************************************************************
打开优途国外网络加速器
*********************************************************************
点击谷歌扩展程序,三个杠菜单,输入vue搜索。不带标识的是vue3
*********************************************************************
详情里:配置在所有网站上。允许访问文件地址。
*********************************************************************
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Simple体验vue</title>
</head>
<body>

<div id="app">
    {{name}} {{age}}
</div>

</body>
<script src="../src/vue.js"></script>
<script>
  Vue.config.devtools = true; // 更改默认的保护项

  const vm = new Vue({
    el: '#app',
    data: {
      name: '陈翔',
      age: 20
    }
  })
</script>
</html>

****************************************************************************************************************************************************************************

复制代码
2、指令
【1】内容渲染指令
v-text、{{}}、v-html
*************************************************************************看下面代码即可懂
<div id="app">
    <p v-text="name" style="color: red"></p>
    {{name}} {{age}}
    <p v-text="simple"></p>
    <p v-html="simple"></p>
</div>
*************************************************************************
胡子语法(插值表达式)比v-text更常用
【2】属性绑定指令
v-bind:或: 为元素动态绑定属性值
*************************************************************************看下方代码
<div id="app">
    <input type="text" v-bind:placeholder="info"/>
    <input type="text" :placeholder="info"/>
</div>
【3】使用js表达式
在{{}}表达式中使用简单的js运算
*************************************************************************看代码
<div id="app">
    <p>{{age+12}}</p>

    <p>{{moneyFlag?'有钱':'没钱'}}</p>

    <p>{{name.split('').reverse().join('')}}</p>

    <p :id="'00'+id"></p>

    <p :id="'00'+id">{{dog.name}}</p>
</div>
【4】事件绑定指令
v-on:或@click、@keyup...
*************************************************************************代码
<body>

<div id="app">
    <button @click="show">点击</button>
</div>

</body>
<script src="../src/vue.js"></script>
<script>
  Vue.config.devtools = true;

  const vm = new Vue({
    el: '#app',
    data: {
      name: '陈翔',
    },
    methods: {
      show() {
        alert("您好呀!")
      }
    }
  })
</script>
</html>
【5】事件对象event
@可以接收到事件对象event
*************************************************************************看代码
<body>

<div id="app">
    <button @click="show">点击</button>
</div>

</body>
<script src="../src/vue.js"></script>
<script>
  Vue.config.devtools = true;

  const vm = new Vue({
    el: '#app',
    data: {
      name: '陈翔',
    },
    methods: {
      show(event) {
        const color = event.target.style.backgroundColor
        // console.log(color)
        event.target.style.backgroundColor = color === 'red' ? '' : 'red'
      }
    }
  })
</script>
</html>
【6】绑定事件并传参与$event的使用
<body>

<div id="app">
    <button @click="show(2,$event)">点击</button>
</div>

</body>
<script src="../src/vue.js"></script>
<script>
  Vue.config.devtools = true;

  const vm = new Vue({
    el: '#app',
    data: {
      name: '陈翔',
    },
    methods: {
      show(num, event) {
        alert(num)
        event.target.style.backgroundColor = event.target.style.backgroundColor === 'red' ? '' : 'red'
      }
    }
  })
</script>
</html>
【7】事件修饰符
.prevent阻止默认行为  .stop阻止事件冒泡
*************************************************************************阻止默认行为
<div id="app">
    <a href="https://baidu.com/">百度</a>
    <a href="https://baidu.com/" @click.prevent="">百度</a>
</div>
*************************************************************************阻止冒泡
<div id="app">
    <div class="out" style="width: 200px;height: 200px;background-color: aquamarine" @click="outClick">
        外面
        <div class="in" style="width: 100px;height: 100px;background-color: aqua" @click.stop="inClick">
            里面
        </div>
    </div>
</div>

</body>
<script src="../src/vue.js"></script>
<script>
  Vue.config.devtools = true;

  const vm = new Vue({
    el: '#app',
    data: {
      name: '陈翔',
    },
    methods: {
      inClick() {
        alert('点击里面')
      },
      outClick() {
        alert('点击外面')
      }
    }
  })
</script>
</html>
*************************************************************************
.onec 事件仅触发一次。
【8】按键修饰符,比如敲回车扫码示例
<div id="app">

    <input type="text" @keyup.enter="keyMethod">

</div>

</body>
<script src="../src/vue.js"></script>
<script>
  Vue.config.devtools = true;

  const vm = new Vue({
    el: '#app',
    data: {
      name: '陈翔',
    },
    methods: {
      keyMethod(event) {
        alert('按了Enter' + event.target.value)
      }
    }
  })
</script>
</html>
*************************************************************************
只能配合键盘使用!!!!!!!!!
【9】双向绑定指令
v-model双向数据绑定,没有简写
*************************************************************************
<body>
<div id="app">
    <input type="text" v-model="name"><br>
    {{name}}
</div>
</body>
<script src="../src/vue.js"></script>
<script>
  Vue.config.devtools = true;
  const vm = new Vue({
    el: '#app',
    data: {
      name: '陈翔',
    }
  })
</script>
</html>
【10】v-model指令修饰符
.number数值。 .trim去除首尾空格。 .lazy 非实时更新值
*************************************************************************
<body>
<div id="app">
    <input type="text" v-model.trim="name"><br>
    {{name}}<br>
    <input type="text" v-model.number="age"><br>
    {{age}}<br>
    <input type="text" v-model.lazy="name"><br>
    {{name}}<br>
</div>
</body>
<script src="../src/vue.js"></script>
<script>
  Vue.config.devtools = true;
  const vm = new Vue({
    el: '#app',
    data: {
      name: '陈翔',
      age: 20
    }
  })
</script>
</html>
【11】条件渲染指令
v-if v-show
*************************************************************************
<body>
<div id="app">
    <button @click="flag=!flag">点击切换真假</button>

    <p v-if="flag">{{name}}</p>
    <p v-show="flag">{{name}}</p>
</div>
</body>
<script src="../src/vue.js"></script>
<script>
  Vue.config.devtools = true;
  const vm = new Vue({
    el: '#app',
    data: {
      name: '陈翔',
      flag: false
    }
  })
</script>
</html>
*************************************************************************两者的区别
v-if是动态创建或移出元素。v-show是通过样式来控制隐藏与显示。
v-if有更高的切换开销。而v-show有更高的渲染开销。
使用就取决于是否频繁切换?v-show     不频繁v-if
*************************************************************************v-if v-else 配合使用
<div id="app">
    <button @click="flag=!flag">点击切换真假</button>

    <p v-if="flag">有钱</p>
    <p v-else>没有钱</p>
</div>
*************************************************************************v-else-if
<div id="app">
    <button @click="count=count+1">点击切换真假</button>

    <p v-if="count===2">2</p>
    <p v-else-if="count===3">3</p>
    <p v-else>!=2&&!=3</p>
</div>
【12】列表渲染指令v-for
基于数组渲染相似的UI结构
*************************************************************************看代码
<body>
<div id="app">
    <ul>
        <li v-for="item in userList" :key="item.id">姓名为:{{item.name}}</li>
    </ul>
</div>
</body>
<script src="../src/vue.js"></script>
<script>
  Vue.config.devtools = true;
  const vm = new Vue({
    el: '#app',
    data: {
      userList: [
        {id: 1, name: '陈翔'},
        {id: 2, name: '球球'},
      ]
    }
  })
</script>
</html>
*************************************************************************索引
<li v-for="(item,index) in userList" :key="item.id">索引为:{{index}},姓名为:{{item.name}}</li>
*************************************************************************使用key维护列表状态
保证列表的状态不紊乱。比如选择框的列表。所以需要加:key='item.id'。这样就能保证状态选择还是选择。
而且还能提升渲染效率。
*************************************************************************key使用注意事项
一句话:key使用id就完事了。一定要指定key的值,提升性能、防止紊乱。

****************************************************************************************************************************************************************************

复制代码
3、过滤器
【1】过滤器常用于文本的格式化,例如hello -->HELLO
****************************************************************************
一般放在js表达式的尾部,由管道符调用
****************************************************************************
过滤器可以用在插值表达式、和v-bind属性绑定
****************************************************************************注意是filters下定义方法
<body>
<div id="app">

    <p>{{name | myFilter}}</p>

</div>
</body>
<script src="../src/vue.js"></script>
<script>
  Vue.config.devtools = true;
  const vm = new Vue({
    el: '#app',
    data: {name: '陈翔'},
    filters: {
      myFilter(str) {
        return str + '过滤'
      }
    }
  })
</script>
</html>
【2】私有过滤器和全局过滤器
私有化的过滤器,只能在#app中使用。
****************************************************************************全局过滤器
<body>

<div id="app">
    <p>{{name | myFilter}}</p>
</div>

</body>
<script src="../src/vue.js"></script>
<script>
  Vue.config.devtools = true;
  Vue.filter('myFilter', (str) => { // !!!!!!!!!!!!!!!!!!!
    return str + '过滤'
  })
  const vm = new Vue({
    el: '#app',
    data: {name: '陈翔'},
    filters: {}
  })
</script>
</html>
【3】调用多个过滤器
{{name | myFilter | myFilter2}}
****************************************************************************
 <body>

<div id="app">
    <p>{{name | myFilter | myFilter2}}</p>
</div>

</body>
<script src="../src/vue.js"></script>
<script>
  Vue.config.devtools = true;
  Vue.filter('myFilter', (str) => {
    return str + '过滤'
  })
  Vue.filter('myFilter2', (str) => {
    return str + '二次过滤'
  })
  const vm = new Vue({
    el: '#app',
    data: {name: '陈翔'},
    filters: {}
  })
</script>
</html>
【4】过滤器传参
<body>

<div id="app">
    <p>{{name | myFilter('1参数','2参数')}}</p>
</div>

</body>
<script src="../src/vue.js"></script>
<script>
  Vue.config.devtools = true;
  Vue.filter('myFilter', (str, firstArg, secondArg) => {
    return str + firstArg + secondArg + '过滤'
  })
  const vm = new Vue({
    el: '#app',
    data: {name: '陈翔'},
    filters: {}
  })
</script>
</html>
【5】过滤器的兼容性
vue3.x已经剔除了过滤器。官方建议使用计算属性或方法的形式替代过滤器

****************************************************************************************************************************************************************************

复制代码
4、实际案例
【1】知识点:
bootStrap4.x、vue指令
*************************************************************************实现步骤
创建vue实例、vue渲染表格数据、增删改查
【2】循环渲染表格
<body>

<div id="app">
    <!--卡片区域-->
    <div class="card">
        品牌名称:<input type="text">
        <button>添加品牌</button>
    </div>
    <!--品牌列表-->
    <table class="table table-bordered table-striped mt-2">
        <tr>
            <td>#</td>
            <td>名称</td>
            <td>状态</td>
            <td>时间</td>
            <td>操作</td>
        </tr>
        <tr v-for="item in brandList" :key="item.id">  <!--!!!!!!!!!!!!!!!!-->
            <td>{{item.id}}</td>
            <td>{{item.name}}</td>
            <td>{{item.state}}</td>
            <td>{{item.addTime}}</td>
            <td><a href="#">删除</a></td>
        </tr>
    </table>
</div>

</body>
<script src="../src/vue.js"></script>
<script>
  Vue.config.devtools = true;

  const vm = new Vue({
    el: '#app',
    data: {
      brandList: [
        {id: 1, name: '宝马', state: true, addTime: new Date()},
        {id: 2, name: '奥迪', state: false, addTime: new Date()},
        {id: 3, name: '奔驰', state: true, addTime: new Date()},
      ]
    }
  })
</script>
</html>
【3】把状态渲染成开关效果,其实就用到了bootstrip4.x的UI渲染操作,和Element UI一样。
【4】使用全局过滤格式化时间
<td>{{item.addTime | dateFormat}}</td>
*************************************************************************
 Vue.filter('dateFormat', (str) => {
    const dt = new Date(str)
    const y = dt.getFullYear()
    const m = appendZero(dt.getMonth())
    const d = appendZero(dt.getDay())
    const hh = appendZero(dt.getHours())
    const mm = appendZero(dt.getMinutes())
    const ss = appendZero(dt.getSeconds())
    return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
  })

  function appendZero(n) {
    return n > 9 ? n : '0' + n
  }
 【5】添加品牌的功能
 品牌名称:<input type="text" v-model.trim="name">
 *************************************************************************
  data: {
      name: '',
 *************************************************************************
 methods: {
      // 添加新品牌
      addBrand() {
        if (!this.name) {
          return alert('品牌名不能为空')
        }
        this.brandList.push(
          {id: this.nextId, name: this.name, state: true, addTime: new Date()}
        )
        this.name = ''
        this.nextId++
      }
    }
【6】快速清空文本框
<input type="text" v-model.trim="name" @keyup.esc="clearName">
 *************************************************************************
 clearName() {
        this.name = ''
      }
 【7】删除品牌
 <td><a href="#" @click.prevent="removeBrand(item.id)">删除</a></td>
  *************************************************************************
  removeBrand(id) {
        this.brandList = this.brandList.filter(item => item.id !== id)
      }
【8】总结:知道vue基本使用步骤、常见指令的使用、vue过滤器的用法

****************************************************************************************************************************************************************************

复制代码
第三章
1、单页面应用程序(332开始)
【1】single page application,就是web中仅有一个唯一的html页面。好比小程序。
****************************************************************************特点
所有功能含在一个页面中,仅在页面初始化时加载相应资源。不会因为用户的操作进行页面的重新
加载或跳转。
****************************************************************************
【2】spa项目优点:
良好的交互体验、良好的前后端工作分离、减轻服务端渲染压力。
****************************************************************************
后端只专注提供API接口。是容易实现API接口的复用。
****************************************************************************
后端只负责提供数据,不负责页面渲染。
****************************************************************************缺点
首屏加载慢、不利于SEO
【3】如何快速创建vue的spa项目
基于vite创建vue3.x、基于vue-cli创建vue3.x vue2.x。建议使用vue-cli
****************************************************************************
【4】学习阶段体验下vite
npm init vite-app codeSimple
****************************************************************************
cd codeSimple
****************************************************************************
npm i
****************************************************************************
npm run dev
【5】梳理项目的基本目录
public 公共静态资源目录
****************************************************************************
src 是项目源代码目录
****************************************************************************
index.html是SPA单页面应用程序的唯一的HTML页面
****************************************************************************
assets 存放静态资源。components存放自定义组件的。
****************************************************************************
App.vue是项目的根组件。index.css是项目的全局样式表。main.js是打包入口文件
【6】vite项目的运行流程
vue要做的事情就是通过:main.js把App.vue渲染到index.html指定区域中。
****************************************************************************
//main.js
// 1、按需导入createApp函数
import {createApp} from 'vue'
// 2、导入渲染的App.vue组件
import App from "./App.vue";
import './index.css'

// 3、调用createApp函数,创建spa实例。并用mount挂载app
createApp(App).mount('#app')
【7】组件化开发
封装的思想,把重复使用的部分封装为组件,比如http://www.ibootstrap.cn/
****************************************************************************
有点:提高了代码的复用性和灵活性,提升开发效率和可维护性。
****************************************************************************
vue组件化开发。.vue结尾的都是组件。

****************************************************************************************************************************************************************************

复制代码
2、vue的三个组成部分
【1】template    script   style
template模板结构是必须的。 script   style是可选的
【2】template节点的使用
包裹性质的节点,不会被渲染到index.html中的dom元素
******************************************************************************
template支持所有指令语法
******************************************************************************
vue2.x仅支持单个根节点。vue3.x支持定义多个根节点
【3】script节点是可选的
<script>
export default{
}
</script>
******************************************************************************
name 当前组件名称。打开调试工具时用得到
******************************************************************************data节点
data() {
      return {
        name: '陈翔'
      }
    }
注意:组件里,声明data数据,必须指向一个函数
******************************************************************************methods节点
 methods: {
      show() {
        alert('展示自己')
      }
    }
******************************************************************************插件到对应位置添加
C:UsersAdministratorAppDataLocalGoogleChromeUser DataDefaultExtensions
【4】style节点
h1 {
     color: red;
}
******************************************************************************
可以使得对应目录变红。接下来配置less
******************************************************************************
npm i less -D
******************************************************************************
<style>
    h1 {
        color: red;

        span {
            color: deepskyblue;
        }
    }
</style>

****************************************************************************************************************************************************************************

复制代码
3、注册vue组件
【1】原则:先注册,后使用
组件注册的两种方式:全局注册、局部注册
【2】全局注册!!!!!!!!!!!!!!!!!!!!!!!!!!
// 1、按需导入createApp函数
import {createApp} from 'vue'
// 2、导入渲染的App.vue组件
import App from "./App.vue";
import './index.css'
import Goods from "./components/Goods.vue"; // 导入全局注册组件

// 3、调用createApp函数,创建spa实例。并用mount挂载app
const vue = createApp(App);
vue.component('Goods', Goods) // 全局注册组件

vue.mount('#app')
*******************************************************************************
 <Goods></Goods> 注意不要重复用局部引入了。就可以正常使用了
 【3】局部注册
 import Area from "./components/Area.vue"; // 注意以xx.vue结尾
 *******************************************************************************
  components: {
      HelloWorld,
      Area
    },
  *******************************************************************************
<Area></Area> 局部注册完成,可以使用了。
【4】全局与局部注册组件的区别
全局的可以随便使用如Goods。而局部只能在谁注册,在谁使用,如Area
【5】组件注册时名称的大小写
我的建议是大驼峰命名法。就是组件叫什么名字,注册的时候也叫什么名字,这叫统一性。
*******************************************************************************
官方也建议使用大驼峰命名法。
【6】通过name来作为组件的注册名称,Goods.name这就更爽了,卧槽!!!!!!!!
vue.component(Goods.name, Goods) // 全局注册组件
【7】解决样式冲突问题。作用域
不加scoped,父组件样式<h1>标红等会影响子组件。
*******************************************************************************
加scoped,子组件就不受影响了。但是全局注册的组件还受到影响。
【8】deep样式穿透
<h1>局部注册组件Area</h1>
*******************************************************************************
<style scoped>
    h1 {
        color: red;

        span {
            color: deepskyblue;
        }
    }

    .title {
        color: blue; // 父组件想影响子组件样式
    }
</style>
*******************************************************************************
貌似被弃用了,不支持了。

****************************************************************************************************************************************************************************

复制代码
4、如何声明props
【1】props概念
组件dom结构、style样式要尽量复用。"组件中要展示的数据,尽量由组件提供"
*********************************************************************************
为了方便使用者为组件提供要展示的"数据"。vue就提供了props的概念。
*********************************************************************************
父组件通过props向子组件   传递要展示的数据。
*********************************************************************************
props的好处,提升组件的复用性。
*********************************************************************************
 <Area info="爸爸给的数据"></Area>
 *********************************************************************************
   export default {
    name: "Area",
    props: ['info']
  }
 *********************************************************************************
 <template>
    <h3 class="title">局部注册组件Area</h3>
    {{info}}
</template>
【2】无法使用未声明的props
<template>
    <h3 class="title">局部注册组件Area</h3>
    {{info}}{{age}}  不能使用age!!!!!!!!!!!!!!!!!!
</template>

<script>


  export default {
    name: "Area",
    props: ['info'] // 因为这里没有声明!!!!!!!!!!!!!!!!!!
  }
</script>

<style scoped>

</style>
【3】动态绑定props的值
其实就是从父组件写死,变换到父组件写活而已。
*********************************************************************************
 data() {
      return {
        name: '陈翔',
        fatherInfo: '爸爸给的信息'
      }
    },
*********************************************************************************	
<Area :info="fatherInfo"></Area>  // 多加了v-bind或:
【4】props的大小写命名规则
小驼峰命名法:areaInfo
*********************************************************************************
 appInfo: '爸爸给的信息'
 *********************************************************************************
 <Area :areaInfo="appInfo"></Area>
 *********************************************************************************
 export default {
    name: "Area",
    props: ['areaInfo']
  }
*********************************************************************************
  {{areaInfo}}

****************************************************************************************************************************************************************************

复制代码
5、如何进行样式绑定
【1】动态绑定HTML的class
 flag: false
 ******************************************************************************
<h3 class="thin" :class="flag ? 'italic' : ''">MyStyle 组件</h3>
<button @click="flag=!flag">改变样式</button>
 ******************************************************************************
.thin {
        font-weight: 200;
    }
.italic {
	font-style: italic;
}
【2】以数组的语法绑定多个class
<h3 class="thin" :class="[flagOne ? 'italic' : '',flagTwo ? 'delete' : '']">MyStyle 组件</h3>
<button @click="flagOne=!flagOne">改变样式1</button>
<button @click="flagTwo=!flagTwo">改变样式2</button>
******************************************************************************
flagOne: false,
flagTwo: false
******************************************************************************
.thin {
font-weight: 200;
}

.italic {
font-style: italic;
}

.delete {
text-decoration: line-through;
}
【3】以对象语法绑定HTML的class。解决以上绑定臃肿的问题,非常好!!!!!!!!!!
<h3 class="thin" :class="h3Class">MyStyle 组件</h3>
<button @click="h3Class.italic=!h3Class.italic">改变样式1</button>
<button @click="h3Class.delete=!h3Class.delete">改变样式2</button>
******************************************************************************
h3Class: { // 对象中,属性名是class的类名,值是布尔值
  italic: false,
  delete: false
}
【4】以对象语法绑定内联的style
<div :style="{color:active,fontSize:fontSize+'px',backgroundColor:bgColor}">我的风格独自秀</div>
******************************************************************************
active: 'red',
fontSize: 30,
bgColor: 'pink'
******************************************************************************
<button @click="fontSize=fontSize+1">字号+1</button>
<button @click="fontSize=fontSize-1">字号-1</button>

****************************************************************************************************************************************************************************

复制代码
6、组件封装案例
【1】封装要求
可以自定义title标题
***********************************************************************
可以自定义bgColor颜色
***********************************************************************
自定义color文本颜色
***********************************************************************
支持fixed固定位置,且z-index等于999
【2】实际封装
***********************************************************************App.vue不是一成不变,看引入哪个!
//import App from "./App.vue";
import App from "./components/App.vue";
***********************************************************************
<template>
    <div>
        <h1>App根组件</h1>
        <hr>
        <Header title="HIT" bg-color="black" color="white"></Header>
    </div>
</template>

<script>
  import Header from "./Header.vue";

  export default {
    name: "App",
    components: {
      Header
    }
  }
</script>

<style scoped>
    .app-container {
        margin-top: 45px;
    }
</style>
***********************************************************************重点在下面!!!!!!!!
<!--被封装的组件-->
<template>
    <div :style="{backgroundColor:bgColor,color:color}">
        {{title||'Header组件'}} <!--传就展示传的,不传就展示Header组件-->
    </div>
</template>

<script>
  export default {
    name: "Header",
    props: ['title', 'bgColor', 'color']
  }
</script>

<style scoped>
    .header-container {
        height: 45px;
        background-color: pink;
        text-align: center;
        line-height: 45px;
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        z-index: 999;
    }
</style>
【3】总结:单页面程序。.vue组成。注册vue组件。生命props属性。进行样式绑定。

****************************************************************************************************************************************************************************

复制代码
第四章
1、对props进行验证
【1】在封装组件时对外界传来的数据进行验证,防止数据不合法。
如果加上类型约束,不合法的传递会在控制台提示
*******************************************************************************
vue.js:1598 [Vue warn]: Invalid prop: type check failed for prop "count". Expected Number with value NaN, got String with value "ABC". 
vue.js:1598 [Vue warn]: Invalid prop: type check failed for prop "state". Expected Boolean, got Number with value 3. 
*******************************************************************************
正确传递就不会报错了
<Count :count="12" :state="true"></Count>
【2】多个可能的类型
基础检查、多个可能类型、必填项校验、默认值、自定义验证函数
*******************************************************************************
基础类型检查:
props: {
  count: Number,
  state: Boolean
}
*******************************************************************************
多个可能的类型:通过数组形式
props: {
  count: [String,Number]
}
【3】必填项校验
 export default {
    name: "Count",
    props: {
      count: Number,
      state: Boolean,
      info: {
        type: String,
        required: true
      }
    }
  }
*******************************************************************************
info就是必传项。如果不传info就会在控制台报错。
【4】属性默认值
  export default {
    name: "Count",
    props: {
      count: Number,
      state: Boolean,
      info: {
        type: String,
        default: '默认信息'
      }
    }
  }
*******************************************************************************
不传info属性,就回展示默认信息
<div class="count-container">
	<p>数量---{{count}}</p>
	<p>状态---{{state}}</p>
	<p>信息---{{info}}</p>
</div>
【5】自定义验证函数
export default {
    name: "Count",
    props: {
      count: Number,
      state: Boolean,
      info: {
        validator(value) {
          return ['success', 'danger'].indexOf(value) !== -1
        },
        required: true
      }
    }
  }
*******************************************************************************要求更严格!!!
<Count :count="12" :state="true" :info="'success'"></Count>

****************************************************************************************************************************************************************************

复制代码
2、使用计算属性
【1】计算属性的概念
本质上一个function函数,实时监听data中数据的变化,并return一个新值&渲染dom
***************************************************************************
以function函数生命在computed选项中
***************************************************************************
<template>
    <div class="count-container">
        <input type="text" v-model.number="count"/>
        <p>{{count}}*2={{multiply}}</p>
    </div>
</template>

<script>
  export default {
    name: "Count",
    data() {
      return {
        count: 1
      }
    },
    computed: {
      multiply() {
        return this.count * 2
      }
    }
  }
</script>

<style scoped>

</style>
***************************************************************************
计算属性必须要有return的值。必须有双向绑定。必须是一个funciton函数。必须定义在computed节点。计算属性要当做普通属性使用。
【2】计算属性和方法的区别
只有当计算属性依赖的项(变量)发生变化时,才会重新运算求值。因此性能比方法好。
***************************************************************************
计算属性会被缓存,性能好
【3】计算属性的案例:水果店 npm i
【4】动态计算水果总数量。
computed: {
  // 勾选水果的总数量
  totalNum() {
	let total = 0;
	this.fruitList.forEach(x => {
	  if (x.state) {
		total = total + x.count
	  }
	})
	return total
  }
}
【5】我简直是一个小天才了。书读百遍其义自见!!!!!!!!!
<template>
    <div class="count-container">

        <ul>
            <li v-for="item in fruitList" :key="item.id">
                {{item.name}}
                ---商品状态<input type="text" v-model="item.state">
                ---商品数量<input type="text" v-model.number="item.count">
            </li>
        </ul>

        <hr>
        <span>总数量:{{totalNum}}</span>
        <hr>
        <span>总价格:{{totalPrice}}元</span>
        <hr>
        <button :disabled="ableFlag">结算</button>
    </div>
</template>

<script>
  export default {
    name: "Count",
    data() {
      return {
        fruitList: [
          {id: 1, name: '香蕉', image: '', price: 5, count: 1, state: true},
          {id: 2, name: '橘子', image: '', price: 6, count: 1, state: false},
          {id: 3, name: '苹果', image: '', price: 7, count: 1, state: false},
        ],
        ableFlag: false
      }
    },
    computed: {
      // 勾选水果的总数量
      totalNum() {
        let totalNum = 0
        this.fruitList.forEach(x => {
          if (x.state) {
            totalNum = totalNum + x.count
          }
        })
        return totalNum
      },
      /*已勾商品的总价格*/
      totalPrice() {
        let totalPrice = 0
        this.fruitList.forEach(x => {
          if (x.state) {
            totalPrice = totalPrice + x.price * x.count
          }
        })
        return totalPrice
      },
      /*结算按钮是否可以点击*/
      ableFlag() {
        return this.totalNum === 0
      }
    }
  }
</script>

<style scoped>

</style>

****************************************************************************************************************************************************************************

复制代码
3、为组件自定义事件
【1】自定义时间概念
让组件使用者可以监听到组件内状态的变化,就需要用到自定义事件。
************************************************************************
就是父组件监听子组件内状态的变化。
【2】自定义事件的三个使用步骤。
儿子声明—>儿子触发—>父亲使用
************************************************************************
自定义事件,必须在emits节点中声明。
************************************************************************
this.$emit('change')
************************************************************************
使用时通过v-on指令绑定事件。
************************************************************************实际使用
/*自定义组件*/
    emits: ['changeCount'],
    methods: {
      add() {
        this.count = this.count + 1
        /*内部调用触发自定义时间*/
        this.$emit('changeCount')
      }
    }
************************************************************************
<p>count的值{{count}}</p>
<button @click="add">点击+1</button>
************************************************************************
<Count @changeCount="watchCount"></Count> // !!!!!!!!!!!!!
************************************************************************
watchCount() {
     console.log('触发了watchCount事件...')
}
************************************************************************
本质是儿子组件内部的变化,父亲监控做响应的举动。
【3】自定义事件传参
methods: {
  add() {
	this.count = this.count + 1
	/*内部调用触发自定义时间*/
	this.$emit('changeCount', this.count)/*第二个参数传参*/
  }
}
************************************************************************
methods: {
  watchCount(val) { // 通过val接收。就可以获得子传过来的属性值
	console.log('触发了watchCount事件...' + val)
  }
}

****************************************************************************************************************************************************************************

复制代码
4、在组件使用v-model
【1】为什么需要再组件上使用v-model
当需要维护组件内外数据的同步时,可以在组件上使用v-model指令
*****************************************************************************
就是父子组件的数据同步
【2】组件间使用v-model的步骤。父向子同步。
父亲:number='count'     儿子props:['number']
*****************************************************************************父亲
data() {
  return {
	count: 0
  }
},
<Count :appCount="count"></Count>  // 用的其实是v-bind: 简写是:
*****************************************************************************儿子
 props: ['appCount']
<p>count的值{{appCount}}</p>
【3】子向父同步数据。子向父同步。
父亲需要升级v-model
 <Count v-model:appCount="count"></Count>
*****************************************************************************儿子
 emits: ['update:appCount'], // 格式是固定的update:要更新属性的名字
*****************************************************************************
<p>count的值{{appCount}}</p>
<button @click="add">儿子点击+1</button>
add() {
	this.$emit('update:appCount', this.appCount + 1)
  }

****************************************************************************************************************************************************************************

复制代码
5、任务列表案例
【1】用到的知识点
vite创建项目、组件封装与注册、props、样式绑定、计算属性、自定义事件、组件间v-model
【2】初始化项目
npm i
*****************************************************************************
npm i less -D
【3】梳理项目结构
index.css
:root {
    font-size: 12px;
}

body {
    padding: 8px;
}
*****************************************************************************App.vue
<template>
    <div>
        <h1>App 根组件</h1>
        <hr>

    </div>
</template>

<script>
  export default {
    name: "App",
    data() {
      return {
        todoList: [
          {id: 1, task: '周一辣子鸡', doneFlag: false},
          {id: 2, task: '周二红烧肉', doneFlag: false},
          {id: 3, task: '周三口水鸡', doneFlag: true},
        ]
      }
    }
  }
</script>

<style scoped>

</style>
*****************************************************************************main.js
// 1、按需导入createApp函数
import {createApp} from 'vue'
// 2、导入样式表
import "./index.css"
// 3、导入渲染的App.vue组件
import App from "./App.vue";

// 4、调用createApp函数,创建spa实例。并用mount挂载app
const vue = createApp(App)
vue.mount('#app')
【4】封装todoList组件
<template>
    <div>
        <h3>TodoList组件</h3>
    </div>
</template>

<script>
  export default {
    name: "TodoList",

  }
</script>

<style scoped>

</style>
*****************************************************************************
<template>
    <div>
        <h1>App 根组件</h1>
        <hr>
        <TodoList></TodoList>
    </div>
</template>

<script>
  import TodoList from "./components/TodoList.vue";

  export default {
    name: "App",
    components: {
      TodoList
    },
    data() {
      return {
        todoList: [
          {id: 1, task: '周一辣子鸡', doneFlag: false},
          {id: 2, task: '周二红烧肉', doneFlag: false},
          {id: 3, task: '周三口水鸡', doneFlag: true},
        ]
      }
    }
  }
</script>

<style scoped>

</style>
【5】基于bootStrap渲染基本结构
https://v4.bootcss.com/docs/getting-started/download/
导入下载后的css文件夹到assets
*****************************************************************************main.js
import "./assets/css/bootstrap.css"
*****************************************************************************
<template>
    <div>

        <ul>
            <li class="list-group-item d-flex justify-content-between align-items-center">
                <!--复选框-->
                <div class="custom-control custom-checkbox">
                    <input type="checkbox" value="" id="defaultCheck1">
                    <label for="defaultCheck1">
                        Default checkbox
                    </label>
                </div>
                <!--徽标-->
                <span class="badge badge-success badge-pill">完成</span>
                <span class="badge badge-warning badge-pill">未完成</span>
            </li>
        </ul>

    </div>
</template>

<script>
  export default {
    name: "TodoList",

  }
</script>

<style scoped>

</style>
【6】为todoList组件生命props属性
props: {
  xxx_list: {
type: Array,
required: true,
default: []
  }
}
*****************************************************************************
<TodoList :xxx_list="todoList"></TodoList>
【7】通过循环,渲染出任务列表信息
<template>
    <div>

        <ul>
            <li class="list-group-item d-flex justify-content-between align-items-center" v-for="item in xxx_list"
                :key="item.id">
                <!--复选框-->
                <div class="custom-control custom-checkbox">
                    <input type="checkbox" value="" :id="item.id">
                    <label :for="item.id">
                        {{item.task}}
                    </label>
                </div>
                <!--徽标-->
                <span class="badge badge-success badge-pill" v-if="item.doneFlag">完成</span>
                <span class="badge badge-warning badge-pill" v-else>未完成</span>
            </li>
        </ul>

    </div>
</template>

<script>
  export default {
    name: "TodoList",
    props: {
      xxx_list: {
        type: Array,
        required: true,
        default: []
      }
    }
  }
</script>

<style scoped>

</style>
【8】复选框和当前任务的绑定关系
<input type="checkbox" value="" :id="item.id" v-model="item.doneFlag">
主要是勾选状态和item.doneFlag绑定了。
*****************************************************************************
v-model="item.doneFlag" 这个是重点
【9】美化已经完成的任务
.delete {
text-decoration: line-through;
color: gray;
font-style: italic;
}
*****************************************************************************根据是否完成添加样式
<label :for="item.id" :class="item.doneFlag?'delete':''">
{{item.task}}
</label>
【10】封装todo-input组件。并通过App.vue引入
<template>
    <div>
        <h3>TodoInput组件</h3>
    </div>
</template>

<script>
  export default {
    name: "TodoInput"
  }
</script>

<style scoped>

</style>
【11】渲染todoInput.vue
<template>
    <div>
        <form>

            <div class="input-group mb-2 mr-sm-2">
                <div>
                    <div>任务</div>
                </div>
                <input type="text" id="inlineFormInputGroup" placeholder="请输入任务信息"
                       style="width: 356px;">
            </div>

            <button type="submit" class="btn btn-primary mb-2">添加新任务</button>
        </form>
    </div>
</template>

<script>
  export default {
    name: "TodoInput"
  }
</script>

<style scoped>

</style>
【12】input组件向外传递数据
data() {
  return {
taskName: ''
  }
}
*****************************************************************************v-model
<input type="text" id="inlineFormInputGroup" placeholder="请输入任务信息"
style="width: 356px;" v-model.trim="taskName">
*****************************************************************************
<form @submit.prevent="onFormSubmit">
*****************************************************************************
emits: ['todoInput_father_add'],
*****************************************************************************命名里有艺术
onFormSubmit() {
if (!this.taskName) {
  return alert('任务名称不能为空')
} else {
   this.$emit('todoInput_father_add', this.taskName)
   this.taskName = ''
}
}
*****************************************************************************命名里有艺术
<TodoInput @todoInput_father_add="watchAdd"></TodoInput>
【12】实现添加新任务功能
data() {
  return {
todoList: [
  {id: 1, task: '周一辣子鸡', doneFlag: false},
  {id: 2, task: '周二红烧肉', doneFlag: false},
  {id: 3, task: '周三口水鸡', doneFlag: true},
],
nextId: 4 // 下一个可用的id是4
  }
},
methods: {
  watchAdd(taskName) {
// console.log(taskName)
this.todoList.push({
  id: this.nextId,
  task: taskName,
  doneFlag: false
})
this.nextId++
  }
}
【13】todo-button组件,并在App.vue中使用
<template>
    <div>
        <h3>TodoButton组件</h3>

    </div>
</template>

<script>
  export default {
    name: "TodoButton"
  }
</script>

<style scoped>

</style>
【14】渲染TodoButton
<template>
    <div class="todoButton-container mt-3">

        <div role="group" aria-label="Basic example">
            <button type="button" class="btn btn-primary">全部</button>
            <button type="button" class="btn btn-secondary">已完成</button>
            <button type="button" class="btn btn-secondary">未完成</button>
        </div>

    </div>
</template>

<script>
  export default {
    name: "TodoButton"
  }
</script>

<style scoped>
    .todoButton-container {
        width: 400px;
        text-align: center;
    }
</style>
【15】通过props来激活对应按钮。xxx_active命名里有艺术
<template>
    <div class="todoButton-container mt-3">

        <div role="group" aria-label="Basic example">
            <button type="button" :class="xxx_active===0?'btn btn-primary':'btn-secondary'">全部</button>
            <button type="button" :class="xxx_active===1?'btn btn-primary':'btn-secondary'">已完成</button>
            <button type="button" :class="xxx_active===2?'btn btn-primary':'btn-secondary'">未完成</button>
        </div>

    </div>
</template>

<script>
  export default {
    name: "TodoButton",
    props: {
      xxx_active: {
        required: true,
        type: Number,
        default: 0
      }
    }
  }
</script>

<style scoped>
    .todoButton-container {
        width: 400px;
        text-align: center;
    }
</style>
【16】v-model更新激活项
父向子:props        子向父:v-model
*****************************************************************************
v-bind和v-on的缩写@  需要特别注意。
*****************************************************************************两种方式都介绍下,先v-model
emits: ['update:xxx_active'],
methods: {
  changeButton(index) {
if (index === this.xxx_active) {
  return
} else {
  this.$emit('update:xxx_active', index)
}
  }
}
*****************************************************************************优点:简介
<TodoButton v-model:xxx_active="activeIndex"></TodoButton>
*****************************************************************************方式二,就是$emit传递。@..._father_active接收
emits: ['todoButton_father_active'],
methods: {
  changeButton(index) {
if (index === this.xxx_active) {
  return
} else {
  this.$emit('todoButton_father_active', index)
}
  }
}
*****************************************************************************优点:结构清晰
<TodoButton :xxx_active="activeIndex" @todoButton_father_active="watchActive"></TodoButton>
【17】使用计算属性实现动态展示
/*计算属性*/
computed: {
  taskList() {
switch (this.activeIndex) {
  case 0:// 全部
return this.todoList
  case 1: // 已完成
return this.todoList.filter(x => x.doneFlag)
  case 2: // 未完成
return this.todoList.filter(x => !x.doneFlag)
}
  }
}
*****************************************************************************绑定
<TodoList :xxx_list="taskList"></TodoList>
【18】总结
props验证、计算属性、为组件绑定自定义事件@(v-on)emits:['']  $emit('',val)、v-model或子传父

****************************************************************************************************************************************************************************

复制代码
第五章
1、watch侦听器使用
【1】侦听器的概念:如用watch监听,当input变化时监听用户名是否可用。
<template>
    <h2>watch 组件</h2>
    <input type="text" class="form-control" v-model.trim="name">
</template>

<script>
  export default {
    name: "Watch",
    data() {
      return {
        name: ''
      }
    },
    watch: {
      name(newVal, oldVal) {  // watch里的方法名与属性一致
        console.log(newVal + '---VS---' + oldVal)
      }
    }
  }
</script>

<style scoped>

</style>
【2】检测用户名是否可用
npm i axios -S   必须要用-S,踩坑了卧槽
下载axios的位置必须在"dependencies"中而不能是 "devDependencies"
*********************************************************************************async...await
<template>
    <h2>watch 组件</h2>
    <input type="text" class="form-control" v-model.trim="name">
</template>

<script>
  import axios from 'axios'

  export default {
    name: "Watch",
    data() {
      return {
        name: ''
      }
    },
    watch: {
      async name(newVal, oldVal) {
        console.log(newVal + '---VS---' + oldVal)
        const {data: res} = await axios.get("https://www.escook.cn/api/findUser/" + newVal)  // 结构data 并重命名res
        console.log(res)
      }
    }
  }
</script>

<style scoped>

</style>
*********************************************************************************
还用到了结构+重命名   {data:res}=await ....
【3】immediate选项,渲染后就默认侦听一次。就是用它来实现
<template>
    <h2>watch 组件</h2>
    <input type="text" class="form-control" v-model.trim="name">
</template>

<script>
  import axios from 'axios'

  export default {
    name: "Watch",
    data() {
      return {
        name: 'z'
      }
    },
    watch: {
      name: {
        async handler(newVal, oldVal) {
          const {data: res} = await axios.get("https://www.escook.cn/api/findUser/" + newVal)  // 结构data 并重命名res
          console.log(res)
        },
        immediate: true // 立刻触发
      }
    }
  }
</script>

<style scoped>

</style>
【4】deep选项。监听对象,是无法监听到对象属性值变化的,如果想监听需要用deep
<template>
    <h2>watch 组件</h2>
    <input type="text" class="form-control" v-model.trim="dog.name">
</template>

<script>
  import axios from 'axios'

  export default {
    name: "Watch",
    data() {
      return {
        name: 'z',
        dog: {
          name: '小黑'
        }
      }
    },
    watch: {
      dog: {
        async handler(newVal) {
          const {data: res} = await axios.get("https://www.escook.cn/api/findUser/" + newVal.name)  // 结构data 并重命名res
          console.log(res)
        },
        deep: true // !!!!!!!!!!
      }
    }
  }
</script>

<style scoped>

</style>
*********************************************************************************
高级的用法都得用handler呀 卧槽
【5】监听对象中单个属性。用deep不合适,因为其他属性变化也会导致他变。
dog: {
	  name: '小黑',
	  age: 2
	}
  }
},
watch: {
  dog: {
	async handler(newVal) {
	  const {data: res} = await axios.get("https://www.escook.cn/api/findUser/" + newVal.name)  // 结构data 并重命名res
	  console.log(res)
	},
	deep: true
  }
*********************************************************************************
watch: {
  'dog.name': {  // 这里是重点哦,需要加两个单引号包裹
	async handler(newVal) {
	  const {data: res} = await axios.get("https://www.escook.cn/api/findUser/" + newVal.name)  // 结构data 并重命名res
	  console.log(res)
	}
  }
}
【6】计算属性和监听器的区别
计算属性侧重监听多个值的变化,最终计算并返回一个新值。
*********************************************************************************
监听器侧重监听单个数据,指定特定业务逻辑,不需要返回值。

****************************************************************************************************************************************************************************

复制代码
2、常用的生命周期函数
【1】组件生命周期
import导入组件---注册组件---以标签形式使用组件---
内存中创建实例---渲染到页面上---销毁
*********************************************************************
组件生命周期:创建---运行---销毁的整个过程。强调的是时间段。
【2】通过生命周期函数。created  mounted unmounted 伴随着生命周期自动调用
<template>
    <h2>Life组件</h2>
</template>

<script>
  export default {
    name: "Life",
    created() {
      console.log('组件被创建')
    },
    mounted() {
      console.log('组件第一次被渲染到页面上')
    },
    unmounted() {
      console.log('组件被销毁')
    }
  }
</script>

<style scoped>

</style>
*********************************************************************
<template>
    <div>
        <h1>App 根组件</h1>
        <hr>
        <Life v-if="showFlag"></Life>
        <button @click="showFlag=!showFlag">变化状态</button> // 能展示销毁!!!!!
    </div>
</template>

<script>
  import Life from "./components/Life.vue";

  export default {
    name: "App",
    components: {
      Life
    },
    data() {
      return {
        showFlag: true
      }
    },
  }
</script>

<style scoped>

</style>
【3】通过updated监听组件的更新
当组件被重新渲染完毕,会调用updated
*********************************************************************
<template>
    <h2>Life组件{{count}}</h2>
    <button @click="count=count+1">count+1</button>
</template>

<script>
  export default {
    name: "Life",
    created() {
      console.log('组件被创建')
    },
    mounted() {
      console.log('组件第一次被渲染到页面上') // !!!!!!!只要count变化了
    },
    unmounted() {
      console.log('组件被销毁')
    },
    updated() {
      console.log('触发了重新渲染完毕')
    },
    data() {
      return {
        count: 0
      }
    }
  }
</script>

<style scoped>

</style>
【4】主要生命周期函数总结
created mounted 唯一一次。
updated 0或多次。
unmounted 销毁 也是唯一一次。
*********************************************************************
created最常用,用于初始化列表。
mounted是最早操作dom,echarts的操作!!!!!!
*********************************************************************
【5】全部生命周期函数
beforeCreate、beforeMount、beforeUpdate、beforeUnmount

****************************************************************************************************************************************************************************

复制代码
3、实现组件间数据共享
【1】组件之间的关系
父子关系、兄弟关系、后代关系
【2】父子组件之间的数据共享
父向子、子向父、双向传递
*****************************************************************************父向子
v-bind: : 父传子
<template>
    <div>
        <h1>App 根组件</h1>
        <hr>
        <Son :xxx_msg="msg" :xxx_userInfo="userInfo"></Son>  // !!!!!!!!
    </div>
</template>

<script>
  import Son from "./components/Son.vue";

  export default {
    name: "App",
    components: {
      Son
    },
    data() {
      return {
        msg: 'Hello',
        userInfo: {
          name: "陈翔",
          age: 20
        }
      }
    },
  }
</script>

<style scoped>

</style>
*****************************************************************************
<template>
    <h2>Son组件</h2>
    {{xxx_msg}} <br>
    {{xxx_userInfo}}
</template>

<script>
  export default {
    name: "Son",
    props: ['xxx_msg', 'xxx_userInfo'] // !!!!!!!!
  }
</script>

<style scoped>

</style>
【2】子传父,用到的是自定义时间son_fff_numChange。
<template>
    <h2>Son组件</h2>
    {{xxx_msg}} <br>
    {{xxx_userInfo}}
    <button @click="add">加1</button>
</template>

<script>
  export default {
    name: "Son",
    props: ['xxx_msg', 'xxx_userInfo'],
    emits: ['son_fff_numChange'],
    methods: {
      add() {
        this.$emit('son_fff_numChange', this.xxx_userInfo.age + 1)
      }
    }
  }
</script>

<style scoped>

</style>
*****************************************************************************@son_fff_numChange="son_fff_numChange"
<template>
    <div>
        <h1>App 根组件</h1>
        年龄: {{userInfo.age}}
        <hr>
        <Son :xxx_msg="msg" :xxx_userInfo="userInfo" @son_fff_numChange="son_fff_numChange"></Son>
    </div>
</template>

<script>
  import Son from "./components/Son.vue";

  export default {
    name: "App",
    components: {
      Son
    },
    data() {
      return {
        msg: 'Hello',
        userInfo: {
          name: "陈翔",
          age: 20
        }
      }
    },
    methods: {
      son_fff_numChange(val) {
        this.userInfo.age = val
      }
    }
  }
</script>

<style scoped>

</style>
【3】父子组件间数据共享v-model(子传父 父传子综合应用也可以实现!!!!)
讲道理不太好用,还是子传父 父传子综合应用也可以实现!!!!好,牛批。
*****************************************************************************
总结下规则:父传子 fff_子组件名称_属性名
                   子传父:子组件名称_fff_方法名
【4】兄弟组件间的数据共享,EventBus
npm i mitt@2.1.0 -S
*****************************************************************************
setting-->Editor--> File and Code Templates可以编辑默认VUE模板,爽!!!!!!
*****************************************************************************创建eventBus.js与App同级
import mitt from 'mitt'

const bus = mitt()

export default bus
*****************************************************************************
<template>
    <h2>Left</h2>
    数据发送方的count的值:{{count}}
    <br>
    <button @click="add">count+1</button>
</template>

<script>
  import bus from '../eventBus.js'  // !!!!!!!!!!!!!

  export default {
    name: "Left",
    data() {
      return {
        count: 0
      }
    },
    methods: {
      add() {
        this.count = this.count + 1
        bus.emit('left_countChange', this.count) // !!!!!!!!!!!!!
      }
    }
  }
</script>

<style scoped>

</style>
*****************************************************************************
<template>
    <h2>Right</h2>
    接收方num的值:{{num}}
</template>

<script>
  import bus from '../eventBus'  // !!!!!!!!!!!!!

  export default {
    name: "Right",
    data() {
      return {
        num: 0
      }
    },
    created() {
      /*接收方*/
      bus.on('left_countChange', (count) => {  // !!!!!!!!!!!!!
        this.num = count
      })
    }
  }
</script>

<style scoped>

</style>
【5】后代关系组件之间数据共享provide inject。必须要有直接或间接的嵌套关系。
父节点生命provide方法,对其子孙组件共享数据。
*****************************************************************************
子孙节点中用inject接收
*****************************************************************************
<template>
    <div>
        <h1>App 根组件</h1>
        <hr>
        <Son></Son>
    </div>
</template>

<script>
  import Son from "./components/Son.vue";

  export default {
    name: "App",
    components: {
      Son
    },
    data() {
      return {
        color: 'red'
      }
    },
    provide() {
      /*返回要共享数据对象*/
      return {
        color: this.color
      }
    }
  }
</script>

<style scoped>

</style>
*****************************************************************************
<template>
    <h2>Son</h2>
    <Posterity></Posterity>
</template>

<script>
  import Posterity from "./Posterity.vue";

  export default {
    name: "Son",
    components: {
      Posterity
    }
  }
</script>

<style scoped>

</style>
*****************************************************************************
<template>
    <h3>Posterity</h3>
    {{color}}
</template>

<script>
  export default {
    name: "Posterity",
    inject: ['color']
  }
</script>

<style scoped>

</style>
【6】基于provide响应式的数据。下面就能实现App颜色变化,Posterity.vue的值也同步变化了。
<template>
    <div>
        <h1 :class="color==='blue'?'h1':''">App 根组件</h1>
        <button @click="color='blue'">改变颜色</button>
        <hr>
        <Son></Son>
    </div>
</template>

<script>
  import Son from "./components/Son.vue";
  import {computed} from 'vue'

  export default {
    name: "App",
    components: {
      Son
    },
    data() {
      return {
        color: 'red'
      }
    },
    provide() {
      /*返回要共享数据对象*/
      return {
        color: computed(() => this.color)  // !!!!!!!!!!!!!
      }
    }
  }
</script>

<style scoped>
    .h1 {
        color: blue;
    }
</style>
*****************************************************************************
<template>
    <h3>Posterity</h3>
    {{color.value}}  // !!!!!!!!!!!!!
</template>

<script>
  export default {
    name: "Posterity",
    inject: ['color']
  }
</script>

<style scoped>

</style>
【7】vuex概念及使用
vuex是终极组件之间数据共享的方案,vuex可以使得组件间数据共享变的高效、清晰、易维护。
*****************************************************************************
vuex提供STORE来存储,哪个组件需要用STORE直接获取。场景:大范围、频繁的数据共享
【8】组件间数据共享总结
父-子 属性绑定
子-父 事件绑定
父子双向。属性绑定+事件绑定或v-model
兄弟关系 EventBus
后代关系  provide  inject
全局数据共享 vuex  大范围的

****************************************************************************************************************************************************************************

复制代码
4、vue3.x配置axios
【1】为什么配置?因为axios使用频繁,需要不断导入,导致代码臃肿,复用性低。
而且每次都要填写完整请求路径,不利于维护。
******************************************************************************
在main.js通过app.config.globalProperties全局挂载axios
******************************************************************************
先安装axios 
"dependencies": { // !!!!!!!!这里注意
    "axios": "^1.3.4",
******************************************************************************
/*main.js*/
// 1、按需导入createApp函数
import {createApp} from 'vue'
// 2、导入样式表
import "./assets/css/bootstrap.css"
import "./index.css"
// 3、导入渲染的App.vue组件
import App from "./App.vue";
import axios from "axios";

// 4、调用createApp函数,创建spa实例。并用mount挂载app
const vue = createApp(App)
/*5、配置axios*/
axios.defaults.baseURL = 'https://www.escook.cn'
vue.config.globalProperties.$http = axios // 挂载

vue.mount('#app')
******************************************************************************
<template>
    <h2>Get</h2>
    <button @click="get">发起GET请求</button>
</template>

<script>
  export default {
    name: "Get",
    methods: {
      async get() {
        const {data: res} = await this.$http.get('/api/get', {
          params: {
            name: 'ls',
            age: 20
          }
        })
        console.log(res)
      }
    }
  }
</script>

<style scoped>

</style>
******************************************************************************
<template>
    <h2>Post</h2>
    <button @click="post">发起POST请求</button>
</template>

<script>
  export default {
    name: "Post",
    methods: {
      async post() {
        const {data: res} = await this.$http.post('/api/post', {
          name: 'zs',
          age: 20
        })
        console.log(res)
      }
    }
  }
</script>

<style scoped>

</style>

****************************************************************************************************************************************************************************

复制代码
5、购物车案例
【1】初始化项目结构
Header、Footer、Goods、Counter组件
************************************************************************
npm init vite-app code-cart
************************************************************************
npm i
************************************************************************
使用bootStrap
************************************************************************
npm i less -D
************************************************************************
/*index.css*/
:root {
    font-size: 12px;
}

body {
    padding: 8px;
}
【2】Header组件封装。修改了下组件模板
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>#[[$Title$]]#</title>
</head>
<body>
#[[$END$]]#
</body>
</html>
************************************************************************
<template>
    <div>
        <h2>Header</h2>

    </div>
</template>

<script>
  export default {
    name: "Header"
  }
</script>

<style scoped>

</style>
************************************************************************具体封装,看:style 多学习吧!!!!!
<template>
    <div
         :style="{backgroundColor:fff_header_bgColor,color:fff_header_color,fontSize:fff_header_fontSize+'px'}">
        {{fff_header_title}}
    </div>
</template>

<script>
  export default {
    name: "Header",
    props: {
      fff_header_title: {
        type: String,
        default: 'header'
      },
      fff_header_bgColor: {
        type: String,
        default: '#007BFF'
      },
      fff_header_color: {
        type: String,
        default: '#ffffff'
      },
      fff_header_fontSize: {
        type: Number,
        default: 12
      },
    }
  }
</script>

<style scoped>
    .Header-container {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 45px;
        text-align: center;
        line-height: 45px;
        z-index: 999;
    }
</style>
************************************************************************
<template>
    <div>
        <h1>App 根组件</h1>
        <hr>
        <Header fff_header_title="购物车案例"></Header>
    </div>
</template>

<script>
  import Header from "./components/Header.vue";

  export default {
    name: "App",
    components: {
      Header
    },
    data() {
      return {}
    },
  }
</script>

<style scoped>
    .App-container {
        padding-top: 45px;
    }
</style>
【3】基于axios请求商品类表数据
npm i axios -S  代表保存到生产环境save
************************************************************************
main.js挂载
************************************************************************
<template>
    <div>
        <h1>App 根组件</h1>
        <hr>
        <Header fff_header_title="购物车案例"></Header>
    </div>
</template>

<script>
  import Header from "./components/Header.vue";

  export default {
    name: "App",
    components: {
      Header
    },
    data() {
      return {
        goodsList: []
      }
    },
    created() {
      this.getGoodsList()  // !!!!!!!!!!!!发起请求
    },
    methods: {
      /*获取商品列表数据*/
      async getGoodsList() {
        const {data: res} = await this.$http.get('/api/cart')
        // console.log(res)
        if (res.status !== 200) {
          alert('请求失败')
        } else {
          this.goodsList = res.list // 赋值
        }
      }
    }
  }
</script>

<style scoped>
    .App-container {
        padding-top: 45px;
    }
</style>
【4】Footer组件封装
<template>
    <div>
        <h2>Footer</h2>

    </div>
</template>

<script>
  export default {
    name: "Footer"
  }
</script>

<style scoped>

</style>
************************************************************************
App引入使用
************************************************************************
<template>
    <div>
        <!--全选区域-->
        <div class="custom-control custom-checkbox">
            <input type="checkbox" id="fullCheck">
            <label for="fullCheck">全选</label>
        </div>
        <!--合计区域-->
        <div>
            <span>合计:</span>
            <span>¥0.00</span>
        </div>
        <!--结算按钮-->
        <button type="button" class="btn btn-primary btn-settle">结算(0)</button>
    </div>
</template>

<script>
  export default {
    name: "Footer"
  }
</script>

<style scoped>
    .Footer-container {
        height: 50px;
        width: 100%;
        background-color: white;
        border-top: 1px solid #efefef;
        position: fixed;
        bottom: 0;
        left: 0;
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 0 10px;
    }

    .amount {
        font-weight: bold;
        color: red;
    }

    .btn-settle {
        min-width: 90px;
        height: 38px;
        border-radius: 19px;
    }
</style>
************************************************************************自定义属性props
<template>
    <div>
        <!--全选区域-->
        <div class="custom-control custom-checkbox">
            <input type="checkbox" id="fullCheck">
            <label for="fullCheck">全选</label>
        </div>
        <!--合计区域-->
        <div>
            <span>合计:</span>
            <span>¥{{fff_footer_amount.toFixed(2)}}</span>
        </div>
        <!--结算按钮-->
        <button type="button" class="btn btn-primary btn-settle" :disabled="fff_footer_total===0">
            结算({{fff_footer_total}})
        </button>
    </div>
</template>

<script>
  export default {
    name: "Footer",
    props: {
      // 已勾选商品的总价格
      fff_footer_amount: {
        type: Number,
        default: 10
      },
      // 已勾选商品的总数量
      fff_footer_total: {
        type: Number,
        default: 1
      }
    }
  }
</script>

<style scoped>
    .Footer-container {
        height: 50px;
        width: 100%;
        background-color: white;
        border-top: 1px solid #efefef;
        position: fixed;
        bottom: 0;
        left: 0;
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 0 10px;
    }

    .amount {
        font-weight: bold;
        color: red;
    }

    .btn-settle {
        min-width: 90px;
        height: 38px;
        border-radius: 19px;
    }
</style>
************************************************************************fff_footer_fullFlag与自定义事件
<template>
    <div>
        <!--全选区域-->
        <div class="custom-control custom-checkbox">
            <input type="checkbox" id="fullCheck" :checked="fff_footer_fullFlag"
                   @change="onCheckBoxChange">
            <label for="fullCheck">全选</label>
        </div>
        <!--合计区域-->
        <div>
            <span>合计:</span>
            <span>¥{{fff_footer_amount.toFixed(2)}}</span>
        </div>
        <!--结算按钮-->
        <button type="button" class="btn btn-primary btn-settle" :disabled="fff_footer_total===0">
            结算({{fff_footer_total}})
        </button>
    </div>
</template>

<script>
  export default {
    name: "Footer",
    props: {
      // 已勾选商品的总价格
      fff_footer_amount: {
        type: Number,
        default: 10
      },
      // 已勾选商品的总数量
      fff_footer_total: {
        type: Number,
        default: 1
      },
      fff_footer_fullFlag: {
        type: Boolean,
        default: false
      }
    },
    methods: {
      onCheckBoxChange(event) {
        // console.log(event.target.checked)
        this.$emit('footer_fff_fullFlagChange', event.target.checked)
      }
    },
    emits: ['footer_fff_fullFlagChange']
  }
</script>

<style scoped>
    .Footer-container {
        height: 50px;
        width: 100%;
        background-color: white;
        border-top: 1px solid #efefef;
        position: fixed;
        bottom: 0;
        left: 0;
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 0 10px;
    }

    .amount {
        font-weight: bold;
        color: red;
    }

    .btn-settle {
        min-width: 90px;
        height: 38px;
        border-radius: 19px;
    }
</style>
************************************************************************
@footer_fff_fullFlagChange="footer_fff_fullFlagChange"
************************************************************************
/*监听选中状态变化*/
footer_fff_fullFlagChange(footerVal) {
     console.log(footerVal)
}
【5】封装商品信息组件Goods.vue
创建在App中引入使用。
************************************************************************基础布局
<template>
    <div>
        <!--左侧图片-->
        <div>
            <div class="custom-control custom-checkbox">
                <input type="checkbox" id="fullCheck" :checked="fff_footer_fullFlag"
                       @change="onCheckBoxChange">
                <label for="fullCheck">
                    <img src="" alt="商品图片">
                </label>
            </div>
        </div>
        <!--右侧信息区域-->
        <div>
            <div>xxx</div>
            <div>
                <div>¥0.00</div>
                <div>数量</div>
            </div>
        </div>
    </div>
</template>

<script>
  export default {
    name: "Goods"
  }
</script>

<style scoped>
    .Goods-container {
        + .Goods-container {
            border-top: 1px solid #efefef;
        }

        display: flex;
        padding: 10px;
        //左侧图片
        .left {
            margin-right: 10px;

            .thumb {
                display: block;
                width: 100px;
                height: 100px;
                background-color: #efefef;
            }
        }

        // 右侧商品
        .right {
            display: flex;
            flex-direction: column;
            justify-content: space-between;
            flex: 1;

            .top {
                font-weight: bold;
            }

            .bottom {
                display: flex;
                justify-content: space-between;
                align-items: center;

                .price {
                    color: red;
                    font-weight: bold;
                }
            }
        }
    }

    .custom-control-label::before, .custom-control-label::after {
        top: 3.4rem
    }
</style>
************************************************************************属性props
props: {
  fff_goods_id: {
type: [String, Number],
required: true
  },
  fff_goods_img: {
type: String,
required: true
  },
  fff_goods_thumb: { // 缩略图
type: String,
required: true
  },
  fff_goods_title: {
type: String,
required: true
  },
  fff_goods_price: {
type: Number,
required: true
  },
  fff_goods_count: {
type: Number,
required: true
  },
  fff_goods_checked: {
type: Boolean,
required: true
  }
}
************************************************************************自定义事件
methods: {
  onCheckedChange(event) {
this.$emit('goods_fff_onCheckedChange', event.target.checked)
  }
},
emits: ['goods_fff_onCheckedChange']
************************************************************************
<Goods v-for="item in goodsList" :key="item.id"
               :fff_goods_id="item.id"
               :fff_goods_thumb="item.goods_img"
               :fff_goods_title="item.goods_name"
               :fff_goods_price="item.goods_price"
               :fff_goods_count="item.goods_count"
               :fff_goods_checked="item.goods_state"
               @goods_fff_onCheckedChange="goods_fff_onCheckedChange">
************************************************************************
goods_fff_onCheckedChange(goodsVal) { // 这个要用   子组件Val。有点意思了!!!!!
   console.log(goodsVal)
}
************************************************************************
onCheckedChange(event) {
this.$emit('goods_fff_onCheckedChange', {
  id: this.fff_goods_id,
  value: event.target.checked
})
  }
 注意:this.$emit参数还能传递对象
 ************************************************************************
goods_fff_onCheckedChange(goodsVal) {
// console.log(goodsVal)
const findRes = this.goodsList.find(item => item.id === goodsVal.id)
if (findRes) {
  findRes.goods_state = goodsVal.value // 改变真的状态
}
  }
【6】实现合计、结算、全选功能
<template>
    <div>
        <h1>App 根组件</h1>
        <hr>
        <Header fff_header_title="购物车案例"></Header>
        <Goods v-for="item in goodsList" :key="item.id"
               :fff_goods_id="item.id"
               :fff_goods_thumb="item.goods_img"
               :fff_goods_title="item.goods_name"
               :fff_goods_price="item.goods_price"
               :fff_goods_count="item.goods_count"
               :fff_goods_checked="item.goods_state"
               @goods_fff_onCheckedChange="goods_fff_onCheckedChange">
        </Goods>
        <Footer @footer_fff_fullFlagChange="footer_fff_fullFlagChange" :fff_footer_amount="amount"
                :fff_footer_total="total"></Footer>
    </div>
</template>

<script>
  import Header from "./components/Header.vue";
  import Footer from "./components/Footer.vue";
  import Goods from "./components/Goods.vue";

  export default {
    name: "App",
    components: {
      Header,
      Footer,
      Goods
    },
    data() {
      return {
        goodsList: []
      }
    },
    created() {
      this.getGoodsList()
    },
    methods: {
      /*获取商品列表数据*/
      async getGoodsList() {
        const {data: res} = await this.$http.get('/api/cart')
        // console.log(res)
        if (res.status !== 200) {
          alert('请求失败')
        } else {
          this.goodsList = res.list // 赋值
        }
      },
      /*监听选中状态变化*/
      footer_fff_fullFlagChange(footerVal) {
        // console.log(footerVal)
        this.goodsList.forEach(x => {
          x.goods_state = footerVal
        })
      },
      goods_fff_onCheckedChange(goodsVal) {
        // console.log(goodsVal)
        const findRes = this.goodsList.find(item => item.id === goodsVal.id)
        if (findRes) {
          findRes.goods_state = goodsVal.value
        }
      }
    },
    /*计算属性*/
    computed: {
      // 已勾选商品的总价格
      amount() {
        let a = 0;
        this.goodsList.filter(x => x.goods_state).forEach(x => {
          a = a + x.goods_price * x.goods_count
        })
        // console.log(a)
        return a
      },
      // 勾选商品总数量
      total() {
        let t = 0;
        this.goodsList.filter(x => x.goods_state).forEach(x => {
          t = t + x.goods_count
        })
        return t
      }
    }
  }
</script>

<style scoped>
    .App-container {
        padding-top: 45px;
    }
</style>
【7】封装数字选择器组件Counter........................................
<template>
    <div>
        <h2>Counter</h2>

    </div>
</template>

<script>
  export default {
    name: "Counter"
  }
</script>

<style scoped>

</style>
************************************************************************Goods组件里使用
  import Counter from "./Counter.vue";

  export default {
    name: "Goods",
    components: {
      Counter
    },
************************************************************************封装需求
实现数值的渲染。props传的值是只读的,如果想修改,通过data把num转存到number
<template>
    <div>
        <!--数量-1按钮-->
        <button type="button" class="btn btn-light btn-sm" @click="onSubClick">-</button>
        <!--输入框-->
        <input type="number" class="form-control form-control-sm ipt-num" v-model.number="number"/>
        <!--数量+1按钮-->
        <button type="button" class="btn btn-light btn-sm" @click="onAddClick">+</button>
    </div>
</template>

<script>
  export default {
    name: "Counter",
    props: {
      goods_counter_num: {
        type: Number,
        default: 0
      }
    },
    data() {
      return {
        number: this.goods_counter_num // 数据转存到data中
      }
    },
    methods: {
      onSubClick() {
        this.number--
      },
      onAddClick() {
        this.number++
      }
    }
  }
</script>

<style scoped>
    .Counter-container {
        display: flex;

        .btn {
            width: 25px;
        }

        .ipt-num {
            width: 34px;
            text-align: center;
            margin: 0 4px;
        }
    }
</style>
************************************************************************实现min最小值
  export default {
    name: "Counter",
    props: {
      goods_counter_num: {
        type: Number,
        default: 0
      },
      goods_counter_min: {
        type: Number,
        default: NaN
      }
    },
    data() {
      return {
        number: this.goods_counter_num // 数据转存到data中
      }
    },
    methods: {
      onSubClick() {
        if (!isNaN(this.goods_counter_min) && this.number - 1 < this.goods_counter_min) {
          return
        } else {
          this.number--
        }
      },
      onAddClick() {
        this.number++
      }
    }
  }
  
<Counter :goods_counter_num="fff_goods_count" :goods_counter_min="1"></Counter>
************************************************************************处理输入框的输入结果
watch: {
  number(newVal) {
/*输入值转为整数*/
const parseRes = parseInt(newVal)
if (isNaN(parseRes) || parseRes < 1) {
  this.number = 1
  return
}
/*如果输入为小数*/
if (String(newVal).indexOf('.') !== -1) {
  this.number = parseRes
  return
}
console.log(this.number)
  }
}
************************************************************************把最新的数据传递给使用者
自定义事件:从counter传递到goods
emits: ['counter_goods_numChange']

this.$emit('counter_goods_numChange', this.number)

@counter_goods_numChange="counter_goods_numChange"

counter_goods_numChange(counterVal) {
   console.log(counterVal)
}
************************************************************************goods再传递给app组件
counter_goods_numChange(counterVal) {
        console.log(counterVal)
        this.$emit('goods_fff_countChange', {
          id: this.fff_goods_id,
          value: counterVal
        })
      }
    },
    emits: ['goods_fff_onCheckedChange', 'goods_fff_countChange']


@goods_fff_countChange="goods_fff_countChange"

goods_fff_countChange(goodsVal) {
        // console.log(goodsVal)
        const findRes = this.goodsList.find(x => x.id === goodsVal.id)
        if (findRes) {
          findRes.goods_count = goodsVal.value
        }
      }
  
同时解决了,v-model那个地方不更新的问题。
【8】样式美化与总结。底部问题解决。
.App-container {
padding-top: 45px;
padding-bottom: 50px;
}
************************************************************************
watch侦听器的使用、生命周期函数、组件间数据共享、vue3中全局配置axios

****************************************************************************************************************************************************************************

复制代码
第六章
1、ref引用dom与组件实例
【1】概念:ref是辅助开发者在不依赖于jQuery的情况下,获取dom元素或组件的引用。
每一个vue组件上都包含一个$ref对象。
**********************************************************************************
<template>
    <div>
        <h2 ref="h2">Ref</h2>  // !!!!!!!!!!!!!!!
        <button @click="getRef">获取$ref引用</button>
    </div>
</template>

<script>
  export default {
    name: "Ref",
    methods: {
      getRef() {
        //  console.log(this) // this代表当前示例对象 this.$ref默认指向空对象
        this.$refs.h2.style.color = 'red'  // !!!!!!!!!!!!!!!
      }
    }
  }
</script>

<style scoped>

</style>
【2】使用ref获取组件的引用
<Ref ref="Ref"></Ref>
<button @click="getComRef">获取组件的引用</button>
**********************************************************************************
methods: {
  getComRef() {
// console.log(this.$refs.Ref)
/*通过获取到Ref组件,调用它的getRef()方法*/
this.$refs.Ref.getRef()
this.$refs.Ref.name = '被App根组件改变后的Ref名称'
  }
}
**********************************************************************************
有点强大,那还用数据共享、方法传递属性吗?????
【3】ref应用场景
<template>
    <div>
        <h2 ref="h2">Ref</h2>
        <input type="text" v-if="visFlag" ref="inputText">
        <button type="button" class="btn btn-primary" v-else @click="showInput">展示input输入框</button>
    </div>
</template>

<script>
  export default {
    name: "Ref",
    data() {
      return {
        visFlag: false
      }
    },
    methods: {
      showInput() {
        this.visFlag = true
        /*dom更新是异步的,想拿到dom拿不到.....导致了问题*/ // !!!!!!!!!!!!!!!
        console.log(this.$refs.inputText)
        this.$refs.inputText.focus()
      }
    }
  }
</script>

<style scoped>

</style>

****************************************************************************************************************************************************************************

复制代码
2、$nextTick调用时机
【1】基于ref引用dom异步渲染导致的问题,需要用这个来解决
this.$nextTick(cb)方法来解决
**************************************************************************
showInput() {
	this.visFlag = true
	/*dom更新是异步的,想拿到dom拿不到.....导致了问题*/
	this.$nextTick(() => { // 延迟函数的执行 !!!!!!!!!!!!
	  this.$refs.inputText.focus()
	})
  }
**************************************************************************
通过this.$nextTick(cb)可以保证dom更新完毕后,再操作

****************************************************************************************************************************************************************************

复制代码
3、keep-alive的作用
【1】动态组件<component>
是组件的占位符。
通过is属性来动态指定要渲染组件的名称。
<component is="组件名称"></component>
******************************************************************************
<template>
    <div>
        <h1>App 根组件</h1>
        <hr>
// !!!!!!!!!!!!!!!!!!!!!!!!!
        <component :is="changeName"></component>
        <button @click="changeName='Move'">展示Move组件</button>
        <button @click="changeName='Run'">展示Run组件</button>
    </div>
</template>

<script>
  import Run from "./components/Run.vue";
  import Move from "./components/Move.vue";

  export default {
    name: "App",
    components: {
      Run,
      Move
    },
    data() {
      return {
        changeName: 'Run'  // !!!!!!!!!!!!!!!!!!!!!!!!!
      }
    }
  }
</script>

<style scoped>

</style>
******************************************************************************
这玩意和路由导航有点像呀,沃日
【2】使用keep-alive保持组件的状态
<h2>Move中count的值 {{count}}</h2>
<button type="button" class="btn btn-primary" @click="count=count+1">自增+1</button>
******************************************************************************切换组件时,组件被销毁了
导致count的原有值变为了0,怎么处理?keep-alive
******************************************************************************包裹一层接口。
<keep-alive>
<component :is="changeName"></component>
</keep-alive>

****************************************************************************************************************************************************************************

复制代码
4、插槽的基本用法
【1】slot,为组件封装着提供的占位符。使用者可以传入使用。
这不是和props差不多吗,沃日哦。
【2】具体的使用
<p>第一个P标签</p>
 <slot></slot>
<p>最后一个P标签</p>
***************************************************************************
<Simple>
		<p>中间P标签</p>
</Simple>
***************************************************************************
如果没有预留插槽,用户在组件标签之间提供的内容将会被丢弃。
<p>第一个P标签</p>
<!--  <slot></slot>-->
<p>最后一个P标签</p>
【3】为预留的插槽提供后备内容(默认内容)
<p>第一个P标签</p>
<slot>这是默认内容</slot>
<p>最后一个P标签</p>
***************************************************************************
<Simple>
	<!--<p>中间P标签</p>-->
</Simple>
没指定,就回展示默认内容。
【4】具名插槽,如果需要预留多个插槽slot,为slot指定名称,就叫做具名插槽....
需要吗?沃日,还多个?
***************************************************************************
<p>第一个P标签</p>
<slot name="mid">这是中间默认内容</slot>
<p>最后一个P标签</p>
<slot name="last">这是最后最后默认内容</slot>
***********************************************************************
<Simple>
	<p slot="mid"></p>
	<p slot="last"></p>
</Simple>
***********************************************************************外层template无法被省略
<Simple>
	<template v-slot:mid>
		<p>1</p>
	</template>
	<template v-slot:last>
		<p>2</p>
	</template>
</Simple>
【5】具名插槽的简写形式
<Simple>
	<template #mid>
		<p>1</p>
	</template>
	<template #last>
		<p>2</p>
	</template>
</Simple>
***********************************************************************
v-slot: 简写为#
【6】作用域插槽
为预留的插槽<slot>插槽绑定props数据。这种带有props数据的<slot>叫做作用域插槽。
使用了props还需要用插槽吗?????????
***********************************************************************
<slot :info="info" :msg="msg">这是中间默认内容</slot>
***********************************************************************
<template #default="scope">
	<p>1----{{scope}}</p>
</template>
***********************************************************************
展示了info+msg组合的大对象。这是不是element ui的一种用法
***********************************************************************
还可以展示里面的属性。
<template #default="scope">
	<p>1----{{scope}}---{{scope.msg}}</p>
</template>
***********************************************************************
【7】解构作用域插槽的Prop
<template #default="{info,msg}">
	<p>1----{{info}}---{{msg}}</p>
</template>
***********************************************************************
另一种直接的用法而已
【8】作用域插槽概述。还是挺有使用价值的!!!!!!!!!!!!!!!!!!!!
<tr v-for="item in list" :key="item.id">
	<!-- <th>{{item.id}}</th>
	 <th>{{item.name}}</th>
	 <th>{{item.state}}</th>-->
	<slot :user="item"></slot>
</tr>
***********************************************************************
<Table>
	<template #default="{user}">
		<td>{{user.id}}</td>
		<td>{{user.name}}</td>
		<td>
			<input type="checkbox" :checked="user.state">
		</td>
	</template>
</Table>

****************************************************************************************************************************************************************************

复制代码
5、自定义指令
【1】概述:开发者自定义的指令。分为私有自定义指令与全局自定义指令。
【2】创建私有自定义指令
感觉必要性不大
*******************************************************************************
<template>
    <div>
        <h2>Simple</h2>
        <br>
        <input type="text" v-focus/>
    </div>
</template>

<script>
  export default {
    name: "Simple",
    directives: {
      focus: {}
    }
  }
</script>

<style scoped>

</style>
【3】实现文本框自动获取焦点
<template>
    <div>
        <h2>Simple</h2>
        <br>
        <input type="text" v-focus/>
    </div>
</template>

<script>
  export default {
    name: "Simple",
    directives: {
      focus: {
        mounted(el) {
          el.focus()
        }
      }
    }
  }
</script>

<style scoped>

</style>
【4】创建全局自定义指令
/*main.js定义全局自定义指令*/
vue.directive('focus', {
  mounted(el) {
    el.focus()
  }
})
****************************************************************************App.vue可以用
<input type="text" v-focus/>
****************************************************************************Simple.vue也可以用
<!-- <input type="text" v-focus/>-->
【5】updated函数使用。每次点击+1,仍然input会自动获取焦点
/*main.js*/
// 1、按需导入createApp函数
import {createApp} from 'vue'
// 2、导入样式表
import "./assets/css/bootstrap.css"
import "./index.css"
// 3、导入渲染的App.vue组件
import App from "./App.vue";
import axios from "axios";

// 4、调用createApp函数,创建spa实例。并用mount挂载app
const vue = createApp(App)
/*5、配置axios*/
axios.defaults.baseURL = 'https://www.escook.cn'
vue.config.globalProperties.$http = axios // 挂载
/*6、定义全局自定义指令*/
vue.directive('focus', {
  mounted(el) {
    el.focus()
  },
  /*这个函数在每次DOM更新后都会被调用*/
  updated(el) { // !!!!!!!!!!!!!!!!!!!!!!!!!
    el.focus()
  }
})

vue.mount('#app')
****************************************************************************
<template>
    <div>
        <h2>Simple---{{count}}</h2>
        <br>
        <input type="text" v-focus/>
        <button type="button" class="btn btn-primary" @click="count=count+1">+1</button>
    </div>
</template>

<script>
  export default {
    name: "Simple",
    data() {
      return {
        count: 0
      }
    }
  }
</script>

<style scoped>

</style>
【6】自定义指令的两个注意项
vue3的学习为主吧,vue2有不同,不了解了。
****************************************************************************
vue.directive('focus', {
  mounted(el) {
    el.focus()
  },
  /*这个函数在每次DOM更新后都会被调用*/
  updated(el) {
    el.focus()
  }
})
****************************************************************************简化写法,在mounted与updated仍会都调用
vue.directive('focus', (el) => {
  el.focus()
})
【7】自定义指令获取参数值
vue.directive('color', (el, binding) => { // !!!!!!!!!!!!!!
  el.style.color = binding.value
})
****************************************************************************接收颜色参数
<span v-color="'red'">自定义指令接受参数变成红色</span><br>
<span v-color="'green'">自定义指令接受参数变成绿色</span>

****************************************************************************************************************************************************************************

复制代码
6、Table案例
【1】案例效果概述
封装自己的Table组件
****************************************************************************用到的知识点
组件封装、具名插槽、作用域插槽、自定义指令
****************************************************************************
步骤:封装Table、  实现删除的功能、 实现添加标签的功能
【2】初始化项目
npm init vite-app table-demo
****************************************************************************
cd table-demo
****************************************************************************
npm i
****************************************************************************
npm i less
****************************************************************************
npm run dev
****************************************************************************
/*index.css*/
:root {
    font-size: 12px;
}

body {
    padding: 8px;
}
****************************************************************************
// 2、导入样式表
import "./assets/css/bootstrap.css"
import "./index.css"
【3】请求商品列表
<template>
    <div>
        <h1>App 根组件</h1>
        <hr>
        <Table></Table>
    </div>
</template>

<script>
  import Table from "./components/Table.vue";

  export default {
    name: "App",
    components: {
      Table
    },
    data() {
      return {
        goodsList: []
      }
    },
    created() {
      this.getGoodsList()
    },
    methods: {
      async getGoodsList() {
        const {data: res} = await this.$http.get('/api/goods')
        console.log(res)
        if (res.status !== 0) {
          alert('获取商品列表失败')
        } else {
          this.goodsList = res.data // 赋值
        }
      }
    }
  }
</script>

<style scoped>

</style>
【4】封装Table组件
<template>
    <div>
        <h2>Table</h2>

    </div>
</template>

<script>
  export default {
    name: "Table",
    props: {
      fff_table_data: { // !!!!!!!!!!!!!!!!!
        type: Array,
        required: true,
        default: []
      }
    }
  }
</script>

<style scoped>

</style>
****************************************************************************渲染基础表格
<div>
        <table class="table table-bordered table-striped">
            <!--标题区-->
            <thead>
            <tr>
                <slot name="header"></slot>
            </tr>
            </thead>
            <!--主体区-->
            <tbody></tbody>
        </table>
    </div>
****************************************************************************
<Table :fff_table_data="goodsList">
<template #header>
<th>序号</th>
<th>名称</th>
<th>价格</th>
<th>标签</th>
<th>操作</th>
</template>
</Table>
****************************************************************************作用域插槽
<tr v-for="(item,index) in fff_table_data" :key="item.id">
<slot name="body" :row="item" :index="index"></slot>
</tr>
****************************************************************************
<template #body="scope">
<td>{{scope.index+1}}</td>
<td>{{scope.row.goods_name}}</td>
<td>{{scope.row.goods_price}}</td>
<td>{{scope.row.tags}}</td>
<td>
<button type="button" class="btn btn-danger btn-sm">删除</button>
</td>
</template>
【5】实现删除商品功能
<button type="button" class="btn btn-danger btn-sm" @click="remove(scope.row.id)">删除</button>
****************************************************************************
remove(id) { // 删除方法
     this.goodsList = this.goodsList.filter(x => x.id !== id)
}
【6】渲染tag标签
<td>
<!--循环渲染标签信息-->
<span class="badge badge-warning ml-2" v-for="item in scope.row.tags" :key="item">{{item}}</span>
</td>
【7】实现input和button按需展示
<td>
<input type="text" class="form-control form-control-sm form-ipt" v-if="scope.row.inputVisible">
<button type="button" class="btn btn-primary" v-else @click="scope.row.inputVisible=true">+Tag
</button>
<!--循环渲染标签信息-->
<span class="badge badge-warning ml-2" v-for="item in scope.row.tags" :key="item">{{item}}</span>
</td>
【8】实现文本框自动获取焦点
vue.directive('focus', (el) => {
  el.focus()
})
****************************************************************************
<input type="text" class="form-control form-control-sm form-ipt" v-if="scope.row.inputVisible"
v-focus>
【9】失去焦点后自动切换为添加按钮
<input type="text" class="form-control form-control-sm form-ipt" v-if="scope.row.inputVisible"
v-focus v-model.trim="scope.row.inputValue" @blur="inputConfirm(scope.row)">
****************************************************************************
inputConfirm(row) {
// 用户输入的值  !!!!!!!!!!!!!!!!!!
const val = row.inputValue
row.inputValue = ''
row.inputVisible = false
}
【10】实现添加新标签
if (!val || row.tags.indexOf(val) !== -1) {
  return
} else {
  row.tags.push(val)
}
****************************************************************************实现回车添加
<input type="text" class="form-control form-control-sm form-ipt" v-if="scope.row.inputVisible"
v-focus v-model.trim="scope.row.inputValue" @blur="inputConfirm(scope.row)"
@keyup.enter="inputConfirm(scope.row)">
【11】总结
使用ref来引用dom和组件实例。
知道$nextTick的调用时机。
keep-alive元素的作用。
插槽的基本用法。
如何自定义指令。

****************************************************************************************************************************************************************************

复制代码
第七章
1、前端路由的概念与原理
【1】概念
路由英文router,本质就是对应关系。路由分为两大类:
后端路由java、前端路由vue
**********************************************************************
SPA是离不开前端路由的。前端路由对于SPA开发是至关重要的
**********************************************************************
前端路由:就是Hash地址与组件之间的一一对应关系。
【2】前端路由的工作方式
用户点击页面上的路由,导致URL地址栏中的Hash值变化。
前端路由监听到hash地址的变化,前端路由吧hash地址对应的组件渲染到浏览器中。
【3】手动模拟实现简单路由
<template>
    <div>
        <h1>App 根组件</h1>
        <a href="#/home">Home</a>&nbsp&nbsp&nbsp
        <a href="#/movie">Movie</a>&nbsp&nbsp&nbsp
        <a href="#/about">About</a>&nbsp&nbsp&nbsp
        <hr>
        <component :is="changeName"></component>
    </div>
</template>

<script>
  import Home from "./components/Home.vue";
  import Movie from "./components/Movie.vue";
  import About from "./components/About.vue";

  export default {
    name: "App",
    components: {
      Home,
      Movie,
      About
    },
    data() {
      return {
        changeName: 'Home'
      }
    },
    created() {
      window.onhashchange = () => {
        switch (location.hash) {
          case '#/home':
            this.changeName = 'Home'
            break
          case '#/movie':
            this.changeName = 'Movie'
            break
          case '#/about':
            this.changeName = 'About'
            break
        }
      }
    },
  }
</script>

<style scoped>
    .form-ipt {
        width: 80px;
        display: inline;
    }
</style>
**********************************************************************
还是挺牛批的,主要是window.onhashchange = () => {}。只是简单的示例

****************************************************************************************************************************************************************************

复制代码
2、vue-router的基本使用
【1】概述
vue-router是官方给出的路由解决方案,通过vue-router可以轻松解决路由问题。
*****************************************************************************
vue-router4.x只能结合vue3进行使用。
【2】vue-router4.x的基本使用
安装vue-router
自定义路由组件
声明路由链接与占位符
创建路由模块
导入并挂载路由模块
*****************************************************************************
npm install vue-router@next -S
*****************************************************************************
Home Moive About说明单个英文字母再复杂,在开发中也不要用多个英文字母.....
*****************************************************************************
<router-link>   与<router-view>来使用

<!--声明路由链接-->
<router-link to="/home">首页</router-link>
<router-link to="/movie">电影</router-link>
<router-link to="/about">关于</router-link>
<!--声明路由占位符-->
<router-view></router-view>
***************************************************************************** 创建路由模块router.js
import {createRouter, createWebHashHistory} from 'vue-router'
import Home from "../components/Home.vue";
import Movie from "../components/Movie.vue";
import About from "../components/About.vue";

const router = createRouter({
  history: createWebHashHistory(),
  routes: [ // 这里指定所有的路由规则
    {path: '/home', component: Home},
    {path: '/movie', component: Movie},
    {path: '/about', component: About},
  ]
})

export default router // 向外共享
*****************************************************************************导入并挂载路由模块
// main.js 都会抢答了....
import router from "./router/router";
vue.use(router) // 挂载路由

****************************************************************************************************************************************************************************

复制代码
3、vue-router高级用法
【1】redirect路由重定向
访问地址A,强制让用户跳转路由C,通过redirect指向新地址。
**********************************************************************/根路径重定向到/home
 routes: [ // 这里指定所有的路由规则
    {path: '/', redirect: '/home'},
    {path: '/home', component: Home},
    {path: '/movie', component: Movie},
    {path: '/about', component: About},
 ]
 【2】为激活的路由链接设置高亮
 使用默认或自定义的高亮class类
**********************************************************************通过默认的就可以了
.router-link-active {
background-color: red;
color: white;
font-weight: bold;
}
【3】嵌套路由,层层嵌套
<template>
    <div>
        <h2>About</h2>
        <hr>
        <!--路由链接-->
        <router-link to="/about/tab1">Tab1</router-link>
        <router-link to="/about/tab2">Tab2</router-link>
        <!--路由展示-->
        <router-view></router-view>
    </div>
</template>

<script>
  import Tab1 from "./tab/Tab1.vue";
  import Tab2 from "./tab/Tab2.vue";

  export default {
    name: "About",
    components: {
      Tab1,
      Tab2
    }
  }
</script>

<style scoped>

</style>
**********************************************************************两个注意点
/*嵌套子路由*/
{
  path: '/about',
  component: About,
  children: [/*注意path不要以斜线开头*/  !!!!!!!!!!!!!!!!!!!!!
{path: 'tab1', component: Tab1},
{path: 'tab2', component: Tab2},
  ]
},
【4】嵌套路由中,路由的重定向。刚进入关于就展示tab1
{
  path: '/about',
  component: About,
  redirect: '/about/tab1',
  children: [/*注意path不要以斜线开头*/
{path: 'tab1', component: Tab1},
{path: 'tab2', component: Tab2},
  ]
},
**********************************************************************
上面是'/about'重定向到/about/tab1。
【5】动态路由匹配
<router-link to="/movie/1">电影1</router-link>
&nbsp&nbsp&nbsp
<router-link to="/movie/2">电影2</router-link>
&nbsp&nbsp&nbsp
<router-link to="/movie/3">电影3</router-link>
&nbsp&nbsp&nbsp
**********************************************************************router.js
 {path: '/movie/:id', component: Movie},
**********************************************************************组件获取参数$route.params.id
<h2>Movie---{{$route.params.id}}</h2>
**********************************************************************第二种方式
{path: '/movie/:id', component: Movie, props: true}, // 代表可以用props的方式
**********************************************************************
<template>
    <div>
        <h2>Movie---{{$route.params.id}}---{{id}}</h2>

    </div>
</template>

<script>
  export default {
    name: "Movie",
    props: ['id']
  }
</script>

<style scoped>

</style>
【6】编程式导航
通过JS的API实现导航的方式,叫做编程式导航。
点击页面上的连接实现导航的方式,叫做声明式导航。
******************************
****************************************
vue-router提供常见的编程式API导航为:this.$router.push('xxx')。this.$router.go(数值)
**********************************************************************
<button type="button" class="btn btn-primary" @click="toMovie(3)">跳转到Movie</button>
**********************************************************************
toMovie(id) {
this.$router.push('/movie/' + id)
}
**********************************************************************
<button type="button" class="btn btn-danger" @click="back">后退</button>

back() { // 从哪里来到哪里去....
this.$router.go(-1)
}
【7】命名路由。如果名字短,并不太有优势
通过name属性为路由规则定义名称。叫做命名路由。
**********************************************************************
<router-link :to="{name:'mov',params:{id:3}}">
to movie
</router-link>
**********************************************************************
{name: 'mov', path: '/movie/:id', component: Movie, props: true}, // 这里命名路由
**********************************************************************通过命名路由实现编程式导航
<button type="button" class="btn btn-primary" @click="nameToMovie(1)">命名组件编程式跳转</button>
**********************************************************************
nameToMovie(id) {
this.$router.push({
  name: 'mov',
  params: {
id: id
  }
})
}
【7】导航守卫
可以控制路由的访问权限。权限控制的实现
**********************************************************************声明全局导航守卫
// 全局导航守卫
router.beforeEach(() => {// 守卫
  console.log('OK')
})
**********************************************************************
// to 将要访问的路由(目标路由)
// from 将要离开的路由(当前路由)
**********************************************************************
next形参,代表权限控制。
next接收了,必须调用next()才能访问路由
【8】next函数的3中调用方式
next(false)强制用户停留在当前页面
**********************************************************************
next('/home') // 强制跳转到home页面
【8】结合token控制后台主页的访问
const token = localStorage.getItem('token') // 通过浏览器存token !!!!!!!!
  if (to.path === '/main' && !token) {
    next('/home') // 强制跳转到home页面
  } else {
    next()
 }

****************************************************************************************************************************************************************************

复制代码
4、后台管理案例
【1】案例效果,登录页-后台、退出登录
用到的知识点:
命名路由、路由重定向、导航守卫、嵌套路由、动态路由匹配、编程式导航
【2】初始化项目并安装vue-router
npm i vue-router@next -S
*************************************************************************
const router = createRouter({
  history: createWebHashHistory(),
  routes: [ // 这里指定所有的路由规则
    {path: '/', redirect: '/home'},
    {path: '/main', component: Main},
  ]
})
*************************************************************************
import router from "./router/router";
vue.use(router) // 挂载路由
【3】展示Login登录页面
const router = createRouter({
  history: createWebHashHistory(),
  routes: [ // 这里指定所有的路由规则
    {path: '/', redirect: '/login'},
    {path: '/login', component: Login,name:'login'},
  ]
})
*************************************************************************
<template>
    <div>
        <h2>Login</h2>
        <input type="text" placeholder="请输入账号"><br><br>
        <input type="password" placeholder="请输入密码">

    </div>
</template>

<script>
  export default {
    name: "Login"
  }
</script>

<style scoped>

</style>
【4】模拟实现登录功能。
<template>
    <div>
        <h2>Login</h2>
        <input type="text" placeholder="请输入账号" v-model.trim="name"><br><br>
        <input type="password" placeholder="请输入密码" v-model.trim="password">
        <hr>
        <button type="button" class="btn btn-primary" @click="loginClick">登录</button>
    </div>
</template>

<script>
  export default {
    name: "Login",
    data() {
      return {
        name: '',
        password: ''
      }
    },
    methods: {
      loginClick() {
        if (this.name === 'admin' && this.password === '123456') {
          this.$router.push('/main')
          return localStorage.setItem('token', 'Bearer xxx')
        }
        // 登录失败
        localStorage.removeItem('token')
      }
    }
  }
</script>

<style scoped>

</style>
*************************************************************************
数据双向绑定+localStorage.setItem('token', 'Bearer xxx')
localStorage.removeItem('token')
即可实现登录存token模拟。
*************************************************************************
输入admin 123456就能跳转/main,否则将停留在当前页面。
【5】登录成功后渲染main后台主页
<template>
    <div>
        <h2>Main---后台主页</h2>

    </div>
</template>

<script>
  export default {
    name: "Main"
  }
</script>

<style scoped>

</style>
*************************************************************************
{path: '/main', component: Main, name: 'main'},
【6】实现退出登录功能,主要是清空token并跳转到login页面
<template>
    <div>
        <h2>Main---后台主页</h2>
        <button type="button" class="btn btn-danger" @click="logoutClick">退出登录</button>
    </div>
</template>

<script>
  export default {
    name: "Main",
    methods: {
      logoutClick() { // !!!!!!!!!!!!!!!!!!!!!!!!
        localStorage.removeItem('token')
        // 强制跳转登录页面
        this.$router.push('/login')
      }
    }
  }
</script>

<style scoped>

</style>
【7】全局控制访问权限,对/main后台页面加守卫。
// 全局导航守卫
router.beforeEach((to, from, next) => {// 守卫
  // console.log('OK')
  // to 将要访问的路由
  // from 将要离开的路由
  const token = localStorage.getItem('token') // 通过浏览器存token
  if (to.path === '/main' && !token) { // !!!!!!!!!!!!!!
    next('/login') // 强制跳转到home页面
  } else {
    next()
  }
})
【8】左侧菜单改造为路由链接
<router-link></router-link>
*************************************************************************
<router-view></router-view>
【9】渲染用户列表数据与点击详情页跳转
v-for='xxx'
*************************************************************************
<router-link></router-link>
*************************************************************************
能用一个英文代替的绝对不用两个。方便理解。非要用两个的也可以用文件夹作为第一个。
看到别人的都给他改造为一个。卧槽,切记!!!!!!!!!!!!!!!!!
【10】为详情页路由开启props传参
{{}}插值表达式渲染
【11】编程式导航实现后退。
this.$router.go(-1)
【12】总结
在vue配置路由。createRouter、vue.use(router)。
知道如何使用嵌套路由。children。
知道如何实现动态路由匹配。
实现编程式导航。
使用全局导航守卫。

****************************************************************************************************************************************************************************

复制代码
第八章
1、vue-cli创建vue项目
【1】概念:vue-cli是vue官方提供的,快速生成vue工程化项目的工具。
*****************************************************************************
开箱即用、基于webpack、功能丰富易于扩展、支持vue2与vue3
*****************************************************************************
cli.vuejs.org/zh
*****************************************************************************
npm install -g @vue/cli
*****************************************************************************
vue --version查看是否安装成功
【2】基于vue-cli创建项目。有个界面化的我直接忽略了。
vue create xxx
*****************************************************************************
选择bable与css Pre-processors
*****************************************************************************
选择2.x
*****************************************************************************
Less选择
*****************************************************************************
In dedicated config files 这个
*****************************************************************************
y
*****************************************************************************
use npm
*****************************************************************************run serve
http://localhost:8080/ 访问即可
【3】梳理vue2项目的基本结构
App.vue  main.js
*****************************************************************************最简单的结构
<template>
    <div>
        <h2>App</h2>
        <hr>
    </div>
</template>

<script>
  export default {
    name: "App"
  }
</script>

<style scoped>

</style>
*****************************************************************************
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false // 是否在console里显示vue的提示消息

const vue = new Vue({
  render: h => h(App), // 把根组件渲染到指定的el区域
})

// 挂载app实例
vue.$mount('#app')
【4】vue使用路由。vue2只能安装3.x vue-router
仅仅是创建路由模块的方式不同。与刚刚学的vue3-router.js的不同。
*****************************************************************************
npm i vue-router@3 -S
*****************************************************************************
//router.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from "@/components/Home.vue";
import Movie from "@/components/Movie.vue";

Vue.use(VueRouter)

const router = new VueRouter({
  routes: [
    {path: '/', redirect: '/home'},
    {path: '/home', component: Home},
    {path: '/movie', component: Movie},
  ]
})

export default router
【5】main.js挂载router对象
import Vue from 'vue'
import App from './App.vue'
import router from "@/router/router";

Vue.config.productionTip = false // 是否在console里显示vue的提示消息

const vue = new Vue({
  render: h => h(App), // 把根组件渲染到指定的el区域
  router: router  // 挂载router
})

// 挂载app实例
vue.$mount('#app')
*****************************************************************************
<template>
    <div>
        <h2>App</h2>
        <hr>
        <!--路由链接-->
        <router-link to="/home">首页</router-link>
        &nbsp&nbsp&nbsp
        <router-link to="/movie">电影</router-link>
        <!--路由组件展示-->
        <br>
        <router-view></router-view>
    </div>
</template>

<script>
  export default {
    name: "App"
  }
</script>

<style scoped>

</style>

****************************************************************************************************************************************************************************

复制代码
2、安装配置element-ui
【1】组件库的概念以及常见组件库
可以直接下载,下载别人封装的组件进行使用,就是组件库。
********************************************************************
vue组件库与bootstrap的区别:
bootstrap只提供了纯粹的原材料(css html js)
vue组件库是遵循vue语法,高度定制的现成组件,开箱即用。
********************************************************************
最常用的组件库:
PC端 Elment UI  / View UI
移动端  Mint UI /  Vant
【2】Element UI
饿了吗前端团队开源的一套PC端vue组件库。
********************************************************************
vue2----Element UI 旧版
vue3---Element Plus 新版
********************************************************************
npm i element-ui -S
********************************************************************
可以一次性引入,也可以按需引入。各有优缺点
********************************************************************
import Vue from 'vue'
import App from './App.vue'
import router from "@/router/router";
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'

Vue.config.productionTip = false // 是否在console里显示vue的提示消息
Vue.use(ElementUI) // 注册插件

const vue = new Vue({
  render: h => h(App), // 把根组件渲染到指定的el区域
  router: router  // 挂载router
})

// 挂载app实例
vue.$mount('#app')
********************************************************************
https://element.eleme.cn/#/zh-CN/component/quickstart
官网都有说明....
********************************************************************使用!!!!!
<template>
    <div>
        <h2>App</h2>
        <el-row>
            <el-button>默认按钮</el-button>
            <el-button type="primary">主要按钮</el-button>
            <el-button type="success">成功按钮</el-button>
            <el-button type="info">信息按钮</el-button>
            <el-button type="warning">警告按钮</el-button>
            <el-button type="danger">危险按钮</el-button>
        </el-row>

        <el-row>
            <el-button plain>朴素按钮</el-button>
            <el-button type="primary" plain>主要按钮</el-button>
            <el-button type="success" plain>成功按钮</el-button>
            <el-button type="info" plain>信息按钮</el-button>
            <el-button type="warning" plain>警告按钮</el-button>
            <el-button type="danger" plain>危险按钮</el-button>
        </el-row>

        <el-row>
            <el-button round>圆角按钮</el-button>
            <el-button type="primary" round>主要按钮</el-button>
            <el-button type="success" round>成功按钮</el-button>
            <el-button type="info" round>信息按钮</el-button>
            <el-button type="warning" round>警告按钮</el-button>
            <el-button type="danger" round>危险按钮</el-button>
        </el-row>

        <el-row>
            <el-button icon="el-icon-search" circle></el-button>
            <el-button type="primary" icon="el-icon-edit" circle></el-button>
            <el-button type="success" icon="el-icon-check" circle></el-button>
            <el-button type="info" icon="el-icon-message" circle></el-button>
            <el-button type="warning" icon="el-icon-star-off" circle></el-button>
            <el-button type="danger" icon="el-icon-delete" circle></el-button>
        </el-row>
        <hr>
        <!--路由链接-->
        <router-link to="/home">首页</router-link>
        &nbsp&nbsp&nbsp
        <router-link to="/movie">电影</router-link>
        <!--路由组件展示-->
        <br>
        <router-view></router-view>
    </div>
</template>

<script>
  export default {
    name: "App"
  }
</script>

<style scoped>

</style>
【3】按需引入
npm i babel-plugin-component -D
********************************************************************
/*babel.config.js*/
module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset'
  ],
  "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}
********************************************************************
import Vue from 'vue'
import App from './App.vue'
import router from "@/router/router";
/*import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'*/
import {Button} from "element-ui";


Vue.config.productionTip = false // 是否在console里显示vue的提示消息
/*Vue.use(ElementUI) // 注册插件*/
Vue.use(Button) // 注册

const vue = new Vue({
  render: h => h(App), // 把根组件渲染到指定的el区域
  router: router  // 挂载router
})

// 挂载app实例
vue.$mount('#app')
********************************************************************还是全局引入直接,噗嗤
<el-row><!--如果按需引入仅按钮el-row会报错-->
<el-button>默认按钮</el-button>
<el-button type="primary">主要按钮</el-button>
<el-button type="success">成功按钮</el-button>
<el-button type="info">信息按钮</el-button>
<el-button type="warning">警告按钮</el-button>
<el-button type="danger">危险按钮</el-button>
</el-row>
【4】把ElementUI操作单独摘出去
// src/element-ui/elementUI.js
import Vue from 'vue'
import {Button, Input, row} from "element-ui";

// 注册需要的组件
Vue.use(Button)
Vue.use(Input)
Vue.use(row)
********************************************************************main.js结构就变清晰了
import Vue from 'vue'
import App from './App.vue'
import router from "@/router/router";
import './element-ui/elementUI.js'

Vue.config.productionTip = false // 是否在console里显示vue的提示消息


const vue = new Vue({
  render: h => h(App), // 把根组件渲染到指定的el区域
  router: router  // 挂载router
})

// 挂载app实例
vue.$mount('#app')

****************************************************************************************************************************************************************************

复制代码
4、axios拦截器的使用
【1】配置axios
main.js通过Vue构造函数的prototype原型对象配置axios
************************************************************************
npm i axios -S
************************************************************************
import Vue from 'vue'
import App from './App.vue'
import router from "@/router/router";
import './element-ui/elementUI.js'
import axios from "axios";

Vue.config.productionTip = false // 是否在console里显示vue的提示消息

axios.defaults.baseURL = 'https://www.escook.cn'
Vue.prototype.$http = axios

const vue = new Vue({
  render: h => h(App), // 把根组件渲染到指定的el区域
  router: router  // 挂载router
})

// 挂载app实例
vue.$mount('#app')
************************************************************************
<template>
    <div>
        <h2>App</h2>
        <el-button type="primary" @click="getInfo">主要按钮</el-button>
        <hr>
        <!--路由链接-->
        <router-link to="/home">首页</router-link>
        &nbsp&nbsp&nbsp
        <router-link to="/movie">电影</router-link>
        <!--路由组件展示-->
        <br>
        <router-view></router-view>
    </div>
</template>

<script>
  export default {
    name: "App",
    data() {
      return {
        name: ''
      }
    },
    methods: {
      async getInfo() {
        const {data: res} = await this.$http.get('/api/get', {
          params: {
            name: 'zs',
            age: 20
          }
        })
        console.log(res)
      }
    }
  }
</script>

<style scoped>

</style>
【2】axios拦截器
应用场景:token身份认证、Loading效果
【3】配置请求拦截器
请求拦截器 token认证
************************************************************************
axios.defaults.baseURL = 'https://www.escook.cn'
axios.interceptors.request.use(config => { // 请求拦截器配置
  console.log(config) // 可以可以看到添加的Authorization字段
  config.headers.Authorization = 'Bearer xxx' // 添加自定义字段
  return config
})
Vue.prototype.$http = axios
【4】展示Loading效果
import Vue from 'vue'
import {Button, Input, Row, Loading} from "element-ui";

// 注册需要的组件
Vue.use(Button)
Vue.use(Input)
Vue.use(Row)
Vue.use(Loading)
************************************************************************
import Vue from 'vue'
import App from './App.vue'
import router from "@/router/router";
import './element-ui/elementUI.js'
import axios from "axios";
import {Loading} from "element-ui";

Vue.config.productionTip = false // 是否在console里显示vue的提示消息

let loadingInstance = null // !!!!!!!!!!!!!!!!!!!!!
axios.defaults.baseURL = 'https://www.escook.cn'
axios.interceptors.request.use(config => { // 请求拦截器配置
  // console.log(config)
  loadingInstance = Loading.service({fullscreen: true}) // 调用Loading组件service方法,创建Loading组件实例,并全屏展示Loading效果
  return config
})
Vue.prototype.$http = axios

const vue = new Vue({
  render: h => h(App), // 把根组件渲染到指定的el区域
  router: router  // 挂载router
})

// 挂载app实例
vue.$mount('#app')
************************************************************************通过响应拦截器关闭Loading
let loadingInstance = null
axios.defaults.baseURL = 'https://www.escook.cn'
axios.interceptors.request.use(config => { // 请求拦截器配置
                                           // console.log(config)
  loadingInstance = Loading.service({fullscreen: true}) // 调用Loading组件service方法,创建Loading组件实例,并全屏展示Loading效果
  return config
})
axios.interceptors.response.use(res => { // 响应拦截器 配置
  loadingInstance.close()
  return res
})
Vue.prototype.$http = axios
************************************************************************一闪而过的Loading
怎么看?通过Network---把信号哪里的无节流改为-slow 3g,效果很明显,记得改回来....

****************************************************************************************************************************************************************************

复制代码
5、配proxy接口代理
【1】接口跨域问题。
如果后端没有开启跨域共享,在默认情况下在localhost:8080请求API就存在跨域问题。
************************************************************************怎么解决呢?
通知后端改!!!哈哈哈哈
************************************************************************通过代理的方式解决跨域问题
把axios的请求根路径,设置为vue项目运行地址(接口请求不再跨域)。
vue项目发现请求接口不存在,把请求转交给proxy代理。
代理把请求根路径替换为devServer.proxy属性的值。发起真正的数据请求。
代理请求到的数据,转发给axios。
************************************************************************
自己请求自己,不行----给代理,代理请求,拿到数据---转交给自己!!!!!!!
【2】跨域问题的实际操作
请求/api/users
************************************************************************
No 'Access-Control-Allow-Origin' header is present on the requested resource.
************************************************************************
axios.defaults.baseURL = 'http://localhost:8080'
************************************************************************
/*vue.config.js  与src同级!!!!!!!!!!!!!!!日尼玛,浪费我半小时*/
module.exports = {
  devServer: {
    //当前项目在开发调试阶段,会把任何位置请求(没有匹配到静态资源文件的请求)代理到以下地址
    proxy: 'https://www.escook.cn',
  },
}
【3】仅在开发调试阶段生效,在上线环境还是需要后端开启跨域,我日尼玛!!!!!!!!!!!!!!!!!

****************************************************************************************************************************************************************************

复制代码
6、用户列表案例
【1】效果展示
知识点:
vue-cli创建vue2项目。
element ui组件库。
axios拦截器。
proxy 跨域接口代理。
vue-router路由。
**************************************************************************实现整体步骤
初始化项目。
渲染用户表格数据。
基于全局过滤器处理时间格式。
实现添加用户的操作。
实现删除用户的操作。
通过路由跳转详情页。
【2】初始化项目、路由
vue create code-simple
**************************************************************************
import Vue from 'vue'
import VueRouter from 'vue-router'


Vue.use(VueRouter)

const router = new VueRouter({
  routes: []
})

export default router
**************************************************************************
import router from "@/router/router";
const vue = new Vue({
  render: h => h(App), // 把根组件渲染到指定的el区域
  router: router  // 挂载router
})
【3】使用路由渲染UserList
 routes: [
    {path: '/', redirect: '/userlist'},
    {path: '/userlist', component: UserList},
  ]
**************************************************************************
<h1>App</h1>
<hr>
<!--路由展示-->
<router-view></router-view>
**************************************************************************
import axios from "axios";
import {Loading} from "element-ui";

Vue.config.productionTip = false // 是否在console里显示vue的提示消息

let loadingInstance = null
/*axios.defaults.baseURL = 'https://www.escook.cn'*/
axios.defaults.baseURL = 'http://localhost:3000'
axios.interceptors.request.use(config => { // 请求拦截器配置
  // console.log(config)
  loadingInstance = Loading.service({fullscreen: true}) // 调用Loading组件service方法,创建Loading组件实例,并全屏展示Loading效果
  return config
})
axios.interceptors.response.use(res => { // 响应拦截器 配置
  loadingInstance.close()
  return res
})
Vue.prototype.$http = axios
**************************************************************************配置开发阶段的代理
/*vue.config.js  与src同级!!!!!!!!!!!!!!!日尼玛*/
module.exports = {
  devServer: {
    //当前项目在开发调试阶段,会把任何位置请求(没有匹配到静态资源文件的请求)代理到以下地址
    proxy: 'https://www.escook.cn',
    host: 'localhost',
    port: 3000, //指定端口
    open: true // 自动打开浏览器
  },
}
**************************************************************************安装配置elementUI
import Vue from 'vue'
import {Button, Input, Row, Loading} from "element-ui";

// 注册需要的组件
Vue.use(Button)
Vue.use(Input)
Vue.use(Row)
Vue.use(Loading)
**************************************************************************还是全局的直接
import ElementUI from 'element-ui'
import {Loading} from "element-ui";

Vue.config.productionTip = false // 是否在console里显示vue的提示消息
Vue.use(ElementUI) // 注册插件
**************************************************************************自定义时间
Vue.filter('dateFormat', (dtStr) => {
  const dt = new Date(dtStr)
  const y = padZero(dt.getFullYear())
  const m = padZero(dt.getMonth() + 1)
  const d = padZero(dt.getDate())

  const hh = padZero(dt.getHours())
  const mm = padZero(dt.getMinutes())
  const ss = padZero(dt.getSeconds())
  return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
})

function padZero(n) {
  return n > 9 ? n : '0' + n
}
**************************************************************************操作列
<el-table-column label="操作" width="180">
<template>
<a href="#">详情</a>&nbsp&nbsp&nbsp
<a href="#">删除</a>
</template>
</el-table-column>
【3】添加新用户
<!--对话框-->
<el-dialog
title="提示"
:visible.sync="dialogVisible"
width="50%">
<span>这是一段信息</span>
<span slot="footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="dialogVisible = false">确 定</el-button>
</span>
</el-dialog>
**************************************************************************表单
<!--添加用户的表单-->
<el-form v-model="userForm" label-width="80px">
<!--一列-->
<el-form-item label="用户姓名">
<el-input v-model="userForm.name" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="用户年龄">
<el-input v-model="userForm.age" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="用户头衔">
<el-input v-model="userForm.position" autocomplete="off"></el-input>
</el-form-item>
</el-form>
**************************************************************************实现表单验证
 :model  // !!!!!!!!!!!!!!!!!!!!!!!卧槽
 :rules="rules" // !!!!!!!!!!!!!!!!!!!!!!!
 prop="name" // !!!!!!!!!!!!!!!!!!!!!!!
  name: [ // !!!!!!!!!!!!!!!!!!!!!!!
            {required: true, message: '请输入活动名称', trigger: 'blur'},
            {min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur'}
          ]
**************************************************************************   
<el-form :model="userForm" label-width="80px" :rules="rules">
<!--一列-->
<el-form-item label="用户姓名" prop="name">
<el-input v-model="userForm.name" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="用户年龄">
<el-input v-model="userForm.age" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="用户头衔">
<el-input v-model="userForm.position" autocomplete="off"></el-input>
</el-form-item>
</el-form>
**************************************************************************关闭点击旁边关闭
<el-dialog
                title="添加新用户"
                :visible.sync="dialogVisible"
                :close-on-click-modal="false"
                width="50%">
**************************************************************************可以自定义验证规则
{validator:checkAge}
**************************************************************************重置填写数据
@close="dialogClose"
**************************************************************************
<el-form :model="userForm" label-width="80px" :rules="rules" ref="addForm">
**************************************************************************
dialogClose() {
// alert('ok')
// this.userForm={}
this.$refs.addForm.resetFields();
  },
**************************************************************************添加新用户的预验证
<el-button type="primary" @click="insertNewUser">确 定</el-button>
/*添加按钮*/
insertNewUser() {
this.$refs.addForm.validate((valid) => { // 会根据要求项是否填写来进行验证
  //alert(valid)
  if (!valid) {
return
  } else { // 执行添加

  }
})
},
**************************************************************************添加用户逻辑
insertNewUser() {
this.$refs.addForm.validate(async valid => { // 会根据要求项是否填写来进行验证
  //alert(valid)
  if (!valid) {
return
  } else { // 执行添加
const {data: res} = await this.$http.post('/api/users', this.userForm)
if (res.status !== 0) {
  alert('添加失败')
} else {
  console.log('添加成功')
  this.dialogVisible = false
  await this.getUserList()
}
  }
})
  },
【4】优化提升的效果
this.$message.error("操作失败")
this.$message.success("操作成功")
**************************************************************************删除确认框
async removeUser() {
// alert('OK')
const res = await this.$confirm('此操作将永久删除该用户, 是否继续?', '提示', {
  confirmButtonText: '确定',
  cancelButtonText: '取消',
  type: 'warning'
}).catch(err => err)
//alert(res)
if (res !== 'confirm') {// 取消了
  return this.$message.info('您取消了删除')
} else {// 操作删除
  this.$message.success('删除成功')
}
  },
**************************************************************************发起API请求删除
 /*删除用户*/
      async removeUser(id) {
        // alert('OK')
        const res = await this.$confirm('此操作将永久删除该用户, 是否继续?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).catch(err => err)
        //alert(res)
        if (res !== 'confirm') {// 取消了
          return this.$message.info('您取消了删除')
        } else {// 操作删除
          const {data: res} = await this.$http.delete('/api/users/' + id)
          if (res.status !== 0) {
            this.$message.error("删除失败")
          } else {
            this.$message.success('删除成功')
            await this.getUserList()
          }
        }
      },
【5】渲染用户详情
<router-link :to="'/userList/'+scope.row.id">详情</router-link>
&nbsp&nbsp&nbsp
**************************************************************************
<template>
    <div>
        <h2>UserInfo</h2>

    </div>
</template>

<script>
  export default {
    name: "UserInfo"
  }
</script>

<style scoped>

</style>
**************************************************************************路由配置
{path: '/userlist/:id', component: UserInfo},
**************************************************************************详情页渲染
<template>
    <div>
        <h2>UserInfo---{{id}}</h2>
        <el-card>
            <div slot="header">
                <span>用户详情</span>
                <el-button style="float: right; padding: 3px 0" type="text" @click="back">返回</el-button>
            </div>
            <div>
                <p>姓名:{{userInfo.name}}</p>
                <p>年龄:{{userInfo.age}}</p>
                <p>头衔:{{userInfo.position}}</p>
            </div>
        </el-card>
    </div>
</template>

<script>
  export default {
    name: "UserInfo",
    props: ['id'],
    data() {
      return {
        userInfo: {}
      }
    },
    created() {
      this.getUserInfo()
    },
    methods: {
      back() {
        this.$router.go(-1)
      },
      async getUserInfo() {
        const {data: res} = await this.$http.get('/api/users/' + this.id)
        if (res.status === 0) {
          this.userInfo = res.data
        }
      }
    }
  }
</script>

<style scoped>

</style>
【6】实现loading效果,好屌呀,已经实现了。emmm
import Vue from 'vue'
import App from './App.vue'
import router from "@/router/router";
import axios from "axios";
import ElementUI from 'element-ui'
import {Loading} from "element-ui";

Vue.config.productionTip = false // 是否在console里显示vue的提示消息
Vue.use(ElementUI) // 注册插件

Vue.filter('dateFormat', (dtStr) => {
  const dt = new Date(dtStr)
  const y = padZero(dt.getFullYear())
  const m = padZero(dt.getMonth() + 1)
  const d = padZero(dt.getDate())

  const hh = padZero(dt.getHours())
  const mm = padZero(dt.getMinutes())
  const ss = padZero(dt.getSeconds())
  return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
})

function padZero(n) {
  return n > 9 ? n : '0' + n
}

let loadingInstance = null
/*axios.defaults.baseURL = 'https://www.escook.cn'*/
axios.defaults.baseURL = 'http://localhost:3000'
axios.interceptors.request.use(config => { // 请求拦截器配置
                                           // console.log(config)
  loadingInstance = Loading.service({fullscreen: true}) // 调用Loading组件service方法,创建Loading组件实例,并全屏展示Loading效果
  return config
})
axios.interceptors.response.use(res => { // 响应拦截器 配置
  loadingInstance.close()
  return res
})
Vue.prototype.$http = axios

const vue = new Vue({
  render: h => h(App), // 把根组件渲染到指定的el区域
  router: router  // 挂载router
})

// 挂载app实例
vue.$mount('#app')
【7】总结
vue-clic创建项目。
完整引入、按需引入elementUI/常见组件的使用。
使用axios拦截器(Loading)。
配置proxy代理。
【8】开启black white非黑即白的开发之旅!!!!!!!!!!!!!!!!!!!!!!!外星人台式机定义未来!!!!!!!
相关推荐
cronaldo9127 分钟前
研发效能DevOps: Vite 使用 Element Plus
vue.js·vue·devops
yg_小小程序员13 小时前
vue3中使用vuedraggable实现拖拽
typescript·vue
川石教育16 小时前
Vue前端开发-缓存优化
前端·javascript·vue.js·缓存·前端框架·vue·数据缓存
漫天转悠1 天前
VScode中配置ESlint+Prettier详细步骤(图文详情)
vscode·vue
落魄实习生2 天前
AI应用-本地模型实现AI生成PPT(简易版)
python·ai·vue·ppt
bpmf_fff2 天前
二九(vue2-05)、父子通信v-model、sync、ref、¥nextTick、自定义指令、具名插槽、作用域插槽、综合案例 - 商品列表
vue
java_heartLake2 天前
Vue3之状态管理Vuex
vue·vuex·前端状态管理
小马超会养兔子2 天前
如何写一个数字老虎机滚轮
开发语言·前端·javascript·vue
小阳生煎2 天前
多个Echart遍历生成 / 词图云
vue
小马超会养兔子3 天前
如何写一个转盘
开发语言·前端·vue