Vue2电商前台项目——完成Home首页模块业务

Vue2电商前台项目------完成Home首页模块业务

Vue基础知识点击此处------Vue.js

文章目录

一、项目开发的步骤

1、书写静态页面(HTML,CSS)

2、拆分组件

3、获取服务器的数据动态展示

4、完成相应的动态业务逻辑

经分析,Home首页可以拆分为7个组件,分别是:TypeNav三级联动导航,ListContainer(列表),Recommend(推荐),Rank(商品排行),Like(猜你喜欢),Floor(楼层),Brand(商标)

二、Home首页拆分静态组件

1、完成TypeNav三级联动组件:全局组件

三级联动(导航部分)Home,Search,Detail组件都在使用,可以把它注册为全局组件,全局组件一般都创建在components里而不是pages里,这里放错了

好处:只需要注册一次,就可以在项目任意地方使用

注意:全局组件要在main.js里面注册,注册为全局组件后使用时不用再注册及引入组件

2、完成其余静态组件:局部组件

1、创建组件,组件文件夹 => index.vue

2、导入结构(html) 样式(css) ,查看图片位置有无错误进行修改(最好每个组件文件夹内都新建一个images文件夹,然后把相应的图片拿过来)

3、引入组件 => 注册组件 => 使用组件

三、请求服务器数据的准备工作

1、axios二次封装

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

这里用axios,安装:脚手架目录下 npm i axios

  • 为什么需要二次封装axios?

    因为要用到 请求和响应拦截器。

    请求拦截器:可以在发请求之前处理一些业务。

    响应拦截器:当服务器数据返回以后,可以处理一些事情

  • 在项目当中经常会出现 api 文件夹,一般是放关于【axios】请求的。
    baseURL:'/api',:基础路径,发请求的时候,路径当中会出现基础api
    timeout:5000,: 代表请求超时的时间5s,在5s之内没有响应就失败了

axios基础不好的,可以参考 git | axios 文档,后边要补一下axios的内容。

js 复制代码
//src/api/request.js
//对axios进行二次封装
import axios from "axios";

//1、利用axios对象的方法create,去创建一个axios实例
//2、request就是axios,只不过配置了一下
const request = 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接口统一管理

项目很小:完全可以在组件的生命周期函数中发请求

项目大,有很多接口:axios.get('xxx')

js 复制代码
// 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里测试是否能发送ajax请求

js 复制代码
import { reqCategoryList } from './api';
reqCategoryList();

这里会出现跨域问题

什么是跨域:协议,域名,端口号不同的请求,称之为跨域

从这里http://localhost:8081/#/home ----前端项目本地服务器

向这里发请求 http://gmall-h5-api.atguigu.cn ---- 后台服务器

跨域的解决方案:JSONP,CROS,配置代理等

具体配置代理怎么搞详见:脚手架配置代理

3、nprogress进度条的使用

安装nprogress插件npm i nprogress

只要项目当中发请求,进度条就开始往前动,服务器数据返回之后,进度条就结束

用在 请求和响应拦截器中 src/api/request.js

nprogress.start方法:进度条开始
nprogress.done方法:进度条结束

注:进度条的颜色可以在nprogress.css里面进行修改。

四、Vuex模块化开发

vuex是官方提供的一个插件,状态管理库,集中式管理项目中组件共用的数据。

切记,并不是全部项目都需要vuex

如果项目很小,完全不需要vuex

如果项目很大,组件很多,数据很多,数据维护很费劲,需要vuex

安装vuex npm i vuex@3,Vuex知识和使用详情点击此处进行了解或复习------Vuex笔记

由于使用单一状态数时,应用的啊u哦有状态会集中到一个比较大的对象。当应用变得非常复杂时,store对象就有可能变得相当臃肿。

为了解决这个问题,Vuex允许我们将store分割成模块(module)。每个模块拥有自己的state、mutation、action、getter,甚至是嵌套子模块------从上至下进行同样方式的分割。

1、本项目采用Vuex模块化开发,每个模块建立自己的小仓库,然后小仓库引入到大仓库里并放到Vuex实例身上(Vuex.Store)

