项目网络教学视频链接:尚硅谷VUE项目实战,前端项目-尚品汇(大型\重磅)_哔哩哔哩_bilibili

目录

一、 使用vue-cli脚手架去初始化项目

二、项目的其他配置

三、项目路由分析

四、创建Header和Footer非路由组件

五、完成路由组件的搭建

六、利用【路由元信息】实现显示或隐藏组件

七、路由传递参数

八、重写push和replace方法

九、Home首页组件拆分业务分析

十、完成三级联动全局组件

十一、Home首页拆分静态组件

十二、使用【POSTMAN工具】测试接口

十三、对axios进行二次封装

十四、接口统一管理

十五、nprogress进度条的使用

十六、VUEX模块式开发

十七、动态展示三级联动

十八、三级联动动态背景颜色

十九、通过JS控制二三级分类的显示与隐藏

二十、引入防抖与节流

二十一、三级联动路由跳转分析

二十二、实现三级联动的路由跳转与传递参数

二十三、Search模块商品分类与过渡动画

二十四、TypeNav商品分类列表的优化

二十五、合并params和query参数

二十六、mock.js模拟数据

二十七、获取Banner轮播图数据

二十八、swiper基本使用

二十九、Banner实现轮播图(第一种解决方案)

三十、轮播图:watch+nextTick( )(第二种解决方案)

三十一、获取floor组件mooc数据

三十二、动态展示Floor组件

三十三、共用组件Carsouel(轮播图)

三十四、Search模块的静态组件

三十五、Search模块的VUEX操作

三十六、Search模块动态展示产品列表

三十七、Search模块根据不同的参数进行数据展示

三十七、Search模块中子组件动态开发

三十八、监听路由的变化再次发请求获取数据

三十九、面包屑处理分类的操作

四十、面包屑处理关键字

四十一、面包屑处理品牌信息

四十二、平台售卖属性的操作

四十三、排序操作

四十四、分页器静态组件

四十五、分页功能分析

四十六、分页器动态展示

四十七、分页器完成

四十八、滚动行为

四十九、获取产品详情数据

五十、产品详情数据动态展示

五十一、zoom放大镜展示数据

五十二、detail路由组件展示产品售卖属性

五十二、产品售卖属性值排他操作

五十三、放大镜操作

五十四、购买产品个数的操作

五十五、加入购物车

五十六、路由传递参数结合会话存储

五十七、购物车静态组件与修改

五十八、uuid游客身份获取购物车数据

五十九、购物车动态展示数据

六十、处理商品数量

六十一、删除购物车产品的操作

六十二、修改产品勾选状态

六十三、删除全部选中的商品

六十四、全部产品的勾选状态修改

六十五、注册业务实现

六十六、登录业务


一、 使用vue-cli脚手架去初始化项目

准备:提前安装好node、webpack、淘宝镜像(最好有)

1. 找到文件夹目录,输入cmd,出现下面内容,输入“ vue create 项目名”,回车确认

2. 选择vue2版本,创建好之后,使用VS打开该文件夹。

3. 分析目录组成

二、项目的其他配置

1. 如何让浏览器自动打开这个项目?找到package.json这个文件,找到 "serve": "vue-cli-service serve",将其改为"serve": "vue-cli-service serve --open",如图所示:

2. 关闭eslint校验功能:在根目录下,创建一个vue.config.js文件。配置以下内容:

module.exports = {//关闭校验工具lintOnSave:false,
}

3. 设置src文件夹简写方式,配置别名:@。在根目录下,创建jsconfig.json文件,配置以下内容:

{"compilerOptions": {"target": "es5","module": "esnext","baseUrl": "./","moduleResolution": "node","paths": {"@/*": ["src/*"]},"lib": ["esnext","dom","dom.iterable","scripthost"]}
}

三、项目路由分析

前端路由:类似于【key--value键值对】的形式,其中key表示URL,value表示对应的路由组件

这里需要使用vue-router来实现。

项目结构主要分为上、中、下三部分

路由组件包括:Home首页路由组件、Search搜索路由组件、login登录路由组件、注册路由组件

非路由组件包括:Header组件【首页、搜索、登录、注册】、Footer组件【在首页、搜索页】

四、创建Header和Footer非路由组件

(1)在开发项目的时候:1. 书写静态页面(HTML+CSS)

2. 拆分组件

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

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

(2)那么非路由组件创建在哪里?在src文件夹下创建components文件夹,在该文件夹中分别创建Header和Footer文件夹,用于实现非路由组件。

(在创建组件时,需要注意三要素:组件结构+组件的样式+图片资源)

(3)在非路由组件文件夹中,创建vue类型的文件:index.vue

对于样式,如果采用的是less样式,浏览器不能识别less样式,需要通过less、less-loader进行处理,把less样式变为css样式,这样浏览器才能识别。

1. 先安装less-loader依赖(这里需要注意,版本不能过高,否则不能使用,这里选择5版本,如果不说明默认是最高版本)

2. 还需要在style标签的身上加上lang=lees

对于图片资源,在非路由组件文件中创建一个images文件夹,用于存放数据

(4)当组件创建好之后,就要使用该组件了,步骤为:引入----注册----使用

五、完成路由组件的搭建

(1)安装vue-router插件

(2)通过上面分析,路由组件应该有四个:Home、Search、Login、Register,

那么路由组件通常创建在哪里呢?在src文件夹下创建pages文件夹,在该文件夹中分别创建Home、Search、Login、Register文件夹,用于实现路由组件。

(3)配置路由

在src文件夹下创建router文件夹,在该文件夹中创建一个index.js文件,用来配置路由信息

配置路由的时候,还要实现【重定向】,即在项目跑起来的时候,当访问 / 时,会立马定位到首页

(4)接着,在main.js文件中【引入路由】和【注册路由】

PS:当这里书写router的时候,不管是路由组件还是非路由组件,身上都拥有$route、$router属性

$route:一般获取路由信息【路径、query、params】

$router:一般进行编程式路由导航进行路由跳转【push | replace】

(5)最后还要展示路由,即在App.vue文件中设置【路由组件出口的地方】

(6)【总结】路由组件和非路由组件的区别?

1. 路由组件一般

(7)进行路由跳转

有两种形式:1.声明式导航router-link,可以进行路由的跳转

2.编程式导航push|replace,可以进行路由跳转

声明式导航能做的,编程式导航都能做,但是编程式导航除了可以进行路由跳转,还可以做一些其他的业务逻辑。

在【index.vue】中设置路由跳转

六、利用【路由元信息】实现显示或隐藏组件

分析Footer组件:实现它在Home、Search中显示,在Register、Login中隐藏

(1)方法一(不推荐):在上节中,我们知道这时组件已经具备$route属性,可以获取路由路径

显示或者隐藏组件:v-if、v-show(这里采用v-show,性能更好,不频繁操作DOM)

(2)方法二(推荐):即利用【路由元信息】

这里放上有关路由元信息的官方文档内容:路由元信息 | Vue Router

找到router文件夹中的index.js文件,将【谁可以具有Footer组件的信息】通过接收属性对象的meta属性来实现,并且它可以在路由地址和导航守卫上都被访问到。

然后在App.vue文件中,进行$route.meta.show判断,如果为真则显示,如果为假则隐藏

七、路由传递参数

我们已经了解到路由跳转有两种方式:声明式导航、编程式导航

路由进行传参时,参数一般有种写法:

params参数:属于路径当中的一部分,在配置路由的时候需要【占位】

query参数:不属于路径当中的一部分,类似于ajax中的queryString,不需要占位

(1)第一种路由传递参数的方式:【字符串形式】

1.先在路由配置信息中进行占位

2.进行路由push跳转,跳转到search页面时传递相应的【路由参数】

3.这时在Search页面中,通过【路由信息】就可以获取到params参数

(2)第二种路由传递参数的方式:【模板字符串】

1.第一步和上个方法相同

2.和上个方法的第二部有些区别,采用模板字符串的方式

3.接收参数和上个方法相同

(3)第三种路由传递参数的方式:【对象】

1. 当使用【对象】的方式进行传参,传入的参数又是params参数时,需要在路由配置信息中           为路由设置【名字】,name: "XXX"

2.传递参数,形式如下图所示

3.接收参数和上个方法相同

八、重写push和replace方法

