这是我的第一篇个人博客,主要是对我学习前端过程中的一些主记录。由于我在江苏,从2022年过完年,一直到年中,疫情反反复复,又正是大三,马上面临就业难题,我选择了前端。
从html,css,js基础,webAPI,js进阶,ES6,ajax,node.js一直到最近刚学完尚硅谷的vue2+vue3的全家桶,近期也是刚做完一个尚品汇的项目,作为我第一次做一个相对完整的项目。尚品汇项目是一个电商项目,可以登陆注册账号,搜索查找,产品详情,放大镜,购买商品,加入购物车,下单,提交订单以及支付等模块。用vue-router控制路由的跳转,vuex对仓库中资源进行统一管理,用swiper展示轮播图,element-UI做弹出框,三级联动对分类进行路由跳转,路由之间的转参query和params,还有选中分类或者搜索都会有面包屑处理,综合与价格的排序,手写的分页器作为全局组件,在搜索页面与我的订单中使用,还有在未登录时与登录时的路由守卫判断,个人中心二级路由展示,图片懒加载,用vee-validate插件进行表单验证,路由懒加载。
这个项目类似的界面,其实没有什么特色,做为培训机构给我们白嫖的练手项目也是非常不错的。在这个项目中,对vue2的使用,还有一些功能实现的原理,以后业务逻辑的实现,是不做项目不打代码练不出来的。在这个过程过我也遇到了非常多的困难,但是这项目比较得普遍,还有跟着老师来的,以及通过百度,很多debug也就相对比较容易一些,只不过还是有困扰的点,我会在之后的博客中详细介绍。
在做完这个项目之后,我也算是对这阶段的学习告一段落了,在做完这个项目最后,我通过阿里云的学生活动,成功领到了一台服务器,看B站鱼皮大佬的教程部署了wordpress创建了一个很简易的个人博客。还有很多不足希望大佬可以提出来,感谢大佬可以给我的学习过程中提供帮助,我会继续学习,继续更新博客的。
本人博客: 孙浩的个人博客 – 前端学习之路
下面是具体细节问题:
三级联动路由传参:
三级联动
1、先把点击对象拿到,再解构出来(categoryname,categord1id,categord2id,categord3id)自定义属性
2、外层判断categoryname,有则为a链接,内层再判断是哪一级
3、准备好要给真正query参数的对象
let query = {categoryName:categoryname}//表示要查找的是哪个分类
3、判断原本这个有无params参数(可能搜索框有),把要跳转的对象给给真正的
if (this.$route.params) {//有 location.params = this.$route.params; location.query = query; this.$router.push(location); }
但是在跳转路由的时候,若是搜索框里面是空串就会出bug,(外加可能没有点分类,直接搜索)解决:
if (this.$route.query) { let loction = { name: "search", params: { keyword: this.keyword || undefined },//没有则是undefined }; loction.query = this.$route.query; this.$router.push(loction); }
浏览器报错,因为在push的时候是要返回一个promise的所以为了一劳永逸要重写push和replace方法。(当然不写也行,就是飘红)
let originPush = VueRouter.prototype.push; VueRouter.prototype.push = function (location, resolve, reject) { if (resolve && reject) { //当传了成功与失败的回调时 originPush.call(this, location, resolve, reject) } else { originPush.call(this, location, () => { }, () => { })//没有传的话就返回两个 空对象 } }
ngrogress,让请求的时候显示进度条
//请求拦截器:在发请求之前,请求拦截器可以检测到请请发之前做的一些事 requests.interceptors.request.use((config) => { //config:配置对象,里面有请求头 //发请求前进度条开始 nProgress.start() return config }) //响应拦截器 requests.interceptors.response.use((res) => { //成功的回调:服务器响应数据回来以后,响应拦截器可以检测到 //响应成功进度条结束 nProgress.done() return res.data; }, (err) => { return Promise.reject(new Error('faile')); })
防抖和节流
防抖:在连续快速触发的时候,后面的会重复计时。
节流:在一定范围内重复快速触发,只有一次生效。
用lodash插件实现
mock插件的使用
因为后端没有主页下面的数据,所以我们还像调用接口一样,只不过请求会拦截,从而调用本地资源
src下新建文件夹,放mock中数据,还有模拟写的接口 //先引入mockjs模块 import mockjs from "mockjs";//把JSON数据格式引入进来 // webpack默认对外暴露:图片,JSON import banner from "./banner.json" import floor from "./floor.json"//mock数据:第一个参数为请求地址,第二个参数为请求数据 mockjs.mock("/mock/banner", { code: 200, data: banner }) mockjs.mock("/mock/floor", { code: 200, data: floor }) 在api文件夹中再新建,再创建一个axios实例,发送请求 const requests = axios.create({ baseURL: "/mock",//基础路径,发请求时路径会自动出现/api timeout: 5000,//代表请求超时时间 })
面包屑
面包屑
面包屑来源有3种:三级联动分类,搜索框,searchSelector子组件中的分类
前两种都只要把相应的categoryName变为undefined,再重新发送请求即可,搜索框多一步,要把input里清空,但搜索框不在同一个组件,所以就要用到兄弟间传参,选用$bus,再第三种,又分售卖的品牌和属性,只要把数据改了,再发一遍请求即可
排序(综合与价格)
先定义两个变量获取最开始是综合还是价格,升序还是降序。再定义一个新的用于再发请求,若点的是当前的元素,再排序反转,若点的是另一个,则默认为降序,然后把数据更新再发请求。
changeOrder(flag) { //1为综合,2为价格 let originFlag = this.searchParams.order.split(":")[0]; //最开始是综合还是价格 let originSort = this.searchParams.order.split(":")[1]; //最开始的升序还是降序 let newOrder = ""; //点的是当前的元素,再就是排序反转 if (flag === originFlag) { newOrder = `${originFlag}:${originSort == "desc" ? "asc" : "desc"}`; } else { //点的不是当前元素,再转到目标元素且,默认desc降序 newOrder = `${flag}:${"desc"}`; } //将新的order赋给searchParams并再次发请求 this.searchParams.order = newOrder; this.getData(); },
分页器
分页器
点哪个把哪一页的编号,更新数据再发请求
重点是手写分页器
分3个部分
第一部分是:1和……
第二部分是:连续的页码
第三部分是:……和最后一页的编号
由于后端没有直接返回多少页,所以要计算。
主要是第二部分,中间的连续页。
分页器特殊情况,在前5后5之内,连续页为5(连续的页数可以自定义)
startNumAndEndNum() { //先定义两个变量存储起始数字和结束数字 let start = 0, end = 0; //连续的页码至少要有5页 //不正常现象(总页数没有连续的页码多) if (this.continues > this.totalPage) { start = 1; end = this.totalPage; } else { start = this.pageNo - parseInt(this.continues / 2); end = this.pageNo + parseInt(this.continues / 2); //若是算出来start可能会少于1 if (start < 1) { start = 1; end = this.continues; } //若end大于了最大页数 if (end > this.totalPage) { end = this.totalPage; start = this.totalPage - this.continues + 1; } } return { start, end }; },
返回一个对象,限定了连续的开始和结束(eg:若连续页为5,当前页为6,再连续页为4,5,6,7,8)对称
但要是算出来开始小于了1再把开始限定为1,算出来结束大于了最大页,再把结束限定为最大页,且开始再变为了最大页-连续页+1
点击进入详情页路由时,会默认到最底部,解决:
在配置路由的时候加scrollBehavior配置项
let router = new VueRouter({ routes, //让路由跳转后,自动滚轮在最上方 scrollBehavior() { return { y: 0 } } })
放大镜
放大镜
放大镜效果本来在pink老师就练过一次,这里再练一次,且单独拿出来做为子组件。
首先绑定一个鼠标悬浮事件,出现一个遮罩层加一个大图,但是这个大图的显示出的大小还是一样的,只不过放大了2倍,然后就是移动的时候对应同时移动了
用$refs获取DOM,第一让遮罩层跟着鼠标移动,第二相对应放大层的背景图也要移动,但是鼠标向右移时,背景图是向左移2倍,第三约束遮罩层的范围
handler(e) { let mask = this.$refs.mask; //获取遮罩层DOM let big = this.$refs.big; //获取放大层DOM //获取遮罩层左上角的坐标:鼠标的坐标-0.5*遮罩层冠宽或高 let left = e.offsetX - mask.offsetWidth / 2; let top = e.offsetY - mask.offsetHeight / 2; // 约束遮罩层的范围 if (left <= 0) { left = 0; } else if (left >= mask.offsetWidth) { left = mask.offsetWidth; } if (top <= 0) { top = 0; } else if (top >= mask.offsetHeight) { top = mask.offsetHeight; } mask.style.left = left + "px"; mask.style.top = top + "px"; big.style.left = -2 * left + "px"; big.style.top = -2 * top + "px"; },
uuid(之前禹哥教过用这个的轻量版nanoid)
因为要进购物车,但是未登录的话,后端是不会返回数据的,所以要用uuid生成一个token
新建一个utils,再建一个js文件,判断是还本地已经有了,没有就生成,存在本地存储的同时还要把数据返回
这里把两个方法再单独放一个模块,作为对浏览器中本地存储的操作
import { v4 as uuidv4 } from 'uuid' export const getUUID = () => { //先从本地获取uuid是否有 let uuid_token = localStorage.getItem('UUIDTOKEN') //如果没有 if (!uuid_token) { //生成游客临时身份 uuid_token = uuidv4(); localStorage.setItem('UUIDTOKEN', uuid_token) } //返回id,没有return 再返回的是undefined return uuid_token; }
购物车中全选
全选分两部分:第一部分是点击全选复选框,让所有的都选上,只需要把数据中的isChecked属性都变为1即可
async updataAllCartChecked(e) { try { let isChecked = e.target.checked ? "1" : "0"; this.isAllChecked = e.target.checked; await this.$store.dispatch("updataAllCartIsChecked", isChecked); this.getData(); } catch (error) { alert(error.message); } },
第二部分是当点击其余小项时判断,若当时状态全部的小项全勾上的时候,则把全选给勾上
let flag = true; //遍历的是数组所以直接用in的话,v就是键名,数组键名为索引 //遍历判断若全是勾选状态再把全选也勾上 for (let v of this.cartInfoList) { if (!v.isChecked) { flag = false; } } this.isAllChecked = flag; this.getData(); } catch (error) { alert(error.message); } },
导航守卫
因为商城项目是要有用户,然后访问对应购物车等每个用户私有的数据的,所以在登录或未登录的时候不能跳转到一些特定的路由去,所以就要用到路由导航守卫了。
当用户未登录:
要是去订单,支付,订单详情页面都把路由跳转到登录页面,为了登录之后可以直接访问原本要去的路由,则可以用params参数转到登录页方便跳转。
当用户登录了:
要去登录或者注册页,是不行的,直接去往主页(其实这个需求可以不要)
还有就是因为token是存在浏览器本地的,而name是存在仓库的,所以要判断有token但是没有用户名,有就放行,没有则再获取一下用户信息,但这里要判断一下成功与失败,若是失败的话再要跳转到登录界面,因为你即便有token,但是长时间不登录,后端的数据会更新,从而token会失效
大概如下图,来自CSDN的@毛毛虫鸣鸣
个人中心(二级路由,路由懒加载,路由重定位)
{ name: 'center', path: '/center', component: () => import('@/pages/Center'),//路由懒加载 children: [ { path: 'myorder', component: () => import('@/pages/Center/myOrder') }, { path: 'grouporder', component: () => import('@/pages/Center/groupOrder') }, , { path: '',//当路径默认的时候(完全的路径的/center/) redirect: '/center/myorder'//重定向到myorder路由 } ] },
想要去支付页面,一定要从订单来才行,所以要用独享路由守卫
{ name: 'pay', path: '/pay', component: () => import('@/pages/Pay'), beforeEnter: (to, from, next) => { //只有是从trade订单来的才能进入pay支付路由 if (from.path == '/trade') { next() } else { next(false) } } },
使用图片懒加载(用vue-lazyload插件)
图片懒加载
//引入插件(图片懒加载) import VueLazyload from 'vue-lazyload' //引入懒加载的图片 import loadimage from '@/assets/images/111.jpg' //注册图片懒加载插件 Vue.use(VueLazyload, { loading: loadimage, })
使用plugins表单验证插件(其实用element-UI也行)
eg:手机号 :
<div class="content"> <label>手机号:</label> <input placeholder="请输入你的手机号" v-model="phone" name="phone" v-validate="{ required: true, regex: /^1\d{10}$/ }" //以1开始,后面10个数字 :class="{ invalid: errors.has('phone') }" //否则提示 /> <span class="error-msg">{{ errors.first("phone") }}</span> </div>
为了方便也是全局引入
import "@/plugins/validate"
再新建一个文件使用插件
//vee-validate插件:表单验证区域 import Vue from 'vue' import VeeValidate from 'vee-validate'; //中文提示 import zh_CN from "vee-validate/dist/locale/zh_CN" //使用注册 Vue.use(VeeValidate) //表单验证 VeeValidate.Validator.localize("zh_CN", { messages: { ...zh_CN.messages, is: (field) => `${field}必须与密码相同`,//修改内置规则的message,让确认密码与密码相同 }, attributes: { phone: "手机号", code: "验证码", password: "密码", password1: "确认密码", agree: "协议" } }) //自定义校验规则,必须要打勾才行 VeeValidate.Validator.extend("tongyi", { validate: (value) => { return value }, getMessage: (field) => field + "必须同意" })
大概的项目就是这样了,项目配置,因为现在默认是vue3了,所以像脚手架一定要安装对应的版本,最新本是不能向下兼容的。还有不是所有路由中,footer组件都要显示的所以要加一个属性,来判断是否显示,路由中不能直接绑定自定义属性所以要在meta配置项下定义一个show来判断。还有就是因为要解决跨域问题,这次项目通过代理的方法解决,在vue.confing.js中加入
devServer: { proxy: { '/api': { target: 'http://gmall-h5-api.atguigu.cn',//访问的接口地址 } } }
还有因为要请求,所以就要涉及到异步,就要用到async与await配合使用,若要判断成功还是失败,还要用到try……catch()
再一个问题就是在切换组件的时候,因为三级联动组件不仅在home路由中使用,在别的也要,所以要注册为全局组件,还有就是切换的时候会销毁组件,但是在一些路由中要用到,为了不重复得请求接口,只发一次请求,就把请求放在App.vue中mounted中(对仓库的操作)
整个项目完全,差不多不到两周的时间,其中学到了很多东西,编程还是要打代码才可以,很多问题就是在实践中发现的,所以我打算再做一个后台项目,加深对vue2的使用,之后再学小程序,再深入原码。不得不说因为这个项目是培训机构上的课,不是专门拿出来当做网上的白嫖福利,所以开发周期太长了,不过好在非常得细。