2、去main.js里引入大仓库,并把$store放到每个组件实例身上

五、TypeNav导航三级联动

TypeNav是全局注册组件,一般都放在components文件夹中

1、三级联动展示数据

(1)组件挂载完毕后dispatch给Vuex

(2)去home仓库请求数据

  1. actions执行的时候,要通过api里面的接口函数调用,向服务器发请求,获取服务器的数据,需要把之前的api引入进来,在这里发请求就是要调用这个reqCategoryList函数,如果请求成功(code===200),那么把数据交给mutations进行处理

  2. mutations中把数据给state

  3. state中覆盖掉初始值,注意要根据接口的返回值来初始化,服务器返回对象,初始值就是对象,服务器返回数组,初始值就是数组。这里因为后台数据响应体是数组,故初始值为一个空数组

js 复制代码
// home模块的小仓库

import { reqCategoryList } from "@/api/index";

const state = {
  //state中数据默认初始值别瞎写
  // 服务器返回对象,服务器返回数组。【根据接口返回值进行初识化】
  categoryList: [],
};
const mutations = {
  // 把数据接收一下传给state
  CATEGORYLIST(state, categoryList) {
    state.categoryList = categoryList;
  },
};
const actions = {
  // 通过api里面的接口函数调用,向服务器发请求,获取服务器数据
  // 若成功就把数据通过commit交给mutations进行操作
  async categoryList({ commit }) {
    let result = await reqCategoryList();
    console.log(result);
    if (result.code == 200) {
      commit("CATEGORYLIST", result.data);
    }
  },
};
const getters = {};

// 对外暴露
export default {
  state,
  mutations,
  actions,
  getters,
};

(3)TypeNav接收数据

写法1:使用mapState来搞个计算属性,使用对象形式,这里和之前学的不一样的地方是,属性值写成一个函数,参数是大仓库的state,可以顺着找到home的state。这样的话我们就拿到了home里请求到的数据,可以直接使用插值语法在页面使用了。

这里要注意,由于仓库中数据默认值为空,一开始其实computed读到的是空,也就是在mounted中是读不到的,但是当异步请求到数据时,仓库中数据改变,也就是computed依赖的值发生了改变,那么此时根据computed的特征,get函数会重新触发,属性值改变,导致模板重新解析。所以我们才能够把异步请求到的数据传给子组件使用。(子组件先挂载,父组件再挂载)

写法2:还有另一种写法,那就是首先小仓库home开启命名空间namespaced:true

然后dispatch时就要变成模块名/actions方法名,注意只要开启了命名空间,就要写home/categoryList,然后计算属性写数组或写法1的函数都行:

(4)把数据渲染到页面上

观察后台数据的结构,大概是这样子的:

复制代码
[
    //一级数据
    {
       [
        //二级数据
        {
            [ 三级数据{id,name},{},{}],
            id,
            name
        },
        {},
        {}
       ],
       id,
       name 
    },
    {},
    {}
]

数组里有好多对象(一级数据),对象里有数组,数组里还有对象(二级数据),对象里还有数组,数组里还有对象(三级数据)


2、一级分类动态添加背景颜色

(1)采用css样式实现

直接在下面样式加上:

css 复制代码
.item:hover {
    background: #e1564e
}

so easy,这对我们来说太简单了,没有挑战性,要做就做点难的,顺便练习下JS。

(2)通过JS实现

给一级分类添加鼠标经过事件和鼠标离开事件,把鼠标离开放到大div上(一级分类的div)。只要鼠标在二级和三级数据上,一级数据就会保持背景颜色。鼠标移出大div(也就是三级导航以外的地方),背景颜色消失。

1、先设置一个响应式属性,存储用户鼠标移上哪一个一级分类currentIndex = -1 ;代表鼠标谁都没有移上去。

