这是我的第一篇个人博客,主要是对我学习前端过程中的一些主记录。由于我在江苏,从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的使用,之后再学小程序,再深入原码。不得不说因为这个项目是培训机构上的课,不是专门拿出来当做网上的白嫖福利,所以开发周期太长了,不过好在非常得细。



















