尚品汇

项目配置

在后面加 –open可以使网页自动打开

项目的其他配置

![](../images/Vue_shp/3.png)

v-if和v-show选择

v-show和v-if是Vue中用于条件渲染的两个指令,它们的作用是根据条件来控制元素的显示和隐藏。虽然它们最终都能实现类似的效果,但它们的工作原理有所不同。

v-show指令会根据条件来切换元素的CSS属性”display”,当条件为true时,元素显示;当条件为false时,元素隐藏。这意味着无论条件如何变化,元素始终保留在DOM中,只是通过改变CSS属性来控制显示和隐藏。

v-if指令则是真正的条件渲染,当条件为true时,元素被渲染到DOM中;当条件为false时,元素从DOM中移除。这意味着当条件变化时,v-if会销毁或重新创建元素,而不仅仅是改变CSS属性。

一般来说,如果需要频繁切换元素的显示和隐藏,可以使用v-show;如果条件在运行时不经常变化,可以使用v-if。另外,v-if还可以与v-else和v-else-if结合,实现更复杂的条件逻辑。

路由元信息

路由传参

在 Vue Router 中,有两种主要的参数传递方式:params 参数和 query 参数。

1. params 参数:

params 参数是通过路由的path中的占位符来传递的。例如,在以下路由配置中:

1
2
3
4
5
6
7
8
9
10
// 路由配置
export default new VueRouter({
routes: [
{
path: '/user/:id',
component: UserProfile,
props: true
}
]
});

在这个例子中,:id 是一个动态参数,它会被作为 params 参数传递给 UserProfile 组件。在组件中,你可以通过 this.$route.params.id 访问这个参数。

2. query 参数:

query 参数是通过 URL 查询字符串传递的,通常在 URL 中以 ? 开头。例如:

1
2
3
4
5
6
7
8
9
10
// 路由配置
export default new VueRouter({
routes: [
{
path: '/search',
component: SearchResults,
props: true
}
]
});

在这个例子中,你可以通过 this.$route.query 访问查询参数。例如,如果你的 URL 是 /search?keyword=vue&category=programming,那么在组件中可以通过 this.$route.query.keywordthis.$route.query.category 访问这两个查询参数。

总结一下:

  • params 是通过路由路径中的占位符传递的,可以通过 this.$route.params 访问。
  • query 是通过 URL 查询字符串传递的,可以通过 this.$route.query 访问。

路由传递参数(对象写法)path是否可以结合params参数一起使用?

不可以,在对象写法中,path不能和params一起使用,必须使用name来代替path

如何指定params参数可传可不传?

对于可选params参数,可以在路由路径中使用问号(?),例如

/user-with-optional/:id?,这样 id 就成了可选的参数。

params参数可以传递也可以不传递,但是如果传递是空串,如何解决?

通过给params参数赋值underfined,例如:

1
id: route.params.id !== undefined ? route.params.id : ''

无论用户传递了空串还是没有传递参数,组件都会接收到一个 id 属性,并且在没有提供参数时,默认为一个空串。

路由组件能不能传递props数据?

是的,路由组件可以通过 props 属性接收来自路由的数据。在 Vue Router 中,你可以使用不同的方式传递数据给组件的 props

布尔模式 (props: true):