html 复制代码
  <div class="item bo" 
      v-for="(c1,index) in categoryList" 
       :key="c1.categoryId"
       @mouseleave="leaveIndex">
           <h3 @mouseenter="changeIndex(index)" 
           :class="{cur: currentIndex === index}">
               <a href="">{{c1.categoryName}}</a>
           </h3>
           ......
  </div>

2、然后鼠标移上去时触发回调,拿到当前index给currentIndex,这样的话添加的:class="{cur: currentIndex === index}就会变成true,从而cur样式生效(css去写个背景色)

css 复制代码
.cur {
	background-color: #e1564e;
}
js 复制代码
 data() {
    return {
      // 响应式属性,存储用户鼠标移上哪一个一级分类
      currentIndex: -1, // 代表鼠标谁都没有移上去
    };
  },
  methods: {
    // 鼠标进入修改响应式数据currentIndex属性
    changeIndex(index) {
      // index 鼠标移上某一个一级分类的元素的索引值
      this.currentIndex = index;
    },
    // 一级分类鼠标移出的事件回调
    leaveIndex(){
      // 鼠标移出currentIndex=-1
      this.currentIndex =-1;
    }
  },

3、鼠标离开后currentIndex置为-1,就不会触发样式cur

3、JS控制二三级数据显示和隐藏

这个有很多种写法。

第一种写法比较简单,可以直接通过css样式display:none|block控制显示或隐藏。

第二种方法,就是用原生JS实现

第三种方法,用v-show实现

4、三级联动的防抖和节流

正常:事件触发的非常频繁,而且每一次的触发,回调函数都要去执行(如果时间很短,而函数内部有计算,那么很可能出现浏览器卡顿)

防抖与节流详细内容点击此处------防抖与节流

(1)什么是防抖

防抖:前面的所有的触发都被取消,最后一次执行在规定的事件之后才会触发,也就是说如果连续的快速触发,只会执行一次 ----------------------当事件被触发后,延迟 n 秒后再执行回调,返回的是一个函数。

类似于------王者回城

js 复制代码
const result = _.debounce(function(){
    ...
},1000)

(2)什么是节流

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

类似于------王者技能释放与CD

js 复制代码
const result = _.throttle(function(){
    ...
},1000)

(3)三级联动节流

鼠标来回滑动的时候,把频繁触发变成少量触发,进行节流

js 复制代码
// 这种引入的方式,是把lodash全部功能函数引入,使用时_.throttle
// import _ from 'lodash';
// 最好的引入方式:按需加载
import throttle from 'lodash/throttle'
...

  methods:{
	  // 鼠标进入修改响应式数据currentIndex属性
	  //这里不能写简写形式,返回的结果就是一个函数嘛
	  changeIndex:throttle(function(index){
	    // index 鼠标移上某一个一级分类的元素的索引值
	     this.currentIndex = index;
	  },50),
  }

注意:这里的throttle回调函数别用箭头函数,可能出现上下文this问题

5、三级联动路由跳转与传参

这里要用到自定义属性,忘了或者不熟悉点击此处------js自定义属性笔记

(1)需求分析

需求:当你点击某个分类(a链接)的时候,会从home模块跳转到search模块,并且把分类的名字categoryName和ID传递给search模块,然后search模块拿到这些参数向服务器发请求展示相应数据。

路由跳转最先想到可以用<router-link></router-link>,但是这样的话<router-view>会生成好多组件,页面会卡顿。

如果只用编程式导航,要给每个a加点击事件,也不太好。这让我想到可以在编程式导航的基础上用事件委派,也就是冒泡。

事件委派忘了或者不熟悉点击此处------

所以最好的解决方案是编程式导航+事件委派

(2)编程式导航+事件委派

事件委派可以避免给每个a去添加点击事件,方法是在包含所有a标签的父标签里(一级数据的父标签即可)添加点击事件,这样点里边的任意标签都会事件冒泡到父标签,触发点击事件。

存在一些问题:

  • 第一个问题:事件委派,是把全部的子节点【h3、dt、dl、em】的事件都委派给了父亲节点。需求是点击a标签的时候才会进行路由跳转【怎么确定点击的一定是a标签呢】

  • 第二个问题:即使确定点击的是a标签,如何区别点击的是一级、二级、三级分类的标签

