防抖和节流
防抖(比如用户在input中输入关键字,oninput会打印出你输入时的每一次)
let inp = document.querySelector('input')
inp.oninput = function(){
console.log(this.value)
}
节流(比如用户拖动滚动条时,你console.log的时候会一直打印)
const content = document.getElementById('content')
window.onscroll = function(){
let count = 0
console.log(count)
}
感觉有点像
但是防抖和节流的概念不一样
防抖合并事件并在最后时间去触发事件
节流则是隔段时间触发一次
这里是使用node_modules自带的lodash,把typeNav中changeIndex节流掉
import _ from 'lodash'
,这里是全局引入
import {throttle} from 'lodash/throttle'
这个是按需引入
changeIndex:throttle(function(index){
this.currentIndex = index
},50),
这个是在按需引入下的使用
手写防抖和节流还不会。。
typeNav三级联动的路由跳转
开始了typeNav的路由跳转
路由跳转有两种方式
一种是 router-link 跳转 一种是 this.$router.push/replace
1. 我们首先尝试用router-link to="/search"
此时发现在选择typeNav会出现卡顿,这是因为用了三次v-for嵌套,因此用了router-link之后,下面的第二层和第三层会遍历很多很多遍,router-link本身就是组件,组件遍历多多多次,出现卡顿
2. 选择用this.$router.push
这个呢就需要事件来触发,如果在每个a标签下面加@click,感觉也可以的样子,三个不同的点击事件,把store中的categoryList的数据拿出来存放到data里面,第一层点击就用
let categoryname =this.categorylist.categoryName
let query1 = this.categorylist.categoryId
this.$router.push({name:'search',query:{categoryname:categoryname,categoryid:categoryid1}以此类推--------------------------->(个人想法,不知道怎么把state拿出来放在data里面)
就是点击typeNav里的a标签跳转到/search?xxx
首先呢把a标签里面的href删掉,这个没用,我们需要自己定义事件跳转页面
但是有三层,我们如果点击哪一层的话,也不知道,这时候 事件委派 出场了
事件委派
就是把事件绑定给共同的祖先,当事件触发的时候,通过冒泡,确定子代是哪个发生
通俗来讲,就是把事件给祖先绑定,然后用事件的形参为e,只要你在一堆子元素里,e.target都能把你捞出来
getSearch(e){
console.log(e.target)
}
确定了你是哪层之后,现在要确定你叫什么(也就是啥都不知道的情况下,点击标签的时候要知道他是哪家的(v-for的哪一层),他叫什么(categoryName),他男的女的(categoryId))
dataset可以知道自定义属性的属性值
这里我们就使用自定义属性完成点击的是哪一个
给每一层的v-for加自定义属性categoryName,但是这样还不够,我们还得知道点击的typeNav是哪一层的,则再加一个自定义属性categoryId
<div class="item" v-for="(c1,index) in categoryList" :key="c1.categoryId" @click="getSearch" @mouseleave="leaveIndex">
<h3 @mouseenter="changeIndex(index)" :class="currentIndex==index ? 'cur' : ''" >
<a :data-category="c1.categoryName" :data-categoryid1="c1.categoryId">{{c1.categoryName}}</a>
<!-- <router-link to="/search">{{c1.categoryName}}</router-link> -->
</h3>
<div class="item-list clearfix" :style="{display:currentIndex==index?'block':'none'}">
<div class="subitem" v-for="(c2,indey) in c1.categoryChild" :key="c2.categoryId">
<dl class="fore">
<dt>
<a :data-category="c2.categoryName" :data-categoryid2="c2.categoryId">{{c2.categoryName}}</a>
<!-- <router-link to="/search">{{c2.categoryName}}</router-link> -->
</dt>
<dd v-for="(c3,indez) in c2.categoryChild" :key="c3.categoryId">
<em>
<a :data-category="c3.categoryName" :data-categoryid3="c3.categoryId">{{c3.categoryName}}</a>
<!-- <router-link to="/search">{{c3.categoryName}}</router-link> -->
</em>
</dd>
</dl>
</div>
</div>
</div>
然后通过解构赋值,把dataset的数据传递给categoryName、categoryId等
现在开始拼接
把categoryName赋给query,还需要知道categoryId,则需要if判断为哪一层,判断完之后在query添加属性用query.categoryId = xxx
最后再把name和query拼接起来完成整个url
getSearch(e){
// console.log(e.target)
let element = e.target
console.log(element.dataset)
// 节点中有一个属性dataset属性,可以获取节点的自定义属性和属性值
const {category,categoryid1,categoryid2,categoryid3} = element.dataset
const location = {name:'search'}
const query = {categoryName:category}
if(categoryid1){
query.categoryid1 = categoryid1
}else if(categoryid2){
query.categoryid2 = categoryid2
}else{
query.categoryid3 = categoryid3
}
// 整理参数
location.query = query
this.$router.push(location)
}
实现url拼接输入框的文字和点击typeNav(检索这两个同时存在的url)
当我们点击typeNav之后搜索关键字的时候
或者输入关键字再点击typeNav的时候
url为/关键字?typeNav的参数
header的index.js的代码为
this.$router.push({name:'search',params:{keyword:this.keyword || undefined},query:{k:this.keyword.toUpperCase()}})
此时就会发现如果在输入关键字之后query和params都有值,而我们需要在输入关键字之后,url为/search/关键字,因此我们把query去掉,这样的话,只有name和params,当需要query时,我们再给他传值
this.$router.push({name:'search',params:{keyword:this.keyword || undefined}})
现在需要判断是否有query,如果有query的话,我们需要传入到location
如果没有,直接输出location
if(this.$route.query){
let location = {
name:'search',
params:{keyword:this.keyword || undefined},
}
location.query = this.$route.query
this.$router.push(location)
}else{
let location = {
name:'search',
params:{keyword:this.keyword || undefined},
}
this.$router.push(location)
}
模拟数据Mock
mock是前端伪造ajax请求,不发送给服务器,自己和自己请求
安装mockjs npm i mockjs
在文档中cv floors和banners的json文件(记得格式化,不然有空格的话文件就可能无法运行)
floor和banners的json文件有图片,在public中的images引入(public文件夹在打包的时候,会把相应的资源原封不动打包到dist文件夹)
在mock下新建一个mockServe文件,这个文件是用来引入mock和拿到数据,import Mock from 'mockjs' 记得前面的Mock要大写 ,引入floors和banners(图片、JSON文件是默认暴露的)
此时我们还没有在全局引入mock,需要在入口文件引入 import "mockServe" from "@/mock/mockServe"
使用mock数据
mock数据:第一个参数是请求地址,第二个参数是请求数据
Mock.mock('/mock/banners',{code:200,data:banners}) //模拟首页轮播图
模拟的数据写完了之后,就应该开始模拟请求了
在request下面新建一个目录叫做mockAjax里面存放
import axios from 'axios'
// 引入进度条
import nprogress from 'nprogress'
// 引入进度条样式
import "nprogress/nprogress.css"
// start:进度条开始 done:进度条结束
export function mockRequests(config) {
const instance = axios.create({
baseURL: '/mock', // <-----很重要,不用向服务器发请求,是在本地发请求,所以直接写 /mock
timeout: 3000
})
instance.interceptors.request.use(config => {
// 进度条开始动
nprogress.start()
return config
}, err => {
// return Promise.reject(new Error(err))
})
instance.interceptors.response.use(res => {
// 进度条停止
nprogress.done()
return res.data
}, err => {
return Promise.reject(new Error(err))
})
return instance(config)
}
之后需要使用这个请求,在index.js下面引入,并且一定要记得import mockRequests!!!
// 统一管理项目接口的模块
import { request } from "./request.js"
import { mockRequests } from './mockAjax.js'
export function reqCategoryList() {
return request({
url: '/product/getBaseCategoryList',
methods: 'get'
})
}
// export const reqCategoryList = () => requests.get('/product/getBaseCategoryList')
export function reqBannersList() {
return mockRequests({
url: '/banners',
methods: 'get'
})
}
请求网络也完成了,现在开始使用
因为轮播图只有在home里面使用,我们可以不在app.vue里面导入,直接在listContainer导入
首先派发action,在mounted里面写,this.$store.dispatch(’bannersList')
然后在store的home的index里面写
import {reqCategoryList} from '@/api/index.js'
import {reqBannersList} from '@/api/index.js'
const state = {
// state 中数据默认初始值不能随便写,服务器返回对象就写对象
categoryList:[],
bannersList:[]
}
const mutations = {
// CATEGORYLIST(state,categoryList)这里的categoryList只是传参,是从下面的commit(,res.data)里面传过来的
CATEGORYLIST(state,categoryList){
state.categoryList = categoryList.slice(0,16)
},
BANNERSLIST(state,bannersList){
state.bannersList = bannersList
// console.log()
}
}
const actions = {
// dispatch:异步操作,
// commit :同步操作
// 首先,dispatch是去分发一个action的。而commit则是去调用mutations的,那这两个玩意儿又有啥区别呢
// action: 用于调用mutations去变更state。
// 自身会返回一个promise,
// 支持异步操作
// 通过api里面的接口函数调用,向服务器发请求,获取服务器的数据
// 获取商品分类的数据
async categoryList({commit}){
// 这里要写async,否则显示的是一个promise对象,await相当于.then()
const res = await reqCategoryList()
// console.log(res)
if(res.code == 200){
commit('CATEGORYLIST',res.data)
}
},
//获取轮播图数据
async bannersList({commit}){
const res = await reqBannersList()
console.log(res)
if(res.code == 200){
commit('BANNERSLIST',res.data)
}
}
}
const getters = {}
export default {
state,
mutations,
actions,
getters
}
记得记得记得一定要导入在request下面写的请求,不然报错
现在state也有了,我们需要去使用他,就是在computed里面用mapstate映射数据
computed:{
...mapState({
bannersList:state => state.home.bannersList
})
}
你就会在浏览器的vuex里面看到bannersList下的4个数据
swiper
安装swiper(这里安装的是swipe5.4.5)npm i swiper@5.4.5
这里记得swiper的版本和使用相关,不同的版本,语法也有所不同
需要引入css和js
在home.vue里面导入js import Swiper from ‘swiper'
在main.js全局导入css import 'swiper/css/swiper.css'
能够获取bannersList的数据之后,用v-for循环出图片
现在还差初始化swiper
首先尝试在mounted中挂载初始swiper实例,发现行不通,此时的页面是静态的
原因是在mounted中有异步语句dispatch,当运行mounted时,dom还没有完全渲染完毕,v-for还没执行完毕,就开始执行初始swiper,这当然是行不通的
当mounted中有异步dispatch时,执行顺序:先执行mounted中同步的语句然后执行dispatch,dispatch发送给action,action执行完毕再执行初始swiper,最后执行mutations
我们需要在页面所有dom加载完毕之后再执行初始化swiper,也就是说初始化swiper需要异步
现在我们考虑把初始swiper放在updated中,但也不行,因为在updated中,假如有一个代码数据发生了变化都会调用一遍,整个页面都会刷新一遍,太消耗性能
watch+$nextTick(解决因为异步数据变化或者还没有完成,dom没有渲染完成的问题)
$nextTick经常与其他插件使用(需要dom存在)
考虑放在watch里面,watch监听某个数据的变化从而来执行一些操作
但是页面依旧没有变化,因此我们还需要$nextTick()
$nextTick所指定的回调函数,会在DOM节点更新完毕之后再执行
watch:{
// 监听bannersLisr数据的变化,由空数组变4个元素
bannersList:{
handler(newValue,oldValue){
this.$nextTick(()=>{
var mySwiper = new Swiper ('.swiper-container', {
loop: true, // 循环模式选项
// nextTick所指定的回调函数,会在DOM节点更新完毕之后再执行
// 如果需要分页器
pagination: {
el: '.swiper-pagination',
clickable:true
},
// 如果需要前进后退按钮
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
// 如果需要滚动条
scrollbar: {
el: '.swiper-scrollbar',
},
})
})
}
}
}
开始写floor的组件
跟listcontainer一样,来一个先派发dispatch到store的home.js中,action接收,接收之后传给mutation,mutation传递给state,因为有两个floor,而且里面的内容不同,所以遍历floor,自定义组件是可以被遍历的,在home.vue中接收的接收state的数据,再在floor中父传子得到数据,最后重复的在html中调用数据
由于floor也有轮播图,就考虑做一个全局组件,那样复用性高,不用重复代码
全局组件在components中新建一个目录carousel,里面导入swiper,把swiper的代码粘贴到carousel中,因为是轮播图,所以我们需要数据中的图片,也就是需要父传子(在floor和listContainer中props传递数据给carousel),而且我们需要把floor直接得到的数据改成watch+$nextTick的形式
这里由于是父传子得到的数据,watch的话,数据没有变化则不会返回里面的内容,
这里immediate就出现了,这个是立即执行的,不管你的数据有没有变化,他都会马上执行
然后把floor和listContainer中的数据父传子给carousel
<template>
<div class="swiper-container" id="floor1Swiper" ref="cur">
<div class="swiper-wrapper">
<div class="swiper-slide" v-for="(carousel,index) in list" :key="carousel.id">
<img :src="carousel.imgUrl">
</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:{
type:Array
}
},
watch:{
list:{
// 立即监听,不管数据有没有变化,上来就监听
immediate:true,
handler(newValue,oldValue){
// 只能监听到数据已经有了,但是v-for动态渲染结构我们还是没有办法确定的,因此还是需要用nextTick
// console.log('hhh ')
this.$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',
},
// 如果需要滚动条
scrollbar: {
el: '.swiper-scrollbar',
},
})
})
}
}
}
}
</script>
<style>
</style>
开发模块的套路
1.先静态页面 + 静态组件拆分出来
2.发请求(API)
3.vuex(三连环)
4.组件获取仓库数据,动态展示数据
search模块的开发
先导入search的静态页面,并能使静态页面成功展示,search的index.vue由两部分组成,上面由SearchSelector构成,下面由search的商品列表以及分页器构成
发请求给search的商品列表,商品的请求地址为/list,请求方式为post,但是跟之前的不同的是,这次是有请求参数,把params放在data中,携带参数请求服务器
export function reqSearchList(params){
return request({
url:'/list',
method:'post',
data:params
})
}
// export const reqSearchList = (params) => request({url:'/list',methods:'post',data:params})
跟之前的三连环的套路差不多,在search的index中派发actions的请求,后面要跟一个空对象
mounted() {
this.$store.dispatch('searchList', {})
},
在store的search中接收数据,params当作形参,而且至少要是一个空对象,
post请求用data来接收参数(走的是请求体),get请求用params来接收参数(这个走的是路径)
get用params,“请求参数”
post用data,“推送用户数据”
const actions = {
async searchList({commit},params={}){
// 当前这个reqSearchList这个函数在调用获取服务器数据的时候,至少传递一个参数(空对象)
// params形参,是当用户派发actions的时候,第二个参数传递过来的,至少是一个空对象
const res = await reqSearchList(params)
console.log(res)
if(res.code == 200)
commit('SEARCHLIST',res.data)
}
}
然后commit给mutation,在传给state
在actions里面的console可以知道这个数据里面还有三个对象,而且要在search使用,为了增加复用性,在getters里面把三个对象分别拆出来,在search的index用computed分别接收,
接收到goodslist的数据之后,把数据遍历出来
在input关键字和分类的query两层之中检索相关的商品
如果要检索的话,就必须要传递相关的query,把keyword和categoryName以及categoryId传递进去,之前的空对象就要被替换,用data把要传递进去的数据用searchParams传递
但问题是,如何在点击typeNav和输入关键字的搜索时更新商品的页面
mounted+beforMount只能把点击之后传递的数据能用vuetool显示出来,并不能完成页面的更新
我们需要用路由的变化完成这个需求
用watch+$route
用watch监听变化,如果路由发生了变化,则就调用接口的数据
<script>
import {mapGetters} from 'vuex'
import SearchSelector from './SearchSelector/SearchSelector'
export default {
name: 'search',
data(){
return{
searchParams:{
category1Id:'',
category2Id:'',
category3Id:'',
categoryName:'',
keyword:'',
// 筛选标签
props:[],
// 品牌
trademark:'',
// 排序
order:'',
pageNo:1,
pageSize:10
}
}
},
components: {
SearchSelector
},
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
// console.log(this.$route.query)
// Object.assign(this.searchParams,this.$route.query,this.$route.params)
// console.log('发送请求之前',this.searchParams)
},
mounted() {
// 在发请求之前带给服务器参数【searchParams参数发生变化有数值带给服务器】
// this.getData()
},
computed: {
...mapGetters(['goodsList'])
},
methods:{
getData(){
this.$store.dispatch('searchList', this.searchParams)
}
},
watch:{
$route(newValue,oldValue){
// 发请求之前整理带给服务器参数
Object.assign(this.searchParams,this.$route.query,this.$route.params)
this.getData()
console.log(this.searchParams)
this.searchParams.category1Id = ''
this.searchParams.category2Id = ''
this.searchParams.category3Id = ''
}
}
}
</script>
以上是点击typeNav和输入input之后,url携带了params和query的过程
添加面包屑(typeNav分类/关键字/品牌)
添加typeNav分类的面包屑
当携带了参数后而且可以随时监听路由发生变化之后请求api接口,得到相应的数据
在search添加分类面包屑
<ul class="fl sui-tag">
<!-- 分类的面包屑 -->
<li class="with-x" v-show="searchParams.categoryName">{{searchParams.categoryName}}<i @click="removeCategoryName">×</i></li>
</ul>
当searchParams.categoryName不为空时,则显示
关闭分类绑定的事件,需要在关闭面包屑的同时把分类的query也去掉,并且清空之前留下来的数据,可以把undefined赋给这些数据,而不是用‘’,因为undefined可以不把这些数据带给服务器,页面的性能也就更高
// 分类li的移除按钮
removeCategoryName(){
// 带给服务器参数说明可有可无:如果属性值为空的字符串还是会把相应的字段带给服务器
// 但是你把相应的字段变成undefined,这个字段不会带给服务器
this.searchParams.categoryName = undefined
this.searchParams.category1Id = undefined
this.searchParams.category2Id = undefined
this.searchParams.category3Id = undefined
// this.getData()
this.$router.push({name:'search',params:{keyword:this.searchParams.keyword}})
},
添加关键字的面包屑
在上面的ul里面添加关键字的面包屑
<li class="with-x" v-show="searchParams.keyword">{{searchParams.keyword}}<i @click="removeKeyword">×</i></li>
这个跟上面的同理,但是这个面包屑关闭的时候,需要把params去掉
注:这个关键字移除的需求并没有完整,完整的代码在下面点
// 关键字的移除按钮
removeKeyword(){
this.searchParams.keyword = undefined
if(this.$route.query){
this.$router.push({name:'search',query:this.$route.query})
}
},
此时我们发现面包屑关闭了,但是输入关键字的input的里面还是有关键字,我们需要清空
输入关键字的input在header组件里面,header组件和search是兄弟组件,我们需要兄弟组件通信,此时EventBus(事件总线)出场了
EventBus(事件总线)
在main.js里面配置$bus
new Vue({
router,
// 注册仓库:组件实例身上会多一个$store属性
store,
render: h => h(App),
// 全局事件总线$bus配置
beforeCreate() {
Vue.prototype.$bus = this
}
因为是通过点击关闭关键字的那个按钮,所以在search中发射这个事件,在header中接收
完整的移除关键字的代码
search
// 关键字的移除按钮
removeKeyword(){
this.searchParams.keyword = undefined
// 通知兄弟组件header清除关键字
this.$bus.$emit('clear')
if(this.$route.query){
this.$router.push({name:'search',query:this.$route.query})
}
},
header中挂载
mounted() {
this.$bus.$on('clear',()=>{
this.keyword = ''
})
}
品牌的面包屑
当点击品牌中的item时,item传到面包屑中,需要子(searchSelector)传父(search),用$emit
searchSelector
<li @click="trademarkItem(trademark)">{{trademark.tmName}}</li>
methods:{
trademarkItem(item){
this.$emit('TrademarkItem',item)
}
}
search
// 品牌的添加li按钮
getTrademark(item){
console.log(item)
// this.searchParams.trademark = item.tmName
this.searchParams.trademark = `${item.tmId}:${item.tmName}`
console.log(this.searchParams)
this.getData()
},
removeTrademark(){
this.searchParams.trademark = undefined
}
商品点击参数添加props以及面包屑
props这个是在data里面的一个属性,
props是一个数组,而且要求格式为:商品属性的数组: ["属性ID:属性值:属性名"]
示例: ["2:6.0~6.24英寸:屏幕尺寸"]
是一个用:分割,有3个值的数组
data(){
return{
searchParams:{
category1Id:'',
category2Id:'',
category3Id:'',
categoryName:'',
keyword:'',
// 筛选标签
props:[],
// 品牌
trademark:'',
// 排序
order:'1:asc',
pageNo:1,
pageSize:10
}
}
},
因为这个是在searchSelector选择的,面包屑是在select里面的,涉及到了父子通信
<li v-for="(attr,index) in attrs.attrValueList" @click="attrItem(attr,attrs)" :key="index">
<a>{{attr}}</a>
</li>
当然这个通信是子传父,而且需要把三个值都传递出来,点击的时候传递了attrs这整个关于li的消息,所以考虑在子组件中就把那三个值给完整的传递给父组件
这时候需要模板字符串组成一个新的内容并且传递出去
attrItem(attr,attrs){
// console.log(attrs)
let attrList = `${attrs.attrId}:${attr}:${attrs.attrName}`
// attrList.push(attrs.attrId,attr,attrs.attrName)
this.$emit('attrItem',attrList)
}
在父组件里面使用的子组件中把数据接收到
<SearchSelector @TrademarkItem="getTrademark" @attrItem="getProps"/>
然后在父组件中使用,需要切割数组,并且显示属性值,而且要记得去重,去重现在知道三种方法
数组去重
1.indexOf()
2.includes()
这个返回的不是一个数组,而是一个对象,数组去重不能使用这个方法 new Set()
<!-- 筛选标签的面包屑 -->
<li class="with-x" v-if="searchParams.props" v-for="(item,index) in searchParams.props">{{item.split(':')[1]}}<i @click="removeProps(index)">×</i></li>
getProps(attrList){
// if(this.searchParams.props.indexOf(attrList)==-1){
// this.searchParams.props.push(attrList)
// }
if(this.searchParams.props.includes(attrList)==false){
this.searchParams.props.push(attrList)
}
console.log(attrList)
// let pushlist = this.searchParams.props.push(attrList)
// console.log(pushlist)
// this.searchParams.props = new Set(this.searchParams.props)
// console.log(this.searchParams.props)
},
关闭这个面包屑的时候,需要删除那个数组,所以也需要index来标识是哪一个面包屑
removeProps(index){
// console.log(index)
this.searchParams.props.splice(index,1)
console.log(this.searchParams.props)
// console.log()
},
综合和价格排序
<li :class="{active:isOne}" @click="changeOrder(1)">
<a href="#">综合
<span v-show="isOne&&isDesc">⬇</span>
<span v-show="isOne&&isAsc">⬆</span>
</a>
</li>
<li :class="{active:isTwo}" @click="changeOrder(2)">
<a href="#">销量
<span v-show="isTwo&&isDesc">⬇</span>
<span v-show="isTwo&&isAsc">⬆</span>
</a>
</li>
这个需要点击哪一个li,哪一个li的颜色就发生变化,很明显用:class动态绑定颜色,但是为了提高性能,我们把后面的判断用计算属性,这个order里面是否含有1还是2来判断true还是false,记得computed的方法里面的数据需要return
isOne(){
return this.searchParams.order.indexOf('1')!==-1
},
isTwo(){
return this.searchParams.order.indexOf('2')!==-1
},
接下来用@click绑定事件,用传参的方式来表明是点击了综合还是价格,而且点击之后要取反,因为有升序还是降序两个箭头
changeOrder(flag){
let flagNum = this.searchParams.order.split(':')[0]
let flagText = this.searchParams.order.split(':')[1]
let flagList = this.searchParams.order
if(flagNum == flag){
// if(flagText == 'asc'){
// flagText = 'desc'
// }else{
// flagText = 'asc'
// }
flagList = `${flagNum}:${flagText=='asc'?'desc':'asc'}`
}else{
// if(flagText == 'asc'){
// flagNum = flag
// flagText = 'desc'
// }else{
// flagNum = flag
// flagText = 'asc'
// }
flagList = `${flag}:${flagText=='asc'?'desc':'asc'}`
}
// this.searchParams.order = `${flagNum}:${flagText}`
this.searchParams.order = flagList
console.log(this.searchParams.order)
}