- 列表进入商品详情(常见的路由传参)
- 接口跨域问题
- axios封装和api接口统一管理
- 插槽可以添加事件吗?
持续更新中----------------------------------------------------------------------------------------------------------------------------------------
解决办法:
列表进入商品详情(路由传参)
路由传参有两种方式:query、params+动态路由传参。
例如:商品列表页进入商品详情页,需要传商品id
<router-link :to="{path: 'detail', query: {id: 1}}">前往detail页面</router-link>
c页面的路径为http://localhost:8080/detail?id=1,可以看到传了一个参数id=1,并且就算刷新页面id也还会存在。此时在c页面可以通过id来获取对应的详情数据,获取id的方式是this.$route.query.id
说一下两者的区别(query方式和params方式):
(1)query通过path切换路由,params通过name切换路由
// query通过path切换路由
<router-link :to="{path: 'Detail', query: { id: 1 }}">前往Detail页面</router-link>
//也可以通过点击事件js的方式跳转路由
this.$route.push("{path:'/detail',query:{id:1}}");
// params通过name切换路由
<router-link :to="{name: 'Detail', params: { id: 1 }}">前往Detail页面</router-link>
this.$route.push("{name:'/detail',params:{id:1}}");
(2)query通过this.route.params来接收参数。
// query通过this.$route.query接收参数
created () {
const id = this.$route.query.id;
}
// params通过this.$route.params来接收参数
created () {
const id = this.$route.params.id;
}
(3)query传参的url展现方式:/detail?id=1&user=123&identity=1&更多参数
params+动态路由的url方式:/detail/123
(4)params动态路由传参,需要在路由中定义参数,然后在路由跳转的时候必须要加上参数,否则就是空白页面:
{
path: '/detail/:id',
name: 'Detail',
component: Detail
},
注意,params传参时,如果没有在路由中定义参数,也是可以传过去的,同时也能接收到,但是一旦刷新页面,这个参数就不存在了。这对于需要依赖参数进行某些操作的行为是行不通的,因为你总不可能要求用户不能刷新页面吧。
例子:
在路由配置文件中:
// 定义的路由中,只定义一个id参数
{
path: 'detail/:id',
name: 'Detail',
components: Detail
}
在要跳转路由的组件中:
// template中的路由传参,
// 传了一个id参数和一个token参数
// id是在路由配置中已经定义的参数,而token没有定义
<router-link :to="{name: 'Detail', params: { id: 1, token: '123456' }}">前往Detail页面</router-link>
在详情页接收:
created () {
// 以下都可以正常获取到
// 但是页面刷新后,id依然可以获取,而token此时就不存在了
const id = this.$route.params.id;
const token = this.$route.params.token;
}
接口跨域问题
当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域

这是常见的请求接口跨域问题:,报错是说没有访问权限(跨域问题)。
本地开发项目请求服务器接口的时候,因为客户端的同源策略,导致了跨域的问题。
(1)下面先演示一个没有配置允许本地跨域的的情况:

这是请求:
this.axios.get("https://c.y.qq.com/v8/fcg-bin/fcg_v8_toplist_cp.fcg?g_tk=5381&uin=0&format=json&inCharset=utf-8&outCharset=utf-8%C2%ACice=0&platform=h5&needNewCode=1&tpl=3&page=detail&type=top&topid=27&_=1519963122923%20%E2%80%94%E2%80%94%E2%80%94%E2%80%94%E2%80%94%E2%80%94%E2%80%94%E2%80%94%E2%80%94%E2%80%94%E2%80%94%E2%80%94%E2%80%94%E2%80%94%E2%80%94%E2%80%94%20%E7%89%88%E6%9D%83%E5%A3%B0%E6%98%8E%EF%BC%9A%E6%9C%AC%E6%96%87%E4%B8%BACSDN%E5%8D%9A%E4%B8%BB%E3%80%8Cxiayiye5%E3%80%8D%E7%9A%84%E5%8E%9F%E5%88%9B%E6%96%87%E7%AB%A0%EF%BC%8C%E9%81%B5%E5%BE%AACC%204.0%20BY-SA%E7%89%88%E6%9D%83%E5%8D%8F%E8%AE%AE%EF%BC%8C%E8%BD%AC%E8%BD%BD%E8%AF%B7%E9%99%84%E4%B8%8A%E5%8E%9F%E6%96%87%E5%87%BA%E5%A4%84%E9%93%BE%E6%8E%A5%E5%8F%8A%E6%9C%AC%E5%A3%B0%E6%98%8E%E3%80%82%20%E5%8E%9F%E6%96%87%E9%93%BE%E6%8E%A5%EF%BC%9Ahttps://blog.csdn.net/xiayiye5/article/details/79487560").then(res=>{
console.log(res)
})
结果可想而知:显示跨域