解决:自定义属性

  • 给所有a标签添加自定义属性data-categoryname,然后使用事件对象event拿到当前点击对象event.target的event.target.dataset,返回的是该元素身上的所有自定义属性,并使用解构赋值来接收,如果categoryname这个属性不为空,那么就说明点击的是a标签

  • 如何获取参数【1,2,3级分类的产品的名字和ID】?

    名字好说,解构赋值都已经拿到了,主要是id怎么搞?还是自定义属性,分别给每一级别数据的a标签添加自定义属性

使用解构赋值接收,然后做个判断,如果拿到一级数据的id,就传一级数据,二级传二级,以此类推,最后写出来的点击事件函数长这样:

js 复制代码
  goSearch(event) {
      // 最好的解决方法:编程式导航+事件委派
      // 存在一些问题:
      // 第一个问题事件委派,是把全部的子节点【h3、dt、dl、em】的事件都委派给了父亲节点。需求是点击a标签的时候才会进行路由跳转【怎么确定点击的一定是a标签呢】
      // 第二个问题:即使确定点击的是a标签,如何区别点击的是一级、二级、三级分类的标签

      // 解决:
      // 第一个问题:把子节点当中的a标签,加上自定义属性data-categoryName,其余的子节点是没有的
      // 获取当前出发这个事件的节点,需要带有data-categoryname这样的节点【一定是a标签】
      // 节点有一个dataset属性,可以获取节点的自定义属性与属性值
      let { categoryname, category1id, category2id, category3id } =
        event.target.dataset;
      // 如果标签身上拥有categoryname一定是a标签
      if (categoryname) {
        //1.首先定义一个query参数对象,先把categoryname拿过来
        let querydj = { categoryName: categoryname };
        //2.判断,如果收到了一级数据的id,就给querydj添加属性和值,以此类推
        if (category1id) {
          querydj.category1Id = category1id; //添加属性
        } else if (category2id) {
          querydj.category2Id = category2id; //添加属性
        } else {
          querydj.category3Id = category3id; //添加属性
        }
        //这样querydj拿到了类别名字和id,就可以传参了
        this.$router.push({
          name: "sousuo",
          query: querydj, //把上面定义的那玩意儿拿过来
        });
      }
    },

6、Search模块中三级联动的显示与隐藏

(1)实现显示与隐藏效果

需求:我们要做的是在home主页中保持显示三级联动模块,在其他组件中(如Search)默认上来隐藏,然后鼠标移入显示,鼠标离开隐藏。

  1. 我们可以在TtpeNav组件中加入一个数据show用来决定组件的显示和隐藏,默认值为true
  1. 利用v-show和数据show来判断是否显示三级导航的内容部分
  1. 当从Home进入到Search的时候,TypeNav会再重新挂载,所以当进入Search的时候,让show变成false,而且要判断只要不是往主页跳,挂载完都要隐藏(这样的话能够保证主页的三级导航一直显示,避免bug)
  1. 配置回调函数,离开时加个判断,在home中就一直显示,其他组件鼠标离开即隐藏
js 复制代码
// 当鼠标移入时,让商品分类列表进行展示
    enterShow() {
      this.show = true;
    },
    // 当鼠标离开时,让商品分类列表进行隐藏
    leaveShow() {
      this.currentIndex = -1;
      if (this.$route.path != "/home") {
        this.show = false;
      }
    },

(2)添加过渡动画

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

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 .1s linear;
}

7、解决三级导航ajax请求重复发送的bug

要点:所有只触发一次的行为,最好都写到app.vue中。这是因为App是唯一的根组件,它的mounted只会执行一次,且App组件早就先执行的。

之前我们是把下面这段代码写到TypeNav组件的mounted钩子中,但是这样只要home和search来回切换,就重新发起TypeNav中的数据请求,这样连续发送ajax请求不太行啊,所以我们把请求放到App.vue中,这样请求只会发送一次,想用数据直接去Vuex仓库拿就完事。

