天天看点

尚硅谷--尚品汇项目笔记项目核心

文章目录

  • 项目核心
    • 一、项目准备
    • 二、脚手架目录作用
    • 三、项目的其他配置
    • 四、路由的分析
      • 1、路由组件的搭建
      • 2、配置路由
      • 3、路由组件和非路由组件区别:
      • 4、路由的跳转
      • 5、路由传参
      • 6、路由传递参数相关面试题
      • 7、路由传参方式
      • 8、NavigationDuplicated的警告错误
    • 五、注册全局组件
    • 六、关于axios
      • 1、axios的二次封装
      • 2、通过代理解决跨域问题
      • 3、请求接口统一封装
      • 4、nprogress进度条插件
    • 七、vuex 状态管理库
      • 1、什么是vuex
      • 2、vuex的使用
      • 3、vuex的模块化使用
    • 八、函数的防抖与节流
      • 1、函数防抖
      • 2、函数节流
      • 3、将changeIndex设置了节流
    • 九、联动路由跳转
      • 1、router-link:声明式导航
      • 2、编程式导航
      • 3、最优方案:编程式导航+事件委派
    • 十、模拟数据-mock插件
      • 1、安装mockjs
      • 2、使用步骤
    • 十一、vuex数据具体使用方法
    • 十二、swiper插件
      • 1、无法加载轮播图片的问题
      • 2、将swiper封装组件
    • 十三、getters
    • 十四、ES6新增:Object.asign
    • 十五、面包屑相关内容
      • 1、删除 query --- 分类名
      • 2、删除 params ---关键字
        • 1、关键点:设计组件通
        • 2、$bus 使用
    • 十六、封装分页器
    • 十七、[滚动行为](https://router.vuejs.org/zh/guide/advanced/scroll-behavior.html)
    • 十八、浏览器存储功能
    • 十九、购物车相关知识
      • 问题一:cartList为空
      • 问题二:如何传uuid_token
      • 1、判断底部勾选框是否勾选
      • 2、修改购物车数量
    • 二十、登录业务
      • 1、注册
      • 2、登录
    • 二十一、[导航守卫](https://router.vuejs.org/zh/guide/advanced/navigation-guards.html)
      • 1、全局守卫
      • 2、路由独享守卫
      • 3、组件内的守卫
    • 二十二、表单验证
    • 二十三、路由懒加载
    • 二十四、项目打包上线

项目核心

  • 书写静态页面(HTML + CSS)
  • 拆分组件
  • 获取服务器的数据动态展示
  • 完成相应的动态业务逻辑

一、项目准备

1、vue-cli 脚手架初始化项目

2、node +webpack+淘宝镜像

二、脚手架目录作用

1、node_modules — 放置项目依赖文件夹

2、public — 一般放置一些共用的静态资源(图片),打包上线的时候,public文件夹里面资源原封不动打包到dist文件夹里面

3、src — 程序员源代码文件夹

  • assets文件夹 — 经常放置一些静态资源(多个组件公用的静态资源),assets文件夹里面资源webpack会进行打包为一个模块(js文件夹里面)
  • components — 一般放置非路由组件(全局组件)
  • api — axios 相关文件
  • views | pages — 放置路由组件
  • router — 配置路由的文件夹
  • App.vue — 唯一的根组件
  • main.js — 入口文件【程序最先执行的文件】
  • babel.config.js — babel配置文件
  • package.json — 看到项目描述、项目依赖、项目运行指令
  • package-lock.json — 缓存性文件
  • README.md — 项目说明文件

三、项目的其他配置

1、项目运行,浏览器自动打开

<!-- package.json文件夹 -->
    "scripts": {
    "serve": "vue-cli-service serve --open",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
    },
           

2、关闭eslint校验工具

  • 根目录下创建vue.config.js,进行配置
module.exports = {
   lintOnSave:false,
}
           

3、src文件夹配置别名,创建jsconfig.json,用@/代替src/,exclude表示不可以使用该别名的文件

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

四、路由的分析

vue-router

前端路由:kv键值对

Key — 即为URL(地址栏中的路径)

Value — 即为相应的路由组件

1、路由组件的搭建

  • components — 一般放置非路由组件(全局组件)
  • views | pages — 放置路由组件

2、配置路由

放置在 router 文件夹中

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

  • 非路由组件放在 components 中,路由组件放在 pages 或 views 中
  • 非路由组件使用时以标签的形式使用,路由组件需要在 router 文件中进行注册(使用的是组件的文字)通过路由使用
  • 注册完路由,所有的路由和非路由组件身上都会拥有$router $route属性

    $router:一般进行编程式导航进行路由跳转【 push | replace】

    $route: 一般获取路由信息【query、path、params等】

4、路由的跳转

  • 声明式导航 — < router-link > ,(务必要有to属性)
  • 编程式导航 —< push | replace >
    注:声明式导航能做的编程式都能做,而且还可以处理一些业务
               

5、路由传参

  • params参数:路由需要占位,程序就崩了,属于URL当中一部分
    参数对应的路由信息要修改为

    path: "/search/:id"

    这里的

    /:id

    就是一个params参数的占位符
  • query参数:路由不需要占位,写法类似于ajax当中query参数
    书写形式:

    path: "/search?k=v"

6、路由传递参数相关面试题

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

    ---- 不可以,程序会崩掉

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

    — 解决方法:在占位后面加 ? =>

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

    — 解决方法:用 undefined =>

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

    — 可以,但是只能传递params参数,具体知识为props属性

7、路由传参方式

  • 字符串形式
  • 模板字符串
  • 对象(常用)

8、NavigationDuplicated的警告错误

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

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

    1、是给push 方法,传入相应的成功的回调与失败的回调,可以捕捉当前错误,代码如下

// 这种写法治标不治本,将来在别的组件中push|replace,编程式导航还是会有类似错误
this.$router.push({name:‘Search’,params:{keyword}},()=>{},()=>{})
           

2、重写push 方法(二次封装),代码如下

//1、先把VueRouter原型对象的push,保存一份
let originPush = VueRouter.prototype.push;
//2、重写push|replace
//第一个参数:告诉原来的push,跳转的目标位置和传递了哪些参数
VueRouter.prototype.push = function (location,resolve,reject){
    if(resolve && reject){
        originPush.call(this,location,resolve,reject)
    }else{
        originPush.call(this,location,() => {},() => {})
    }
}
           

五、注册全局组件

// mian.js 中书写
//将三级联动组件注册为全局组件
import TypeNav from '@/pages/Home/TypeNav';
//第一个参数:全局组件名字,第二个参数:全局组件
Vue.component(TypeNav.name,TypeNav);
//在页面中使用时: 直接通过 <TypeNav/> 使用,不需要引入

           

六、关于axios

1、axios的二次封装

  • 为什么需要进行二次封装axios?
    • 请求拦截器:可以在发请求之前,处理一些业务
    • 响应拦截器:当服务器数据返回后,可以处理一些事情
//在根目录下创建api文件夹,创建request.js文件
import axios from "axios";
//1、对axios二次封装
const requests = axios.create({
    //基础路径,requests发出的请求在端口号后面会跟改baseURl
    baseURL:'/api',
    timeout: 5000,
})
//2、配置请求拦截器
requests.interceptors.request.use(config => {
    //config内主要是对请求头Header配置
    return config;
})
//3、配置相应拦截器
requests.interceptors.response.use((res) => {
    //成功的回调函数
    return  res.data;
},(error) => {
    //失败的回调函数
    console.log("响应失败"+error)
    return Promise.reject(new Error('fail'))
})
//4、对外暴露
export default requests;
           

2、通过代理解决跨域问题

module.exports = {
        //代理服务器解决跨域
        proxy: {
            //会把请求路径中的/api换为后面的代理服务器
            '/api': {
                //提供数据的服务器地址
                // 时间2022/10/23,服务器地址为http://gmall-h5-api.atguigu.cn
                target: 'http://gmall-h5-api.atguigu.cn',
            }
        },
    }
           

3、请求接口统一封装

  • 项目过大时,推荐使用。如果项目小,在生命周期函数中发请求即可
  • 将每个请求封装为一个函数,并暴露出去,组件只需要调用相应函数即可,这样当我们的接口比较多时,如果需要修改只需要修改该文件即可
//在文件夹api中创建index.js文件,用于封装所有请求
import requests from "@/api/request";
//首页三级分类接口
export const reqCateGoryList = () => {
    return  requests({
        url: '/product/getBaseCategoryList',
        method: 'GET'
    })
}

           

4、nprogress进度条插件

//安装nprogress进度条插件
cnpm install--save nprogress

           

相关配置

  • start:进度条开始 done 进度条结束
  • 进度条的颜色可以修改,修改源码样式.bar类名的
//引入进度条
import nprogress from 'nprogress';
//引入进度条样式
import "nprogress/nprogress.css";
requests.interceptors.request.use(config => {
    //开启进度条
    nprogress.start();
    return config;
})
requests.interceptors.response.use((res) => {
    //响应成功,关闭进度条
    nprogress.done()
},(error) => {
})

           

七、vuex 状态管理库

并不是所有项目都需要vuex,如果项目小,完全不需要使用vuex,当项目很大、组件很多、数据维护费劲时使用

1、什么是vuex

集中式管理项目中的组件公用的数据

  • Vuex核心概念:state、actions、mutations、getters、modules

2、vuex的使用

  • 首先确保安装了vuex,根目录创建store文件夹,文件夹下创建index.js,内容如下:
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

//对外暴露store的一个实例
export default new Vuex.Store({
    //state:仓库存储数据的地方,公共状态
    state:{},
    //mutations:修改state的唯一手段,只支持同步
    mutations:{},
    //actions:处理action,书写自己的业务逻辑,提交mutation,但是不能修改state支持同步异步
    actions:{},
    //getters:理解为计算属性,用于简化仓库数据,让组件获取仓库的数据更加方便(一般不使用)
    getters:{},
})


           

3、vuex的模块化使用

采用vuex模块式管理数据的原因:

  • 项目过大、组件过多、接口过多、返回数据多时,如果还用以前的方式存储数据,导致vuex中的state数据格式比较复杂。

1、建立小仓库 =>在store文件下 home.js,

// home 模块的小仓库
//home 相关逻辑在此页面进行书写
const state = {}
const getters = {}
const mutations = {}
const actions = {}
const modules = {}
export default {
  state,
  getters,
  mutations,
  actions,
  modules
}

           

2、在大仓库中引入小仓库 =>在store文件下 index.js,

import Vue from 'vue'
import Vuex from 'vuex'
// 引入小仓库
import home from './home'
Vue.use(Vuex)
export default new Vuex.Store({
  // 实现vuex 仓库模块式开发存储数据
  modules: {
    home
  }
})

           

八、函数的防抖与节流

  • 出现卡顿现象原因:事件触发非常频繁,而且每一次的触发,回调函数都要去执行(如果时间很短,而回调函数内部有计算,那么很可能出现浏览器卡顿
  • 此时我们可以采用debounce(防抖)和throttle(节流)的方式来减少调用频率,同时又不影响实际效果
  • 安装lodash插件,该插件提供了防抖和节流的函数【闭包 + 延迟器】,我们可以引入js文件,直接调用。向外暴露 _ 函数

    ladash官网

1、函数防抖

  • 前面的所有的触发都被取消,最后一次执行在规定的时间之后才会触发,也就是说如果连续快速的触发,只会执行最后一次
    用户操作很频繁,但是只是执行一次
               
    ladash官网–防抖用法

2、函数节流

  • 在规定的间隔时间范围内不会重复触发回调,只有大于这个时间间隔才会触发回调,把频繁触发变为少量触发
    用户操作很频繁,但是把频繁的操作变为少量操作【给浏览器充裕的时间解析代码】
               
    ladash官网–节流用法

3、将changeIndex设置了节流

// 全部引入 =>import _ from 'lodash'=>使用时 _.throttle
// 最好按需加载
import throttle from 'lodash/throttle'
 methods: {
    //采用键值对形式创建函数,将changeIndex定义为节流函数,该函数触发很频繁时,设置50ms才会执行一次
    // throttle回调函数别用箭头函数,防止出现this问题
    changeIndex: throttle(function (index){
      this.currentIndex = index
    },50),
  }


           

九、联动路由跳转

1、router-link:声明式导航

  • 缺点:由于生成的标签过多,出现卡顿现象
  • 出现卡顿的原因:

    — router-link是一个组件:相当于VueComponent类的实例对象,一瞬间new VueComponent很多实例(1000+),很消耗内存,因此导致卡顿。

2、编程式导航

我们是通过触发点击事件实现路由跳转。同理有多少个a标签就会有多少个触发函数。虽然不会出现卡顿,但是也会影响性能。

3、最优方案:编程式导航+事件委派

事件委派即把子节点的触发事件都委托给父节点
           
  • 事件委派问题:

1、如何确定我们点击的一定是a标签呢?如何保证我们只能通过点击a标签才跳转呢?

  • 解决办法: 把子节点当中

    a

    标签,加上自定义属性

    :data-categoryName

    ,其余的子节点没有

​2、如何区分一、二、三级的标签

  • 解决办法:
    • 为三个等级的a标签再添加自定义属性

      data-category1Id

      :data-category2Id

      :data-category3Id

      来获取三 个等级a标签的商品id,用于路由跳转。
    • 我们可以通过在函数中传入event参数,通过

      event.target

      属性获取当前点击节点,再通过dataset属性获取节点的属性信息

十、模拟数据-mock插件

mock官网

  • 生成随机数据,拦截 Ajax 请求,返回我们自定义的数据用于测试前端接口
  • 前端mock数据不会和你的服务器进行任何通信

1、安装mockjs

cnpm install mockjs

2、使用步骤

  1. 在项目当中src文件夹中创建mock文件夹
  2. 设计JSON 数据结构(在mock文件中创建相应JSON文件)

    注意:JSON文件需要格式化一下,不能留有空格,否则跑不起来

  3. 将所需图片放置在public文件夹中
    • public文件夹在打包时,会把相应的资源打包dist文件夹中
  4. 创建mockServe.js,通过mockjs插件实现模拟数据
    import Mock  from 'mockjs'
    //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})
               
  5. mockServe.js文件在入口文件中引入(至少需要执行一次,才能模拟数据)
    //在main.js中引入
    import ''@/mock/mockServe
               
  6. 在API文件夹中创建mockRequest.js
    • 专门请求mock接口的axios封装

十一、vuex数据具体使用方法

把公共数据、获取数据方法都统一放在store中,取其数据的固定步骤如下:(轮播)
           

1、派发action:通过vuex 发起Ajax请求,将数据储存在仓库中

//ListContainer.vue 文件
 mounted() {
    this.$store.dispatch("getBannerList")
  },
           

2、在store文件中的home.js中,书写相关逻辑,并提交

mutations

actions:{
        //获取首页轮播图数据
        async getBannerList({commit}){
            let result = await reqGetBannerList()
            if(result.code ===  200){
                commit("BANNERLIST",result.data)
            }
        }
    }
           

3、获取到数据后,在

mutations

中修改

state

mutations:{
        BANNERLIST(state,bannerList){
            state.bannerList = bannerList
        }
    },
           

4、在state中,添加

bannerList

状态

state:{
   bannerList:[]
}
           

5、在ListContainer.vue组件在store中获取轮播图数据。

import {mapState} from "vuex";
export default {
  computed: {
    ...mapState({
      bannerList: state => state.home.bannerList
    })
   },
  }
           

十二、swiper插件

swiper官网

swiper使用方法

1、无法加载轮播图片的问题

原因:在mounted中,先异步请求轮播图数据,然后创建swiper实例。由于请求数据是异步的,所以浏览器不会等待该请求执行完再去创建swiper,而是先创建了swiper实例,但是此时我们的轮播图数据还没有获得,就导致了轮播图展示失败。

解决方法:

1、添加延迟器(不是完美的解决方案)

mounted() {
    setTimeout(()=>{
      let mySwiper = new Swiper(...)
    },2000)
  },
           

缺点:无法确定用户请求到底需要多长时间,因此没办法确定延迟器时间

2、watch + nextTick

this. $nextTick

:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM

<template>
   <!--banner轮播-->
     <div class="swiper-container" ref="mySwiper">
       <div class="swiper-wrapper">
         <div class="swiper-slide" v-for="(carouse,index) in bannerList" :key="carouse.id">
           <img :src="carouse.imgUrl" />
         </div>
       </div>
       <!-- 如果需要分页器 -->
       <div class="swiper-pagination"></div>
       <!-- 如果需要导航按钮 -->
       <div class="swiper-button-prev" ></div>
       <div class="swiper-button-next"></div>
     </div>
   </div>
</template>
<script>
// 引入Swiper
import Swiper from 'swiper'
// 引入Swiper样式
import 'swiper/css/swiper.css'
import {mapState} from "vuex";
export default {
  mounted() {
    // ajax请求轮播图图片
    this.$store.dispatch("getBannerList")
  },
  computed:{
    ...mapState({
      // 从仓库中获取轮播图数据
      bannerList: (state) => {return state.home.bannerList}
    })
  },
  watch:{
    bannerList(newValue,oldValue){
        // this.$nextTick()使用
        this.$nextTick(()=>{
          let mySwiper = new Swiper(this.$refs.mySwiper,{
            pagination:{
              el: '.swiper-pagination',
              clickable: true,
            },
            // 如果需要前进后退按钮
            navigation: {
              nextEl: '.swiper-button-next',
              prevEl: '.swiper-button-prev',
            }
          })
        })
    }
  }
}
</script>
           

2、将swiper封装组件

由于 floor.vue 中,数据是由父组件传递过来的,且从来没有发生改变,因此监听不到list。所以我们需要借助

immediate

immediate

:立即监听,不管数据是否变化,都监听一次

<template>
  <div>
    <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>
  </div>
</template>
<script>
import Swiper from 'swiper'
export default {
  name: 'Carousel',
  props: ['list'],
  watch: {
    list: {
      // 立即监听:不管数据是否变化,都监听一次
      immediate: true,
      handler () {
        this.$nextTick(() => {
          // eslint-disable-next-line no-unused-vars
          const mySwiper = new Swiper(this.$refs.cur, {
            loop: true,
            // 如果需要分页器
            pagination: {
              el: '.swiper-pagination',
              clickable: true
            },
            // 如果需要前进后退按钮
            navigation: {
              nextEl: '.swiper-button-next',
              prevEl: '.swiper-button-prev'
            }
          })
        })
      }
    }
  }
}
</script>
<style lang="less" scoped>
</style>
           

十三、getters

  • 作用:简化仓库中的数据(相当于计算属性)
  • mapGetters

    里面的写法:数组,因为

    getters

    计算是没有划分模块的(

    state

    :划分home,search)

十四、ES6新增:Object.asign

Object.assign()

方法将所有可枚举的自有属性从一个或多个源对象复制到目标对象,返回修改后的对象。

  • 实现对象拷贝,合并对象
    // 举例
    const obj1 = {
      a:1,
      b:2
    };
    const obj2 = Object.assign({b:3, c:4}, obj1);
    console.log(object1)  // { a: 1, b: 2}
    console.log(object2)  // { b: 2 c: 4, a: 1}
    // 如果目标对象中的属性具有相同的键,则属性将被源对象中的属性覆盖。后面的源对象的属性将类似地覆盖前面的源对象的属性
    
               

十五、面包屑相关内容

1、删除 query — 分类名

  • 方法:删除时,将属性值赋值成

    undefined

    ,并且进行路由跳转(自己跳自己)
    removeCategoryName () {
          // 赋值成undefined(不消耗宽带)
          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: 'search', params: this.$route.params })
          }
        },
               