(2)那么接下来我们演示设置允许跨域后的数据获取情况:
在项目根目录下创建文件vue.config.js:设置完成之后一定要重启项目,否则无效!!!!!!!!!!!!!!

请求的组件中:

接口请求接口是成功的:

要注意我们访问接口时,写的是/api,此处的/api指代的就是我们要请求的接口域名。如果我们不想每次接口都带上/api,可以更改axios的默认配置axios.defaults.baseURL = '/api';这样,我们请求接口就可以直接this.$axios.get('app.php?m=App&c=Index&a=index'),很简单有木有。此时如果你在network中查看xhr请求,你会发现显示的是localhost:8080/api的请求地址。这样没什么大惊小怪的,代理而已:
最后附上代理代码:
module.exports = {
devServer:{
//vue-cli开发时服务器的端口号
port:"8088",
//服务器运行完毕之后,是否自动打开浏览器
open:true,
//配置开发服务器的代理
proxy:{
// 这里用'/api'代替target里面的地址,组件中调用接口时直接用'/api'代替。比如我要调用'https://xxx.com/news.直接写'/api/news'即可
'/api':{
target:"https://c.y.qq.com",
changeOrigin:true,
//真正发起请求的时候,将触发代理的标识去掉,不让其成为路由的一部分,还是与后台服务一致 保持之前的路由 不用写api,已经变成空串了
pathRewrite:{
"^/api":"",
}
},
}
}
};
axios封装和api接口的统一管理
Axios 是一个基于 promise 的 HTTP 库,简单的讲就是可以发送get、post请求。说到get、post,大家应该第一时间想到的就是Jquery吧,毕竟前几年Jquery比较火的时候,大家都在用他。但是由于Vue、React等框架的出现,Jquery也不是那么吃香了。也正是Vue、React等框架的出现,促使了Axios轻量级库的出现,因为Vue等,不需要操作Dom,所以不需要引入Jquery.js了。
Axios请求的特性
- 可以在浏览器中发送 XMLHttpRequests
- 可以在 node.js 发送 http 请求
- 支持 Promise API
- 拦截请求和响应
- 转换请求数据和响应数据
- 能够取消请求
- 自动转换 JSON 数据
- 客户端支持保护安全免受 XSRF 攻击
在vue-cli的项目中,axios的封装,主要是用来帮我们进行请求拦截和相应拦截
在请求拦截中,可以携带token,post请求头以及qs对post请求提交的数据的序列化等
在相应拦截中,我们可以根据状态码来进行错误的统一处理等
axios接口的统一管理,是做项目时必须的流程。这样可以方便我们管理我们的接口,在接口更新时我们不必再返回到我们的业务代码中去修改接口。
一般我是在项目里面新建一个request文件夹:在里面存放http.js来封装axios请求以及接口处理api.js

