【Vue2项目(尚品汇前台)】(二)Home首页模块搭建

一、Home首页的开发步骤

Home首页可以拆分为7个组件,分别是:TypeNav三级联动导航,ListContainer,Recommend,Rank,Like,Floor,Brand

1、导航栏TypeNav三级联动组件(全局组件)

在components下创建TypeNav组件(前面说到过全局组件创建在components中)
在这里插入图片描述
在这里插入图片描述
设置全局组件的三个步骤:

  1. 创建组件
  2. 引入组件并设置全局组件
  3. 使用组件,不需要导入声明

这里我遇到了一个问题:vue router路由跳转了,但是页面没有变
解决方法:App.vue文件模板里加一句<router-view></router-view>

<template>   
	 <div>
    	<Header></Header>
    	<router-view></router-view>
    	<Footer v-show="$route.meta.showFooter"></Footer>
   	</div> 
</template>

2、其余局部组件

  1. 创建组件
  2. 导入结构和样式还有图片
  3. 引入、注册、使用

在这里插入图片描述

二、请求服务器的准备工作

1、axios二次封装

向服务器发请求的方式有:XMLHttpRequest,fetch,JQ,axios

我们使用axios,安装:脚手架目录下 npm install --save axios

  1. 为什么需要进行二次封装axios?
    请求拦截器、响应拦截器:请求拦截器,可以在发请求之前处理一些业务;响应拦截器:当服务器数据返回以后,可以处理一些事情

  2. 在项目中出现api的文件夹,一般都是放axios请求的

//对于axios进行二次封装
import axios from "axios";

//1. 利用axios对象的方法create,去创建一个axios实例
//2. request就是axios,只不过稍微配置一下
const requests = axios.create({
    //配置对象
    //基础路径,发请求的时候,路径当中会出现api
    baseURL:"/api",
    //代表请求超时的时间5s
    timeout:5000,
});
//请求拦截器:在发请求之前,请求拦截器可以检测到,可以在请求发出去之前做一些事情
requests.interceptors.request.use((config)=>{
    //config:配置对象,对象里面有一个属性很重要,headers请求头
    return config;
});

//响应拦截器
requests.interceptors.response.use((res)=>{
    //成功的回调函数:服务器响应数据回来之后,响应拦截器可以检测到,可以做一些事情
    return res.data;
},(error)=>{
    //响应失败的回调函数
    return Promise.reject(new Error('faile'));
})

//对外暴露
export default requests;

2、api接口统一管理

// src/api/index.js
//本文件用于:API的统一管理
import requests from './request';

//三级联动(导航部分)的接口
// /api/product/getBaseCategoryList get 无参数
export const reqCategoryList = function () {
    //发请求:axios发请求返回结果是Promise对象
    return requests({
        url: '/product/getBaseCategoryList',
        method: 'get'
    });
}

在main.js中测试api

import {reqCategoryList} from '@/api'
reqCategoryList();

会发现报404错误,数据无法从服务器获取
在这里插入图片描述

这是因为跨域的问题
从这里http://localhost:8080/#/home ----前端项目本地服务器
向这里发请求 http://gmall-h5-api.atguigu.cn ---- 后台服务器

之前说到过跨域问题,解决的方法有jsonp、CORS、代理跨域问题

这里我们使用代理的方法
在vue.config.js文件中添加:

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  // 关闭eslint
  lintOnSave: false,

  //配置代理解决跨域请求问题
  devServer:{
    proxy:{
      '/api':{
        target:'http://gmall-h5-api.atguigu.cn',
        //因为此项目前面带了baseUrl都设置了带api,这里就不用设置了
        // pathRewrite:{'^/api':''},
      }
    }
  }

})

3、nprogress进度条的使用

安装脚手架:npm install --save nprogress

作用:只要项目发出请求,进度条就会开始走,服务器数据返回之后,进度条就结束
用在 请求和响应拦截器中 src/api/request.js