2、删除 params —关键字

  • 方法:删除时,将keyword赋值成

    undefined

    ,然后修改路由信息,同时删除兄弟组件Header中输入框内的关键字

1、关键点:设计组件通

  • props:父子
  • 自定义事件:子父
  • vuex:万能
  • 插槽:父子
  • pubsub-js:完成
  • $bus:全局事件总线(本案例运用此方法)

2、$bus 使用

  • 在main.js中配置

    $bus

    new Vue({
      //全局事件总线$bus配置
      beforeCreate() {
        //此处的this就是这个new Vue()对象
        Vue.prototype.$bus = this
      },
    }).$mount('#app')
               
  • 在Search.vue中触发$bus
    • $emit ('事件名',data)

      removeKeyword () {
           // 参数赋空
            this.searchParams.keyword = undefined
            this.getData()
            // 通知兄弟组件清除关键字
            // data用于传递数据,此页面只是用于通知header组件进行相应操作,故不传data
            this.$bus.$emit('clear')
            // 进行路由跳转
            this.$router.push({ name: 'search', query: this.$route.query })
      }
                 
  • 在Header.vue 中监听$bus
    • $on('事件名',( )=>{ })

      mounted () {
          // 通过全局总线清除关键字
          this.$bus.$on('clear', () => {
            this.keyword = ''
          })
        }
                 

