尚硅谷Vue电商项目笔记3-Search搜索自定义事件和全局事件总线以及翻页
Search组件的展示
静态组件
直接使用资料提供的即可
获取动态数据并且展示
- 编写接口请求函数,在api下的index.js进行编写
- 编写vuex,在sorte下的search.js模块中进行编写,并且使用
getters
来简化state中数据的获取- 要注意:获取到的searchInfo中的数据很多,里面有用来进行条件搜索的参数,但是为了动态数据的展示,使用getters简化了列表的数据获取
- 在Search组件中dispath发送请求获取数据
- 使用
mapGetters
将vuex中的getters
数据放在组件上方便使用 - 直接使用
v-for
遍历即可
请求接口函数:
// 获取Search页的动态数据,在调用该函数的时候,需要一个参数,该参数是post请求的请求体数据
export const reqSearchInfo =(searchParams)=>{
return request({
url:'/list',
method:"post",
data:searchParams // 用于搜索的参数,是在发送请求获取数据的时候传递的,在发送请求的时候传递空对象也能得到数据,即默认数据,用于初始化展示
})
}
// 测试,如果没有传递空对象法获取数据
// reqSearchInfo({})无
vuex的编写:
// store的search模块
import { reqSearchInfo } from "@/api"
const state = {
// 返回的data是一个对象,所以指定对象
searchInfo:{}
}
const mutations = {
REVEIVE_SEARCHINFO(state,searchInfo){
state.searchInfo = searchInfo
}
}
const actions = {
async getSearchInfo({commit},searchParams={}){
// searchParams是用户在发送请求的时候传递的,如果没有传递该参数就指定一个空对象
const result = await reqSearchInfo(searchParams)
if(result.code === 200){
commit('REVEIVE_SEARCHINFO',result.data)
}
}
}
// 因为数据内部的结构比较复杂,使用起来不方便,所以通过计算属性来获取
const getters = {
// 这里是在search模块,使用getters直接使用state下的searchInfo来获取
attrsList(state){
// 如果数据拿不到,获取到的是unfeind,undefiend循环会报错,所以拿不到使用[]赋值
return state.searchInfo.attrsList || []
},
goodsList(state){
return state.searchInfo.goodsList || []
},
trademarkList(state){
return state.searchInfo.trademarkList || []
}
}
export default {
state,mutations,actions,getters
}
在Search组件中派发请求获取数据:
// 挂载完毕就发送请求获取默认的数据
mounted(){
this.getSearchInfo()
},
methods:{
// 因为搜索要经常发送请求,所以封装成一个方法
getSearchInfo(){
// dispath方法只能接收两个参数,如果需要传递多个参数,就需要够成一个对象去传递
// 要注意这里获取了很多数据,只是把需要动态展示的先通过getters获取出来了,这样方便使用
this.$store.dispatch('getSearchInfo',{})
}
},
// 因为getter是不分家的,所以不用向直接使用state的内容,一样指定模块的名称,使用goodsList动态展示商品列表即可
computed:{
...mapGetters(['goodsList'])
}
SearchSelector中数据的动态展示
- SearchSelector是Search的子组件
- 当Search组件挂载发送请求获取到的数据,存储到vuex中
- vuex中就已经有了数据,现在需要在SearchSelector使用,可以直接使用
getters
中的数据 - 使用
v-for
遍历即可
import { mapGetters } from 'vuex'
export default {
name: 'SearchSelector',
computed:{
...mapGetters(['attrsList','trademarkList'])
}
}
基本搜索
发送初始化参数获取数据
- 在data当中需要自定义一个对象,表示搜索条件组成的对象,被称为初始化搜索参数
- 一部分是空字符串,作为搜索条件,需要我们在发送请求之前传递
- 另一部分是给定的默认参数,比如排序方式和展示数量等
- 我们在发送请求的时候要传递定义的搜索参数对象,不能向之前那样传递一个空对象(只是为了页面展示,现在是根据条件搜索)
- 修改初始化搜索参数要在发送请求之前,而请求时在
mouted
发送的,所以在beforeMoute
中进行初始化参数的修改- 初始化参数就根据我们之前通过点击三级导航跳转到Search传递的query参数
- 和Header中的搜索按钮跳转到Search传递的params参数
import { mapGetters } from 'vuex'
import SearchSelector from './SearchSelector/SearchSelector'
export default {
name: 'Search',
components: {SearchSelector},
data(){
return {
// 初始化搜索条件,进行搜索的条件都在该对象内部进行初始化操作,刚开始都是空内容
searchParams:{
category1Id: "",
category2Id: "",
category3Id: "",
categoryName: "",
keyword: "",
props: [],
trademark: "",
// 默认的搜索条件,order为排序规则,pageNo表示当前是第几页,pagSize表示每页显示多少条数据,通过这三个参数告诉服务器我们需要的数据排序方式和数量
order: "1:desc",
pageNo: 1,
pageSize: 10,
}
}
},
// 因为数据是在mouted中发送的,所以要在motued之前将搜索数据填充到初始化搜索条件中
beforeMount(){
// 结构出params参数和query参数,如果没有该数据则结构出来的是undefined,undefiend不会发送给服务器
const {category1Id,category2Id,category3Id,categoryName} = this.$route.query
const {keyword} = this.$route.params
const searchParams = {...this.searchParams,category1Id,category2Id,category3Id,keyword,categoryName}
// 覆盖掉原有的初始化参数
this.searchParams = searchParams
},
mounted(){
this.getSearchInfo()
},
methods:{
getSearchInfo(){
// 最开始为了展示数据,所以传递空对象,现在传递的是我们的搜索条件集合的对象,刚开始对象的参数内容都是空字符串和空对象的结果一样,需要动态的根据不同的搜索形式改变参数对象的内容而进行搜索
this.$store.dispatch('getSearchInfo',this.searchParams)
}
},
computed:{
...mapGetters(['goodsList'])
}
}
改变初始化参数重新搜索
之前将发送请求写在mouted
中,但是该生命周期函数只会执行一次,所以无法重复的去发送请求
- 封装设置初始化搜索条件的方法
- 监视
$route
因为点击搜索按钮或者点击三级导航进行搜索会导致地址栏的改变,地址栏发生改变则$route
路由对象改变,所以监视路由对象- 监视路由对象,路由对象改变,然后重新整理参数
- 继续发送请求
import { mapGetters } from 'vuex'
import SearchSelector from './SearchSelector/SearchSelector'
export default {
name: 'Search',
components: {SearchSelector},
data(){
return {
// 初始化搜索条件,进行搜索的条件都在该对象内部进行初始化操作,刚开始都是空内容
searchParams:{
category1Id: "",
category2Id: "",
category3Id: "",
categoryName: "",
keyword: "",
props: [],
trademark: "",
order: "1:desc",
pageNo: 1,
pageSize: 10,
}
}
},
// 整理参数
beforeMount(){
this.setSearchParams()
},
// 挂载发送请求
mounted(){
this.getSearchInfo()
},
// mouted只会执行一次,只会发送一次请求,所以使用监视属性发送请求
watch:{
// 再次点击三级导航或者搜索按钮,会引起路径的变化,路径的变化就是route对象发生了变化,所以监视$route
$route:{
handler(){
// 重新整理参数,重新发送请求
this.setSearchParams()
this.getSearchInfo()
}
}
},
methods:{
// 发送请求
getSearchInfo(){
this.$store.dispatch('getSearchInfo',this.searchParams)
},
// 整理参数
setSearchParams(){
const {category1Id,category2Id,category3Id,categoryName} = this.$route.query
const {keyword} = this.$route.params
const searchParams = {...this.searchParams,category1Id,category2Id,category3Id,keyword,categoryName}
this.searchParams = searchParams
}
},
computed:{
...mapGetters(['goodsList'])
}
}
面包屑搜索
展示面包屑和删除面包屑搜索以及使用事件全局总线删除关键字并且改变请求路径的变化
面包屑的展示和删除:
- 清除categoryName之后,需要重新发送请求
- 清除keyword之后,也需要重新发送请求,这个时候发送请求就不需要通过监视
$route
的改变实现,因为没有改变,我们需要自己调用发送请求的方法 - 手动清除categoryName和keyword之后,那么初始化的搜索条件对象就变成默认的会展示默认的数据
时间全局总线的使用:
- 绑定事件全局总线在main.js入口文件中
- 在发送数据的地方使用
this.$bus.$emit('函数名')
指定函数 - 在接收数据的地方的
mounted
中使用this.$bus.$on('函数名',回调函数)
来触发指定的回调函数(回调函数定义在methods中)
删除categoryName和key从新发送请求,但是请求路径却没有发生改变的解决:
- 使用
push
重新发送请求,引起路径的变化,引起监视属性工作,然后通过监视内部重新收集数据,重新发送请求
面包屑的展示并且绑定对应的事件:
<li v-if="searchParams.categoryName" class="with-x">{{searchParams.categoryName}}<i @click="removeCategoryName">x</i></li>
<li v-if="searchParams.keyword" class="with-x">{{searchParams.keyword}}<i @click="removeKeyword">x</i></li>
逻辑部分完整代码:
import { mapGetters } from 'vuex'
import SearchSelector from './SearchSelector/SearchSelector'
export default {
name: 'Search',
components: {SearchSelector},
data(){
return {
// 初始化搜索条件,进行搜索的条件都在该对象内部进行初始化操作,刚开始都是空内容
searchParams:{
category1Id: "",
category2Id: "",
category3Id: "",
categoryName: "",
keyword: "",
props: [],
trademark: "",
order: "1:desc",
pageNo: 1,
pageSize: 10,
}
}
},
beforeMount(){
this.setSearchParams()
},
mounted(){
this.getSearchInfo()
},
// mouted只会执行一次,只会发送一次请求,所以使用监视属性发送请求
watch:{
// 再次点击三级导航或者搜索按钮,会引起路径的变化,路径的变化就是route对象发生了变化,所以监视$route
$route:{
handler(){
this.setSearchParams()
this.getSearchInfo()
}
}
},
methods:{
getSearchInfo(){
this.$store.dispatch('getSearchInfo',this.searchParams)
},
// 整理参数
setSearchParams(){
const {category1Id,category2Id,category3Id,categoryName} = this.$route.query
const {keyword} = this.$route.params
const searchParams = {...this.searchParams,category1Id,category2Id,category3Id,keyword,categoryName}
this.searchParams = searchParams
},
// 删除三级导航带来的面包屑catetoryName
removeCategoryName(){
// 如果不清除id则搜索还会带有原来的id,索性将所有的id都清除掉,这里使用undefiend是因为空字符串会发送给服务器,而undefiend不会
this.searchParams.category1Id = undefined
this.searchParams.category2Id = undefined
this.searchParams.category3Id = undefined
this.searchParams.categoryName = undefined
// 因为这里不需要从路由对象中获取参数,所以不需要整理参数,没有通过监视路由对象的变化发送请求直接发送请求即可
// 解决路径问题,不这样发送请求
// this.getSearchInfo()
// 在这之前已经将categoryName相关的数据清空了,然后发送请求的时候只携带params参数,也就是keyword,那么引起路径变化,引起监视属性变化,重新发送请求,之前收集参数的时候,只能收集到keyword
this.$router.push({name:'search',params:this.$route.params})
},
// 移除面包屑的关键字
removeKeyword(){
this.searchParams.keyword = undefined
// 通知header组件清空keyword
this.$bus.$emit('clearKeyword')
// this.getSearchInfo()
this.$router.push({name:'search',query:this.$route.query})
}
},
computed:{
...mapGetters(['goodsList'])
}
}
绑定事件全局总线,在main.js中进行绑定:
beforeMount(){
Vue.prototype.$bus = this // 安装全局事件总线,在任意组件内部都可以通过this.$bus访问到vm实例(这里vm实例作为总线)
},
Header组件中绑定事件总线函数:
methods: {
toSearch() {
const location = {name:'search',params:{keyword:this.keyword || undefined}}
if(this.$route.query){
location.query = this.$route.query
}
this.$router.push(location)
},
// 事件总线的回调函数,清空data中的双向绑定数据keyword
clearKeyWord(){
this.keyword = ''
}
},
mounted(){
this.$bus.$on('clearKeyword',this.clearKeyWord)
}
点击SearchSelector中的品牌搜索按
品牌信息在子组件SearchSelector中,当用于点击品牌,把品牌的信息传递给父组件,在父组件当中修改searchParasm和发送请求
子组件向父组件传递数据:使用自定义事件
- 在子组件中使用
this.$emit()
触发绑定在使用组件的时候绑定的自定义事件 - 在父组件的methods中定义事件触发的回调
- 同样需要面包屑展示搜索的条件和移除该面包屑需要重新发送请求
- 发送的trademark是一个字符串,包含id和name这个请求格式是接口规定的,所以展示的时候要拆分数组
- 其实展示和删除品牌重新发送请求和移除关键字以及categoryName的逻辑是一样的,只不过关键字和categoryName作为query参数和params参数会引起路径的变化所以要通过
push
借助监听重新发送请求 - 而在这里追击筛选条件,只需要改变其中的某个条件直接发送请求即可
// 1. SearchSelect组件中绑定事件,事件传递trademark
<li @click="searchForTrademark(trademark)" v-for="trademark in trademarkList" :key="trademark.tmId">{{trademark.tmName}}</li>
// 2. SearchSelect中事件的回调中触发自定义事件
methods:{
// 用户点击该事件,将trademark传递给父组件,让父组件去发送请求
searchForTrademark(trademark){
this.$emit('searchForTrademark',trademark)
}
}
// 3. Serach组件中使用子组件的时候绑定事件
<SearchSelector @searchForTrademark="searchForTrademark" />
// 4. 在methods中定义的自定义事件回调函数
searchForTrademark(trademark){
this.searchParams.trademark = `${trademark.tmId}:${trademark.tmName}`
this.getSearchInfo()
},
// 5. 展示品牌的面包屑
<li v-if="searchParams.trademark" class="with-x">{{searchParams.trademark.split(':')[1]}}<i @click="removeTrademark">x</i></li>
// 6. 面包屑移除的事件回调
removeTrademark(){
this.searchParams.trademark = undefined
this.getSearchInfo()
}
点击SearchSelector中的参数按照参数搜索
原理跟点击品牌进行搜索是一样的,都需要使用自定义事件,需要注意的是:
- 通过属性进行搜索需要的数据是一个数组形式的
- 在进行面包屑展示的时候需要使用循环
- 并且在多次添加相同的属性会有重复添加的情况,所以要进行判断
- 数组的
some()
方法和every()
方法some()
数组的每一个元素跟指定的条件比较,只要有一个满足,true,剩余的不进行比较every()
数组的每一个元素跟指定的条件比较,只要有一个不满足,就返回true,剩余的不在进行比较
// 1. 在SearchSelect组件中绑定的事件attr中id和attr中的name都以及attrValue都是服务器需要的数据
<a href="javascript:;" @click="searchForProp(attr,attrValue)">{{attrValue}}</a>
// 2. 事件的回调
// 用户点击该事件将attr传递给父组件,让父组件去发送请求
searchForProp(attr,attrValue){
this.$emit('searchForProp',attr,attrValue)
}
// 3. Search组件中的面包屑展示,和移除事件的绑定
<li v-for="prop in searchParams.props" :key="prop.attId" class="with-x">{{prop.split(':')[1]}}<i @click="removeProp(prop)">x</i></li>
// 4. 更具属性搜索
// 点击属性进行搜索的自定义事件
searchForProp(attr,attrValue){
// "props": ["1:1700-2799:价格", "2:6.65-6.74英寸:屏幕尺寸"]
// 这个格式就是服务器需要的格式
// console.log(`${attr.attrId}:${attrValue}:${attr.attrName}`)
const prop = `${attr.attrId}:${attrValue}:${attr.attrName}`
// props本身是一个数组所以用push
const isFlag = this.searchParams.props.some(function(item){
item === prop
})
if(isFlag){
return
// 已经存在该属性
}
this.searchParams.props.push(prop)
this.getSearchInfo()
},
// 5. 移除一个面包屑属对应的回调
// 移除一个pros重新发送请求,老师传递过来的是index,通过索引删除splice(index,1),我这里使用filter方法
removeProp(prop){
const newProp = this.searchParams.props.filter((item)=>{
return item != prop
})
this.searchParams.props = newProp
this.getSearchInfo()
}
解决几个问题
解决搜索页面多次点击不能直接返回Home的问题
描述:就是在路由跳转的时候,如果是在Search中,不让其产生历史记录,这样在使用浏览器后退按钮的时候,可以做到直接返回到Home
解决:
- 在Search进行路由跳转的
push
替换为replace
- 清除categoryName跳转路由引起监视的位置
- 清除keyWord跳转路由引起监视的位置
- 在TypeNav使用三级导航跳转到Search的时候需要判断,如果当前不是在Home页,那就使用
replace
跳转路由 - 在Header点击按钮跳转到Search的时候同样需要判断,判断的条件和TypeNav相同
// 在Search中替换push为replace
this.$router.replace({name:'search',params:this.$route.params})
this.$router.replace({name:'search',query:this.$route.query})
// TypeNav和Header中需要进行的判断
if(this.$route.path != '/home'){
this.$router.replace(location)
}
this.$router.push(location)
参数处理部分的优化
浏览器发送ajax请求,携带的属性值如果是undefined,这个参数的属性是不会被发送的,不占带宽,但是如果值是字符串,是发送到服务器的,会占据带宽
既然空字符串没有用,我们在整理参数的时候,将整理的参数赋值给this.searchParams
的时候将空字符串替换为undefined
集中循环的区别:
- for循环是js当中最简单的遍历方法,主要是针对数组进行遍历,效率不高,但是可以使用break和continue
- for-in循环主要是用来遍历对象的(遍历对象的可枚举属性,比如使用
Object.defineProperty()
添加的属性默认就是不可枚举的)效率最低,因为不但要遍历自身的属性还要遍历原型的 - forEach是数组的一个方法,主要是用来遍历数组,效率高,不可以使用break和continue,可以借助
Object.keys()
方法来遍历对象 - for-of是es6新增的一种遍历方式(前提是必须是可迭代的对象,实现iterator接口,实现该接口还可以使用展开运算符,但是对象不可以,因为没有实现iterator接口)效率没有forEach高,可以使用break和continue
// 整理参数
setSearchParams(){
const {category1Id,category2Id,category3Id,categoryName} = this.$route.query
const {keyword} = this.$route.params
const searchParams = {...this.searchParams,category1Id,category2Id,category3Id,keyword,categoryName}
// 添加如下代码之后,通过netword选项查看携带的参数,字符串值的参数没有发送
const keys = Object.keys(searchParams)
keys.forEach((key)=>{
if(searchParams[key] === ''){
searchParams[key] = undefined // 这里也可以使用delete.searchParams[key]进行删除
}
})
this.searchParams = searchParams
},
排序搜索
动态展示效果
排序规则:order: "1:desc"
- 冒号前面的数字是排序的标志:1为综合排序,2为价格排序
- 冒号后面的是排序类型:asc是升序,desc是降序
动态展示:
- 样式的显示需要根据
order: "1:desc"
中冒号之前的部分来动态指定(根据排序标志指定) - 动态显示图标,图标是否展示的条件跟样式是否添加的条件是一样的,并且展示那种样式是根据asc和desc决定的
字体图标的使用:
- 使用阿里巴巴图标库地址
- 可以使用在线地址,不需要下载,将图标添加到项目之后,可以点击生成在线地址:
//at.alicdn.com/t/font_3202053_gi6fkc1pjk9.css
- 很多资源都需要使用这些静态图标,所以在public下的idnex中进行引入,在引入的时候要补充前缀
https
- 字体图标的class的名称用
-
分割,所以在使用的时候使用字符串包裹
<!-- 如果为1则添加样式,如果为1则展示图片,但是展示那个图片,需要根据排序的规则指定 -->
<li :class="{active:searchParams.order.split(':')[0] === '1'}">
<a href="#">综合
<span v-if="searchParams.order.split(':')[0] === '1'" class="iconfont" :class="{'icon-Downxiangxia':searchParams.order.split(':')[1] === 'desc','icon-arrow_up_fill':searchParams.order.split(':')[1] === 'asc'}"></span>
</a>
</li>
<li :class="{active:searchParams.order.split(':')[0] === '2'}">
<a href="#">价格
<span v-if="searchParams.order.split(':')[0] === '2'" class="iconfont" :class="{'icon-Downxiangxia':searchParams.order.split(':')[1] === 'desc','icon-arrow_up_fill':searchParams.order.split(':')[1] === 'asc'}"></span>
</a>
</li>
因为每次获取order中的内容需要很长的代码,使用计算属性优化:
computed:{
...mapGetters(['goodsList']),
// 获取order按照综合还是价格规则
orderBefore(){
return this.searchParams.order.split(':')[0]
},
// 获取order按照升序还是降序规则
orderAfter(){
return this.searchParams.order.split(':')[1]
}
}
优化后的html结构:
<li :class="{active:orderBefore === '1'}">
<a href="#">综合
<span v-if="orderBefore === '1'" class="iconfont" :class="{'icon-Downxiangxia':orderAfter === 'desc','icon-arrow_up_fill':orderAfter === 'asc'}"></span>
</a>
</li>
<li :class="{active:orderBefore === '2'}">
<a href="#">价格
<span v-if="orderBefore === '2'" class="iconfont" :class="{'icon-Downxiangxia':orderAfter === 'desc','icon-arrow_up_fill':orderAfter === 'asc'}"></span>
</a>
</li>
改变排序的搜索条件发送请求
- 给每个a标签添加事件,传递参数为当前排序的标志
- 在回调函数内部判断当前点击的按钮是否是原来的,以及指定排序规则
// 添加事件
<a href="#" @click="changeSort('1')">综合</a>
<a href="#" @click="changeSort('2')">价格</a>
// 排序的回调函数
changeSort(flag){
const originFlag = this.orderBefore
const originType = this.orderAfter
let newOrder = ''
// 如果点击的按钮和当前排序的前缀一致,就说明,是要改变排序规则
if(flag === originFlag){
// 排序类型还是原来的,但是排序规则看原来的是什么
newOrder = `${originFlag}:${originType === 'asc' ? 'desc' : 'asc'}`
}else{
// 点击的不一样,就用新点击的表示,排序指定一个默认的
newOrder = `${flag}:desc`
}
// 改变参数重新发送请求
this.searchParams.order = newOrder
this.getSearchInfo()
}
分页搜索
分页器的使用
使用提供的分页组件,进行全局注册,在Search页面中直接使用
组件代码:
<template>
<div class="pagination">
<button>上一页</button>
<button>1</button>
<button>···</button>
<button>3</button>
<button>4</button>
<button>5</button>
<button>6</button>
<button>7</button>
<button>···</button>
<button>9</button>
<button>上一页</button>
<button style="margin-left: 30px">共 60 条</button>
</div>
</template>
<script>
export default {
name: "Pagination",
}
</script>
<style lang="less" scoped>
.pagination {
button {
margin: 0 5px;
background-color: #f4f4f5;
color: #606266;
outline: none;
border-radius: 2px;
padding: 0 4px;
vertical-align: top;
display: inline-block;
font-size: 13px;
min-width: 35.5px;
height: 28px;
line-height: 28px;
cursor: pointer;
box-sizing: border-box;
text-align: center;
border: 0;
&[disabled] {
color: #c0c4cc;
cursor: not-allowed;
}
&.active {
cursor: not-allowed;
background-color: #409eff;
color: #fff;
}
}
}
</style>
分页器的展示
分页器组件的作用和需要的数据:
- 展示当前页码:就是Search当中的初始化参数的pageNo
- 展示总条数:总条数在vuex中的searchInfo中存储的total,需要使用
mapState
获取到searchInof- 注意:total是发请求获取的数据,需要指定一个默认值,否则获取到的是undefined
- 展示总页码:通过总条数和每页显示的数量进行计算(每页显示的数量就是Search初始化参数的pageSize)其实在vuex中也存储了totalPages
- 这些数据也可以在Pagination中从vuex中获取,但是分页组件包含在Search,从父组件传递是最合理的方式
- 展示连续页数:需要我们自己定义,一般都是奇数
计算属性得到vuex中的searchInfo,主要是用到其中的tatal(总页数)
...mapState({searchInfo:state=>state.search.searchInfo}),
将信息传递给pagination组件:
<Pagination :currentPageNo="searchParams.pageNo" :pageSize = "searchParams.pageSize" :total="searchInfo.total" :continueNo="5"></Pagination>
pagination组件中使用props获取数据:
props:{
currentPageNo:Number,
pageSize:{
type:Number,
default:10
},
// 注意taotal必须给一个默认值,否则会报一个错误,total是undefined,因为searchInfo是根据请求获取来的数据,请求回来数据是需要时间的
// 当SearchInfo没回来的时候就是空对象,因为传递过来的是undefined
total:{
type:Number,
default:0
},
continueNo:{
type:Number,
required:true
}
},
需要计算的数据:
- 总页数
- 连续页的起始位置和连续页的结束位置
computed:{
// 计算总页码
totalPageNo(){
return Math.ceil(this.total/this.pageSize)
},
// 计算连续页的起始位置和结束位置
startEnd(){
const {currentPageNo,continueNo,totalPageNo} = this
let start = 0
let end = 0
// 如果连续页大于总页数,代表没有那么多连续页
if(continueNo >= totalPageNo){
start = 1
end = totalPageNo
}else{
// 正常情况,比如说当前页是5,那么连续页就是34567,一下就是计算的规律
start = currentPageNo - Math.floor(continueNo/2)
end = currentPageNo + Math.floor(continueNo/2)
// 非正常情况,计算出来的连续页是01234,那么纠正过来就应该是12345(为什么是5个数字,因为连续页是5)
if(start <= 0){
start = 1
end = continueNo
}
// 非正常情况,计算出来的连续页是56789,如果只有9页那正好,如果总共只有8页,那么计算出来应该是45678,结束位置就是8,开始位置8-5+1,也就是总页数-连续数+1
if(end > totalPageNo){
start = totalPageNo - continueNo + 1
end = totalPageNo
}
}
return {start,end}
}
}
展示部分:
注意:for和if可以一起在同一个标签使用,for的优先级比较高,先执行生成结构,但是是否转成真实dom在页面呈现还得继续看if中的判断
<template>
<div class="pagination">
<!-- 如果当前页就是第一页了,禁止上一页 -->
<button :disabled="currentPageNo === 1">上一页</button>
<!-- 第一页什么时候显示:当连连续页起始位置大于1时候,如果连续页的起始位置为1,就通过循环生成了1,2,3,4,5,只有当起始位置大于1的时候才显示 -->
<button v-if="startEnd.start > 1">1</button>
<!-- 三个点,比如连续页为起始位置为1,就12345,起始位置为2就23456,起始位置为3,就34567,这个时候1会显示,然后需要三个点形成1...34567 -->
<button v-if="startEnd.start > 2">···</button>
<!-- for可以和if一起使用,会for执行的优先级高,先执行for里面的然后,进入if进行判断最终决定是否显示 -->
<button :class="{active:page === currentPageNo}" v-for="page in startEnd.end" :key="page" v-if="page >= startEnd.start">{{page}}</button>
<button v-if="startEnd.end < totalPageNo - 1">···</button>
<button v-if="startEnd.end < totalPageNo">{{totalPageNo}}</button>
<button v-if="currentPageNo === totalPageNo">下一页</button>
<button style="margin-left: 30px">共 {{total}} 条</button>
</div>
</template>
点击分页按钮发送请求
子组件需要使用自定义事件将页码传递给父组件,然后由父组件Search发送请求:
<template>
<div class="pagination">
<!-- 如果当前页就是第一页了,禁止上一页 -->
<button @click="$emit('changePageNo',currentPageNo-1)" :disabled="currentPageNo === 1">上一页</button>
<!-- 第一页什么时候显示:当连连续页起始位置大于1时候,如果连续页的起始位置为1,就通过循环生成了1,2,3,4,5,只有当起始位置大于1的时候才显示 -->
<button @click="$emit('changePageNo',1)" v-if="startEnd.start > 1">1</button>
<!-- 三个点,比如连续页为起始位置为1,就12345,起始位置为2就23456,起始位置为3,就34567,这个时候1会显示,然后需要三个点形成1...34567 -->
<button v-if="startEnd.start > 2">···</button>
<!-- for可以和if一起使用,会for执行的优先级高,先执行for里面的然后,进入if进行判断最终决定是否显示 -->
<button @click="$emit('changePageNo',page)" :class="{active:page === currentPageNo}" v-for="page in startEnd.end" :key="page" v-if="page >= startEnd.start">{{page}}</button>
<button v-if="startEnd.end < totalPageNo - 1">···</button>
<button @click="$emit('changePageNo',totalPageNo)" v-if="startEnd.end < totalPageNo">{{totalPageNo}}</button>
<button @click="$emit('changePageNo',currentPageNo + 1)" :disabled="currentPageNo === totalPageNo">下一页</button>
<button style="margin-left: 30px">共 {{total}} 条</button>
</div>
</template>
事件的回调:
<Pagination @changePageNo="changePageNo" :currentPageNo="searchParams.pageNo" :pageSize = "searchParams.pageSize" :total="searchInfo.total" :continueNo="5"></Pagination>
// 分页的自定义事件回调函数
changePageNo(page){
this.searchParams.pageNo = page
this.getSearchInfo()
}
问题:如果翻页到任何一页,这样在使用其他搜索方式进行搜索,那么应该从第一页开始,所以在其他请求之前,应该需要提前将初始化参数中的pageNo变为1,添加如下代码
this.searchParams.pageNo = 1