第一章=>
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>    <a href="#/movie">Movie</a>    <a href="#/about">About</a>    <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>     <router-link to="/movie/2">电影2</router-link>     <router-link to="/movie/3">电影3</router-link>     **********************************************************************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>     <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>     <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>     <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>    <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>     ************************************************************************** <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非黑即白的开发之旅!!!!!!!!!!!!!!!!!!!!!!!外星人台式机定义未来!!!!!!!