vue前台项目

Vue前台项目-电商平台

技术架构:vue2+vue-cli+vuex+vue-router+axios+less

实现功能

  • 封装通用组件
  • 登录注册
  • token
  • 守卫
  • 购物车
  • 支付
  • 项目性能优化等

1. 初始化项目

vue-cli脚手架依赖环境:node+webpack

1.1 vue create 项目名称

选择安装Vue2的default

用vue-cli脚手架初始化的项目目录中

  • node_modules文件夹: 项目依赖
  • public文件夹:一般放置静态资源,webpack在打包的时候会原封不动打包到dist文件夹中
  • src文件夹(程序员源代码文件夹):
    • assets文件夹:一般放置多个组件公用的静态资源,webpack打包的时候会把静态资源当成一个模块打包到JS文件中
    • components文件夹:非路由组件
    • App.vue:唯一的根组件
    • main.js: 程序的入口文件,整个程序最先执行的文件
  • babel.config.js: 配置文件,与babel相关。
  • package.json文件: 记录项目各种信息,e.g. 项目名称、项目依赖、项目如何运行等。
  • package-lock.json: 缓存性文件

1.2 项目的其他配置

  • 项目运行起来的时候,让浏览器自动打开

    在package.json中的scripts节点下,找到serve命令,添加–open,就可以自动在输入npm run serve命令后自动打开

    “scripts”: {

    “serve”: “vue-cli-service serve –open“,

    “build”: “vue-cli-service build”,

    “lint”: “vue-cli-service lint”

    }

  • 将eslint校验功能关闭

    在vue.config.js的导出中,添加:lintOnSave: false

  • 配置src目录路径的简写形式。因为我们程序员需要经常大量的操作src目录下的文件,避免各种../路径的查找,我们可以配置给src一个别名,如:@。

    在jsconfig.json中

    “compilerOptions”: {

    “baseUrl”: “.”,

    “paths”: {

    “@/“: [“./src/“]}

2. 项目的路由分析

我希望达到的效果是在/home地址中展示主页,在/search地址中展示搜索页面,但是在切换页面时,整体页面的header和footer部分是不变的,故考虑将这两个组件放到非路由组件,而不同地址对应的页面中间部分的组件考虑放到路由组件中。

其他页面同理,拆分路由组件和非路由组件。

3. Header-非路由组件

注意,非路由组件放到components节点里。

3.1 布局

在抽取组件内容之前,我先写了静态的HTML页面搭配Less样式和js行为。

  • .top:头部的第一行,可以进行点击跳转,设置行高等于height可以让字体垂直居中,设置背景颜色。再将内容都包含在其中的版心模块container,设置margin: 0 auto, overflow: hidden

    • .loginList: 需要贴着版心的左侧显示,设置向左浮动

    • .typeList: 向右浮动

      对于文字中间的 | 分割,可以考虑用展示左边框实现。在less文件中,使用&+a选择除了第一个a以外的所有a设置border-left

  • .bottom: 头部的第二行,主要是搜索区域,同样设置居中

    • .logoArea:在头部放置logo图片的位置,向左浮动

      其中,图片img首先需要被a包裹,确保可以进行点击,a还需要被h1包裹,以便于SEO优化

    • .searchArea: 搜索栏的位置,向右浮动

      中间只有一个form表单元素。form中包含input和button。

3.2 抽取为组件

  • 在components节点下新建Header文件夹,里面新建myHeader.vue头部组件,以及存放logo的images文件夹

  • 将样式粘贴到头部组件的template中

  • 将less样式粘贴到style中,注意要确保项目安装了less、less-loader,还需给style标签添加lang=”less”属性

  • 将reset.css清除默认样式的css文件放到静态资源public中

4. Footer-非路由组件

4.1 布局

footer.footer设置一个背景颜色,里面只包含一个footer-container,设置成版心格式。在版心中包含上下两个部分

  • .footerList: footer的上面部分,其中又分为六个部分,统一给类名:.footerItem再统一浮动起来在一排显示。每个.footerItem里包含一个h4标题和一个ul,只有最后一个的ul替换为img
  • .copyright: footer的下面部分,由一个ul、两个p组成。

4.2 抽取为组件

和3.2同理

4.3 显示与隐藏的控制

Footer组件虽和Header组件同为非路由组件,但只在Home、Search界面中同时出现Header和Footer,登录注册界面没有Footer只有Header,在这里进行Footer的显示与隐藏的控制。控制组件的显示与隐藏有以下三个方案:

  • v-if:具有很高的切换开销

  • v-show:

    • 在App根组件中利用v-show控制footer的显示与隐藏,利用$route.path的值来判断式哪个页面

      < Footer v-show=”$route.path==’/home’ || $route.path==’/search’” >< /Footer >

    • 问题:如果这里可展示的组件有100个,写起来太麻烦了

    • 利用路由元信息meta:在index.js路由规则配置中,加入对象格式的meta元信息,存储是否展示footer的布尔值。

      meta的获取:$route.meta

    code

  • 动态组件component: 通过对is属性的赋值来控制展示什么组件,同样需要搭配路由元信息的使用

    code

5. 路由组件的搭建

5.1 vue-router的安装

注意!vue-router3.x配合vue2.x使用,vue-router4.x配合vue3.x使用

5.2 router文件夹的创建

如果使用vue-cli脚手架搭建的时候没有选router,则需要在项目中install然后在src目录下新建pages文件夹,用来存放路由组件

根据项目需要,在里面给每一个组件新建一个文件夹。

5.3 配置路由

在项目中配置路由一般在src->router文件夹中

  • 新建router文件夹,在里面新建index.js文件用来配置路由

    • 因为Vue-router是Vue的插件,先引入Vue
    • 再引入Vue-router
    • 使用Vue.use(VueRouter)方法来使用vuerouter这个插件
    • 用new VueRouter()创建一个路由实例router,在里面配置路由
      • routes数组声明路由的匹配规则,每条规则用对象的形式,path指定hash地址,component指定需要展示的组件,所以还需要再index.js中import所需的所有路由组件
      • 我们希望一进来就直接展示首页,这里做路由的redirect重定向
    • 向外导出路由实例router
  • 我们需要让index.js生效,所以需要在入口文件main.js中导入并挂载路由模块

    • 这里需要注意!!挂载router的时候就是在注册路由信息,这样可以使每!个!组!件!(路由和非路由都有)身上都有$route, $routers属性
    • $route: 一般使获取路由信息的,e.g. 路径、query、params等
    • $router: 一般进行编程式导航进行路由跳转, e.g. push、replace
  • 我们的路由还需要一个展示的出口,在App.vue中router-link声明路由链接和router-view占位符

5.4 路由跳转

路由跳转有两种形式:声明式导航和编程式导航

回顾:编程式导航能做所有声明式导航可以做的,并且还能实现一些其他的业务逻辑

  • 在我们的Header组件中,将包含登录的a改造成router-link, to属性设置为/login,注册、点击图片。

  • 我们还需要在点击搜索按钮的时候跳转到search,这里尝试用编程式导航

    给button绑定点击事件goSearch,在methods中声明,处理函数为this.$router.push(‘/search’)

5.5 路由传参

知识点可以参考我的另一篇笔记:路由传参

现在我想实现的业务逻辑为:当点击搜索的button按钮进行路由跳转的同时,将前面input输入框的值传递到Search组件中

code

注意

  • 路由传参的时候,对象的写法可以是name或者path形式,但是path不能和params参数一起
  • 在配置路由的时候,params已经占位了(/search/:keyword), 但是如果路由跳转的时候没带params就会出现路径上的问题无法跳转,如果想指定params可传可不传,需要在配置路由的时候,params占位后面加个?

问题】:现在我们发现,使用编程式导航进行路由跳转时,如果参数不变,多次执行就会抛出redundant navigation的错误警告

原因】:打印了以下this.$router.push的返回值,发现是一个Promise类型,但是promise需要成功、失败的回调函数

解决1】:想着给this.$router.push再传两个空的函数,this.$router.push({name: ‘search’, params: {keyword: ‘’}, query: {k: this.keyword.toUpperCase()}}, () => {}, () => {}); //但是这种解决方法在别的组件中每次都要这么写

解决2】:改写push方法

首先通过打印,发现push方法是VueRouter.prototype上的方法

$router是VueRouter类的一个实例

VueRouter.prototype.push = function() {

//上下文是VueRouter类的一个实例}

改写push

6. 三级联动-全局组件

因为三级联动这个功能在很多组件中都需要使用,现在想把三级联动抽成一个组件的时候就考虑全局组件,只需要注册一次就可以全局使用。

6.1 布局

.typeNav添加下边框且包含.container来控制这一行导航居中展示。

  • .all全部商品分类给宽高和红色背景

  • .nav包含了导航栏其他的窗格,其中每个窗格都被一个a包裹

  • .sort就是竖着这个精细的导航栏部分,想用定位定到全部商品分类的下面,所以添加position: absolute同时给父元素container添加position: relative。其中每一个分类都是一个.item,为了完成三级联动的效果,每个.item除了包含本身左侧的h3标题,还包含要在中间展示的具体内容.item-list

    • .item-list: 首先在我们不用鼠标经过的时候是不展示的,设置display:none;其次,展示的时候位置需要通过定位的形式,添加position: absolute,通过left:.sort的宽度就可以定位到贴着sort的右侧展示的位置;子元素.sub-item向左浮动。

      中间的内容每一条选择用自定义表单元素dl进行包含,dt是左侧的,dd是右侧的

6.2 注册为全局组件

抽取出template中的UI结构以及style组成typeNav.vue

需要在main.js中引入并Vue.component(全局组件的名字,哪个组件)注册为全局组件

6.3 在Home组件中使用

因为已经是注册为全局组件了,不需要引入注册,可以直接使用~

7. ListContainer组件:包含轮播图和快报

7.1 布局

.list-container设置版心结构,加个padding-left展示出6的三级联动组件。

  • .center是轮播图模块:找到想要的效果的demo对应的html文件打开复制样式、结构和初始js

  • .right就是轮播图右侧的模块:

    • .news是尚品汇快报模块:其中第一行用h4展示,里面两个模块分别向左右浮动;第二行的.news-list中放ul展示新闻

    • ul.lifeservices是三行小图标模块:设置为display:flex布局,为了让中间的li可以多行展示,再添加flex-wrap: wrap属性

      其中小图标选择使用精灵图,给每个.list-item设置精灵图为background-image,然后再通过nth-child选择器给每个item调整background-position

    • .ads是最下的广告模块:选择插入一张广告图,并且添加transition属性,当鼠标经过hover的时候opacity从0.8变1

7.2 在Home组件中使用

这次需要严格按照引入、注册、使用的步骤

8. Home首页剩下的其他组件

8.1 Recommend今日推荐

包含container做版心的居中对齐,其中包含一个ul.recommend中每个li是一个推荐。

8.2 Rank商品排行

  • .tab: 上面的切换栏。设置margin: 0 auto水平居中。给每个块用a包裹达到点击的效果,每个a里面包含上一个img下一个p。
  • .content: 下面的内容区域。一看到这种整齐的结构就考虑用ul>li去呈现。给每个item设置一个红色的border,里面再分别包含上面图片下面相关信息。

8.3 Like猜你喜欢

  • .title: 上面的文字+可点击的换一换
  • .bd: 还是考虑用ul>li去呈现每个item。

8.4 Floor可以做组件的复用

  • .title: 上面的标题和导航栏。一个h3左浮动,一个ul>li*7右浮动
  • .tab-content: 内容栏。每一列是一个子元素,需要分裂的.split。

8.5 Brand商标组件

看到整齐的结构,ul>li,只不过每个li包含一个商标的图片

9. axios二次封装

为了设置请求拦截器和响应拦截器

首先需要在项目装安装axios【axios知识点可以参考我的另一篇笔记:数据的请求与发送

且在项目中通常在src下面的api文件夹存放axios相关js文件。故在src下新建api文件夹,在文件夹中新建request.js来使用axios发送请求。在这里对axios进行二次封装。

9.1 配置axios实例

利用axios.create()方法可以创建一个axios实例,传入配置对象就可以进行配置。配置对象中比较常用的两个属性:

  • baseURL:基础路径,比如我们的接口文档中路径都带有api,就可以配置上,免得手写
  • timeout:设置请求超时的时间

9.1 请求拦截器

请求拦截器:在发送请求之,请求拦截器可以检测到,就可以在请求发出去之前做一些事情。

9.2 响应拦截器

响应拦截器:在服务器响应之后,响应拦截器可以检测到,这时候可以完成一些业务逻辑,比如设置成功和失败的回调

axios二次封装

10. api接口统一管理

因为项目较大,在api下新建index.js用来统一管理api接口。

10.1 跨域问题

在api->index.js文件中我们向外暴露了一个获取三级联动数据的函数,但是出现了404错误,原因是跨域了。

跨域协议、域名、端口号不同请求,就成为跨域。(非同源

Reference: 你还不知道跨域问题是怎样造成的吗? - 知乎 (zhihu.com)

http://localhost:8080/#/home 这是前端项目的本地服务器

http://gmall-h5-api.atguigu.cn 这是后台服务器

10.2 跨域问题解决

由于跨域问题只存在于浏览器间,服务器间不存在,故考虑使用代理服务器!!

在vue.config.js中配置代理proxy:只要请求路径中有/api,代理服务器就会去找真实服务器请求数据

code

11. nprogress插件显示进度条

首先业务逻辑是开始发请求的时候就开始显示进度条,服务器的数据返回之后进度条结束。

想到可以在请求和响应拦截器中使用nprogress的start和done方法。

然后引入nproress进度条css样式即可。如果想要修改样式就到css中去修改就行了~

12. 在项目中使用vuex状态管理库

vuex:官方提供的一个插件,(vue-router也是),是做状态管理用的库,可以集中式管理项目中组件公用的数据

12.1 vuex的安装

我用的vue2.x,要注意npm i –save vuex@3.2.0才能版本适配

12.2 在项目中配置vuex

src下新建store文件夹。在其中新建index.js来配置vuex。

下面就可以类比配置vuerouter的步骤了。

  • 引入vue和vuex,vue.use(vuex)注册为插件

  • 向外暴露Vuex.Store({配置对象})的这样一个实例对象

    其中,配置对象主要有以下几个属性:

    • state:仓库存储数据的地方
    • mutations:修改state的唯一手段
    • actions:处理action
    • getters:理解为计算属性,用于简化仓库数据
  • 在入口文件中引入、注册仓库。此时,所有的组件身上会带有$store的属性了。

12.3 利用vuex进行模块化开发

在store下面为每个模块新建一个文件夹,分别存储这个模块里的数据,在文件夹中新建index.js文件,向外暴露state、mutations、actions和getters对象。

再在store下面的index.js中的modules进行引用:

code

13. 三级联动的静态页面改写

现在我们的三级联动TypeNav组件里面的内容都是我们手动写死的,但是项目接口文档中指出了请求路径和方式,我们要把里面手动写死的数据替换为动态请求来的数据。

13.1 何时发送请求?

我们考虑在组件挂载完毕,就可以通知vuex向服务器发送请求数据,存储在仓库中

  • 挂载完毕:就是在组件的mounted()声明周期函数里完成这个发送请求的业务逻辑

  • 通过vuex进行数据的统一管理

    • 因为是要发送数据请求,所以需要异步操作,要用this.$store.dispatch(‘’)派发actions里的函数

    • 我们已经封装好了发送请求的函数,引入调接口即可

    • 注意axios发出的数据请求返回的是一个Promise对象,为了拿到调用成功的结果,使用async/await对函数进行改造

    • 如果请求成功,则需要修改state里面的数据,所以我们需要传入context参数,这里选择对齐做解构赋值{commit}作为参数,判断如果res.code == 200, 那么通过commit再去提交mutation去修改state中的值

      这里注意一下在vuex的使用中,为了方便这些函数间的相互调用,每个对象里方法的参数!!!注意!!!

      Reference:(70条消息) vuex commit 传递多个参数的两种方法_vuex传递多个参数_hbwhypw的博客-CSDN博客

13.2 请求到了数据如何修改template?

现在已经拿到了数据,所以将我们的template中的所有并列的一级结构中除了第一个都删了,给第一个加v-for指令。

v-for=”(c1, index) in categoryList”

注意!!v-for还要绑定key!!!

code

13.3 卡顿现象引入防抖与节流

我们发现,如果从全部商品分类快速的用鼠标下划,并不会触发所有一级分类的mouseenter事件的回调函数,这是由于用户操作过快,且每一次的触发,回调函数都需去执行,这时候如果事件很短且回调函数内部有计算,很有可能出现浏览器卡顿现象的出现。

防抖前面的所有触发都被取消,最后一次执行在规定的时间之后才会触发。也就是说如果连续快速的触发,只会执行一次

节流在规定的间隔时间范围内不会重复触发回调,只有大于这个时间间隔才会触发回调。也就是说把频繁触发变为少量触发

所以我们考虑在三级联动的模块使用lodash添加节流的技术,防止mouseover事件触发的太频繁造成的卡顿现象。

但是我们es6语法的methods中的格式没法用lodash,考虑写es5格式的kv

changeIndex: _.throttle(function(id) {

this.currentIndex = id;

},50)

13.4 三级联动路由跳转 - 编程式导航+事件的委派

在我们的三级联动模块,业务需要点击一级或二级或三级分类标题就可以跳转到搜索模块,并将用户选中的产品名字和id在路由跳转的时候进行传递。

这时候路由跳转我们有两种选择:声明式导航和编程式导航。

虽然声明式导航router-link可以实现路由的跳转和参数的传递,但是会造成卡顿的现象!!因为它可是一个组件!!在for循环的时候创建太多的组件再渲染是一件高开销的事情。故尝试编程式导航。

但是给每个a都加一个click事件的处理函数进行this.$router.push的跳转虽然会比声明式导航好一些,但是也是大量的回调函数。

考虑事件的委派!!!太妙了啊!!!!给就近的父盒子添加@click事件

但是还仍然存在两个问题:

  • 如何知道点击的一定是a标签才触发的父盒子的click事件呢?

    ans:利用自定义属性,给所有子节点中的a都加上data-categoryName

  • 现在已经知道是a标签,如何区分是一级?二级还是三级?

    ans:可以借鉴上面的思想,也利用自定义属性,分别给一二三级a标签添加自定义属性data-category1Id、data-category2Id、data-category3Id来进行区分

  • 整理参数就可以跳转啦~~~

14. Search页面

14.1 Search页面引入三级联动并加入过渡效果

因为三级联动组件TypeNav已经注册为了全局组件,直接在search页面中引入即可。

但是现在和我们想要的效果还相差甚远啊55555

  • 问题1:我们希望已进入search界面这个三级联动的下拉菜单是默认隐藏的,当鼠标经过才下拉显示

    解决:在TypeNav组件中,给下拉的导航栏首先尝试写死v-show:’false’达到目的,现在我们就考虑通过定义一个变量navShow的值来动态的控制下拉导航栏的显示与隐藏。

  • 问题2:那么从home跳转到search组件的时候,哪里会有区别可以改写navShow的值呢?

    解决:mounted!!!!!

    mounted() {

    ​ if(this.$route.path!=’/home’) {

    ​ this.navShow = false

    }}

  • 问题3:如何添加过渡动画效果?

    解决:要注意组件或者元素能够添加过渡动画效果的前提是必须有v-if/v-show指令!!!

    用transition标签将包含v-show的内容包含,其中过渡动画的效果是用指令来完成的。

14.2 从Home首页跳转到Search页面的参数合并

现在我们有两个入口可以从Home首页跳转到Search页面,且我们分别在搜索框的button按钮和三级联动写好了路由传参。

但是现在业务想要实现的逻辑是,如果有params就把params和query一起传递然后跳转到search。

解决:我们在三级联动中push之前先判断if(this.$route.params)如果为true,就合并参数再传递。同理在header组件中push之前也先判断有无query参数~

14.3 Search模块数据操作

现在写好的是静态的页面,我们还是需要向服务器发送请求、将数据存储到vuex的仓库中、再在Search组件中动态展示数据。

14.3.1 api

在api/index.js中写上获取搜索商品的数据的部分,根据接口文档,注意发起axios请求的类型是POST

这个地方要注意发起axios请求的post的语法,axios({method:’post’, url: ‘xxx’, data: xxx})传递post的参数要用data!!

且根据接口文档,传的参数起码是一个空对象。

14.3.2 vuex操作

注意现在已经要切换到search小仓库啦~

然后就是vuex的三连环操作,actions中发请求,mutations中修改state中的数据就已经存储到仓库中了

在组件中引用mapState把仓库的数据映射成组件身上的数据

14.3.3 在组件中动态展示数据

本来的写法:computed: {

…mapState({

goodsList: state=>state.search.searchList.goodsList

})}

但是这个地方太复杂了,想要在仓库中先利用getters简化一下数据再在search组件中使用mapGetters将getters映射成组件自己的计算属性

注意!state是分模块的(home、search等模块)所以mapState({})中间是对象的格式,但是getters部分模块,直接用数据的形式就行

改进

code

14.4 发送请求的位置改写

现在我们的请求数据的代码是写在了mounted钩子函数中,但是这只会执行一次,我们的业务需求是当携带不同的参数进行查询时,都应当返回不一样的数据。

考虑将发送请求的部分封装在函数(methods)里,这样就可以声明一次,多次调用。

考虑watch监听路由的变化再次发送请求获取新的数据

我们选择在beforeMount声明周期函数中,修改params这样的话就可以在mounted前有要在mounted中传递的参数了

这里的小trick:

原始版本:this.searchParams.keyword = this.$route.qarams.keyword

​ this.searchParams.category1Id = this.$route.query.category1Id,…..

改进版本使用es6语法:Object.assign方法

​ Object.assign(this.searchParams, this.$route.params, this.$route.query)

14.5 分页器

14.5.1 将静态页面中的分页器摘出来做全局组件

全局组件书写的位置:src->components->Pagination->myPagination.vue

在main.js中注册全局组件

14.5.2 对自定义分页器使用假的数据进行调试

在父组件Search.vue中通过自定义属性向子组件传递假数据:

  • pageNo:当前是第几页的数据
  • pageSize:每一页有多少条数据
  • total:一共有多少条数据
  • continues:连续的页码数

e.g. < Pagination :pageNo=”1” :pageSize=”3” :total=”91” :continues=”5”>< /Pagination>

Q1: 如何算出连续页面的起始数字和结束数字?

Ans:这个地方需要考虑很多种情况。

  • continues > 一共的页数
  • continues <= 一共的页数 :
    • 出现负的页码数
    • 超出totalPages
    • 正常

code

14.5.3 向服务器发送请求真实的数据动态展示

我们将分页器的结构分为三部分:

  • 上一页 + 1 + …
  • 中间的连续页数部分
  • … + totalPages + 下一页

这个时候我们要分别考虑这三块的显示与隐藏:

  1. 考虑中间的部分: 将start和end中间的数字push到一个数组中,用v-for遍历展示。
  2. 第一部分:
    • 1:如果start > 1就显示
    • … : 如果start > 2就显示
  3. 第二部分:
    • totalPages : 如果end<totalPages就显示
    • … : 如果end < totalPages - 1就显示

为每个button添加点击事件,通过自定义事件向父组件传递pageNo,父组件向服务器请求数据,将父组件Search中传递的数据替换成服务器返回的数据。

15. Home首页

在Home首页,我们可以从服务器请求回来的数据只有三级联动里面的数据。

其他的数据服务器都不提供,就需要模拟mock数据,这里利用第三方插件mock.js: 生成随机数据,拦截Ajax请求

也就是说前端mock的数据不会和服务器进行任何的通信。

首先,在我们的项目中安装mock.js:

mock.js的使用步骤:

  • src下创建mock文件夹,在文件夹中创建相应的json文件

  • 将mock数据需要的图片放到public文件夹中

  • 在mock文件夹下创建mockServe.js来进行相应的配置。

    • 引入mockjs

    • 引入两个json文件

      注意!!!webpack默认json文件是对外暴露的

    • Mock.mock(rurl,template): 当拦截到匹配rurl的ajax请求时,将根据数据模板template生成模拟数据并作为响应数据返回。

  • 将我们的mockServe.js文件在入口文件main.js中引入

15.1 ListContainer组件

在mounted生命周期函数中发请求,mock回来的数据用vuex存储到仓库中。

现在有了数据了,要开始对轮播图进行动态的展示。

  • 引入swiper的js和css

  • new Swiper实例(新版本的Swiper可能不是这种写法了)

    问题!!!!我发现在mounted中写这个new Swiper不起效果!!!!

    原因v-for!!!!是动态创建的!!!!!!!!!!!!!!!前提是服务器返回了数据我才能生成对应的DOM,这是异步的!!!!!

    【解决】:watch+nextTick

    • watch对bannerList进行监听:从初始化的空数组到接收到服务器返回来的数据,定义handler函数,函数内部肯定是在已经拿到了数据的基础上进行操作的。

      但是还不行!!!!!!

      原因:还是因为结构的问题。当前的handler执行了只能保证有数据,但是v-for动态的往上渲染不能保证完毕。

    • $nextTick: 在下次DOM更新循环结束之后执行延迟回调。在修改数据后立即使用这个方法获得更新后的DOM

code

问题:vue中尽量不使用原生的这个js获取元素的写法!!!

改进:使用ref引用啊!!!

给container添加ref属性=”myswiper“,

ref引用

15.2 Floor组件

和上面15.1的逻辑大差不差,不做过多展示。

16. 详情页

src->pages->Detail->myDetail.vue

16.1 注册为路由组件

在router->index.js中引入Detail组件并且在routes数组中添加路由规则。注意要用id占位。在路由跳转带上id。

16.2 解决在搜索页最底部点击商品跳转到详情页滚轮还在底下的问题

利用Vue路由进阶-滚动行为scrollBehavior,注意和routes平级

scrollBehavior(to, from, savedPosition) {

//return 期望滚到的地方

return { y: 0 }

}

16.3 放大镜功能的实现

16.3.1 放大镜的布局

![放大镜.png]

通过.event:hover触发.big和.mask的显示

其中.big通过定位left:100%,overflow:hidden和提升z-idex=999使得在盒子右侧压住其他内容显示。

其中的img就需要将width和height设置为200%,又因为本身img容器.big是overflow:hidden就会模拟出放大镜的效果嘞!!!!

16.3.2 绑定mousemove事件使得mask随着鼠标移动

在上述样式中,我们的mask的位置是写死的,现在需要给event添加mousemove事件,触发时使得mask随着鼠标移动。

核心思想通过ref引用拿到mask的DOM对象的定位的left和top属性,将其改写为鼠标当前坐标(offsetX/offsetY) - 1/2(mask自身宽度offsetWidth/高度)

这里注意clientX/offsetX/pageX的区别https://www.jianshu.com/p/a52077e8369d

还需要注意:对于没有设置宽度的元素或者是css设置宽度非内嵌式的,DOM.style.width并不能拿到宽度值,只能设置宽度值。只能通过offsetWidth拿到宽度

【问题:还需要对mask的范围进行约束】

top和left同理,只拿left记录:

  • 异常1:left<=0, 直接强制赋值left=0
  • 异常2:left>= 盒子宽度 - mask自身宽度(这时候mask的右侧边边超出去了!!),强制赋值left=这个最大的left值

16.3.3 根据mask修改好的left和top修改大图定位的坐标

注意现在大图big是按照2倍进行放大,那么left和top值也得是2倍!

16.4 加入购物车功能的实现

16.4.1 购买产品个数操作

给input表单元素绑定v-model,数据双向绑定!!

给+和-添加点击事件,给个数做+1和-1操作,需要注意个数的取值范围,必须大于等于1。

现在我们还希望用户可以自己输入购买的个数,那么就要注意判定输入的合法性啦~~

为input表单绑定change事件,在文本框中的值发生变化且丧失焦点时触发。

(这个操作存在的合理性就在于:我们使用v-model双向绑定后不管用户输入什么都可以直接赋值给skuNum,但是我们想通过change事件的处理函数先检查e.target.value的合法性再存。)

  • Q: 如何判断用户输入的带字符?

    A: 文本 * 1 只要是Nan就是!!!

    测试

    数字字符串*1是数字本身,只要带有其他字符的 * 1就是Nan

16.4.2 点击加入购物车需要实现的功能

  1. 向服务器发送请求,将产品加入到数据库

  2. 若服务器存储成功,进行带参路由跳转

    需要传递的太多了,如果通过query传参,url地址贼混乱

    考虑query传参只带个数,结合会话存储传递产品信息数据(复杂)

    不考虑localStorage的原因:有上限(5M左右)、且持久化(购物的信息不需要一直存储在本地)

    但是我本来想把skuInfo这个对象直接存储在sessionStorage里的!!失败了!!

    localStorage和sessionStorage都不能存储对象!!都是按键值对存储字符串!!存储的时候使用JSON.stringfy()将对象转化为字符串再存!!!从storage中取的时候也得用JSON.parse()将字符串再转化为对象进行使用!!!

  3. 若服务器存储失败,给用户进行提示

    catch(error) {alert(error.message)}

17. 加入购物车成功页面

  • 通过点击查看商品详情回退到详情页:

    两种思路:

    • 绑定点击事件,处理函数中this.$router.go(-1)回退到上一页
    • 利用声明式导航router-link带着id参数进行跳转
  • 通过点击去购物车结算的按钮: 路由跳转到 ‘/shopcart’, 展示ShopCart组件

18. 购物车页面

第一步还是熟悉的操作啊…

向服务器发送请求获取购物车的数据+操作vuex三连环+拿到数据在组件中动态的展示数据

还是同样的问题!!!注意this.$store.dispatch派发actions要写在methods的某个函数中,可以多次调用获取数据!!!不能写在mounted周期函数中

可是现在的问题是我们发现:从服务器返回的数据是个空数组

why?

问题出在了16.4.2添加购物车这个步骤我们需要再额外传递用户身份的信息!!!

身份有两种:登录了的用户 / 游客身份

16.6.1 增加detail模块中加入购物车功能时传递的参数

利用uuid第三方库生成唯一的用户id来标识不同用户。

由于现在我们希望每个用户作为游客身份的时候不管何时再次浏览都是同样的ID!!!考虑localStorage!!

在src下新建utils文件夹,常放项目中的功能模块,如正则、临时身份等等。

但是uuid()方法每次调用都会生成一个新的唯一的标识id,我们希望可以对于一个用户只生成唯一的一个标识!!

解决:先判断本地存储有没有,没有就用uuid生成并存储,有了就直接getItem!

Q:如何在发送请求时带上用户身份id?

A:在请求头中加入UUID标识!

在请求拦截器中配置,代码如图

code

16.6.2 利用计算属性才完成全选按钮的状态

一开始没思路,记得todolist就是这个操作!!!

对产品array进行遍历!!用array.every更简单

return this.cartInfoList.every(item => item.isChecked == 1) //evey方法发现数组的每一个都是勾选的状态就会返回true,不然返回false

16.6.3 点击+-对产品购买数量进行修改

这个地方除了需要完成对数量的修改操作外,还需要注意,每点击一次都需要发送请求,告诉服务器购物车模块里的数据变化。然后再从服务器获取最近的数据再展示。

现在我们发现不管点击+还是-还是在input中修改数量最终都会触发同一个action,注意在方法中传递参数可以给三种不同的操作区分开。

另外!!!!!!为了避免用户点的太快!!!!!!!!!!用上节流(还是熟悉的节奏,引lodash)

16.6.4 在购物车中删除某产品

这里发送请求的方式是DELETE

16.6.5 删除全部选中的商品和全部商品勾选

对于这种需要一键删除全部或者选中宣布的功能,肯定是需要遍历我们的购物车数据存放的数组的。

但是我们现在的actions里只有一次删除一条或者一次勾选上一条的函数,那么大体的思路就是遍历数组,符合条件的多次派发action

我们想在一个action中派发另一个action并且想拿到全部的购物车商品数据:就需要在占位中解构出{dispatch,getters}

我们每次派发action都会有一个promise返回值,那我们怎么在我们现在的点击事件中拿到唯一的返回的promise值呢?

利用Promise.all([p1,p2,…])方法!!!数组中都是promise,只有都为成功时,Promise.all就会返回成功,有一个失败就是失败啦~

code

19. 登录注册页面

19.1 注册页面

19.1.1 点击按钮获取验证码

业务逻辑:点击按钮发送请求,需要携带的参数就是用户第一行填写的手机号值。

19.1.2 点击完成注册按钮进行跳转

业务逻辑:点击完成注册,先向服务器发送请求,如果成功就跳转到登录页,失败就停在注册页。

19.2 登录页面

19.2.1 token

现在我们点击登录按钮,带着手机号和密码向服务器发送请求,服务器成功的data里面会返回一个token

token令牌,是用户的唯一标识符

前台需要持久化存储这个token,这样的话我们就可以拿着token去找服务器要这个用户的各种数据了。

Q1:我们向服务器发送了登录请求后,服务器成功返回唯一的用户标识token,我们现在只是在vuex的仓库中进行存储,but!!!vuex并不能持久化存储,一刷新就没了,我们想持久化存储token怎么办?

Ans1:拿到token就存在localStorage中

Q2:我们在发送请求得到res==200后,进行路由跳转到home组件,还是和之前一样!!怎么才能展示用户名和退出登录的部分呢?

Ans2:还需要带着token再次向服务器发送请求555555,api: /user/passport/auth/getUserInfo,get请求。因为接口没有占位,我们想get还带参数的话,用请求头带进去。注意派发action的时间是在主页的mounted渲染好后派发!在Header组件中,需要根据有无用户信息来动态展示左上角登录的地方,考虑v-show/v-if

19.2.2 退出登录

需要做的事情:

  • 发请求,通知服务器清除一些数据如token
  • 清除前台项目里的数据如token、userInfo等
  • 退出成功就回到首页

19.3 导航守卫

导航守卫:可以控制路由的访问权限。

分为:全局导航守卫、路由独享守卫、组件内守卫

全局守卫又分:全局前置守卫、全局解析守卫和全局后置守卫

我们在这里使用全局前置守卫,router.beforeEach(to,from,next)

待解决问题1:现在用户已经登录了但是还能跳转到login页面

解决:通过登录用户才有token来判断!再进行全局守卫的判断和放行。

if(token) {

​ // 成功登录的状态

​ if(to.path == ‘/login’) { 通过to.path来判断去哪!!

​ next(‘/‘)}

​ else{

​ //去除了login以外的其他页面

​ if(name) {

​ next()}

​ else {

​ //没有用户信息

​ try {

​ await store.dispatch(‘getUserInfo’);

​ next()} catch(error) {

​ //有token但是捞不到用户的信息!!!(有可能token国企捏)先清空信息再跳到login界面

​ await store.dispatch(‘logOut’);

​ next(‘/login’)}

​ }}

}

路由独享守卫beforeEnter: (to, from, next) => {}

现在我们希望对于想跳到trade的路由必须得从shopcart购物车跳过来才行!!!

其他的就停留在原地,next(false)

{

path: ‘/trade’,

component: Trade,

meta: { show: true },

//路由独享守卫

beforeEnter: (to, from, next) => {

​ if (from.path == ‘/shopcart’) {

​ next()

​ } else {

​ next(false)

​ }

},

},

其他的同理,比如pay支付页必须从trade页跳来等等。

20.交易页面

20.1 用户地址信息的显示

在正常的发请求->vuex存数据->组件中捞并替换为动态展示的数据之后

我们希望可以实现点击哪个地址就给哪个地址加上被勾选的状态:排他

且不管是点人还是点地址都想触发click,就委派给父亲p标签~~~

函数传递的参数 - 点击的address和数组addresses

给数组进行遍历全部清除相关样式,再给点击的那个添加样式

20.2 提交订单

之前都是固定操作:写api发请求,拿到数据写vuex,在组件中捞数据进行展示

现在想不用vuex,直接在组件中存数据。

但是我又不想在每一个组件中分别引用api里写好的方法,怎么才能将api里的方法暴露一次就全局可用了呢?

想到了event bus!!和它类似操作

main.js中引入api中全部的请求函数

import * as API from ‘@/api/index.js’,这个对象就包含了所有的请求方法

然后Vue.prototype.$API = API!!!!!直接挂载到vue原型对象的身上。所有的组件可以直接使用。


但是在我们获取支付信息的时候,遇到了在mounted钩子函数中调用API发送请求,不要给周期函数加async

巧妙的解决办法:在生命周期函数中写个this.某个函数,再在methods中声明这个方法即可~~~

20.3 立即支付

业务需求:点击立即支付会弹出一个担忧微信支付二维码的弹窗。

我自己写的话会采用加一个遮罩层再加上z-index。

这里用elementUI组件库的MessageBox弹框组件

组件库总结:

React: pc - antd 移动 - antd-mobile

Vue: pc - ElementUi 移动端 - vant

这个二维码支付成功与失败需要向服务器发送请求才能知道。

那么什么时候发?发几次?

我们需要在展示二维码的时候用定时器一!!!直!!!!发!!!!

20.4 查看订单

点击查看订单跳转到个人中心

21.个人中心页面

21.1 二级路由的搭建

code

注意二级路由的路径的写法!!

21.2 请求我的订单的数据

api: ‘/api/order/auth/{page}/{limit}’

注意何时向服务器发送请求要数据呢?

有分页器需要每次点击都重新获取,所以不考虑写在mounted里,应该写在methods里,然后在mounted和每次点击分别调用

22.图片懒加载

利用插件:vue-lazyload@1.3.3


vue前台项目
http://example.com/2023/07/18/vue前台项目/
Author
John Doe
Posted on
July 18, 2023
Licensed under