当你在路由配置中将 props 设置为 true 时,路由会把所有的路由参数注入到组件的 props 中。(只有params参数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 路由配置
export default new VueRouter({
routes: [
{
path: '/user/:id',
component: UserProfile,
props: true
}
]
});
// UserProfile.vue
export default {
props: ['id'],

}

函数模式 (props: route => ({ id: route.params.id })):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 路由配置
export default new VueRouter({
routes: [
{
path: '/user/:id',
component: UserProfile,
props: route => ({ id: route.params.id })
}
]
});

// UserProfile.vue
export default {
props: ['id'],
}

更加推荐使用函数模式

对象模式少用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 路由配置
export default new VueRouter({
routes: [
{
path: '/user/:id',
component: UserProfile,
props: {
id: {
type: Number, // 设置类型验证
default: 1 // 设置默认值
},
isAdmin: {
type: Boolean,
default: false
}
}
}
]
});

// UserProfile.vue
export default {
props: {
id: {
type: Number,
default: 1
},
isAdmin: {
type: Boolean,
default: false
}
},
}

重写push和replace方法

编程式路由跳转到当前路由(参数不变),多次执行会抛出NavigationDuplicated的警告错误?

原因分析:

vue-router3.1.0之后, 引入了push()的promise的语法, 如果没有通过参数指定回调函数,就返回一个promise来指定成功/失败的回调, 且内部会判断如果要跳转的路径和参数都没有变化, 会抛出一个失败的promise

解决:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

方案1: 在进行跳转时, 指定跳转成功的回调函数或catch错误
// catch()处理错误
this.$router.push(`/search/${this.keyword}`).catch(() => {})
// 指定成功的回调函数
this.$router.push(`/search/${this.keyword}`, () => {})
// 指定失败的回调函数
this.$router.push(`/search/${this.keyword}`, undefined, () => {})


方案2: 修正Vue原型上的push和replace方法
// 缓存原型上的push函数
const originPush = VueRouter.prototype.push
const originReplace = VueRouter.prototype.replace
// 给原型对象上的push指定新函数函数
VueRouter.prototype.push = function (location, onComplete, onAbort) {
// 判断如果没有指定回调函数, 通过call调用源函数并使用catch来处理错误
if (onComplete===undefined && onAbort===undefined) {
return originPush.call(this, location, onComplete, onAbort).catch(() => {})
} else { // 如果有指定任意回调函数, 通过call调用源push函数处理
originPush.call(this, location, onComplete, onAbort)
}
}
VueRouter.prototype.replace = function (location, onComplete, onAbort) {
if (onComplete===undefined && onAbort===undefined) {
return originReplace.call(this, location, onComplete, onAbort).catch(() => {})
} else {
originReplace.call(this, location, onComplete, onAbort)
}
}


postman测试接口可用

  • postman是用来测试API接口的工具
  • postman也是一个活接口文档

使用步骤

(1) 启动 ===> 选择登陆==> cancel ===> 进入主界面

(2) 输入url/参数进行请求测试

(3) 注意post请求体参数需要指定为json格式

(4) 保存测试接口 ==> 后面可以反复使用

测试成功:

api接口统一管理和解决跨域问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave: false,
//配置代理跨域
devServer: {
proxy: {
//表示凡是遇到/api就开始代理服务器
'/api': {
target: 'http://gmall-h5-api.atguigu.cn',
}
}
}
}
)

axios二次包装和nprogress进度条使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/* 
对axios进行二次包装
1. 配置通用的基础路径和超时
2. 显示请求进度条
3. 成功返回的数据不再是response, 而直接是响应体数据response.data
4. 统一处理请求错误, 具体请求也可以选择处理或不处理
*/
import axios from 'axios'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'

// 配置不显示右上角的旋转进度条, 只显示水平进度条
NProgress.configure({ showSpinner: false })

const service = axios.create({
baseURL: "/api",// 基础路径
timeout: 5000// 连接请求超时时间
})
//请求拦截器
service.interceptors.request.use((config) => {
// 显示请求中的水平进度条
NProgress.start()

// 必须返回配置对象
return config
})
//响应拦截器
service.interceptors.response.use((response) => {
// 隐藏进度条
NProgress.done()
// 返回响应体数据
return response.data
}, (error) => {
// 隐藏进度条
NProgress.done()

// 统一处理一下错误
alert(`请求出错:${error.message || '未知错误'}`)

// 后面可以选择不处理或处理
return Promise.reject(error)
})

export default service

vuex模块化开发

Vuex是一个专为Vue.js应用程序开发的状态管理模式。它充当了一个集中式存储,用于管理所有组件的状态。Vuex的作用在于解决了多个组件共享状态时的管理问题,确保状态的一致性和可维护性。

Vuex的核心概念包括state(状态)、getters(获取器)、mutations(突变)和actions(动作)。State代表应用程序的状态数据,Getters用于从state中派生出一些新的状态,Mutations是用于修改state的方法,而Actions则是用于提交mutations的方法,可以包含任意异步操作。

使用Vuex的好处在于,它可以帮助开发者更好地组织和管理应用程序的状态,避免了状态分散在各个组件中导致的混乱和难以维护的问题。此外,Vuex还提供了一些高级特性,如严格模式、插件等,以及开发工具的支持,帮助开发者更好地调试和监控状态的变化。

总之,Vuex是一个强大的状态管理工具,适用于中大型的Vue.js应用程序,能够提高应用程序的可维护性和可扩展性。

三级列表的Vuex实现

TypeNav的index.vue:

vuex调用和映射:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script>
import { mapState } from 'vuex';
export default {
name: "TypeNav",
mounted() {
// 调用store中的dispatch方法,获取分类列表
this.$store.dispatch('CategoryList')
},
computed:{
// 使用mapState方法,将state中的categoryList映射到组件的computed中
...mapState({
categoryList: state => state.home.categoryList
})
}
};
</script>

