前端项目(二) vue项目实战-尚品汇


一、项目简介

此项目包括首页, 搜索列表, 商品详情, 购物车, 订单, 支付, 用户登陆/注册等多个子模块,使用 Vue 全家 桶+ES6++Webpack+Axios 等技术,采用模块化、组件化、工程化的模式开发。

大家刚学完vue需要项目练手可以参考这个项目,B站视频教程 尚硅谷VUE项目实战,前端项目-尚品汇(大型\重磅)_哔哩哔哩_bilibili

二、开发过程

day01

前端核心

开发一个前端模块可以概括为以下几个步骤:

(1)写静态页面、拆分为静态组件

(2)发请求(API)

(3)vuex(actions、mutations、state三连操作)

(4)组件获取仓库数据,动态展示 

   Ⅰ 开发准备

    1. 在项目当前目录安装vue2脚手架(前提是已安装npm)

    2. 安装完毕后,我们进行一些配置

        (1) 在package.json中设置项目运行浏览器自动打开,在vue.config.js中设置浏览器打开的IP地址及端口号。

//package.json
"scripts": {
    "serve": "vue-cli-service serve --open", //项目运行完自动打开浏览器
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
}

//vue.config.js
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  lintOnSave:false,
 // 设置浏览器打开的IP地址及端口号
  devServer: {
    open: true,
    host: 'localhost',
    //这里因为我tomcat所用端口也是8080,vue也是8080,所以这里我改成8088了
    port: 8088
  }
})

        (2) vue.config.js中关闭eslint校验工具(不关闭会有各种规范,不按照规范就会报错,如变量定义未使用、命名规范等小问题)

//vue.config.js
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  lintOnSave:false,  //关闭关闭eslint校验工具
  devServer: {
    open: true,
    host: 'localhost',
    port: 8080
  }
})

        (3) 若组件样式是用less书写的,需要下载相关依赖,因为浏览器不识别该样式

npm install --save less@4.1.3
npm install --save less-loader@11.1.0

        (4) 导入reset.css文件清除vue页面默认的样式,因为vue单页面开发,我们只需要修改public下的index.html文件

<link rel="stylesheet" href="reset.css">

   (4) 在src目录下面创建pages文件夹用于存放路由组件,非路由组件存放在components。在pages文件中创建路由组件,同时创建router文件夹,里面创建index.js进行路由配置,并在main.js中进行引入注册。

路由组件和非路由组件区别:

- 非路由组件放在components中,路由组件放在pages或views中

- 非路由组件通过标签使用,路由组件通过路由使用

- 在main.js注册完路由,所有的路由和非路由组件身上都会拥有$router $route属性

- $router:一般进行编程式导航进行路由跳转

- $route: 一般获取路由信息(name path params等)

/* 在router的index.js中配置路由 */
//引入vue、vue-router
import vue from "vue";
import vueRouer from 'vue-router'
//使用路由
vue.use(vueRouer)

import Home from '@/pages/Home/MyHome'
import Login from '@/pages/Login/MyLogin'
import Register from '@/pages/Register/MyRegister'
import Search from '@/pages/Search/MySearch'


export default new vueRouer({
    routes:[
        {
            path:'/home',
            component:Home,
        },
        {
            path:'/login',
            component:Login,
        },
        {
            path:'/register',
            component:Register,
        },
        {
            path:'/search',
            component:Search,
        },
        {
            path:'*',
            redirect:'/home', 
        }
    ]
})

/* 在main.js中进行引入注册 */
import Vue from 'vue'
import App from './App.vue'
import router from '@/router'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  router
}).$mount('#app')

到此,我们vue项目的前置准备工作都已完成,下面进入组件开发阶段!!! 

Ⅱ 静态页面拆分成vue组件

在拆分前,我们先分析下自己的页面,网页头部和尾部是每个页面的公共部分(除登录注册页面没有尾部),所以我们把他们弄成非路由组件,放在components文件夹中;而每个页面的中间内容部分都是不一样的,所以我们把这部分弄成路由组件,放在pages文件夹中,然后在router文件夹下对路由注册、配置。

        (1) 网页头部、尾部拆分成非路由组件

           1. 在src文件夹下创建好目录

            2. 在MyHeader.vue、MyFooter.vue中创建好模板(我用vscode,安装完插件后,<v,可以快速创建模板),然后把静态页面的html、css、图片等资源都弄到对应组件中来

/* MyHeader.vue组件 */
<template>
  <header class="header">
    <!-- 头部的第一行 -->
    <div class="top">
      <div class="container">
        <div class="loginList">
          <p>尚品汇欢迎您!</p>
          <p>
            <span>请</span>
            <a href="###">登录</a>
            <a href="###" class="register">免费注册</a>
          </p>
        </div>
        <div class="typeList">
          <a href="###">我的订单</a>
          <a href="###">我的购物车</a>
          <a href="###">我的尚品汇</a>
          <a href="###">尚品汇会员</a>
          <a href="###">企业采购</a>
          <a href="###">关注尚品汇</a>
          <a href="###">合作招商</a>
          <a href="###">商家后台</a>
        </div>
      </div>
    </div>
    <!--头部第二行 搜索区域-->
    <div class="bottom">
      <h1 class="logoArea">
        <a class="logo" title="尚品汇" href="###" target="_blank">
          <img src="./images/logo.png" alt="" />
        </a>
      </h1>
      <div class="searchArea">
        <form action="###" class="searchForm">
          <input
            type="text"
            id="autocomplete"
            class="input-error input-xxlarge"
          />
          <button class="sui-btn btn-xlarge btn-danger" type="button">
            搜索
          </button>
        </form>
      </div>
    </div>
  </header>
</template>

<script>
export default {};
</script>

<style lang="less" scoped>
        .header {
        &>.top {
            background-color: #eaeaea;
            height: 30px;
            line-height: 30px;

            .container {
                width: 1200px;
                margin: 0 auto;
                overflow: hidden;

                .loginList {
                    float: left;

                    p {
                        float: left;
                        margin-right: 10px;

                        .register {
                            border-left: 1px solid #b3aeae;
                            padding: 0 5px;
                            margin-left: 5px;
                        }
                    }
                }

                .typeList {
                    float: right;

                    a {
                        padding: 0 10px;

                        &+a {
                            border-left: 1px solid #b3aeae;
                        }
                    }

                }

            }
        }

        &>.bottom {
            width: 1200px;
            margin: 0 auto;
            overflow: hidden;

            .logoArea {
                float: left;

                .logo {
                    img {
                        width: 175px;
                        margin: 25px 45px;
                    }
                }
            }

            .searchArea {
                float: right;
                margin-top: 35px;

                .searchForm {
                    overflow: hidden;

                    input {
                        box-sizing: border-box;
                        width: 490px;
                        height: 32px;
                        padding: 0px 4px;
                        border: 2px solid #ea4a36;
                        float: left;

                        &:focus {
                            outline: none;
                        }
                    }

                    button {
                        height: 32px;
                        width: 68px;
                        background-color: #ea4a36;
                        border: none;
                        color: #fff;
                        float: left;
                        cursor: pointer;

                        &:focus {
                            outline: none;
                        }
                    }
                }
            }
        }
    }