十六、封装分页器

  • 分页器需要的数据(条件)
    1. pageNo :当前页数
    2. pageSize :每页展示几条数据
    3. total :总共返回多少条数据
    4. continues:连续页码数【5|7】
  • 重点:计算出连续页面起始数字和结束数字
    // 连续页数的起始页数和结束页数
    startNumAndEndNum () {
      const { continues, pageNo, totalPage } = this
      let start = 0
      let end = 0
      // 判断总页数是否等于连续页(5),
      // 不等于
      if (continues > totalPage) {
        start = 1
        end = totalPage
      } else {
        // 等于
        start = pageNo - parseInt(continues / 2)
        end = pageNo + parseInt(continues / 2)
        // start= 0/负数
        if (start < 1) {
          start = 1
          end = continues
        }
        // end>总页码
        if (end > totalPage) {
          start = totalPage - continues + 1
          end = totalPage
        }
      }
      return { start, end }
    }
               

十七、滚动行为

  • 当切换到新路由时,滚动条的位置,vue-router就可以做到。
  • vue-route 提供了

    scrollBehavior

    方法(只在支持

    history.pushState

    的浏览器中可用)
    scrollBehavior (to, from, savedPosition) {
        // return 期望滚动到哪个的位置
        return { y: 0 }
      }
               

