Vue商城项目笔记一

目录

0.开发项目流程

1.创建项目

2.项目的其他配置

2.1 自动打开浏览器

2.2 eslint校验功能关闭

2.3 src文件夹别名

3.项目路由分析

4.搭建非路由组件

5.搭建路由组件

5.1 配置并搭建路由

5.2 总结

5.2.1路由组件和非路由组件的区别

5.2.2$router和$route的区别

5.2.3路由的跳转 

6.组件显示与隐藏

7.路由传参

8.Home组件拆分

8.1 全局组件

8.2 局部组件

9.axios二次封装

9.1为什么要二次封装axios?

9.2 api文件夹

10.接口统一管理

11.跨域问题

12.nprogress进度条的使用

13.vuex状态管理库

13.1 vuex是什么

13.2 vuex的使用

13.3 vuex实现模块式开发

13.4 动态展示三级联动列表数据

13.4 一级分类添加背景颜色

13.5 通过js控制二三级分类动态显示隐藏

14.防抖与节流

14.1 防抖与节流的概念

14.2 完成三级联动节流的操作

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

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

17. TypeNav商品分类列表的优化

18.swiper使用

19. 通过nextTick解决问题

20.组件通信的方式

21.封装共用组件carsouel


0.开发项目流程

1、书写静态页面

2、拆分组件

3、获取服务器的数据动态展示

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

1.创建项目

在对应目录下使用脚手架创建项目

vue create vue_shop

创建项目后,先了解对应的目录结构

Vue商城项目笔记一_第1张图片

node_modules:项目依赖文件夹

public:一般放置一些静态资源(图片等),放在public文件夹中的静态资源,webpack进行打包的时候,会原封不动打包到dist文件夹中

src源代码文件夹

        ----assets:放置静态资源(一般放置多个组件共用的静态资源),在webpack打包时,会把此文件夹中的静态资源当做一个模块打包到js文件中

        ----components:放置非路由组件(全局组件)

        ----App.vue:项目的唯一根组件,vue当中的组件都是.vue

        ----main.js:程序入口文件,也是整个程序当中最先执行的文件

.gitgnore:git忽略文件

babel.config.js:配置文件,与babel相关,可以把ES6自动翻译为ES5

package-lock.json:缓存性文件,记录了项目中的依赖包是哪里来的,方便后续的扩展和变更

package.json:项目信息,记录了项目叫什么、有哪些依赖、项目怎么运行等

README.md:说明性文件

2.项目的其他配置

2.1 自动打开浏览器

在package.json下 script属性的serve里添加 --open

"serve": "vue-cli-service serve --open"

2.2 eslint校验功能关闭

在根目录下创建vue.config.js文件,并在其中添加以下代码:

module.exports={
    // 关闭eslint
    lintOnSave:false
}

2.3 src文件夹别名

可以将src目录配置为@,之后引入的所有文件,凡是带了@的,都会到对应的src目录中找,而使用exclude将node_modules和dist排除后,vscode就不会在这两个目录里面查找文件了

在根目录下创建jsconfig.json文件并添加如下代码

{
    "compilerOptions": {
        "baseUrl": "./",
        "paths": {
            "@/*": [
                "src/*"
            ]
        }
    },
    "exclude": [
        "node_modules",
        "dist"
    ]
}

3.项目路由分析

路由组件:Home首页路由组件、Search路由组件、login登录路由、register注册路由

非路由组件:Header(所有页面都有)、Footer(首页、搜索页)

4.搭建非路由组件

在components目录下创建Header 和 Footer文件夹,Header、Footer下创建images文件夹放入图片,并创建index.vue

在App.vue中引入注册使用Header、Footer组件

5.搭建路由组件

5.1 配置并搭建路由

搭建路由组件需要vue-router,注意使用vue2需要使用3版本的vue-router,最新版本只能vue3使用

npm i vue-router@3 --save

