vue2电商项目 search模块

一、项目开发的步骤

1、书写静态页面

2、拆分组件

3、获取服务器的数据动态展示:写api => Vuex调用api请求数据存入store => 组件拿到仓库store的数据 => 动态渲染

4、完成相应的动态业务逻辑

二、数据动态展示

1、search模块API接口的书写

axios已经提前封装好了,向服务器发送post请求(带参数)

ps.发送post请求时,params不能为undefined,至少有个默认值(空对象{ })否则会请求失败。

src/api/index.js
import ajax from './ajax'  //封装好的axios实例对象
import ajaxmock from './ajaxmock'

...
const reqProductList = (params)=>ajax({
    method:'post',
    url:'/list',
    data:params
})
export {reqCategoryList,reqBanners,reqFloors,reqProductList}

2、写Vuex中的search仓库

src\store\modules\search\index.js
import { reqProductList } from "@/api"
const state = {
    // product信息 查看api可知返回的是一个对象
    productList:{}
}
const actions = {
    async getProductList({commit},params){
        // 去除params中key为空的元素
        Object.keys(params).forEach(e=>{
            if(!e) delete params[e]
        })
        const result = await reqProductList(params)
        if(result.code == 200) commit('GET_PRODUCT_LIST',result.data)
    }
}
const mutations = {
    GET_PRODUCT_LIST(state,data){
        state.productList = data
    }
}
const getters = {
}

export default{
    namespaced: true,
    state,
    actions,
    mutations,
    getters
}

point:

用到的方法

  • Object.keys方法:获取对象的key值,返回数组

  • delete方法:删除对象的属性

3、完善search仓库的getters

观看后端提供的接口解释文档,可以观察到返回的数据结构为数组

getters类似计算属性,可以简化仓库中的数据

const getters = {
    // 品牌列表
    trademarkList(state){
        // 异步请求之前 productList是空对象 考虑网速慢情况 为防止trademarkList返回undefined 导致界面v-for遍历报错
        return state.productList.trademarkList || []
    },
    // 属性列表
    attrsList(state){
        return state.productList.attrsList || []
    },
    // 商品列表
    goodsList(state){
        return state.productList.goodsList || []
    },
}

point:

  • 在书写getters时,和state同理,要注意取相应的默认值(空数组或空对象),以防在仓库state为空时,getters取undefined导致页面渲染v-for时报错

  • 开启命名空间(namespaced: true)后,在组件中使用mapGetters函数来绑定带命名空间的模块时,写为

...mapGetters('search', ['goodsList', 'attrsList', 'trademarkList'])

请参考vuex官网 https://vuex.vuejs.org/zh/guide/modules.html #带命名空间的绑定函数

4、渲染商品数据到页面

通过v-for遍历 展示商品列表页面 (简单)

5、search模块根据不同的参数获取数据展示

  • 在search组件的data中写一个option对象,对应发请求的params参数对象,通过各种事件修改option对象的属性值;

  • 封装一个getProductList请求函数,在每次修改option对象后,执行该函数,修改仓库数据,并在页面渲染最新请求返回数据;

  • 封装updateOption函数,作用是通过路由来修改option对象。首次跳转至search路由时,是通过$route携带的params和query来进行请求的发送的。

option对象配置

注意默认值的问题,根据数据类型定义 String的定义空串 Array定义空数组 Number定义默认的数字

src\pages\Search\index.vue
  data() {
    return {
      options:{
        // 注意默认的初始值 根据数据类型定义 String的定义空串 Array定义空数组 Number定义默认的数字
        'category1Id':'',
        'category2Id':'',
        'category3Id':'',
        'categoryName':'',
        'keyword':'',
        'props':[],
        'trademark':'',
        'order':'1:desc',
        'pageNo':1,
        'pageSize':10,
      }
    }
  },

getProductList函数封装

  methods:{
    // 根据当前option发送post请求
    getProductList(pageNo=1){
      // 默认pageNo是1 第一页内容
      this.options.pageNo = pageNo
      this.$store.dispatch('search/getProductList',this.options)
    },
  ...
  }