js 复制代码
// 派发actions。通知Vuex发请求,获取商品分类三级列表的数据,存储在仓库当中
    this.$store.dispatch("categoryList");

8、合并params与query参数

我们往Search组件跳转有两种方式,一种是通过搜索关键字跳转,另一种是点击三级导航的链接跳转,三级导航传的是query参数,搜索关键字传的是params参数

但是这两种方式只能传一个地方的组件,如果先三级导航跳转再搜索的话,搜索传的params参数和空query参数会覆盖三级导航传的query参数和空params参数。

所以我们可以两边都通过一个判断来实现参数的合并,就是params和query参数同时到url中。

这里有个疑惑的点,当this. r o u t e . p a r a m s 或 t h i s . route.params或this. route.params或this.route.query为空对象时,为什么也能进if里??因为我上来直接点链接(此时明明query是空),也能跳转,好奇怪,空对象是true吗,如果是的话,那这个判断条件完全可以不写。

发现问题原因:空对象既不是true也不是false。空对象或者空数组,都是构造函数的实例化对象,ta们就算没有自定义的属性或者元素,但是其本身是有定义好的属性和方法的,所以写在if里也能够执行条件体。所以这里的if判断写了个寂寞。。。

六、开发Home首页中的ListContainer、Floor组件

由于接口返回的后端数据只有商品菜单分类数据(三级联动),对于ListContainer组件与Floor组件数据服务器没有提供,我们这里要用到mock模拟数据。mock能够模拟后台的数据,但是只是在前端自己用,前端mock数据不会和服务器进行任何通信

1、mock搭建模拟数据

mock模拟数据:如果你想mock数据,需要用到一个插件mockjs:安装npm i mockjs

1、在项目src文件夹中创建mock文件夹--------提供假数据的文件夹

2、准备JSON数据的两个文件(记得格式化一下)

(1)首页广告轮播数据: src/mock/banner.json,数据来自老师给的文件

(2)首页楼层数据: src/mock/floor.json,数据来自老师给的文件

3、把mock数据需要的图片(包括轮播图和floor)放置到public/images文件夹中【public文件夹在打包的时候,会把相应的资源原封不动打包到dist文件夹中】

4、在mock文件夹下创建mockServe.js,并利用mockjs模块模拟出来假数据,这里要注意url路径是以/mock开头的,后边配置axios时要改一下基础路径。

js 复制代码
// 引入mockjs模块
import Mock from "mockjs";
// 把JSON数据格式引入进来
// JSON不用对外暴露也可以引入
// webpack默认对外暴露:图片、JSON数据格式
import banner from "./banner.json";
import floor from "./floor.json";

// mock数据:第一个参数是请求地址,第二个参数是请求数据
Mock.mock("/mock/banner", { code: 200, data: banner }); //模拟首页大轮播图的数据
Mock.mock("/mock/floor", { code: 200, data: floor }); //模拟首页的楼层

5、在main.js里引入src/mock/mockServe.js,让配置假数据一上来就先执行一下

js 复制代码
//引入模拟数据的js文件,让它先执行一下
import './mock/mockServe';

2、mock虚拟数据的ajax请求

之前二次封装的axios是用来给后台服务器发送ajax请求的,那虚拟数据该如何请求?很简单,只需要在api文件夹中再配置一个axios,只把其中的baseURL属性值替换成我们在src/mock/mockServe.js文件中配置的数据路径/mock

然后去统一管理API的文件(src/api/index.js)中配置轮播图部分的接口:

3、获取Banner轮播图的数据

和前边获取TypeNav三级导航数据的操作是一样的,只不过那个是真发送ajax请求从后台拿数据,这个是从我们自己模拟的假数据里拿数据

和之前获取三级联动的数据是一个套路

1、首先挂载完毕后组件派发action,调用仓库中的getBannerList函数

js 复制代码
mounted() {
    // 派发actions 通知Vuex发起ajax请求,将数据存储在仓库当中
    this.$store.dispatch("getBannerList");
  },