(1)引入进度条和进度条样式,设置发送请求时开始,和成功请求后结束
在这里插入图片描述
(2)修改进度条的颜色
在node_modules文件中的nprogress.css文件找到.bar的background

三、Vuex模块式开发

vuex是官方提供的一个插件,状态管理库,集中式管理项目中组件共用的数据(并不是全部项目都需要,如果项目很小,完全不需要vuex)

安装脚手架:npm i vuex@3

在这里插入图片描述

1、TypeNav导航三级联动

(1)当组件挂载完毕,就向服务器发请求

在这里插入图片描述

(2)去home仓库请求数据

//home模块的小仓库
import { reqCategoryList } from "@/api";
const state = {
    //state中数据默认初始值别瞎写
    categoryList:[],
};

const mutations = {
    CATEGORYLIST(state,categoryList){
        state.categoryList = categoryList;
    }
};

const actions = {
    async categoryList({commit}){
        let result =await reqCategoryList();
        // console.log(result);
        if(result.code == 200){
            commit("CATEGORYLIST",result.data);
        }
    }
};

(3)TypeNav接收数据

import {mapState} from 'vuex';
export default {
    ...mapState({
      //右侧需要的是一个函数,当使用这个计算属性的时候,右侧函数会立即执行一次
      //注入一个参数state,其实即为大仓库中的数据
      categoryList:state=>state.home.categoryList.slice(0,16)
      // categoryList:(state)=>{
      //   return state.home.categoryList;
      // }
    })
  }
};

(4)渲染数据

接收来的数据就是这样的:
在这里插入图片描述

<div class="item" v-for="(c1,index) in categoryList" :key="c1.categoryId">
            <h3>
              <a href="">{{ c1.categoryName }}</a>
            </h3>
            <div class="item-list clearfix">
              <div class="subitem" v-for="(c2,index) in c1.categoryList" :key="c2.categoryId">
                <dl class="fore">
                  <dt>
                    <a href="">{{c2.categoryName}}</a>
                  </dt>
                  <dd>
                    <em v-for="(c3,index) in c4.categoryChild" :key="c3.categoryId">
                      <a href="">{{c3.categoryName}}</a>
                    </em>
                  </dd>
                </dl>
              </div>
            </div>
          </div>

在这里插入图片描述
在这里插入图片描述

(5)三级联动的防抖和节流

1、防抖和节流的介绍
  1. 防抖(回城技能)
    前面的所有的触发都被取消,最后一次执行在规定的时间之后才会触发,也就是说如果连续快速的触发,只会执行一次
    (简单来说就是回城技能)
const result = _.debounce(function(){
    
},1000)
  1. 节流(回城cd)
    在规定的间隔时间范围内不会重复触发回调,只有大于这个时间间隔才会触发回调,把频繁触发变为少量触发
const result = _.throttle(function(){
    
},1000)

lodash插件中封装了防抖与节流的业务【闭包+延迟器】

2、三级联动节流操作
//按需引入
import throttle from '_lodash/throttle'

methods:{
	changeIndex:throttle(function(index){
		this.currentIndex = index;
},50)
}

(6)、三级联动的路由跳转与传参

  1. 需求分析
    当你点击一个分类的时候,会从home模块跳转到search模块,并且把分类的名字categoryName和ID传递给search模块,然后search模块拿到这些参数向服务器发请求展示相应数据
  2. 解决方案
    (1)路由跳转可以用<router-link></router-link>,但是这样的话<router-view>会生成好多组件,页面会卡
    (2)如果只用编程式导航,要给每个a加点击事件
    (3)最好的解决方案是编程式导航+事件委派

事件委派
给每个a标签添加@click=“goSearch”
在这里插入图片描述
事件委派的问题:
(1)如何确定我们点击的一定是a标签呢?如何保证我们只能通过点击a标签才跳转呢?
(2)如何获取子节点标签的商品名称和商品id(我们是通过商品名称和商品id进行页面跳转的)

解决方法:
问题1:为三个等级的a标签添加自定义属性date-categoryName绑定商品标签名称来标识a标签(其余的标签是没有该属性的)。

