跨域解决
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;
}