Home页面有7个子组件。
1. TypeNav三级联动组件,在home,search,detail组件都在使用,所以可以注册为全局组件,这样只需要注册一次,就可以在项目的任意地方使用。
步骤:
1)创建组件(命名为TypeNav,全局组件一般放在components文件夹中)
2)全局注册组件(在main.js文件中)
// 三级联动组件(全局组件)
import TypeNav from './components/TypeNav'
// 全局组件:第一个参数是全局组件的名字,第二个参数是哪个组件
Vue.component(TypeNav.name,TypeNav)
3)使用组件(在home组件中使用TypeNav)
2. 完成其余的组件
步骤一样,先创建组件,再在Home组件中引入和注册并使用
说明:
1)本项目是一个前后台分离的项目: 前台应用与后台应用
2)后台应用负责处理前台应用提交的请求, 并给前台应用返回json数据
3)前台应用负责展现数据, 与用户交互, 与后台应用交互
3. 前后端交互AJAX
1)先安装axios
npm install -S axios nprogress
2)axios一般存放在api文件夹(自己创建)里面
index.js---对api进行统一管理
ajax.js---二次封装axios
二次封装的原因:在本项目中需要使用到请求拦截器和响应拦截器
请求拦截器:在发请求之前处理一些业务
响应拦截器:在服务器数据返回后,处理一些事情
在/api/ajax.js
// 二次封装axios
import axios from 'axios'
const service = axios.create({
baseURL: '/api',//基础路径
timeout: 15000//连接请求超时时间
})
// 请求拦截器
service.interceptors.request.use((config) => {
// 必须返回配置对象
return config;
})
// 响应拦截器
service.interceptors.response.use(
(response) => {
// 返回响应体数据
return response.data
}, (error) => {
// 统一处理一下错误
alert(`请求出错: ${error.message || '未知错误'}`)
// 后面可以选择不处理或处理
return Promise.reject(error)
})
export default service
3)api接口的统一管理
项目很小:可以在组件的生命周期函数里发请求
项目大:使用axios.get('xxx')
// 对接口的统一管理
import ajax from './ajax.js'
// 获取商品的三级分类列表
// /api/product/getBaseCategoryList get 无参数
export const reqBaseCategoryList=()=>{
return ajax({url:'api/product/getBaseCategoryList',method:'get'})
}
// export const reqBaseCategoryList=()=>ajax.get(`api/product/getBaseCategoryList`)
4)引入进度条
先安装cnpm install --save nprogress
4.vuex模块化开发
注意:
1)vue2使用vuex3 , vue3使用vuex4
2)项目小的,可以不用vuex;项目大的,组件多的,需要使用vuex
操作流程:给每一个模块创建小仓库,再把小仓库引入到大仓库中
步骤:
1)先创建大仓库(store/index.js),再创建小仓库(store/home.js)
//在store/home.js中
import { reqBaseCategoryList } from '@/api'
const home={
state:{
baseCateGoryList:[]
},
mutations:{
RECEIVE_BASE_CATEGORY_LIST(state,list){
list.shift()//去除数组的第一个元素
state.baseCateGoryList=list
},
},
actions:{
async getBaseCateGoryList({commit}){
const result=await reqBaseCategoryList()
if(result.code==200){
commit('RECEIVE_BASE_CATEGORY_LIST', result.data)
}
}
},
getters:{},
namespaced:true
}
export default home
2)在大仓库里引入小仓库home
在store/index.js
// 大仓库
import Vue from 'vue'
import Vuex from 'vuex'
// 需要使用插件一次
Vue.use(Vuex)
import home from './home'
// 对外暴露store类的一个实例
export default new Vuex.Store({
// 实现Vuex仓库模块化开发存储数据
modules:{
home,
}
})
3)在main.js中,引入store
// 引入仓库
import store from './store'
new Vue({
render: h => h(App),
// 注册路由
router:router,
// 注册仓库:
store
}).$mount('#app')
5. 实现TypeNav三级动态联动(向服务器发送请求,获取数据,进行展示)
1)当组件挂载完毕后,可以向服务器发送请求
//在TypeNav/index.vue中
mounted(){
this.$store.dispatch('home/getBaseCateGoryList')
},
2)去home仓库请求数据
actions在执行时,会调用api里面的接口函数,向服务器发请求,调用api里的reqBaseCategoryLis函数发送。
请求成功后,把数据给mutations处理。
再把数据给state。
3)TypeNav接收数据
4)渲染数据
注意
1. home仓库使用了namespaced,开启了命名空间,在使用仓库时,记得添加/home
2. 在使用vuex模块化时,需要向外暴露一个对象,不需要用到createStore。所以给home里面命名一下,并向外暴露。
3.注意vuex3和vuex4的区别 。
6. 给三级联动的一级分类添加背景颜色,二三级进行显示与隐藏
步骤:
1)首先设置一个动态属性 currentindex=-1 ,表示鼠标都没有移上去。
//在TypeNav/index.vue中
data() {
return {
// 响应式属性,存储用户鼠标移上哪一个一级分类
currentIndex: -1, // 代表鼠标谁都没有移上去
};
},
2) 添加一个less属性,设置背景颜色
3)给一级分类添加鼠标进入和离开事件。进入时,需要携带参数索引号,表示是哪一个一级分类。
当 :class="{cur: currentIndex === index} 为真时,就给当前的一级分类添加背景颜色。
//在TypeNav/index.vue中
<div class="item" v-for="(c1, index) in baseCateGoryList" :key="c1.categoryId"
@mouseleave="leaveIndex">
<h3 @mouseenter="changeIndex(index)" :class="{ cur: currentIndex === index }" >
<a>{{ c1.categoryName }}</a>
</h3>
</div>
methods: {
// 鼠标进入修改响应式数据currentIndex属性
changeIndex(index) {
// index 鼠标移上某一个一级分类的元素的索引值
this.currentIndex = index;
},
// 一级分类鼠标移出的事件回调
leaveIndex(){
// 鼠标移出currentIndex=-1
this.currentIndex =-1;
}
},
4)使用原生JS实现二三级分类的显示与隐藏(利用三元表达式实现)
7. 三级联动的防抖和节流
防抖:前面的触发都取消,最后一次执行在规定时间之后才会触发。也就是说如果连续快速的触发,只会执行最后一次。
节流:在规定间隔时间内,不会重复触发回调,只有大于了时间间隔,才会触发,把频繁触发变为少量触发。
本项目中,鼠标不停的在三级联动上来回切换,进行节流操作,把频繁触发变为少量触发。
这里需要使用 lodash,一般都下载好了的。并且进行按需加载。
//在TypeNav/index.vue中
//按需引入节流
import throttle from "lodash/throttle";
methods: {
// 鼠标进入修改响应式数据currentIndex属性
// throttle回调函数不要用箭头函数,防止出现上下文this问题
changeIndex: throttle(function (index) {
// index:鼠标移上某一个一级分类的元素的索引值
this.currentIndex = index;
}, 50),
},
8.三级联动的路由跳转(从home跳转到search)
需求:当点击某个分类时,进行路由跳转,并把分类的名字和id传给search,然后search拿到参数向服务器发请求,显示相应的数据。
路由跳转的两种方式:声明式导航和编程式导航。
如果使用声明式导航,需要使用<router-link></router-link>,并且<router-view>会生成好多组件,会出现页面的卡顿。
如果使用编程式导航,需要给每一个a添加点击事件。
这里使用编程式导航+事件委派。
事件委派:在父节点上添加事件监听器,利用事件冒泡影响每一个子节点。
注意两个问题:
1)事件委派,是把全部的子节点的事件委派给父亲节点,如何确定我们点击的一定是a标签
利用自定义属性,给子节点中的a标签添加自定义属性data-categoryName,其余子节点没有。
2)确定了点击的是a标签,如何区分是一级,二级,三级分类的标签
同样也是利用自定义属性,给子节点中的a标签添加自定义属性data-category1Id、data-category2Id、data-category3Id。
步骤:先判断是不是a标签,是则进一步判断是哪一个分类。
1)先给一级分类的父标签添加点击事件,进行路由跳转。
2)给每一分类级的a标签添加自定义属性。
3)编写跳转函数
//进行路由跳转
// 从home跳转到search
goSearch(event){
//获取当前事件的触发对象
let element=event.target;
//利用节点的dataset属性,获取节点的自定义属性(要小写)
let { categoryname, category1id, category2id, category3id }=element.dataset;
//判断,如果categoryname为真,则表示当前点击的是a标签
if(categoryname){
//整理路由跳转的参数
let location={name:'Search'};//记得在router/index.js中给search命名
// 小写的是自定义属性值,大写的是新添加的属性值
// 需要获取小写的自定义属性值给大写的新添加的属性值
let query={categoryName:categoryname};
// 判断是一级,二级,三级哪一个分类
if(category1id){
query.category1Id=category1id;
}else if(category2id){
query.category2Id=category2id
}else {
query.category3Id=category3id
}
//此时query里面就有了name和id
//再把query给到location
location.query=query;
//现在进行传参
this.$router.push(location)
}
}
9.Search模块中三级联动的显示与隐藏和添加过渡动画效果
步骤:
1)先给TypeNav组件添加一个属性show,表示显示与隐藏三级联动。
2)给三级联动使用v-show,进行显示与隐藏。
3)在搜索页面,最开始三级联动是隐藏的,只有当鼠标移上去,才显示出来。
所以,当组件挂载完毕后,如果不是home组件,就隐藏三级联动
4) 鼠标在全部商品分类上移入移出,进行三级联动的显示与隐藏。
这里可以调用事件委派,给透明封装一个大盒子,在大盒子上进行鼠标移入移出事件。
5)添加过渡动画
注意:
1)组件或者元素务必要有v-if或者v-show指令
2)记得加一个name,使用transition标签包住
// 过渡动画的样式
// 过渡动画开始的状态(进入)
.sort-enter {
height: 0px;
}
// 过渡动画结束状态(进入)
.sort-enter-to {
height: 461px;
}
// 定义动画时间、速率
.sort-enter-active {
transition: all 0.5s linear;
}
10.TypeNav组件在进行路由跳转时,会传递参数,就需要发送ajax请求,每一次跳转都会发一次请求,很耗性能,需要进行优化!
操作:最开始的时候,发送请求是在TypeNav组件中的mounted钩子函数中,现在把请求放入到App.vue文件中。因为最先执行的就是App.vue,这样请求就只会发送一次!!!
11.合并参数,实现跳转时的params参数和query参数都可以传递
从首页向Search组件跳转有两种方式:
1.通过搜索关键字跳转(传的是params参数)
2.点击三级导航的链接跳转(传的是query参数)
如果既点击了搜索按钮,又点击了三级导航的链接,那么点击的后者的参数会覆盖前者的参数,所以需要进行参数的合并,这样两个参数都可以传递!!!
//在Header.vue中
methods:{
//在搜索框输入params参数后,点击搜索按钮进行跳转
search(){
// this.$router.push(`/search/${this.keyword}`)
//这里进行参数合并,query和params
let location={name:'Search',params:{keyword:this.keyword||undefined}}
location.query=this.$route.query;
this.$router.push(location)
}
}
在TypeNav/index.js中
12.开发首页的ListContainer(轮播图)、Floor(底部)组件
首先使用mock搭建模拟数据
步骤:
1)先安装:npm install mockjs
2)在src文件夹中创建mock文件夹,用来存放json假数据
3)在mock文件夹中准备假数据,引入ListContainer、Floor的数据
4)在public下创建一个images文件夹,把mock数据需要的图片存放进去
5)在mock文件夹下创建mockServe.js,通过Mock.mock方法进行模拟数据
6)在main.js里引入src/mock/mockServe.js,让配置假数据执行一次
7)在api文件夹中创建mockAjax.js文件,模拟发送请求。
里面的内容与原先二次封装的request.js文件中内容一样,但是要注意,记得把基础路径改为'/mock',因为我们是模拟发送请求
8)在src/api/index.js文件中,进行api的统一管理,模拟轮播图的接口
其次,获取轮播图数据
1)在组件挂载完毕后,向服务器发送请求
2)使用vuex三连环
3)接收数据
然后,绘制轮播图
这里需要使用swiper插件
安装swiper插件:npm i swiper --save
使用swiper的三步骤:
1.引入相应的包
2.页面结构必须先有(给轮播图添加静态效果)
3.有结构后再new Swiper实例(给轮播图添加动态效果)
步骤:
1.引包
1)在main.js中引入css(这样组件都可以使用)
// 引入swiper样式
import 'swiper/css/swiper.css'
2)在listContainer组件中引入swiper
// 引包
import Swiper from 'swiper'
2.搭建轮播图页面的结构
3.添加动态效果(重点)
这里要注意,不能直接在mounted里面写,一般情况下在里面写是正确的。但这里不行,这里有dispatch,涉及到异步语句,需要发请求,拿数据,导致v-for遍历的时候,结构还没有完全,所以还不能实例化swiper。
如果直接在里面写,会先实例化swiper,再搭建结构,这样是不符合swiper操作步骤的。
页面结构必须先生成,再new Swiper!!!
解决方法:
1)使用定时器包裹swiper,但是有问题,因为发送ajax请求的时间不确定,所以定时器时间不好把握。
2)使用watch监听,但是只使用watch,只能监听到数据的变化,不能判断v-for已经执行完毕了,所以也不行。
3)使用watch+$nextTick,在监听到数据回来后,结构完成后,使用$nextTick来实例化swiper,就可以完成动态效果渲染。
所以使用方法三。
//在Home/ListContainer.vue
watch:{
// 监听banners数据的变化,有空数组变为数组里有4个元素
banners:{
immediate:true,
handler(newValue,oldValue){
// 通过监听banners属性值的变化,如果执行了Hanler,则表示数据有了,
//但是只能保证有数据了,不能保证v-for已经执行完毕
//只有v-for执行完后,才有结构,才能实例化swiper
//所以这里需要添加$nextTick
// nextTick:在下次DOM更新, 循环结束之后,执行延迟回调。在 修改数据之后 立即使用这个方法,获取更新后的DOM。
this.$nextTick(()=>{
// 下面的是直接复制的swiper里面的代码
var mySwiper = new Swiper ('.swiper', {
loop: true, // 循环模式选项
autoplay:true,//自动切换
// 如果需要分页器
pagination: {
el: '.swiper-pagination',
clickable:true,
},
// 如果需要前进后退按钮
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
})
})
}
}
}
开发floor组件
和前面一样,先写静态,之后发请求(写api),写完api之后就写仓库三连环,仓库存储数据后,组件捞数据,捞完之后展示。
1)配置api接口
2)仓库三连环存储数据
在store/home.js中写vuex三连环
3)派发action
注意,这里不能在floor组件里面派发,因为这里有两个floor组件,需要使用v-for遍历,所以在他的父组件home组件去派发。
4)收取数据
因为现在这个数据在父组件home里,子组件floor想要得到数据,就得使用组件间的通信,这里使用父向子传。
使用props传递。
5)渲染数据到页面
6)floor轮播图制作
注意在这里就可以直接在mounted里面实例化swiper。
因为这里请求是父组件发的,父组件通过props传递过来,并且结构已经有了,才执行的mounted
封装Carsouel全局组件
因为在ListContainer和Floor中都用到了carsouel轮播图,我们可以封装成一个全局组件,这样方便使用。
封装成为全局组件,注意里面的内容都是一样的,所以需要修改一下原先的内容。
由于floor组件的是父亲传过来的值,所以里面数据已经有了,所以数据没有变化,无法通过watch监听到,所以要使用immediate,立即监听。
由于ListContainer组件的v-for结构还不能确定已经完成了,所以要使用nextTick
然后两者一结合,写在components文件夹里面创建的一个Carousel文件夹中。
这样当需要使用轮播图的时候,直接引入这个文件就可以了。
注意:全局组件记得在main.js中引入并注册!!!