问题2:为三个等级的a标签再添加自定义属性data-category1Id、data-category2Id、data-category3Id来获取三个等级a标签的商品id,用于路由跳转。
我们可以通过在函数中传入event参数,获取当前的点击事件,通过event.target属性获取当前点击节点,再通过dataset属性获取节点的属性信息。

在这里插入图片描述

methods: {
    goSearch() {
      //第一个问题:把子节点当中a标签,我加上自定义属性data-categoryName,其余的子节点是没有的
      let element = event.target;
      //获取到当前触发这个事情的节点【h3/a/dt/dl】,需要带有data-categoryname这样的节点【一定是a标签】
      let { categoryname, category1id, category2id, category3id } =
        element.dataset;
      //如果标签身上拥有categoryname一定是a标签
      if (categoryname) {
        //整理路由跳转的参数
        let location = { name: "search" };
        let query = { categoryName: categoryname };
        //一级分类、二级分类、三级分类的a标签
        if (category1id) {
          query.category1Id = category1id;
        } else if (category2id) {
          query.category2Id = category2id;
        } else {
          query.category3Id = category3id;
        }
        //整理完参数
        location.query = query;
        //路由跳转
        this.$router.push(location);
      }
    },
  },

(7)Search模块中三级联动的显示与隐藏

①显示与隐藏

当我们在home页面时,TypeNav导航栏三级联动要一直显示,但是在其他组件,例如search中,我们要隐藏TypeNav,让它具有隐藏和显示的功能
1、我们可以在TtpeNav组件中加入一个数据show用来决定组件的显示和隐藏,默认值为true
在这里插入图片描述
在这里插入图片描述
2、利用事件委派添加鼠标进入和鼠标离开函数的回调和show还有v-show来控制是否隐藏和展示
3、当从Home进入到Search的时候,TypeNav会再重新挂载,所以当进入Search的时候,让show变成false,而且要判断只要不是往主页跳,挂载完都要隐藏
4、添加鼠标进入和鼠标离开的回调函数
在这里插入图片描述

②过渡动画

前提组件或者元素务必要有v-if或者v-show指令才可以进行过渡动画

用transition标签包住带有v-show属性的标签,加一个name,然后去写css样式

// 三级导航内容部分过渡动画
    //过渡动画开始的状态
    .sort-enter,
    .sort-leave-to {
      height: 0px;
    }

    //过渡动画的结束状态
    .sort-enter-to,
    .sort-leave {
      height: 461px;
    }

    //定义动画时间,速率
    .sort-enter-active,
    .sort-leave-active {
      overflow: hidden;
      transition: all 0.2s linear;
    }

(8)TypeNav商品分类列表ajax重复请求问题

在这里插入图片描述

在这里插入图片描述
我们之前的方法是写在TypeNav组件中的mounted钩子函数中,如果来回切换,就会重复发起TypeNav中的数据请求
解决方法:把请求放入到App.vue文件中,当我们打开网页的时候,最先开启的是App.vue,这样请求只会发送一次,想用数据直接去Vuex仓库那就可以了

(8)合并params和query参数

我们往Search组件跳转有两种方式,一种是通过搜索关键字跳转,另一种是点击三级导航的链接跳转,三级导航传的是query参数,搜索传的是params参数
在这里插入图片描述
在这里插入图片描述
但是这两种方式只能传一个地方的组件,如果先三级导航跳转再搜索的话,搜索传的params参数和空query参数会覆盖三级导航传的query参数和空params参数
所以我们可以两边都通过一个判断来实现参数的合并,就是params和query参数一起到地址栏中。

  if(this.$route.query){
    // 代表的是有query参数也带出去
    let location = {
      name:"search",
      params:{keyword:this.keyword || undefined}};
      location.query = this.$route.query;
      this.$router.push(location)
    }
  }

2、ListContainer、Floor组件

(1)mockjs模拟数据