直接放代码:
http.js:
/*这个文件用来封装axios*/
// 在http.js中引入axios
import axios from "axios"; // 引入axios
import QS from "qs"; // 引入qs模块,用来序列化post类型的数据,后面会提到
// vant的toast提示框组件,大家可根据自己的ui组件更改。
import { Toast } from "vant";
import router from "../router";
// 先导入vuex,因为我们要使用到里面的状态对象
// vuex的路径根据自己的路径去写
import store from "../store/index";
/*我们的项目有开发环境 测试环境 生产环境 通过node的前缀来匹配接口的前缀 axios.defaults.baseURL可以设置axios的默认请求地址就不多说了。*/
// 环境的切换
console.log(process.env.NODE_ENV);
if (process.env.NODE_ENV == "development") {
axios.defaults.baseURL = "/api";
} else if (process.env.NODE_ENV == "debug") {
axios.defaults.baseURL = "https://www.ceshi.com";
} else if (process.env.NODE_ENV == "production") {
axios.defaults.baseURL = "https://www.production.com";
}
axios.defaults.timeout = 10000;
//post请求的时候,我们需要加上一个请求头
// axios.defaults.headers.post["Content-Type"] =
// "application/x-www-form-urlencoded;charset=UTF-8";
axios.defaults.withCredentials=true;
/*我们在发送请求的时候需要发送一个请求拦截 为什么要拦截呢?
我们拦截请求是用来做什么的呢?比如,有些请求是需要用户登录之后才能访问的,或者post请求的时候,我们需要序列化我们提交的数据。这时候,我们可以在请求被发送之前进行一个拦截,从而进行我们想要的操作。
* */
// 请求拦截器
/*
这里的token是指用户登录成功之后,会将用户的token通过localstorage或者cookie存在本地,然后用户每次进入页面的时候main.js,会从本地取出token,如果存在token的话,说明用户已经登陆过,那么更新vuex里面的状态,在每次请求的header中带上token,后台通过判断token是否过期以及是否携带token,如果没有携带token说明没有登录过
*/
axios.interceptors.request.use(
(config) => {
// 每次发送请求之前判断vuex中是否存在token
// 如果存在,则统一在http请求的header都加上token,这样后台根据token判断你的登录情况
// 即使本地存在token,也有可能token是过期的,所以在响应拦截器中要对返回状态进行判断
const token = store.state.token;
token && (config.headers.Authorization = token);
return config;
},
(error) => {
return Promise.error(error);
}
);
//响应拦截器
/*
响应拦截器很好理解,就是服务器返回给我们的数据,我们在拿到之前可以对他进行一些处理。例如上面的思想:如果后台返回的状态码是200,则正常返回数据,否则的根据错误的状态码类型进行一些我们需要的错误,其实这里主要就是进行了错误的统一处理和没登录或登录过期后调整登录页的一个操作。
*/
axios.interceptors.response.use(
(response) => {
// 如果返回的状态码为200,说明接口请求成功,可以正常拿到数据
// 否则的话抛出错误
if (response.status === 200) {
return Promise.resolve(response);
} else {
return Promise.reject(response);
}
},
// 服务器状态码不是2开头的的情况
// 这里可以跟你们的后台开发人员协商好统一的错误状态码
// 然后根据返回的状态码进行一些操作,例如登录过期提示,错误提示等等
// 下面列举几个常见的操作,其他需求可自行扩展
(error) => {
if (error.response.status) {
switch (error.response.status) {
// 401: 未登录
// 未登录则跳转登录页面,并携带当前页面的路径 可以让用户后期登录之后返回当前页面
// 在登录成功后返回当前页面,这一步需要在登录页操作。
case 401:
router.push({
path: "/login",
query: {
redirect: router.currentRoute.fullPath,
},
});
break;
// 403 token过期
// 登录过期对用户进行提示
// 清除本地token和清空vuex中token对象
// 跳转登录页面
case 403:
Toast({
message: "登录过期,请重新登录",
duration: 1000,
forbidClick: true,
});
// 清除token
localStorage.removeItem("token");
store.commit("loginSuccess", null);
// 跳转登录页面,并将要浏览的页面fullPath传过去,登录成功后跳转需要访问的页面
setTimeout(() => {
router.replace({
path: "/login",
query: {
redirect: router.currentRoute.fullPath,
},
});
}, 1000);
break;
// 404请求不存在
case 404:
Toast({
message: "网络请求不存在",
duration: 1500,
forbidClick: true,
});
break;
// 其他错误,直接抛出错误提示
default:
Toast({
message: error.response.data.message,
duration: 1500,
forbidClick: true,
});
}
return Promise.reject(error.response);
}
}
);
/*下面是请求方式get和post的封装*/
/*
第一种:
get方法:我们通过定义一个get函数,get函数有两个参数,第一个参数表示我们要请求的url地址,第二个参数是我们要携带的请求参数。get函数返回一个promise对象,当axios其请求成功时resolve服务器返回 值,请求失败时reject错误值。最后通过export抛出get函数。
第二种:
post方法:原理同get基本一样,但是要注意的是,post方法必须要使用对提交从参数对象进行序列化的操作,所以这里我们通过node的qs模块来序列化我们的参数。这个很重要,如果没有序列化操作,后台是拿不到你提交的数据的。
*/
/**
* get方法,对应get请求
* @param {String} url [请求的url地址]
* @param {Object} params [请求时携带的参数]
*/
export function get(url, params) {
return new Promise((resolve, reject) => {
axios
.get(url, {
params: params,
},{withCredentials: true})
.then((res) => {
resolve(res.data);
})
.catch((err) => {
reject(err.data);
});
});
}
/**
* post方法,对应post请求
* @param {String} url [请求的url地址]
* @param {Object} params [请求时携带的参数]
*/
export function post(url, params) {
return new Promise((resolve, reject) => {
axios
.post(url, QS.stringify(params),{withCredentials: true})
.then((res) => {
resolve(res.data);
})
.catch((err) => {
reject(err.data);
});
});
}
api.js:
/*用来统一管理我们的接口*/
/*
api接口管理的一个好处就是,我们把api统一集中起来,如果后期需要修改接口,我们就直接在api.js中找到对应的修改就好了,而不用去每一个页面查找我们的接口然后再修改会很麻烦。关键是,万一修改的量比较大,就规格gg了。还有就是如果直接在我们的业务代码修改接口,一不小心还容易动到我们的业务代码造成不必要的麻烦。
*/
import { get, post } from "./http";
/*
测试接口:
get请求
https://github.com/bailicangdu/node-elm/blob/master/API.md
https://elm.cangdu.org/v1/cities?type=guess(get请求 定位当前城市)
https://elm.cangdu.org/v1/cities/1 //获取所选城市 get请求 城市id是需要传的参数
https://elm.cangdu.org/v2/index_entry //食品分类列表 get
post请求:
https://elm.cangdu.org/v1/captchas 获取验证码
https://elm.cangdu.org/shopping/addcategory
*/
//获取当前定位城市(type=guess),热门城市(type=hot),城市列表(type="group")
export const getAddress = (p) => get("/v1/cities?type=" + p.type, "");
//get请求
/*
* +p+'/orders?limit=10&offset=0*/
export const getOrderList = (p) =>
get("/bos/v2/users/" + p.user_id + "/orders", p);
// 我们定义了一个apiAddress方法,这个方法有一个参数p,p是我们请求接口时携带的参数对象。而后调用了我们封装的post方法,post方法的第一个参数是我们的接口地址,第二个参数是apiAddress的p参数,即请求接口时携带的参数对象。最后通过export导出apiAddress
//封装一个post请求
export const addRes = (p) => post( "/shopping/addcategory", p);
// 用户登录
export const userLogin = (p)=>post('/v2/login',p);
// 图片验证码
export const captchasCode = ()=>post('/v1/captchas');
在组件发起请求里面直接调用api.js里面的方法就可以了:
例如:

插槽可以添加事件吗?

此处点击登录按钮(Login.vue)的时候,将后台返回的错误提示需要弹窗出来,这里的弹窗封装为一个组件,错误提示为插槽,错误提示为插槽内容
实现方式:将弹窗封装为一个组件(Alert.vue),错误提示为一个插槽(name为alert)
Alert.vue:
<!-- -->
<template>
<div class="alet_container">
<section class="tip_text_container">
<div class="tip_icon">
<span></span>
<span></span>
</div>
<p class="tip_text">
<slot name='alert'></slot>
</p>
<!-- 调用父级的关闭弹窗 -->
<p class="confrim" @click="$parent.confirmClose()">
确定
</p>
</section>
</div>
</template>
<script>
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';
export default {
//import引入的组件需要注入到对象中才能使用
components: {},
data() {
//这里存放数据
return {
};
},
//监听属性 类似于data概念
computed: {},
//监控data中的数据变化
watch: {},
//方法集合
methods: {
confirmClose(){
console.log("关闭")
this.alertShow = false
}
},
//生命周期 - 创建完成(可以访问当前this实例)
created() {
},
//生命周期 - 挂载完成(可以访问DOM元素)
mounted() {
},
beforeCreate() {}, //生命周期 - 创建之前
beforeMount() {}, //生命周期 - 挂载之前
beforeUpdate() {}, //生命周期 - 更新之前
updated() {}, //生命周期 - 更新之后
beforeDestroy() {}, //生命周期 - 销毁之前
destroyed() {}, //生命周期 - 销毁完成
activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
}
</script>
<style scoped>
.alet_container{
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 200;
}
.tip_text_container{
position: absolute;
top: 50%;
left: 50%;
margin-top: -7rem;
margin-left: -7rem;
width: 14rem;
animation: tipMove .4s;
background-color: #fff;
padding-top: 1rem;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
border: 1px;
border-radius: .25rem;
}
@keyframes tipMove{
0% {
transform: scale(1);
}
35% {
transform: scale(.8);
}
70% {
transform: scale(1.1);
}
100% {
transform: scale(1);
}
}
.tip_text_container .tip_icon{
width: 3rem;
height: 3rem;
border: .15rem solid #f8cb86;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.tip_text_container .tip_icon span:first-of-type {
width: .12rem;
height: 1.5rem;
background-color: #f8cb86;
}
.tip_text_container .tip_icon span:nth-of-type(2) {
width: .2rem;
height: .2rem;
border: 1px;
border-radius: 50%;
margin-top: .2rem;
background-color: #f8cb86;
}
.tip_text_container .tip_text {
font-size: 1rem;
color: #333;
line-height: .9rem;
text-align: center;
margin-top: .8rem;
padding: 0 .4rem;
}
.tip_text_container .confrim {
font-size: 1.1rem;
color: #fff;
font-weight: 700;
margin-top: .8rem;
background-color: #4cd964;
width: 100%;
text-align: center;
line-height: 2rem;
border: 1px;
border-bottom-left-radius: .25rem;
border-bottom-right-radius: .25rem;
z-index:203;
}
</style>