</style>
/* MyFooter.vue 组件 */
<template>
  <div class="footer">
    <div class="footer-container">
      <div class="footerList">
        <div class="footerItem">
          <h4>购物指南</h4>
          <ul class="footerItemCon">
            <li>购物流程</li>
            <li>会员介绍</li>
            <li>生活旅行/团购</li>
            <li>常见问题</li>
            <li>购物指南</li>
          </ul>
        </div>
        <div class="footerItem">
          <h4>配送方式</h4>
          <ul class="footerItemCon">
            <li>上门自提</li>
            <li>211限时达</li>
            <li>配送服务查询</li>
            <li>配送费收取标准</li>
            <li>海外配送</li>
          </ul>
        </div>
        <div class="footerItem">
          <h4>支付方式</h4>
          <ul class="footerItemCon">
            <li>货到付款</li>
            <li>在线支付</li>
            <li>分期付款</li>
            <li>邮局汇款</li>
            <li>公司转账</li>
          </ul>
        </div>
        <div class="footerItem">
          <h4>售后服务</h4>
          <ul class="footerItemCon">
            <li>售后政策</li>
            <li>价格保护</li>
            <li>退款说明</li>
            <li>返修/退换货</li>
            <li>取消订单</li>
          </ul>
        </div>
        <div class="footerItem">
          <h4>特色服务</h4>
          <ul class="footerItemCon">
            <li>夺宝岛</li>
            <li>DIY装机</li>
            <li>延保服务</li>
            <li>尚品汇E卡</li>
            <li>尚品汇通信</li>
          </ul>
        </div>
        <div class="footerItem">
          <h4>帮助中心</h4>
          <img src="./images/wx_cz.jpg" />
        </div>
      </div>
      <div class="copyright">
        <ul class="helpLink">
          <li>
            关于我们
            <span class="space"></span>
          </li>
          <li>
            联系我们
            <span class="space"></span>
          </li>
          <li>
            关于我们
            <span class="space"></span>
          </li>
          <li>
            商家入驻
            <span class="space"></span>
          </li>
          <li>
            营销中心
            <span class="space"></span>
          </li>
          <li>
            友情链接
            <span class="space"></span>
          </li>
          <li>
            关于我们
            <span class="space"></span>
          </li>
          <li>
            营销中心
            <span class="space"></span>
          </li>
          <li>
            友情链接
            <span class="space"></span>
          </li>
          <li>关于我们</li>
        </ul>
        <p>地址:北京市昌平区宏福科技园综合楼6层</p>
        <p>京ICP备19006430号</p>
      </div>
    </div>
  </div>
</template>

<script>
export default {};
</script>

<style lang="less" scoped>
        .footer {
        background-color: #eaeaea;

        .footer-container {
            width: 1200px;
            margin: 0 auto;
            padding: 0 15px;

            .footerList {
                padding: 20px;
                border-bottom: 1px solid #e4e1e1;
                border-top: 1px solid #e4e1e1;
                overflow: hidden;
                padding-left: 40px;

                .footerItem {
                    width: 16.6666667%;
                    float: left;

                    h4 {
                        font-size: 14px;
                    }

                    .footerItemCon {
                        li {
                            line-height: 18px;
                        }
                    }

                    &:last-child img {
                        width: 121px;
                    }
                }
            }

            .copyright {
                padding: 20px;

                .helpLink {
                    text-align: center;

                    li {
                        display: inline;

                        .space {
                            border-left: 1px solid #666;
                            width: 1px;
                            height: 13px;
                            background: #666;
                            margin: 8px 10px;
                        }
                    }
                }

                p {
                    margin: 10px 0;
                    text-align: center;
                }
            }
        }
    }
</style>

           3. 这时,我们已经完成了网页头部、尾部组件,然后在App.vue组件中引入、注册使用

<template>
  <div id="app">
    <MyHeaderVue></MyHeaderVue>


    <MyFooterVue></MyFooterVue>
  </div>
</template>

<script>
import MyHeaderVue from './components/Header/MyHeader.vue'
import MyFooterVue from './components/Footer/MyFooter.vue'

export default {
  name: 'App',
  components: {
    MyHeaderVue,
    MyFooterVue,

  }
}
</script>

<style>

</style>

            4. 此时我们在终端npm run serve启动脚手架,打开网页后看到了基本效果

         (2) 将每个页面的中间内容部分都弄成路由组件,并实现search页面路由跳转

           1. 将页面中间部分弄成路由组件(在前面我们已完成)

           2. 实现首页的搜索按钮点击路由跳转

路由跳转方式:  

- 声明式导航router-link标签 <router-link to=“path”>,可以把router-link理解为一个a标签,它 也可以加class修饰

- 编程式导航 :声明式导航能做的编程式都能做,而且还可以处理一些业务

            这里我们用编程式导航来实现,首先需要v-model进行双向绑定获取搜索框输入的内容,然后为搜索按钮添加goSearch方法,实现路由跳转并传参。

传参方法

- 字符串形式    

this.$router.push("/search/"+this.params传参+"?k="+this.query传参)

- 模板字符串  

this.$router.push("/search/+${this.params传参}?k=${this.query传参}")  

**注意**: 上面字符串的传参方法可以看出params参数和'/'结合,query参数和?结合

`http://localhost:8080/#/search/asd?keyword=asd`  

上面url中asd为params的值,keyword=asd为query传递的值。

- 对象(常用)  

