Vue学习笔记

416 阅读7分钟

跨域解决

1.vue-cli4创建项目

在根目录下的webpack.config.js文件中添加代码

...   //省略代码webpack配置文件
module.exports={
    ... //省略代码webpack配置进、出口文件
    devServer:{
        ... //省略代码webpack
        proxy:{
            '/app':{
                target:'http://www.weather.com',//要代理的地址
                changeOrigin:true,
                pathRewrite:{
                '^/app':''
            }
            },
            '/api':{
                    target:'http://www.baidu.com',//要代理的地址
                    changeOrigin:true,
                    pathRewrite:{
                    '^/app':''
                   }
             }
        }
    }
}

2.vue-cli3创建项目

Config文件夹下的index.js文件

...   //省略代码webpack配置文件
module.exports={
    dev:{
        ... //省略代码webpack
        proxyTable:{
            '/app':{
                target:'http://www.weather.com',//要代理的地址
                changeOrigin:true,
                pathRewrite:{
                '^/app':''
            }
            },
            '/api':{
                    target:'http://www.baidu.com',//要代理的地址
                    changeOrigin:true,
                    pathRewrite:{
                    '^/app':''
                   }
             }
        }
    }
     ... //省略代码webpack配置进、出口文件
}

重新启动服务器

3. Vue-cli 4以上版本修改

将router下的index.js中的mode设置成hash,不要设置成history

在vue.config.js文件中加入如下配置:

module.exports = { 
  devServer : { 
     open:true, 
     port:8089, 
     host:"127.0.0.1",      
      proxy:{    // 设置代理
        '/api':{ 
             target:'', 
             changeOrigin:true, 
             pathRewrite:{ 
                '^/api':'' 
             } 
          } 
      } 
   }, 
   publicPath:'./', 
   outputDir:'dist', 
   assetsDir:'static' 
}
module.exports = { 
 publicPath: "./", // 公共路径(必须有的) 
 outputDir: "dist", // 输出文件目录  
 // 静态资源存放的文件夹(相对于ouputDir) 
 assetsDir: "static", 
 // eslint-loader 是否在保存的时候检查(果断不用,这玩意儿我都没装) 
 lintOnSave:false, 
 // 我用的only,打包后小些 
 runtimeCompiler: false, 
 productionSourceMap: true, // 不需要生产环境的设置false可以减小dist文件大小,加速构建  
 devServer: { 
  open: true, // npm run serve后自动打开页面 
  host: '0.0.0.0', // 匹配本机IP地址(默认是0.0.0.0) 
  port: 8080, // 开发服务器运行端口号 
  proxy: null, 
 }, 
} 

最全的

 module.exports = { 
   publicPath: "./",  // 公共路径(必须有的) 
   outputDir: "dist",     // 输出文件目录
   assetsDir: "assets",   // 静态资源存放的文件夹(相对于ouputDir) 
   lintOnSave:false,    // eslint-loader 是否在保存的时候检查(果断不用,这玩意儿我都没装)
   compiler: false,    // 我用的only,打包后小些  
   productionSourceMap: true, // 不需要生产环境的设置false可以减小dist文件大小,加速构建  
     // css: {   // css相关配置(我暂时没用到)
     // extract: true,   // 是否使用css分离插件 ExtractTextPlugin 
     // sourceMap: false, // 开启 CSS source maps? 
     // loaderOptions: {},  // css预设器配置项 
     // modules: false   // 启用 CSS modules for all css / pre-processor files. 
     // }, 
    devServer: {   // webpack-dev-server 相关配置
      open:true, // npm run serve后自动打开页面 
      host: '0.0.0.0', // 匹配本机IP地址(默认是0.0.0.0) 
      port: 8080, // 开发服务器运行端口号 
      proxy: null, 
      // 注:目前本项目暂时没有写后台接口,没有跨域问题,暂时不配置proxy 
    }, 
} 

4.生产环境

把dist目录复制到nginx下的html文件夹下面 , 更改conf文件夹下nginx.conf文件如下图

...   //省略代码
server{
	listen    8082;   //端口号
	server_name   localhost;   //ip地址
    root   D:/nginx-1.17.5/html/dist;  //打包生产文件夹位置
    index   index.html;   //打开html文件
    ...   ////省略代码
    location /api/{
        proxy_pass http://127.0.0.1:3000/;
        add_eader Content_Type "teXt/plain;charset = utf_8";
        add_header "Access_Control_Allow_Origin" '*';
        add_header "Access_Control_Allow_Credentials" 'true';
        add_header "Access_Control_Allow_Methods" 'GET','POST';
    }      //如果多个在此处继续写
   ...   //省略代码 
}

