Vue2项目实战:尚品汇(一)项目基本框架、home首页模块

目录

(一)项目框架搭建

1.搭建框架

2.项目的路由分析

3.完成项目的基本框架搭建

4.完成项目的路由搭建

编程式路由多次跳转当前路由(参数不变)会出现NavigationDuplicated警告错误的处理方法

[1]直接捕获并略过

[2]重写push和replace函数

5.完成首页静态界面的搭建

(二)完成home首页模块业务

1.二次封装axios

2.Vuex仓库的搭建

3.三级联动列表交互功能的完善

[1]实现实时请求三级联动列表数据并渲染页面

[2]实现鼠标进入一级菜单出现背景颜色和进入一级菜单展示二三级菜单

[3]防抖与节流***

(1)防抖

(2)节流

[4]点击一二三级菜单对应的标签进入search界面并传输数据

[5]typeNav的性能优化

4.mock模拟数据

[1]mock.js的使用

[2]banner图片数据

[3]floor商品和图片数据 

5.轮播图功能完善

[1]swiper组件 

[2]banner轮播图

[3]floor轮播图

[4]独立出轮播图组件(优化) 


(一)项目框架搭建

1.搭建框架

项目开始前进行些配置有助于更好的完成项目

  1. 在package.json中,添加--open可在运行后自动打开网页
  2. 在vue.config.js文件中,添加lintOnSave: false, //关闭大小写提示
  3. 在jsconfig.js文件中,设置有基础路径

2.项目的路由分析

网站的header和footer在多个网页中都存在,故写为非路由组件;

除此之外的中间部分采用路由组件实现,打开网站就马上跳转为home组件,点击对应按钮跳转到登陆、注册、搜索等界面;

header组件在:home、search、login、register组件;footer组件在home、search组件;

3.完成项目的基本框架搭建

(1)编写非路由组件header和footer

在App.vue中:

<template>
  <div id="app">
    <Header></Header>
    <Footer></Footer>
  </div>
</template>

<script>
import Header from './components/Header'
import Footer from './components/Footer'


export default {
  name: 'App',
  components: {
    Header, Footer
  },

}
</script>

<style></style>

4.完成项目的路由搭建

 

在router/index.js文件中 

初始路径重定向为home路由,使得网页打开就是首页 

import Vue from "vue";
import VueRouter from "vue-router";
Vue.use(VueRouter)
// 引入路由组件
import Home from '@/pages/Home'
import Search from '@/pages/Search'
import Login from '@/pages/Login'
import Register from '@/pages/Register'

// 创建并暴露一个路由器
export default new VueRouter({
    routes: [
        {
            path: '/home',
            component: Home,
            // 路由元信息 存放footer是否展示的标识
            meta: { show: true }
        },
        {
            path: '/search',
            component: Search,
            meta: { show: true }
        },
        {
            path: '/login',
            component: Login,
            meta: { show: false }
        },
        {
            path: '/register',
            component: Register,
            meta: { show: false }
        },
        // 重定向
        {
            path: '/',  // 或者'*'
            redirect: '/home'
        }
    ]
})

上文说过footer组件在login、register路由组件中不显示,只需要在路由元信息中存储是否展示的布尔值,再直接v-show判断即可 

footer组件中:
<div class="footer" v-show="this.$route.meta.show">

从home跳转到login、register路由可以直接使用声明式路由导航;跳转到search路由时需要传递参数等操作所以使用编程式路由导航;

搜索框输入的关键字使用params参数传递,当params参数可传可不传时要加个问号 

在router/index中:
{
    name: 'search',
    path: '/search/:keywords?',
    component: Search,
    meta: { show: true }
},
在header/index中:
<!--头部第一行-->
<p>
    <span>请</span>
    <router-link to="/login">登录</router-link>
    <router-link to="/register" class="register">免费注册</router-link>