this.$router.push({name:"路由名字",params:{传参},query:{传参})。  

以对象方式传参时,如果我们传参中使用了params,只能使用name,不能使用path,如果只是使用query传参,可以使用path  。

<input
    v-model="keyword"  //双向绑定事件
    type="text"
    id="autocomplete"
    class="input-error input-xxlarge"
    />
//goSearach路由跳转
<button class="sui-btn btn-xlarge btn-danger" type="button" @click="goSearch">
   搜索
</button>

//goSearch方法
 goSearch(){
      /* name这需要在router中index.js路由进行配置,这里传参我选择的是query参数,
      若使用params则需要,在路由的path中占位,如:path:'/search/:keyword',同时还需要注意传递参 
      数为空的情况,需要这么处理,params:{name:search,this.keyword || params: 
      {keyword:this.keyword || undefined}}  */
      this.$router.push({name:'MySearch',query:{keyword:this.keyword}})
    }

            3. 此时我们注意一个小问题,当我们多次点击搜索按钮的时候,控制台会出现报错,这是因为我们实现路由跳转用的是$router.push()方法,他的返回值是一个promise,当传递参数多次且重复,会抛出异常。在router文件夹下的index.js文件中修改即可(重写push、replace方法)

//需要重写VueRouter.prototype原型对象身上的push|replace方法

//这里我们先把VueRouter.prototype身上的push|replace方法进行保存一份
let originPush = VueRouter.prototype.push
let originReplace = VueRouter.prototype.push

//随后重写VueRouter.prototype身上的push方法
VueRouter.prototype.push = function (location, resolve, reject) {
  if (resolve && reject) {
    originPush.call(this, location, resolve, reject);
  } else {
    //push方法没有产生第二个参数|第三个参数
    originPush.call(
      this,
      location,
      () => { },
      () => { }
    )
  }
}

//重写VueRouter.prototype身上的replace方法
VueRouter.prototype.replace = function (location, resolve, reject) {
  if (resolve && reject) {
    originReplace.call(this, location, resolve, reject);
  } else {
    originReplace.call(
      this,
      location,
      () => { },
      () => { }
    );
  }
};

         (3) 定义全局组件

             首页的三级联动组件在其他页面也有用到,所以这里我们把TypeNav注册为全局组件,全局的配置需要在main.js中进行配置

//main.js中引入TypeNav全局组件
import TypeNav from '@/components/TypeNav/TypeNav'
//TypeNav全局组件注册,第一个参数全局的名字(组件名字一定要在TypeNav中定义上),第二个参数哪一个组件
Vue.component(TypeNav.name,TypeNav)

            全局组件定义注册完后,就可以在其他地方直接使用了

        (4) 完成首页内容的其他组件搭建,同样是html、css、图片等资源搭建,最后统一在MyHome.vue组件中进行引入、注册、使用 

// 在MyHome.vue组件中完成其他中间内容组件的引入、注册、使用

<template>
  <div>
    <TypeNav></TypeNav>

    <ListContainerVue></ListContainerVue>
    <TodayRecommendVue></TodayRecommendVue>
    <MyRankVue></MyRankVue>
    <MyLikeVue></MyLikeVue>
    <MyFloorVue></MyFloorVue>
    <MyFloorVue></MyFloorVue>
    <MyBrandVue></MyBrandVue>
  </div>
</template>

<script>
import MyBrandVue from "./Brand/MyBrand.vue";
import MyFloorVue from "./Floor/MyFloor.vue";
import MyLikeVue from "./Like/MyLike.vue";
import ListContainerVue from "./ListContainer/ListContainer.vue";
import MyRankVue from "./Rank/MyRank.vue";
import TodayRecommendVue from "./TodayRecommend/TodayRecommend.vue";

export default {
  name: "MyHome",
  components: {
    MyBrandVue,
    MyFloorVue,
    MyLikeVue,
    ListContainerVue,
    MyRankVue,
    TodayRecommendVue,
  },
};
</script>

<style>
</style>

 至此,我们已经成功完成了首页静态页面的搭建!下图是day01的成果

day02

完成API请求

       我们网页的一些数据是需要动态获取的,当前页面三级联动的数据内容是写死的,我们要完成动态获取的需要,下面我们一起来看看怎么完成。

Ⅰ 向API发送请求,获取数据,动态三级联动

        (1) 二次封装axios(主要是为了使用请求、响应拦截器)

npm i -S axios  //安装axios 
/* api文件夹下的request.js文件 */

//对axios进行二次封装
import axios from 'axios'

//1.利用axios对象的方法create,去创建一个axios实例
//2.request就是axios,只是就行了一些配置
const requests = axios.create({
    //配置对象
    //基础路径,发请求的时候,路径中会出现api
    baseURL: '/api',
    timeout: 5000
})

//请求拦截器:在发请求之前,请求拦截器可以检测到,可以在请求发出去之前做一些处理
requests.interceptors.request.use((config) => {
    //config:配置对象,对象里面有一个属性很重要,headers请求头
    return config;
})

//响应拦截器
requests.interceptors.response.use((res) => {
    //响应成功的回调函数:服务器响应数据回来以后,响应拦截器可以检测到,可以做一些事情
    return res.data
}, (error) => {

    //响应失败的回调函数
    return Promise.reject(new Error(error))
})

//对外暴露
export default requests

        (2) 准备向服务器发送请求,获取数据

//当前模块将所有的API进行统一管理
import requests from "@/api/request";

//三级联动接口 /api/product/getBaseCategoryList  get    无参数

//发请求,axios发请求返回结果是promise对象

//下面是axios发送对象请求的方式(三级联动发送数据请求)
export const reqCategoryList = () => requests({url:'/product/getBaseCategoryList',method:'get'})

跨域问题: 

此时我们向服务器发请求获取数据,浏览器会报错,可能是因为跨域问题。跨域问题是指

浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器施加的安全限制,即域名、协议、端口有一个不相同就是跨域。

这里我们采用代理服务器办法来解决跨域问题

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  lintOnSave:false,
  //设置浏览器打开的IP地址及端口号
  devServer:{
    open:true,
    host:'localhost',
    port:8088,
    //代理服务器解决跨域问题(协议、域名、端口号不同)
    proxy:{
      'api':{
        target:'http://39.98.123.211:8510'
      }
    }
  }
})

        (3) 将获取到的数据存储到vuex中

在项目比较大的时候,组件多,数据多,这时候需要用到vuex来存储数据。首先我们在src下创建store文件夹,再创建index.js,在index.js中引入vuex;在main.js注册store;

当项目较大时,我们需要使用vuex模块化来存储数据,在src的store文件夹下创建home、search文件夹,这两个文件夹的index.js是两个小仓库,最后需要在store的index.js大仓库中引入这两个小仓库,这时我们成功搭建了vuex的store仓库

            1. 创建好store及其子目录,并完成配置在main.js中注册

/*home小仓库*/
const state = {}
const mutations = {}
const actions = {}
const getters = {}

export default {
    state,
    mutations,
    actions,
    getters
}

/*search小仓库*/
const state = {}
const mutations = {}
const actions = {}
const getters = {}



export default {
    state,
    mutations,
    actions,
    getters
}

/*store大仓库*/
import Vue from "vue";
import Vuex from 'vuex'
Vue.use(Vuex)

//引入小仓库
import home from './Home'
import search from './Search'


//对外暴露Store类的一个实例
export default new Vuex.Store({
    modules:{
        home,
        search
    }
})

/*在main.js中完成注册*/
import Vue from 'vue'
import App from './App.vue'
import router from '@/router'
import store from '@/store'

