尚硅谷VUE项目-前端项目问题总结05
search模块开发
1.静态页面
2.api
//获取search数据 /api/list POST 需要传递多个参数
//当前接口,给服务器传递一个默认参数,至少是一个空对象
export const reqGetSearchInfo=(params)=>requests({url:'/list',method:'post',data:params})
//例子:
// {
// "category3Id": "61",
// "categoryName": "手机",
// "keyword": "小米",
// "order": "1:desc",
// "pageNo": 1,
// "pageSize": 10,
// "props": ["1:1700-2799:价格", "2:6.65-6.74英寸:屏幕尺寸"],
// "trademark": "4:小米"
// }
/**请求方式两种:
* axios('get')
* axios({})
* post用data,get用params
*/
测试接口是否有数据,在main.js中
import {reqGetSearchInfo} from '@/api/index'
//至少是一个空对象
console.log(reqGetSearchInfo({}),'12212');
3.vuex
store->search
//search组件模块的小仓库
import { reqGetSearchInfo } from "@/api/index";
const state={
searchlist:{},
};
const mutations={
GETSEARCHLIST(state,searchlist){
state.searchlist=searchlist
},
};
const actions={
//第一个上下文对象context,第二个载荷
async getSearchList({commit},params={}){
//当reqGetSearchInfo在调用获取服务器数据时,至少传递一个参数(空对象)
//params是当前用户派发actio时,第二个参数,传递过来的,至少是一个空对象
let result=await reqGetSearchInfo(params)
if(result.code==200){
commit('GETSEARCHLIST',result.data)
}
},
};
//计算属性,在项目中,为了简化数据而生
const getters={};
export default {
// namespaced:true,
state,
mutations,
actions,
getters,
}
可用getters处理数据:
state是分模块的,getters不分模块(如果开了命名空间请注意,会分模块)
开启了命名空间 …mapGetters([‘goodsList’])
开启了命名空间 …mapGetters(‘search’,[‘goodsList’])
//计算属性,在项目中,为了简化数据而生
// 可以把我们将来在组件中需要用的数据进行简化,将来组件在获取组件的时候就方便了
const getters={
//当前形参state为当前仓库的state,而非大仓库的state
attrsList(state){
//如果服务器数据回来了,state.searchlist.attrsList是一个数组,
//如果网络慢或者没网,state.searchlist.attrsList应该返回的是undefined了,所以加【】
return state.searchlist.attrsList||[]
},
goodsList(state){
return state.searchlist.goodsList||[]
},
trademarkList(state){
return state.searchlist.trademarkList||[]
},
};
mounted() {//==》暂时写这里,之后封装一个函数,因为search不可能只执行一次
//先测试接口返回的数据格式
this.$store.dispatch("getSearchList", {});
//用命名空间
// this.$store.dispatch("search/getSearchList", {});
},
computed: {
//普通用法
// ...mapState({
// getSearchList: (state) => state.search.searchlist,
// }),
//getters用法:
//没用命名空间[开启了命名空间的这样拿 ...mapGetters('search',['goodsList'])]
//'attrsList','trademarkList'
...mapGetters(["goodsList"]),
//用命名空间
// ...mapGetters('search',['goodsList','attrsList','trademarkList'])
},
4. search产品模块和search子组件SearchSelector模块
一个知识点:对象合并Object.assign(A, b,c)把b,c的值合并到A上
search模块
let A = {
category1Id: "",
category2Id: "",
category3Id: "",
categoryName: "",
keyword: "",
order: "",
pageNo: 1,
pageSize: 10,
props: [],
trademark: "",
}
let b={category1Id:'2'};
let c={order:'4'};
console.log(Object.assign(A,b,c));
data() {
return {
searchParams: {
category1Id: "",
category2Id: "",
category3Id: "",
categoryName: "",
keyword: "",
order: "",
pageNo: 1,
pageSize: 10,
props: [],
trademark: "",
},
};
},
beforeMount() {
//点击菜单或者搜索时,收集参数【发送请求之前获取参数在beforeMounted中进行】
//复杂写法
// this.searchParams.category1Id=this.$route.query.categorqy1Id;
// 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;
//es6新增语法:合并对象【Object.assign】
//在发送请求之前,把接口需要传递的参数进行整理
Object.assign(this.searchParams, this.$route.query, this.$route.params);
},
mounted() {
//在发送请求之前带给服务器参数【searchParams参数发生变化有数值带给服务器--searchParams】
this.getData();
},
computed: {
//普通用法
// ...mapState({
// getSearchList: (state) => state.search.searchlist,
// }),
//getters用法:
//没用命名空间[开启了命名空间的这样拿 ...mapGetters('search',['goodsList'])]
...mapGetters(["goodsList"]),
//用命名空间
// ...mapGetters('search',['goodsList','attrsList','trademarkList'])
},
watch: {
//不是this.$route
//监听路由信息是否发生变化,如果有变化则再次发送请求。
$route(newValue, oldValue) {
//this.searchParams无变化
// console.log(this.searchParams);
//发送请求之前,先把对应的1,2,3,级分类置空,让他接收下一次的三级id
//分类名字categoryName不用清理,每一次路由变化的时候,都会赋予它新的值。
//放到发送请求前面和后面都可以
this.searchParams.category1Id=''
this.searchParams.category2Id=''
this.searchParams.category3Id=''
//再次发送请求之前整理带给服务器的参数
Object.assign(this.searchParams, this.$route.query, this.$route.params);
this.getData();
//置空放这也可以
},
},
methods: {
getData() {
this.$store.dispatch("getSearchList", this.searchParams);
//用命名空间
// this.$store.dispatch("search/getSearchList", {});
},
},
注意:注意:不需要深度监听,他只是简单的对象形式{}
search子模块–SearchSelector
import { mapGetters } from "vuex"
export default {
name: 'SearchSelector',
mounted(){
// this.$store.dispatch("getSearchList", {});
},
computed:{
...mapGetters(['attrsList','trademarkList'])
},
}
5 面包屑【含组件兄弟通信$bus,子父自定义事件】
1. 面包屑处理分类
删除面包屑时优化注意的点:
removeCategoryName() {
//参数置空,getData没有必须要传入的参数,可带可不带,越少越好;
//所以将this.searchParams.categoryName = undefined;【即不向服务器传参】
// this.searchParams.categoryName = "";
// this.searchParams.category1Id='';
// this.searchParams.category2Id='';
// this.searchParams.category3Id='';
this.searchParams.categoryName = undefined;
this.searchParams.category1Id = undefined;
this.searchParams.category2Id = undefined;
this.searchParams.category3Id = undefined;
// this.getData();//不需要再发请求,watch已发过请求
//路由跳转,但不要把params删除了
//if判断不需要了,watch监听,已经处理了。肯定为true
// if (this.$route.params) {
this.$router.push({ name: "search", params: this.$route.params });
// }
},
2. 面包屑处理关键字【搜索】
用到同级组件通信:
props:父子
自定义事件:子父
vuex:万能 【目前在用作仓库数据】
插槽:父子
pubsub-js:万能(vue很少用)
$bus【eventbus】:全局事件总线
1.1配置全局事件总线:
在main.js中
new Vue({
render: h => h(App),
//全局事件总线$bus配置
beforeCreate(){
Vue.prototype.$bus=this;
},
//注册路由:底下的写法是kv一致省略v,切router小写
//注册路由信息,当这里书写router时,组件身上都拥有$route,$router属性
router,
//注册仓库,组件实例身上会多出一个$store属性
store,
}).$mount('#app')
1.2 search的删除面包屑函数中:
removeKeyword() {
this.searchParams.keyword = undefined;
// this.getData(); //不需要再发请求,watch已发过请求
// if (this.$route.query) {//不用判断,都是true
this.$router.push({ name: "search", query: this.$route.query });
// }
//通知兄弟组件Header清除关键字
this.$bus.$emit("clear");
},
1.3 header的mounted中:
mounted(){
//通过全局事件总线清除关键字
this.$bus.$on('clear',()=>{
this.keyword='';
})
},
2. 面包屑处理品牌信息
分类,关键字,品牌的显示用v-if;而属性值的显示用v-for【props数组形式】,且需要去重
点击品牌–》跳转到对应的品牌页【子传父,一般是直接传过来,然后在父组件处理】
父组件:
<SearchSelector @tradeMarkInfo="tradeMarkInfo"/>
tradeMarkInfo(tradeMark){
this.searchParams.trademark=`${tradeMark.tmId}:${tradeMark.tmName}`
this.getData()
},
removetradeMark() {
this.searchParams.trademark = undefined;
//路由没跳转
this.getData();
},
子组件:
methods:{
tradeMarkHandler(trademark){
this.$emit('tradeMarkInfo',trademark)
},
},
知识点:
1. 对象转换为字符串: 将数据{tmId: 6,tmName: “VIVO”}---------》处理数据为’6:VIVO’==== 》 用模板字符串
2. 字符串切割为另一个字符串:将数据’1:xxx’--------》处理数据为’xxx’============ 》split()将字符串切割成数组,选中哪一项:
3. 字符串添加到数组中:将字符串格式"106:安卓手机:手机一级"—》处理为数组的内容[“106:安卓手机:手机一级”] : 把字符串push到数组里
4. 数组去重: xxx.indexOf(props)==-1
5. 删除数组信息:this.searchParams.props.splice(index,1)
includes【数组和字符串都可】和indexOf差不多
//1. 将数据{tmId: 6,tmName: "VIVO"}--》处理数据为'1:xxx'
tradeMarkInfo(tradeMark){
this.searchParams.trademark=`${tradeMark.tmId}:${tradeMark.tmName}`
this.getData()
},
//2. 将数据'1:xxx'--》处理数据为'xxx'
<ul class="fl sui-tag">
<!-- 分类的面包屑 -->
<li class="with-x" v-if="searchParams.categoryName">
{{ searchParams.categoryName
}}<i @click="removeCategoryName">×</i>
</li>
<!-- 关键字的面包屑 -->
<li class="with-x" v-if="searchParams.keyword">
{{ searchParams.keyword }}<i @click="removeKeyword">×</i>
</li>
<!-- 品牌的面包屑 -->
<li class="with-x" v-if="searchParams.trademark">
{{ searchParams.trademark.split(":")[1] }}<i @click="removeKeyword">×</i>
</li>
</ul>
//3. 将字符串格式"106:安卓手机:手机一级"---》处理为数组的内容["106:安卓手机:手机一级"]
attrInfo(attr, attrValue) {
//['属性ID1:属性值1:属性名1',''属性ID2:属性值2:属性名2'']
let props=`${attr.attrId}:${attrValue}:${attr.attrName}`
this.searchParams.props.push(props)
this.getData()
},
//4. 数组去重,if判断,只有一行代码,可省略{}
if(this.searchParams.props.indexOf(props)==-1){
this.searchParams.props.push(props)
}
//5. 删除数组信息:splice
removeAttr(index){
//删除售卖的属性值
this.searchParams.props.splice(index,1)
this.getData()
},
6. 排序
综合:1.asc 2.desc
价格:1.asc 2.desc
包含可用:indexOf和includes
active选中类:
<ul class="sui-nav">
<li :class="{active:searchParams.order.indexOf('1')!=-1}">
//<li :class="{active:searchParams.order.includes('1')}">
<a href="#">综合</a>
</li>
<li :class="{active:searchParams.order.indexOf('2')!=-1}">
<a href="#">价格⬆</a>
</li>
</ul>
在标签里写一大串,可以封装在computed里:
<ul class="sui-nav">
<!-- <li :class="{active:searchParams.order.indexOf('1')!=-1}"> -->
<li :class="{ active: isOne }">
<a href="#">综合</a>
</li>
<li :class="{ active: isTwo }">
<a href="#">价格⬆</a>
</li>
</ul>
computed: {
isOne() {
return this.searchParams.order.indexOf("1") != -1;
},
isTwo() {
return this.searchParams.order.indexOf("2") != -1;
},
},
选中箭头
引入iconfont:
1.添加项目选font-class,用在线链接:在public下引入
<!-- 引入iconfont -->
<link rel="stylesheet" href="https://at.alicdn.com/t/font_3277412_5u3t7cla0ut.css">
<link rel="stylesheet" href="http://at.alicdn.com/t/font_3277412_5u3t7cla0ut.css">
2.组件中引用
<ul class="sui-nav">
<!-- <li :class="{active:searchParams.order.indexOf('1')!=-1}"> -->
<li :class="{ active: isOne }">
<a href="#">综合<span v-show="isOne" class="iconfont" :class="{'icon-sort-up':isAsc,'icon-sort-down':isDesc}"></span></a>
</li>
<li :class="{ active: isTwo }">
<a href="#" >价格<span v-show="isTwo" class="iconfont" :class="{'icon-sort-up':isAsc,'icon-sort-down':isDesc}"></span></a>
</li>
</ul>
切换排序
<ul class="sui-nav">
<!-- <li :class="{active:searchParams.order.indexOf('1')!=-1}"> -->
<li :class="{ active: isOne }" @click="changeOrder('1')">
<a href="#"
>综合<span
v-show="isOne"
class="iconfont"
:class="{
'icon-sort-up': isAsc,
'icon-sort-down': isDesc,
}"
></span
></a>
</li>
<li :class="{ active: isTwo }" @click="changeOrder('2')">
<a href="#"
>价格<span
v-show="isTwo"
class="iconfont"
:class="{
'icon-sort-up': isAsc,
'icon-sort-down': isDesc,
}"
></span
></a>
</li>
</ul>
changeOrder(flag) {
let originOrder=this.searchParams.order;
let newOrder='';
let originFlag=this.searchParams.order.split(':')[0];
let originSort=this.searchParams.order.split(':')[1];
if(flag==originFlag){
newOrder=`${originFlag}:${originSort=='desc'?'asc':'desc'}`
}else{
newOrder=`${flag}:${'desc'}`
}
this.searchParams.order=newOrder
this.getData()
},