</p>
<!--头部第二行 搜索区域-->
<form action="###" class="searchForm">
    <input type="text" 
        id="autocomplete" 
        class="input-error input-xxlarge" 
        v-model="searchData" />
    <button class="sui-btn btn-xlarge btn-danger" type="button" 
        @click="goSearch">搜索</button>
</form>

data() {
    return {
        searchData: ''
    }
},
methods: {
    goSearch() {
        // 点击搜素跳转路由使用编程式导航路由,可以处理数据
        this.$router.push({
                path: '/search',
                query: {
                    keywords: this.keywords
                }
            })
    }
},

编程式路由多次跳转当前路由(参数不变)会出现NavigationDuplicated警告错误的处理方法

[1]直接捕获并略过

这种方法简答但路由数量过多就太麻烦了

push函数本质是返回一个promise函数,只需要用两个参数来承接promise的resolve和reject参数即可捕获到异常并不做处理

goSearch() {
    // 点击搜素跳转路由使用编程式导航路由,可以处理数据
    this.$router.push({
        path: '/search',
        query: {
            keywords: this.keywords
        }
    }, (resolve) => { console.log(resolve); }, (reject) => { console.log(reject); })
}

[2]重写push和replace函数

直接修改VueRouter原型上的方法,生成的$router就会自动继承这些重写的方法

不能使用originPush(),因为originPush定义时this指向的是window,而push函数需要在vueRouter内使用,this指向不同,所以使用call()或者apply()来调用
call和apply函数都能调用函数并修改this指向,区别是apply函数的参数时要以数组形式添加

// 重写push方法;实现捕获和忽略编程式导航同状态多次点击报错情况
let originPush = VueRouter.prototype.push
VueRouter.prototype.push = function (location, resolve, reject) {
    // 如果成功回调与失败回调都返回了就调用原生push
    if (resolve && reject) {
        // 不能使用originPush(),因为originPush定义时this指向的是window,而push函数需要在vueRouter内使用,this指向不同,所以使用call()或者apply()来调用
        // call和apply函数都能调用函数并修改this指向,区别是apply函数的参数时要以数组形式添加
        originPush.call(this, location, resolve, reject)
    } else { //捕获异常并不做处理
        originPush.call(this, location, () => { }, () => { })
    }
}
// 重写replace 与上面相同
let originReplace = VueRouter.prototype.replace
VueRouter.prototype.replace = function (location, resolve, reject) {
    if (resolve && reject) {
        originReplace.call(this, location, resolve, reject)
    } else { //捕获异常并不做处理
        originReplace.call(this, location, () => { }, () => { })
    }
}

5.完成首页静态界面的搭建

<template>
  <div id="app">
    <Header></Header>
    <router-view></router-view>
    <Footer></Footer>
  </div>
</template>

将home组件中其余部分组件陆陆续续加入

 

typeNav组件作为全局组件,放在components中

main.js文件:
// 三级联动导航菜单 全局组件;以后只需要标签引入使用
import TypeNav from '@/components/TypeNav'
Vue.component(TypeNav.name, TypeNav)

其余组件直接放在home组件的文件夹中,便于后续的管理

(二)完成home首页模块业务

通过上一步路由搭建和连接,首页已经能进行不同页面的跳转了,接下来就是实现二次封装axios、三级联动列表功能完善、

1.二次封装axios

上篇文章也封装了axios,感觉比这个更详细点;但是上次是直接在axios默认配置里写的,这次是new了一个axios对象来写基础设置和拦截器,还用到了NProgress

ajax.js文件用于写axios的配置信息

// 该文件用于配置axios
import axios from "axios"
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'

// 配置不显示右上角的旋转进度条, 只显示水平进度条
NProgress.configure({ showSpinner: false })

const requests = axios.create({
    baseURL: '/api', // 设置公共路径
    timeout: 1000 * 10 // 设置请求超过时间 10s
})