十八、浏览器存储功能

HTML5中新增,本地存储和会话存储。

  • 本地存储–localStorage:持久化的(5M)
  • 会话存储–sessionStorage:并非持久–会话结束(关闭浏览器)就消失、

十九、购物车相关知识

问题一:cartList为空

  • 发现:发请求时,获取不得购物车里面的数据

    =>原因:因为服务器不知道你是谁

    =>解决方案:生成uuid(临时游客身份)

    • 由于每个用户的uuid不能发生变化,还要持久存储,所以封装游客身份模块uuid—生成一个随机字符串
    • 创建utils文件夹=>创建uuid_token.js文件
      // 我用 uuid_token 会有红杠,提示没有用驼峰写法,因此加上了下面注释
      /* eslint-disable camelcase */
      
      import { v4 as uuidv4 } from 'uuid'
      // 生成一个随机字符串,且每次执行都不能发生变化,还要持久存储
      export const getUUID = () => {
        // 判断本地存储是否由uuid
        let uuid_token = localStorage.getItem('UUIDTOKEN')
        // 本地存储没有uuid
        if (!uuid_token) {
          // 生成uuid
          uuid_token = uuidv4()
          // 存储本地
          localStorage.setItem('UUIDTOKEN', uuid_token)
        }
        // 当用户有uuid时就不会再生成
        return uuid_token
      }
                 

