安装:npm i mockjs
。
在src/mock/index.js
内容如下:
import Mock from 'mockjs'
//制订拦截规则
Mock.mock('http://www.0313.com','get','你好啊')
记得在main.js
中引入一下,让其参与整个项目的运行。
只要发出去的是get
类型的ajax
请求,地址只要是http://www.0313.com
就能拦截了。
备注:使用mockjs
后,浏览器的网络选项卡中一定看不到被拦截的请求。
去Test
组件中发送一个请求测试一下。
async handleTest(){
const result = await axios.get('http://www.0313.com')
console.log(result)
}
问题描述:从undefined
上读取属性会报错,代码演示如下:
//例如vuex中数据格式如下:
const state = {
a:[],
x:{}
}
<h1>{{a[0].id}}h1>
<h1>{{x.y.z}}h1>
如何解决?
第一种方法:在最初就设计好数据层次 —— 不推荐。
const state = {
a:[{}],
x:{y:{}}
}
第二种方法:使用数据时加判断。
<h1 v-if="a[0]">{{a[0].id}}h1>
<h1 v-if="x.y">{{x.y.z}}h1>
第三种方法:使用问号 —— 最推荐。
<h1>{{a[0]?.id}}h1>
<h1>{{x.y?.z}}h1>
第四种方法:若模板中恰巧使用了v-for
遍历,那么问题自动消失。
<h1 v-for="s in carouselList" :key="s.id">{{s.imgUrl}}h1>
第五种方法:使用&&
短路
<h1>{{a[0] && a[0].id}}h1>
<h1>{{x.y && x.y.z}}h1>
Swiper
是专门做轮播图的库,在:原生项目、Vue
项目、React
项目中,均可使用。
在Vue
项目中,可以使用vue-awesome-swiper
来更方便的实现轮播图。
安装:npm i [email protected]
。
在Test
组件中实现一个简单的轮播
<template>
<swiper class="swiper" :options="swiperOption">
<swiper-slide class="item">Slide 1</swiper-slide>
<swiper-slide class="item">Slide 2</swiper-slide>
<swiper-slide class="item">Slide 3</swiper-slide>
<swiper-slide class="item">Slide 4</swiper-slide>
<div class="swiper-pagination" slot="pagination"></div>
<div class="swiper-button-prev" slot="button-prev"></div>
<div class="swiper-button-next" slot="button-next"></div>
</swiper>
</template>
<script>
import {Swiper, SwiperSlide} from 'vue-awesome-swiper'
import 'swiper/css/swiper.css'
export default {
name: 'Test',
components: {Swiper,SwiperSlide},
data() {
return {
//轮播图配置对象
swiperOption: {
slidesPerView: 1, //同时展示几屏
spaceBetween: 30, //每屏间隔
loop: true, //是否循环轮播
speed: 1000, //切换速度
//自动轮播
autoplay: {
delay: 2000,//轮播间隔
disableOnInteraction: false //鼠标点击后,是否禁止自动轮播
},
//分页器配置(小圆点)
pagination: {
el: '.swiper-pagination', //分页器元素
clickable: true //小圆点是否可以点击
},
//导航按钮(左箭头、右箭头)
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev'
}
}
}
}
}
</script>
<style lang="less" scoped>
.item {
line-height: 200px;
text-align: center;
font-size: 40px;
background-image: linear-gradient(45deg,red,yellow,green);
height: 200px;
}
</style>
目标:无论是点击分类,还是点击搜索,Search
组件都需要接收路由参数。
使用watch
监听$route
watch:{
$route:{
//立即监视,目的是为了让第一次搜索的时候,可以拿到参数。
immediate:true,
//此处的value是谁? —— 是$route的新值(监视的是谁,handler得到的就是谁的新值)
handler(value){
console.log(value.query)
}
}
}
具体思路:使用watch
监听$route
,只要当前路由信息变化,就存储搜索参数。
watch:{
$route:{
//追加立即监视,为了初次挂载拿到参数
immediate:true,
//注意此处的query是谁?—— 路由传递过来的query参数
handler({query}){
//重置之前的一级id、二级id、三级id,随后将最新的参数合并进去
Object.assign(this.searchParams,{
category1Id:'', //重置一级分类id
category2Id:'', //重置二级分类id
category3Id:'', //重置三级分类id
},query)
}
}
}
3
、5
、7
等,为什么?—— 好看,通常是5
。total/pageSize
(注意要向上取整)备注:红色是必须传项,绿色的不用传,可以根据红色算出来。
连续页数continues > totalPage
具体编码:
export default {
name: "Pagination",
props:['total','pageSize','pageNo','continues'],
computed:{
// 计算总页数
totalPage(){
return Math.ceil(this.total / this.pageSize)
},
// 计算连续页的起始页、结束页
startEnd(){
// 获取外部传入的各种参数
const {continues,pageNo,totalPage} = this
let start = 0 //起始页初始值
let end = 0 //结束页初始值
// 判断一下目前的情况是不是一个“大变态”
//(你要求的连续页数,比我倾家荡产的总页数都要多,推都不推,都给你)
if(continues > totalPage){
start = 1
end = totalPage
}else{
// 标准情况
start = pageNo - (continues - 1)/2
end = pageNo + (continues - 1)/2
}
return {start,end}
}
}
};
算连续页start
的时候,往左推,推多了,出现 start < 1
了。
处理方式如下:
export default {
name: "Pagination",
props:['total','pageSize','pageNo','continues'],
computed:{
// 计算总页数
totalPage(){
return Math.ceil(this.total / this.pageSize)
},
// 计算连续页的起始页、结束页
startEnd(){
// 获取外部传入的各种参数
const {continues,pageNo,totalPage} = this
let start = 0 //起始页初始值
let end = 0 //结束页初始值
// 判断一下目前的情况是不是一个“大变态”
//(你要求的连续页数,比我倾家荡产的总页数都要多,推都不推,都给你)
if(continues > totalPage){
start = 1
end = totalPage
}else{
// 标准情况(正常左推,右推)
start = pageNo - (continues - 1)/2
end = pageNo + (continues - 1)/2
// 判断左边是否“冒了”(start是否小于1)
if(start < 1){
start = 1
end = continues
}
}
return {start,end}
}
}
};
往右推,算结束页码的时候,推多了,出现end > totalPage
了。
处理方式如下:
export default {
name: "Pagination",
props:['total','pageSize','pageNo','continues'],
computed:{
// 计算总页数
totalPage(){
return Math.ceil(this.total / this.pageSize)
},
// 计算连续页的起始页、结束页
startEnd(){
// 获取外部传入的各种参数
const {continues,pageNo,totalPage} = this
let start = 0 //起始页初始值
let end = 0 //结束页初始值
// 判断一下目前的情况是不是一个“大变态”
//(你要求的连续页数,比我倾家荡产的总页数都要多,推都不推,都给你)
if(continues > totalPage){
start = 1
end = totalPage
}else{
// 标准情况(正常左推,右推)
start = pageNo - (continues - 1)/2
end = pageNo + (continues - 1)/2
// 判断左边是否“冒了”(start是否小于1)
if(start < 1){
start = 1
end = continues
}
// 判断右边是否“冒了”(end是否大于totalPage)
if(end > totalPage){
start = totalPage - continues + 1
end = totalPage
}
}
return {start,end}
}
}
};
分页器整体的显示,逻辑如下:
···
···
共 {{total}} 条
第一步:对接真实数据,把:total
、pageNo
、pageSize
,传给Pagination
组件。
注意1:
total
是服务器返回给我们的,要去searchResInfo
中拿。注意2:
pageNo
、pageSize
是我们传给服务器的,要去searchParams
中拿。注意3:
continues
是连续页数,是我们自己指定的。
第二步:传递页码,Pagination
组件将用户点击的页码,传给Search
组件,Search
组件接收参数,随后重新请求数据。
Saecrh
组件中:
Pagination
组件中:
思路:鼠标动,遮罩动,大图区域跟着动。
具体代码:
详情组件,给放大镜组件传递图片url
<Zoom :imgurl="info.skuInfo.skuDefaultImg"/>
放大镜组件:
<template>
<div class="spec-preview">
<img :src="imgurl" />
<div class="event" @mousemove="move"></div>
<div class="big">
<img :src="imgurl" ref="bigImg"/>
</div>
<div class="mask" ref="mask"></div>
</div>
</template>
<script>
export default {
name: "Zoom",
props:['imgurl'],
methods: {
move(event){
// 获取鼠标位置
let mouseX = event.offsetX
let mouseY = event.offsetY
// 获取遮罩DOM元素
let {mask,bigImg} = this.$refs
// 计算出遮罩位置
let maskX = mouseX - mask.offsetWidth/2
let maskY = mouseY - mask.offsetHeight/2
// 限制遮罩不要超出父容器
if(maskX < 0){
maskX = 0
}else if(maskX > mask.offsetWidth){
maskX = mask.offsetWidth
}
if(maskY < 0){
maskY = 0
}else if(maskY > mask.offsetHeight){
maskY = mask.offsetHeight
}
// 给遮罩应用位置
mask.style.left = maskX + 'px'
mask.style.top = maskY + 'px'
// 给大图应用位置
bigImg.style.left = -2*maskX + 'px'
bigImg.style.top = -2*maskY + 'px'
}
},
}
</script>
用第三方库实现:
vue-photo-zoom-pro
购买商品时,输入的最大购买数量,有两种选择:200
或 最大库存量
,大部分电商的输入限制是200
1
到200
的正整数。1-200
之间的小数,则取其整数部分。1
,则重置为1
。200
,则重置为200
。1
。明确几个点:
每个用户都有自己的购物车,不可能大家共用一个。
如何区分不同的用户?(服务器如何知道你是谁?)
办法一:登录系统 (最正式的做法)
办法二:使用临时标识(其实就是:随机生成的一个
UUID
,不能真正下单,只是为了临时支撑购物车)。
为什么有的网站,不登录也有购物车?
尽可能引导用户消费,或者说增强用户体验。
为什么有的网站,必须登录才有购物车,甚至必须登录才能搜索?
①用户量大(不愁用户)②网站业务逻辑复杂,登录以后有些逻辑好处理。
使用uuidjs
生成,安装uuid:npm i uuid
。
为确保生成的userTempId
不丢失,需要存到localStorage
中。
/src/utils/auth.js
中创建一个getUserTempId
的方法,用于提供userTempId
。
整体思路为:
localStorage
中有,就返回。localStorage
中,再返回。/*
getUserTempId的功能是:
方法会从localStorage中读取用户临时标识,
1.如果有就返回,随后就用。
2.如果没有
第一件事:生成一个新的临时标识,
第二件事:存入localStorage —— 为了以后使用
第三件事:返回 —— 为了这次使用
*/
import {v4} from 'uuid';
export function getUserTempId(){
// 尝试从localStorage中读取临时标识
const userTempId = localStorage.getItem('userTempId')
// 判断标识是否存在
if(userTempId){
// 返回之前存入的标识
return userTempId
}else {
// 生成一个新的临时标识
const newUserTempId = v4()
// 存入localStorage
localStorage.setItem('userTempId',newUserTempId)
// 返回
return newUserTempId
}
}
编辑/src/api/myAxios.js
,在axios
请求拦截器中,让所有请求头,都携带userTempId
。
import {getUserTempId} from '@/utils/auth'
//请求拦截器
myAxios.interceptors.request.use((config)=>{
//进度条走起
nprogress.start()
//向请求头中添加userTempId
config.headers.userTempId = getUserTempId()
//返回本次请求的配置信息
return config
})
当我们勾选一个商品时,先阻止掉默认行为,即:不让checkbox
立刻勾选上。因为这样checkbox
的勾选才是完全由服务器决定,操作很简单:加上一个prevent
修饰符。
<input
type="checkbox" name="chk_list"
:checked="cartInfo.isChecked"
@click.prevent="checkOne(cartInfo)"
>
标准概念:事件触发n
秒后再执行逻辑,若这n
秒内事件又被触发,则重新计时n
秒,之前的逻辑不执行。
通俗理解:要做的事,总是改来改去,那么就等你下发指令后n
秒,我再做,免得你再改。
目的:防止复杂逻辑频繁触发(例如:复杂的运算、网络请求等)。
生活中的例子:调节空调温度时,按下【+】或【-】,等1秒空调才有反应。
最简洁的记法:就要最后那一下。
最具代表性的应用场景:实时搜索,但要注意:像百度这种专业做搜索的网站,为了用户的丝滑体验,没有做防抖,但像boss直聘
这种主营业务不是搜索,但有搜索功能的网站,都会做防抖。
例子如下:
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>1.函数防抖title>
<script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.min.js">script>
head>
<body>
<input type="text" placeholder="请输入关键词" id="input">
<script type="text/javascript" >
const input = document.getElementById('input')
// 靠程序员自己去用定时器实现防抖
/* let timer
input.addEventListener('keyup',(e)=>{
if(timer){
clearTimeout(timer)
}
timer = setTimeout(()=>{
console.log('发请求了',e.target.value)
},300)
}) */
// 靠lodash去实现防抖
input.addEventListener('keyup',_.debounce((e)=>{
console.log('发请求了',e.target.value)
},300))
script>
body>
html>
标准概念:在n
秒内,无论触发事件多少次,逻辑只执行一次。
通俗理解:你催的再急,也没用,我的速度是有极限的。
目的:在一定的时间内,防止复杂逻辑频繁触发(例如:复杂的运算、网络请求等)。
生活中的例子:
3
秒内也只能吃一口面。通俗理解:别催,催也没有用,我就是这个速度。
最具代表性的应用场景:秒杀按钮。
关于lodash
节流的配置
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>2.函数节流title>
<script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.min.js">script>
head>
<body>
<button id="btn">立即购买button>
<script type="text/javascript" >
const btn = document.getElementById('btn')
let canExecute = true //用于标识是否可以执行逻辑
/* 靠程序员,用定时器去实现节流,不推荐 */
/* btn.onclick = ()=>{
if(canExecute){
canExecute = false
console.log('发起网络请求~~')
setTimeout(()=>canExecute = true,2000)
}
} */
btn.onclick = _.throttle(()=>{
console.log('发起网络请求~~')
},2000,{trailing:false})
script>
body>
html>
第一步:安装,命令为 npm i element-ui
第二步:安装一个包,npm install babel-plugin-component -D
第三步:修改babel.config.js
内容如下:
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
plugins: [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
第三步:去main.js
中引入并注册,你想用的组件 或 使用插件
import {Button} from 'element-ui';
// 第一种方式:全局注册按钮组件
// Vue.component(Button.name, Button);
// 第二种方式:使用Button插件
Vue.use(Button)
第四步:随便在任何一个组件里,去使用上一步注册的组件即可
在main.js
中引入所有提示相关组件,并放在Vue原型上。
import {Message} from 'element-ui';
Vue.prototype.$message = MessageBox;
所有用到提示的地方,直接复制官网上的代码即可,例如:
this.$message.info('一些消息');
this.$message.success('一些成功消息');
this.$message.warning('一些警告消息');
this.$message.error('一些错误消息');
this.$message({
type:'success',
message:'恭喜支付成功!',
showClose:true,
duration:1000,
})
只有登录成功,服务器才会为该用户生成一个token
。
token
是用户的真正标识,根据token
,可以去服务器那查询到该用户的所有信息。
浏览器端需要保存一份token
,哪里保存? —— localStorage
中。
为什么? —— 为了做自动登录(即:刷新页面、重开浏览器,依然是登录的状态)
我们的服务器在登录成功后,会返回:用户昵称、用户真实姓名、token
等等,但我们只存token
。
为什么? —— 根据
token
可以找服务器获取到更多的用户信息。大多数情况,登录成功后,服务器不会给任何多余的信息,只给一个
token
。
若服务器不允许多端登录,则服务器要在用户登录后,把之前的token
销毁,再生成一个新的token
。
这个得看具体需求,目前我们的尚品汇服务器允许多端登录。
token
不是永久有效的,通常都有一个有效期,短的2
小时,长的30
天,具体时间看项目需求。
服务器那边什么情况需要销毁token
?—— 以下几个场景:
- 到了
token
的过期时间,服务器销毁了token
。- 若不允许多端登录,用户在其他终端登录了系统,服务器销毁了之前的
token
。- 若允许多端登录,但达到了上限,服务器会销毁最开始的
token
。- 用户触发退出登录,服务器会销毁该用户的
token
。
明确:登录成功后,token
要存入localStorage
中。
编写src/utils/auth.js
追加三个方法,如下:
//用于将token存入localStorage
export function saveToken(token){
localStorage.setItem('token',token)
}
//用于从localStorage中读取token
export function readToken(){
return localStorage.getItem('token')
}
//从localStoragre删除用户token
export function deleteToken(){
localStorage.removeItem('token')
}
Login
组件中,登录成功后,将token
存入localStorage
中
methods: {
async login() {
// 获取登录需要的参数
const { phone, password } = this;
// 请求登录
let { code, data, message } = await reqLogin({ phone, password });
if (code === 200) {
// 登录成功给个提示
this.$message.success("登录成功!");
//token存入本地
saveToken(data.token)
// 跳转到主页
this.$router.push("/home");
} else {
// 登录失败提示原因
this.$message.warning(message);
}
},
},
修改src/api/ajax.js
每次请求,都要携带token
import {getUserTempId,readToken} from '@/utils/auth'
//请求拦截器
request.interceptors.request.use((config)=>{
/*****/
// 携带token
config.headers.token = readToken()
/*****/
return config
})
明确:根据token
获取到的用户信息,存在vuex
中,方便其他组件使用。
编写接口请求函数,根据token获取用户信息。
// 此函数专门用于获取用户信息
export const reqUserInfo = () => request.get('/api/user/passport/auth/getUserInfo')
操作文件:src/store/modules/user.js
,编写vuex
代码,存储用户信息。
import {reqUserInfo} from '@/api'
import {Message} from 'element-ui'
const actions = {
async getUserInfo({commit}){
//发送请求获取用户信息
const {code,message,data} = await reqUserInfo()
//判断是否获取成功
if(code === 200){
commit('SAVE_USER_INFO',data)
}else{
Message.error(message)
}
}
}
const mutations = {
// 保存用户数据
SAVE_USER_INFO(state,info){
state.info = info
}
}
const state = {
info:{} //初始化用户信息
}
export default {
namespaced:true,
actions,
mutations,
state
}
在Login
组件中,登录成功后,dispatch
一个getUserInfo
获取用户信息。
async login(){
// 获取用户的输入
const {phone,password} = this
// 请求登录
const result = await reqLogin({phone,password})
// 判断登录是否成功
if(result.code === 200){
// 第一步:提示信息
this.$message.success('登录成功')
// 第二步:存储token
saveToken(result.data.token)
// 第三步:获取用户信息
this.$store.dispatch('user/getUserInfo')
// 第四步跳转到主页
this.$router.push('/home')
}else{
// 若登录失败,提示原因
this.$message.error(result.message)
}
}
问题描述:刷新会导致vuex
中的用户信息丢失。
如何解决?
第一种方式:登录成功后dispatch
一下,在App
组件中mounted
的时候再dispatch
一下
import { readToken } from './utils/auth'
export default {
name:'App',
components:{Header,Footer},
mounted() {
if(readToken()){
this.$store.dispatch('user/getUserInfo')
}
},
}
第二种方式:靠导航守卫
去维护用户信息。
token
存入localStorage
,根据token
获取的用户信息存入vuex
,每次刷新页面时,靠导航守卫
去维护用户信息。
概念:路由跳转时,在特定的时刻,执行的一些函数。
作用:可以在路由跳转时,追加特殊逻辑(路由鉴权、获取用户信息等)。
技巧:守卫越早使用越好。
分类:
A
座楼的保安A
座楼3027
室的保安我们的项目为什么使用导航守卫?
严重注意:导航守卫一定一定要有出口!—— 什么是出口?next()
就是。
导航守卫小案例:
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../pages/Home'
import Hot from '../pages/Hot'
import Like from '../pages/Like'
import Near from '../pages/Near'
Vue.use(VueRouter)
//暴露路由器
const router = new VueRouter({
mode:'history',
routes:[
{
path:'/home',
component:Home
},
{
path:'/hot',
component:Hot
},
{
path:'/like',
component:Like
},
{
path:'/near',
component:Near
},
]
})
//需求:只有尊贵的vip用户,才能访问: /like /near
// 用于标识是否为vip
let isVIP = false
// 设计一个鉴权名单
let authPath = ['/like','/near']
// beforeEach所指定的函数就是全局守卫
// 全局守卫何时调用?—— 1.整个应用初始化的时候调用(一上来)2.每次路由跳转的时候
router.beforeEach((to,from,next)=>{
// 判断是否为尊贵的VIP
if(isVIP){
console.log('你是尊贵的VIP,你随便去哪,我直接放行')
next()
}else{
console.log('你不是尊贵的VIP,我需要进一步看看,你要去哪?')
if(authPath.includes(to.path)){
console.log('想得美,还看VIP专属路由,放行到/home')
next('/home')
}else{
console.log('还好,你看的是那些随便看的,直接放行')
next()
}
}
})
export default router
this.$store.dispatch('xxx')
的返回值是一个Promise
实例。
返回的这个Promise
实例,成功还是失败,要看xxx
函数的返回值,规则同then
方法。
可以去Test
组件中做一个测试
在src\store\index.js
中
import Vue from 'vue'
import Vuex,{Store} from 'vuex'
import user from './modules/user'
import home from './modules/home'
Vue.use(Vuex)
// actions中的方法用于响应组件中的动作 —— 服务员
const actions = {
jia({state,commit},value){
if(state.a < 10){
commit('JIA',value)
// return ''
}else{
// alert('不能再加了,因为不能超过10')
return Promise.reject('不能大于10')
}
}
}
// mutations中的方法用于修改状态 —— 厨师
const mutations = {
JIA(state,value){
state.a += value
}
}
// state用于指定初始的数据 —— 原材料
const state = {
a:1
}
export default new Store({
actions,
mutations,
state,
modules:{
user,
home
}
})
在src\pages\Test\index.vue
中
async test() {
//第一种写法
/* const x = this.$store.dispatch("jia");
x.then(
() => {console.log("加成了")},
() => {console.log("没加成")}
) */
//第二种写法
try {
let x = await this.$store.dispatch("jia", 1);
console.log(x);
} catch (error) {
console.log(error);
}
},
src\store\user.js
里,找到:getUserInfo
,让getUserInfo
可以反馈自己的工作成果,同时修改一下获取信息失败的提示,换成一个比较好的提示 —— 【身份过期,请重新登录!】const actions = {
// 根据token获取用户信息(token已经在请求头中携带了)
async getUserInfo({commit}){
/**********/
if(code === 200){
//如果用户信息获取成功,则不写return,相当于return了undefined,
//那么dispatch('getUserInfo')得到的值就是一个成功的Promise实例
/**********/
}else{
/**********/
//如果用户信息获取失败,则return一个失败的Promise实例
//那么dispatch('getUserInfo')得到的值就是一个失败的Promise实例
Message.warning('身份过期,请重新登录!')
return Promise.reject()
}
}
}
router.beforeEach(async(to,from,next)=>{
// 从localStorage中读取token
const token = readToken()
// 判断token是否存在
if(token){
console.log('你登录过,我要继续从vuex中读取用户信息')
// 从vuex中读取用户信息
const {info} = store.state.user
// 判断用户信息是否为空
if(info.id){
console.log('要token有token,要信息有信息,直接放行')
// 放行
next()
}else{
console.log('虽然有token,但没信息,拿着token去要信息')
try {
// 获取用户信息
await store.dispatch('user/getUserInfo')
console.log('拿着你的token获取到了用户信息,漂亮!放行!')
next()
} catch (error) {
console.log('拿着你的token没有获取到用户信息,你那个破token失效了,我帮你删了,放行到登录')
// 删除已经不靠谱的token
// deleteToken()
next('/login')
}
}
}else{
console.log('你没有登录,所以有些敏感路由,你不能去了,但现在没有啥路由是敏感的,所以暂时饶你一命,想去哪就去哪')
next()
}
})
localStorage
中删除token
,不登录,随便点一点,所有路由都能看才正常。vuex
里有没有用户信息,一直有才正常。localStorage
中,故意把token
改错,刷新,打回登录才正常。使用element-ui
实现付款弹窗
<a class="btn" @click="pay">立即支付</a>
<script>
pay(){
// 弹窗中的html内容
const htmlStr = ''
// 弹窗的具体配置
const options = {
dangerouslyUseHTMLString: true, //用于支持html字符串的解析
center:true,
showCancelButton:true,
cancelButtonText:'支付遇到问题',
confirmButtonText :'已完成支付',
showClose:false,
title:'微信扫码支付'
}
// 弹窗弹起来!
this.$alert(htmlStr,{
// 弹窗的配置
...options,
// 点击弹窗中的确定或关闭按钮的回调
callback(type){
if(type === 'confirm'){
console.log('你点了确定按钮')
}else{
console.log('你点了关闭按钮')
}
}
});
}
</script>
<template>
<div>
<button @click="test">点我测试一下qrcode</button>
<br>
<img :src="url" alt="">
</div>
</template>
<script>
import QRCode from 'qrcode'
export default {
name:'Test',
data() {
return {
url:'http://49.232.112.44/images/hot.jpg'
}
},
methods: {
test(){
// 将“泰裤辣”转为二维码
const x = QRCode.toDataURL('泰裤辣,草莓,蓝莓、蔓越莓、今天你想我了没')
x.then(
value => {
console.log('转换二维码成功',value)
this.url = value
},
reason => {
console.log('转换二维码失败',reason)
}
)
}
},
}
</script>
<style lang="less" scoped>
</style>
安装qrcode:npm i qrcode
代码如下:
<script>
import {reqPayInfo} from "@/api";
import QRCode from "qrcode";
export default {
methods: {
async pay(){
try {
// 调用QRCode将codeUrl转为二维码
const url = await QRCode.toDataURL(this.payInfo.codeUrl)
// 弹窗中的html内容
const htmlStr = `${url}">`
// 弹窗的具体配置
const options = {
dangerouslyUseHTMLString: true, //用于支持html字符串的解析
center:true, //居中布局
showCancelButton:true, //显示取消按钮
cancelButtonText:'支付遇到问题',
confirmButtonText :'已完成支付',
showClose:false, //隐藏右上角的x
title:'微信扫码支付'
}
// 弹窗弹起来!
this.$alert(htmlStr,{
// 弹窗的配置
...options,
// 点击弹窗中的确定或关闭按钮的回调
callback(type){
if(type === 'confirm'){
console.log('你点了确定按钮')
}else{
console.log('你点了关闭按钮')
}
}
});
} catch (error) {
// 若转换二维码失败,提示一下
this.$message.warning('支付二维码显示失败,请联系客服!')
}
}
}
};
</script>
安装vue-lazyload
:npm i vue-lazyload@1
。
注意:
vue-lazyload
这包,默认版本是3
的内测版,所以请安装时,加上版本限制
提前准备好一个过渡图片:/assets/images/loading.gif
main.js
中
import VueLazyload from 'vue-lazyload'
import picture from '@/assets/images/loading.gif'
//....
Vue.use(VueLazyload,{
loading:picture
})
使用图片的地方改为
<img v-lazy="goods.defaultImg" />
什么是懒加载:其实就是延迟加载,即当需要用到的时候再去加载。
Vue中路由的懒加载:
//原来的引入方式
/*
import Home from '@/pages/Home'
import Login from '@/pages/Login'
import Register from '@/pages/Register'
import Search from '@/pages/Search'
import Detail from '@/pages/Detail'
import AddCartSuccess from '@/pages/AddCartSuccess'
import Cart from '@/pages/Cart'
import Trade from '@/pages/Trade'
import Pay from '@/pages/Pay'
import PaySuccess from '@/pages/PaySuccess'
*/
//懒加载的引入方式
export default [
{
path:'/home',
component:() => import('@/pages/Home'),
},
{
path:'/login',
component:() => import('@/pages/Login'),
meta:{isHideFooter:true}
},
{
path:'/register',
component:() => import('@/pages/Register'),
meta:{isHideFooter:true}
},
{
path:'/search',
component:() => import('@/pages/Search'),
},
{
path:'/test',
component:() => import('@/pages/Test'),
},
{
name:'detail',
path:'/detail/:id',
component:() => import('@/pages/Detail'),
},
{
path:'/addcart_success',
component:() => import('@/pages/AddCartSuccess'),
},
{
path:'/shopcart',
component:() => import('@/pages/ShopCart'),
},
{
path:'/trade',
component:() => import('@/pages/Trade'),
},
{
path:'/pay',
component:() => import('@/pages/Pay'),
},
{
path:'/paysuccess',
component:() => import('@/pages/PaySuccess'),
},
{
path:'/',
redirect:'/home',
}
]
第一步:安装 npm i vee-validate@3
Vue2
中,我们要使用vee-validate
的2
版本、或3
版本,vee-validate
的4
版本是给Vue3
用的
第二步:在main.js
中引入:ValidationProvider
、extend
,并注册ValidationProvider
import {ValidationProvider,extend} from 'vee-validate';
Vue.component('ValidationProvider',ValidationProvider)
第三步:使用ValidationProvider
包裹表单元素,并指定具体规则。
<ValidationProvider rules="shouji" v-slot="{errors}">
<input type="text" placeholder="请输入你的手机号" v-model="phone">
<span class="error-msg">{{errors[0]}}span>
ValidationProvider>
第四步:在main.js
中,创建一个手机的验1证规则
const phoneReg = /^(0|86|17951)?(13[0-9]|15[012356789]|166|17[3678]|18[0-9]|14[57])[0-9]{8}$/
extend('shouji',{
validate:(value)=>phoneReg.test(value),
message:'手机号格式不合法'
})
分析:很多的输入项需要多种规则,例如手机号的规则是:必填项、格式要合法。
第一步:在main.js
中,引入内置的验必填项规则
import {required} from 'vee-validate/dist/rules'
//extend('required',required)
第二步:在内置规则基础上,自定义一下必填项规则
extend('bixu',{
...required,
message:'{_field_}必须填写',
})
第三步:组件中使用规则
<ValidationProvider name="手机" rules="shouji|bixu" v-slot="{errors}">
<input type="text" placeholder="请输入你的手机号" v-model="phone">
<span class="error-msg">{{errors[0]}}span>
ValidationProvider>
随后完成:验证码、密码、重复密码、用户协议的校验,且要注意重复密码需要传参
- 补充其他规则:
const codeReg = /^\d{6}$/ const passwordReg = /^\w{6,21}$/ extend('code',{ validate:(value)=> codeReg.test(value), message:'验证码必须为6位数字', }) extend('pwd',{ validate:(value)=> passwordReg.test(value), message:'密码必须为:数字、字母、下划线', }) extend('agree',{ validate: value => value, message:'请同意协议', }) extend('repwd',{ validate:(value,[params])=> { console.log(params) return value === params }, message:'确认密码必须与密码一致' })
- 组件中使用
<ValidationProvider name="验证码" rules="code|bixu" v-slot="{errors}"> <input type="text" placeholder="请输入验证码" v-model="code"> <button class="getcode" @click="getCode">获取验证码button> <span class="error-msg">{{errors[0]}}span> ValidationProvider> <ValidationProvider name="密码" rules="pwd|bixu" v-slot="{errors}"> <input type="password" placeholder="请输入你的登录密码" v-model="password"> <span class="error-msg">{{errors[0]}}span> ValidationProvider> <ValidationProvider name="确认密码" :rules="`repwd:${password}|bixu`" v-slot="{errors}"> <input type="password" placeholder="请输入确认密码" v-model="rePassword"> <span class="error-msg">{{errors[0]}}span> ValidationProvider> <ValidationProvider name="用户协议" rules="agree" v-slot="{errors}"> <input name="m1" type="checkbox" v-model="agree"> <span>同意协议并注册《尚品汇用户协议》span> <span class="error-msg">{{errors[0]}}span> ValidationProvider>
操作Register
组件
<ValidationObserver ref="register">
<!-- 所有输入项 -->
</ValidationObserver>
<script>
async register(){
const result = await this.$refs.registerForm.validate()
if(result){
// 获取用户输入的东西
const {phone,password,code} = this
// 发请求去注册
const {code:c1,message,data} = await reqRegister({phone,password,code})
if(c1 === 200){
this.$message.success(`注册成功`)
this.$router.push('/login')
}else{
this.$message.warning(`注册失败:${message}`)
}
}else{
this.$message.warning('请检查输入项的合法性')
}
}
</script>
明确:有些路由只有登录了,才能看。—— 使用全局守卫去做。
他们分别是:/trade
、/pay
、/paysuccess
、/order
在全局导航守卫中追加几个规则,具体编码如下:
// 以下路由必须登录后才能看
const authPath = ['/trade','/pay','/paysuccess','/order']
明确:只有在特定路由,才能去特定路由。
使用【路由独享守卫】细化鉴权规则,配置写在路由规则中(src/router/routes.js
)。
//只有从【详情】页面才能跳到【添加购物车成功】页面
{
path:'/addcart_success',
component:AddCartSuccess,
beforeEnter: (to, from, next) => {
if(from.path.slice(0,7) === '/detail') next()
else next('/home')
}
},
//只有从【交易】页面才能跳到【支付】页面
{
path:'/pay',
component:Pay,
beforeEnter: (to, from, next) => {
if(from.path === '/trade') next()
else next('/home')
}
},
使用【组件内守卫】细化鉴权规则,配置写在组件中(.vue
文件)
export default {
name: 'PaySuccess',
//只有从【支付】页面,才能去【支付成功】页面
beforeRouteEnter(to, from, next) {
if(from.path === '/pay'){
next()
}else{
next('/home')
}
}
}