安装脚手架:npm install mockjs

  1. 在src文件夹下创建mock文件夹,来存放json假数据
  2. 准备json数据
  3. 把mock数据需要的图片放置到public文件夹中【public文件夹在打包的时候,会把相应的资源原封不动打包到dist文件夹中】
  4. 创建mockServer.js通过mock.js插件实现模拟数据
  5. mockServer.js文件在入口文件中引入(至少需要执行一次,才能模拟数据)
//MockServer.js文件
//先引入mockjs模块
import Mock from "mockjs";
//把JSON数据格式引入进来[JSON数据格式根本没有对外暴露,但是可以引入]
//webpack默认对外暴露的:图片、JSON数据格式
import banner from './banner.json';
import banner from './floor.json';

//mock数据:第一个参数请求地址    第二个参数:请求数据
Mock.mock("/mock/banner",{code:200,data:banner});//模拟首页大的轮播图的数据
Mock.mock("/mock/floor",{code:200,data:floor});
  1. 在main.js中引入一下,让假的数据一上来就执行一次
//引入MockServer.js ---mock数据
import '@/mock/mockServer'

(2)mock虚拟数据的ajax请求

跟之前二次封装axios数据一样,复制一份,再配置一个,把baseUrl的属性改为/mock
在这里插入图片描述
在统一管理api的文件中引入并配置接口:
在这里插入图片描述

(3)ListContainer组件

1、获取Banner轮播图的数据

我们会把公共的数据放在store中,然后使用时再去store中取。

  1. 在轮播图组件ListContainer.vue组件加载完毕后发起轮播图数据请求。
 mounted() {
   //派发action:通过Vuex发送ajax请求,将数据存储在仓库中
   this.$store.dispatch("getBannerList");
 },
  1. 请求实际是在store中的actions中完成的
const actions = {
    // 获取首页轮播图的数据
    async getBannerList({commit}) {
        let result = await reqGetBannerList();
        console.log(result);
        if (result.code == 200) {
            commit("GETBANNERLIST", result.data);
        }
    }
};
  1. 获取到数据后存入store仓库,在mutations完成
const mutations = {
    CATEGORYLIST(state, categoryList) {
        state.categoryList = categoryList;
    },
    GETBANNERLIST(state,bannerList){
        state.bannerList = bannerList;
    }
};
  1. 轮播图组件ListContainer.vue组件在store中获取轮播图数据。由于在这个数据是通过异步请求获得的,所以我们要通过计算属性computed获取轮播图数据。
//ListContainer.vue
import { mapState } from "vuex";
export default {
  name: "",
  mounted() {
    //派发action:通过Vuex发送ajax请求,将数据存储在仓库中
    this.$store.dispatch("getBannerList");
  },
  computed: {
    ...mapState({
      bannerList: (state) => state.home.bannerList,
    }),
  },
};
2、制作ListContainer轮播图

swiper安装脚手架:npm install --save swiper@5 使用文档

使用swiper分三步:
1.引入相应的包
2.页面结构必须先有(给轮播图添加静态效果)
3.有结构后再new Swiper实例(给轮播图添加动态效果)

  1. 引包
    在main.js中引入swiper样式(main.js是全局样式,在这引入全都可以使用)
//引入swiper样式
import "swiper/css/swiper.css"
  1. 在ListContainer中引入swiper
//swiper
import Swiper from 'swiper';
  1. 搭建轮播图页面结构
    v-for遍历返回的数据,把返回的数据渲染到页面中
    在这里插入图片描述

  2. 添加轮播效果
    如果把效果放到mounted中可以吗?
    在这里插入图片描述
    当然不可以,我们在mounted中先去异步请求了轮播图数据,然后又创建的swiper实例。由于请求数据是异步的,所以浏览器不会等待该请求执行完再去创建swiper,而是先创建了swiper实例,但是此时我们的轮播图数据还没有获得,就导致了轮播图展示失败。

解决方案:
第一种:延时器
设置一个延时器,等我们的数据请求完毕后,再创建swiper