问题二:如何传uuid_token

  • 购物车的请求函数中没有参数,无法传 uuid_token

    =>解决方法:把uuid_token加在请求头中

    • 在store中的detail模块中定义uuid_token
      const state =  {
          //游客身份
          uuid_token: getUUID()
      }
                 
    • 在request.js中设置请求头
      // 引入store
      import store from '@/store';
      requests.interceptors.request.use(config => {
       // 判断uuid_token是否为空
       if (store.state.detail.uuid_token) {
          // 请求头添加一个字段:userTempId字段和后端统一
          config.headers.userTempId = store.state.detail.uuid_token
        }
          return config;
      })
                 

1、判断底部勾选框是否勾选

=>解决方法:every

//判断底部勾选框是否全部勾选
isAllCheck() {
   //every遍历某个数组,判断数组中的元素是否满足表达式,全部为满足返回true,否则返回false
   return this.cartInfoList.every(item => item.isChecked === 1)
 }
           

2、修改购物车数量

  • @click @change 使用同一个方法进行修改

    =>问题:如何辨别点击哪一个

    =>解决方法:通过传参来区分谁是谁

    <a  @click="handler('minus',-1,cartInfo)">-</a>
     <input  @change="handler('change',$event.target.value,cartInfo)">
     <a  @click="handler('add',1,cartInfo)">+</a>
               
  • handler 函数=> 需要节流
    import throttle from 'lodash/throttle'
    
    handler: throttle(async function (type, disNum, cart) {
     // type:区分三个元素
      // disNum:+ 变化量(1) -变化量(-1)   input最终个数(不是变化量)
      // cart:分辨那个产品
     // 向服务器发请求
     switch (type) {
        case 'add':
          disNum = 1
          break
        case 'minus':
          // 判断产品个数: skuNum>1,传-1,skuNum<=1,传0
          disNum = cart.skuNum > 1 ? -1 : 0
          break
        case 'change':
          // 用户输入非数字||负数=>0
         disNum = (isNaN(disNum) || disNum < 1) ? 0 : parseInt(disNum) - cart.skuNum
          break
      }
      try {
        await this.$store.dispatch('addOrUpdateShopCart', { skuId: cart.skuId, skuNum: disNum })
        this.getDate()
      } catch (error) {
      }
    }, 1000)
               

