Vue2电商前台项目------完成Home首页模块业务
Vue基础知识点击此处------Vue.js
文章目录
- Vue2电商前台项目------完成Home首页模块业务
一、项目开发的步骤
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仓库请求数据
-
actions执行的时候,要通过api里面的接口函数调用,向服务器发请求,获取服务器的数据,需要把之前的api引入进来,在这里发请求就是要调用这个reqCategoryList函数,如果请求成功(code===200),那么把数据交给mutations进行处理
-
mutations中把数据给state
-
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)默认上来隐藏,然后鼠标移入显示,鼠标离开隐藏。
- 我们可以在TtpeNav组件中加入一个数据
show
用来决定组件的显示和隐藏,默认值为true
- 利用
v-show
和数据show
来判断是否显示三级导航的内容部分
- 当从Home进入到Search的时候,TypeNav会再重新挂载,所以当进入Search的时候,让show变成
false
,而且要判断只要不是往主页跳,挂载完都要隐藏(这样的话能够保证主页的三级导航一直显示,避免bug)
- 配置回调函数,离开时加个判断,在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首页就基本完结啦!!!!撒花~继续加油!