mounted() {
    //派发action:通过Vuex发送ajax请求,将数据存储在仓库中
    this.$store.dispatch("getBannerList");
    setTimeout(() => {
      var mySwiper = new Swiper(document.querySelector(".swiper-container"), {
        loop: true,
        //如果需要分页器
        pagination: {
          el: ".swiper-pagination",
        },
        //如果需要前进后退按钮
        navigation: {
          nextEl: ".swiper-button-next",
          prevEl: ".swiper-button-prev",
        },
      });
    }, 2000);
  },

但是这种方法有一种弊端,就是你不知道ajax什么时候请求结束,所以你不好控制定时器的事件,所以我们不采用

第二种:使用watch监听

watch: {
    //监听bannerList数据的变化,因为这条数据发生过变化---由空数组变为数组里面有四个元素
    bannerList: {
      handler(newValue, oldValue) {
        //现在咱们通过watch监听bannerList属性的属性值的变化
        //如果执行handler方法,代表组件实例身上这个属性的属性已经有了【数组:四个元素】
        //当前这个函数执行,只能保证bannerList数据已经有了,但是你没办法保证v-for已经执行结束了
        var mySwiper = new Swiper(document.querySelector(".swiper-container"), {
          loop: true,
          //如果需要分页器
          pagination: {
            el: ".swiper-pagination",
            //点击小球的时候也切换图片
            clickable: true,
          },
          //如果需要前进后退按钮
          navigation: {
            nextEl: ".swiper-button-next",
            prevEl: ".swiper-button-prev",
          },
        });
      },
    },
  },

使用watch监听会发现,没办法换页啊,watch监听是监听bannerList从空数组变成有数组,然后再执行下面的换页功能,但是当这个函数执行时,只能保证bannerList数据已经有了,但是你没办法保证v-for已经执行结束了,才导致功能无法显示。

第三种:watch + nextTick(完美方案)
watch:监听bannerList数据的变化,如果执行handler方法,代表组件实例身上这个属性的属性值数组已经有了mock数据
nextTick:在下次DOM更新 循环结束之后 执行延迟回调。在 修改数据之后 立即使用这个方法,获取更新后的DOM(简单点说就是获取了最新的数据并且渲染成功之后,再执行的方法)

watch: {
    //监听bannerList数据的变化,因为这条数据发生过变化---由空数组变为数组里面有四个元素
    bannerList: {
      handler(newValue, oldValue) {
        //现在咱们通过watch监听bannerList属性的属性值的变化
        //如果执行handler方法,代表组件实例身上这个属性的属性已经有了【数组:四个元素】
        //当前这个函数执行,只能保证bannerList数据已经有了,但是你没办法保证v-for已经执行结束了
        this.$nextTick(() => {
          var mySwiper = new Swiper(
            document.querySelector(".swiper-container"),
            {
              loop: true,
              //如果需要分页器
              pagination: {
                el: ".swiper-pagination",
                //点击小球的时候也切换图片
                clickable: true,
              },
              //如果需要前进后退按钮
              navigation: {
                nextEl: ".swiper-button-next",
                prevEl: ".swiper-button-prev",
              },
            }
          );
        });
      },
    },
  },

(4)Floor组件

1、获取Floor的数据

还是之前的那一套

我们的mock数据里面是有两个数据,因为我们有两个floor组件,那我们能在floor组件里创建dispath吗?那显然是不行,没办法拿数据,那怎么办呢?
解决方案:在home中dispath(做不了的事交给父亲)
但是这里有个问题,因为我们上面用dispath把数据交给了父亲,但是floor要拿到里面的数据,那要如何拿呢?
解决方案:父子组件通信

组件通信的方式有哪些?
props
自定义事件:@on @emit
全局事件总线:$bus 全能
pubsub-js:vue当中几乎不用 但是全能
插槽
vuex

在父文件中的floor组件中加入:list="floor"
在这里插入图片描述
在子组件中添加props
在这里插入图片描述

2、渲染数据

拿到数据就简单了,v-for和插值语法就可以渲染数据了
在这里插入图片描述