2、去Vuex中配置getBannerList函数,通过vuex的actions发起ajax请求,将数据存储在仓库中的state。

3、组件接收数据,使用mapState语法糖

js 复制代码
  computed: {
    ...mapState({
      bannerList: (state) => state.home.bannerList,
    }),
  },
  或者:
    computed: {
    ...mapState('home',['bannerList']),
  },

4、制作ListContainer轮播图

轮播图可以用这个插件:swiper的基本使用

安装:npm i swiper@5

使用swiper要分三步:

1.引入相应的包

2.页面结构必须先有(给轮播图添加静态效果)

3.有结构后再new Swiper实例(给轮播图添加动态效果)

(1)引入相应的包(js和css)

JS在对应组件内引入:

js 复制代码
import Swiper from 'swiper';

css在main.js里引入:由于floor组件和listContainer组件都用到了轮播图,所以我们在main.js里引入swiper的样式,这样的话就都哪个组件都可以用swiper的样式了

js 复制代码
//引入轮播图插件样式swiper
import 'swiper/css/swiper.css';

(2)搭建轮播图页面结构

carousel单词是轮播图的意思

这里做个铺垫:v-for 遍历mock返回的数据,数据返回后才会渲染遍历到页面

(3)给轮播图添加动态效果

如果把new Swiper直接放到mounted里边儿,行吗?

不行。因为dispatch去Vuex中请求数据,在actions中发送ajax请求是一个异步操作(用到了async,去看代码),这样的话,会导致new Swiper先执行,然后再去请求数据,再把数据v-for放到轮播图页面结构上。这样的话页面结构还没完整就new Swiper了,会无法正常显示(页面结构必须先生成再new Swiper)

  • 解决方法1:定时器(不推荐,因为发送ajax请求的时间不确定,所以定时器时间不好把握)
js 复制代码
mounted() {
    //挂载完毕后通过Vuex发送ajax请求,将数据存储在仓库中
    this.$store.dispatch('home/getBannerList');
    setTimeout(() => {
        var mySwiper = new Swiper(".swiper-container", {
            loop: true,
            cssMode: true,
            navigation: {
                nextEl: ".swiper-button-next",
                prevEl: ".swiper-button-prev",
            },
            pagination: {
                el: ".swiper-pagination",
                clickable: true, // 点击小球的时候也切换
            },
            mousewheel: true,
            keyboard: true,
        });
    }, 1000);
},
  • 解决方法2:watch和$nextTick(用这个)
    watch:数据监听,监听已有数据的变化

    1、监听bannerList数据的变化,因为这条数据由空数组变为数组里面有mock数据

    2、通过watch监听bannerList属性的属性值的变化

    3、如果执行handler方法,代表组件实例身上这个属性的属性值数组已经有了mock数据

    4、但是这里 结构没有渲染完成 ,现在只能保证数据已经有了,但是没法保证v-for已经执行完成了

    $nextTick:在下次 DOM 更新,循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。当执行这个回调的时候,保证服务器的数据回来了,v-for执行完毕了【轮播图的结构已经完成了】。$nextTick可以保证页面中的结构一定是有的,经常和很多插件一起使用【都需要DOM存在】

js 复制代码
//   文件:src/pages/Home/ListContainer
watch: {
    //监听bannerList数据的变化:由空数组变成mock数据
     bannerList: {
         //当bannerList从空数组变成有数据时(ajax请求成功),执行handler函数
         //但是,当前函数执行的时机只能保证数据有了,但是v-for是否执行结束不知道
         //而v-for执行完毕的时候,页面才能有结构
         handler(newVal, oldVal) {
             //nextTick能保证页面结构先渲染出来,然后再执行回调函数
             this.$nextTick(function () {
                 var mySwiper = new Swiper(".swiper-container", {
                     loop: true,
                     cssMode: true,
                     navigation: {
                         nextEl: ".swiper-button-next",
                         prevEl: ".swiper-button-prev",
                     },
                     pagination: {
                         el: ".swiper-pagination",
                         clickable: true, // 点击小球的时候也切换
                     },
                     mousewheel: true,
                     keyboard: true,
                 });
             })
         }
     }
 }