【问题】:编程式路由导航跳转到当前路由(参数不变),多次执行会抛出NavigationDuplicated的警告错误(但不影响最终的结果)?而声明式导航是没有这类问题的,因为vue-router底层就已经处理好了。

【原因】:最新的vue-router引入了promise,即调用push方法会返回promise对象,但没有向其中传入成功的回调和失败的回调。

【解决方法1】:在调用push方法时,就传入成功和失败的回调。(可以捕获出error看看错误类型)但是这种方法治标不治本。将来在别的组件中,不管是push还是replace,编程式导航还是有类似的错误。这样一次次解决下去太麻烦了。

【解决方法2】:首先搞清楚上段代码中的this是什么、this.$router是什么、push是什么

this:当前组件实例

this.$router属性:这个属性的属性值是VueRouter类的一个实例,即当在入口文件注册路由的时候,给组件实例添加的$router和$route属性

push:VueRouter类原型上的方法

为了更好的理解this.$router.push( )方法,我们根据这三个的特性实现简单的伪代码

//构造函数VueRouter
function VueRouter(){}
//原型对象上的方法
VueRouter.prototype.push = function(){//函数的上下位为VueRouter类的一个实例
}
//实例化一个VueRouter对象
let $router = new VueRouter();$router.push(xxx);

因此想要治本,必须重写VueRouter原型上的push方法。在有【路由配置信息】的文件中进行重写,因为在这个文件中,我们是可以获取到VueRouter类的

(replace方法重写和上述类似)

九、Home首页组件拆分业务分析

【第一个组件】:因为【三级联动组件】在很多页面中都使用了,因此将其拆分成一个全局组件,哪里想用就用哪里(红色框出来的就是三级联动的展示)。

【第二个组件】:轮播图+尚品汇快报

【第三个组件】:今日推荐

【第四个组件】:排行榜

【第五个组件】:猜你喜欢

【第六个组件】:家用电器|手机通讯等,组件可被复用

【第七个组件】:商品logo

十、完成三级联动全局组件

(1)在page文件夹中的Home文件夹下,新建一个文件夹TypeNav,在该文件夹中创建index.vue文件,用来配置【三级联动组件】的内容

(2)在HTML静态资源中找到有关【三级联动】的结构代码,把代码内容放入到index.vue文件的template标签中。

(3)在css|less静态资源中找到有关【三级联动】的代码,将代码内容放入到index.vue文件的style标签中,并设置lang属性,以便能够正常处理less

(4)将该组件注册为全局组件:找到入口文件main.js,在该文件中将【三级联动组件】注册为全局组件。

(5)此时【三级联动组件】已经注册为全局组件,在其他地方使用它时,不需要进行引入和注册,直接使用即可。

十一、Home首页拆分静态组件

拆分时要注意三部分:HTML、CSS、图片资源

(1)创建一个名为ListContainer的组件,按上小节的步骤对HTML和CSS进行拆分,这里需要注意的是:HTML中图片资源的路径可能已经发生了变化,需要根据目前的路径进行修改。

(2)该组件创建好之后,在Home组件中进行【引入】、【注册】和【使用】

(Recommend组件、Rank组件、TypeNav组件、Like组件的【创建、引入、注册和使用方式】和上述相同,这里不再赘述)

十二、使用【POSTMAN工具】测试接口

测试后端给的接口是不是可用,后端通常会给出服务器地址、请求地址、请求方式等等信息。根据这些信息,在POSTMAN工具中配置好这些信息。

十三、对axios进行二次封装

首先,搞清楚为什么要进行二次封装?因为我们想使用请求拦截器和响应拦截器

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

【响应拦截器】:当服务器返回数据之后,可以处理一些业务


使用前先进行安装:npm install --save axios

可以在package.json中查看是否已经安装成功,如下


在项目中通常使用API文件夹放置【axios】相关内容,因此在src文件夹中创建一个api文件夹

在api文件夹中创建一个request.js的文件,在其中实现axios的二次封装,代码如下

//对axios进行二次封装,
import axios from 'axios'// 利用axios对象得方法create,去创建一个axios实例
// request就是axios,只不过稍微配置一下
const requests = axios.create({//配置对象//基础路径,发送请求的时候,路径当中会出现apibaseURL:'/api',//代表请求超时的时间5Stimeout:5000
});
// 请求拦截器
requests.interceptors.request.use((config)=>{//config:配置对象,对象里面有一个属性很重要,header请求头return config;
});
// 响应拦截器
requests.interceptors.response.use((res)=>{//成功的回调函数:服务器相应数据回来以后,响应拦截器可以检测到,可以做一些事情return res.data;
},(error)=>{console.log(error)//响应失败的回调函数return Promise.reject(new Error('faile'))
})//对外暴露
export default requests;

十四、接口统一管理

如果项目规模很小,完全可以在组件的生命周期函数中发请求

如果项目规模比较大,会存在这样一种情况:有几十个组件使用了这个接口,后期接口变动了,就得一个个去修改组件当中接口的内容,很不方便。因此采用【接口统一管理】


在api文件夹中新创建一个js文件,名为index,在其中进行接口的统一管理

//当前这个模块:API进行统一管理
import requests from './request';//三级联动接口
//暴露这个函数,外面拿到这个函数,直接调用,就能发送请求获取数据了
export const reqCategoryList = ()=>{//返回的结果是promise对象 当前函数执行需要把服务器返回结果进行返回return requests({url:'/product/getBaseCategoryList',method:'get'})
}

测试之后,发现请求发生404错误,这是因为【跨域问题】

解决跨域问题的方法有很多,这里采用【代理服务器】去解决,在vue.config.js文件中进行配置

module.exports = {//打包时不要有map文件productionSourceMap:false,//关闭校验工具lintOnSave:false,//代理跨域devServer:{proxy:{'/api':{ //遇到带有api的请求,代理服务器才会将其转发target:'http://gmall-h5-api.atguigu.cn',// pathRewrite:{'^/api':''},}}}
}

注意:这是一个配置文件,写好之后需要重新运行一下才可以~

十五、nprogress进度条的使用

先下载nprogress进度条:npm install --save nprogress,

下载完成之后在package.json中查看是否安装成功。


nprogress进度条需要在请求拦截器和响应拦截器中去使用

先引入进度条:import nprogress from 'nprogress'

还要引入进度条样式:import "nprogress/nprogress.css"

【请求拦截器】:启动进度条 nprogress.start( )

【响应拦截器】:结束进度条nprogress.done( )

十六、VUEX模块式开发

vuex:并不是所有的项目都需要vuex,如果项目很小,则不需要;如果项目比较大,则需要使用vuex进行数据的统一管理

先安装vuex:npm install --save vuex,下载完成之后在package.json中查看是否安装成功

在src中新建一个文件夹store,用来实现vuex,创建index.js文件进行配置

import Vue from 'vue'
import Vuex from 'vuex'
//需要使用插件一次
Vue.use(Vuex)
//state:仓库存储数据的地方
const state = {}
//mutation:修改state的唯一手段
const mutation = {}
//actions:可以书写自己的业务逻辑,也可以处理异步
const actions = {}
//getters:可以理解为计算属性,用于简化仓库数据,让组件获取仓库的数据更加方便//对外暴露Store类的一个实例
export default new Vuex.Store({state,mutations,actions,getters
})

还要在入口文件main.js中引入这个仓库:import store from '@/store' 并进行注册

import Vue from 'vue'
import App from './App.vue'
//三级联动组件+全局组件
import TypeNav from "@/components/TypeNav"//第一个参数:全局组件的名字 第二个参数:哪一个组件
Vue.component(TypeNav.name, TypeNav)//测试
//引入仓库
import store from '@/store'new Vue({render: h => h(App),//注册路由:底下的写法KV一致省略V//注册路由信息:当这里书写router的时候,组件身上都拥有$route,$router属性router,//注册仓库:组件实例的身上会多一个$store属性store
}).$mount('#app')

接下来就要进行vuex的模块化开发了

为什么需要模块化开发?如果项目过大,组件过多,接口也很多,数据也很多,store对象会变得相当臃肿,因此可以让vuex实现模块化开发,即把一个大仓库拆分成一个个的小仓库。

可以给home、search等这样的模块单独设置一个store小模块,然后再把小模块混入到大模块中