updateOption函数封装

point:对象的解构赋值

    // 根据query和params来更新options数据
    updateOption(){
      // 解构赋值时,如果解构不成功,默认值会为undefined
      let {category1Id,category2Id,category3Id,categoryName} = this.$route.query
      let {keyword} = this.$route.params
      // 在对象中配合...扩展运算符 后面的(不是undefined的)会覆盖前面的
      this.options = {
        ...this.options,
        category1Id,
        category2Id,
        category3Id,
        categoryName,
        keyword
      }
    },

生命周期钩子 beforeMount

  mounted(){
    this.updateOption()
    this.getProductList()
  },
  beforeMount(){
    this.updateOption()
  },
  mounted(){
    this.getProductList()
  },

教程说要保证在获取产品数据之前,根据路由params和query修改当前options,所以要在挂载之前beforeMount就修改option

但我认为没有必要,写在mounted里是一样的,反正是单线程顺序执行,在请求之前肯定已经修改好option了,而且getProductList还是异步请求。

(在getProductList里输出了一下option,测试了一下都写在mounted里的写法,也只请求了一次是修改option之后)

三、完成动态业务逻辑:对请求参数进行增删改

1、SearchSelector子组件的书写

两大功能:

  • 获取仓库数据,展示目前商品列表的品牌及属性信息

使用v-for遍历即可

  • 完成增删改功能,修改父组件Search的option,再次发送请求,修改仓库数据,展示最新信息

(子组件给父组件传数据,在子组件中触发事件,并修改父组件的数据)

=>将数据和回调函数通过props传给子组件

=>或在父组件中给子组件绑定自定义事件,在子组件中触发后传值给父组件

父组件

src\pages\Search\index.vue

<SearchSelector 
:setTrademark="setTrademark"
:addProp="addProp"/>
 // setTrademark、addProp 传给searchselector子组件的方法 修改option里的trademark和prop
    setTrademark(trademark){
      this.options.trademark = trademark
      this.getProductList()
    },
    addProp(prop){
      // 先查找是否已添加过该属性
      if(this.options.props.indexOf(prop)!=-1) return 
      this.options.props.push(prop)
      this.getProductList()
    },

子组件

src\pages\Search\SearchSelector\index.vue

子组件中声明接受props,并注明类型

    props:{
      setTrademark:Function,
      addProp:Function
    },

2、Bread子组件面包屑导航编写

页面效果

两大功能:

展示当前option中不为空的属性

删除option对象中的指定属性,重发请求

point:

在删除categoryName和keyword属性时,应该对当前路由$route包含的params和query进行相应的修改

方法和数据以props传给子组件

methods里
    // 删除相关option
    removeCategoryName(){
      this.options.category1Id = ''
      this.options.category2Id = ''
      this.options.category3Id = ''
      this.options.categoryName = ''
      // 重新跳转到当前路由,当前路由中的query参数也该删去
      // $route.path不带query参数, 但带params参数(如果有)
      // this.$router.replace({name:'search',params:this.$route.params})
      this.$router.replace(this.$route.path)
    },
    removeKeyword(){
      this.options.keyword = ''
      // 当前路由中的params参数也该删去
      this.$router.replace({name:'search',query:this.$route.query})
    },
    removeTrademark(){
      this.options.trademark = ''
      this.getProductList()
    },
    removeProp(idx){
      // 删除下标为idx的prop
      this.options.props.splice(idx,1)
      this.getProductList()
    },
  watch:{
    // 监听当前路由 发生变化时 更改options 重新发请求
    $route(){
      this.updateOption()
      this.getProductList()
    }
  },

3、商品展示栏里的排序

页面效果

通过点击综合/价格切换按哪个排序,默认降序

如果综合已经高亮,再次点击综合,切换降序/升序