三级列表动态渲染:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<div class="sort">
<div class="all-sort-list2">
<!-- 这里的categoryList是收到的数据里的categoryList,而不是上面的dispatch的CategoryList -->
<div class="item" v-for="(c1) in categoryList" :key="c1.categoryId">
<h3>
<a href="">{{c1.categoryName}}</a>
</h3>
<div class="item-list clearfix">
<div class="subitem" v-for="(c2) in c1.categoryChild" :key="c2.categoryId">
<dl class="fore">
<dt>
<a href="">{{c2.categoryName}}</a>
</dt>
<dd>
<em v-for="(c3) in c2.categoryChild" :key="c3.categoryId">
<a href="">{{c3.categoryName}}</a>
</em>
</dd>
</dl>
</div>
</div>
</div>
</div>
</div>

store/home/index.js :

三级列表属于home分仓库的内容

发送请求得到数据和vuex的具体流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//引入二次包装的获取商品的三级分类列表的axios请求
import { reqBaseCategoryList } from "@/api";

//state:仓库存储数据的地方
const state = {
//state中数据默认初始值别瞎写,服务器返回对象,服务器返回数组。【根据接口返回值初始化】
categoryList:[],
};
//mutations: 修改state的唯一手段
const mutations = {
// 设置分类列表
CATEGORYLIST(state,categoryList){
// 设置state的categoryList属性为categoryList
state.categoryList = categoryList;
}
};
//action:处理action,可以书写自己的业务逻辑,也可以处理异步
const actions = {
//通过api里面的接口函数调用,向服务器发送请求,获取服务器的数据
//异步调用CategoryList函数,获取基础分类列表
async CategoryList({commit}){
//调用reqBaseCategoryList函数,获取基础分类列表
let result = await reqBaseCategoryList();

//如果返回的code为200,则调用commit函数,将返回的data赋值给CATEGORYLIST
if(result.code == 200){
commit("CATEGORYLIST",result.data);
}
}

};
//getters:理解为计算属性,用于简化仓库数据,让组件获取仓库的数据更加方便
const getters = {};
//对外暴露Store类的一个实例
export default {
state,
mutations,
actions,
getters,
};

三级列表的动态背景颜色实现

  1. 可以直接用简单的css的hover属性实现
  2. 上面的css方法虽然简便,但没有我们vue体现到数据响应的本质,所以这里我们可以用js的事件绑定来响应数据,并用到事件委托来控制父子元素的事件关联

防抖和节流

正常:事件触发非常频繁,而且每一次的触发,回调函数都要去执行(如果时间很短,而回调函数内部有计算,那么很可能出现浏览器卡顿)

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

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

正常:就像商城每次输入都要触发回调,放抖就是让只有最后一次输入过一段你规定的时间生效,前面的都不生效

为了实现防抖,我们需要引入Lodash这个工具库,并使用它里面的防抖动函数

防抖:类似于回城被打断

节流:类似于技能的cd

三级列表联动节流

node_modules已经有Lodash

三级联动组件的路由跳转与传递参数

声明式导航和编程式导航的选择:

我们选择利用事件委托+编程式导航来实现路由的跳转与传递参数:

事件委托:

需要用自定义事件和自定义属性来筛选获取当前点击的a标签:

进行路由跳转的方法:

实现效果:

Search模块中商品分类与过渡动画

要在search中动态显示商品分类,默认是不展示的

所以要对TypeNav组件进行v-show动态绑定数据,而且search模块也要有能鼠标经过显示商品分类和鼠标离开隐藏商品分类的功能,所以还要给全部商品分类绑定两个函数,判断当当前路由不是Home组件的时候,实现上面的功能。

第一行为绑定能鼠标经过显示商品分类和鼠标离开隐藏商品分类的函数

第二行为v-show动态绑定实现显示和隐藏的切换

第一行为判断当当前路由不是Home组件的时候,改变show属性为false

第二行为show属性默认是true

第三行为实现其它用到该全局组件TypeNav时也要有能鼠标经过显示商品分类和鼠标离开隐藏商品分类的功能实现

三级列表获取数据性能优化

没优化前:组件的切换和销毁每次在挂载时都要发送一个数据请求

思路:放在只会挂载一次的地方,也就是App.vue里

优化后,无论组件怎么切换和销毁,都只会发送一次数据请求

参数合并