// 添加请求拦截器
requests.interceptors.request.use(
    (config) => {
        // 这里写发送请求后的操作
        NProgress.start() // 显示请求中的水平进度条
        return config  //返回配置对象
    },
    (err) => {
        // 这里写对请求错误执行的操作
        return Promise.reject(err)
    }
)

// 添加响应拦截器
requests.interceptors.response.use(
    (config) => {
        // 这里写响应请求后的操作
        NProgress.done() // 隐藏进度条
        // 返回响应体数据
        return config.data  //直接返回data

    },
    (err) => {
        // 这里写对响应错误执行的操作
        NProgress.done() // 隐藏进度条
        alert(`请求出错:${err.message}` || '未知错误')

        return Promise.reject(err)
    }
)

export default requests

 index.js文件用于向目标网址发送请求得到数据,便于后续组件直接引用

// 该文件用于写所有接口请求函数

import requests from './ajax'

// 获取商品的三级分类列表
export const categoryList = () => requests.get('/product/getBaseCategoryList')

采用分别暴露

在vue.config.js文件里配置代理服务器

devServer: { // 配置代理
    proxy: {
      '/api': {
        target: 'http://gmall-h5-api.atguigu.cn', //目标url
        changeOrigin: true // 支持跨域
      }
    }
  },

发送axios请求后数据的获取方法

import {categoryList} from '...'

async getCategoryList(context) {
    const result = await categoryList()
}

axios响应后返回一个promise实例,通过async/await异步操作直接获取promise数据;

在这放个链接方便自己随时看看:ES6 入门教程

2.Vuex仓库的搭建

存储在store里的state、actions、mutations可以供所有组件通过$store方式获取和应用

在项目过大时,可将store仓库按照不同的组件划分成很多小仓库(模块化),便于后期管理

在index.js文件中:

// 该文件用于创建Vuex中最核心的store

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

// 引入模块化的小仓库
import Home from './Home'
import Login from './Login'
import Register from './Register'
import Search from './Search'

// 用于异步操作 不进行数据操作
const actions = {}
// 进行数据操作
const mutations = {}
// 用于加工state内的数据 类似于computed
const getters = {}
// 存储共享数据
const state = {}

export default new Vuex.Store({
    // 模块化 项目大时便于管理
    modules: {
        Home:Home,
        Login,
        Register,
        Search
    }
})

其余小仓库中:

// login组件的store仓库
// 用于异步操作 不进行数据操作
const actions = {}
// 进行数据操作
const mutations = {}
// 用于加工state内的数据 类似于computed
const getters = {}
// 存储共享数据
const state = {}

export default { actions, mutations, getters, state }

3.三级联动列表交互功能的完善

[1]实现实时请求三级联动列表数据并渲染页面

在store仓库里写入发送请求并接收categoryList数据的功能

// home组件的store仓库

// 引入axios CategoryList
import { categoryList } from "@/api"

// 用于异步操作 不进行数据操作
const actions = {
    // 发送请求获取categorylist数据
    async getCategoryList(context) {
        const result = await categoryList()
        if (result.code == 200) { //成功接收
            context.commit('GETCATEGORYLIST', result)
        }
    }
}
// 进行数据操作
const mutations = {
    // 更新state中categoryList的值
    GETCATEGORYLIST(state, result) {
        state.categoryList = result.data
    }
}
// 用于加工state内的数据 类似于computed
const getters = {}
// 存储共享数据
const state = {
    categoryList: []
}

export default { actions, mutations, getters, state }

在typeNav组件中获取categoryList数据

 ...mapState对象写法:接收参数state,因为store分成了多个模块,所以要在对应的小仓库里获取state

mounted() {
    // 向store仓库获取categoryList
    this.$store.dispatch('getCategoryList')
},
computed: {
    ...mapState({
    // 会自动调用右侧函数
    categoryList: state => state.Home.categoryList
    })
},

将接收到的实时数据渲染在页面上

使用v-for实现