//home模块的小仓库
const state = {};
const mutations = {};
const actions = {};
const getters = {};
export default {state,mutations,actions,getters
}
//大仓库
import Vue from 'vue'
import Vuex from 'vuex'
//需要使用插件一次
Vue.use(Vuex)
//引入小仓库
import home from './home'
import search from './search'//对外暴露Store类的一个实例
export default new Vuex.Store({//实现Vuex仓库模块式开发存储数据modules:{home,search}
})

十七、动态展示三级联动

【三级联动】组件是一个全局组件,放在components文件夹中。

下面这个图就很好地展现出组件是如何获取数据的、仓库是如何去请求数据的

对三级联动组件TypeNav进行配置

<script>
import {mapState} from 'vuex';export default {name:'TypeNav',//组建挂载完毕:可以向服务器发请求mounted() {//通知vuex发请求,获取数据,存储于仓库中this.$store.dispatch('categoryList') },computed:{...mapState({//右侧需要的是一个函数,当使用这个计算属性的时候,右侧函数会立即执行一次//注入一个参数state,这指的是大仓库中的数据categoryList:(state)=>{return state.home.categoryList;}})}
};
</script>

找到home模块的小仓库,进行配置

import {reqCategoryList} from '@/api';
//home模块的小仓库
const state = {//state中数据默认初始值别瞎写 【根据接口的返回值去初始化】categoryList:[],
};
const mutations = {CATEGORYLIST(state,categoryList){state.categoryList = categoryList},
};
const actions = {//通过API里面的接口函数调用,向服务器发送请求,获取服务器的数据async categoryList({commit}){ //对commit进行解构赋值let result = await reqCategoryList();if(result.code === 200){commit("CATEGORYLIST",result.data);}}
};
const getters = {};
export default {state,mutations,actions,getters
}

通过以上步骤,三级联动组件TypeNav就已经获取到数据啦!接下来就要把数据展示到页面上了。

对代码进行分析,发现一级目录很多,如下图这样:

因此可以只留一个,并通过v-for进行优化

<div class="item" v-for="(c1,index) in categoryList" :key="c1.categoryId">

则一级目录的a标签名称也要改

<a href=" ">{{c1.categoryName}}</a>

二级分类也很多,同样采用v-for进行优化

<div class="subitem" v-for="(c2,index) in c1.categoryChild" :key="c2.categoryId" >

则二级目录的a标签名称也要改变

<a>{{c2.categoryName}}</a>

三级分类也很多,同样采用v-for进行优化

<em v-for="(c3,index) in c2.categoryChild" :key="c3.categoryId">

则三级目录的a标签名称也要改变

<a>{{c3.categoryName}}</a>

十八、三级联动动态背景颜色

第一种解决方案:直接添加CSS样式(这里不用,因为很简单,来些具有挑战性的,哈哈哈)

第二种解决方案:动态添加类名

先来理一下思路:

1. 在data中定义一个变量,名为currentIndex,初始值设置为-1(不能设置为0-15之间的数,总共有16个标题)

