第一章=>
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非黑即白的开发之旅!!!!!!!!!!!!!!!!!!!!!!!外星人台式机定义未来!!!!!!!