computed:{
      // 判断当前active的为升序/降序
      activeIcon(){
        return this.options.order.split(':')[1] === 'asc' ? 'icon-up' : 'icon-down'
      },
      // 判断当前active的是综合/价格
      isPriceActive(){
        return this.options.order.indexOf('2') === 0 
      },
...
}
setOrder(order1){
      // order2默认降序
      let order2 = 'desc'
      // 如果点击的当前order1项已经是active了 就改当前option的第二项即升降序
      if(this.options.order.indexOf(order1) === 0){
         order2 = (this.options.order.split(':')[1] == 'asc' ? 'desc' : 'asc')
      }
      let order = `${order1}:${order2}`
      this.options.order = order
      this.getProductList()
    },

point:动态绑定样式的方法:

  1. 字符串写法

:style="{fontSize:xxx}" 其中xxx是动态值
适用于样式的类名不确定,需要动态指定

  1. 数组写法

:style="[a,b]" 其中a,b是样式对象
适用于要绑定的个数不确定,名字也不确定

  1. 对象写法

:style="styleObj"
适用于要绑定样式个数确定,名字确定,但是要动态决定用不用

使用阿里的iconfont

  1. 在线地址: https://www.iconfont.cn/

  1. 注册并登陆

  1. 创建一个可以包含需要的所有图标的项目

  1. 搜索图标并添加到购物车

  1. 将购物车中的图标添加到指定项目

  1. 修改图标的名称

  1. 选择Font class的使用方式, 并点击生成在线样式url

  1. 在index页面中引入此图标的在线样式链接:

<link rel="stylesheet"href="" target="_blank">http://at.alicdn.com/t/font_1766283_dobjk7xxze7.css">

  1. 在组件中使用

<i class=”iconfont icondown”>

可以通过color改变颜色, 通过font-size改变大小

4、pagination子组件分页器

页面效果

分页器所需要的数据(参数)

1、需要知道当前是第几页:pageNo 字段代表当前页数

2、需要知道每一页需要展示多少条数据:pageSize字段进行代表

3、需要知道整个分页器一共有多少条数据:total字段进行代表----【通过和每页放几个能计算获取另外一条数据:一共有几页】

4、需要知道分页器连续页码个数:showPageNo字段进行代表 5 | 7 【为什么是奇数?因为对称好看】

连续页的起始与结束数字计算

分页器在开发的时候可以先自己传递假的数据进行调试,调试成功后再用服务器数据

 <Pagination
  :currentPage="options.pageNo"
  :pageSize="options.pageSize"
  :total="productList.total"
  :showPageNo="5"
  v-on:changeCurrentPage="getProductList"
 />
    name:'Pagination',
    props:{
        // 当前页码
        currentPage:{
            type:Number,
            default:1
        },
        // 每页数量
        pageSize:{
            type:Number,
            default:5
        },
        // 总商品数
        total:{
            type:Number,
            default:0
        },
        // 连续页码数
        showPageNo:{
            type:Number,
            default:5
        },
    },
    computed:{
        // 总共需要的页数
        totalPage(){
            return Math.ceil(this.total/this.pageSize)
        },
        // 省略号中间显示的页码
        startEnd(){
            const {currentPage,showPageNo,totalPage} = this
            let start = currentPage - Math.floor(showPageNo/2)
            // start最小为2
            if(start < 2) start = 2
            let end = start + showPageNo
            // end最大为totalPage-1
            if(end > totalPage - 1) end = totalPage - 1
            // 返回一个对象 具有start和end属性
            return {start,end}
        },
    },

向父组件传递数据(当前页码currentPage)

在父组件中绑定自定义事件, v-on:changeCurrentPage="getProductList"

在子组件中触发该自定义事件,并将数据传给父组件,父组件执行回调

getProductList后不要带括号,子组件中触发自定义事件后传的参数会直接在此处使用

methods:{
        // 生成一个从 start 到 end 的连续数组
        generateArray (start, end) {
            return Array.from(new Array(end + 1).keys()).slice(start)
        },
        changeCurrentPage(currentPage){
            this.$emit('changeCurrentPage',currentPage)
        }
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值