参考其他博主的笔记,超详细:👇👇👇
尚品汇项目笔记
组件显示与隐藏
法1.可以根据组件身上的$route获取当前路由信息,通过路由路径判断组件显示与隐藏。
法2.给路由添加路由原信息【meta】(一般都用这个)
//router里 index.js
{
path: '/search',
component: Search,
meta: {show: true}
},
{
path: '/login',
component: Login,
meta: {show: false}
}
//APP.vue
<Footer v-show="$route.path== '/login'"></Footer> //1.不常用
<Footer v-show="$route.meta.show"></Footer> //2.常用
路由传参
//router里的 index.js
{ path: '/search/:keyword?',
component: Search,
meta: {show: true},
name: 'search'
},
//注意:在路由跳转要传参且用对象形式时,需要用name属性,不能用path形式
this.$router.push({name:"search",query:{keyword:this.keyword}})
// /:keyword是一个params参数的占位符;
// ?可以params参数可传可不传
// 加入||undefined,当我们传递的参数为空串时地址栏url也可以保持正常
this.$router.push({name:"search",params:{keyword:''||undefined},query:{keyword:this.keyword}})
全局组件
// main.js 全局的配置需要在main.js中配置
import TypeNav from '@/pages/Home/TypeNav';
//第一个参数:全局组件名字,第二个参数:全局组件
Vue.component(TypeNav.name,TypeNav);
//全局组件可以在任一页面中直接使用,不需要导入声明
store存取
//store里的home.js
import {reqCategoryList} from "@/api"
const state = {
categoryList: []
};
const mutations = {
CATEGORYLIST(state, categoryList) {
state.categoryList = categoryList
}
};
const actions = {
async categoryList({commit}) {
const res = await reqCategoryList()
if (res.code == 200) {
console.log(res.data)
commit('CATEGORYLIST', res.data)
}
}
};
//home.vue
{{categoryList}}
import {mapState} from 'vuex'
mounted() {
this.$store.dispatch('categoryList')
},
computed: {
...mapState({
categoryList: state => state.home.categoryList
})
},
节流、防抖
节流:用户操作频繁,把频繁变少量
防抖:用户操作频繁,只执行最后一次
lodash官网
<script src="lodash.js"></script>
//节流:5s执行一次
button.onclick = _.throttle(function () {
count++;
}, 5000)
//防抖
let result = _.debounce(function () {
console.log('我在一秒之后执行一次')
},1000)
//全部引入 // import _ from 'lodash';
//按需引入lodash
import {throttle} from 'lodash'
changeIndex: throttle(function (index){
this.currentIndex = index
},50)
//有异步时
函数名: throttle(async function (参数列表){
await ...
},50)
编程式导航+事件委托实现路由跳转
见笔记
三级分类显示时添加动画效果
//用transition包起来 name属性
<transition name="sort">
<div class="sort">
......
</div>
</transition>
/* 过渡动画*/
.sort-enter{
height: 0;
}
.sort-enter-to{
height: 461px;
}
.sort-enter-active{
transition: all .5s linear;
}
mock模拟数据
1.参照这个案例吧
2.
第一步(安装):npm install --save express
第二步(造数据):src同级新建 mock/index.js
const express = require('express')
const app = express()
const vipLogin = require('./data/vip_login')
const adminLogin = require('./data/admin_login')
const url = require('url')
app.get('/login',(req,res)=>{
cosnt user = url.parse(req.url,true).query.user
if(user==='admin'){
res.send(adminLogin )
}else{
res.send(vipLogin)
}
})
app.lesten(3300,()=>{
console.log('服务器运行在3300')
})
//mock/data/vip_login
{
"code":0,
"message":"登录成功",
"data":{ "token":"vip" }
}
//mock/data/admin_login
{
"code":0,
"message":"登录成功",
"data":{ "token":"admin" }
}
第三步:切换目录:运行 node index.js //此时控制台会输出:服务器运行在3300
//可以通过浏览器访问 :http://localhost:3300/login
// http://localhost:3300/login?user=admin
// http://localhost:3300/login?user=vip
第四步(封装axios):utils/http.js
把utils/request.js复制过来,axios.create中 不用再写 baseURL(之后要用反向代理解决跨越)
......
axios.create({timeout:3000})
......
第五步(反向代理):vue.config.js
devServer: {
proxy: {
"/express": {
target: "http://localhost:3300",
changeOrigin: true,
pathRewrite: {
"^/express": "",
},
},
},
},
第六步(写api):api/http.js
import axios from "../utils/http"
export function login(user){
return axios.get('/express/login?user=' + user)
}
第七步(使用):testExpress.vue
import { login } from "@/api/http"
const res = await login("admin")
注意:mock(模拟数据)数据需要使用到mockjs模块,可以帮助我们模拟数据。
注意:mockjs【并非mock.js mock-js】
mock官网
第一步: 安装依赖包mockjs
第二步: 在src文件夹下创建一个文件夹,文件夹mock文件夹。
第三步: 准备模拟的数据:把mock数据需要的图片放置于public文件夹中!
比如:listContainer中的轮播图的数据
[
{id:1,imgUrl:‘xxxxxxxxx’},
{id:2,imgUrl:‘xxxxxxxxx’},
{id:3,imgUrl:‘xxxxxxxxx’},
]
第四步: 在mock文件夹中创建一个server.js文件。
注意:在server.js文件当中对于banner.json||floor.json的数据没有暴露,但是可以在server模块中使用。
对于webpack当中一些模块:图片、json,不需要对外暴露,因为默认就是对外暴露。
第五步: 通过mock模块模拟出数据:通过Mock.mock方法进行模拟数据
第六步: 回到入口文件,引入serve.js:mock需要的数据|相关mock代码页书写完毕,关于mock当中serve.js需要执行一次,如果不执行,和你没有书写一样的。
第七步: 在API文件夹中创建mockRequest【axios实例:baseURL:‘/mock’】,专门获取模拟数据用的axios实例。
在开发项目的时候:切记,单元测试,某一个功能完毕,一定要测试是否OK
安装、mock里两步、main里引入、api两步、(store处理)、页面使用
一、npm install mockjs
二、/* src/mock/banner.json */
[
{ "id": "1","imgUrl": "/images/banner1.jpg" },
{ "id": "2","imgUrl": "/images/banner2.jpg" },
{ "id": "3","imgUrl": "/images/banner3.jpg" },
{ "id": "4","imgUrl": "/images/banner4.jpg" }
]
三、/* src/mock/mockServe.js */
import Mock from 'mockjs'
//webpack默认对外暴露:json、图片
import banner from './banner.json'
//模拟数据
Mock.mock('/mock/banner', {code: 200, data: banner})
四、/* main.js */
import '@/mock/mockServe'
五、/* api/mockAjax.js */
/*
PS:真实从后端接口拿数据就不需要以上四步进行数据模拟了,
但需要vue.config.js里代理服务器:
devServer: { proxy: { '/api': {target: 'http://39.98.123.211',}} }
而接下来的步骤基本一致
*/
//把 api/ajax.js 复制过来,只把 baseURL: '/api' 改为 baseURL: '/mock'
//二次封装axios
import axios from "axios";
//引入进度条
import nprogress from "nprogress"
//引入进度条样式
import "nprogress/nprogress.css";
//1、对axios二次封装
const requests = axios.create({
baseURL: '/mock',
timeout: 5000
})
//2.请求拦截器
requests.interceptors.request.use((config) => {
//config内主要是对请求头Header配置
//比如添加token
//开启进度条
nprogress.start()
return config;
})
//3.响应拦截器
requests.interceptors.response.use((res) => {
//响应成功,关闭进度条
nprogress.done()
return res.data;
}, (error) => {
//失败的回调函数
console.log("响应失败" + error)
return Promise.reject(new Error('fail'))
})
//4、对外暴露
export default requests
六、/* api/index.js */
//引入二次封装的api
import requests from "@/api/ajax";
//引入二次封装的mock
import mockRequests from "@/api/mockAjax"
//真实api数据接口
//获取三级联动列表(两种写法)
// export const reqCategoryList = () => requests({url: '/product/getBaseCategoryList', methods: 'get'})
export const reqCategoryList = () => requests.get(`/product/getBaseCategoryList`)
//mock模拟数据接口
//获取banner(Home首页轮播图)
export const reqGetBannerList = () => mockRequests.get(`/banner`)
七、/* store/home/index.js */ //引入接口,三连处理数据
import {reqCategoryList, reqGetBannerList} from "@/api"
const state = {
// 三级联动数据
categoryList: [],
//轮播图数据
bannerList: []
};
const mutations = {
CATEGORYLIST(state, categoryList) {
state.categoryList = categoryList
},
GETBANNERLIST(state, bannerList) {
state.bannerList = bannerList
}
};
const actions = {
// 获取三级联动数据
async getCategoryList({commit}) {
const res = await reqCategoryList()
if (res.code == 200) {
console.log('categoryList', res.data)
commit('CATEGORYLIST', res.data)
}
},
// 获取首页轮播图数据
async getBannerList({commit}) {
const res = await reqGetBannerList()
if (res.code == 200) {
console.log('bannerList', res.data)
commit('GETBANNERLIST', res.data)
}
}
};
const getters = {};
export default {
state,
mutations,
actions,
getters
}
八、/* pages/Home/ListContainer/index.vue */
{{bannerList}}
import {mapState} from 'vuex'
export default {
name: "listContainer",
mounted() {
this.$store.dispatch('getBannerList')
},
computed: {
...mapState({
bannerList: state => state.home.bannerList
})
}
}
swiper使用
npm install --save swiper@5
//ListContainer/index,vue
<div class="swiper-container" ref="mySwiper">
<div class="swiper-wrapper">
<div class="swiper-slide" v-for="(carousel,index) in bannerList" :key="carousel.id">
<img :src="carousel.imgUrl">
</div>
</div>
<!-- 如果需要分页器 -->
<div class="swiper-pagination"></div>
<!-- 如果需要导航按钮 -->
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
</div>
import Swiper from 'swiper'
import "swiper/css/swiper.css"
watch: {
bannerList: {
handler() {
//watch只能保证在bannerList变化时创建swiper对象,但是并不能保证此时v-for已经执行完了
// this. $nextTick它会将回调延迟到下次 DOM 更新循环之后执行
this.$nextTick(()=>{
var mySwiper = new Swiper(this.$refs.mySwiper, {
loop: true, // 循环模式选项
// 如果需要分页器
pagination: {
el: '.swiper-pagination',
clickable: true
},
// 如果需要前进后退按钮
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
}
})
})
}
}
}
Object.assign()对象拷贝
Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
Object.assign(target, …sources) 【target:目标对象】,【souce:源对象(可多个)】
/*
this.searchParams.category1Id = this.$route.query.category1Id
this.searchParams.category2Id = this.$route.query.category2Id
this.searchParams.category3Id = this.$route.query.category3Id
this.searchParams.categoryName = this.$route.query.categoryName
this.searchParams.keyword = this.$route.params.keyword
*/
// 简化写法
Object.assign(this.searchParams, this.$route.query, this.$router.params)
深拷贝与浅拷贝区别:
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。
但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
Object.assign() 进行的是 浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。
JSON.parse(JSON.stringify())、.cloneDeep 进行的是 深拷贝
点击切换Sort排序
<ul class="sui-nav">
<li :class="{active:isOne}" @click="changeOrder(1)">
<a>综合 <span v-show="isOne" class="iconfont"
:class="{'icon-up':isAsc,'icon-todown':isDesc}"></span></a>
</li>
<li :class="{active:isTwo}" @click="changeOrder(2)">
<a>价格 <span v-show="isTwo" class="iconfont"
:class="{'icon-up':isAsc,'icon-todown':isDesc}"></span></a>
</li>
</ul>
computed: {
//综合
isOne() {
return this.searchParams.order.indexOf('1') !== -1
},
//价格
isTwo() {
return this.searchParams.order.indexOf('2') !== -1
},
//降序
isDesc() {
return this.searchParams.order.indexOf('desc') !== -1
},
//升序
isAsc() {
return this.searchParams.order.indexOf('asc') !== -1
}
},
//改变升降序
changeOrder(flag) {
let oraginFlag = this.searchParams.order.split(':')[0]
let oraginSort = this.searchParams.order.split(':')[1]
let newOrder = ''
if (flag == oraginFlag) {
newOrder = `${flag}:${oraginSort == 'desc' ? 'asc' : 'desc'}`
} else {
newOrder = `${flag}:${'desc'}`
}
this.searchParams.order = newOrder
this.getData()
}
跳转路由始终回到顶部
//router/index.js
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes,
scrollBehavior(to, from, savedPosition) {
// 始终滚动到顶部
return {y: 0}
},
})
sessionStorage、localStorage概念
sessionStorage:为每一个给定的源维持一个独立的存储区域,该区域在页面会话期间可用(即只要浏览器处于打开状态,包括页面重新加载和恢复)。
localStorage:同样的功能,但是在浏览器关闭,然后重新打开后数据仍然存在。
注意:无论是session还是local存储的值都是字符串形式。如果我们想要存储对象,需要在存储前JSON.stringify()将对象转为字符串,在取数据后通过JSON.parse()将字符串转为对象。
接口返回无数据时
//store/detail.js
// 将产品添加到购物车
async addOrUpdateShopCart({commit}, {skuId, skuNum}) {
const res = await reqAddOrUpdateShopCart(skuId, skuNum)
// console.log(res)
// 只返回了code=200,数据是null,不需要三连环存储数据了
if (res.code == 200) {
return 'ok'
} else {
return Promise.reject(new Error('faile'))
}
}
//Detail.js
async addShopCar() {
try {
await this.$store.dispatch('addOrUpdateShopCart', {
skuId: this.$route.params.skuId,
skuNum: this.skuNum
})
sessionStorage.setItem('SKUINFO',JSON.stringify(this.skuInfo))
this.$router.push({name:'addCartSuccess',query{skuNum: this.skuNum}})
} catch (error) {
alert(error.message)
}
}
//AddCartSuccess.vue
computed: {
skuInfo() {
return JSON.parse(sessionStorage.getItem('SKUINFO'))
}
}
生成uuid并永久存储
//src/utils/uuid_token.js
import {v4 as uuidv4} from 'uuid'
export const getUUID = () => {
let uuid_token = localStorage.getItem('UUIDTOKEN')
if (!uuid_token) {
uuid_token = uuidv4()
localStorage.setItem('UUIDTOKEN', uuid_token)
}
return uuid_token
}
//detail.js
//封装游客身份模块uuid-->生成一个随机字符串(不能再变)
import {getUUID} from '@/utils/uuid_token'
const state = {
uuid_token: getUUID()
};
//api/ajax.js
//引入store(this.$store只能在组件中使用,不能再js文件中使用)
import store from '@/store'
//请求拦截器
requests.interceptors.request.use((config) => {
//config内主要是对请求头Header配置
//比如添加token(userTempId字段和后端统一)
if (store.state.detail.uuid_token) {
config.headers.userTempId = store.state.detail.uuid_token
}
//开启进度条
nprogress.start()
return config;
})
在store中获取参数并调用方法
//store/shopCart.js
const actions={
......
// 删除选中
//context:小仓库,commit【提交mutations修改state】 getters【计算属性】 dispatch【派发action】 state【当前仓库数据】
deleteAllCheckedCart({dispatch, getters}) {
let PromiseAll = [];
getters.cartList.cartInfoList.forEach(item => {
let promise = item.isChecked == 1 ? dispatch('deleteCartListBySkuId', item.skuId) : ''
PromiseAll.push(promise)
})
//只有p1|p2...都成功,返回结果才成功;又一个失败就返回失败
return Promise.all(PromiseAll)
}
}
//ShopCart/index.vue
// 删除选中
async deleteAllChecked() {
try {
await this.$store.dispatch('deleteAllCheckedCart')
this.getData()
} catch (error) {
alert(error.message)
}
}
生成微信支付二维码
import QRCode from 'qrcode'
//点击支付按钮
async open() {
//生成二维码
let url = await QRCode.toDataURL(this.payInfo.codeUrl)
this.$alert(`<img src="${url}"></img>`, '请你微信支付', {
dangerouslyUseHTMLString: true,
center: true,
showCancelButton: true,
cancelButtonText: '支付遇到问题',
confirmButtonText: '已支付成功',
showClose: false,
beforeClose: (type, instance, done) => {
if (type == 'cancel') {
alert('请联系管理员')
// 清除定时器
clearInterval(this.timer)
this.timer = null
// 关闭弹出框
done()
} else {
//是否真的支付了
if (this.code == 200) {
clearInterval(this.timer)
this.timer = null
done()
this.$router.push('/paySuccess')
}
}
}
});
// 支付成功路由跳转;失败提示
if (!this.timer) {
this.timer = setInterval(async () => {
let res = await this.$API.reqPayStatus(this.orderId)
if (res.code == 200) {
// 清楚定时器
clearInterval(this.timer)
this.timer = null
// 保存支付成功返回的code
this.code = res.code
// 关闭弹出框
this.$msgbox.close()
// 跳转到下一级路由
this.$router.push('/paySuccess')
}
}, 1000)
}
}
路由前置守卫
//全局守卫:前置守卫
router.beforeEach(async (to, from, next) => {
next();
console.log(store)
let token = store.state.user.token
let name = store.state.user.userInfo.name
//已登录
if (token) {
console.log('111')
if (to.path == '/login') {
next('/home')
} else {
//有用户信息
if (name) {
console.log('222')
//放行
next()
} else {
try {
//获取用户信息
await store.dispatch('getUserInfo')
console.log('333')
//放行
next()
} catch (error) {
//token过期获取不到用户信息(重新登录,清除token)
await store.dispatch('userLogout')
next('/login')
}
}
}
} else {
console.log('444')
let toPath = to.path;
if (toPath.indexOf('/trade') !== -1 || toPath.indexOf('/pay') !== -1 || toPath.indexOf('/center') !== -1) {
next('/login?redirect=' + toPath)
} else {
next()
}
/* if (to.path === '/login' || to.path === '/home' || to.path === '/register')
next()
else {
alert('请先登录')
next('/login')
}*/
}
})
//登陆时
async userLogin() {
try {
const {phone, password} = this;
(phone, password) && (await this.$store.dispatch('userLogin', {phone, password}))
let toPath = this.$route.query.redirect || '/home'
this.$router.push(toPath)
} catch (error) {
alert(error.message)
}
路由独享守卫
{
path: '/trade',
component: Trade,
meta: {show: true},
name: 'trade',
// 路由独享守卫
beforeEnter: (to, from, next) => {
if (from.path == '/shopCart') {
next()
} else {
//从哪来回哪去
next(false)
}
}
},
组件内守卫
export default {
name: 'PaySuccess',
// 组件内守卫
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
if (from.path == '/pay') {
next()
} else {
next(false)
},
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
console.log("12313131311313");
},
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
next();
},
...
}