在这里插入图片描述

2、Floor的轮播图
mounted() {
    var mySwiper = new Swiper(this.$refs.floor1Swiper, {
      loop: true,
      //如果需要分页器
      pagination: {
        el: ".swiper-pagination",
        //点击小球的时候也切换图片
        clickable: true,
      },
      //如果需要前进后退按钮
      navigation: {
        nextEl: ".swiper-button-next",
        prevEl: ".swiper-button-prev",
      },
    });
  },

这里为什么能直接放到mounted里面呢?
因为是父组件发请求,父组件通过props传递过来的,props已经解析完毕了,所以挂载的时候能正常获取数据

(5)封装Carsouel全局组件

在ListContainer和Floor中都用到了carsouel轮播图,我们可以把他封装成一个组件,需要的时候拿来用

在ListContainer里用的watch+$nextTick,监听bannerList变化了才执行,但在Floor中,它的数据是从父级拿过来的,当它拿到floorList的时候,已经是一个有数据的数组了,没有变化,所以用监听的方式无法获取到
解决方案:添加immediate:true(不管数据变没变先调用handler)

watch: {
    list: {
      //立即监听:不管你数据有没有变化,我上来立即监听一次
      //为什么watch监听不到list,因为这个数据从来没有发生变化
      immediate: true,
      handler() {
        //只能监听数据已经有了,但是v-for动态渲染结构我们还是没有办法确定,因此还是需要用nextTick
        this.$nextTick(() => {
          var mySwiper = new Swiper(this.$refs.floor1Swiper, {
            loop: true,
            //如果需要分页器
            pagination: {
              el: ".swiper-pagination",
              //点击小球的时候也切换图片
              clickable: true,
            },
            //如果需要前进后退按钮
            navigation: {
              nextEl: ".swiper-button-next",
              prevEl: ".swiper-button-prev",
            },
          });
        });
      },
    },
  },

在这里插入图片描述

四、总结

1、从接口获取数据

在这里插入图片描述

2、封装全局组件

在这里插入图片描述

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
【资源说明】 基于vue2和vant-ui构建的前端H5商城源码+项目说明+在线预览.zip基于vue2和vant-ui构建的前端H5商城源码+项目说明+在线预览.zip 在线预览:http://swqjuelian.github.io 改进和优化大致内容如下: 1. 首页:下拉刷新、商品item一键加收藏、加购物车、分享、图片懒加载 2. 商品详情页:增加van的SKU选择面板 3. 分类:图片懒加载、空分类van-empty 4. 搜索页面:支持上拉加载更多数据(van的list组件运用)、支持价格排序(只有价格排序接口...) 5. 购物车:SKU项左滑可一键收藏和删除、商品标签显示。 6. 结算页:添加支付宝、微信支付方式、优惠卷选择组件、收货地址编辑。 7. 提供商品收藏功能:本地localstroage保存数据、支持左滑删除收藏。 8. 提供收货地址列表、收货地址编辑、新增收货地址。 9. 个人中心:添加待评价快捷按钮。 10. 支持评价商品,针对一个订单中的某个SKU分别评价(模仿京东和淘宝) 11. 提供确认收货、取消订单功能。 12. 订单页允许快捷重新将商品加入到购物车(模仿京东和淘宝)。 13. 细节:各个组件一些CSS样式调节(主要是模仿一下京东和淘宝、会额外添加一些按钮图标之类的) 14. keep-alive 缓存首页和分类页。 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用!有问题请及时沟通交流。 2、适用人群:计算机相关专业(如计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网、自动化、电子信息等)在校学生、专业老师或者企业员工下载使用。 3、用途:项目具有较高的学习借鉴价值,不仅适用于小白学习入门进阶。也可作为毕设项目、课程设计、大作业、初期项目立项演示等。 4、如果基础还行,或热爱钻研,亦可在此项目代码基础上进行修改添加,实现其他不同功能。 欢迎下载,沟通交流,互相学习,共同进步!
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值