目录
一、请求数据并展示
1.写Search模块的接口
2.写Vuex中的search仓库(三连环)
3.组件拿到search仓库的数据
用getters简化仓库中的数据
4.渲染商品数据到页面
5.search模块根据不同的参数获取数据展示
(1)把派发actions的操作封装为函数
(2)设置参数默认值
(3)Object.assign合并对象
(4)再次发请求获取数据
6.渲染子组件数据到页面
三、面包屑部分的开发
1.面包屑处理分类的问题
2.面包屑处理关键字
(1)删除标签
(2)删除表单框里的关键字
(3)删除路径中的params参数
3.面包屑处理品牌信息
(1)子传父:组件自定义事件
(2)添加品牌面包屑
四、平台售卖属性的操作
(1)子传父:组件自定义事件
(2)添加面包屑
五、Search模块的排序操作!!!
1.谁应该有类名
2.谁应该有箭头
3.实现点击切换样式和箭头
六、实现分页器功能!!!
1.分页器所需要的数据(4个条件)
2.先传假数据
(1)计算总页数
(2)计算连续页的开始和结束数字
(3)分页器动态展示
3.首尾页和省略号什么时候显示?
4.把假数据替换为真数据
5.给当前按钮添加样式
一、请求数据并展示
写完静态组件就写api然后写三连环
1.写Search模块的接口
//获取search模块接口,请求需要带参数
//给服务器传递参数的值至少是个空数组
export const reqGetSearchInfo = (params) => {
return requests({ url: "http://gmall-h5-api.atguigu.cn/api/list", method: 'post', data: params })
}
2.写Vuex中的search仓库(三连环)
import { reqGetSearchInfo } from "@/api"
// import search from "core-js/fn/symbol/search"
//search模块下的小仓库
const state = {
searchList: {}
}
const mutations = {
GETSEARCHLIST(state, searchList) {
state.searchList = searchList
}
}
const actions = {
//获取search模块数据
async getSearchList({ commit }, params = {}) {
let result = await reqGetSearchInfo(params)
if (result.code == 200) {
commit('GETSEARCHLIST', result.data)
}
}
}
//getters为了简化仓库当中的数据而生
const getters = {}
export default {
state,
mutations,
actions,
getters
}
入口文件引入:
import {reqGetSearchInfo} from '@/api'
search组件当中派发action:
mounted() {
this.$store.dispatch('getSearchList', {});
},
3.组件拿到search仓库的数据
捞数据,以前的写法:
import {mapState} from 'vuex'
、、、
computed:{
...mapState({
goodsList:state=>state.search.searchList.goodsList
})
}
用getters简化仓库中的数据
注意仓库当中state是分模块的,getters是不分模块的
const getters = {
goodsList(state) {
return state.searchList.goodsList||[]
//为了防止没网,返回的就是undefined
},
trademarkList(state) {
return state.searchList.trademarkList||[]
},
attrsList(state) {
return state.searchList.attrsList||[]
},
}
import {mapGetters} from 'vuex'
mounted() {
this.$store.dispatch('getSearchList', {});
},
computed:{
...mapGetters(['goodsList'])
//这样直接写search组件中就有goodsList数组了
//getters模块不划分模块,直接用数组
}
4.渲染商品数据到页面
<li class="yui3-u-1-5" v-for="(good,index) in goodsList" :key="good.id">
<div class="list-wrap">
<div class="p-img">
<a href="item.html" target="_blank"
><img :src="good.defaultImg"
/></a>
</div>
<div class="price">
<strong>
<em>¥ </em>
<i>{{good.price}}.00</i>
5.search模块根据不同的参数获取数据展示
(1)把派发actions的操作封装为函数
我们目前的dispatch只能在挂载完毕的时候发起一次请求进行捞取数据,正确的应该是我再搜索啥应该再去捞取
可以把它生成一个函数,调用一次就去捞取数据
methods: {
getData() {
//根据参数向服务器发请求获取数据
this.$store.dispatch("getSearchList", {});
},
},
mounted负责来调用函数
mounted() {
this.getData();
},
(2)设置参数默认值
数据类型是对象,参数:
data() {
//我们之前返回的参数都是空数组,现在给它带上参数
return {
searchParams: {
category1Id: "",
category2Id: "",
category3Id: "",
categoryName: "",//分类名
keyword: "",//用户填进去的
order: "",//排序
//这些全是空因为不知道用户填的什么
pageNo: 1,
//默认打开是第一页
pageSize: 10,
//页面显示多少个数据
props: [],
trademark: "",//品牌
},
};
},
在mounted之前我们就得把参数值带过去才能捞取数据,mounted生命周期之前的有beforecreat、created、beforemount,而且beforecreat是获取不到数据的,我们选择在beforemount里,发请求之前发参数,复杂写法:
(3)Object.assign合并对象
用Object.assign:ES6新增的语法,合并对象
Object.assign(x,a,b),三个参数都是数组,后两个给空的x数组赋值
beforeMount() {
Object.assign(this.searchParams,this.$route.query,this.$route.params)
},
现在的问题是更新的函数在mounted里面, 所以请求只能发一次,点了首页的手机再搜索华为页面不动
(4)再次发请求获取数据
我们每次筛选之后路由的信息都在发生变化,所以我们不在mounted里写了,监听路由的变化($route的params和query参数),路由变就请求
watch:{
//这里不用this.$route,$route就直接是vc上的属性
$route(newValue,oldValue){
//参数发生变化的时候我们还得重新去整理参数
Object.assign(this.searchParams,this.$route.query,this.$route.params)
//再次发起ajax请求
this.getData();
}
},
每一次请求完毕想要下一次请求之前应该先把当前的请求都删了
watch:{
//这里不用this.$route,$route就直接是vc上的属性
$route(newValue,oldValue){
//参数发生变化的时候我们还得重新去整理参数
Object.assign(this.searchParams,this.$route.query,this.$route.params)
//再次发起ajax请求
this.getData();
//置空上一次的请求
this.searchParams.category1Id=''
this.searchParams.category2Id=''
this.searchParams.category3Id=''
}
},
keyword有时候会复用所以不置空
6.渲染子组件数据到页面
现在开始做search组件的小组件SearchSelector
import {mapGetters} from 'vuex'
export default {
name: 'SearchSelector',
computed:{
...mapGetters(['trademarkList','attrsList'])
}
}
<li v-for="trademark in trademarkList" :key="trademark.tmId">{{trademark.tmName}})
、、、
<div class="type-wrap" v-for="attr in attrsList" :key="attr.attrId">
<div class="fl key">{{attr.attrName}}</div>
<div class="fl value">
<ul class="type-list">
<li v-for="(attrValue,index) in attr.attrValueList" :key="index">
<a>{{attrValue}}</a>
、、、
然后v-for该拆了拆
三、面包屑部分的开发
1.面包屑处理分类的问题
面包屑可能有也可能没有用v-if,而且点击x号就会删除,设置一个点击事件
<li class="with-x" v-if="searchParams.categoryName">{{searchParams.categoryName}}<i @click="removeCategoryName">×</i></li>
//删除分类的名字
removeCategoryName() {
this.searchParams.categoryName = ""; //只删一个为啥置空啊
//删了之后name和id都得置空
this.searchParams.category1Id = "";
this.searchParams.category2Id = "";
this.searchParams.category3Id = "";
//置空之后还得向服务器发请求
this.getData();
},
这里删除置空好像因为目前我们每次都只有一个标签,所以直接置空了
所有Id和name都置空还是会有十个参数返回服务器那边找数据,既然是空的就没必要添加负担,可以赋值为undefined
this.searchParams.categoryName = undefined; //只删一个为啥置空啊
//删了之后name和id都得置空
this.searchParams.category1Id = undefined;
this.searchParams.category2Id = undefined;
this.searchParams.category3Id = undefined;
还有一个bug就是我们删除标签之后地址栏还是显示那个标签
点击选项之后路由跳转带着参数,页面的路径就会带着标签,我们可以让删除标签之后再进行页面跳转,自己跳自己,再改路径
//地址也需要修改
this.$router.push({name:"search"})
这么写不严谨,我只是x了标签,但是我可能有keyword呢,这句话连我的params参数一起都删了
if (this.$route.params) {
this.$router.push({ name: "search" ,params:this.$route.params});
}
要是有params参数的话,跳转到search而且带着params参数
2.面包屑处理关键字
现在实现搜索关键字,关键字也会有标签,关闭标签路径当中的params没了,搜索框中的文字也没了
(1)删除标签
<!-- 关键字的面包屑 -->
<li class="with-x" v-if="searchParams.keyword">{{ searchParams.keyword}}<i @click="removeKeyword">×</i></li>
//删除关键字
removeKeyword(){
//这一步置空的是search路由的关键字,header上的没有
this.searchParams.keyword = undefined;
//重新发请求
this.getData();
(2)删除表单框里的关键字
清除search路由的关键字并且重新发请求,然后就该清除header组件的关键字了,我们现在在search组件中,他俩是兄弟关系,用到组件间通信,配置$bus全局事件总线
main.js:
new Vue({
render: h => h(App),
beforeCreate(){
Vue.prototype.$bus=this
},
search通知:
//通知兄弟组件header 清除关键字
this.$bus.$emit("clear")
header:
mounted() {
//通过全局事件总线清除关键字
this.$bus.$on("clear",()=>{
this.keyword=''
})
},
(3)删除路径中的params参数
//路由的跳转
this.$router.push({ "name": 'search' })
这么写又把人家的query参数给丢了
//路由的跳转
if (this.$route.params) {
this.$router.push({ name: "search" ,query:this.$route.query});
}
3.面包屑处理品牌信息
(1)子传父:组件自定义事件
实现点击品牌也能出现标签,首先给品牌添加点击事件,(trademark)传递参数为品牌的信息,参数类型为对象,子组件:
<ul class="logo-list">
<li v-for="trademark in trademarkList" :key="trademark.tmId" @click="tradeMarkHandler(trademark)">{{trademark.tmName}})</li>
</ul>
点击之后就得带着品牌信息向服务器捞数据
注意:我们是让父组件发信息给服务器的,因为之前整理的参数都在父组件里,所以我们的子组件需要给父组件传过去品牌信息
子给父通信:自定义事件,父组件给子组件绑定一个自定义事件:
<!--selector-->
<SearchSelector @trademarkInfo="trademarkInfo"/>
儿子触发回调函数:
methods: {
tradeMarkHandler(trademark){
//点击品牌之后整理参数,向服务器发请求
//trademark是个对象 有相应的name和id
this.$emit('trademarkInfo',trademark)
//trademark为品牌名
}
},
父亲整理参数再重新发送:
trademarkInfo(trademark){
//自定义事件的回调,触发在子组件触发
//整理参数,格式要求是id:name
this.searchParams.trademark=`${trademark.tmId}:${trademark.tmName}`
//发请求
this.getData();
}
(2)添加品牌面包屑
<!-- 品牌的面包屑 -->
<li class="with-x" v-if="searchParams.trademark">{{ searchParams.trademark.split(":")[1] }}<i @click="removeTrademark">×</i></li>
trademark现在的格式是id:name,但是我们展示标签的时候只想展示name,所以用split分割开,要数组【1】
removeTrademark(){
this.searchParams.trademark=undefined
this.getData();
}
四、平台售卖属性的操作
(1)子传父:组件自定义事件
给系统、内存啥的添加面包屑
我们点击的是每个小li,给li加点击事件;props参数格式——id:name
<div class="type-wrap" v-for="attr in attrsList" :key="attr.attrId">
<div class="fl key">{{attr.attrName}}</div>
<div class="fl value">
<ul class="type-list">
<li v-for="(attrValue,index) in attr.attrValueList" :key="index" @click="attrInfo(attr,attrValue)">
<!-- 这个attr是上面的v-for的参数,attrValue是这个v-for的参数 -->
<a>{{attrValue}}</a>
</li>
</ul>
</div>
//平台售卖属性
attrInfo(attr,attrValue){
this.$emit('attrInfo',attr,attrValue)
}
然后我们得把attr和attrValue往父组件送
绑定自定义事件:
<!--selector-->
<SearchSelector @trademarkInfo="trademarkInfo" @attrInfo="attrInfo"/>
attrInfo(attr,attrValue){
let props=`${attr.attrId}:${attrValue}:${attr.attrName}`
this.searchParams.props.push(props)
this.getData();
}
(2)添加面包屑
我们这次就不能再用v-if了,因为以前那些要么显示要么不显示,这次是遍历props数组,有几个就得展示几个,用v-for
<li class="with-x" v-for="(attrValue,index) in searchParams.props" :key="index">
{{ attrValue.split(":")[1] }}
但是有个bug,就是如果点两次16G它就给你弄两个16G的标签,所以往数组里push的时候就得先判断
if (this.searchParams.props.indexOf(props) == -1) {
this.searchParams.props.push(props);
}
数组去重:???.indexOf(xxx)==-1数组索引号为-1说明还没有
删除:先确定需不需要传参,我们删除的时候需要获得数组的索引值
<!-- 平台售卖的面包屑 -->
<li
class="with-x"
v-for="(attrValue, index) in searchParams.props"
:key="index"
>
{{ attrValue.split(":")[1] }}<i @click="removeAttr(index)">×</i>
</li>
//移出平台售卖
removeAttr(index){
this.searchParams.props.splice(index,1)
this.getData();
}
五、Search模块的排序操作!!!
1.谁应该有类名
看数据里的order写的是1还是2,是1就是综合,2就是价格
<li :class="{active:searchParams.order.indexOf('1')!=-1}">
<a>综合</a>
</li>
<li :class="{active:searchParams.order.indexOf('2')!=-1}">
<a>价格</a>
</li>
order是几,哪个就是红色的
将上面这段代码封装成函数:
<li :class="{active:isOne}">
<a>综合</a>
</li>
<li :class="{active:isTwo}">
<a>价格</a>
</li>
computed: {
、、、
isOne(){
return this.searchParams.order.indexOf('1')!=-1
},
isTwo(){
return this.searchParams.order.indexOf('2')!=-1
}
},
2.谁应该有箭头
谁有类名谁就应该有箭头,箭头用iconfont,这次来使用一下在线的
在线链接没有协议名,往index.html link引入的时候要加上这个网站的协议名https
<link rel="stylesheet" href="https://at.alicdn.com/t/c/font_4501496_w5xz3uenvcs.css">
箭头向上还是向下取决于order数据里的desc还是asc
<ul class="sui-nav">
<li :class="{active:isOne}"><a>综合<span v-show="isOne" class="iconfont" :class="{'icon-down':isDown,'icon-up':isUp}"></span></a>
</li>
<li :class="{active:isTwo}"><a>价格<span v-show="isTwo" class="iconfont" :class="{'icon-down':isDown,'icon-up':isUp}"></span></a>
</li>
本来我还纳闷那个span里的v-show,后来发现order数字变了之后之前的还留着箭头,箭头应该是有红色样式的才有,也就是目前是它的数字span才能显示
isDown(){
return this.searchParams.order.indexOf('desc')!=-1
},
isUp(){
return this.searchParams.order.indexOf('asc')!=-1
}
3.实现点击切换样式和箭头
我们点了一个红色的,它的箭头方向就应该切换;我们点一个白色的,它的背景色就应该变红,箭头默认为降序,所以我们得传参进去,判断点的是1还是2
<li :class="{active:isOne}" @click="changeOrder('1')">
、、、
<li :class="{active:isTwo}" @click="changeOrder('2')">
changeOrder(flag){
//flag代表用户点击的是1还是2
let originOrder=this.searchParams.order//起始状态
let originFlag=this.searchParams.order.split(":")[0]//记录起始是0还是1
let originSort=this.searchParams.order.split(":")[1]//记录起始的顺序
let newOrder=''//准备一个新的order属性值
//点击综合
if(flag==originFlag){
newOrder=`${originFlag}:${originSort=="desc"?"asc":"desc"}`
//这样写就不用一种一种的分情况写了
}else{
//点击的是价格,默认就降序
newOrder=`${flag}:${'desc'}`
}
//将新的order给旧的
this.searchParams.order=newOrder
this.getData()
}
这个三元表达式太妙了,我自己想肯定得一个一个情况分着写,最后别忘记把新的order给旧的
六、实现分页器功能!!!
1.分页器所需要的数据(4个条件)
分页是一个全局组件:pagitation,全局组件都得到main.js注册
Vue.component(Pagination.name,Pagination)
import {reqGetSearchInfo} from '@/api'
需要知道当前是第几页:pageNo字段
需要知道每一个需要展示多少条数据:pageSize字段
需要知道整个分页器一共有多少条数据:total字段
需要知道连续的页面个数:5或7(奇数——对称):continues字段
2.先传假数据
<Pagination :pageNo="1" :pageSize="3" :total="91" :continues="5" />
props:['pageNo','pageSize','total','continues']
真数据在服务器里面,我们先自己设置一些假数据把组件管理一下再传真数据
(1)计算总页数
computed:{
totalPage(){
return Math.ceil(this.total/this.pageSize)
//向上取整
}
}
Math.ceil(xxx) 向上取整
<button>{{totalPage}}</button>
(2)计算连续页的开始和结束数字
startNumAndEndNum() {
const {continues,pageNo,totalPage}=this//解构一下
//定义两个变量存储起始和结束数字
let start=0 ,end = 0
//连续页数可能不到5页
if(continues>totalPage){
start=1
end=totalPage
}else{
//正常数据,这块不能写死因为你到时候连续页数不一定是5,没准是7
// start=pageNo-2
// end =pageNo+2
start=pageNo-parseInt(continues/2)
end=pageNo+parseInt(continues/2)
}
现在这样写有bug,就是假如你现在页数很多,但是你现在处于第一页,那么start就成负数了
假如当前是第一页,应该是1、2、3、4、5
假如是第二页,也应该是1、2、3、4、5
//不正常的数字纠正
if(start<=0){
start=1
end=continues
}
end还有bug,假如我现在在28页,end是(continues=5)30,但是我总页数可能就29页呢
//end判断
if(end>totalPage){
end=totalPage
start=totalPage-continues+1
}
}
return {start,end}
//记得最后return啊,要不然上面用end、start的时候说undefined
(3)分页器动态展示
一个小tips:v-for可以遍历数组、数字、字符串、对象
<!-- 中间部分 -->
<button v-for="(page, index) in startNumAndEndNum.end" :key="index">
{{ page }}
</button>
让它去遍历到最后一个数,如果end是10的话(pageNo=8),continues显示的是从1到10
过滤掉前面的数字:
<button v-for="(page, index) in startNumAndEndNum.end" :key="index" v-if="page>=startNumAndEndNum.start">{{ page }}</button>
3.首尾页和省略号什么时候显示?
但是存在bug,continues的start如果是1的话,跟前面的1重复了,就不再要前面的1了
<button v-if="startNumAndEndNum.start>1">1</button>
而且···得在start为3时才能出现:
<button v-if="startNumAndEndNum.start>2">···</button>
前面start的bug解决了,就该解决end的了
<button v-if="startNumAndEndNum.end<totalPage-1">···</button>
<button v-if="startNumAndEndNum.end<totalPage">{{ totalPage }}</button>
4.把假数据替换为真数据
去仓库中捞一下total,pageNo和pageSize在data中有
import { mapGetters,mapState } from "vuex";
computed:{
...mapState({
total:state=>state.search.searchList.total
})
<!-- 分页器 -->
<Pagination :pageNo="searchParams.pageNo" :pageSize="searchParams.pageSize" :total="total" :continues="5" />
儿子点击了哪页就得告诉父亲,当前的pageNo,子给父通信:自定义事件
<!-- 分页器 -->
<Pagination @getPageNo="getPageNo" 、、、 />
<button :disabled="pageNo==1" @click="$emit('getPageNo',getPageNo-1)">上一页</button>
<!-- 点击传入当前页码-1,页数为1的时候不能点 -->
<button v-if="startNumAndEndNum.start>1" @click="$emit('getPageNo',1)">1</button>
<button v-if="startNumAndEndNum.start>2">···</button>
<!-- 中间部分 -->
<button v-for="(page, index) in startNumAndEndNum.end" :key="index" v-if="page>=startNumAndEndNum.start" @click="$emit('getPageNo',page)">
{{ page }}
</button>
<button v-if="startNumAndEndNum.end<totalPage-1">···</button>
<button v-if="startNumAndEndNum.end<totalPage" @click="$emit('getPageNo',totalPage)">{{ totalPage }}</button>
<button :disabled="pageNo==totalPage" @click="$emit('getPageNo',getPageNo+1)">下一页</button>
分情况处理点击事件,点击的如果是上一页:第一页不能点击上一页,其他的点击传给当前的页数+1;下一页同理
中间部分点击之后就传当前点击的页数
然后父组件就得开始整理参数,然后重新发请求
getPageNo(){
//整理带给服务器的参数
this.searchParams.pageNo=pageNo
this.getData()
}
5.给当前按钮添加样式
点击哪个按钮它的颜色就变成skyblue
<button v-if="startNumAndEndNum.start>1" @click="$emit('getPageNo',1)" :class="{active:pageNo==1}">1</button>
<button v-if="startNumAndEndNum.start>2">···</button>
<!-- 中间部分 -->
<button v-for="(page, index) in startNumAndEndNum.end" :key="index" v-if="page>=startNumAndEndNum.start" @click="$emit('getPageNo',page)" :class="{active:pageNo==page}">
{{ page }}
</button>
<button v-if="startNumAndEndNum.end<totalPage-1">···</button>
<button v-if="startNumAndEndNum.end<totalPage" @click="$emit('getPageNo',totalPage)" :class="{active:pageNo==totalPage}">{{ totalPage }}</button>
<button :disabled="pageNo==totalPage" @click="$emit('getPageNo',getPageNo+1)">下一页</button>