search
模块中的TypeNav
(过渡动画效果)以下是之前的测试数据
params参数{{ $route.params.keyword }}=========={{ keyword }}
query参数{{ $route.query.k }}
export default {
name: "SearchYu",
//路由传递 props 参数 要接受一下 有了props 上面模板就可以直接写了
props: ["keyword"],
};
一个功能在一些组件中显示,在一些组件中隐藏,那么就可以考虑用路由路径来判断,或者在配置路由的时候加上元信息来判断(如
Footer
组件的显示与隐藏,或者TypeNav
的显示与隐藏)
控制分类表显示与隐藏(
home
显示,其他隐藏),可以用一个响应式数据配合v-show
来,但是注意返回home
的时候需要显示,想到TypeNav
的生命周期,每跳转一次路由TypeNav
就会被挂载一次,所以在挂载完毕后判断当前路由是不是home
,是的话显示,否则隐藏
标签中绑定事件:@mouseleave="leaveIndex" @mouseenter="enterShow"
//一级分类鼠标移除的事件回调
leaveIndex() {
this.currentInedx = -1;
//列表隐藏 需要加个判断
if (this.$route.path != "/home") {
this.show = false;
}
},
//当鼠标移入全部商品分类的时候,展示商品分类列表
//这里也可以加判断
enterShow() {
this.show = true;
},
注意:过渡动画的前提是组件或元素务必要有
v-if
或v-show
指令才可以
...
//过渡动画样式---name属性
//开始状态(进入)
.sort-enter {
height: 0px;
}
//结束状态(进入0
.sort-enter-to {
height: 461px;
}
//定义动画时间速率等(进入的过程)
.sort-enter-active {
transition: all 0.5s linear;
}
在home
和search
路由之间进行跳转的时候,会频繁发送请求,因为都用到了TypeNav
组件 this.$store.dispatch("categoryList");
所以可以放到只执行一次的地方:App
(根组件)的mounted(){}
中----这样其他组件用数据的时候,仓库早已经有了
放在
main.js
中不可以,因为this
不是同一个东西,main.js
不是组件,我要的this
是组件的this
,组件身上才有$store
属性,才可以this.store
params
和query
参数
TypeNav
中
//判断,如果路由跳转时有params参数,一并带过去
if (this.$route.params) {
location.params = this.$route.params;
//整理好参数
location.query = query;
//路由跳转
this.$router.push(location);
}
Header
中的搜索
//有query参数也传过去
if (this.$route.query) {
let location = { name: "search", params: { keyword: this.keyword } };
location.query = this.$route.query;
this.$router.push(location);
}
这边这样写是有问题的,比如在主页
home
下,都还没点击呢。接下来点击三级列表,那要进入TypeNav
中的if判断,此时没有params
参数,为假,怎么发送push
呢(这里注意为什么会成功跳转呢,因为if的空对象判断是true!!!!{}为true!!!
)。一个办法是把push
写在if
外面,此时需要var
声明location
。
Home
首页中的ListContainer
组件与Floor
组件服务器返回的只有分类菜单数据,对于ListContaine
和Floor
组件没有提供数据
mock
数据(模拟):想要使用模拟数据(mock
),就需要安装一个插件 npm i mockjs
浏览器会拦截ajax
请求,不会向服务器发送请求,自己随机生成数据,自己在前台玩
使用步骤:
在项目的src文件夹
中创建mock文件夹
准备JSON
数据(在项目文档中提供了模拟数据),在mock文件夹
中创建相应的JSON文件
注意需要格式化一下,有空格无法运行
把mock
需要的图片资源放置到public文件夹
中,新建images
(public文件夹
在打包的时候,会把相应的资源原封不动的打包到dist文件夹中
)
创建mockServe.js
文件,通过mockjs
插件实现模拟数据
webpack
默认对外暴露的:图片资源,json
数据格式
mockServer.js
文件在入口文件中引入(至少需要执行一次。才能模拟数据)
mockServe中
//引入mockjs模块
import Mock from 'mockjs'
//把json数据格式引入---直接引入,默认对外暴露
import banner from './banner.json'
import floor from './floor.json'
//mock数据
//mock()的第一个参数:请求地址,第二个参数:请求数据
Mock.mock("/mock/banner", { code: 200, data: banner })//模拟首页轮播图的数据
Mock.mock("/mock/floor", { code: 200, data: floor })//模拟首页楼层的数据
main.js中
//引入mock数据
import '@/mock/mockServe'
OK
,现在静态组件准备好了,mock
数据准备好了,接下来向服务器发请求了获取服务器数据,存储到Vuex
中,然后展示数据注意
mockjs
发的请求会被浏览器拦截,但是用的时候就当作服务器返回的数据
ListContainer
组件开发重点首先,我们要开始发请求,但是之前封装的request
是向真实服务器发请求的,以“/api”
开头;但是我们现在要向mock
发请求,是以“/mock”
开头的,所以将request.js
复制一份成mockRequest.js
,同时修改baseURL
mockRequest.js中
baseURL: "/mock",
//开头跟这个对应:mock()的第一个参数:请求地址,第二个参数:请求数据
Mock.mock("/mock/banner", { code: 200, data: banner })
Mock.mock("/mock/floor", { code: 200, data: floor })
同时注意:
mockRequest中对外暴露的变量名修改一下 :mockRequests
请求拦截器和响应拦截器也要修改:mockRequests
现在需要在api文件
的index.js
中,把发请求的函数封装好
api的index.js接口文件中
import mockRequests from './mockRequest'
//获取banner(首页轮播图接口)
export const reqGetBannerList = () => mockRequests({ url: '/banner', method: 'get', });
接下来发请求,数据放仓库---组件加载完毕的时候发请求(mounted)
轮播图组件中中
mounted() {
//派发action:通过Vuex发起ajax请求,将数据存储在仓库中
this.$store.dispatch("home/getBannerList");
},
在home小仓库中配置对应函数---仓库三连环
第一步actions
async getBannerList(context) {
const result = await reqGetBannerList();
//成功返回的话我们要修改仓库中的数据了
if (result.code == 200) {
context.commit('GETBANNERLIST', result.data)
}
}
第二步mutations
GETBANNERLIST(state, bannerList) {
//修改state中的categoryList---事先准备好空的categoryList
state.bannerList = bannerList
},
第三步state中配置对应数据类型的初始值
bannerList: []
//OK,到此为止数据已经存储在仓库中了,接下来就是组件拿数据进行展示
ListContainer
组件中---组件从仓库中拿数据
ListContainer组件中
import { mapState } from "vuex";
我在home小仓库中开启了命名空间
...mapState("home", ["bannerList"]),
//到此位置,ListContainer组件拿到了仓库中的数据了
接下来在组件中进行展示即可
Swiper
安装Swiper
插件:npm i swiper@5
swiper
三步骤:引包(JS
和CSS
),页面结构,创建Swiper
实例(放在mounted
当中不行,因为v-for遍历的时候,结构还没完全--因为是ajax
异步请求,派发action--action向服务器请求数据--new Swiper
实例(问题出在这,数据还没回来,v-for还未执行,结构当然没有了)--mutation修改仓库数据,异步。放updated
中也没必要,因为将来如果还有更新的数据,每次都要new Swiper
实例,没必要)
方案一:mounted
中设置延迟器,new Swiper
放在延迟器里面,不推荐
方案二:用监听watch
+nextTick
解决。监听bannerList
数据的变化
$nextTick
:将回调延迟到下次DOM
更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM
更新。
$nextTick
:可以保证页面的结构一定是有的,再new swiper
实例。经常和很多插件一起使用【都需要DOM
存在了】
swiper引包在组件引入即可(JS)
swiper引入样式(CSS)的时候,可以在每个组件中引入,但是考虑岛多个组件都会使用,所以在main.js中引入
ListContainer组件中:
//引包
import Swiper from "swiper";
//引入样式---在main.js中引入
main.js文件中
import 'swiper/css/swiper.css'
ListContainer组件中:
准备好结构和样式
//动态绑定
引入swiper动态操作的JS
在下一轮更新中创建实例this.$nextTick()
$nextTick:将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。
watch: {
//监听bannerList数据的变化---有空数组变成数组中有四个对象
bannerList: {
handler(newValue, oldValue) {
// console.log(newValue, oldValue);
//通过watch监听bannerList属性的变化
//如果handler回调执行,说明组件实例身上这个属性的属性值已经有了---只能保证数据已经有了,结构不一定
//v-for执行完毕了结构才有
this.$nextTick(() => {
//当下一次DOM更新结束后执行回调
var mySwiper = new Swiper(".swiper-container", {
loop: true, // 循环模式选项
// 如果需要分页器
pagination: {
el: ".swiper-pagination",
//点击小球切换图片
clickable: true,
},
// 如果需要前进后退按钮
navigation: {
nextEl: ".swiper-button-next",
prevEl: ".swiper-button-prev",
},
});
});
},
},
},
//注意:Swiper函数的第一个参数也可以是DOM元素
直接操作DOM不太好,可以用ref给节点打标签,然后
this.$refs.mySwiper获取节点元素
Floor
组件静态页面写完,发请求-----写API
仓库存储数据三连环
组件捞数据-----展示
//1 写API
//获取floor数据
export const reqGetFloorList = () => mockRequests({ url: '/floor', method: 'get' })
//2 仓库三连环
const state = {
floorList: []
}
const action = {
// 获取floor数据,commit意思是将来数据要提交给mutations
async getFloorList(context) {
let result = await reqGetFloorList();
if (result.code == 200) {
//提交mutation
context.commit('GETFLOORLIST', result.data)
}
}
}
const mutation = {
GETFLOORLIST(state, floorList) {
state.floorList = floorList
}
}
注意:在
Home组件
中去派发请求获取数据,Home组件
拿到数据,而不是在Floor组件
中。因为我们要遍历Floor组件
//getFloorList这个action在哪里触发?需要在Home路由组件中触发。不能在floor这个组件,因为我们需要v-for遍历floor组件
//3 路由组件捞数据 注意是哪个
//在floor的父亲Home路由组件上捞 派发action
mounted() {
//在派发action,获取floor组件的数据
this.$store.dispatch("getFloorList");
},
//有数据之后 让Home组件拿到数据
import { mapState } from "vuex";
computed: {
...mapState({
floorList: (state) => state.home.floorList,
}),
},
//Home组件数据已经有了,里面就不用写两个 ,可以用v-for遍历了
v-for
也可以在自定义标签中使用
到此为止,父组件
Home
拿到数据,在子组件FloorYu
中v-for
。接下来要给子组件把数据送过去,涉及父子组件中的数据通信
组件间的通信方式有哪些
props
:父子间
自定义事件:@on
@emit
子给父
全局事件总线:$bus
全能
pubsub-js
:几乎不用 全能
插槽(三种)
vuex
我们这里Home
组件给Floor
组件传数据 用props
//Home组件中 用:list 传过去(注意 写floor可能报错
//Floor组件用props接受
props: ["list"],
接下来子组件拿到数据之后,动态展示数据(在FloorYu
组件的标签中,将死的数据,用插值语法填入动态数据)
一个注意点:
mounted() {
//上一次ListContainer写的时候,放在mounted中不可以,这次为什么可以
//第一次写轮播图,是在当前组件内部发送请求的,动态渲染解构【前台至少服务器数据需要回来】,因此不行
//而这次没有在Floor内部发请求,数据是父组件Home给的,在挂载完毕之前数据和结构都完成了
new Swiper(".swiper-container", {
loop: true, // 循环模式选项
// 如果需要分页器
pagination: {
el: ".swiper-pagination",
clickable: true,
},
// 如果需要前进后退按钮
navigation: {
nextEl: ".swiper-button-next",
prevEl: ".swiper-button-prev",
},
});
},
轮播图在ListContainer
和Floor
都有用到,所以可以封装成一个全局组件使用
要保证大概一致,结构样式交互
所以Floor
里面也改用监听来写:
watch: {
list: {
//父亲传过来的数据没有变化,监听不到,所以用immediate上来就监听一次
immediate: true,
handler() {
//只能监听数据,v-for动态渲染结构还无法确定,所以还需要nextTick
this.$nextTick(() => {
new Swiper(".swiper-container", {
loop: true, // 循环模式选项
// 如果需要分页器
pagination: {
el: ".swiper-pagination",
clickable: true,
},
// 如果需要前进后退按钮
navigation: {
nextEl: ".swiper-button-next",
prevEl: ".swiper-button-prev",
},
});
});
},
},
},
公用的组件和非路由组件都放在components文件夹中
首先,在components
文件夹下创建Carousel
文件---index.vue
。将轮播图结构剪切过去,将轮播图JS
的watch
剪切过去
Carousel组件
然后,在入口文件main.js
中全局注册
//轮播图组件----注册全局组件
import Carousel from '@/components/Carousel'
Vue.component('Carousel', Carousel)
然后,在需要的组件中使用Carousel
组件---
(现在给子组件传递数据---Floor组件给Carousel组件传递props方法)
同理,将ListContainer
中的轮播图结构和JS
干掉,swiper
也不用引入了---直接:
Search
模块的开发模块四部曲:
先写静态页面+静态组件拆分出来
发请求(API
)
vuex
仓库(三连环)
组件获取仓库数据,动态展示数据
发送带参请求
//当前这个函数需要外部传递参数吗 要
//给服务器传递的params参数,至少是一个空对象
export const reqGetSearchInfo = (params) => requests({ url: '/list', method: 'post', data: params });
search
小仓库中:
首先引入ajax函数
import { reqGetSearchInfo } from '@/api'
其次书写仓库三连环---注意searchList的类型,可以先在search组件中派发action回来看看数据再写
state: {
//类型不能瞎写,根据返回来
searchList: {}
},
actions: {
//通过API里面的接口函数调用,向服务器发请求,获取search模块的数据
async getSearchList(context, params = {}) {
//reqGetSearchInfo这个函数调用时,至少传递一个空对象
//params形参,是当用户派发action的时候,第二个参数传递过来的,至少是一个空对象
const result = await reqGetSearchInfo(params);
//成功返回的话我们要修改仓库中的数据了
if (result.code == 200) {
context.commit('GETSEARCHLIST', result.data)
}
},
},
mutations: {
GETSEARCHLIST(state, searchList) {
state.searchList = searchList
},
},
//OK到此为止仓库里面有数据了,正常来说我们接下来回到组件中捞数据,展示数据就可以
//但是在这里,我们可以现在仓库中整理好,再捞数据----getter
getters: {
goodsList(state) {
return state.searchList.goodsList || []
},
tradeMarkList(state) {
return state.searchList.trademarkList || []
},
attrsList(state) {
return state.searchList.attrsList || []
},
}
search
组件中捞数据
//开启了命名空间,需要加上'search'
...mapGetters("search", ["goodsList"]),
展示数据
注意:发请求写在search
组件的mounted
中只能发送一次,而search组件
要随着搜索内容的不同,或者用户点击列表的不同,带上不同的参数向服务器发请求获取数据
可以把发请求封装成一个函数getData()
getData() {
this.$store.dispatch("search/getSearchList", this.searchParams);
},
同时,我们把要带过去的参数不能写死为空对象,可以设置一个响应式数据searchParams:{}
,要带哪些参数具体看API接口
//带给服务器的参数
searchParams: {
//注意这边的参数初始值,是用户点击或输入的
category1Id: "",
category2Id: "",
category3Id: "",
categoryName: "",
keyword: "",
order: "",
pageNo: 1,
pageSize: 3,
props: [],
trademark: "",
}
//现在都是初始值,将来要修改他们的值,带给服务器,返回不同数据进行展示
修改好参数
//当组件挂载完毕之前,整理好参数之后再发请求
beforeMount() {
//复杂写法
// this.searchParams.category1Id = this.$route.query.category1Id;
// this.searchParams.category2Id = this.$route.query.category2Id;
// this.searchParams.category3Id = this.$route.query.category3Id;
// this.searchParams.categoryName = this.$route.query.categoryName;
// this.searchParams.keyword = this.$route.params.keyword;
//Object.assign:ES6新增语法,合并对象
Object.assign(this.searchParams, this.$route.query, this.$route.params);
},
search的子组件拿数据,展示数据
import { mapGetters } from "vuex";
...mapGetters("search", ["tradeMarkList", "attrsList"]),
//展示数据即可
今日陷阱:
写bannerList
的属性时,把imgUrl
写成imgURL
,导致home
主页轮播图丢失
记得修改swiper
的高和宽,适应轮播图大小
注意轮播图的使用三步骤,尤其是数据模板的和实例的先后关系