//引入TypeNav全局组件
import TypeNav from '@/components/TypeNav/TypeNav'
//TypeNav全局组件注册,第一个参数全局的名字,第二个参数哪一个组件
Vue.component(TypeNav.name,TypeNav)

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  //注册路由:当这里写上router时,组件实例身上都拥有了$route,&router属性
  router,
  //注册全局组件
  TypeNav,
  //注册仓库:组件实例的身上会多一个$store属性
  store,
}).$mount('#app')

            2. 我们需要向服务器请求数据,然后保存到vuex中

我们需要获取的是TypeNav.vue组件三级联动的一些商品信息。当TypeNav组件挂载完毕时,我们需要向服务器请求数据,将获取到的数据用vuex的"三连环"存储到home小仓库中

npm i -S vuex@3.6.2 //安装vuex
/* TypeNav.vue */
<script>
export default {
  name: "TypeNav",
  //当组件挂载完毕,可以向服务器发送请求
  mounted() {
    //通知vuex发送请求,将获取到的数据存储到仓库当中
    this.$store.dispatch('categoryList')
  },
  
};
</script>

/* home小仓库完成数据获取、存储 */
/* home的小仓库 */
import { reqCategoryList } from '@/api'


//state:仓库存储数据的地方
//mutations:修改state的唯一手段
//action:处理action,可以书写自己的业务逻辑,也可以处理异步
//通过API里面的接口函数调用,向服务器发送请求,获取服务器数据
//getters:理解为计算属性,用于简化仓库数据,让组件获取仓科的数据更加方便

const state = {
    categoryList:[]
}
const mutations = {
    CATEGORYLIST(state,categoryList){
        state.categoryList = categoryList
    }
}
const actions = {
    //通过API里面的接口函数调用,向服务器发送请求获取数据
    async categoryList({ commit }) {
        let result = await reqCategoryList()
        if(result.code == 200){
            commit("CATEGORYLIST",result.data)
        }
    }
}

const getters = {}

export default {
    state,
    mutations,
    actions,
    getters
}

           此时我们打开浏览器vue插件,会发现home组件下state下完成了数据的存储

        (4). 将TypeNav.vue组件的三级联动动态实现

           1. 在TypeNav.vue组件中获取到vuex中数据,在TypeNav组件下可查看到categoryList数据

/* TypeNav.vue组件 */
<script>
import {mapState} from 'vuex'
export default {
  name: "TypeNav",
  //当组件挂载完毕,可以向服务器发送请求
  mounted() {
    //通知vuex发送请求,将获取到的数据存储到仓库当中
    this.$store.dispatch('categoryList')
  },
  computed:{
    ...mapState({
      //对象写法,右侧是一个函数,当使用计算属性的时候,右侧函数会立即执行
      //state参数是大仓库中的数据
      categoryList:state => state.home.categoryList
    })
  }
  
};
</script>

           2. 将静态数据删除,v-for动态实现三级联动

<template>
  <div class="type-nav">
    <div class="container">
      <h2 class="all">全部商品分类</h2>
      <nav class="nav">
        <a href="###">服装城</a>
        <a href="###">美妆馆</a>
        <a href="###">尚品汇超市</a>
        <a href="###">全球购</a>
        <a href="###">闪购</a>
        <a href="###">团购</a>
        <a href="###">有趣</a>
        <a href="###">秒杀</a>
      </nav>
      <div class="sort">
        <div class="all-sort-list2">
          <div class="item" v-for="(c1,index) in categoryList" :key="c1.categoryId">
            <h3>
              <a href="">{{c1.categoryName}}</a>
            </h3>
            <div class="item-list clearfix">
              <div class="subitem" v-for="(c2, index) in c1.categoryChild" :key="c2.categoryId">
                <dl class="fore">
                  <dt>
                    <a href="">{{c2.categoryName}}</a>
                  </dt>
                  <dd>
                    <em v-for="(c3,index) in c2.categoryChild" :key="c3.categoryId">
                      <a href="">{{c3.categoryName}}</a>
                    </em>
                  </dd>
                </dl>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

此时我们已经完成了从服务器API请求数据,保存到vuex中,最后再动态地渲染页面!

Ⅱ 为三级联动菜单动态添加背景颜色

当鼠标移动到一级菜单时,此菜单会有背景颜色出现。当然,此效果我们可以用css的:hover伪类实现,但是为了练习js,此效果我们采用js来动态添加。

首先为一级菜单h2标题添加鼠标移入、移出事件,当鼠标移入出现背景颜色,鼠标移出背景颜色消失。如何准确地为当前菜单添加上背景颜色呢?这里,我们可以获取到每一个菜单的索引值,创建一个变量当前的索引,当它和鼠标所在菜单的索引相同时,为此菜单添加类名,即添加上背景颜色,鼠标离开,则将索引值置为-1,取消类名

<template>
  <div class="type-nav">
    <div class="container">
      <div @mouseleave="leaveStyle">
        <h2 class="all">全部商品分类</h2>
        <div class="sort">
          <div class="all-sort-list2">
            <div
              class="item"
              v-for="(c1, index) in categoryList"
              :key="c1.categoryId"
            >
              <h3
                @mouseenter="enterStyle(index)"
                :class="{ cur: currentIndex == index }"
              >
                <a :data-categoryName="c1.categoryName" :data-category1Id="c1.categoryId">{{ c1.categoryName }}</a>
              </h3>
              <div class="item-list clearfix" :style="{display:currentIndex == index ?'block':'none'}">
                <div
                  class="subitem"
                  v-for="(c2, index) in c1.categoryChild"
                  :key="c2.categoryId"
                >
                  <dl class="fore">
                    <dt>
                      <a :data-categoryName="c2.categoryName" :data-category2Id="c2.categoryId">{{ c2.categoryName }}</a>
                    </dt>
                    <dd>
                      <em
                        v-for="(c3, index) in c2.categoryChild"
                        :key="c3.categoryId"
                      >
                        <a  :data-categoryName="c3.categoryName"  :data-category3Id="c3.categoryId">{{ c3.categoryName }}</a>
                      </em>
                    </dd>
                  </dl>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
      <nav class="nav">
        <a href="###">服装城</a>
        <a href="###">美妆馆</a>
        <a href="###">尚品汇超市</a>
        <a href="###">全球购</a>
        <a href="###">闪购</a>
        <a href="###">团购</a>
        <a href="###">有趣</a>
        <a href="###">秒杀</a>
      </nav>
    </div>
  </div>
</template>