<div class="item bo" 
    v-for="(c1, index) in categoryList" 
    :key="c1.categoryId">
    <!-- 实现鼠标移入样式 -->
    <h3>
        <a>{{c1.categoryName }}</a>
    </h3>
    <!-- 二、三级列表 使用v-show实现列表的显示与隐藏 -->
    <div class="item-list>
        <div class="subitem">
            <dl class="fore" v-for="c2 in c1.categoryChild" :key="c2.categoryId">
                <dt>
                    <a>{{c2.categoryName }}</a>
                </dt>
                <dd>
                    <em v-for="c3 in c2.categoryChild" :key="c3.categoryId">
                    <a>{{c3.categoryName }}</a>
                    </em>
                </dd>
            </dl>
        </div>
    </div>
</div>

 

[2]实现鼠标进入一级菜单出现背景颜色和进入一级菜单展示二三级菜单

可以直接使用css的hover写法以及display:block/display:none

这里使用vue写法,鼠标进入触发mouseenter事件,离开触发mouseleave事件,再搭配上一级菜单各个标签的index,实现目标功能(同时也便于后续功能的实现);

  • 新添加currentIndex,当鼠标移入一级菜单标签触发事件,当currentIndex=index时,一级菜单标签添加样式类,与之对应的二三级菜单展示出来;
  • 当鼠标离开整个一二三级菜单后背景颜色样式类才会消失
<div class="item bo" 
    v-for="(c1, index) in categoryList" 
    :key="c1.categoryId" 
    @mouseleave="leaveIndex">
    <!-- 实现鼠标移入样式 -->
    <h3 @mouseenter="changeIndex(index)" 
        :class="{ current: currentIndex == index }">
        <a>{{c1.categoryName }}</a>
    </h3>
    <!-- 二、三级列表 使用v-show实现列表的显示与隐藏 -->
    <div class="item-list clearfix" 
        v-show="currentIndex == index">
        <div class="subitem">
            ...
        </div>
    </div>
</div>

