vue 是MVVM框架
0.p2 mvvm是双向数据绑定的框架:vue本身实现了数据和视图的相互监听影响
mvc是单向数据绑定个,数据更改可以渲染视图,但是视图更改没有更改数据,需要我们自己在控制层基于change事件实现数据的更改(React)
1.p3 增加对象的键值对
vm.obj={...obj,
name:'珠峰培训'
}
vm.$set(vm.obj,'name','珠峰培训')
并不是所有的数据更改最后都会通知视图重新渲染
初始化数据是一个对象,对象中没有xxx键值对,后期新增的键值对是不会让视图重新渲染的,解决方法:
一,最好在初始化数据的时候,就把视图需要的数据提前声明好(可以使空值,但是要有这个属性)原理:只有在data中初始化过的属性才有GET/SET属性
二,不要修改某个属性名,而是把对象的值整体替换(指向新的堆内存)
三,可以基于vm.$set内置方法修改数据:vm.$set(obj,key,value)
在胡子语法中丙丁的数据值是对象类型,会基于JSON.stringfy把其编译为字符串再呈现出来(而不是直接toString处理的)
2.p3 如果数据是一个数组,我们修改数据基于ARR[N]=xxx或者ARR.length--等操作方法,是无法让视图重新渲染的,需要基于:
push/pop等内置的方法
重新把ARR的值重写(指向新的堆内存)
vm.$set
3.p4 v-once:绑定的数据是一次行的。后面无论怎么改变,试图都不会重新渲染
v-if :如果对于的值是true,当前元素会显示在结构中,如果是false,当前元素会在结构中移除(dom的增加和删除)
v-show :和v-if类似,只不过它是控制元素像是的显示
在频繁的切换过程中,v-if 明显要比v-show要低一些
p5 es6 新增for of 循环
1.获取的不是属性名是属性值
2.不会遍历原型上共有的属性方法
3.只能遍历可被迭代的数据类型(Symbol.ineratoer):Array、String 、Arguments、NodeList、Set、Map等,但是普通对象时不可被迭代的数据,所以不能用for of循环
for in
可以遍历数组也可以遍历对象
优先遍历属性名为数字的,
会把属性类型上自定义的属性方法遍历到
let arr = [10,20,30,40]
Object.prototype.AA =123
for(let key in obj){
if(!obj .hasOwnProperty(key)) break;
console.log(key)
}
p7 购物车案列 全选和非全选
4.p8 filters只能用在v-bind 和{{}}中,filters 没有挂载到vue的实例中
5.p9 computed会挂载到vue实例中,它存储的值是对应方法返回的结果(getter函数处理的结果)
第一次结果获取后,会把这个结果缓存下来
真实项目中:我们一般用一个计算属性和某些响应式数据进行关联,响应式数据发生改变,计算属性的getter函数会重新执行,否则使用的是上次计算出来的缓存结果
vm.$forceUpdate()强制更新视图的重新渲染
p7 p10 购物车全选和非全选案例 computed setter
6.p11 watch当需要在数据变化时执行异步或开销较大的操作时应用监听器
watch监听响应式数据改变(watch中监听的响应式数据必须在data中初始化)和computed中的setter类似,只不过computed是自己单独设计的计算属性(不能和data中的冲突),而watch只能监听data中有的属性
购物车全选和非全选案列 watch
p12 vue 绑定class ,style
p13 选项卡案列
p14 计算器案例
products.map()
reduce
获取总价格
totalPrice(){
return this.productList.reduce((accumulator.item)=>{
return accumultor + item.count*item.price
},0)
}
获取最大单价
maxPrice(){
let arr= this.productList.filter(item=>{
return item.count>=1
}).map(item=>{
return item.price
})
return arr.length === 0 ? 0 : Math.max(...arr)
}
7.p15 beforeCreate 创建vue实例之前
created 创建实例成功(一般在这里实现数据的异步请求)
beforeMount 渲染dom之前(加载组件第一次渲染)
Mounted 渲染dom完成(加载组件第一次渲染)
beforeUpdate重新渲染之前(数据更新等操作控制DOM重新渲染)
update重新渲染完成
beforeDestroy销毁之前
destroyed销毁完成
vm.$mount("#app")
8.p16 this.$refs对象 只有在mounted才能获取这些元素
9.p17购物商品多项选择列表案例
删除其中一个筛选条件
this.data = this.data.filter(item=>{
return item.type!==type
})
点击选项添加条件
handleSelect(type,name){
let flag = this.data.some(item=>parseInt(item.type)===parseInt(type))
if(flag){
this.data = this.data.map(item=>{
if(parseInt(item.type)===parseInt(type)){
item.name = name
}
return item
})
}else{
this.data.push({
type,
name
})
}
}
排序
this.data.sort((A,B)=>{
return A.type-B.type
})
10 p18
全局组件:无需单独引用或者配置,直接在大组件中调取全局组件即可
Vue.component(componentName,options)
Vue.component('my-button',{
template:`<button>按钮组件<button>`
data:{},
created(){
}
})
组件命名规则
kebab-case:横线作为分隔符 只能 基于kebab方法调取
PasalCase:单词首字母大写 也是基于debab方法调取(如果在template中可以使用pasalCase方式调用)
调取组件的时候,会把所有组件的单词渲染为小写字母(我们命名的时候除PasalCase模式外,我们都要把组件名设置为小写,调取组件的时候可以使大写也可以使小写(最后都是按照小写渲染的))
调取组件
单闭合:不符合w3c规范,调取完成后,后面的视图不识别(避免使用)
双闭合:可以设置除组件规定内容外的其余内容(slot插槽)
Vue.component('MyButton',{
//必须设置template 或者render函数,来规定当前组件的渲染视图
template:`<button>组件中的按钮</button>`
})
template:
必须设置template 或者render函数,来规定当前组件的渲染视图
每个组件只能有一个根元素
模板字符串方式
template标记方式
slot插槽处理
data必须是一个函数,包装不同组件之间的数据互相不干扰(返回的对象中初始化数据)
11. p20
局部组件
const MyButton={
template: `<button>局部button</button>`,
data(){
return{
}
}
}
let vm=Vue({
data:{
},
components:{
MyButton,
'mybutton':MyButton
}
})
12.p21
vue porps 父传子数据
props:{
title:"",
required:true,
default:'默认值',
validator(){
自定义验证规则 val传递的属性值 在函数中,根据自己的规则,返回true和false来验证传递的值是否符合规则
}
}
13.p22
vue单向数据流
14.p23
$emit()
15.p24 投票系统案例
$on()
16.p25
基于ref实现父子组件信息通信
ref如果在普通的dom元素上使用,引用指向的就是dom元素,如果用在子组件上,引用就指向组件实例,基于此可以快速获取和操作子组件中的数据
$parent和$children示获取组件和子组件的实例,只不过$children是一个数组集合,需要我们记住组件顺序才可以
17.p26
provide 和inject 绑定并不是响应式的,这是刻意为之的,然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的
基于provide 和inject实现祖先与后代的通信
这些数据存储在this._provided:可以使对象也可以使闭包的方式,闭包方式。闭包的方式好处是“它会在实例上的信息都挂载完成后再处理”
provide(){
return{
num:0,
change:this.change
}
}
methods:{
change(){
type==='support'?this._provided.num
this.$forceUpdate()
}
}
inject:['change']
18.p27-p29
轮播图
19.p30
defineProperty()
object.defineProperty(obj,;name;,{
get(){
}
set(){
}
value:'',
confirguable:true,//属性时否可被删除
enumerable:true //是否为可枚举属性
writable:true//是否当前属性可被修改
})
vue内置的observer/defineRetive函数,会帮我们把所有在DATA中初始化的属性都GETTER/SETTER了(递归处理)
Array本身被Getter/Setter了,但是里面的每一项都没有被处理,所以基于Arr【index】=xxx修改值不能通知视图重新渲染
vue对于数组这样处理的:把数组中的一些内置方法进行重写
push/pop/unshift/shift/splice/reverse/sort,
当我们调取这些方法的时候,vue会帮我们刷新视图
$set不仅仅是用来修改数据,而且可以把被修改的属性基于defineProperty进行getter/setter
this.$set(this.obj,'name','名字')
20.p31
v-model 实现原理
22.p32
webpack
webpack 安装
为了防止全局安装出现的版本冲突,我们一般把webpack安装在本地项目
yarn add webpack webpack-cli -D
npm i webpack webpack-cli --save-dev
从npm5.2 版本后,提供了一个命令:npx ,基于这个命令可以执行本地安装的模块
npx webpack 基于npx执行了webpack命令,而这个命令就是实现打包部署的
找到node_modules/.bin
要求我们得有webpack.cmd 的文件
执行webpack.cmd
也可以从webpack.json 中配置可执行的脚本
“scripts”:{
"build":"webpack",
}
npm run build /yarn build
基础打包语法
src:存储项目开发的源文件
dist:打包后的文件目录
从第四代版本开始,可以支持零配置
23.p33
webpack配置文件
自定义基础配置(webpack 是基于node js 写的。所以用common.js规则)
webpack.config.js 或者 webpackfile.js
在这个文件中设置我们自定义的打包规则
1,所有的规则都写在module.exports={}中
let path = require('path')
module.exports ={
//配置环境 production和devlopment
mode:'production',
//入口
entry:'./src/index-my.js',
//出口
output:{
//输出的文件名
filename:'bundle.min.js'
//输出的目录必须是绝对路径
path:path.resolve(__dirname,'build')
}
}
指定webpack的配置文件 打包
npx webpack --config webpack.config.development
shift+alt+F 代码格式化
24 p34
webpack -dev-server
webpack-dev-server --config xxx.js (特点:服务器启动后,默认是不关闭的,当我们修改src中的文件时,它会自动进行编译,然后自动刷新浏览器)
let path = require('path')
module.exports ={
mode:'production',
entry:'./src/index-my.js',
output:{
filename:'bundle.min.js'
path:path.resolve(__dirname,'build')
}
devServer:{
prot: 3000, //创建服务指定的端口号
progress: true // 显示打包的速度
contentBase:'./build'//指定当前服务处理资源的目录
open: true //编译完会自动打开浏览器
}
}
npx webpack-dev-server --config webpack.config.development.js
25 p35
html-webpack-plugin
let path = require('path')
let HtmlWebpackPlugin = requrie('html-webpack-plugin') //每一个导入进来的插件都是一个类 new HtmlWebpackPlugin({})
module.exports ={
mode:'production',
entry:'./src/index-my.js',
output:{
//让每一次生成的文件名都带着hash值
filename:'bundle.min.[hash].js'
path:path.resolve(__dirname,'build')
}
devServer:{
prot: 3000, //创建服务指定的端口号
propress: true // 显示打包的速度
contentBase:'./build'//指定当前服务处理资源的目录
open: true //编译完会自动打开浏览器
},
//使用插件
plugins:[
new HtmlWebpackPlugin({
//不指定模板会按照默认模板创建一个HTML页面,当然真实项目中一般都是把自己写好的HTML进行编译
template:'./src/index.html',
//输出的文件名
filename:'index.html',
//让我们引入的js后面加上hash戳(清除缓存),但在真实项目中我们一般都是每一次编译生成不同的js文件引入
hash:true,
//控制压缩html
minify:{
collapseWhitespace: true,
removeComments:true,
removeAttributeQuotes:true,
removeEmptyAttributes:true
}
})
]
}
27. p36
加载其loader
使用加载器loaer来处理css规则
处理css 时需要在js引用文件中导入css
requrie('./css/index.css')
let path = require('path')
let HtmlWebpackPlugin = requrie('html-webpack-plugin')
module.exports ={
mode:'production',
entry:'./src/index-my.js',
output:{
//让每一次生成的文件名都带着hash值
filename:'bundle.min.[hash].js'
path:path.resolve(__dirname,'build')
}
devServer:{
prot: 3000, //创建服务指定的端口号
propress: true // 显示打包的速度
contentBase:'./build'//指定当前服务处理资源的目录
open: true //编译完会自动打开浏览器
},
//使用插件
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html',
filename:'index.html',
hash:true,
minify:{
collapseWhitespace: true,
removeComments:true,
removeAttributeQuotes:true,
removeEmptyAttributes:true
}
})
],
//使用加载器loader 处理规则
module:{
rules:[{
test:/\.(css|less)$/, //基于正则区配处理那些文件
use:[ //控制使用的规则,有顺序,从右到左执行
"style-loader", //把编译的css插入到页面的Head中(内嵌式样式)
"css-loader", //@import/url()这种语法的
"postcss-loaer", //设置前缀
或者
{
loader:'postcss-loader',
options:{
ident:'postcss',
plugins:[
require('autoprefixer')
]
}
},
{
loader:'less-loader',
options:{
}
}
]
}]
}
}
post-css 需要一个autoprefixer
新建一个post.config.js文件
module.export ={
plugins:[
require('autoprefixer')
]
}
在package.json中配置
“browserslist”:[
">1%",
"last 2 versions"
]
28 p37
css抽离
mini-css-extract-plugin
let path = require('path')
let HtmlWebpackPlugin = requrie('html-webpack-plugin')
let MiniCssExtractPlugin = require("mini-css-extract-plugin")
let OptimizeCssAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin")
let UglifyjsWebpackPlugin = require("uglifyjs-webpack-plugin")
module.exports ={
mode:'production',
entry:'./src/index-my.js',
output:{
//让每一次生成的文件名都带着hash值
filename:'bundle.min.[hash].js'
path:path.resolve(__dirname,'build')
}
devServer:{
prot: 3000, //创建服务指定的端口号
propress: true // 显示打包的速度
contentBase:'./build'//指定当前服务处理资源的目录
open: true //编译完会自动打开浏览器
},
//配置优化规则
optimization:{
//压缩优化
minimizer:[
//压缩css(产生问题:js压缩不在执行自己默认的压缩方式了,也走的是这个插件从而导致无法压缩)
new OptimizeCssAssetsWebpackPlugin(),
new UglifyjsWebpackPlugin({
cache: true, //是否使用缓存
parallel: true, //是否是并发编译
courceMap: true //启动源码映射(方便调试)
})
]
},
//使用插件
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html',
filename:'index.html',
hash:true,
minify:{
collapseWhitespace: true,
removeComments:true,
removeAttributeQuotes:true,
removeEmptyAttributes:true
}
}),
//引用的css处理
new MiniCssExtractPlugin({
filename:"main.min.css", //指定输出文件
})
],
内嵌css处理
//使用加载器loader 处理规则
module:{
rules:[{
test:/\.(css|less)$/, //基于正则区配处理那些文件
use:[ //控制使用的规则,有顺序,从右到左执行
MiniCssExtractPlugin.loader, //把抽离的css文件外联到html文件中
"style-loader", //把编译的css插入到页面的Head中(内嵌式样式)
"css-loader", //@import/url()这种语法的
"postcss-loaer", //设置前缀
或者
{
loader:'postcss-loader',
options:{
ident:'postcss',
plugins:[
require('autoprefixer')
]
}
},
{
loader:'less-loader',
options:{
}
}
]
}]
}
}
29 p38
基于babel实现es6的转换和ESLint语法检测
css抽离
mini-css-extract-plugin
let path = require('path')
let HtmlWebpackPlugin = requrie('html-webpack-plugin')
let MiniCssExtractPlugin = require("mini-css-extract-plugin")
let OptimizeCssAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin")
let UglifyjsWebpackPlugin = require("uglifyjs-webpack-plugin")
module.exports ={
mode:'production',
entry:["@babel/polyfill",'./src/index-my.js'],
output:{
filename:'bundle.min.[hash].js'
path:path.resolve(__dirname,'build')
}
devServer:{
prot: 3000,
propress: true
contentBase:'./build'
open: true
},
//配置优化规则
optimization:{
minimizer:[
new OptimizeCssAssetsWebpackPlugin(),
new UglifyjsWebpackPlugin({
cache: true,
parallel: true,
courceMap: true
})
]
},
//使用插件
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html',
filename:'index.html',
hash:true,
minify:{
collapseWhitespace: true,
removeComments:true,
removeAttributeQuotes:true,
removeEmptyAttributes:true
}
}),
new MiniCssExtractPlugin({
filename:"main.min.css",
})
],
//使用加载器loader 处理规则
module:{
rules:[
{
test:/\.(css|less)$/,
use:[
MiniCssExtractPlugin.loader,
"style-loader",
"css-loader",
"postcss-loaer",
或者
{
loader:'postcss-loader',
options:{
ident:'postcss',
plugins:[
require('autoprefixer')
]
}
},
{
loader:'less-loader',
options:{
}
}
]
},
{
test:/\.js$/i,
use:[
{
loader:'babel-loader',
options:{
//基于BABEL的语法解析包(es6->es5)
presets:['@babel/preset-env'],
//使用插件处理es6的特殊语法
plugins:[
["@babel/plugin-proposal-decorators",{"legacy":true}],
["@babel/plugin-proposal-class-properties",{"loose": true}],
"@babel/plugin-transform-runtime",
]
}
},
"eslint-loader" //添加eslink loader
按照官方文档生成一个eslintrc.json
],
//指定js的编译目录(忽略哪些目录)
exclude:/node_modules/,
include:path.resolve(__dirname,'src')
},
]
}
}
babel 中的插件polyfill和runtime
@babel/polyfill 和@babel/runtime 需安装到生产环境下,先导入require()或者在entry:["@babel/polyfill",'./src/index-my.js']中加入
eslink
添加"eslint-loader"
生成eslintrc.json
30 p39
在vscode中开启es7中类的装饰器,项目根目录中设置jsconfig.json,再重启vscode
{
"compilerOptions":{
"experimentalDecorators": true
}
}
处理jquery
暴露全局loader
expose-loader -D
//内联加载器
import jquery from 'expose-loader?$!query'(每个模块都要引入,麻烦)
//在每个模块中都可以使用,不用引用
let webpack = require('webpack')
module.exports ={
plugins:[
//在每个模块中都注入$
new webpack.ProvidePlugin({
"$":'jquery'
})
]
}
图片的处理
html中的图片
rules:[
{
//图片处理
test:/\.(png|jpg|jpeg|gif|ico|webp|bmp)$/i,
//use:['file-loader']或者url-loader(url-loader可以使用base64的格式)
use:[
{
loader:'url-loader',
options:{
//图片小于200kb 直接给base64
limit:200*1024
}
}
]
},
{
//处理html中导入的图片
test:/\.(html|htm|xml)$/i,
use:[
"html-withimg-loader"
]
}
]
js中相对路径在webpack打包时,无法打包图片无法显示需要先把图片导入进来 ,绝对地址不需要
webpak打包,在js中使用图片 ,需要先把图片导入进来,然后再使用 两种方法
import img from './stati/psb.jpg'
let img = require('./static/psb.jpg')
分目录打包
module.exports = {
output:{
filename:'js/bundle.min.[hash].js'
path:path.resolve(__dirname,'build')
//编译后引用资源地址前面设置前面设置前缀
publicPath:'./'
},
plugins:[
new MiniCssExtractPlugin({
filename:'css/main.css'
})
],
module:{
rules:[
{
test:/\.(png|jpg|jpeg|gif|ico|webp|bmp)$/i,
use:[
{
loader:'url-lader',
options:{
//控制打包图片所在目录
outputPath:'images'
}
}
]
},
]
}
}
31 p40 webpack配置实例
let path =require('path')
let HtmlWebpackPlugin = require('html-webpack-plugin')
let MiniCssExtractPlugin = require("mini-css-extract-plugin")
let OptimizeCssAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin")
let UglifyjsWebpackPlugin = require("uglifyjs-webpack-plugin")
module.exoprts = {
mode:'production',
entry:'./src/index.js',
output:{
filename:'bundle.min.[hash].js',
path:path.resolve(__dirname,'dist')
},
devServer:{
port:3000,
contentBase:"./dist",
progress: true,
open:true
},
//压缩css
optimization:{
minimizer:{
new MiniCssExtractPlugin({
}),
new UglifyjsWebpackPlugin({
cache:true,
parallel:true,
sourceMap:true
})
}
},
plugins:[
new HtmlWebpackPlugin({
template:"./src/banner.html",
filename:'index.html',
}) ,
MiniCssExtractPlugin ({
filename:"bundle.min.[hash].css",
})
],
modules:[
rules:[
{
test:/.\(css|less)$/i,
use:[
//内嵌式用style-loader,抽离时用MinCssExtractPlugin.loader
MinCssExtractPlugin.loader(用MiniCssExtractPlugin时用)或者("style-loader"),
"css-loader",
{
loader:"postcss-loader",
options:{
indent:"postcss",
plugins:[require("autofixer")]
}
},
"less-loader"
],
},
{
test:/.\js$/i,
exclude:/node_modules/,
include:path.resolve(__dirname,'src'),
use:[
{
loader:'babel-loader',
option:{
presets:["@babel/preset-env"],
plugins:[
"@babel/plugin-transform-runtime"
]
}
}
]
},
{
test:/\.(png|jpg|jpeg|gif|ico|webp|bmp)$/i,
//use:['file-loader'],url-loader可以处理base64或者
use:[
{
loader:'url-lader',
options:{
limit:200*1024,
outputPath:'images'
}
}
]
},
{
//处理html中的图片
test:/\.(html|htm|xml)$/i,
use:[
"html-withimg-loader"
]
}
]
]
}
postcss中不兼容前缀时
在package.json中配置
“browserslist”:[
">1%",
"last 2 versions"
]
32.p41
脚手架
1.安装脚手架(一般安装在全局)
npm install @vue/cli -g 或者 yarn global add @vue/cli
2.创建工程化项目
vue create 项目名称
3.vue inspect 查看配置
项目目录
crc 存储项目开发的源文件
main.sj 打包编译的入口文件
app.vue 项目页面的入口文件
components 存储当前项目的公共组件
xx.vue
assets 一般存放项目中需要引入的静态资源文件
xxx.png
xxx.css
public
index.html 当前项目的主页面(最后把所有在src中写入的内容通过webpack/vue编译渲染后,最后都会呈现在index.html的#app容器中)
xxx.xx.虽然把代码或资源都放在src中,但是有时候,某些资源也可能需要单独在index.html就引用了
直接在html中引入资源的时候最好都设置为<%=BASE_URL %>这种方式,因为资源写的webpack会帮我们进行编译处理
33.p42 webpack配置
vue inpect 查看当前默认的webpack配置信息
vue create 创建一个项目
vue add[plugin]在当前项目中安装插件
修改默认的webpack配置
需要在根目录中设置vue.cofig.js
module.exports ={
publicPath:process.env.NODE_ENV ==="production"?"http://www.zhufengpeixun.cn/":'/',
//自定义一个目录名称,把生成的js/css/图片等静态资源放到这个目录中
assetsDir:'assets',
//关闭生产环境下的资源映射(生产环境下不在创建xxx.js.map文件)
productionSourceMap:false,
//设置一些webpack配置项,用这些配置项和默认的配置项合并https://github.com/survivejs/webpack-merge
configureWebpack:{
plugins:[]
}
//直接修改内置的webpack配置项
chainWebpack:config=>{
//原始配置信息对象
config.module
.rule('images')
.use('url-loader')
.lodaer('url-loader')
.tap(options=>{
optioms.limit=200*1024;
return options;
})
}
}
//修改webpack-dev-server配置(尤其是跨域代理)
devServer:{
proxy:{
//请求地址 /user/add
//代理地址 http://api.zhufengpeixun.cn/user/add
"/":{
changeOrigin:true,
traget:"http://api.zhufengpeixun.cn"
}
}
},
//多余1核cpu时,启动并行压缩
parallel:{
parallel: require('os').cpus().length>1
}
//第三方插件配置
pluginOptions:{
}
33.p43
vuex状态管理模式
实现组件之间通信的方法
1.props 父->子
2.$on/$emit 子<->父 拥有共同父亲的兄弟 隔代处理
3.$parent($children|$refs)
4.provide/inject 隔代处理
5.listeners/$attrs
$attrs:获取的是父组件传递进来的属性信息(排除掉在props中注册过的,排除class/style等)都是基于v-bind绑定的属性或者直接静态属性
$listeners: 获取的是父组件传递进来的事件信息 基于$xxx=“xxx”处理的
34.p44
vuex
store = new Vuex.store({
state:{
},
mutations:{
example(state,payload){
//state 容器中储存的状态信息,payload是commit执行的时候传递进来的参数
//this.$store.commit('example',2000)
},
},
//这些方法首先异步获取需要的数据,然后再基于commit触发Mutations中的方法,从而改变state
actions:{
exampleAtion(context({commit}),payload){
//this.$store.dispath('exampleAction','参数')
//context.commit
setTimeout(()=>{
context.commit('example',1000)
//commit('example',1000)
},1000)
}
},
//getters储存的方式等价于computed计算属性,监听当前容器中state的计算属性
getters:{
}
})
35.p45 vuex能处理任何情况下的组件信息通信
前提:spa单页面(实现的是同一个页面中,各组件的信息通信)
vuex:vue中实现公共状态管理的插件
import logger from 'vuex/dist/lodder' vuex
export default new Vuex.store({
state:{
},
mutations:{
},
actions:{
},
getters:{
},
//使用logger中间插件能够详细
plugins:[lodder()]
})
36.p46 如果页面不刷新 父组件中的子组件date中的数据不会更新
因为data中的数据在created中创建,date在update中不会更改
this.$nextTick(()=>{
})
37.p47
//遍历stroe容器中的state,获取到的结果是一个新对象
mapState(['votelist',"message"])=>{votelist:xxx,message:xxx}
import {mapState,mapGetters,mapMuataions,mapActions} from 'vuex'
mapState(["votelist","message"])=>{votelist:xxx,message:xxx}
相当于
function mapState(arr){
let obj={},
arr.forEach(item=>{
obj[item]=this.$stor.state[item]
})
return obj
}
function mapMutation(arr){
let obj={},
arr.forEach(item=>{
obj[item]=function(...args){
this.$store.commit(item,...args)
}
})
return obj
}
export defautl{
computed:{
...mapState({
supNum:state=>state.voteList.supNum,
oppNum:state=>state.voteList.oppNum,
}),//=>{supNum:function(){},oppNum:xxx}
...mapGetters(["ratio"]),
...mapMutations(["supHandle"]),
...mapActions({
oppHandle:'opposeAction'
})
}
}
38.p48
模块化管理vuex
//person.js 个人模块中的状态管理
export default{
namespaced:true,
state:{
name:'珠峰培训',
baseInfo:{
email:'1111@qq.com',
phone:'111111',
}
},
getters:{
queryBase(state){
return '${state.name}的邮箱是:$state.baseInfo.email'
}
},
mutations:{
changeName(state,payload){
state.name=payload
}
},
actions:{
actionDemo(context,payload){
//context.state 当前模块私有的状态
//context.rootState整个store中的状态
}
}
}
//product.js //产品板块中的公共状态管理
export default{
namespaced:true,
state:{
name:'前端开发',
baseInfo:{
time:"5 month"
}
},
getters:{
queryBase(state){
return '${state.name}课程的周期是:$state.baseInfo.time'
}
},
mutations:{
changeName(state,payload){
state.name=payload
}
}
}
//index.js //modules 把每一个模块中的State Actions都合并
import Person from './person'
import Product from './product'
Vue.use(Vuex)
export default new Vuex.store({
//把每个模块中的State,Actions都进行合并
modules:{
Preson,
Product
},
//各个模块中公共的方法和状态
state:{
isLogin:true
},
mutations:{
changeName(state,payload){
state.isLogin= payload
}
}
})
state会按照各个板块进行区分 state={
Person:{...},Product:{...},isLogin:true
}
但是GETTERS/MUTATIONS/Actions默认不会进行模块区分,默认是全部合并在一起,这样会导致冲突
解决方案:每个模块设置namespaced:true,这样最后虽然也是把每个模块中的方法合并在一起了,但是会以模块的名字作为前缀,并进行标识和区分mutations={'Persion/changeName':function(){}}
调用:
this.$store.getters["Person/queryBase"]
this.$store.commit('Person/changeName','姓名')
export default{
computed:{
...mapState({
name: state=>{
//state是全局的
return state.Person.name
}
})
}
}
export default{
computed:{
...mapState('Person',{
name: state=>{
//state是Person下的
return state.name
}
})
}
}
合并模块分开调用
store
index.js
import Product from './product/'
export default vuex.store({
//把每个模块中的state-actions都进行合并
//1.state会按照各个板块进行区分
modules:{
Person,
Product
},
//公共状态方法
state:{
insLogin:true
}
})
person.js
export default{
namespaced:true,
state:{
name:{
}
},
mutations:{
changeName(state,payload){
state.name=payload
}
},
getters:{
queryBase(state){
}
}
}
Product.js
export default{
namespaced:true,
state:{
name:"前端就业开发班"
}
}
import {createNamespaceHelpers} from "vuex"
let {mapstate,mapMutations}= cretaeNampspaceHelpers("Product")
export default{
computed:{
...mapState(['name'])
},
methodes:{
...mapMutations(['changeName'])
},
mounted(){
setTimeout({
this.changeName("调用this.changeName")
},1000)
}
}
抽离名称
store.types.js
export const PRODUCR_MUTATION_CHANGTE_NAME="PRODUCR_MUTATION_CHANGTE_NAME"
import * as types from ',/store-types.js'
调用
export default{
getters:{
[types.PRODUCR_MUTATION_CHANGTE_NAME]
}
mounted(){
setTimeout({
this[types.PRODUCR_MUTATION_CHANGTE_NAME]("调用this.changeName")
},1000)
}
}
39.p49
todo案例
40.p50 本地存储
localStorage
1.存储的信息都是字符串,每次还需要不断解析处理
2.永久性存储,页面刷新也不会消失
3.存储的限制
41.p51 api 单独写入一个文件
新建src->api->index.js
如果项目是基于axios来完成请求处理的,此处我们需要做的就是对axios进行默认的全局配置:如果我们是基于fetch来处理的此处也是对fetch的二次封装
import axios from 'axios';
axios.defaults.baseURL= 'http://127.0.0.1:8888';
axios.defualts.withCredentials = true;
axios.defaults.headers['Content-Type']='application/x-www-form-urlencoded';
axios.defaults.transformRequest=function(data){
if(!date) return date;
let result =``;
for (let attr in data){
if(!data.hasOwnProperty(attr)) treak;
result += `&${attr}=${data[attr]}`;
}
return result.substring(1);
}
axios.interceptors.response.use(function onFulfilled(response){
return response.data;
},function onRejected(reason){
return Promise.reject(reason);
})
axios.defaults.validateStatus= function(status){
return /^(2|3)\d{2}$/.test(status)
}
export default axios;
新建src->api->task.js
import axios from './index';
/*
*queryTask:获取任务列表信息
*/
function queryTask(state=0){
axios.get('/getTaskList',{
params:{
limit:1000,
page:1,
state
}
})
}
export default {
queryTask
}
42.p51 获取数据缓存到vuex中
模块化store
新建store-task.js
import {queryTask} from '../api/task'
export default {
namespaced:true,
state:{
//存储所有任务信息的状态
taskList:null
},
getters:{},
mutations:{
//更新任务列表
updateTaskLIst(state,payload){
state.taskList=payload;
}
},
actions:{
//从服务器获取数据,获取成功后通知mutations中的方法执行
updateTaskList(context){
let commitName = 'updateTaskList';
queryTask().then(result=>{
if(parseInt(result.code)===0){
context.commit(commitName,result.list)
return
}
return Promise.reject();
}).catch(reason=>{
//默认存储的是Null,代表还未从服务器获取过数据,如果获取失败,我们让其为空数组,虽然还是没有数据,但是至少证明我们尝试过获取 操作
context.commit(commitName,[])
});
}
}
}
将模块的task.js合并到store->index.js中
import Vue from 'vue';
import Vuex from 'vuex';
import logger from 'vuex/dist/logger';
import tast from './task'
Vue.use (Vuex);
export default new Vuex.Store({
modules:{
task
},
plugins:[logger()]
})
43.p52
api
新建src->api->task.js
import axios from './index';
/*
*queryTask:获取任务列表信息
*/
export function queryTask(state=0){
axios.get('/getTaskList',{
params:{
limit:1000,
page:1,
state
}
})
}
/*
*addTask增加任务信息
*/
export function addTask(task,time){
return axios.get('/addTask',{
task,
time
})
}
44.p53
ElementUI table 表格
列表页面切换
45.p54
table列表中的删除和完成功能
slot-scope
46.p55 vueRouter
47.p56 hash模式和history模式
location.href,
history.pushState(state,title,url|?xxx=xxx)
48.p57
vur-router配置路由
新建router.js
import Vue from 'vue'
import VueRouter from 'vur-router'
import Home from './home'
Vue.use(VueRouter)
export default new VueRouter({
//设置路由模式:hash/history
mode:'hash',
routes:[
{
path:'/',
redirect:'/home'
//component:Home
},
{
path:'/custom',
name:custom,//命名路由
component:'/custom/list',
children:[
{
path:'/custom/list/:lx',//=>动态路由(把需要传递的参数作为路由地址的一部分)
name:customList
component:CustomList
},
{
path:'/handle',//=>可以简写,但是不能写斜杠,否则默认为是根目录下访问
name:customHandle
component:CustomHandle
},
]
},
]
})
49.p58
基于地址path跳转,
<router-link to="/custom/list?lx=my"></router-link>
<router-link :to="{path:'/custom/list',query:{lx:'my'}}"></router-link> path方式跳转不能基于params传参,只能通过query问号传参
命名路由跳转
<router-link :to="{name:customList}"></router-link>
<router-link :to="{name:customList,query:{lx:'my'}}"></router-link>问号传参会显示在地址栏中
<router-link :to="{name:customList,params:{lx:'my'}}"></router-link>直接传递参数不会显示在地址栏中
动态路由,路由跳转的地址是动态处理的
children:[
{
path:'/custom/list/:lx',//=>动态路由(把需要传递的参数作为路由地址的一部分)
name:customList
component:CustomList
},
]
动态路由跳转(路由跳转的地址是动态处理的)
path:'/custom/list/:lx' =>动态路由
<router-link to="/custom/list/lx"></router-link>
获取(当前地址下刷新页面,params中的信息也不会显示)
this.$router.currentRoute.params={lx:'my'}
query和params的区别
共同点,基于router-link或者this.$router.push跳转的时候,传递的参数可以在渲染组件中获取到
一,
query传递的信息可以在地址栏中显示,
params传递的信息不能在地址栏中显示
二,
query在当前地址下刷新,依然可以基于this.$router.currentRoute.query获取到
params在当前地址下刷新浏览器,params中的信息就清空了
this.$router
go(n)回退或者前进N步
back()=>go(-1)
forward()=>go(-1)
push():跳到指定的路由,实现路由切换
50.p59
this.$route ===this.$router.currentRoute
命名视图
<view></view>
<view name="name2"></view>
components:{
default:Home,
name2:My
}
import Vue from 'vue'
import VueRouter from 'vur-router'
import Home from './home'
Vue.use(VueRouter)
export default new VueRouter({
//设置路由模式:hash/history
mode:'hash',
routes:[
{
path:'/',
redirect:'/home'
components:{
default:Home,
name2:My
}
},
]
component:()=>{
return import (',/pages/Home')
}
总结: component:可以使一个组件
components{}可以使对象(命名视图)
component:函数只有路由去陪成功才引入和加载当前模块,也可以在函数中做点事,列如权限效验
一般在路由列表最后加一个匹配
{
path:'*',
redirect:'/'
//component:Error
}
每一次路由的跳转和切换,都是把之前渲染的组件销毁(destoryed)},重新渲染新组件(before-created =>data)
51.p59
导航守卫(路由权限效验)
全局守卫(不管路由区配哪个地址,哪个组件都会触发)
router.beforeEach((to,from,next)=>{
//to:将要跳转的到的路由对象
//from:从哪个路由来,存储的也是这个路由对象
console.log('===全局before each')
next()
})
router.beforeResolve((to,from)=>{
next()
})
router.afterEach((to,from)=>{
//to:将要跳转的到的路由对象
//from:从哪个路由来,存储的也是这个路由对象
console.log()
next()
})
路由独有守卫
beforeEnter(to,from,next){
next()
}
组件独有守卫
beforeRouteEnter(to,from,next){
next()
}
beforeRouteUpdate(to,from,next){
next()
}
beforeRouteLeave(to,from,next){
next()
}
const router =new VueRouter({
})
export default router
52.p60
配置多页面入口
vue.config.js
modules.exports={
pages:{
login:{
entry:'src/login.js',
template:'public/login.html'
},
index:{
entry:'src/main.js',
templage:'public/index.html'
}
}
}
登录页面
1.表单验证,(防止sql或者特殊字符的注入:防XSS攻击)
2.密码的MD5加密(POST请求)
3.向服务器发送请求
4.接受服务器返回的结果
失败: 直接做一个提示
成功: 可以把登录状态或者权限信息存储到本地(localStorage)
app.vue(vue-router实现SPA)
1.从服务器重新获取登录状态和权限
未登录:给予提示,然后跳转到登录页
已登录,把登录状态和权限存储到vuex中(一般用vuex代替本地存储的内容)
2.各个板块渲染的时候,首先从vuex中获取权限信息,根据获取的结果做权限效验
用户名,手机,邮箱验证
import * as API from './api/login'
checkAccount(){
let argArr=[用户名正则,手机正则,邮箱正则,]
return argArr.some(item=>{
return item.test(this.account)
})
}
checkPassWord(){
return /^\w{6,16}$/.test(this.password)
}
import md5 from 'blueimp-md5'
handleLogin(){
//1.防止xss攻击,先做表单效验
if(!this.checkAccount()||!this.checkPassWord()){
this.$message.error("")
return
}
//2.把状态中的密码进行MD5加密(不能修改原始状态,因为一定修改,视图里的也会被修改)
let password=md5(this.password)
//3.发送给服务器进行效验
API.handleLogin(this.account,this.password)
.then(power=>{
this.$alert("登录成功","系统提示",{
callback:action=>{
location.href= location.origin//默认找index.html
}
})
})
.catch(reson=>{
this.$alert("账号密码不区配","系统提示")
})
}
新建api
api->login
import axios from './index'
export function handleLogin(account,password){
return axios.post('/user/login',{
account,
password
}).then(result=>{
if(parseInt(result.code===0)){
return result.power
}
return Promise.reject(result.codeText)
})
}
53.p61
hash路由和history的区别
history模式下,浏览器刷新后找不到页面会报错,服务器要有个对404页面请求的支持,对404页面特殊处理。
router.js
import Vue from 'vue';
import VueRouter from 'vue-router',
import Custom from './pages/Custom.vue';
import CustomList from './pages/custom/CustomList.vue'
import CustomHandle from './pages/custom/CustomHandle.vue'
imort Systen from './pages/system.vue'
Vue.use(VueRouter)
const router= new VueRouter({
mode:'hash'
routes:[
{
path:'/',
director:'/custom',
},
{
path:'custom',
name:'/custom',
component:Custom,
children:[
{
path:'/custom',
redirect:{
path:'/custom/list',
query:{
type:2
}
}
},
{
path:'list',
name:'customList',
component:CustomList
},
{
path:'Handle/:customId',
name:'customHandle',
component:CustomHandle
},
]
},
{
path:'system',
name:'/system',
component:System
},
{
path:'*',
director:'/custom',
},
]
})
export default router
判断一级导航的切换显示
多次插入同一条路由会报错, 可以进行判断
activeIndex(){
let url= location.href.includes;
if(url.includes('/custom')) return "1"
if(url.includes('/system')) return "2"
}
changeRouter(){
let arr =['/custom/list?type=my',
'/custom/list?type=all',
'/custom/handle'
]
index = parseInt(index);
if(location.href.includes(arr[index-1])) return
this.$router.push(arr[index-1])
}
54.p62 首页登录验证
在api->login.js
export function checkLogin(){
return axios.get('/usre/login').then(result=>{
if(parseInt(result.code)===0){
return true;
}
return Promise.reject(false);
})
}
//获取用户权限
export function queryPwoer(){
return axios.get('/user/power').then(result=>{
if(parseInt(result.code)===0){
return result.power
}
return Promise.reject(result.codeText)
})
}
在store->store-types.js
宏观管控store中的方法名
export const CHECK_IS_LOGIN= 'CHECK_IS_LOGIN';
export const QUERY_POWER="QUERY_POWER";
在store->index.js
import Vue from 'vue';
import Vuex from 'vuex';
import logger from 'vuex/dist/logger';
import * as types from './store-types'
import {queryPower} from '../api/login'
Vue.use(Vuex)
export default new Vuex.Store({
modules:{
}
state:{
isLogin:false,
power:''
},
mutations:{
[types.CHECK_IS_LOGIN](state,isLogin=true){
state.isLogin= isLogin
},
[types.QUERY_POWER](state,power){
state.power = power
}
},
actions:{
[types.QUERY_POWER](context){
queryPower().then(power=>{
context.commit(types.QUERY_POWER,power)
})
}
}
plugins:[logger()]
})
main.js中
import Vue from 'vue'
import App from './App.vue'
import store from './store/index'
import router from './router'
import * as types from './store/store-types.js'
import { checkLogin} from './api/login'
检测是否登录,只有保证是登录状态,才让其继续渲染组件等
checkLogin().then(result=>{
//把登录状态存储到Vuex中
store.commit(stypes.CHECK_IS_lOGIN,true);
store.dispatch(types.QUERY_POWER);
//渲染组件
new Vue({
router,
store,
render: h=>h(APP)
}).$mount('#app');
}).catch(reson=>{
Vue,prototype.$alert('只有登录的用户才能访问系统',系统提示,{
callback:action=>P{
location.href=location.origin+'/login.html'
}
})
})
router.js
import Vue from 'vue';
import VueRouter from 'vue-router',
import Custom from './pages/Custom.vue';
import CustomList from './pages/custom/CustomList.vue'
import CustomHandle from './pages/custom/CustomHandle.vue'
imort Systen from './pages/system.vue'
Vue.use(VueRouter)
const router= new VueRouter({
mode:'hash'
routes:[
{
path:'/',
director:'/custom',
},
{
path:'custom',
name:'/custom',
component:Custom,
children:[
{
path:'/custom',
redirect:{
path:'/custom/list',
query:{
type:2
}
}
},
{
path:'list',
name:'customList',
component:CustomList
},
{
path:'Handle/:customId',
name:'customHandle',
component:CustomHandle
},
]
},
{
path:'system',
name:'/system',
component:System
},
{
path:'*',
director:'/custom',
},
]
})
router.beforeEach((to,from,next)=>{
next();
})
export default router
55.p63
多页面登录逻辑
储存到vuex中
登录成功:储存到本地
1.把登录状态储存到本地(还可以把权限储存到本地)
2.跳转到首页
进入到首页,登录状态和权限都可以从本地获取即可
减少和服务器的交互频率
不能及时和服务器同步(当然也可以每次刷新页面,都从服务器获取一份放到本地)
本地信息容易被修改或干掉 不安全
登录逻辑
单页面引用
index.html(包含了登录模块)
vuex中给初始状态
isLogin:false
power;''
刷新页面第一件是,从服务器获取登录状态和权限,存储到vuex中(异步)
每当进入到组件的时候,都去从vuex中获取登录状态,从而让其显示某个组件
56.p63
权限效验可能出现的情况
1.点击某一个按钮实现路由跳转,此时我们需要效验是否有权限访问当前的组件,如果没有,我们做提示或者禁止跳转
=>beforeEach每次路由跳转必然会执行它,所以我们需要判断,只有访问某个地址我们做对应的权限效验
=>beforeEnter
2.点击某个按钮实现某个功能,如果不管权限是否有,都可以点击,我们可以在点击的时候验证权限有误,从而给予响应的提示
3.没有权限我们让功能消失 vue通常用自定义指令来完成
import store from './store/index'
const router= new VueRouter({
mode:'hash'
routes:[
{
path:'/',
director:'/custom',
},
{
path:'custom',
name:'/custom',
component:Custom,
{
path:'system',
name:'/system',
component:System,
//系统设置的权限效验
beforeEnter(to,from,next){
let power= store.state.power;
if(/(userhandle|departhandle)/.test(power)){
next()
return
Vue.prototype.$alert('无权限访问此模块','系统提示')
}
}
},
{
path:'*',
director:'/custom',
},
]
})
router.beforeEach((to,from,next)=>{
next();
})
export default router
自定义指令:v-power
<el-memu-item v-power="departcustomer|allcustomer">
在main.js
vue.directive('power',{
inserted(el,binding){
setTimeout(__=>{
let arrVal=binding.value.split('|'),
power=store.state.power,
flag= false;
flag =arrVal.some(item=>{
return power.includes(item)
})
!flag?el.parentNode.removeChild(el):null
},0)
},
})
57.p66
存储所有客户信息和我的客户信息的首页
宏观管理统一命名
store->store-types.js
export const CUSTOM_QUERY_LIST='CUSTOM_QUERY_LIST'
新建api->custom.js
import axios frm './index',
export function queryCustomList(options){
let params={
lx:'all',
type:'',
search:'',
page:1,
limit:10
};
params =Object.assign(params,options)
return axios.get('/customer/list',{
params
}).then(result=>{
if(parseInt(result.code)===0){
return result
}
return Promise.reject(result.codeText)
})
}
新建store->custom.js
import * as types from './stor-styoes.js'
import {queryCustomList} from '../api/custom'
export defatult{
namespaced:true,
state:{
myList:null,
allList:null,
},
mutations:{
[types.CUSTOM_QUERY_LIST](state,payload){
//=>payload={result:{},lx:my|all}
let {lx='my',result={}}=payload
lx==="my" ? state.myList=result : state.allList=result
}
},
actions:{
[types.CUSTOM_QUERY_LIST](context,lx='my'){
queryCustomList({
lx
}).then(result){
context.commit(types.CUSTOM_QUERY_LIST,{
result
lx
})
}.catch(()=>{
})
}
},
}
store->index.js
import custom from './custom'
Vue.use(Vuex)
export default new Vuex.store({
modules:{
custom
}
})
custom.js
import * as types from '../../store/store-types'
export defautl{
data(){
return{}
},
computed:{
result(){
return this.queryResult()
}
},
methods:{
queryResult(){
let {myList,allList} = this.$store.state.custom
let lx=this.$route.query.type||'my',
result = lx==="my"? myList : allList
return result
}
},
created(){
//获取问号传递参数的信息
let result = this.queryResult()
if(result === null){
this.$store.dispatch("custom/"+types.CUSTOM_QUERY_LIST,result)
}
}
}
58.p67
customList.vue
import {queryCustomList} from '../../api/custom'
export default{
data(){
return {
type:"',
search:'',
limit:10,
page:1,
}
},
methods:{
queryDate(){
queryCustomList({
lx:this.$route.query.lx||'my',
type:this.type,
search:this.search,
limit:this.limit,
page:this.page
}).then(result=>{
this.result=result
})
}
}
}
watch:{
["$store.state.custom.alLIst"](val){
this.result=val
},
["$store.state.custom.allList"](val){
this.result=val
},
["$storet"](all){
this.onceVuex()
},
}