这两个编程式导航因为有两个可能会传参的search路由跳转地方,

没有传query的加上query,没有传params的加上params,这样传输的search参数就是完整的了

mockjs模拟数据

为了实现轮播图和Floor的数据实现,我们使用mockjs来模拟数据,并拦截Ajax请求,只在前端工作,不涉及到后端。

第一步:

安装mockjs

1
npm i --save mockjs

第二步:

编写模拟JSON数据:

a. 首页广告轮播数据: src/mock/banners.json

b. 首页楼层数据: src/mock/floors.json

注意一定要格式化文档,先用json格式化文档再用prettier格式化文档

第三步:

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

第四步

创建mockServe.js,开始mock(虚拟的数据了),通过mockjs模块实现

1
2
3
4
5
6
7
8
9
//先引入mockjs数据
import Mock from 'mockjs'
//把JSON数据格式引入进来
//webpack默认对外暴露的有: 图片,JSON数据格式
import banner from './banner.json'
import floor from './floor.json'
//mock数据:第一个参数为请求地址,第二个参数为请求数据
Mock.mock('/mock/banner',{code:200,data:banner});//模拟首页大轮播图数据
Mock.mock('/mock/floor',{code:200,data:floor});

第五步:

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

1
2
//引入mockServe.js ----mock数据
import "@/mock/mockServe.js"

轮播图数据获取:

为专门请求mock接口的axios封装:mockAjax.js

在index.js发送轮播和楼层列表请求封装:

1
2
3
4
5
6
7
8
import mockAjax from './mockAjax'

//获取banner,mock模拟实现
export const reqBannerList = () => mockAjax.get('/banner');


// 获取首页楼层列表
export const reqFloors = ()=> mockAjax.get('/floors')

在轮播图组件里挂载发送请求:

1
2
3
4
mounted(){
//派发action,通过Vuex发送ajax请求,将数据存储在仓库当中,这里有mock相关
this.$store.dispatch('getBannerList');
}

在home的分仓库store处理这个请求:

1
2
3
4
5
6
//获取首页轮播图数据
async getBannerList(){
let result = await reqBannerList();
console.log(result);
},

后面的操作和Vuex的三件套基本操作一致,即action,mutation,state。

Swiper+watch+nextTick实现轮播图ListContainer组件

第一步:安装swiper@5

1
npm i --save swiper@5

第二步:引包(相应的JS/CSS)

1
2
//引包
import Swiper from 'swiper';
1
2
//引入swiper样式,这里是因为需要用到轮播样式的不止一个组件,所以在入口函数引入,这样简化了组件的引入操作
import 'swiper/css/swiper.css'

第三步:new Swiper实例(给轮播图添加动态效果)

这里要先有结构才能有new这个实例,所以在组件里先写好这个dom结构,才能new

现在问题来了,我要在哪里new这个实例呢?

1、在mounted里面new这个实例

不可行。组件挂载完毕,正常来说组件结构(Dom)已经全有了,但由于数据的得到是需要经过异步操作,所以mounted完毕后,组件结构还没有办法渲染出来

2、在mounted里面用定时器实现

暂时可行。但还是有不足之处,这样的延迟不满足要求

3.最完美的解决方案:watch+nextTick

通过数据监听,监听已有数据变化

$nextTick:

$nextTick:在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个

$nextTick:可以保证也页面中的结构一定是有的,经常和很多插件一起使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
   watch:{
//监听bannerList数据的变化:因为这条数据发生过变化---由空数组变为数组里面有四个元素
bannerList:{
handler(newVal,oldVal){
//通过watch监听bannerList属性的属性值的变化
//如果执行handler方法,代表组件实例身上这个属性的属性已经有了【数组:四个元素】
//当前这个函数执行:只能保证bannerList数据已经有了,但是你没办法保证v-for已经执行结束了
//v-for执行完毕,才有结构【你现在在watch当中没办法保证的】
//nextTick:在下次DoM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM。
this.$nextTick(()=>{
//当你执行这个回调的时候:保证服务器数据回来了,v-for执行完毕了【轮播图结构一定有了】
var mySwiper = new Swiper (document.querySelector(".swiper-container"), {
// direction: 'vertical', // 垂直切换选项
loop: true, // 循环模式选项

// 如果需要分页器
pagination: {
el: '.swiper-pagination',
//点击小球的时候也切换图片
clickable: true,
},

// 如果需要前进后退按钮
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},

// 如果需要滚动条
scrollbar: {
el: '.swiper-scrollbar',
},
})
})
}
}
}