二十、登录业务

1、注册

=> 通过数据库存储用户信息(名字、密码)

2、登录

=>登录成功时,后台为了区分用户是谁,服务器会下发token【token:令牌=>唯一的标识符===uuid】

=>注意:一般流程=>登录成功服务器下发token,前台持久化存储token【带着token找服务器要用户信息进行展示】

二十一、导航守卫

=>导航:表示路由正在发生改变。进行路由跳转

=>守卫:相当于“紫禁城护卫”

1、全局守卫

=>在项目中,只要发生路由变化,守卫就能监听到

  • 前置守卫:在路由跳转之前进行判断
    router.beforeEach((to, from, next) => {
     // to:可以获取你要跳转到的那个路由信息
      // from:可以获取你从哪里来的那个路由信息
      // next:放行函数 next()=>直接放行 next(path)=>放行到指定路由 next(false)
    })
               
  • 后置守卫:路由跳转已经完成在执行。

2、路由独享守卫

=>只在进入路由时触发

beforeEnter: (to, from,next) => {
}
           

3、组件内的守卫

  • beforeRouteEnter

    //进入
  • beforeRouteUpdate

    //更新,指的是参数发生更新时触发
  • beforeRouteLeave

    //离开

二十二、表单验证

  • Vue官方提供的一个表单验证的插件:vee-validate

    =>不好用,看懂老师操作即可

  • 【elementUI】推荐用它!

二十三、路由懒加载

路由懒加载:当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就会更加高效。

写法:

// 将 import Home from '@/views/Home'
// 替换成
const Home = () => import('@/views/Home')
const router = createRouter({
  routes: [{ path: '/home', component: Home }],
})
//上面是官网上的介绍,下面是优化写法
const router = createRouter({
  routes: [{ path: '/home', component:() => import('@/views/Home') }],
})
           

二十四、项目打包上线

执行

npm run build

map文件:因为代码是经过加密的,如果运行时报错,输出的错误信息无法准确得知时那里的代码报错。有了map就可以向未加密的代码一样,准确的输出是哪一行那一列有错,所以该文件如果项目不需要是可以去掉的。【map文件较大】

//在vue.config.js配置
module.exports ={
  productionSourceMap: false
}
// 修改需要重新build