尚品汇前台项目总结

这是我的第一篇个人博客,主要是对我学习前端过程中的一些主记录。由于我在江苏,从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创建了一个很简易的个人博客。还有很多不足希望大佬可以提出来,感谢大佬可以给我的学习过程中提供帮助,我会继续学习,继续更新博客的。

本人博客: http://8.130.22.116

下面是具体细节问题:

三级联动路由传参:

三级联动

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的使用,之后再学小程序,再深入原码。不得不说因为这个项目是培训机构上的课,不是专门拿出来当做网上的白嫖福利,所以开发周期太长了,不过好在非常得细。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340