打包上线

命令行输入:npm run build

打包出来后项目中就会多了一个文件夹dist,这就是我们打包过后的项目。

第一个问题:

文件引用路径。我们直接运行打包后的文件夹中的index.html文件,会看到网页一片空白,f12调试,全是css,js路径引用错误的问题。

解决:到config文件夹中打开index.js文件。文件里面有两个assetsPublicPath属性,更改第二个,也就是更改build里面的assetsPublicPath属性:assetsPublicPath: './'

assetsPublicPath属性作用是指定编译发布的根目录,‘/'指的是项目的根目录 ,'./'指的是当前目录。

第二个问题:

router-view中的内容显示不出来。路由history模式。这个坑是当你使用了路由之后,在没有后端配合的情况下就手贱打开路由history模式的时候,打包出来的文件也会是一片空白的情况,

解决 : 在 router.js 中将 mode: history注释掉

背景图片引入问题:

img标签引入一般不会有问题,但通过css引入的背景图片或者js引入的就会出现路径问题

解决方法:

找到根目录下build文件中的utils.js文件,搜索ExtractTextPlugin.extract,并为其添加属性和值为 publicPath: '../../',如下图

if (options.extract) {
  return ExtractTextPlug4.extract({
    use:loaders
    publicPath:'../../',
    faliback: 'vue-style-loadec'
  })
 } 

百度地图点的图标(icon)使用本地图片

data(){
  return {   icon1:{url:require('../../ass/img/dw.png'),size:{width:300,heigth:157}}}
}

子父组件传值

父组件传值

 <pop v-if='pop' :father='src' @srcChange="handleSrcChange">
 <script>
	exprort default{
	 methods:{
	    handleSrcChange:function (e){  console.log(e)}//父组件自定义事件。参数e为子组件传递过来的参数
	 }
   }
</script>
//子组件接收
<template>
	{{father}}
	<div class="dsd" @click="baseHidden"></div>
</template>
<script>
    exprort default{
    	props:['father']
         methods:{// this.$emit("父组件自定义的事件名称","要传递的参数")子向父传递数据通过触发事件
            baseHidden:function (){this.$emit("srcChange""this.src") }
         }
    } 
