解决开发环境Ajax跨域问题(使用代理服务器)
通过Vue脚手架配置代理
本案例需要下载axios库npm install axios
配置参考文档 Vue-Cli devServer.proxy
module.exports = {
devServer: {
proxy: 'http://localhost:4000' //端口号为被请求的服务器端口号
}
}
vue.config.js 是一个可选的配置文件,和 package.json 同级的
利用服务器之间访问不会有跨域,在服务器与浏览器端中间开启一个代理服务器,代理服务器端口号和项目浏览器端口号一样
1.1 配置代理服务器的方法一
在vue.config.js中添加如下配置:
Server.proxy 可以是一个指向开发环境 API 服务器的字符串,这会告诉开发服务器将任何未知请求 (没有匹配到静态文件的请求) 代理到http://localhost:5000。
devServer:{
proxy:"http://localhost:5000" //5000为被请求的服务器端口号
}
说明:
优点:配置简单,请求资源时直接发给前端(8080)即可。
缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
代理服务器不是把所有的请求都转发给被请求的服务器,当请求的资源,代理服务器自身就有时,就不会转发请求,脚手架中的public文件夹就相当于代理服务器的根路径,也就是代理服务器里面有什么内容,就看public文件夹中有什么
工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端静态资源)
App.vue
<template>
<div>
<button @click="getStudent">获取学生信息</button>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'App',
methods: {
getStudent() {
axios.get('http://localhost:8084/students').then(
response => {
console.log('请求成功了',response.data)
},
error => {
console.log('请求失败了', error.message)
}
)
}
},
}
</script>
1.2 配置代理服务器的方法二
编写vue.config.js配置具体代理规则:
module.exports ={
devServer:{
proxy:{
'/api1':{// 匹配所有以 '/api1'开头的请求路径
target:'http://localhost:5000',// 代理目标的基础路径
pathRewrite:{'^/api1':''}// 代理往后端服务器的请求去掉 /api1 前缀
changeOrigin:true, //用于控制请求头中的host值
ws:true,//用于支持websocket,默认值为true
},
'/demo':{// 匹配所有以 '/api2'开头的请求路径
target:'http://localhost:5001',// 代理目标的基础路径
pathRewrite:{'^/demo':''},
changeOrigin:true,
}
}
}
}
/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
changeOrigin默认值为true
*/
说明:
优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
缺点:配置略微繁琐,请求资源时必须加前缀。
src/App.vue
<template>
<div>
<button @click="getStudents">获取学生信息</button>
<button @click="getCars">获取汽车信息</button>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'App',
methods: {
getStudents() {
axios.get('http://localhost:8086/api/students').then(
response => {
console.log('请求成功了',response.data)
},
error => {
console.log('请求失败了', error.message)
}
)
},
getCars() {
axios.get('http://localhost:8086/demo/cars').then(
response => {
console.log('请求成功了',response.data)
},
error => {
console.log('请求失败了', error.message)
}
)
}
},
}
</script>
github 用户搜索案例
对于静态页面的中第三方成型的样式放在一个公共的位置(有两种做法):
在src文件夹下建立 assets文件夹(放置静态资源),在该文件夹下配置css文件夹,然后把第三方成型的样式文件放进去,还需要对其进行引入,可以在main.js中引入(不太推荐),也可以在App.vue中引入,如下:
<script>
import './assets/css/bootstrap.css'
export default {
name: 'App',
}
</script>
但此时会报错,因为在该案例的第三方样式中引入了字体样式'../fonts/glyphicons-halflings-regular.eot',但在我们的项目文件夹中并未此创建字体样式,所以会报错。其实本案例并不需要该字体,所以不推荐在assets文件夹中配置,因为写在assets问价夹中,就需要用import 引入,那就有严格的检查 。(bootstrap.css用到了不存在的资源,会报错)
在public文件夹中建立 css文件夹,然后在页面 index.html中进行引入
<!-- 开启移动端的理想视口 -->
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- 配置页签图标 -->
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<!-- 引入第三方样式 -->
<link rel="stylesheet" href="<%= BASE_URL %>css/bootstrap.css">
本案例分为 UserSearch组件 和 UserList组件
接口地址:https://api.github.com/search/users?q=xxx
完善案例:对于 UserList组件应该做到:
页面刚加载出来显示 welcome
页面在搜索的时候,显示 loading
搜索完毕后,显示用户头像及用户名
请求错误时,展示error
想实现如上功能,除了配置 users数据外,还应增添三个数据:
data() {
return {
isFirst: true,
isLoading: false,
errMsg: '',
users: []
}
}
案例代码:
App.vue
<template>
<div class="container">
<UserSearch/>
<UserList/>
</div>
</template>
<script>
import UserSearch from './components/UserSearch'
import UserList from './components/UserList'
export default {
name: 'App',
components: {
UserSearch,
UserList
}
}
</script>
UserSearch.vue:
<template>
<section class="jumbotron">
<h3 class="jumbotron-heading">Search Github Users</h3>
<div>
<input type="text" placeholder="enter the name you search" v-model="keyword"/>
<button @click="searchUsers">Search</button>
</div>
</section>
</template>
<script>
import axios from 'axios'
export default {
name: 'UserSearch',
data() {
return {
keyword: ''
}
},
methods: {
searchUsers() {
// 请求前更新List 数据
this.$bus.$emit('updateListData',{isFirst: false,isLoading: true, errMsg:'', users:[]})
axios.get(`https://api.github.com/search/users?q=${this.keyword}`).then(
response => {
console.log('请求成功了',);
// 请求成功后更新List 数据
// this.$bus.$emit('updateListData',response.data.items)
this.$bus.$emit('updateListData',{isLoading: false,errMsg:'',users:response.data.items})
},
error => {
// 请求失败后更新List 数据
console.log('请求失败了', error.message);
this.$bus.$emit('updateListData',{isLoading: false, errMsg:error.message, users:[]})
}
)
}
},
}
</script>
UserList.vue:
<template>
<div class="row">
<!-- 展示用户列表 -->
<div v-show="info.users.length" class="card" v-for="user in info.users" :key="user.login">
<a :href="user.html_url" target="_blank">
<img :src="user.avatar_url" style='width: 100px'/>
</a>
<p class="card-text">{{user.login}}</p>
</div>
<!-- 展示欢迎词 -->
<h1 v-show="info.isFirst">Welcome!</h1>
<!-- 展示加载中 -->
<h1 v-show="info.isLoading">Loading</h1>
<!-- 展示错误信息 -->
<h1 v-show="info.errMsg">{{info.errMsg}}</h1>
</div>
</template>
<script>
export default {
name: 'UserList',
data() {
return {
info: {
isFirst: true,
isLoading: false,
errMsg: '',
users: []
}
}
},
mounted() {
this.$bus.$on('updateListData', (dataObj)=>{
console.log('我是list组件,收到了数据', dataObj)
// this.isFirst = isFirst
// this.isLoading = isLoading
// this.errMsg = errMsg
// this.users = users
// this.info = dataObj
this.info = {...this.info, ...dataObj}
})
}
}
</script>
<style scoped>
.album {
min-height: 50rem; /* Can be removed; just added for demo purposes */
padding-top: 3rem;
padding-bottom: 3rem;
background-color: #f7f7f7;
}
.card {
float: left;
width: 33.333%;
padding: .75rem;
margin-bottom: 2rem;
border: 1px solid #efefef;
text-align: center;
}
.card > img {
margin-bottom: .75rem;
border-radius: 100px;
}
.card-text {
font-size: 85%;
}
</style>
3. vue项目中常用的 2个Ajax库(vue-resource)
axios
通用的 Ajax 请求库, 官方推荐,使用广泛
vue-resource
vue插件库,vue1.x使用广发,官方已不维护。vue-resource是对 xhr的封装。
使用:
安装插件库 npm i vue-resource
引入插件,使用插件 Vue.use(xxx)
main.js:
// 引入Vue
import Vue from 'vue'
// 引入 App
import App from './App'
// 引入插件
import vueResource from 'vue-resource'
// 关闭Vue的生产提示
Vue.config.productionTip = false
// 使用插件
Vue.use(vueResource)
new Vue({
el: '#root',
render: h => h(App),
// 安装全局事件总线
beforeCreate() {
Vue.prototype.$bus = this
}
})
然后在UserSearch组件库中 用this.$http 换掉 axios即可
使用插件后,vc上会多一个属性 $http,vue-resource插件库所提供的 $http 和 axios身上的Api都一模一样,所以this.$http替换axios即可
使用插件 Vue.use(xxx)
4. slot插槽(默认插槽、具名插槽、作用域插槽)
插槽是什么? 插槽就是子组件中的提供给父组件使用的一个占位符,用<slot></slot> 表示,父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的<slot></slot>标签。简单理解就是子组件中留下个“坑”,父组件可以使用指定内容来补“坑”。
1.作用:让父组件可以向子组件指定位置插入 html结构,也是一种组件间通信的方式,
适用于 父组件 ==> 子组件
2. 分类: 默认插槽、具名插槽、作用域插槽
3. 使用方式:
默认插槽:
父组件中:
<MyCatagory>
<div>html结构</div>
</MyCatagory>
子组件中:
<template>
<div>
<!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
<slot>插槽默认内容...我是一些默认值,当使用者没有传递具体的值时使用</slot>
</div>
</template>
注意:如果B组件的 template 中没有包含一个 <slot> 元素,即不使用插槽,则该组件起始标签和结束标签之间的任何内容都会被抛弃。
具名插槽
所谓具名插槽,顾名思义就是起了名字的插槽。有时我们需要多个插槽,例如当我们想使用某种通用模板:
对于这样的情况,<slot> 元素有一个特殊的 属性name。
一个不带 name 的 <slot> 出口会带有隐含的名字“default”。
在向具名插槽提供内容的时候,我们可以在标签上使用 slot属性 即slot="name属性值",同时也可以使用slot 指令,并以 slot 的参数的形式提供其名称即: v-slot: xxx形式,但此形式只能对 template标签使用
父组件中:
<MyCatagory>
<template slot="center">
<div>html结构</div>
</template>
<template v-slot:footer>
<div>html结构</div>
</template>
</MyCatagory>
子组件中:
<template>
<div>
<!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
<slot name="center">插槽默认内容...当使用者没有传递具体的值时使用</slot>
<slot name="footer">插槽默认内容...当使用者没有传递具体的值时使用</slot>
</div>
</template>
Tips:这种方式在项目中比较常用,可以当成一个复用(通用)模板组件。如多个组件的布局使用相似模板,只是具体内容不同,那么我们可以使用这种插槽方式封装成一个通用组件,在其他组件使用的时候只需要传对应的内容到对应名字的插槽即可,不需要将该模板在每个组件重新写一遍,减少代码冗余,大大提高开发效率。
作用域插槽
scope用于父组件往子组件插槽放的html结构接收子组件的数据,也可使用 slot-scope
为了让 子组件中的数据obj在父级的插槽内容中可用,我们可以将 obj作为 <slot> 元素的一个 属性 绑定上去
理解:数据在组件自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在MyCatagroy组件中,但使用数据所遍历的结构由App决定)
具体编码:
父组件中:
<MyCatagory>
<template scope="scopeData">
<!-- 生成的是ul列表-->
<ul>
<li v-for="(item,index) in scopeData.games" :key="index">{{item}}</li>
</ul>
</template>
</MyCatagory>
<MyCatagory>
<template slot-scope="{games}"> <!-- {games}是进行解构赋值-->
<!-- 生成的是h4 标题-->
<h4 v-for="(item,index) in games" :key="index">{{item}}</h4>
</template>
</MyCatagory>
子组件中:
<template>
<div>
<!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
<slot :games="games">插槽默认内容...当使用者没有传递具体的值时使用</slot>
</div>
</template>
<script>
export default {
name: 'MyCatagory',
props: ['title'],
//数据在子组件自身
data() {
return {
games: ['穿越火线','超级玛丽','wutuan','警戒'],
}
}
}
</script>