一、前言
学习前端已有大半年了,虽然其中备考软件设计师考试花了两个月,但我还是收获颇丰,从最开始的html,到css,js,在到es6,promise,ajax,node.js、vue、webpack我已经有较为靠谱的编码习惯,亲身接触并实行语义化,模块化,注重复用性的项目,了解了节流,防抖,图片懒加载,路由的动态引入,npm,yarn的常用语句,linux常用命令,nginx的首次使用,整个网页从制作到上线腾讯云的linux服务器,在到域名的注册和ssl的申请,当然这次的网站我没有注册域名,就简单的配置了下nginx反向代理。这次网页的开发很好的增长我的编码经验。
二、项目里值得讲的一些点
一、路由的动态引入
在接触这个项目以前,我以前只知道常规的import 引入,相关的知识也就是什么默认暴露,分别暴露啊,引入的时候可以解构啊,在往远了讲,还会个commandjs里的require引入,什么动态引入,真没接触过。
普通写法:import myorder from "@/pages/Center/myorder"
//不管现在要不要用直接引入
动态写法:()=>{
return import("@/pages/Center/myorder")
}
好处:需要用到这个组件的时候在引入,极大节省了内存和空间,可以减少dist文件的大小,
进一步简写:
直接在路由里注册时引入的
component:()=>import("@/pages/Center/myorder"),
二、js的执行顺序,异步和同步
js的执行顺序,先同步后异步, 异步中任务队列的执行顺序: 先微任务microtask队列,再宏任务macrotask队列, Promise 里边的代码属于同步代码,.then() 中执行的代码才属于异步代码。
常见的宏任务:ajax、setTimeout、setIntervar、requestAnimationFrame、messageChannel、UI 渲染、setImmediate
常见的微任务:promise.then、queueMicrotask(基于 then)
项目里的bug
项目里有个给购物车设置数量的按钮,在我设置了正则表达式,以及blur事件限制他不能为负数后,这里设置change没用,我也不知道为啥,然后我当时上线项目之前根本没有发现问题,因为如果你正常频率的点击减1,购物车肯定不会出现负数,但这只防正人君子,在我首次上线后,便分享给我的同学,让他去试下支付接口,然后这家伙可能也是无聊,也算严谨,疯狂给我点击减少,然后竟然出现了负数,他截图给我发来的时候我都惊呆了!
也就是说,如果你点击的频率很快,这个数量会变成负数,然后我就郁闷了,
然后就试了各种方法,什么节流和防抖,但因为服务器本身反应慢,在加这玩意就很不怎么流畅,(在组件里加防抖是没有任何效果的,但在actions里面写防抖的确有效)当时我还特意重新研究了下我10多天前写的源码,不得不说,才仅仅10天前写的我就基本不怎么记得了!!!然后回忆大法之后,发现判断是否为负数的来源不严谨
来源:
...mapGetters(["carList"]),
carInfoList() {
return this.carList.cartInfoList || [];
},
这里是通过actions里面向服务器发请求,拿到的现在购物车改商品的数据,然后存在state里,然后如果正常来讲我发一个减少1的请求,你更新一下,啊我就减1,但是如果你不是正人君子,疯狂点减少,然后会出现一种情况,你这边上一次的减1的请求还没有返回,跟更新到vuex里,数据还是上一次的,而你这边又请求-1,然后他拿着你本应该-1的数据去判断,而你没有-1,就会造成一种数据没有更新,请求又发了的糟糕情况,而我这里用一个timer去判断是不是上一次请求结束没有是可以的,但是因为定时器的加入会有点卡,于是我就想了个野路子,就是商品在减到只剩一件的时候,会有个弹框提示商品只剩一件,直接打断施法,等弹框取消后,vuex早就更新了,根据我的判断语句,为1的时候,你点-1,也是为加个0,就出现不了负数了。为此我还特意研究了什么js的执行顺序,哈哈由于服务器性能,就还是没有写防抖。
判断
if (disNum == -1) {
disNum = cart.skuNum > 1 ? -1 : 0;
}
三、服务器接口的使用(vuex和直接在组件里发请求)
第一阶段发axios请求数据或者mock数据
这里写了个拦截相应器,加了给进度条的插件
真正的发请求
import axios from "axios"
import nProgress from "nprogress"
//引入store
import store from "@/store"
import "nprogress/nprogress.css"
const request=axios.create({
//设置路径后默认追加的路径
baseURL:"/api",
timeout:5000
})
//请求拦截器,发请求前可以做一些事情
request.interceptors.request.use((config)=>{
//config中有header
//进度条开始
if(store.state.detail.uuid_token){
config.headers.userTempId=store.state.detail.uuid_token
}
if(store.state.user.token){
config.headers.token=store.state.user.token
}
nProgress.start()
return config
})
//响应的拦截器
request.interceptors.response.use((res)=>{
//成功的回调
//进度条结束
nProgress.done()
return res.data
},(err)=>{
return Promise.reject(new Error("faile"))
})
export default request
使用具体的接口
// 商品详情的数据
export const reqGoodsInfo=(goodsId)=>{
return request({url:`/item/${goodsId}`,method:"get"})
}
//商品添加到购物车
export const reqAddOrupdataShopCar=(skuId,skuNum)=>{
return request({url:`/cart/addToCart/${skuId}/${skuNum}`,method:"post"})
}
//购物车请求数据
export const reqCarlist=()=>{
return request({url:"/cart/cartList",method:"get"})
}
第二阶段在vuex的action中使用写好的请求数据方法,通过mutations改变state里的值
async getGoodsInfo({commit},info){
let result= await reqGoodsInfo(info)
if(result.code==200){
commit("GETGOODSINFO",result.data)
}
},
GETGOODSINFO(state,info){
state.goodsInfo=info
}
第三阶段,通过mapstate来方法获取数据
...mapGetters(["carList"])
根据自己的业务逻辑使用数据,特别是分类和搜索页商品展现特别需要用到这个
四、微信二维码的生成(支付接口的使用)
首先得用到elementui的弹框组件
这里接需要安装element,以及按需引入
elementui的使用
import { MessageBox } from 'element-ui
二维码生成器 qrcode插件的使用
qrcode
支付二维码数据的请求,如三一致,组件通过dispatch发actions请求,actions通过引入的axios函数向服务器发请求,然后通过mutation改变state里的数据,然后在通过getters简化,最后通过mapgetters引入url,给插件。
支付后立即跳到成功页面,然后看历史的订单上面显示的是支付成功。
为了方便,我还是在支付遇到问题点击后会出现一个兑换码输入框,输入123,就可以跳到成功页面,但历史记录显示未支付。
五、token和uuid的使用
说到登录和注册,以及支付就免不了这个用户信息的验证啊,
我这里其实用的也是插件,游客用的是uuid的插件,服务器就对游客不做登记,通过seesionstore来存储一些购物车信息,正式用户就把改的数据发给服务器存储,token是通过发axios请求来的。
六、图片的懒加载和放大镜的功能
图片的懒加载是插件
VueLazyload
放大镜:
功能源码
handler(event){
let mask=this.$refs.mask;
let big=this.$refs.big
let left=event.offsetX-mask.offsetWidth/2
let top=event.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"
}
七、手写的分页
分页器其实可以不用手写,element里有,但是为了练手我还是自己写了给组件,样式没放啊,核心就是保证有continues个连续的,看1,2,最后需不需要显示
<template>
<div class="pagination" >
<button :disabled="pageNo==1" @click="$emit('getPageNo',pageNo-1)" >上一页</button>
<button v-if="start>1" @click="$emit('getPageNo',1)" :class="{active:1==pageNo}">1</button>
<button v-if="start>2" @click="$emit('getPageNo',2)" :class="{active:2==pageNo}">2</button>
<button v-if="(start-1)>2">···</button>
<button v-for="(page,index) in end" v-show="page>=start" :key="index" @click="$emit('getPageNo',index+1)" :class="{active:(index+1)==pageNo}">{{ page }}</button>
<button v-if="end+1<totalPages">···</button>
<button v-if="end<totalPages" @click="$emit('getPageNo',totalPages)" :class="{active:totalPages==pageNo}"> {{ totalPages }}</button>
<button :disabled="pageNo==end" @click="$emit('getPageNo',pageNo+1)"> 下一页</button>
<button style="margin-left: 30px">总共{{total}}条数据</button>
</div>
</template>
<script>
// pageNo 当前的页数 当前第几个
//pageSize 一页展示多少数据
// totalPages 总共有多少页
//continues 连续分页的连续页码
export default {
name:"pagination",
methods:{
},
computed:{
totalPages(){
return Math.ceil(this.total/this.pageSize)
},
locationPage(){
let start=1,end=1
if(this.continues>this.totalPages){
start=1
end=this.totalPages
}
else{
if (this.pageNo+parseInt(this.continues/2)>this.totalPages) {
start=this.totalPages-this.continues+1
end=this.totalPages
}
else if(this.pageNo-parseInt(this.continues/2)>=1){
start=this.pageNo-parseInt(this.continues/2)
end=this.pageNo+parseInt(this.continues/2)
}
else if (this.pageNo-parseInt(this.continues/2)<1) {
start=1
end=this.continues
}
else if( this.pageNo+parseInt(this.continues/2)<=this.totalPages){
end=this.pageNo+parseInt(this.continues/2)
}
}
return {start,end}
},
start(){
return this.locationPage.start
},
end(){
return this.locationPage.end
}
},
props:["pageNo","pageSize","total","continues"]
}
八、路由守卫实现购物网站应该的业务逻辑
一个合格的网站是需要一定访问限制的,比如你一个游客最多拥有查看商品和加入购物车的权限,不可能有付款和查看支付记录的权限,你都没登录怎么付款?所以就需要用到路由守卫,这里的用到了全局前置,独享路由的守卫,组件内守卫。
上述的付款多是用独享
登录后,不能在进登录页面是全局
支付成功是组件内守卫
如果
//路由组件更新的时候,如果只是params等参数的变化,组件不变,此组件会被复用
beforeRouteUpdate(to,form,next){
next()
}
全局
router.beforeEach(async(to,from,next)=>{
let token=store.state.user.token
let name=store.state.user.userInfo.name
if(token){
if(to.path=="/login"||to.path=="/register"){
next("/home")
}
else{
if(name){
next()
}
else{
try {
//獲取用戶信息的
await store.dispatch("getUserInfo")
next()
} catch (error) {
//token過期
await store.dispatch("userLogOut")
next('/login')
}
}
}
}
//没有登录不能去交易和交易记录,支付成功
else{
let topath =to.path
if(topath.indexOf("/trade")!=-1||topath.indexOf("/pay")!=-1||topath.indexOf("/center")!=-1){
next("/login?redirect="+topath)
}
else{
next()
}
}
})
这里我还加了个滚动默认为Y:0
scrollBehavior(to,from,savePosition){
return {y:0}
}
和通过meta属性来限制,foot组件,搜索框什么时候出现
九、linux使用的注意点
1、nginx方向配置和www文件存放的地方
使用xftp7的时候不要把www文件放到root下,放到var是个不错的选择、
2、使用xshell修改nginx的配置
修改nginx.conf里面的
3、图片命名的要求
刚上线时,部分图片无法正常加载,可在本机客户端运行是可以的,于是我就研究哪些不能加载的图片的共性,结果发现他们命名中都有中文,我估计是编码错误,于是改成中文之后,果然可以。
十、重写vue的push和repalce方法
let originPush=VueRouter.prototype.push;
let orignreplace=VueRouter.prototype.replace;
VueRouter.prototype.replace=function(location,resolve,reject){
if(resolve && reject){
orignreplace.call(this,location,resolve,reject)
}
else{
orignreplace.call(this,location,()=>{},()=>{})
}
}
VueRouter.prototype.push=function(location,resolve,reject){
if(resolve && reject){
originPush.call(this,location,resolve,reject)
}
else{
originPush.call(this,location,()=>{},()=>{})
}
}
因为在push的时候是要返回一个promise的,所以重写push和replace方法。
这里感谢尚硅谷提供的所有api
三、项目业务逻辑和组件的规划
一、业务逻辑
二、组件的规划
vue开发者工具的展现
首页
搜索页和其他页面就是中间这个大组件的切换,和footer,搜索框是否显示
四、项目展示
一、运行截图
1、首页
2、注册
3、登录
4、搜索
支持按价格排序,搜索,和按页数
5、详情
6、购物车
7、确认收货人和地址
8、付款
9、付款成功
10、查看购物记录
点这里访问 百宝阁
五、自我推荐
大三到大四的暑假想找个互联网公司,网友有推荐的吗。