<script>
import { mapState } from "vuex";
//import _ from 'lodash'
import throttle from 'lodash/throttle'
export default {
  name: "TypeNav",
  //当组件挂载完毕,可以向服务器发送请求
  data() {
    return {
      //存储用户鼠标移上哪一个一级分类
      currentIndex: -1,
    };
  },
  mounted() {
    //通知vuex发送请求,将获取到的数据存储到仓库当中
    this.$store.dispatch("categoryList");
  },
  computed: {
    ...mapState({
      //对象写法,右侧是一个函数,当使用计算属性的时候,右侧函数会立即执行
      //state参数是大仓库中的数据
      categoryList: (state) => state.home.categoryList,
    }),
  },
  methods: {
    enterStyle:throttle(function (index){
      this.currentIndex = index

    },50),
    leaveStyle() {
      this.currentIndex = -1;
    },
  },
};
</script>

防抖和节流: 

在进行窗口的resize、scroll,输入框内容校验等操作时,如果事件处理函数调用的频率无限制,会加重浏览器的负担,导致用户体验非常糟糕。此时我们可以采用debounce(防抖)和throttle(节流)的方式来减少调用频率,同时又不影响实际效果。

我们的三级联动,当鼠标从上到下滑动过快,会加重浏览器负担,需要采用节流