这里小小的优化:把

1
document.querySelector(".swiper-container")

改写成

1
this.$refs.swiper

当然,这里要改变dom结构,将它的 id 改为 ref就可以实现

实现Floor组件动态展示

这里实现Floor组件的方法和轮播图差不多,就是发送二次包装过的axios请求,获取数据,通过Vuex管理仓库,再把数据传给组件

这里要注意的点有两个:

1.发送请求这时候不能放在floor组件中

因为floor组件是单个组件,但返回的数据可以生成多个floor组件数据,所以这里要把发送dispatch请求的任何委托给

Home组件下的挂载,计算的数据也要放在Home组件当中

1
2
3
4
5
6
7
8
9
 mounted(){
// 调用store中的dispatch方法,获取Floor列表
this.$store.dispatch('getFloorList')
},
computed:{
...mapState({
floorList:state=>state.home.floorList,
})
},

2.对自定义组件用v-for遍历生成

这样可以批量生成floor组件,也不用担心数据怎么单独发给这每一个独立的组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div>
<!-- 三级联动全局组件:三级联动已经注册为全局组件,因此不需要在引入 -->
<TypeNav/>
<ListContainer/>
<Recommend/>
<Rank/>
<Like/>
<Floor v-for="(floor) in floorList" :key="floor.id"/>
<Brand/>
</div>


</template>

然后父组件Home要给子组件Floor传递数据,所以这里用props传递数据

根据传递来的数据来动态渲染轮播图:

这一次的轮播图实例可以在该组件Floor的mounted函数中创建,那为什么上一次在ListContainer组件不行呢。

这是因为上次书写轮播图的时候,是在当前组件内部发送请求,动态渲染解构,所以前台至少要等到服务器数据回来。而在这次,组件的数据是通过父组件props传递给子组件的,所以不需要等待,挂载的时候,dom结构也已经渲染完毕

对比上个轮播图:

剩下的没有比轮播图更复杂的,就大部分直接用v-for渲染列表就完事了。

把轮播图拆分为一个全局组件,方便使用

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

最重要的是把多个相同的组件抽取加工变为统一的结构

例如:该轮播图的模板,脚本都能适应ListContainer组件的异步要求和Floor组件的普通要求,这样才能成为一个公用的组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<template>
<!--banner轮播-->
<div class="swiper-container" ref="cur">
<div class="swiper-wrapper">
<div class="swiper-slide" v-for="(carousel) 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';
//引入Swiper
export default {
name:"Carsousel",
props:['list'],
watch:{
list:{
//立即监听:不管你数据有没有变化,我上来立即监听一次
immediate:true,
handler(){
this.$nextTick(()=>{
//当你执行这个回调的时候:保证服务器数据回来了,v-for执行完毕了【轮播图结构一定有了】
var mySwiper = new Swiper (this.$refs.cur, {
// direction: 'vertical', // 垂直切换选项
loop: true, // 循环模式选项

// 如果需要分页器
pagination: {
el: '.swiper-pagination',
//点击小球的时候也切换图片
clickable: true,
},

// 如果需要前进后退按钮
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},

// 如果需要滚动条
scrollbar: {
el: '.swiper-scrollbar',
},
})
})
}
}
}
};
</script>

<style scoped>

</style>

注意点:全局组件要在入口函数main.js中引入并且注册

这样,即使是对组件传不同数据,也可以简单的通过自定义组件的props传递数据,实现功能

Search组件开发

步骤:
1:先把静态页面+静态组件拆分出来
2:发请求(API)
3:vuex(三连环)
4:组件获取仓库数据,动态展示数据

发请求(API)_API的封装:

vuex:

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

getters计算属性简化仓库数据,这里没有设置命名空间,所以getters是所有组件公共的

动态展示数据

Search模块根据不同的参数获取数据展示:

SearchSelector模块中子组件的动态开发

这里在Search的SearchSelector子组件中使用getters计算数据,并将它动态渲染到模板中

Bug

这里我写好了模板却始终展示不出来数据。我检查vuex发现子组件也已经有收到getters数据了,而且数据也不是空数据,所以我以为是组件没有注册或者引用的问题,但检查了半天,发现没有什么问题,后面,我检查了一下控制台的输出,发现报错在attr上,我检查代码,才发现v-for被我写成了v-fro,导致整个组件根本没有渲染出来,特此提醒今日的错误。

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

通过数据监听实现,这里要将一些不会变的参数(categoryid)恢复为空,否则影响搜索