在src目录下新建pagesrouter文件夹,用来存放所有的路由组件,pages分别建立各个路由组件的文件夹,router文件夹下的index.js用来配置路由

配置路由:

import Vue from "vue";
import VueRouter from "vue-router"
// 使用插件
Vue.use(VueRouter)
// 引入路由组件
import Home from '@/pages/Home'
import Login from '@/pages/Login'
import Register from '@/pages/Register'
import Search from '@/pages/Search'
// 配置路由
export default new VueRouter({
    routes:[
        {
            path:'/home',
            component:Home
        },
        {
            path:'/search',
            component:Search
        },
        {
            path:'/login',
            component:Login
        },
        {
            path:'/rigister',
            component:Register
        },
        // 重定向,在项目跑起来的时候,访问/,立刻定向到首页
        {
            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')

对登录、注册和logo的路由跳转使用声明式导航,如下

登录

搜索使用编程式导航,给对应的搜索按钮button绑定click事件

同时配置methods

    methods:{
        goSearch(){
            this.$router.push('/search')
        }
    }

5.2 总结

5.2.1路由组件和非路由组件的区别

1、路由组件一般放在views和pages文件夹;非路由组件放在components文件夹中

2、路由组件一般需要在router文件夹中进行注册,使用的即为组件的名字;非路由组件在使用的时候一般都是以标签的形式使用。

3、注册完路由,不管是路由组件还是非路由组件,身上都有$route和$router属性

5.2.2$router和$route的区别

$route一般用于获取路由信息路径、params、query参数等

$router一般用于编程式导航跳转路径push、replace

5.2.3路由的跳转 

路由的跳转分为——

声明式导航

编程式导航push|replace

6.组件显示与隐藏

通过v-show指令,结合路由元信息里面的meta值来控制footer组件的显示与隐藏,设置如下:

    routes:[
        {
            path:'/home',
            component:Home,
            meta:{show:true}
        },

7.路由传参

路由传参,参数的写法:

params:属于路径当中的一部分,在配置路由时需要占位

query:不属于路径当中的一部分,不需要占位

8.Home组件拆分

8.1 全局组件

由于home中的三级联动组件在Home、Search、Detail中都有,所以注册为全局组件(只注册一次,就可以在项目任意地方使用)

在入口文件main.js中引入并注册全局组件

// 三级联动组件--全局组件
import TypeNav from '@/pages/Home/TypeNav'
// 第一个参数:全局组件的名字 第二个参数:哪一个组件
Vue.component(TypeNav.name,TypeNav)

然后就可以在Home目录下的index.vue直接使用,不需要再引入

8.2 局部组件

  1. 在Home文件夹下创建Recommend,Rank,ListContainer,Like,Floor,Brand文件夹(index.vue&images)
  2. 在Home下的index.vue下引入|注册|使用局部组件

9.axios二次封装

9.1为什么要二次封装axios?

主要的目的是处理请求拦截器和响应拦截器。

请求拦截器:在发请求之前可以处理一些业务

响应拦截器:服务器数据返回以后可以处理一些事情

9.2 api文件夹

src目录下新建api文件夹,一般用来放axios请求,新建文件request.js

axios可参考git|npm关于axios的文档

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

// 1、利用axios对象的方法create,去创建一个axios实例
// 2、requests就是axios,只不过稍微配置一下
const requests = axios.create({
    // 基础路径,发请求的时候,路径当中会出现api
    baseURL:'/api',
    // 请求超时的时间
    timeout:5000
});

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

// 响应拦截器:
requests.interceptors.response.use((res)=>{
    // 成功的回调函数:服务器相应数据回来以后,响应拦截器可以监测到,可以做一些事情
    return res.data;
},(error)=>{
    // 响应失败的回调函数
    return Promise.reject(new Error('faile'));
})

export default requests

10.接口统一管理

项目很小:完全可以在组件的生命周期函数中发请求;

项目很大:axios.get('xxx')

api目录下新建index.js文件,对API进行统一的管理

11.跨域问题

什么是跨域: 协议、域名、端口号不同的请求称为跨域

跨域解决方案:JSONP、CORS、代理

跨域代理配置:

vue.config.js文件中配置如下内容:

devServer: {
    proxy: {
      '/api': {
        target: 'http://39.98.123.211',
        // pathRewrite: {'^/api' : ''}  //由于本项目中的接口都有/api,所以无需重写
      }
    }
  }

12.nprogress进度条的使用

nprogress进度条:浏览器顶部的进度条,每当浏览器向服务器发送请求时,进度条就会往前走

安装插件:

npm install --save nprogress

在目录src/api/request.js文件中引入进度条和样式

请求拦截器中添加nprogress.start(),进度条开始

响应拦截器中的成功回调中添加nprogress.done(),进度条结束

可通过修改nprogress.css来更改进度条颜色

// 对axios进行二次封装
import axios from 'axios'
// 引入进度条
import nprogress from 'nprogress'
// 引入进度条样式
import 'nprogress/nprogress.css'


// 1、利用axios对象的方法create,去创建一个axios实例
// 2、requests就是axios,只不过稍微配置一下
const requests = axios.create({
    // 基础路径,发请求的时候,路径当中会出现api
    baseURL:'/api',
    // 请求超时的时间
    timeout:5000
});

// 请求拦截器:在发请求之前,请求拦截器可以监测到,可以在请求发出去之前做一些事情
requests.interceptors.request.use((config)=>{
    // config:配置对象,对象里面有一个属性很重要——header 请求头
    // 进度条开始
    nprogress.start()
    return config;
});

// 响应拦截器:
requests.interceptors.response.use((res)=>{
    // 成功的回调函数:服务器相应数据回来以后,响应拦截器可以监测到,可以做一些事情
    // 进度条结束
    nprogress.done()
    return res.data;
},(error)=>{
    // 响应失败的回调函数
    return Promise.reject(new Error('faile'));
})

export default requests

13.vuex状态管理库

13.1 vuex是什么

vuex是官方提供的一个插件,是一个状态管理库,集中式管理项目中组件共用的数据。

vuex主要是用于项目大、组件多的情况,可以集中式管理数据

并不是所有项目都需要vuex,项目小的话,完全可以不用vuex

13.2 vuex的使用

安装vuex,这里安装的是3版本,直接安装最新版本会报错

npm i --save vuex@3

在src目录下新建store文件夹,并新建文件index.js

import Vue from "vue"
import Vuex from "vuex"
// 需要使用插件一次
Vue.use(Vuex)
// 对外暴露store类的一个实例

// state:仓库存储数据的地方
const state = {}
// 修改state的唯一手段
const mutations = {}
// 处理action,可以书写自己的业务逻辑,也可以处理异步
const actions = {}
// getters 理解为计算属性,用于简化仓库数据,让组件获取仓库的数据更加方便 
const getters = {}

export default new Vuex.Store({
    state,
    mutations,
    actions,
    getters
})

在入口文件main.js引入并注册仓库store

13.3 vuex实现模块式开发

13.2中使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

在Vuex中可以将store 分割成模块(module)来解决上面的问题

在store目录下新建home、search文件夹,并分别在文件夹下新建index.js文件

在store/index.js里引入

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({
    // 实现Vuex仓库模块式开发存储数据
    modules:{
        home,
        search
    }
})

13.4 动态展示三级联动列表数据

store/home/index.js中向服务器发送请求并存储数据

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

export default{
    state,
    mutations,
    actions,
    getters
}

 在components/TypeNav.vue中获取home的数据并展示

 下面是html的结构,注意由于请求服务器得到的数组长度为17,可以用categoryList.slice(0,16)来截取一下

13.4 一级分类添加背景颜色

有两种方法:

1、采用样式完成:给类为item的div添加伪类样式hover,并设置背景颜色为skyblue

.item:hover {
    background-color: skyblue;
}

2、通过js完成

鼠标移入事件:

首先在data下设置一个currentIndex值,初始值为-1,表示鼠标没有移入的状态

    data(){
        return{
            currentIndex:-1
        }
    }

 添加鼠标移入事件,并传入index值

{{c1.categoryName}}

 在method中的事件中接收index值,并使currentIndex值等于index

    methods: {
        // 鼠标进入修改响应式数据currentIndex属性
        changeIndex(index){
            this.currentIndex = index
        }
    },

 给一级分类添加:class="{cur: currentIndex === index}当鼠标移入的下标index与currentIndex相等时,给对应的标签添加一个background:skyblue的类

鼠标移出事件:

  methods: {
    // 鼠标进入修改响应式数据currentIndex属性
    changeIndex(index) {
      this.currentIndex = index;
    },
    // 一级分类鼠标移出,修改样式
    leaveIndex() {
      this.currentIndex = -1;
    }
  }
};

给类为all 和 sort的div外层添加一个div(注意:不需要将div:nav 也加到这个div里面),这里的原理是用到了事件委派 

13.5 通过js控制二三级分类动态显示隐藏

原代码是使用css的hover来控制的,更改的话只需要给对应的类添加如下代码:

给类为item-list的div添加一个动态样式——当对应的currentIndex值与当前的下标值相同时显示display:block  否则display:none

14.防抖与节流

14.1 防抖与节流的概念

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

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

14.2 完成三级联动节流的操作

lodash插件:里面封装了防抖与节流的业务(闭包+延迟器)

// import _ from 'lodash'  全局引入
import throttle from 'lodash/throttle'  //按需引入,性能更好

//将methods里的事件改为,每50毫秒最多触发一次
    changeIndex:throttle(function(index){
      this.currentIndex = index;
    },50),

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

三级联动用户可以点击的有:一级分类、二级分类、三级分类,当点击的时候,Home模块跳转到Search模块,一级会把用户选中的产品(产品的名字、ID)在路由跳转时进行传递

使用声明式导航router-link直接添加给a标签时,虽然可以实现路由的跳转与传参,但是会出现卡顿现象

router-link是一个组件,当服务器的数据返回之后,会循环出很多的router-link组件(创建组件实例,并将虚拟dom转换成真实dom),在创建组件实例的时候,会很耗内存

解决方法:使用编程式导航事件委派,给父级添加click事件

使用事件委派时需要解决的问题:

  • all-sort-list2的div子节点有很多种:h3、a、dl、dd、em都是它的子节点,要确定点击的是a标签时,才会进行路由跳转
  • 确定点击的是a标签后,要区分是一级、二级还是三级分类的标签

 解决方法:给a标签添加自定义属性data-categoryName(确定点击的是a标签)和data-category1Id(确定点击的是一级、二级还是三级分类),例如

:data-categoryName="c1.categoryName" :data-category1Id="c1.categoryId">{{c1.categoryName}}

通过节点的属性dataset来获取节点的自定义属性与属性值

以下是goSearch事件的代码:

    goSearch(event) {
      let element = event.target;
      let {
        categoryname,
        category1id,
        category2Id,
        category3id
      } = element.dataset;
      // 如果标签身上有categoryname一定是a标签
      if (categoryname) {
        // 整理路由跳转的参数
        let location = { name:"search" }
        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)

        
      }
    }

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

在search模块,需要添加全局组件TypeNav的1级菜单的显示与隐藏效果

给对应的div添加一个v-show=‘show’,默认情况下让show为true,在mounted里添加判断

  mounted() {
    // 通知vuex发请求,获取数据,存储于仓库当中
    this.$store.dispatch("categoryList")
    // 当组件挂载完毕,让show属性变为false
    // 如果不是Home路由组件,将TypeNav进行隐藏
    if(this.$route.path != '/home') {
       this.show = false
    }
  },

给父级添加鼠标移入移出事件

  methods: {
    // 鼠标进入修改响应式数据currentIndex属性
    changeIndex: throttle(function(index) {
      this.currentIndex = index;
    }, 50),
    // 一级分类鼠标移出,修改样式
    leaveIndex() {
      this.currentIndex = -1;
      if (this.$route.path != "/home") {
        this.show = false;
      }
    },
    goSearch(event) {
      let element = event.target;
      let {
        categoryname,
        category1id,
        category2Id,
        category3id
      } = element.dataset;
      // 如果标签身上有categoryname一定是a标签
      if (categoryname) {
        // 整理路由跳转的参数
        let location = { name: "search" };
        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);
      }
    },
    // 当鼠标移入的时候,让商品分裂列表进行展示
    enterShow() {
      this.show = true;
    }
  }

加入过渡动画的前提是组件、元素要有v-show或v-if指令才可以进行过渡动画

需要给过渡动画对应的节点添加一个包裹 ,并添加属性name。对应的动画分为加载前、加载后,下面为代码:


          
          
...
    // 过渡动画的样式
    .sort-enter{
      height: 0;
    }
    // 过渡动画结束状态
    .sort-enter-to{
      height: 475px;
    }
    .sort-enter-active{
      transition: all .5s linear;
    }

17. TypeNav商品分类列表的优化

之前将服务器数据请求放在TypeNav组件中,每次切换页面都会发请求

现在将请求放在App.vue里,让请求只执行一次

App.vue里面的代码:

    mounted() {
    // 通知vuex发请求,获取数据,存储于仓库当中
    this.$store.dispatch("categoryList");
    },

18.swiper使用

安装Swiper插件,选择Swiper5版本

npm install --save swiper@5

在main.js中引入Swiper

import 'swiper/css/swiper.css'

19. 通过nextTick解决问题

watch:{
    // 监听bannerList数据的变化——由空数组变为数组里面有4个元素
    bannerList:{
      handler(newVal,oldVal){
        // 现在咱们通过watch监听bannerList属性的属性值的变化
        // 如果执行handler方法,代表组件实例身上这个属性的属性值已经有了(4个元素的数组)
        // 当前这个函数执行:只能保证bannerList数据已经有了,但是你没办法保证v-for已经执行结束了
        // v-for执行完毕,才有结构,你现在在watch中没办法保证的
        // nextTick:在下次DOM更新循环结束之后执行延迟回调,在修改数据之后立即使用这个方法,获取更新后的dom
        // 即:bannerList从空数组变成4个元素的数组后(bannerLinst的数据发生了修改),且v-for循环完成后,会执行nextTick里面的回调函数,确保了mySwiper实例是在页面存在结构后才生成的,保证了swiper的功能正常使用。
        this.$nextTick(function(){
          var mySwiper = new Swiper ('.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',
          },
        });
        })
      }
    }
  }

$nextTick可以保证页面中的结构一定是存在的,经常和很多的插件一起使用【很多插件都需要DOM存在】

20.组件通信的方式

props:用于父子组件通信

自定义事件:$on $emit 可以实现子给父通信

全局事件总线:$bus 全能

pubsub-js :vue中几乎不用 react用的比较多

插槽:3种

vuex

Floor组件的Swiper实例可以直接在mounted中引入,原因如下:

第一次书写Swiper的时候:在mounted当中书写是不可以的,但是为什么现在这里可以啦!
第一次书写轮播图的时候,是在当前组件内部发请求、动态渲染解构【前台至少服务器数据需要回来】

现在的这种写法为什么可以:因为请求是父组件发的,父组件通过props传递过来的,而且结构都已经有了的情况下执行mounted

21.封装共用组件carsouel

carsouel轮播图在项目中多次使用,所以可以拆分组件

在components下新建carsouel文件夹和index.vue文件,代码如下




全局组件在入口文件main.js中注册,在Floor和ListContainer中使用组件并传值,如下

你可能感兴趣的:(项目笔记,vue.js,javascript,webpack)