<script>
import { mapState } from "vuex";
//import _ from 'lodash'
import throttle from 'lodash/throttle'
export default {
  name: "TypeNav",
  methods: {
    /* 节流操作 */
    enterStyle:throttle(function (index){
      this.currentIndex = index
    },50)
};
</script>

Ⅲ 三级联动菜单的路由跳转及传参

当我们点击三级联动的一级、二级、三级菜单的时候,跳转到对应页面,且在地址栏正确完成参数传递。

我们选择编程式导航跳转+事件委托的方法实现跳转,如果不用事件委托的话,每个a标签都有自己的回调,会一直for循环执行方法,大大降低效率;但是采用事件委托后,当我们点击非a标签也会完成跳转,传递参数也是个问题。

此时,我们为a标签添加上自定义属性,:data-categoryName="c1.categoryName"  :data-category1Id="c1.categoryId",这样我们利用event.target.dataset可以识别点击的是不是a,同时也可以获取到id值,也能完成传参工作

<template>
  <div class="type-nav">
    <div class="container">
      <div @mouseleave="leaveStyle">
        <h2 class="all">全部商品分类</h2>
        <div class="sort">
          <div class="all-sort-list2" @click="goSearch">
            <div
              class="item"
              v-for="(c1, index) in categoryList"
              :key="c1.categoryId"
            >
              <h3
                @mouseenter="enterStyle(index)"
                :class="{ cur: currentIndex == index }"
              >
                <a :data-categoryName="c1.categoryName" :data-category1Id="c1.categoryId">{{ c1.categoryName }}</a>
              </h3>
              <div class="item-list clearfix" :style="{display:currentIndex == index ?'block':'none'}">
                <div
                  class="subitem"
                  v-for="(c2, index) in c1.categoryChild"
                  :key="c2.categoryId"
                >
                  <dl class="fore">
                    <dt>
                      <a :data-categoryName="c2.categoryName" :data-category2Id="c2.categoryId">{{ c2.categoryName }}</a>
                    </dt>
                    <dd>
                      <em
                        v-for="(c3, index) in c2.categoryChild"
                        :key="c3.categoryId"
                      >
                        <a  :data-categoryName="c3.categoryName"  :data-category3Id="c3.categoryId">{{ c3.categoryName }}</a>
                      </em>
                    </dd>
                  </dl>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
      <nav class="nav">
        <a href="###">服装城</a>
        <a href="###">美妆馆</a>
        <a href="###">尚品汇超市</a>
        <a href="###">全球购</a>
        <a href="###">闪购</a>
        <a href="###">团购</a>
        <a href="###">有趣</a>
        <a href="###">秒杀</a>
      </nav>
    </div>
  </div>
</template>

<script>
import { mapState } from "vuex";
//import _ from 'lodash'
import throttle from 'lodash/throttle'
export default {
  name: "TypeNav",
  //当组件挂载完毕,可以向服务器发送请求
  data() {
    return {
      //存储用户鼠标移上哪一个一级分类
      currentIndex: -1,
    };
  },
  mounted() {
    //通知vuex发送请求,将获取到的数据存储到仓库当中
    this.$store.dispatch("categoryList");
  },
  computed: {
    ...mapState({
      //对象写法,右侧是一个函数,当使用计算属性的时候,右侧函数会立即执行
      //state参数是大仓库中的数据
      categoryList: (state) => state.home.categoryList,
    }),
  },
  methods: {
    enterStyle:throttle(function (index){
      this.currentIndex = index

    },50),
    leaveStyle() {
      this.currentIndex = -1;
    },
    goSearch(event){
      let element = event.target
      let {categoryname,category1id,category2id,category3id} = element.dataset

      if(categoryname){
        //路由跳转的参数
        let location = {name:'MySearch'}
        let query = {categoryName:categoryname}
        if(category1id){
          query.category1Id = category1id
        }else if(category2id){
          query.category2Id = category2id
        }else{
          query.category3Id = category3id
        }
        location.query = query
        this.$router.push(location)
      }
    },
  },
};
</script>

但是这里我们注意个问题:路由销毁,Vue在路由切换的时候会销毁旧路由。

在上面的三级列表全局组件TypeNav中的mounted进行了请求一次商品分类列表数据,但是由于路由销毁问题,当我们再次使用三级列表全局组件时还会发一次请求。当我们在包含三级列表全局组件的不同组件之间进行切换时,都会进行一次信息请求。
由于每次请求的信息都是一样的,出于性能的考虑我们希望该数据只请求一次,所以我们把这次请求放在App.vue的mounted中。(根组件App.vue的mounted只会执行一次

注意:虽然main.js也是只执行一次,但是不可以放在main.js中。因为只有组件的身上才会有$store属性。

<script>
import MyHeader from './components/Header/MyHeader'
import MyFooter from './components/Footer/MyFooter.vue'
export default {
  name: 'App',
  components: {
      MyHeader,
      MyFooter,
  },
  mounted() {
    //通知vuex发请求,获取数据,存储于仓库中
    this.$store.dispatch("categoryList");
  },
}
</script>

 Ⅳ 合并参数(三级联动和搜索框

    完成效果,搜索框我搜索的是华为,三级联动我点的是图书、音像、电子书

    我们会先判断当前路由的参数,如果是params参数,判断它有没有query参数;如果是query,判断它有没有params参数,如果有的话,会在它当前路径location对象上面添加上另一个参数,这样它就能带两个参数了。

goSearch(){
      if(this.$route.query){
        let location = {name:'MySearch',params:{keyword:this.keyword || undefined}}
        location.query = this.$route.query
        this.$router.push(location)
}

goSearch(event){
      let element = event.target
      let {categoryname,category1id,category2id,category3id} = element.dataset

      if(categoryname){
        //路由跳转的参数
        let location = {name:'MySearch'}
        let query = {categoryName:categoryname}
        if(category1id){
          query.category1Id = category1id
        }else if(category2id){
          query.category2Id = category2id
        }else{
          query.category3Id = category3id
        }
        if(this.$route.params){
          location.params = this.$route.params
          location.query = query
          this.$router.push(location)
        }
        
      }
    }

day03

mock插件模拟后端数据

mock用来拦截前端ajax请求,返回我们自定义的数据用于测试前端接口,即我们自己可以模拟前后端分离项目。

Ⅰ 搭建好mock数据及mockServe.js

在mock文件夹下创建banner.json、floor.json数据,同时mockServe.js配置好数据,当前端发送数据请求时,mock拦截前端ajax请求,及时做出响应。

import Mock from "mockjs";
import banner from './banner.json';
import floor from './floor.json'

//参数:请求地址  请求数据
Mock.mock("/mock/banner",{code:200,data:banner})
Mock.mock("/mock/floor",{code:200,data:floor})

Ⅱ 向mock发送数据请求实现动态轮播

        (1) 封装ajax,得到mockAjax.js

//对axios进行二次封装
import axios from 'axios'
//引入进度条
import nProgress from 'nprogress'
import "nprogress/nprogress.css"
//1.利用axios对象的方法create,去创建一个axios实例
//2.request就是axios,只是就行了一些配置
const requests = axios.create({
    //配置对象
    //基础路径,发请求的时候,路径中会出现mock
    baseURL: '/mock',
    timeout: 5000
})

//请求拦截器:在发请求之前,请求拦截器可以检测到,可以在请求发出去之前做一些处理
requests.interceptors.request.use((config) => {
    //config:配置对象,对象里面有一个属性很重要,headers请求头
    nProgress.start()
    return config;
})

//响应拦截器
requests.interceptors.response.use((res) => {
    //响应成功的回调函数:服务器响应数据回来以后,响应拦截器可以检测到,可以做一些事情
    nProgress.done()
    return res.data
}, (error) => {

    //响应失败的回调函数
    return Promise.reject(new Error(error))
})



//对外暴露
export default requests

         (2) ajax发送请求,获取轮播图数据

//当前模块将所有的API进行统一管理
import requests from "@/api/request";
import mockRequests from "@/api/mockAjax";

//三级联动接口 /api/product/getBaseCategoryList  get    无参数

//发请求,axios发请求返回结果是promise对象

//下面是axios发送对象请求的方式(三级联动发送数据请求)
export const reqCategoryList = () => requests({url:'/product/getBaseCategoryList',method:'get'})

export const reqGetBannerList = () => mockRequests.get('/banner')

        (3)  将获取到的数据存储到vuex中

        (4)  获取vuex中存储的数据,实现轮播图效果

这里我们需要注意一个问题,就是我们是在什么时候调用接口函数实现动态轮播图?当挂载完毕后发送请求我们在mounted中先去异步请求了轮播图数据,然后又创建的swiper实例。由于请求数据是异步的,所以浏览器不会等待该请求执行(获取完数据并且v-for已执行完毕)完再去创建swiper,而是先创建了swiper实例,但是此时我们的轮播图数据还没有获得,就导致了轮播图展示失败。

使用watch+[this.$nextTick()]解决此问题,this. $nextTick它会将回调延迟到下次 DOM更新循环之后执行(循环就是这里的v-for),即nextTick函数会等浏览器执行完请求(获取到数据、v-for执行完毕后),再创建swiper实列,无非是等我们页面中的结构都有了再去执行回调函数,这样就不会出现轮播图不显示的问题了。

我们这可以更加完善点将轮播图组件封装成全局组件进行调用。

<script>
import Swiper from "swiper";
export default {
  name: "MyCarousel",
  props: ["list"],
  watch: {
    list: {
      immediate: true,
      handler() {
        this.$nextTick(() => {
          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>

 此时我们已经完成了home组件的开发!

day04

完成search组件的开发

(1)将search模块拆分为静态组件

(2)发请求(API)获取商品数据

(3)vuex(actions、mutations、state三连操作)

(4)组件获取仓库数据,动态展示商品 

(5)组件获取仓库数据,动态展示商品 

Ⅰ 将search模块拆分为静态组件

        此过程我们在前面已经详细说过了,在此处省略。。。。。

Ⅱ 发请求(API)获取商品数据

        (1) 向服务器发送数据

        在api下的index.js向服务器发送请求,此处发送的请求是需要带有参数的。当我们在搜索框输入数据或者点三级联动的数据时,会向服务器发送请求获取商品数据。

export const reqGetSearchInfo = (params) => requests({url:"/list",method:"post",data:params})

        (2) 获取服务器返回的数据

        我们此处使用getters来传递数据,如果不使用getters属性,我们在组件获取state中的数据表达式为:`this.$store.state.子模块.属性`,如果有多个组件需要用到此属性,我们要么复制这个表达式,或者抽取到一个共享函数然后在多处导入它——无论哪种方式都不是很理想。而getters将获取store中的数据封装为函数,代码维护变得更简单(和我们将请求封装为api一样),而且getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。仓库中的getters是全局属性,是不分模块的,即store中所有模块的getter内的函数都可以通过`$store.getters.函数名`获取。 

在这里注意:我们这个请求是需要参数的,我第一次默写的时候发现了错误,后来才知道这里忘了传递参数,让我检查了半小时。。。

/*serach小仓库文件下的index.js*/
import { reqGetSearchInfo } from "@/api"

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)
       }
    }

}
const getters = {
    goodsList(state){
        return state.searchList.goodsList || []
    },
    trademarkList(state){
        return state.searchList.trademarkList || []
    },
    attrsList(state){
        return state.searchList.attrsList || []
    }
}

export default {
    state,
    mutations,
    actions,
    getters
}

/* serach组件下的index.vue */
<script>
import SearchSelector from "./SearchSelector/SearchSelector";
import { mapGetters } from "vuex";
export default {
  name: "MySearch",
  beforeMount() {
    Object.assign(this.searchParams, this.$route.query, this.$route.params);
  },
  mounted() {
    this.getData();
  },

  components: {
    SearchSelector,
  },
  data() {
    return {
      searchParams: {
        category1Id: "",
        category2Id: "",
        category3Id: "",
        categoryName: "",
        keyword: "",
        order: "",
        pageNo: 1,
        pageSize: 10,
        props: [""],
        trademark: "",
      },
    };
  },
  methods: {
    //根据参数不同获取发请求数据
    getData() {
      this.$store.dispatch("getSelectList", this.searchParams);
    },
  },
  computed: {
    ...mapGetters(["goodsList"]),
  },
};
</script>

        现在我们用getters传递了goodlist数据,并v-for动态地渲染了出来。

         (3)  动态的实现商品搜索功能

当我们点击三级联动的其他地方或搜索框内容变化的时候,即地址栏参数变了,这时候下面的数据也需要变化,即重现向服务发送数据请求,重新遍历。

<script>
import SearchSelector from "./SearchSelector/SearchSelector";
import { mapGetters } from "vuex";
export default {
  name: "MySearch",
  beforeMount() {
    Object.assign(this.searchParams, this.$route.query, this.$route.params);
  },
  mounted() {
    this.getData();
  },

  components: {
    SearchSelector,
  },
  data() {
    return {
      searchParams: {
        category1Id: "",
        category2Id: "",
        category3Id: "",
        categoryName: "",
        keyword: "",
        order: "",
        pageNo: 1,
        pageSize: 10,
        props: [""],
        trademark: "",
      },
    };
  },
  methods: {
    //根据参数不同获取发请求数据
    getData() {
      this.$store.dispatch("getSelectList", this.searchParams);
    },
  },
  computed: {
    ...mapGetters(["goodsList"]),
  },
};
</script>

核心的地方: 

如何实时更新请求参数?

这里我们在data下创建SearchSelector对象来存储所有的参数,当参数发生变化后,会对参数重新赋值,然后根据最新的参数重新发送请求。

Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。

此时,当我们点击三级联动的数据或在搜索框内容输入数据的时候,可以动态的筛选跳转到指定条件的商品,实现了search模块根据不同参数获取数据展示。

        (4) search模块中子组件动态开发

        此组件所需的数据,我们此前已经存储在了vuex中,我们只需获取vuex中数据即可,然后v-for实现动态开发。

<template>
  <div class="clearfix selector">
    <div class="type-wrap logo">
      <div class="fl key brand">品牌</div>
      <div class="value logos">
        <ul class="logo-list" v-for="trademark in trademarkList" :key="trademark.tmId">
          <li>{{trademark.tmName}} </li>
        </ul>
      </div>
      <div class="ext">
        <a href="javascript:void(0);" class="sui-btn">多选</a>
        <a href="javascript:void(0);">更多</a>
      </div>
    </div>
    <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>
          </li>
        </ul>
      </div>
      <div class="fl ext"></div>
    </div>
  </div>
</template>

<script>
import {mapGetters} from 'vuex'
  export default {
    name: 'SearchSelector',
    computed:{
      ...mapGetters(['trademarkList','attrsList'])
    }
  }
</script>

        (6) 监听路由,当参数变化的时候再次发送请求

        当我们来到搜索页面的时候,搜索完一个商品想在搜索一个商品的时候这时候地址栏参数发生了变化,此时我们需要再次向服务器发请求获取新数据。这时我们可以监听路由$router,当参数发生变化就向服务器发请求。

        同时注意一个问题:categoryId在请求完需要置空。当我们点击1Id发请求后没置空,在点2Id这时候会残留上次请求的1Id。

<script>
import SearchSelector from "./SearchSelector/SearchSelector";
import { mapGetters } from "vuex";
export default {
  name: "MySearch",
  beforeMount() {
    Object.assign(this.searchParams, this.$route.query, this.$route.params);
  },
  mounted() {
    this.getData();
  },

  components: {
    SearchSelector,
  },
  data() {
    return {
      searchParams: {
        category1Id: "",
        category2Id: "",
        category3Id: "",
        categoryName: "",
        keyword: "",
        order: "",
        pageNo: 1,
        pageSize: 10,
        props: [""],
        trademark: "",
      },
    };
  },
  methods: {
    //根据参数不同获取发请求数据
    getData() {
      this.$store.dispatch("getSelectList", this.searchParams);
    },
  },
  computed: {
    ...mapGetters(["goodsList"]),
  },
  watch: {
    $route() {
      Object.assign(this.searchParams, this.$route.query, this.$route.params);
      this.getData()
      this.searchParams.category1Id = ''
      this.searchParams.category2Id = ''
      this.searchParams.category3Id = ''
    },
  },
};
</script>

         (7) 面包屑处理分类的操作

        1. 当我们点击三级联动标签时,会动态的创建出对应的面包屑标签,这里使用v-if语句来判断,当searchParams.categoryName存在时,即显示标签

<li class="with-x" v-if="searchParams.categoryName">{{searchParams.categoryName}}<i @click="removeCategoryName">×</i></li>

        2. 点击x后,面包屑标签会消失,即把searchParams.categoryName置为空,但此时要注意需要向服务器重新发送请求,并且将Id置为空。除了面包屑消失外,还要注意地址栏的参数,x会将query参数删除,而保留params参数,这里用路由自己跳自己实现

removeCategoryName(){
      this.searchParams.categoryName = undefined
      this.getData
      this.searchParams.category1Id = undefined
      this.searchParams.category2Id = undefined
      this.searchParams.category3Id = undefined
      //点击x,删除query参数
      if(this.$route.params){
        this.$router.push({name:"search",params:this.$route.params})
      }
    }

此外还需要注意,将值置为 " " 空字符串后,但" "仍会向服务器发送数据,浪费网络资源,我们可以选择将其置为undefined,这样这个字段不会带给服务器。        

        (8) 面包屑处理关键字

        当我们再搜索框输入内容搜索数据时,也会为他动态创建一个面包屑,当然我们点击x同样可以将此标签隐藏起来,同时搜索框的数据也应该被清空,地址栏的params参数也随之消失。

1. 动态创建面包屑,对li标签使用v-if语句来判断

2. 点击x后,我们将v-if()里的值置为空即为flase

3. 搜索框的内容清空,这时我们需要用到兄弟组件间通信,search组件通知header组件,x号被点击了,搜索框内容该被清空了,这里用到了全局事件总线bus

4. 地址栏的参数需要我们使用路由自己跳自己来实现了

<script>
import SearchSelector from "./SearchSelector/SearchSelector";
import { mapGetters } from "vuex";

export default {
  name: "MySearch",
  beforeMount() {
    Object.assign(this.searchParams, this.$route.query, this.$route.params);
  },
  mounted() {
    this.getData();
  },
  components: {
    SearchSelector,
  },
  computed: {
    ...mapGetters(["goodsList"]),
  },
  data() {
    return {
      searchParams: {
        category1Id: "",
        category2Id: "",
        category3Id: "",
        categoryName: "",
        keyword: "",
        order: "",
        pageNo: 1,
        pageSize: 10,
        props: [""],
        trademark: "",
      },
    };
  },
  watch: {
    $route() {
      Object.assign(this.searchParams, this.$route.query, this.$route.params);

      this.getData();
      this.searchParams.category1Id = undefined
      this.searchParams.category2Id = undefined
      this.searchParams.category3Id = undefined
    },
  },
  methods: {
    getData() {
      this.$store.dispatch("getSearchList", this.searchParams);
    },
    removeCategoryName(){
      this.searchParams.categoryName = undefined
      this.searchParams.category1Id = undefined
      this.searchParams.category2Id = undefined
      this.searchParams.category3Id = undefined
      this.getData()
      if(this.$route.params){
        this.$router.push({name:"MySearch",params:this.$route.params})

      }
    },
    removeKeyword(){
      this.searchParams.keyword = undefined
      this.getData()
      this.$emit("clear")
      if(this.$route.query){
        this.$router.push({name:"MySearch",query:this.$route.query})
      }
    },
    trademarkInfo(trademark){
      this.searchParams.trademark = `${trademark.tmId}:${trademark.tmName}`
      this.getData()
    },
    removeTradeMark(){
      this.searchParams.trademark = undefined
      this.getData()
    }
  },
};
</script>

        (9) 面包屑处理品牌信息

        当我们点击品牌信息时要重新获取参数,再次向服务器发送请求获取数据;但是品牌信息在子组件中,而我们更新参数只能在父组件中,所以这时候又需要组件间通信了,自定义事件子组件向父组件传递数据,接收到数据后开始用v-if来创建面包屑,同时还有x号的点击事件

<script>
import SearchSelector from "./SearchSelector/SearchSelector";
import { mapGetters } from "vuex";
export default {
  name: "MySearch",
  beforeMount() {
    Object.assign(this.searchParams, this.$route.query, this.$route.params);
  },
  mounted() {
    this.getData();
  },

  components: {
    SearchSelector,
  },
  data() {
    return {
      searchParams: {
        category1Id: "",
        category2Id: "",
        category3Id: "",
        categoryName: "",
        keyword: "",
        order: "",
        pageNo: 1,
        pageSize: 10,
        props: [""],
        trademark: "",
      },
    };
  },
  methods: {
    //根据参数不同获取发请求数据
    getData() {
      this.$store.dispatch("getSelectList", this.searchParams);
    },
    removeCategoryName() {
      this.searchParams.categoryName = undefined;
      this.getData();
      this.searchParams.category1Id = undefined;
      this.searchParams.category2Id = undefined;
      this.searchParams.category3Id = undefined;
      //点击x,删除query参数
      if (this.$route.params) {
        this.$router.push({ name: "search", params: this.$route.params });
      }
    },
    removeKeyword() {
      this.searchParams.keyword = undefined;
      this.getData();
      this.$bus.$emit("clear");
      console.log(this.$route.query);
      if (this.$route.query) {
        this.$router.push({ name: "search", query: this.$route.query });
      }
      this.$router.push({ name: "search" });
    },
    trademark(tm){
      this.searchParams.trademark = `${tm.tmId}:${tm.tmName}`
      this.getData()
    },
    removeTrademark(){
      this.searchParams.trademark = undefined
      this.getData()

    },
  },
  computed: {
    ...mapGetters(["goodsList"]),
  },
  watch: {
    $route() {
      Object.assign(this.searchParams, this.$route.query, this.$route.params);
      this.getData();
      this.searchParams.category1Id = undefined;
      this.searchParams.category2Id = undefined;
      this.searchParams.category3Id = undefined;
    },
  },
};
</script>

    (10) 平台售卖属性

        每一个商品都有着自己许多的属性,当我们点击商品属性进行商品筛选,这时会再次向服务器请求数据。但是这时我们注意下,因为商品的属性数据是存储在子组件中,但是参数信息是存储在父组件中,这时我们又需要用到了自定义属性来通信,将子组件的数据传到父组件中,创建好标签,发送请求。同时我们的props参数是一个数据,我们需要将新点击数据获取到并用push方法加入到数组中,但是不能一个数据重复加入,需要用indeOf判断他是不是重复数据,点击x后,根据点击数据的索引值,用splice将数据移出数组,然后再次向服务器发请求

<script>
import SearchSelector from "./SearchSelector/SearchSelector";
import { mapGetters } from "vuex";

export default {
  name: "MySearch",
  beforeMount() {
    Object.assign(this.searchParams, this.$route.query, this.$route.params);
  },
  mounted() {
    this.getData();
  },
  components: {
    SearchSelector,
  },
  computed: {
    ...mapGetters(["goodsList"]),
  },
  data() {
    return {
      searchParams: {
        category1Id: "",
        category2Id: "",
        category3Id: "",
        categoryName: "",
        keyword: "",
        order: "",
        pageNo: 1,
        pageSize: 10,
        props: [],
        trademark: "",
      },
    };
  },
  watch: {
    $route() {
      Object.assign(this.searchParams, this.$route.query, this.$route.params);

      this.getData();
      this.searchParams.category1Id = undefined;
      this.searchParams.category2Id = undefined;
      this.searchParams.category3Id = undefined;
    },
  },
  methods: {
    getData() {
      this.$store.dispatch("getSearchList", this.searchParams);
    },
    removeCategoryName() {
      this.searchParams.categoryName = undefined;
      this.searchParams.category1Id = undefined;
      this.searchParams.category2Id = undefined;
      this.searchParams.category3Id = undefined;
      this.getData();
      if (this.$route.params) {
        this.$router.push({ name: "MySearch", params: this.$route.params });
      }
    },
    removeKeyword() {
      this.searchParams.keyword = undefined;
      this.getData();
      this.$emit("clear");
      if (this.$route.query) {
        this.$router.push({ name: "MySearch", query: this.$route.query });
      }
    },
    trademarkInfo(trademark) {
      this.searchParams.trademark = `${trademark.tmId}:${trademark.tmName}`;
      this.getData();
    },
    removeTradeMark() {
      this.searchParams.trademark = undefined;
      this.getData();
    },
    attrInfo(attr, attrValue) {
      let props = `${attr.attrId}:${attrValue}:${attr.attrName}`;
      if (this.searchParams.props.indexOf(props) == -1) {
        this.searchParams.props.push(props);
      }
      this.getData();
    },
    removeAttr(index){
      this.searchParams.props.splice(index,1)
      this.getData()
    },
  },
};
</script>

  (10) 商品排序

      商品对应着综合和价格两个排序规则,点击综合、价格会对商品进行升序降序排序的。我们只需要将参数order配置好就行,还有箭头升序降序(阿里巴巴矢量图),动态地判断当前类名是不是active,动态地为order参数赋值,再重新发请求

changeOrder(flag) {
      let originFlag = this.searchParams.order.split(":")[0]
      let originSort = this.searchParams.order.split(":")[1]

      let newOrder = ""
      if(flag == originFlag){
        newOrder = `${originFlag}:${originSort == 'desc'?"asc":"desc"}`
      }else{
        newOrder = `${flag}:${"desc"}`
      }
      this.searchParams.order = newOrder
      this.getData()
},

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值