data() {return {//存储用户鼠标移上哪一个一级分类currentIndex: -1}},

2. 为标题绑定一个原生JS事件mouseenter,并传入index,事件的回调函数定义在methods中,在回调函数中,将传入的值赋给currentIndex,这样就能拿到鼠标移动到的当前标题的index了

<h3 @mouseenter="changeIndex(index)">
methods:{enterShow(){this.show = true},
}

3. 在一级标题的循环中,判断currentIndex==index是否成立,成立的话就添加一个类,这个类就实现了添加背景色的效果。

<div class="item" v-for="(c1,index) in categoryList" :key="c1.categoryId" :class="{cur:currentIndex == index}">

实现完成之后,发现存在一个问题,鼠标移除之后还有背景颜色,这是不合理的,应该背景颜色去掉才可以。出现问题不用慌,解决就是了,再给标题添加一个鼠标移除事件喽,

但是又出现了一个问题,鼠标移到“全部商品分类”上,背景颜色应该还是存在的。(个人觉得这个实现完全没必要,看起来更像是个BUG,为了练手,还是实现一下吧)

其实就用到了事件委派,就“全部商品分类”和“三级联动”放在同一个div中,且二者是兄弟关系

<!-- 事件的委派 -->
<div @mouseleave="leaveShow"><h2 class="all">全部商品分类</h2><!-- 三级联动 --><div class="sort"></div>
</div>

十九、通过JS控制二三级分类的显示与隐藏

鼠标移动到哪个标题,就展示哪个标题下的二三级分类列表

第一种解决方案:直接改变CSS样式

第二种解决方案:通过JS实现

思路:在上一节中,我们已经通过事件监听将一级标题的index传递给了data中的currentIndex变量,如果index==currentIndex,则将二三级分类的样式设置为display:'block',否则设置为“none”

<div class="item-list clearfix" :style="{display:(currentIndex == index ? 'block':'none')}">

二十、引入防抖与节流

防抖:前面的所有的触发都被取消,最后一次执行在规定时间之后才会触发,也就是说如果连续快速地触发,只会执行一次。

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


实现的时候利用一个插件,叫做lodash,里面封装了防抖与节流的业务【闭包+延时器】

这里举一个防抖的小栗子:输入框输入数据时,进行Ajax请求

如果不采用防抖的话,每输入一个字就要发一次请求,假如我们输入“梅西世界杯”,会发送五次请求。这并不满足我们的实际需求,我们想要输入完这五个字,才会发送请求,因此采用防抖技术进行解决。

let input = document.querySelector('imput')
//不加防抖
input.oninput = function(){//这里放ajax发请求的代码
}
//加了防抖
input.oninput = _.debounce(function(){//这里放ajax发请求的代码
},1000);

这里举一个节流的小栗子:实现一个简单的计时器,即点击按钮,实现数字元素的增加

<h1>我是计时器<span>0</span></h1>
<button>点击我加上1</button>
....let span = document.querySelector('span');
let button = document.querySelector('button');
let count = 0;
//未加节流
button.onclick = function(){count++;span.innerHTML = count;
}
//加了节流
button.onclick = _.throttle(function(){count++;span.innerHTML = count;
},1000);

在项目中实现节流:三级联动这里用户的交互操作可能会过快,导致浏览器反应不过来,如果当前回调函数中有一些大量业务,有可能出现卡顿现象。

vue脚手架中已经下载好了lodash,可直接全部引入lodash内容: import _ from 'lodash'

这里我们可以按需引入,只引入节流:import throttle from 'lodash'

//未加节流的代码
changeIndex(index){this.currentIndex = index;}
//加了节流的代码
//throttle回调函数别用箭头函数,可能会出现上下文this
changeIndex:throttle(function(index){//index:鼠标移上某一个一级分类的元素的索引值//正常情况(用户慢慢地操作):鼠标进入,每一个一级分类h3,都会触发鼠标进入事件//非正常情况(用户操作很快):本身全部的一级分类都应该触发鼠标进入事件,但是经过测试,只有部分h3触发了//就是由于用户的行为过快,导致浏览器反应不过来,如果当前回调函数中有一些大量业务,有可能出现卡顿现象。        this.currentIndex = index;},50),

二十一、三级联动路由跳转分析

关于路由,我发了一篇vue-router思维导图的文章,可以帮助大家回忆起相关内容

链接在此:vue路由知识点概括--思维导图_yuran1的博客-CSDN博客


对于三级联动,用户可以点击的:一级分类、二级分类、三级分类,当我们从Home模块跳转到Search模块时,一级会把用户选中的产品(比如产品的名字、产品的ID)在路由跳转的时候进行相应的传递。

注意:这里如果使用的是声明式路由导航,可以实现路由的跳转与传递参数,但需要注意,会出现卡顿的现象,这是为什么呢?

原因:router-link可以看作是组件,当服务器的数据返回之后,由于v-for的设置,会循环出很多的router-link组件,这种方法很消耗内存,所以会出现卡顿的现象。因此这里采用编程式路由导航


但是那么多a标签,都给它们绑定click事件的回调函数的话,肯定太繁琐、太消耗内存了。

事件委派又派上用场了,我们把click事件的回调函数放在父元素身上,不用再一一绑定了。

<div class="all-sort-list2" @click="goSearch">

但是利用事件委派之后,还存在一些问题:

1. 你怎么知道点击的一定是a标签的?也有可能是div、h3等标签

2. 如何获取参数呢?【1、2、3级分类的产品的名字、id】,如何区分1、2、3级分类的标签?

解决方法看下一节

二十二、实现三级联动的路由跳转与传递参数

为了解决上述问题,这里利用【自定义属性】来解决

为解决第一个问题:为a标签加上自定义属性data-categoryName,其余的子节点是没有的。

//一级分类
<a :data-categoryName="c1.categoryName">{{ c1.categoryName }}</a>
//二级分类
<a :data-categoryName="c2.categoryName">{{ c2.categoryName }}</a>
//三级分类
<a :data-categoryName="c3.categoryName">{{ c3.categoryName }}</a>

在前面的章节中,我们可以知道goSearch( )函数中放置的是进行路由跳转的方法

我们点击子节点就可以触发goSearch( )这个回调函数,在函数中通过event.target拿到被点击的节点元素element,节点身上有一个属性dataset属性,可以获取节点的自定义属性与属性值,可以通过解构赋值取出来,如果有categoryname属性,那么被点击的就是a标签了


注意:有些同学有疑惑了,自定义属性为data-categoryName,那么判断条件应该这样写

if(data-categoryName) {......}

然而实际上是这样写的:

if(categoryname) {......}

原因是:需要在定义属性的时候在前面加上data-才能被dataset函数获取,因此data-只是一个前缀,其次浏览器会自动将属性名转化为小写。


为解决第二个问题:分别为1、2、3级的a标签加上自定义属性data-category1Id、data-category2Id、data-category3Id,其余的子节点是没有的。

<a :data-categoryName="c1.categoryName" :data-category1Id="c1.categoryId"
>{{ c1.categoryName }}</a><a :data-categoryName="c2.categoryName" :data-category1Id="c2.categoryId"
>{{ c2.categoryName }}</a><a :data-categoryName="c3.categoryName" :data-category1Id="c3.categoryId"
>{{ c3.categoryName }}</a>

采取和判断a节点一样的方法,判断点击的节点是1级、2级还是3级,这里不再赘述了。

到此,问题就解决了,接下来就要实现在路由跳转中携带参数了,下面直接上代码:

 goSearch(event) {//获取到当前触发这个事件的节点,从中筛选出带有data-categoryname这样的节点//节点有一个属性dataset属性,可以获取节点的自定义属性和属性值let element = event.target;//获取到的变量已经不是驼峰形式了,自动改变的let { categoryname, category1id, category2id, category3id } =element.dataset;if (categoryname) {//整理路由跳转的参数let location = { name: "search" };let query = { categoryName: categoryname };//一级分类、二级分类、三级分类的a标签if (category1id) {query.category1Id = category1id;} else if    (category2id) {query.category2Id = category2id;} else {query.category3Id = category3id;}location.query = query;//路由跳转this.$router.push(location);}},

二十三、Search模块商品分类与过渡动画

从Home主页点击三级分类的内容,就可以跳转到Search模块

Search模块也有三级联动组件,但是它在Search模块中默认情况下是隐藏的,但是在Home模块下默认是显示的。因而这里使用v-show属性对三级联动组件进行修改,

当处于Home模块下,v-show = true;当处于Search模块下,v-show = false;(通过路由信息判断)

//三级联动
<div class="sort" v-show="show">......
</div>
.
.
.
data() {return {//存储用户鼠标移上哪一个一级分类currentIndex: -1,show: true,};
},//组建挂载完毕:可以向服务器发请求
mounted() {//通知vuex发请求,获取数据,存储于仓库中// this.$store.dispatch('categoryList') 考虑到性能将其挪到了【App.vue】//当组件挂载完毕,让show的属性变为false//如果不是Home路由组件,将typeNav进行隐藏if (this.$route.path != "/home") {this.show = false;}
},

但是它总不能一直隐藏吧,当鼠标移入到 “全部商品分类” 那里,就要显示三级联动的内容了,而鼠标移出后,又要隐藏了。

    <div @mouseleave="leaveShow" @mouseenter="enterShow"><h2 class="all">全部商品分类</h2>//三级联动<div class="sort" v-show="show">......</div></div>..   .//当鼠标移入的时候,让商品分类列表进行展示enterShow() {this.show = true;},//当鼠标离开的时候,让商品分类列表进行隐藏leaveShow() {this.currentIndex = -1;//判断不是Home路由组件的时候才会执行if (this.$route.path != "/home") {this.show = false;}},

接下来实现过渡动画

前提:组件或元素务必要有v-if或v-show指令才可以进行过渡动画

   //过渡动画<transition name="sort">//三级联动<div class="sort" v-show="show">...</div></transition>
    //过渡动画的样式//过渡动画开始状态(进入).sort-enter {height: 0px;}//过渡动画结束状态(进入).sort-leave {height: 461px;}//定义动画时间、速率.sort-enter-active {transition: all 0.5s linear;}

二十四、TypeNav商品分类列表的优化

从Home模块跳转到Search模块:首先TypeNav在Home模块中挂载时,会向后台请求数据,当跳转到Search模块时,Home组件销毁,当中的TypeNav也销毁,Search组件挂载,当中的TypeNav也挂载,挂载时又要发一次请求。

综上可知,发了两次请求,性能不够好。在这个应用中,我就只想请求一次,怎么办?


先来分析一下:首先执行入口文件main.js,其中有App路由组件,她是唯一一个根组件,因此不管如何,她都只会挂载一次。那我们把TypeNav中派发action的操作(用于请求数据)放在App.vue中,就能实现仅请求一次的效果了。

如果放在main.js中可行吗?不行,因为main.js不是一个组件,而是一个js文件,派发action时,this为undefined

二十五、合并params和query参数

前面我们已经实现了点击三级联动分类,从Home主页跳转到Search模块,携带了query参数。如果这时我们在输入框输入内容进行搜索时,会发现携带的query参数没有了,只有刚刚请求的params参数了。两者是不能同时存在的,这显然不符合我们应用场景的。

假如:在三级分类中选择“手机”进入到了Search模块,这时我想在此基础上搜“华为”,如果只携带华为这个参数,那返回来的数据可能会包含华为手表、华为汽车等不相关信息。


首先,如果路由跳转的时候,带有params参数,要和query参数一起捎带过去

​goSearch(event) {...//判断:如果路由跳转的时候,带有params参数,携带参数传递过去if (this.$route.params) {location.params = this.$route.params;//整理完参数location.query = query;//路由跳转this.$router.push(location);}}},

然后,在head组件中,点击搜索时进行路由跳转,如果有query参数,要和params一起捎带过去

    goSearch(){...//如果有query也携带过去if(this.$route.query){let location = {name:'search',params:{keyword:this.keyword || undefined}}location.query = this.$route.query;this.$router.push(location)}},

二十六、mock.js模拟数据

服务器返回的数据(接口)只有商品分类菜单分类数据,对于ListContainer组件与Floor组件数据,服务器都没有提供,因此这里使用mock.js去模拟一些数据。

官网对Mock.js的解释:生成随机数据,拦截Ajax请求。


安装mock.js:cnpm install --save mock.js

使用步骤:

1. 在项目中src文件夹中创建mock文件夹

2. 准备预先设置好的JSON数据(mock文件夹中创建相应的JSON文件)

举个例子,下面是有关轮播图的JSON数据

[{"id": "1","imageUrl": "/images/banner1.jpg"},{"id": "2","imageUrl": "/images/banner2.jpg"},{"id": "3","imageUrl": "/images/banner3.jpg"},{"id": "4","imageUrl": "/images/banner4.jpg"}
]

注意:JSON数据需要格式化一下,别留有空格,否则跑不起来

3. 把mock数据需要的图片资源放置到public文件夹中,因为public文件夹在打包的时候,会把相应的资源原封不动地打包到dist文件夹中。

4. 开始mock,通过mockjs模块实现,在mock文件下创建一个名为mockServer.js文件

/*
利用mockjs提供mock接口
*/
import Mock from 'mockjs'
// JSON数据格式根本没有对外暴露,但是可以引入
// webpack默认对外暴露的:图片、JSON数据格式
import floors from './floors.json'
import banners from './banners.json'// 提供广告轮播接口  第一个参数是请求地址,第二个参数是请求数据
Mock.mock('/mock/banners', {code: 200, data: banners})//模拟首页大的轮播图的数据
// 提供floor接口
Mock.mock('/mock/floors', {code: 200, data: floors})
console.log('MockServer')

5. mockServer.js文件在入口文件main.js中引入(至少需要执行一次,才能模拟数据)

二十七、获取Banner轮播图数据

在api文件夹中创建一个名为mockAjax.js的文件,专门用来请求mock数据。

需要注意:baseURL要改为'/mock'

//对axios进行二次封装,
import axios from 'axios'
//引入进度条
import nprogress from 'nprogress'
//在当前模块中引入store
//引入进度条的样式
import "nprogress/nprogress.css"// 利用axios对象得方法create,去创建一个axios实例
// request就是axios,只不过稍微配置一下
const requests = axios.create({//配置对象//基础路径,发送请求的时候,路径当中会出现apibaseURL:'/mock',//代表请求超时的时间5Stimeout:5000
});
//请求拦截器:在发请求之前,请求拦截器可以检测到,可以在请求发出去之前做一些事情
requests.interceptors.request.use((config)=>{//config:配置对象,对象里面有一个属性很重要,header请求头//进度条开始动nprogress.start();return config;
});
//响应拦截器
requests.interceptors.response.use((res)=>{//成功的回调函数:服务器相应数据回来以后,响应拦截器可以检测到,可以做一些事情nprogress.done();return res.data;
},(error)=>{console.log(error)//响应失败的回调函数return Promise.reject(new Error('faile'))
})//对外暴露
export default requests;

在同文件夹下的index.js文件中写【Home首页轮播图接口】,切记url地址中不带mock,因为前面已经配置过了

export const reqGetBannerList = () => mockRequests.get('/banners') //简写形式

mock数据以及接口都准备完毕后,就要发送请求去获取数据啦

当ListContainer组件挂载时(mounted),派发action,通过vue发起ajax请求,将数据存储在仓库中:

mounted() {this.$store.dispatch('getBannerList');
}

之后在store文件夹下的home文件夹下的index.js中,进行vuex的配置

const state = {...//轮播图的数据bannerList:[]
};
const actions = {...//获取首页轮播图的数据async getBannerList({commit}){let result = await reqGetBannerList();if(result.code == 200){commit('GETBANNERLIST',result.data)}}
};
const mutations = {...GETBANNERLIST(state,bannerList){state.bannerList = bannerList;}
};

这个时候还没有结束哦,ListContainer组件还没拿到这个数据呢,因此可以使用mapState

import {mapState} from 'vuex';export default {name:'ListContainer',mounted() {this.$store.dispatch('getBannerList');}computed:{...mapState({bannerList:state => state.home.bannerList})},
}

二十八、swiper基本使用

在swiper官网下载5版本:下载Swiper - Swiper中文网

关于使用过程,官网给的教程非常详细,自己看看实际操作一下,这里就不再赘述了。

教程链接:Swiper使用方法 - Swiper中文网

需要注意:

1. 在new Swiper实例之前,页面中的结构必须有,因为我们要操作DOM

2. 第一个参数可以是字符串(选择器)也可以是真实DOM节点

二十九、Banner实现轮播图(第一种解决方案)

1. 首先安装Swiper插件:选择5版本,6版本会有一些问题:npm install --save swiper@5

2. 引包(相应JS|CSS):

在组件文件中引入:import Swiper from ‘swiper’  --->引入了JS内容

对于样式来说,可以在每个相关组件中引入,但是因为很多地方都用到了轮播图,且样式是一样的,因此可以在入口文件main.js中引入样式,会更加简洁。

即:import "swiper/css/swiper.css"

注意:引入样式的时候,不用import ... from ... ,没有对外进行暴露

3.在模板语法中,我们发现目前只使用一张图片,但是轮播图却是很多张,因此需要使用v-for进行遍历

<div class="swiper-container" id="mySwiper"><div class="swiper-wrapper"><div class="swiper-slide" v-for="(carousel,indx) in bannerList" :key="carousel.id"><img :src="carousel.imgUrl" /></div></div>
</div>

4. 使用Swiper

new Swiper这个过程要放在哪里写呢?放在mounted( )钩子函数中写,因为这个时候页面结构已经实现好了,符合条件。


但是写了之后,发现没有效果!那这又是因为什么呢?因为结构还不完整!

什么!结构怎么还不完整?原因就在于上面那段代码,我们使用v-for去遍历图片,图片的数据是通过axios请求获得的,涉及到了异步,只有请求数据回来了,此时的结构才能是完整的!

因此可以添加一个延迟函数,延迟使用new Swiper,但是这个方法不好用,延迟效果比较鸡肋。比如轮播图中间的小点点得等待一段时间才能够显示出来。

    setTimeout(()=>{var mySwiper = new Swiper(document.querySelector(".swiper-container"),{loop:true,//如果需要分页器pagination:{el:".swiper-pagination",},//如果需要前进后退按钮navigation:{nextEl:'.swiper-button-next',prevEl:'.swiper-button-prev',},});},1000)

当然,我们也可以把new Swiper放在updated( )钩子函数中,但是如果vue组件中有其他数据的话,其他数据发生改变,就要实现这个new Swiper操作,很浪费内存,不推荐使用,但是效果是正常的。


点击轮播图中的小球,不发生图片的转换,这里就要配置一个属性:clickable:true,放在pagination里。

三十、轮播图:watch+nextTick( )(第二种解决方案)

如果大家不知道nextTick( )是什么,可以看一下我之前发的相关文章

链接在这里:VUE中nextTick( )函数思维导图_yuran1的博客-CSDN博客


使用watch监听bannerList的变化,如果有变化,就会触发watch属性中的handle回调函数,我们可以把new Swiper的过程放在这个回调函数中执行。

但是运行的结果还是不行,说明new Swiper前,页面结构还是不完整的,虽然说数据获取成功了,但是不能保证v-for执行完毕。


为了解决这个问题,就要使用nextTick( )函数了

用法【官方解释】:在下次DOM更新循环结束之后,执行延迟回调。在修改数据之后,立即使用这个方法,获取更新后的DOM

三十一、获取floor组件mooc数据

1. 首先编写API接口,获取floor数据

//获取floor数据
export const reqFloorList = () => mockRequests.get('/floors')

2. 写VUEX三连环

import {reqCategoryList, reqGetBannerList,reqFloorList} from '@/api';
//home模块的小仓库
const state = {//state中数据默认初始值别瞎写 【根据接口的返回值去初始化】categoryList:[],//轮播图的数据bannerList:[],//floor组件的数据floorList:[],
};
const mutations = {CATEGORYLIST(state,categoryList){state.categoryList = categoryList},GETBANNERLIST(state,bannerList){state.bannerList = bannerList;},REQFLOORLIST(state,floorList){state.floorList = floorList}
};
const actions = {//通过API里面的接口函数调用,向服务器发送请求,获取服务器的数据async categoryList({commit}){ //对commit进行解构赋值let result = await reqCategoryList();if(result.code === 200){commit("CATEGORYLIST",result.data);}},//获取首页轮播图的数据async getBannerList({commit}){let result = await reqGetBannerList();if(result.code == 200){commit('GETBANNERLIST',result.data)}},//获取floors数组async getFloorList({commit}){let result = await reqFloorList();if(result.code == 200){commit('REQFLOORLIST',result.data)}}
};
const getters = {};
export default {state,mutations,actions,getters
}

3. 在Home组件中触发action,为什么不在Floor组件中去触发。因为Floor组件要进行复用,如果在Floor组件中通过mapState收到了返回的数据,那将无法创建出不同的Floor组件。而Home组件正是使用Floor组件的地方,可以在这里去触发action,从而拿到相应的数据,通过v-for赋给不同的Floor组件不同的数据。

<template><div><!-- 三级联动全局组件,已经注册为全局组件 -->......<Floor v-for="(floor,index) in floorList" :key="floor.id" :list="floor"/>......</div>
</template><script>
import ........export default {name:'HomeIndex',components:{......},mounted() {//派发action,获取floor组件的数据this.$store.dispatch("getFloorList")......},computed:{...mapState({floorList:state => state.home.floorList})}
}
</script>
<style>
</style>

4. 从上面代码中可以看出父组件Home向子组件Floor传递数据 :list="floor"

子组件用props接收数据

export default {name:'FloorMsg',props:['list'],......
}

三十二、动态展示Floor组件

首先通过浏览器的vue网络工具检查组件的各种属性和方法

根据上图这些内容实现【Floor组件的动态展示】

比如:

<h3 class="fl">{{list.name}}</h3>  //标题
<li class="active" v-for="(nav,index) in list.navList" :key="index"><a href="#tab1" data-toggle="tab">{{nav.text}}</a>
</li>

等等,只要需要动态展示的内容都需要进行相应处理,这里不再一一进行解释


在上述处理中,我们还发现需要设置轮播图,章节二十八、二十九已经介绍过swiper具体的适用步骤,这里也不再赘述了。

但需要注意一点:上次书写Swiper的时候,在mounted( )函数中书写是不可以的,但是为什么在这里就可以了!

原因:上次书写轮播图的时候,是在当前组件内部发请求,动态渲染结构【前台至少服务器数据需要回来】,因此这里的写法在当时是不可行的。现在的这种写法为什么可以?因为请求是父组件发的,父组件通过props传递过来的,而且结构都已经都有了的情况下执行mounted( ),此时页面结构已经是完整的了。

三十三、共用组件Carsouel(轮播图)

把首页中的轮播图拆分成一个共用全局组件Carsouel,在components文件夹中新建一个名为Carsouel的文件夹,用来书写轮播图组件

<template><!-- 轮播图的地方 --><div class="swiper-container" ref="cur"><div class="swiper-wrapper"><div class="swiper-slide" v-for="(carousel,index) in list" :key="carousel.id"><img :src="carousel.imageUrl" /></div></div><!-- 如果需要分页器 --><div class="swiper-pagination"></div><!-- 如果需要导航按钮 --><div class="swiper-button-prev"></div><div class="swiper-button-next"></div></div>
</template><script>
//引包
import Swiper from 'swiper';
export default {name:'Carousel',props:['list'],watch:{list:{//立即监听,不管数据有没有变化,我上来就监听一次//为什么watch监听不到list:因为这个数据从来没有发生过变化,父亲给的时候就是一个对象,对象里面该有的数据都是有的immediate:true,handler(){//只能监听到数据已经有了,但是v-for动态渲染结构我们还是没有办法确定,因此还是需要用到nextTickthis.$nextTick(()=>{var mySwiper = new Swiper(this.$refs.cur,{loop:true,//如果需要分页器pagination:{el:".swiper-pagination",clickable:true},//如果需要前进后退按钮navigation:{nextEl:'.swiper-button-next',prevEl:'.swiper-button-prev',},});});}}}};
</script>

需要注意的点:

1. v-for循环(v-for="(carousel,index) in list" )中的list是通过props传递过来的

2. 为什么watch监听不到list?因为这个数据从来没有发生过变化,父亲给的时候就是一个对象,对象里面该有的数据都是有的。因此设置immediate:true,即无论如何都得监测一次

3. 只能监听到数据已经有了,但是v-for动态渲染结构我们还是没有办法确定,因此还是需要用到nextTick(vue异步更新机制)


Carousel是一个全局组件,需要在全局文件main.js中引入和注册

//引入轮播图组件
import Carousel from "@/components/Carousel"
//注册轮播图组件
Vue.component(Carousel.name, Carousel)

然后回到Floor组件中,在轮播图的地方使用Carousel组件

<div class="floorBanner"><!-- 轮播图的地方 --><Carousel :list="list.carouselList"/>
</div>

注意,要传递数据:list.carouselList

切记:以后在开发项目的时候,如果看到某一个组件在很多地方都使用,你把它变为全局组件,注册一次,可以在任意地方使用,公用的组件|非路由组件放在components文件夹中

三十四、Search模块的静态组件

先理清一下Search模块开发步骤

1. 先静态页面 + 静态组件拆分出来

2. 发请求(API)

3. VUEX(三连环)

4. 组件获取仓库数据,动态展示数据


静态组件的拆分很简单,就是把相应的html代码和css代码拆分出来,放在一个组件里。这里就不再赘述了

三十五、Search模块的VUEX操作

首先查阅api前台接口文档,确定请求方式、请求URL以及请求参数等

//当前这个函数需要接受外部传递参数
//当前这个接口,给服务器传递参数params,至少得是一个空对象
//如果连空对象都没有,那么请求会失败的
export const reqGetSearchInfo = (params) => requests({url:"/list",method:'post',data:params}
) 

注意:

1. 当前这个函数需要接受外部传递参数
2. 当前这个接口,给服务器传递参数params,至少得是一个空对象。如果连空对象都没有,那么请求会失败的


在store文件夹中的search.js文件中进行【vuex三连环】

import { reqGetSearchInfo } from "@/api";
//search模块的小仓库
const state = {//仓库初始状态searchList:{},
};
const mutations = {GETSEARCHLIST(state,searchList){state.searchList = searchList}
};
const actions = {//获取search模块的数据async getSearchList({commit},params={}){//当前这个reqGetSearchInfo这个函数在调用获取服务器数据的时候,至少传递一个参数(空对象)//params形参,是当用户派发action的时候,第二个参数传递过来的,至少是一个空对象let result = await reqGetSearchInfo(params)if(result.code == 200){commit('GETSEARCHLIST',result.data)console.log(result.data)}}
};
export default {state,mutations,actions,getters
}

注意:仓库初始状态 searchList:{ },为什么是一个对象而不是一个数组呢?

这当然不是让我们进行凭空猜测啦,需要进行验证:在Search组件中mounted( )中去派发相应的action(getSearchList),this.$store.dispatch('getSearchList', { })

然后通过浏览器的network工具就可以查看请求回来的数据了,从而可以判断数据是什么格式

三十六、Search模块动态展示产品列表

import {mapState} from 'vuex'
computed:{...mapState({goodsList:state => state.search.searchList.goodsList})
}

上述这段代码虽然可以获取到数据,但是太麻烦,写了一连串的内容,不仅容易出错还不美观


接下来使用getters进行优化,

在项目中,VUEX中的getters是为了简化仓库中的数据而生,想让其他组件捞数据的时候更简单一些,可以把我们将来在组件当中需要用的数据简化一下【将来组件在获取数据的时候就方便了】

const getters = {//当前形参state是当前仓库中的state,并非大仓库中的stategoodsList(state){//如果网络不给力,返回的是undefined,这样不能遍历//计算新的属性的属性值至少是一个数组return state.searchList.goodsList || [];},trademarkList(state){return state.searchList.trademarkList || [];},attrsList(state){return state.searchList.attrsList || [];}
};
import {mapGetters} from 'vuex'computed: {//mapGetters里面的写法:传递的数据,因为getter计算是没有划分模块【home、search】//补充:state是划分模块了state.home / state.search...mapGetters(["goodsList", "trademarkList", "attrsList"]),
}

分析页面的结构,对于【销售产品列表】,结构都是一样的,可以使用【v-for】进行遍历

<li class="yui3-u-1-5" v-for="(good, index) in goodsList":key="good.id">

<li></li>标签内部的动态数据也需要更改,比如图片、价格等,比较简单,不再详细叙述了

三十七、Search模块根据不同的参数进行数据展示

在前面内容中,在Search模块中,我们是在mounted( )钩子函数中去dispatch  action 从而获取到相应的数据,但是这里存在一个问题,由于mounted( )钩子函数只能挂载一次,这导致只能请求一次数据,这并不符合应用的实际需求。

解决方法:在methods中创建一个函数getData( ),只要想请求数据就调用该函数,根据不同的参数返回不同的数据进行展示。

  methods: {//向服务器发送请求获取search模块数据(根据参数不同返回不同的数据进行展示)//把这次请求封装为一个函数,当你需要在调用的时候调用即可getData() {//先测试接口返回的数据模式this.$store.dispatch("getSearchList", this.searchParams); //dispatch是异步操作},}

由于组件挂载的时候,要获取相应的数据,因此在mounted( )去调用getData( )

(至于什么情况下再调用getData去获取数据,这里先不说,请看之后的章节)


对于请求参数而言,从项目开发文档中能发现【携带的参数】至少是10个,参数必须是可以变动的(ps:需要根据不同的参数请求不同的数据),因此把这些参数放入到data中。

下面对各个参数进行解释:

  data() {return {//带给服务器的参数searchParams: {//一级分类的idcategory1Id: "",//二级分类的idcategory2Id: "",//三级分类的idcategory3Id: "",//分类名字categoryName: "",//关键字keyword: "",//排序:初始状态应该是综合|降序order: "1:desc",//分页器用的:代表的是当前是第几页pageNo: 1,//代表的是每一页展示数据的个数pageSize: 10,//平台售卖属性操作带的参数props: [],//品牌trademark: "",},};},

在data中,参数是初始化的,还没有对参数进行赋值。因此需要在正式请求之前,对参数进行更新。更新这一过程需要在mounted( )之前进行,因此将放在beforeMount( )钩子函数中。

  //当组件挂载完毕之前执行一次【先与mounted之前】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(this.searchParams, this.$route.params, this.$route.query);},

三十七、Search模块中子组件动态开发

在Search模块中有一个子组件SearchSelector,在这个子组件中通过【mapGetters】获取vuex中的数据,然后对template中的数据进行更改

三十八、监听路由的变化再次发请求获取数据

为了可以【再次】发请求获取不同的数据,这里首先要确定【再次发请求】的时机:也就是说当路由发生变化的时候,说明需要再次发请求了。因此需要对路由的变化进行监测,即使用【watch】

  //数据监听:监听组件实例身上的属性的属性值变化watch: {//监听路由的信息是否发生变化,如果发生变化,则再次发送请求$route(newValue, oldValue) {//再次发送请求之前整理带给服务器的参数Object.assign(this.searchParams, this.$route.params, this.$route.query);//再次发起ajax请求this.getData();//每一次请求完毕,应该把相应的1、2、3级分类的id置空,让他接受下一次的相应1、2、3id//分类名字与关键字不用清理:因为每一次路由发生变化的时候,都会给他赋予新的数据this.searchParams.category1Id = "";this.searchParams.category2Id = "";this.searchParams.category3Id = "";},},
};

PS:这里老师说关键字keyword不需要置空,但是从真正使用上来说,应该要置空的,否则会影响用户的体验。(京东就对此进行了清空)

三十九、面包屑处理分类的操作

面包屑总共有四类:【分类的面包屑】、【关键字的面包屑】、【品牌的面包屑】、【平台的售卖的属性值展示】

此外,面包屑这部分不应该是死的,应该是动态的。


在Search模块中通过searchParams可以拿到【商品分类】的数据,可作为分类面包屑

在这里通过v-if进行显示判断

<!-- 分类的面包屑 -->
<li class="with-x" v-if="searchParams.categoryName">{{searchParams.categoryName}}<i @click="removecategoryName">×</i>
</li>

上面代码中给 i标签添加了一个点击事件,即删除该面包屑,那么就要重新去请求数据了

    //删除分类的名字removecategoryName() {//把带给服务器的参数置空了,还需要向服务器发请求//带给服务器参数的说明是可有可无的,属性值为空的字符串还是会把相应的字段带给服务器//但是你把相应的字段变为undefined。当前这个字段不会带给服务器,减少带宽消耗this.searchParams.categoryName = undefined;this.searchParams.category1Id = undefined;this.searchParams.category2Id = undefined;this.searchParams.category3Id = undefined;this.getData();//地址栏也需要修改,进行路由的跳转(现在的路由跳转只是跳转到自己这里)//严谨:本意是删除query,如果路径当中出现params不应该删除,路由跳转的时候应该带着params参数if (this.$route.params) {this.$router.push({ name: "search", params: this.$route.params });}},

四十、面包屑处理关键字

【关键字面包屑】和【分类面包屑】的实现原理是一样的

首先通过v-if进行显示判断

<!-- 关键字的面包屑 -->
<li class="with-x" v-if="searchParams.keyword">{{ searchParams.keyword }}<i @click="removeKeyword">×</i>
</li>

再给 i标签 绑定一个监听事件,即去除这个面包屑时,需要重新请求数据

    //删除关键字removeKeyword() {//给服务器带的参数searchParams的keyword置空this.searchParams.keyword = undefined;this.getData();if (this.$route.query) {this.$router.push({ name: "search", query: this.$route.query });}//将搜索框中的内容置空,同级组件之间进行通信//通知兄弟组件Header删除关键字this.$bus.$emit("clear");},

从上面代码中可以看出:为了将搜索框中的内容清空,需要search组件和home组件进行通信,

这两个组件属于兄弟组件,可以使用【全局事件总线】进行通信

  //Home组件mounted() {//通过全局事件总线清楚关键字this.$bus.$on('clear',() => {this.keyword = " ";})},

四十一、面包屑处理品牌信息

这部分和前两部分有一些区别,

首先需要注意,品牌这部分内容不在Search组件中,而是在Search组件的子组件SearchSelector中。先给各个品牌绑定一个点击事件tradeMarkHandler,并传入参数trademark

<ul class="logo-list"><li v-for="(trademark,index) in trademarkList" :key="trademark.tmId" @click="tradeMarkHandler(trademark)">{{trademark.tmName}}</li>
</ul>
    methods: {//品牌的事件处理函数tradeMarkHandler(trademark){//点击了品牌,还是需要整理参数,向服务器发送请求获取相应的数据,并进行展示//为什么是Search发请求,为什么呢?因为父组件中searchParams参数是带给服务器的,子组件把你 //点击的品牌的信息给父组件传递过去this.$emit('trademarkInfo', trademark);},}

从上面的代码中可以看出子向父通信使用自定义事件,子组件通过$emit触发自定义事件trademarkInfo,并传递相应的参数


而在父组件Search中绑定自定义事件,并设置自定义事件的回调函数,并接收传递过来的参数

<SearchSelector @trademarkInfo="trademarkInfo" />
    //自定义事件的回调trademarkInfo(trademark) {//整理品牌字段的参数(按照固定的格式)this.searchParams.trademark = `${trademark.tmId}:${trademark.tmName}`;//需要再次发送请求,获取this.getData();},

除此之外,还要将【品牌面包屑】进行展示,首先通过v-if进行显示判断

<!-- 品牌的面包屑 -->
<li class="with-x" v-if="searchParams.trademark">{{ searchParams.trademark.split(":")[1]}}<i @click="removetrademark">×</i>
</li>

再给 i标签 绑定一个监听事件,即删除这个品牌面包屑后,需要重新发请求去获取数据

    //删除品牌removetrademark() {this.searchParams.trademark = undefined;this.getData();},

四十二、平台售卖属性的操作

【平台售卖属性】这部分的内容不在Search组件中,而是在Search的子组件SearchSelector中,

先给平台售卖属性绑定一个点击事件,并传入两个相应的参数(attr, attrvalue)

<li v-for="(attrvalue,index) in attr.attrValueList" :key="index" @click="attrInfo(attr,attrvalue)" ><a>{{attrvalue}}</a></li>
methods: {....//平台售卖属性值的点击事件attrInfo(attr,attrvalue){//["属性ID:属性值:属性名"]this.$emit("attrInfo",attr,attrvalue)}
},

从上面的代码中可以看出子向父通信使用自定义事件,子组件通过$emit触发自定义事件attrInfo,并传递相应的参数。

而在父组件Search中绑定自定义事件,并设置自定义事件的回调函数,并接收传递过来的参数

<SearchSelector @trademarkInfo="trademarkInfo" @attrInfo="attrInfo" />
    //收集平台属性的回调函数(自定义事件)attrInfo(attr, attrvalue) {//参数的格式先整理好let props = `${attr.attrId}:${attrvalue}:${attr.attrName}`;//数组去重----常见面试题if (this.searchParams.props.indexOf(props) == -1) {this.searchParams.props.push(props);}//再次发送请求this.getData();},

从上面代码中可以看到,进行了【数组去重】操作,为什么这么做呢?

因为如果不进入数组去重的话,多次点击同一个平台售卖属性,会出现多个重复的面包屑。


除此之外,还要将【平台售卖属性】面包屑进行展示,注意这里不再使用v-if,而是使用v-for,因为props是一个数组

<!-- 平台的售卖的属性值展示 -->
<li class="with-x" v-for="(attrvalue, index) in searchParams.props" :key="index">{{ attrvalue.split(":")[1] }}<i @click="removeAttr(index)">×</i>
</li>

再给 i标签 绑定一个监听事件,即删除这个平台售卖属性后,需要重新发请求去获取数据

    //removeAttr删除售卖的属性removeAttr(index) {//再次整理参数this.searchParams.props.splice(index, 1);//再次发送请求this.getData();},

四十三、排序操作

分析api接口文档,发现searchParams中【order参数】就是用来指定排序方式的,下面讲讲它的具体含义

1表示综合;2表示价格;asc表示升序;desc表示降序,因此有如下四种组合:

1:asc        2:desc        1:desc        2:asc        (注意:初始状态为1:desc)


上图是关于排序的两个位置(综合/价格),点击哪个位置,哪个位置就有对应的样式。由此我们知道,这个类是动态添加的。那怎么实现呢?这就需要用到【v-bind指令】喽~当isOne或isTwo为true时,li元素才拥有active这个类。


上图中出现了箭头,先不考虑箭头的指向,什么时候箭头才出现呢?谁有类名active,谁就有箭头呗。根据这种关系,可以考虑使用【v-if】或者【v-show】

箭头可以使用【阿里巴巴矢量图标库】中的素材,具体的使用方法可以进行百度


但是上述过程只能通过手动修改order参数,才能控制显示效果,这肯定不能满足实际需求

因此还是要为【综合】和【价格】两个a标签绑定点击事件,


下面是具体实现的代码,需要注意的事项在注释中写出,一定要好好理解,

<li :class="{ active: isOne }" @click="changOrder('1')"><a>综合<spanv-show="isOne"class="iconfont":class="{'icon-xiangshang': isAsc,'icon-paixu': isDesc,}"></span></a>
</li>
<li :class="{ active: isTwo }" @click="changOrder('2')"><a>综合<spanv-show="isTwo"class="iconfont":class="{'icon-xiangshang': isAsc,'icon-paixu': isDesc,}"></span></a>
</li>
computed: {......isOne() {return this.searchParams.order.indexOf("1") != -1; //返回值为布尔值},isTwo() {return this.searchParams.order.indexOf("2") != -1; //返回值为布尔值},isAsc() {return this.searchParams.order.indexOf("asc") != -1; //返回值为布尔值},isDesc() {return this.searchParams.order.indexOf("desc") != -1; //返回值为布尔值},......},
methods:{//排序的操作changOrder(flag) {//flag形参:它是一个标记,代表用户点击的是综合还是价格 (用户点击的时候传递过来的)//这里获取的是最开始的状态【需要根据初始状态去判断接下来做什么】let originFlag = this.searchParams.order.split(":")[0];let originSort = this.searchParams.order.split(":")[1];//准备一个新的order属性值let newOrder = "";//这个语句能够确定这次点击和上次点击的地方是【同一个】,将排序颠倒过来if (flag == originFlag) {newOrder = `${originFlag}:${originSort == "desc" ? "asc" : "desc"}`;} else {//这次点击和上次点击的地方【不是同一个】,默认排序向下newOrder = `${flag}:${"desc"}`;}//将新的order赋予searchParams【重新赋值】this.searchParams.order = newOrder;//再次发送请求this.getData();},
}

四十四、分页器静态组件

因为分页器不止在一个地方使用,所以需要将分页器的内容作为全局组件来使用

因此在components文件夹下新建一个文件夹【Pagination】,用来存放分页器组件

并在main.js文件中引入该组件并注册,且在Search组件中使用该组件

分页器静态组件的内容在api开发接口文档中已经给出,直接使用就可以了,有些小地方需要进行修改,比较简单,这里就不再进行赘述了

四十五、分页功能分析

为什么很多项目采用分页功能?因为电商平台同时展示的数据有很多(上万条)

我们知道ElementUI实现了分页器,使用起来非常简单,但是在这个项目中不使用它,因为想锻炼一下自身是否掌握了【自定义分页器】的功能


实现分页器之前,先思考分页器都需要哪些数据(条件)呢?

1. 需要知道当前是第几页:pageNo字段代表当前页数

2. 需要知道每页需要展示多少条数据:pageSize字段

3. 需要知道分页器一共有多少条数据:total字段--【获取另外一条信息:一共多少页】

4. 需要知道分页器连续的页码个数:continues字段,一般是5或者7,为什么是奇数呢?因为对称,比较好看

举个栗子

尚品汇项目笔记(持续更新中)相关推荐

  1. 尚硅谷--尚品汇项目笔记

    文章目录 项目核心 一.项目准备 二.脚手架目录作用 三.项目的其他配置 四.路由的分析 1.路由组件的搭建 2.配置路由 3.路由组件和非路由组件区别: 4.路由的跳转 5.路由传参 6.路由传递参 ...

  2. 电商项目尚品汇学习笔记

    本文参考其他文章自己整理补充的,要阅读原文请查看:尚品汇项目笔记_爱哭的毛毛虫的博客-CSDN博客_尚品汇项目 1.vue文件目录 public文件夹:静态资源,webpack进行打包的时候会原封不动 ...

  3. 沉睡者 - 网赚创业VIP项目课程-持续更新中...

    沉睡者 - 网赚创业VIP项目课程-持续更新中... 不管在线下还是线上,都尽量只做有积累可持续的项目,否则就容易进入到一个找项目做项目死项目之后,不断重复找项目,不断重复做苦力没有发展的死循环. 在 ...

  4. 尚硅谷 VUE 尚品汇项目实战问题解决方式整理

    最近在自学大型vue项目,选中了尚硅谷的尚品汇,之前也学习过黑马程序员的一个电商后台管理项目,因为项目打包优化时候出了Bug,就闲置了,但是代码是一行一行敲完啦,也放到了码云上!学完这个尚品汇准备下一 ...

  5. 尚硅谷 VUE 尚品汇项目实战问题解决方式整理(Vue3 版)

    教学视频:https://www.bilibili.com/video/BV1Vf4y1T7bw 不回要资料的评论,资料在视频底下评论有些仓库里面有. 试图自行解决问题也是程序员必备技能之一,多做尝试 ...

  6. 尚硅谷-尚品汇项目开发总结(第十五天)

    27:图片懒加载:vue-lazyload插件实现 27.1 为什么需要图片懒加载? 在图片很多的网站中,如果所有图片全部一次性加载完毕,可能会增加服务器压力.导致页面卡顿.影响用户体验. 为了解决这 ...

  7. Docker快速入门学习笔记-持续更新中

    Docker安装 #1.卸载旧的版本 yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker ...

  8. typescript-----javascript的超集,typescript学习笔记持续更新中......

    Typescript,冲! Typescript 不是一门全新的语言,Typescript是 JavaScript 的超集,它对 JavaScript进行了一些规范和补充.使代码更加严谨. 一个特别好 ...

  9. SpringCloud学习笔记,课程源自黑马程序员,笔记持续更新中...

    @SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式: 学习内容 1.服务拆分-服务远程调用: 2.搭建eureka服务: 2.1.eureka服务注册-client 2 ...

最新文章

  1. 计算器是如何计算sin、cos等科学函数的值呢?
  2. list循环赋值_Python之 for循环
  3. 使用代码删除IBASE object component
  4. 1052. 卖个萌 (20)
  5. endnote怎么和word关联_endnote x9怎么和word关联?Word中用EndNote X9教程
  6. jquery截取字符串中的数字
  7. (49)FPGA线性单驱动(wire型)
  8. cocos2d-x之读取xml文件
  9. 排序 —— 希尔排序(Shell sort)
  10. 太戈编程DEVC++教师答案库
  11. 2016 最好的Bootstrap 管理模板
  12. 软件测试周刊(第10期): 大质量
  13. 粒子滤波与重要性采样
  14. win7与internet时间同步出错_win7系统时间不同步怎么办|win7系统时间同步出错的解决方法...
  15. 新技能get!用 Python 高效背单词!
  16. 会计记账公式、六要素、记账流程
  17. 阿里云服务器是如何实现每台服务器都是公网IP的呢?
  18. Lazy与Suspense
  19. 初中使用计算机教学反思,初中信息技术教学反思与体会
  20. 上海高考惊现0分作文 只因描写同性恋题材

热门文章

  1. 起卦帮同学看工作,应了。
  2. 【重榜】易语言锁机病毒模块分享!!!!
  3. Taro组件库的奇妙bug之旅
  4. pycharm技巧-win10给pycharm设置全局字符串搜索快捷键
  5. Streamlit实战Twitter微博情感分类【Flair】
  6. java 枚举值赋值_Java枚举的几种操作方法
  7. 计算机毕业设计-运动会报名管理系统
  8. 计算机管理的逻辑流程图,逻辑流程图.PPT
  9. 倍福触摸屏维修C7037-1037-0010按键操作面板
  10. [orin] nvidia orin 上安装 pytorch 和 torchvision 实操