data() {
    return {
        currentIndex: -1,
    }
},
methods: {
    changeIndex(index) {
        this.currentIndex = index
    }, 
    leaveIndex() {
        this.currentIndex = -1
    },

// 鼠标进入添加该样式类
.current {
    background-color: #ccc;
}

[3]防抖与节流***

当用户进行频繁操作时,过快的加载反而会导致浏览器性能下降

(1)防抖

防抖 (Debouncing) 的含义是指在一定时间内,多次触发同一个事件,只执行最后一次操作

使用场景:在搜索框内输入关键词时,在规定时间内输入的关键词进行服务器请求与检索;

使用方法:lodash.debounce | Lodash中文文档 | Lodash中文网

(2)节流

节流 (Throttling) 的含义是指在一定时间内,多次触发同一个事件,只执行第一次操作

使用场景:用户拖动网页上的滚动条时,频繁滚动时只执行第一次的滚动;

使用方法:lodash.throttle | Lodash中文文档 | Lodash中文网

实现鼠标进入一级菜单标签背景颜色变化的节流

// 引入节流
import throttle from 'lodash/throttle.js'

// 节流:用户操作速度过快会导致页面卡顿,通过节流,规定时间内只执行一次操作
changeIndex: throttle(function (index) {
    this.currentIndex = index
}, 50),

[4]点击一二三级菜单对应的标签进入search界面并传输数据

使用事件委托(利用冒泡原理),将点击事件放在大div中,根据自定义属性判断是否为目标标签;若直接对a标签使用router-link或点击事件,会导致循环产生多个回调函数,增大网页负担

<div class="all-sort-list2" @click="goSearch($event)">
    <div class="item bo" 
        v-for="(c1, index) in categoryList" 
        :key="c1.categoryId" @mouseleave="leaveIndex">
        <!-- 实现鼠标移入样式 -->
        <h3 @mouseenter="changeIndex(index)" 
            :class="{ current: currentIndex == index }">
            <a :data-categoryName="c1.categoryName" 
                :data-categoryId1="c1.categoryId">{{c1.categoryName }}</a>
        </h3>
        <!-- 二、三级列表 使用v-show实现列表的显示与隐藏 -->
        <div class="item-list clearfix" v-show="currentIndex == index">
            <div class="subitem">
                <dl class="fore" v-for="c2 in c1.categoryChild" :key="c2.categoryId">
                    <dt>
                        <a :data-categoryName="c2.categoryName" 
                            :data-categoryId2="c2.categoryId">{{c2.categoryName }}</a>
                    </dt>
                    <dd>
                        <em v-for="c3 in c2.categoryChild" :key="c3.categoryId">
                            <a :data-categoryName="c3.categoryName" 
                                :data-categoryId3="c3.categoryId">{{c3.categoryName }}</a>
                        </em>
                    </dd>
                </dl>
            </div>
        </div>
    </div>
</div>

对一二三级菜单标签设置自定义属性,有data-categoryName属性一定是目标标签,再根据data-categoryId1/2/3来区别这三级标签;

// 三级联动列表 确定和发送点击对象的名字和id
        goSearch(event) {
            // 点击列表传输query数据
            // 存在自定义事件则不为空
            let { categoryname, categoryid1, categoryid2, categoryid3 } = event.target.dataset
            // 判断点击事件是否为标签
            if (categoryname) {
                const query = { categoryName: categoryname }
                if (categoryid1) { // 判断是否为一级标签
                    query.categoryId = categoryid1
                } else if (categoryid2) { // 判断是否为二级标签
                    query.categoryId = categoryid2
                } else { // 判断是否为三级标签
                    query.categoryId = categoryid3
                }

                // 编程式导航路由跳转 并传送信息
                const location = {
                    name: 'search',
                    query,
                    params: this.$route.params
                }
                this.$router.push(location)
                // 当params参数为真时 合并参数
                // if (this.$route.params) {
                //     location.params = this.$route.params
                //     this.$router.push(location)
                // }
            }

将categoryName和categoryId通过query参数发送到search组件,后续再根据数据进行各自页面的展示  

[5]typeNav的性能优化

原先将发送请求信息放在typeNav组件上,导致每次切换路由、刷新、搜索都需要重新请求数据,大大降低了性能。这些数据在网页加载好的时候请求一次就够了,因此在app.vue挂载好后发送请求是最好的

在APP.vue中:
mounted() {
    // 在app.vue中发起请求只会调用一次 优化性能
    this.$store.dispatch('getCategoryList')
  },

4.mock模拟数据

Mock.js

[1]mock.js的使用

mock.js的作用:是一款模拟数据生成器,可以生成随机数或自己模拟数据,拦截 Ajax 请求

下载mock.jsnpm i mockjs 

这次项目实战中有很多的数据服务器并没有给出,因此可以使用mock.js组件模拟一些数据完成网页的渲染。

mock组件的搭建:

在src目录下新建mock文件夹, banner.json、floor.json用于书写模拟运用到banner和floor组件的数据:

注意:因为模拟数据给的路径在public所以要将对应的图片移入到/public/images 

mockServer.js用于写mock相关的配置

// 引入mock.js
import Mock from "mockjs";
// 引入json
// webpack默认图片和json数据格式对外暴露
import banner from '@/mock/banner.json'
import floor from '@/mock/floor.json'

// mock数据:第一个参数请求地址;第二个参数请求数据
// 当发送ajax请求该地址时,浏览器拦截这条请求并将该数据返回
Mock.mock('/mock/banner', { code: 200, data: banner })
Mock.mock('/mock/floor', { code: 200, data: floor })

虽然数据是通过mock模拟出来的,但是请求数据的步骤还是和请求服务器一样的,请求路径/mock/...,mock就会自动拦截ajax请求,将对应配置好的数据返回

在api文件夹中配置一个/mock路径的文件ajaxMock.js,除了baseURL不一样,其余和ajax,js相同

在api/index.js文件中配置ajaxMock的请求:

在ajax/index.js中:
// 该文件用于写所有接口请求函数

import requests from './ajax'
import mockRequests from './ajaxMock'

// 获取商品的三级分类列表
export const categoryList = () => requests.get('/product/getBaseCategoryList')

// mock模拟数据的获取
export const bannerList = () => mockRequests.get('/banner')
export const floorList = () => mockRequests.get('/floor')

[2]banner图片数据

和之前用仓库store存储请求到的服务器数据步骤相同,用store存储bannerList的数据,并在app.vue挂载后就请求数据;

在store/Home/index中:
// 用于异步操作 不进行数据操作
const actions = {
    // 发送请求获取bannerList数据
    async getBannerList(context) {
        const result = await bannerList()
        if (result.code == 200) {
            context.commit('GETBANNERLIST', result)
        }
    },
    // 发送请求获取floorList数据
    async getFloorList(context) {
        const result = await floorList()
        if (result.code == 200) {
            context.commit('GETFLOORLIST', result)
        }
    },
}
// 进行数据操作
const mutations = {
    // 更新state中bannerList的值
    GETBANNERLIST(state, result) {
        state.bannerList = result.data
    },
    // 更新state中bannerList的值
    GETFLOORLIST(state, result) {
        state.floorList = result.data
    },
}
// 存储共享数据
const state = {
    bannerList: [],
    floorList: [],
}
computed: {
        // 获取store里的bannerList数据
        ...mapState({
            bannerList: (state) => state.Home.bannerList
        }),
    },

[3]floor商品和图片数据 

和之前用仓库store存储请求到的服务器数据步骤相同,用store存储floorList的数据,并在app.vue挂载后就请求数据; 

在store/Home/index中:
// 用于异步操作 不进行数据操作
const actions = {
    // 发送请求获取floorList数据
    async getFloorList(context) {
        const result = await floorList()
        if (result.code == 200) {
            context.commit('GETFLOORLIST', result)
        }
    },
}
// 进行数据操作
const mutations = {
    // 更新state中bannerList的值
    GETFLOORLIST(state, result) {
        state.floorList = result.data
    },
}
// 存储共享数据
const state = {
    floorList: [],
}
在Home/index中:
<!-- 通过v-for生成多个floor,将各自的数据传送给对应的floor -->
<Floor v-for="floor in floorList" :key="floor.id" :floor="floor"></Floor>

在Home/floor/index中:
// 使用props接收传递参数
props: ["floor"],

5.轮播图功能完善

[1]swiper组件 

vue2使用低版本 

swiper组件下载:npm i swiper@5.4.5

Swiper使用方法 - Swiper中文网

vue-awesome-swiper组件下载:npm i vue-awesome-swiper@4.1.1

vue-awesome-swiper | Homepage

[2]banner轮播图

特别注意:直接使用轮播图框架会出现轮播图从最后一张图片开始播放的情况

是因为:

当页面还没有接收ajax数据的时候,bannerList还是空数组,swiper的初始创建是通过空数组创建的,因此swiper轮播图会先显示最后一张图片,当ajax获取数据后,bannerList变成真正的数据项,才获取到新的数据,重新渲染新数据对应的图片;

因此解决方法就是设置一个计算属性showSwiper实时监测bannerLIst数组的长度,不等于0时开始加载swiper组件:v-if="showSwiper"

<swiper ref="swiper" :options="swiperOptions" v-if="showSwiper">
    <swiper-slide v-for="banner in bannerList" :key="banner.id">
        <img :src="banner.imgUrl" />
    </swiper-slide>
    <!-- 左箭头 -->
    <div slot="button-prev" class="swiper-button-prev"></div>
    <!-- 右箭头 -->
    <div slot="button-next" class="swiper-button-next"></div>
    <!-- 分页器 -->
    <div slot="pagination" class="swiper-pagination"></div>
</swiper>

// 局部引入swiper插件
import { Swiper, SwiperSlide } from 'vue-awesome-swiper'
import 'swiper/css/swiper.css'

    data() {
        return {
            // swiperOptions配置项: 使用swiper官网的配置项即可
            swiperOptions: {
                //导航前进后退按钮
                navigation: {
                    nextEl: ".swiper-button-next",
                    prevEl: ".swiper-button-prev",
                },
                //自动轮播图
                autoplay: {
                    delay: 1500,
                    stopOnLastSlide: false,
                    disableOnInteraction: true,
                },
                //无缝衔接
                loop: true,
                //分页器配置项
                pagination: {
                    el: ".swiper-pagination",
                    clickable: true, // 点击分页器小球进行轮播图切换
                },
                //切换效果
                // effect: "cube",
            },
        }
    },
    mounted() {
        //鼠标进入-暂停
        this.$refs.swiper.$el.onmouseenter = () => {
            this.$refs.swiper.$swiper.autoplay.stop();
        }
        //鼠标离开-开始
        this.$refs.swiper.$el.onmouseleave = () => {
            this.$refs.swiper.$swiper.autoplay.start();
        }
    },
    computed: {
        // 获取store里的bannerList数据
        ...mapState({
            bannerList: (state) => state.Home.bannerList
        }),
        // 实时判断bannerList的长度,当ajax获取到数据时轮播图才开始加载
        showSwiper() {
            return this.bannerList.length
        }
    },
    components: {
        Swiper, SwiperSlide
    },

报错Error in mounted hook: "TypeError: Cannot read properties of undefined (reading '$el')"情况:

我在挂载时使用了this.$refs.swiper.$el,先前提到showSwiper作用是获取到bannerList数据后再载入swiper,那么在mounted获取swiper.$el自然是获取不到的。

解决办法就是监视showSwiper的变化,使用$nextTick()在DOM 更新循环结束之后执行延迟回调,届时$el就已经被创建出来可以使用了。

[3]floor轮播图

在floor组件内就不需要使用showSwiper监测floor数据变化,因为floor数组是父组件提供的,它从挂载传入floor组件起就不会发生变化了

<swiper ref="swiper" :options="swiperOptions">
    <swiper-slide v-for="carousel in floor.carouselList" :key="carousel.id">
        <img :src="banner.imgUrl" />
    </swiper-slide>
    <!-- 左箭头 -->
    <div slot="button-prev" class="swiper-button-prev"></div>
    <!-- 右箭头 -->
    <div slot="button-next" class="swiper-button-next"></div>
    <!-- 分页器 -->
    <div slot="pagination" class="swiper-pagination"></div>
</swiper>

// 局部引入swiper插件
import { Swiper, SwiperSlide } from 'vue-awesome-swiper'
import 'swiper/css/swiper.css'

    data() {
        return {
            // swiperOptions配置项: 使用swiper官网的配置项即可
            swiperOptions: {
                //导航前进后退按钮
                navigation: {
                    nextEl: ".swiper-button-next",
                    prevEl: ".swiper-button-prev",
                },
                //自动轮播图
                autoplay: {
                    delay: 1500,
                    stopOnLastSlide: false,
                    disableOnInteraction: true,
                },
                //无缝衔接
                loop: true,
                //分页器配置项
                pagination: {
                    el: ".swiper-pagination",
                    clickable: true, // 点击分页器小球进行轮播图切换
                },
            },
        }
    },
    components: {
        Swiper, SwiperSlide,
    },
    props: ["floor"],
    mounted() {
        //鼠标进入-暂停
        this.$refs.swiper.$el.onmouseenter = () => {
            this.$refs.swiper.$swiper.autoplay.stop();
        }
        //鼠标离开-开始
        this.$refs.swiper.$el.onmouseleave = () => {
            this.$refs.swiper.$swiper.autoplay.start();
        }
    },

[4]独立出轮播图组件(优化) 

上面两个轮播图的框架都大同小异,因此可以封装一个swiper组件,获取父组件提供的数据(使用props或者插槽),为以后进行更好的复用

写组件过程中遇到的问题:

一开始是用watch+$nextTick写的,发现listContainer组件可以正常使用轮播图,但是floor组件的轮播图不能实现移入停止移出继续的功能,说明爷组件Home给父组件floor传入的数据不会再发生改变,carousel组件监听的showSwiper根本不会发生变化;

后来在watch中加入了immediate:true,这样一来floor组件可以正常使用了,但listContainer组件的轮播图报错$el is undefined,是因为父组件listContainer传入props参数时list是空的,在请求到bannerList数据后会再重新传入props,这样一来两种情况获取到的轮播图数据根本不能兼容。

最后,我在listContainer组件传入props之前做一个限定,当请求到bannerList之后再传入props,这样一来传入的数据list就不会改变了,不需要计算属性showSwiper来判断长度(可以理解为我把它写在了需要请求数据的父组件上),鼠标移入暂停移出继续的功能也能直接写在mounted里了

/pages/listContainer/index中:
<!--banner轮播-->
<!-- 当页面还没有接收ajax数据的时候,bannerList还是空数组,监测bannerList长度,
    获取数据后再将bannerList传入Carouse组件 -->
<Carousel :list="bannerList" v-if="bannerList.length"></Carousel>

/pages/floor/index中:
<!-- 商品小轮播图 -->
<Carousel :list="floor.carouselList"></Carousel>
components/Carousel/index中:
<template>
    <div class="carousel">
        <swiper ref="swiper" :options="swiperOptions">
            <swiper-slide v-for="li in list" :key="li.id">
                <img :src="li.imgUrl" />
            </swiper-slide>
            <!-- 左箭头 -->
            <div slot="button-prev" class="swiper-button-prev"></div>
            <!-- 右箭头 -->
            <div slot="button-next" class="swiper-button-next"></div>
            <!-- 分页器 -->
            <div slot="pagination" class="swiper-pagination"></div>
        </swiper>
    </div>
</template>

<script>
// 局部引入swiper插件
import { Swiper, SwiperSlide } from 'vue-awesome-swiper'
import 'swiper/css/swiper.css'
export default {
    name: 'Carousel',
    data() {
        return {
            // swiperOptions配置项: 使用swiper官网的配置项即可
            swiperOptions: {
                //导航前进后退按钮
                navigation: {
                    nextEl: ".swiper-button-next",
                    prevEl: ".swiper-button-prev",
                },
                //自动轮播图
                autoplay: {
                    delay: 1300,
                    stopOnLastSlide: false,
                    disableOnInteraction: true,
                },
                //无缝衔接
                loop: true,
                //分页器配置项
                pagination: {
                    el: ".swiper-pagination",
                    clickable: true, // 点击分页器小球进行轮播图切换
                },
            },
        }
    },
    mounted() {
        //鼠标进入-暂停
        this.$refs.swiper.$el.onmouseenter = () => {
            this.$refs.swiper.$swiper.autoplay.stop();
        }
        //鼠标离开-开始
        this.$refs.swiper.$el.onmouseleave = () => {
            this.$refs.swiper.$swiper.autoplay.start();
        }
    },
    components: {
        Swiper, SwiperSlide
    },
    props: ['list']
}
</script>

<style scoped lang="less">
// 修改轮播图箭头样式
.swiper-button-prev,
.swiper-button-next {
    // display: none;
    opacity: 0;
    color: #fff;
    background-color: #00000023;
}

// 修改轮播图箭头样式
.swiper-button-prev:after,
.swiper-button-next:after {
    font-size: 24px;
}

.carousel:hover .swiper-button-next,
.carousel:hover .swiper-button-prev {
    opacity: 1;
}
</style>

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值