Login.vue:
<!--登录组件-->
<template>
<div class="login-container">
<!-- 引入公共头部 -->
<common-header>
<template v-slot:login>
{{ pageTitle }}
</template>
</common-header>
<!-- 账号密码输入框 -->
<div class="login-form">
<div class="input-field">
<input type="text" placeholder="账号:" v-model="username" />
</div>
<div class="input-field">
<input type="password" placeholder="密码:" v-model="password" />
</div>
<div class="input-field flex">
<input type="text" placeholder="验证码:" v-model="verify_code" />
<div class="img_change_img flex">
<img :src="captcha_code_src" alt="">
<div class="change_img">
<p>看不清</p>
<p class="txt_blue" @click="getCapture">换一张</p>
</div>
</div>
</div>
<!-- 登录按钮 -->
<div class="login_container" @click="toLogin()">登录</div>
</div>
<!-- 弹窗 -->
<alert v-show="alertShow">
<template v-slot:alert>
{{ alertTitle }}
</template>
</alert>
</div>
</template>
<script>
// var _ = require('lodash');
import _ from "lodash";
import CommonHeader from "../components/commonHeader.vue";
// 导入用户登录接口
import {userLogin} from '../request/api';
// 图片验证码
import {captchasCode} from '../request/api';
import Alert from '../components/alert.vue';
import { mapMutations } from 'vuex';
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';
export default {
//import引入的组件需要注入到对象中才能使用
components: { CommonHeader, Alert },
data() {
//这里存放数据
return {
pageTitle: "密码登录",
username: "",
password: "",
verify_code: "",
captcha_code_src:"",
alertShow:false,//是否显示弹窗
alertTitle:'',//弹窗文本
};
},
//监听属性 类似于data概念
computed: {},
//监控data中的数据变化
watch: {},
//方法集合
methods: {
...mapMutations([
'loginTrue'
]),
init() {
let testArr = [123, 343, 213, 121, 31, 12, 243, 23];
let arr1 = _.chunk(testArr, 2);
console.log(arr1, testArr);
let maxValue = _.max(testArr);
console.log(maxValue);
console.log(_.endsWith('abc', 'b', 2));
},
// 登录
toLogin(){
userLogin({
username : this.username,
password : this.password,
captcha_code : this.verify_code,
}).then(res=>{
if(res.status == 0){
this.alertTitle = res.message;
this.alertShow = true;
}else{
// 调用mutations的修改用户登录状态的方法 修改用户登录状态为true
this.loginTrue();
console.log(this.$store.state.userLogin)
console.log(res);
}
}).catch(error=>{
console.log(error);
})
},
// 点击关闭错误弹窗
confirmClose(){
this.alertShow = false;
// 再次刷新验证码
this.getCapture();
},
// 获取图片验证码
getCapture(){
captchasCode().then(res=>{
if(res.status==1){
this.captcha_code_src = res.code;
}
}).catch(error=>{
console.log(error);
})
}
},
//生命周期 - 创建完成(可以访问当前this实例)
created() {
console.log(this.$store.state.userLogin)
// this.init();
// 获取图片验证码
this.getCapture();
},
//生命周期 - 挂载完成(可以访问DOM元素)
mounted() {},
beforeCreate() {}, //生命周期 - 创建之前
beforeMount() {}, //生命周期 - 挂载之前
beforeUpdate() {}, //生命周期 - 更新之前
updated() {}, //生命周期 - 更新之后
beforeDestroy() {}, //生命周期 - 销毁之前
destroyed() {}, //生命周期 - 销毁完成
activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style scoped>
.login-container {
padding-top: 2.85rem;
}
.login-form {
margin-top: 0.875rem;
}
.van-cell {
font-size: 1rem !important;
}
.input-field{
background-color: #fff;
padding: .6rem;
border-bottom: .063rem solid #f1f1f1;
}
.login-form .flex{
display: flex;
justify-content: space-between;
}
.login-form .flex input{
width: 13rem!important;
}
.input-field input {
/* width: 100%; */
border: none;
outline: none;
padding: 0.3rem 0.6rem;
}
.img_change_img img{
width: 5rem;
}
.login_container{
text-align: center;
color: #fff;
background-color: #4cd964;
border-radius: .2rem;
width: 22rem;
height: 2.5rem;
margin: 1rem auto;
line-height: 2.5rem;
}
</style>
当用户点击确定,需要关闭弹窗的(经测试我将关闭弹窗事件加在插槽上,弹窗并不能关闭),因此需要在调用组件的父组件中定义关闭弹窗的方法,子组件通过调用父组件的方法来达到关闭弹窗的效果。


网友评论