5、开发floor组件

组件首先还是拿数据,步骤和之前的一样:配置api接口 => 在actions请求数据,存储到Vuex中 => dispatch给Vuex => 拿到数据渲染到页面上

(1)从mock拿数据

1、配置api接口

js 复制代码
//   src/api/index.js
//3.floor部分的接口
//  /mock/floor
export const reqFloorList = () => mockRequests.get('/floor');

2、数据存储到仓库

js 复制代码
//  src/store/home/index.js
// home模块的小仓库

import { reqCategoryList, reqGetBannerList, reqFloorList } from "@/api/index";

const state = {
  //state中数据默认初始值别瞎写
  // 服务器返回对象,服务器返回数组。【根据接口返回值进行初识化】
  categoryList: [],
  bannerList: [],
  floorList: [],
};
const mutations = {
  CATEGORYLIST(state, categoryList) {
    categoryList.shift();
    categoryList.shift();
    state.categoryList = categoryList;
  },
  BANNERLIST(state, bannerList) {
    state.bannerList = bannerList;
  },
  FLOORLIST(state, floorList) {
    state.floorList = floorList;
  },
};
const actions = {
  // 通过api里面的接口函数调用,向服务器发请求,获取服务器数据
  async categoryList({ commit }) {
    let result = await reqCategoryList();
    // console.log("三级导航数据:"+result);
    if (result.code == 200) {
      commit("CATEGORYLIST", result.data);
    }
  },
  // 获取首页轮播图的数据
  async getBannerList({ commit }) {
    let result = await reqGetBannerList();
    // console.log("轮播图数据:" + result);
    if (result.code == 200) {
      commit("BANNERLIST", result.data);
    }
  },
  //获取floor数据
  async getFloorList({ commit }) {
    let result = await reqFloorList();
    // console.log("Floor数据:" + result);
    if (result.code == 200) {
      commit("FLOORLIST", result.data);
    }
  },
};
const getters = {};

// 对外暴露
export default {
  state,
  mutations,
  actions,
  getters,
};

2、dispatch给Vuex

去哪里dispatch呢?这个要看我们的数据和home页的结构,mock数据里是数组包两个对象[{},{}],所以应该有两个Floor组件,而且每个里面的数据是不一样的,如果去floor组件里dispatch,那么没法拿里面的数据,而且两个数据怎么区分?所以要去home里dispatch

然后使用mapState拿到数据

(2)Home中的数据传给Floor

数据在home中,传给Floor------父子组件通信使用props

第一个floor用数组里的第一个对象,第二个floor用数组里的第二个对象

(3)数据渲染到Floor页面

Floor已经拿到了数据,这块儿就不难了,根据数据的结构和html结构把它们分别用v-for或插值语法渲染到页面上就行了,这里边值得注意的一个地方是这里的轮播图。

轮播图的三步使用:1.导包 2.页面结构先有 3.new Swiper实例后有

和前边不同的是,这里我们可以把new Swiper实例放在mounted里,这是因为Floor里的数据都是Home组件传过来的,而发请求是在父组件Home里面挂载完毕发的,所以传过来的数据是请求好的数据,也就是说Floor组件里面没有任何异步操作,所以挂载完毕之后页面结构就会先有,然后就直接new Swiper就行了。(之前我们是在当前组件ListContainer的内部发请求以及动态的渲染结构,必须watch+$nextTick)