</script>
//传参校验
props: {
    // 基础的类型检查 (`null` 匹配任何类型)
    propA: Number,
    // 多个可能的类型
    propB: [String, Number],
    // 必填的字符串
    propC: {
      type: String,
      required: true
    },
    // 带有默认值的数字
    propD: {
      type: Number,
      default: 100
    },
    // 带有默认值的对象
    propE: {
      type: Object,
      // 对象或数组默认值必须从一个工厂函数获取
      default: function () {   return { message: 'hello' }}
    },
    // 自定义验证函数
    propF: {
      validator: function (value) {
        // 这个值必须匹配下列字符串中的一个
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
  }

v-model

方法一

//父组件
<template>
    <aa class="abc" v-model="test" ></aa>  // 组件中使用v-mod
</template>
//子组件
<templat
      <button @click="fn2">{{'里面的值:'+ msg}}</button>
</template>
<script>
  export default {
    model: { 
      prop: 'msg',
      event: 'cc'
    },
    props: { msg: ''},
    methods: {fn2 () {this.$emit('cc', this.msg+2)}}
  }
</script>

方法二

子组件
<templat
      <button @click="fn2">{{'里面的值:'+ value}}</button>
</template>
<script>
  export default {
    props: {
      value: { default: '',},// 必须要使用value
    },
    methods: {
      fn2 () {
        // 这儿必须用input 发送数据,发送的数据会被父级v-model=“test”接受到,再被value=test传回来。
        this.$emit('input', this.value+2) 
      }
    }
  }

sync

父组件:
<template>
    <aa class="abc" :snycTest.sync="test" ></aa>
</template>
 
子组件:
<template>
      <button @click="fn2">{{'里面的值:'+ snycTest}}</button>
</template>
<script>
  export default {
    props: {snycTest: ''},
     //这儿是关键 update:snycTest
    methods: {fn2 () {this.$emit('update:snycTest', this.snycTest+1)  } } 
  } 
</script>

如果父组件传递图片路径,应以父组件的位置写

VueX使用

第一步:安装:npm install vuex --save

第二步:在src目录下创建一个store文件夹并在store文件夹下创建一个index.js文件

import Vuex from'vuex';
import Vue from 'vue';
 Vue.use(Vuex);
//创建仓库
 const store Vuex.Store({
   state: {   //数据
       test_data: “this is some test data”,
       color: “light-green”
    }, 
    mutations: {//修改值得方法
       setColor(state, coLor) {state.color= color;}
    }
    Action:{
       login({commit}, username) {
          return new Promise((resolve, reject) => {
             setTimeout(() => {
                if (username === 'admin') {
                   commit('setColor',res.data.data);resolve();//调用mutations中方法修改数据
                } else {reject()}
              }, 1000);
            })
          }
       },
    modules: {user}//模块化使用modules定义多个子模块利于组件复杂状态       
  }
export default store

第三步:main.js加载和挂载

import stort from  './store/index'
new Vue({
   el: ‘#app’, 
   router,
   store,
   components: { App }, 
  template: ‘<App!>’
})

第四步:使用

① 修改:

//第一个参数为mutations下的方法,二为参数
//不用加载任何vuex的文件main.js中已经全局加载了
this.$store.commit('setColor',res.data.data)

② 使用

this.$store.state.color

③Action派发

this.$store.dispatch('login', 'admin').then(() => {
    this.$router.push(this.$route.query.redirect)
}).catch(() => {
    alert('用户名或密码错误')
})

④模块化

user.js 定义

export default {
    namespaced: true, // 避免命名冲突
    // ...
}  

访问方式响应变化

// Login.vue
<button @click="login" v-if="!$store.state.user.color">登录</button>  //多上一个文件名$store.state.user.color和$store.state.color
this.$store.dispatch('user/login', 'admin').then(() => {
    const redirect = this.$route.query.redirect || '/'
    this.$router.push(redirect)
}).catch(() => {
	alert('用户名或密码错误')
})

路由携带参数

路由页面是设置

<router-link :to="{path:'/xiangx',query:{a:atm.a}}"></router-link>   //也可以写成to="/xiangx?a=1"

目标页设置

getImgUrl(){console.log(this.$route.query.a)}//获取参数的值

请求

进入项目,npm安装npm install axios --save

在main.js下引用axios

import axios from 'axios';  
Vue.prototype.$axios=axios;  

使用

this.$axios.post('/api/students',{    params: {  name:"admin", pass:123123},     //参数 
}).then(res => { console.log(res)      //请求成功后的处理数
}).catch(err => { console.log(err)     //请求失败后的处理数 
})  

扩展优化

优化方案 一

// api.js
 /* 登入页面获取公钥 */
export const getPublicKey = (data) => {
    return this.$axios.post({ url: '/userGateway/user/getPublicKey' }, data)
}

// 用户登录
export const login = data => {
    return this.$axios.post({ url: '/userGateway/userSentry/login' }, data)
}

// 验证码登录
export const loginByCode = data => {
    return this.$axios.post({ url: '/userGateway/userSentry/loginByCode' }, data)
}

在组件中使用接口:

<script>
import { getPublicKey } from './config/api.js'
export default {
    mounted() {
        getPublicKey().then(res => {
            // xxx
        }).catch(err => {
            // xxx
        })
    }
}
</script>

优化方案 二

/* 在根目录env配置各种前缀地址 */
const url = process.env.VUE_APP_URL || 'http://192.168.8.48:86'

const request = axios.create({
    baseURL: url,
    headers: {'Content-type': 'application/json'}
})
// request interceptor
request.interceptors.request.use(
    config => {
        const token = store.getters.token
        if(!config.url.includes("login")) {
            config.headers['saas-token'] = token
        }
        // console.log(config)
        return config
    }
)
// response interceptor
request.interceptors.response.use(
    response => {
        const res = response.data
        if (res.status == 1001 || res.status == -200) {
            router.push('/')
            Message({message: "Token过期,请重新登录!"})
        }
        return res
    },
    error => {
        return Promise.reject(error)
    }
)
export default request


//其他文件
import request from './index'

// post
export const login = postdata => {
    return request({
        url: "/community/user/login",
        method: 'post',
        data: postdata
    })
}
// get
export const getPublicDetail = postdata => {
    return request({
        url: "/community/notice/selectnoticeById",
        method: 'get',
        params: postdata
    })
}

深度优化

// api.js
const apiList = [
    '/userGateway/user/getPublicKey',  // 登入页面获取公钥
    '/userGateway/userSentry/login',  // 用户登录
    '/userGateway/userSentry/loginByCode',  // 验证码登录
]

let apiName, API = {}
apiList.forEach(path => {
    // 使用正则取到接口路径的最后一个子串,比如: getPublicKey
    apiName = /(?<=\/)[^/]+$/.exec(path)[0]
    API[apiName] = (data) => {
        return this.$axios.post({url: path}, data)
    }
})
export { API }

在组件中使用接口:

<script>
import { API } from './config/api.js'
export default {
    mounted() {
        API.getPublicKey().then(res => {
            // xxx
        }).catch(err => {
            // xxx
        })
    }
}
</script>

全局变量(例如请求地址的域名

(1)引用组件实现全局变量

新建js文件,将全局变量暴露出来,在需要用到全局变量的地方引入。

import common from "../commom.js; //引入js文件   
var serse=common.server 

(2)main.js文件挂载实现全局变量

​ 在main.js文件中如下设置

vue.prototype.server="https://www.imovit.com/superhero";//网站地址为全局变量  

在需要使用此全局变量的地方this.server就可以

Vue实例的生命周期函数和属性

(1)生命周期函数

创建期间的生命周期函数:

beforeCreate:实例刚在内存中被创建出来,创建出来的只是一个空的Vue实例,此时,还没有初始化好 data 和 methods 属性,data和methods都还不能使用

created:实例已经在内存中创建OK,此时 data 和 methods 已经创建OK,此时还没有开始 编译模板。也是最早的,只能在created中进行相关的操作

beforeMount:此时已经完成了模板的编译,但是还没有挂载到页面中,也就是说没有渲染到页面中

mounted:此时,已经将编译好的模板,挂载到了页面指定的容器中显示,已经将数据渲染到页面中了;当执行完mounted就表示,实例已经被完全创建好了,此时,如果没有其他操作的话,这个实例,就静静地躺在内存中,一动不动;如果要通过某些插件操作页面上的DOM节点,最早要在mounted中进行;只要执行完了mounted,就表示整个Vue实例已经初始化完毕了,此时,组件已经脱离了创建 阶段;进入到了运行阶段

运行期间的生命周期函数:

beforeUpdate:状态更新之前执行此函数, 此时 data 中的状态值是最新的,但是界面上显示的数据还是旧的,此时还没有开始重新渲染到DOM节点

updated:实例更新完毕之后调用此函数,此时 data 中的状态值和界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了!

销毁期间的生命周期函数:

beforeDestroy:实例销毁之前调用。当执行beforeDestory钩子函数的是时候,Vue实例就已经从运行阶段,进入到了销毁阶段;但是实例身上所有的data,methods,以及过滤器,指令等都处于可用状态,此时还没有真正的执行销毁过程,只是为销毁实例做准备

destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解除绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。组件就已经被完全销毁了,此时组件中所有的数据,方法,过滤器,指令等都已经不可用了

(2)实例属性

methods :事件方法执行 例:methods:{}

watch :监听一个值的变化,执行相应函数

computed :计算属性

filters :注册过滤器

components :组件挂载

directives :自定义指令

路由守卫

(1)设置和获取localStorage

localStorage.setItem('Authorization', "123"); 
localStorage.getItem('Authorization'); 

(2)设置路由守卫

import Vue from 'vue'  
import Router from 'vue-router'  
import HelloWorld from '@/components/HelloWorld'  	  
Vue.use(Router)   
 const router =new Router({  
  routes: [  
    {  
      path: '/',  
      name: 'HelloWorld',  
      component: HelloWorld  
    },{  
        path: '/home',  
        component: resolve => require(['../components/home.vue'], resolve),  
    },{  
      path: '/alarm',  
        component: resolve => require(['../components/alarm.vue'], resolve),  
    },{  
      path: '/baidu',  
        component: resolve => require(['../components/baidumap.vue'], resolve),  
    },{  
      path: '/',  
        component: resolve => require(['../components/HelloWorld.vue'], resolve),  
    }  
  ]  
});  
 router.beforeEach((to, from, next) => {  
    if (to.path === '/') {   next();  }
    else {  
        let token = localStorage.getItem('Authorization');  
        if (token === null || token === '') {  next('/');  } 
        else {  next(); }  
  }      
});  
export default router;

 this.$router 访问路由器, this.$route 访问当前路由

this.$router 有 push(), replace(), go() 等方法; this.$route有query获取参数对象

this.$router.push('home')  //字符串
this.$router.push({path:'home'})  //对象
this.$router.push({name:'user', params:{userId: '123'}})  //命名的路由
//带查询参数,变成 /register?plan=private
this.$router.push({path:'register', query:{plan:private}})

watch使用

在vue中watch是用来响应数据的,watch的写法有三种

①最简单最常用的

<input type="text" v-model="cityName"/>
new Vue({
	el:"#root",
	data:{cityName:'shanghai'},
	watch:{cityName(newName,oldName){ ....}}
})

② immediate和handler

监听的数据写成对象的形式,包含handler函数和immediate,immediate默认为flaset,第一次绑定时不会执行函数,值改变时才执行。

new Vue({
	el:"#root",
	data:{
		cityName:'shanghai'
	},
	watch:{
		cityName:{
			handler(newName,oldName){....},
                        immediate:true    //watch声明时就立即执行
		}
	}
})

③ deep 深度监听

<input type="text" v-model="cityName"/>

new Vue({
	el:"#root",
	data:{
		cityName:{id:1,name:'shanghai'}
	},
	watch:{
		cityName:{
			handler(newName,oldName){...},
                        immediate:true,    //watch声明时就立即执行
                        deep:true    //给cityName的所有属性都加上监听,任何一个变化都会执行handler函数
		}
	}
})

//如果只想监听对象中的一个属性
	watch:{
		'cityName.name':{
			handler(newName,oldName){  /*....*/},
                        immediate:true,    //watch声明时就立即执行
                        deep:true    //给cityName的所有属性都加上监听,任何一个变化都会执行handler函数
		}
	}

插槽

插槽就是子组件中的提供给父组件使用的一个占位符,用 表示,父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的标签。

①默认插槽

//子组件
<template>
    <div>
        <h1>今天天气状况:</h1>
        <slot></slot>  //子组件中挖坑
    </div>
</template>
//父组件
<template>
    <div>
        <child><div>多云</div></child>//父组件中填坑
    </div>
</template>

②具名插槽

//子组件
<template>
    <div>
        <div class="header"><slot name="header"></slot></div>//我是页头的具体内容
        <div class="defond"><slot></slot></div>  //我是默认内容
        <div class="footer"><slot name="footer"></slot></div>//我是页尾的具体内容
    </div>
</template>
//父组件
<template>
  <div>
    <child1>
        <template v-slot:header><p>我是页头的具体内容</p> </template>
        <template v-slot:footer> <p>我是页尾的具体内容</p></template>
	<template ><p>我是默认内容</p></template>
    </child1>
   </div>
</template>

③作用域插槽

//子组件
<template>
  <div class="two-bedroom">
    <div class="toilet">
      <!--通过v-bind 可以向外传递参数, 告诉外面卫生间可以放洗衣机-->
      <slot name="toilet" v-bind="{ washer: true }"></slot>
    </div>
  </div>
</template>
//父组件
<template>
  <two-bedroom>
    <template v-slot:toilet="scope">
      <!--判断是否可以放洗衣机-->
      <span v-if="scope.washer">这里放洗衣机</span>
    </template>
  </two-bedroom>
</template>

注意:

父级的填充内容如果指定到子组件的没有对应名字插槽,那么该内容不会被填充到默认插槽中。

如果子组件没有默认插槽,而父级的填充内容指定到默认插槽中,那么该内容就“不会”填充到子组件的任何一个插槽中。

如果子组件有多个默认插槽,而父组件所有指定到默认插槽的填充内容,将“” “全都”填充到子组件的每个默认插槽中。

<template>
  <div id="app"><hand></hand> </div>
</template>

<script>
import Hand from './components/HelloWorld.vue'
export default {
  name: 'App',
  data () {return {}},
  components: {   Hand},
  mounted(){ },
  methods: { }
}
</script>

<style scoped>
html,body,#app {
  width: 100%;
  height: 100%;
  margin: 0;
  background-color: #f6f6f6;
}
</style>

下载当前页面中的表格文件

//安装插件
npm install --save xlsx fill-saver
//需要导出的表格上添加id属性
<el-table id="out-table"></el-table>
//js中引入并使用
import FileSaver from 'file-saver'
import XLSX from 'xlsx'
methods:{
    exportExcel(){
        //通过表生产工作簿
        var wb =XLSX.utils.table_to_book(document.querySelector('#out-table'))
        //二进制文件
        var wbout = XLSX.write(wb,{bookType:'xlsx',bookSST:true,type:"array"})
        try{FileSaver.saveAs(new Blob([wbout],{type:'application/octet-stream'}),'导出文件名.xlsx')}catch(e){console.log(e)}
        return wbout;
    }