html 复制代码
<template>
  <div class="floor">
    <div class="py-container">
      <div class="title clearfix">
        <h3 class="fl">{{ eachFloor.name }}</h3>
        <div class="fr">
          <ul class="nav-tabs clearfix">
            <li
              class="active"
              v-for="(nav, index) in eachFloor.navList"
              :key="index"
            >
              <a href="#tab1" data-toggle="tab">{{ nav.text }}</a>
            </li>
          </ul>
        </div>
      </div>
      <div class="tab-content">
        <div class="tab-pane">
          <div class="floor-1">
            <div class="blockgary">
              <ul class="jd-list">
                <li v-for="(keyword, index) in eachFloor.keywords" :key="index">
                  {{ keyword }}
                </li>
              </ul>
              <img :src="eachFloor.imgUrl" />
            </div>
            <div class="floorBanner">
              <div class="swiper-container" ref="floor1Swiper">
                <div class="swiper-wrapper">
                  <div
                    class="swiper-slide"
                    v-for="carousel in eachFloor.carouselList"
                    :key="carousel.id"
                  >
                    <img :src="carousel.imgUrl" />
                  </div>
                </div>
                <!-- 如果需要分页器 -->
                <div class="swiper-pagination"></div>

                <!-- 如果需要导航按钮 -->
                <div class="swiper-button-prev"></div>
                <div class="swiper-button-next"></div>
              </div>
            </div>
            <div class="split">
              <span class="floor-x-line"></span>
              <div class="floor-conver-pit">
                <img :src="eachFloor.recommendList[0]" />
              </div>
              <div class="floor-conver-pit">
                <img :src="eachFloor.recommendList[1]" />
              </div>
            </div>
            <div class="split center">
              <img :src="eachFloor.bigImg" />
            </div>
            <div class="split">
              <span class="floor-x-line"></span>
              <div class="floor-conver-pit">
                <img :src="eachFloor.recommendList[2]" />
              </div>
              <div class="floor-conver-pit">
                <img :src="eachFloor.recommendList[3]" />
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import Swiper from "swiper";
export default {
  name: "",
  props: ["eachFloor"],
  mounted() {
    new Swiper(this.$refs.floor1Swiper, {
      loop: true,
      cssMode: true,
      navigation: {
        nextEl: ".swiper-button-next",
        prevEl: ".swiper-button-prev",
      },
      pagination: {
        el: ".swiper-pagination",
        clickable: true, // 点击小球的时候也切换
      },
      mousewheel: true,
      keyboard: true,
    });
  },
};
</script>

<style lang="less" scoped>
.floor {
  ...
}
</style>

(4)把轮播图封装为全局组件

以后在开发项目的时候,如果看到某一个组件在很多地方都使用,你把它变成全局组件,注册一次,可以在任意地方使用,公用的组件 | 非路由组件放到components文件夹中。结构,样式,行为要几乎一样才能封装成全局组件,大家一起公用

我们发现ListContainer中的轮播图和Floor中的轮播图长得差不多,唯一的区别就是数据不同,还有就是ListContainer里用的watch+ n e x t T i c k (因为要异步请求数据), F l o o r 是直接写道 m o u n t e d 里,其实 F l o o r 也可以用 w a t c h + nextTick(因为要异步请求数据),Floor是直接写道mounted里,其实Floor也可以用watch+ nextTick(因为要异步请求数据),Floor是直接写道mounted里,其实Floor也可以用watch+nextTick,但是由于Floor中的数据是直接拿Home请求好的,所以要加上immediate: true(不管数据变没变先调用handeler),这样的话,轮播图组件就可以封装成以下样子:

至此Home首页就基本完结啦!!!!撒花~继续加油!

相关推荐
工呈士1 分钟前
HTML 模板技术与服务端渲染
前端·html
皮实的芒果3 分钟前
前端实时通信方案对比:WebSocket vs SSE vs setInterval 轮询
前端·javascript·性能优化
鹿九巫3 分钟前
【CSS】层叠,优先级与继承(三):超详细继承知识点
前端·css
奕云4 分钟前
react-redux源码分析
前端
咸鱼一号机5 分钟前
:global 是什么
前端
专业掘金5 分钟前
0425 手打基础丸
前端
五号厂房5 分钟前
Umi Max 如何灵活 配置多环境变量
前端
红尘散仙8 分钟前
六、WebGPU 基础入门——Vertex 缓冲区和 Index 缓冲区
前端·rust·gpu
南望无一8 分钟前
webpack性能优化和构建优化
前端·webpack
il9 分钟前
Deepdive into Tanstack Query - 2.0 Query Core 概览
前端·javascript