笔记整理——Vue2项目尚品汇

目录

一、vue-cli脚手架初始化项目各文件夹

二、 项目的其他配置

2.1 项目运行,让浏览器自动打开

2.2 eslint校验功能(各种规范的报错)的关闭

2.3 src文件夹简写方法,配置别名

2.4 样式的引入

三. 本次项目路由的分析

四. 完成非路由组件Header与Footer业务

五. 路由组件的搭建

5.1 配置路由

5.2 路由组件与非路由组件的区别?

5.3 路由的跳转的两种形式

5.4 重定向    

六. Footer组件显示与隐藏

6.1 v-if和v-show的区别

6.2 Footer组件编写

七. 路由传参

7.1 路由跳转的两种形式

7.2 路由传参(params、query)

7.3 路由传参相关问题

八. 重写push和replace

8.1 为什么编程式导航进行路由跳转时,会有这种警告错误?

九. Home首页组件拆分业务分析

9.1. TypeNav的三级联动全局组件

9.2. 完成其余静态组件

十. POSTMAN测试接口

十一. axios二次封装

11.1 为什么需要进行二次封装axios?

11.2 在项目中,经常会有API文件夹【里面存放的就是axios请求】

 十二. API接口统一管理

12.1 跨域问题

十三. nprogress进度条的使用

13.1 打开页面时,页面上方显示的进度条。

十四. Vuex状态管理库

14.1 vuex是什么?

14.2 Vuex的四个大核心概念

14.3 this.$state.dispatch与this.$state.commit的主要区别

14.4 vuex实现模块式开发的大致概念

十五. TypeNav三级联动展示数据业务

十六. 完成三级联动动态背景颜色

十七. 函数的防抖与节流【lodash插件】

17.1 卡顿现象引入函数的防抖与节流

17.2 函数防抖的理解

17.3 函数节流的理解 

十八. 三级联动

18.1 三级联动中的节流

18.2 三级联动中的路由跳转分析

18.3 用编程式导航 + 事件的委派

十九. Search模块

19.1 Search模块商品模块分类与过渡动画

19.2 typeNav商品分类列表的优化

19.3 合并query和params参数

19.4 mockjs模拟数据

19.5 获取banner轮播图的数据

二十. swiper的基本使用

20.1 Banner实现轮播图的第一种方法

二十一. 轮播图通过watch+nectTick解决问题

21.1 若只用watch监听bannerList轮播图列表的属性

21.2 用watch + this.$neckTick(较为完美的解决方案)

二十二. 获取Floor组件mock数据

22.1 组件通信的方式有哪些?

22.2 动态展示Floor组件

22.3 共用组件Carsouel(轮播图)

二十三. Search模块的静态组件

23.1 写一个模块的步骤(套路):

23.2 箭头函数

二十四. search模块中动态展示产品列表

24.1 Search模块中子组件SearchSelector.vue的动态开发

二十五. Object.assign的用法

二十六. 面包屑

26.1 面包屑处理分类的操作

26.2 面包屑处理关键字的操作

26.3 面包屑处理品牌信息

二十七.平台售卖属性的操作

27.1 子组件传给父组件(用自定义事件)

27.2 将平台售卖属性放置面包屑(数组去重)

二十八. 排序操作(上)

二十九. 操作排序——对升序降序的操作(下)

三十. 分页器静态组件(分页器原理)

三十一. 分页器起始与结束数字计算(分页器逻辑)

31.1 分页器起始与结束(分页器显示的逻辑)

31.2 分页器的动态展示(分页器按钮显示的逻辑)

31.3 分页器的完成

31.4 分页器添加类名

三十二. 产品详情页面——滚动行为

三十三. 产品详情页面数据获取

33.1 产品详情页面数据获取

33.2 产品详情页面动态展示数据

33.3 产品详情页面动态展示数据——右侧

三十四. Zoom放大镜展示数据—裁剪

三十五. detail路由组件展示商品售卖属性

三十六. 产品售卖属性值——排他操作—裁剪

三十七. 放大镜操作(上)

37.1 放大镜操作——ImageList组件和Zoom组件之间的小操作

37.2 放大镜操作——大图片随着小图片的点击也进行改变

37.3 放大镜操作——遮罩层

三十八. 购买产品个数的操作

三十九. “加入购物车”路由

39.1 ”加入购物车“按钮的步骤

39.2 路由传递参数结合会话存储(成功路由跳转与参数传递)

39.3 购物车静态组件与修改

39.4 UUID游客身份获取购物车数据

39.5 购物车动态展示数据

39.6 处理产品数量、修改购物车产品的数量完成

39.7 修改产品个数【对函数进行节流】

39.8 删除购物车产品的操作

39.9 修改产品状态

四十. 删除全部选中的商品

四十一. "全部"产品的勾选状态修改

四十二. 登录注册静态组件【重要】

42.1 注册的静态组件

42.2 登录的静态组件

42.3 携带token获取用户信息

42.4 上一节登录业务存在的问题讲解(不完美的解决办法)

42.5 退出登录

四十三. 导航守卫理解

43.1 导航守卫的判断与操作

四十四. trade交易静态组件

44.1. trade交易静态组件

44.2 用户地址信息的展示(排他思想)

44.3 交易页面完成

四十五. 提交订单(不用vuex)

45.1 提交订单静态组件(不用vuex)

45.2 提交订单(没有vuex时,可以用的方法)

四十六. 获取订单号与展示信息

四十七. 支付页面中使用Element UI以及按需引入

四十八. 微信支付业务(上)

48.1 二维码生成(插件QRCODE)

48.2 获取支付订单状态

四十九. 个人中心——二级路由搭建

五十. 我的订单

五十一. 未登录的导航守卫判断(前置守卫)

五十二. 用户登录(路由独享与组件内守卫)

52.1 路由独享守卫

52.2 组件内守卫(用得不多)

五十三. 图片懒加载(插件:vue-lazyload)

五十四. 表单验证(插件:vee-validate)【了解即可,看懂就行】

五十五. 路由的懒加载

五十六. 处理map文件、打包上线

五十七. 购买服务器等操作(先了解)

57.1 利用xshell工具登录服务器

57.2 nginx反向代理



VUE2项目尚品汇笔记

将Typora记录的笔记、以及手写的笔记进行二次整理。

整理完本次笔记,也会继续将之前学习HTML、CSS、JS、Vue的笔记进行二次整理。

方便以后查找,同时有需要改正或补充的,还请大家指教。

一、vue-cli脚手架初始化项目各文件夹

node_modules文件夹:项目依赖文件夹

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

src文件夹(程序员源代码文件夹):

        assets文件夹:一般放置静态资源(一般放置多个组件共用的静态资源)。注:放置在assets文件夹里的静态资源,在webpack打包时,会把它当做一个模块,打包到JS文件里

         components文件夹一般放置的是非路由组件(全局组件)。

        App.vue:唯一的根组件,Vue当中的组件(.vue)。

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

babel.config.js:配置文件(babel相关)。

package.json文件:类似于项目的‘身份证’,记录项目叫什么,当中有哪些依赖,如何运行

package.lock.json文件:缓存性文件。

README.md文件:说明性文件。

二、 项目的其他配置

2.1 项目运行,让浏览器自动打开

package.json文件中配置

{
......
        "scripts": {
        "serve": "vue-cli-service serve --open",
        "build": "vue-cli-service build",
        "lint": "vue-cli-service lint"
    },
......

2.2 eslint校验功能(各种规范的报错)的关闭

eg:声明了变量,但未进行使用,则eslint校验工具会报错

  • 在根目录下创建文件vue.config.js
    module.exports = {
        //关闭eslint
        lintOnSave: false
    }

2.3 src文件夹简写方法,配置别名

①.jsconfig.json配置别名,可用@提示

【@代表的是src文件夹,常在js文件中使用。"exclude"表示“除了......”,意为配置的别名不能在"node_modules"和"dist"使用】

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

    //表示不能在"node_modules"和"dist"中使用这种配置的别名。
    "exclude":["node_modules","dist"]
    },

②.也可在.css中配置别名@提示 【在@前面加上~这个符号】

2.4 样式的引入

public文件夹下的index.html中引入同级reset.css样式

    

三. 本次项目路由的分析

vue-router 前端的路由:KV键值对。

key:URL(地址栏中的路径) value:相应的路由组件 注:本次项目是上中下结构

  • 本次项目的路由组件可分为:

        Home首页路由组件、Search搜索路由组件、login登录路由组件、Refister注册路由组件

  • 本次项目的非路由组件可分为:

         Header组件:【存在于首页、搜索页】 Footer组件:【存在于首页、搜索页,但登录和注册页面没有】

四. 完成非路由组件Header与Footer业务

本次项目主要关注业务、逻辑。

  • 开发项目步骤:
  1. 书写静态页面(HTML+CSS)(本次项目已提前准备好了)
  2. 拆分组件(静态组件、路由组件等)
  3. 获取服务器的数据动态展示
  4. 完成相应的动态业务逻辑(eg:Js动态业务等)
  • 注意:
  1. 创建组件=组件结构 + 组件的样式 + 图片资源。
  2. 本次采用less样式。

        需通过less、less-loader【安装版本五npm install --save less less-loader@5进行处理less,将其变为css样式。

        若想让组件识别less样式,需在style标签上加上lang=less。

20.1 Banner实现轮播图的第一种方法

安装swipernpm install --save swiper

ListContainer文件夹下的index.vue中的周期函数mouted(){}写一个定时器函数。

(不是最好的办法,但能解决问题)。

此时也不推荐用updata,因为若有其他数据刷新时,会重新new Swiper。

二十一. 轮播图通过watch+nectTick解决问题

21.1 若只用watch监听bannerList轮播图列表的属性



      通过watch监听bannerList属性的属性值变化。若执行handler方法,代表组件实例身上这个属性的属性已经有了【数组:四个元素(仓库两个,ListContainer有两个)】
        当这个函数执行,只能保证bannerList数据已经有了,但是没法办证v-for是否执行结束。
        当v-for执行完毕,才会有结构【但是单纯在watch中使没法保证的】

21.2 用watch + this.$neckTick(较为完美的解决方案)

neckTick:

1.可在下次DOM更新,循环结束之后【这里就可以指的是v-for结束后,页面中有了结构之后】,执行延迟回调。或者说,在修改数据之后,立即使用这个方法,获取更新后的DOM。

2.可保证页面中的结构一定是存在了的,neckTick经常和很多插件一起使用【因为大多插件DOM都是存在的】

二十二. 获取Floor组件mock数据

getFloorList这个action要在Home路由组件发,而不能在Floor组件中发,因为需要v-for遍历组件。v-for也可以在自定义标签中使用

22.1 组件通信的方式有哪些?

  • props:用于父给子组件通信
  • 插槽:(父给子)
  • 自定义事件:@on和 @emit 可实现子给父通信
  • 全局事件总线:$bus (全能)
  • vuex:(万能的方法,该方法可以用兄弟之间,但是本次项目用来当仓库数据)
  • pubsub-js:全能,但是vue中几乎不用

22.2 动态展示Floor组件

由九.Home首页的结构分析,可知Home首页被拆分为了七个部分。其中Floor组件是Home的子组件,所以这里用props进行父给子组件通信

动态传递数据。

运用swiper进行轮播图的修改。

在ListContainer组件中的轮播图,因为当时组件是由内部发请求、动态渲染结构【前台服务器数据需要传回来】,所以ListContainer组件中的轮播图,要把mounted写在外面的(写在了App.vue里),所以这里在ListContainer组件mounted之前结构还没有。

Floor这个组件,是在Home组件里发的请求,数据是父组件给的,子组件已经渲染了,所以说在Floor组件mounted之前结构就已经有了。

Home文件index.vue


Home文件下的Floor子组件



22.3 共用组件Carsouel(轮播图)

将首页Home组件里的轮播图(在Floor组件和ListContainer组件中)功能拆出来,新建成一个共用全局组件。

注:若看到某个组件在很多地方使用,则可把它变成全局组件。注册一次,即可在任意地方使用,共用的组件或非路由组件应放在components文件夹中。

这里在components文件夹下新建文件夹Carsouel(轮播图专用),

Carsouel文件夹index.vue,代码如下:





二十三. Search模块的静态组件

23.1 写一个模块的步骤(套路):

  • 1).先写静态页面 + 将静态组件拆分出来
  • 2).写接口,发请求(API)
  • 3).vuex(三连环)
  • 4).组件获取仓库数据,动态展示数据

23.2 箭头函数

  • ()中定义参数,若只有一个参数,可以不写括号;
  • {}中写函数体,若函数体找那个只有返回值,可不写return。

箭头函数    vs    普通函数

箭头函数 普通函数
this的指向不同 在哪里定义函数,this就指向谁。 谁调用这个函数,this就指向谁

二十四. search模块中动态展示产品列表

写一个模块的步骤(套路):

  • 1).先写静态页面 + 将静态组件拆分出来
  • 2).写接口,发请求(API)
  • 3).vuex(三连环)
  • 4).组件获取仓库数据,动态展示数据

一). API文件夹下的index.js

import requests from "./request";

//获取search模块数据    地址:/api/list  请求方式为:post   这里需要带参数
//当前这个函数也需要接收外部传递参数
//当给这个接口获取搜索模块的数据,给服务器传递一个默认参数【传递的参数至少是一个空对象】

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

二). 在仓库store文件夹下的index.js

//引入API
import { reqGetSearchInfo } from '@/api'
//search模块的小仓库
//vuex的四大核心概念必须要有
const state = {
    searchList: {}
};
const mutations = {
    GETSEARCHLIST(state, searchList) {
        state.searchList = searchList
    }
};
const actions = {
    //获取search模块数据
    async getSearchList({ commit }, params = {}) {
        //当前reqGetSearchInfo这个函数在调用获取服务器数据时,至少传递一个参数(空对象)
        // `this.$store.dispatch('action', payload);`
        //params形参:当用户派发action时,当第二个参数传递过来时,至少是一个空对象。
        let result4 = await reqGetSearchInfo(params);
        if (result4.code == 200) {
            commit("GETSEARCHLIST", result4.data);
        }
    }
};

//计算属性
//项目当中getters主要的作用:简化仓库中的数据。
const getters = {
    //当前的state是小仓库search里的state,并非大仓库中的那个state。
    goodsList(state) {
        //这样书写是会有问题的。
        //若网络不给力或没有网络,则state.searchList.goodsList应返回的是undefined;所以此时计算新的属性的属性值至少给一个数组。
        return state.searchList.goodsList || [];
    },
    trademarkList(state) {
        //这样书写是会有问题的
        return state.searchList.trademarkList;
    },
    attrsList(state) {
        //这样书写是会有问题的
        return state.searchList.attrsList;
    },
}

export default {
    state,
    mutations,
    actions,
    getters
}

三). 在路由pages文件夹下的Search文件中的index.js


24.1 Search模块中子组件SearchSelector.vue的动态开发

重复的、可使用v-for的部分使用起来。

本次所使用的数据在store仓库文件夹下的search文件夹里。Search模块的子组件SearchSelector.vue进行引入使用。



二十五. Object.assign的用法

为了再次发请求得到不同数据,可以用watch数据监听。

  • Object.assign的用法: Object.assign(target, …sources)

                   target: 目标对象,sources: 源对象(可有多个)

用于将所有可枚举属性的值从一个或多个源对象复制到目标对象,并将返回目标对象。

举例:

const o1 {a:1};
const o2 {b:2};
const o3 {c:3};

const obj = Object.assign(o1,o2,o3);
console.log(obj);//{a:1,b:2,c:3}
console.log(o1);//{a:1,b:2,c:3},目标对象自身也会改变

二十六. 面包屑

26.1 面包屑处理分类的操作

用的是编程式路由导航【在自己的路由组件下跳转到自己】

本次项目的面包屑:

  • ①.query删除(本次项目为分类属性的删除,修改路由信息)

        给点击事件一个删除种类名称removeCategoryname的方法



      把带给服务器是参数置空了,还要向服务器发请求。
      带给服务器参数说明可有可无的:若属性值为空的字符串还是会把相应的字段带给服务器。但是把相应的字段变为undefined,则当前这个字段不会带给服务器。所以这里赋值undefined。地址栏也需要进行路由跳转。在自己的路由组件下跳转到自己:this.$router.push({name:"search"});

严谨一点儿的写法:

    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:"search",params:this.$route.params});
      }

26.2 面包屑处理关键字的操作

Search组件中的面包屑关键字清除后,需要让兄弟组件Header组件中的关键字清除。

  • ②.params删除

需要多一步操作:删除输入框内的关键字(因为params参数是从Header组件搜索框内获取的,所以需要让Header组件传递信息给Search组件,然后再进行删除。这俩组件为兄弟组件)

组件通信方法(加深印象):

props:(父给子)

插槽:(父给子)

自定义事件:(子给父)

vuex:(万能的方法,该方法可以用兄弟之间,但是本次项目用来当仓库数据)

$bus:(万能的方法,全局事件总线)

pubsub-js:(万能的方法,但是在vue中用得少)

这里Header组件传递信息给Search组件运用了全局事件总线$bus

  • 第一步:main.js入口文件中进行补写全局事件总线$bus配置
new  Vue({
    //全局事件总线$bus配置
    beforeCreate() {
        Vue.prototype.$bus = this;
    },
}
......)
  • 第二步:Search组件使用$bus通信       this.$bus.$emit("......")


  • 第三步:Header的组件index.vue中补mounted的全局事件总线  $bus  this.$bus.$on
  mounted(){
    //通过全局事件总线$bus清除关键字
    //一挂载就监听clear事件
    this.$bus.$on("clear",()=>{
      this.keyword = '';
    });
  },

26.3 面包屑处理品牌信息

此时用的是自定义事件

  • 首先:Search中的子组件SearchSelector展示标签里,用@定义自定义事件(这里定义了trademarkInfo):

......

	//自定义事件回调
    trademarkInfo(){},

......
  • 其次:SearchSelector.vue中用this.$emit('trademarkInfo',trademark);发送请求给Search组件:

  • 然后:修改、补充Search模块里的内容

二十七.平台售卖属性的操作

27.1 子组件传给父组件(用自定义事件)

  • 首先:Search的子组件SearchSelector组件中补充自定义事件@click="attrInfo(attr,attrvalue)"attrInfo(attr,attrValue)的方法:

  • 其次:Search组件中补充:点击事件@attrInfo="attrInfo"和方法attrInfo(attr,attrValue),即可。

......

27.2 将平台售卖属性放置面包屑(数组去重)

需要遍历,所以用v-for,而不用v-if。

  • Search组件中整理好数据、增加平台的售卖属性值的面包屑:

  • 需要进行数组去重!!:
  • 删除平台的售卖属性值的面包屑。

二十八. 排序操作(上)

在接口文档中,

  • 问题一:order属性的属性值最多有多少种写法?

        四种。1:asc、1.desc、2:asc、2:desc

  • 问题二:应先考虑:谁应该有类名?通过order属性值当中是包含1(综合)或者包含2(价格)
......
			  
......

  • 写得比较复杂,可以放在计算属性computed里书写。

    
    

    • 问题三:谁应该有箭头矢量图?

            ①在阿里巴巴网页上搜索需要的矢量图内容:iconfont-阿里巴巴矢量图标库

    然后在public文件夹中的index.html中引用

      

            ②然后在SearchSelector文件夹中:【iconfont一定要写

            
  • 价格
  •         ③设置业务逻辑

    二十九. 操作排序——对升序降序的操作(下)

    对升序降序的操作

    
    

    前端三个重要的原理:轮播图(Carousel)、分页器(Pagination)、日历

    三十. 分页器静态组件(分页器原理)

    注册全局组件 分页器(Pagination)。

    【本次项目需掌握自定义分页功能】

    • 问题一:分页器展示需要哪些数据(条件)?

    1.一共有多少页?用pageNo字段代表页数。

    2.当前是第几页?

    3.每一页有多少数据展示?用pageSize字段进行代表。

    4.整个分页器一共有多少条数据?用total字段进行代表,(因此可额外获取另一条信息:一共多少页?)。

    5.需要知道分页器连续页码的个数是多少?用continues进行代表,一般是五页或者七页【奇数】,因为奇数对称(好看)。

    总结:对于分页器而言,自定义的前提需要知道四个条件。

    (1). pageNo:当前第几个

    (2).pageSize:代表每一页展示多少条数据

    (3).total:代表整个分页一共要展示多少条数据可得到总共多少页totalpage

    (4).continues:代表分页连续页码的个数【重要部分】

    三十一. 分页器起始与结束数字计算(分页器逻辑)

    再次复习:

    组件通信方法:

    props:(父给子)、插槽:(父给子)、自定义事件:(子给父)、vuex:(万能的方法,该方法可以用兄弟之间,但是本次项目用来当仓库数据)、$bus:(万能的方法,全局事件总线)、pubsub-js:(万能的方法,但是在vue中用得少

    31.1 分页器起始与结束(分页器显示的逻辑)

    • 一).这里使用的是父传子

    第一步:父组件Search模块中传数据。

              
              

    第二步:子组件分页器Paginationindex.js用props接收父传来的数据

    export default {
      name: "Pagination",
      props:['pageNo','pageSize','total','continues']
    };
    • 二).分页器(分页器逻辑)

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

    对于分页器而言,需要算出连续页面起始数字和结束数字

    分页器上面的数字的业务逻辑(用computed):

    pageNo为当前页;

    continues为连续的页数;

    totalPage为总页数;

    start为起始页数;

    end为结束页数。

    
    

    31.2 分页器的动态展示(分页器按钮显示的逻辑)

    分页器动态展示,可分为上中下

    【中间部分】

    v-for可以遍历:数组|数字|字符串|对象

    分页器页码按钮显示逻辑

    31.3 分页器的完成

    子组件Pagination中的业务传给父组件Search

    子传父:

    • 第一步:父组件Search中进行注册自定义事件@getPageNo="getPageNo"
        
        
    • 第二步:父组件中Search定义自定义事件@getPageNo="getPageNo"的回调事件
    • 第三步:子组件Pagination中用@click="$emit('getPageNo',????)"编写事件的触发

    31.4 分页器添加类名

    子组件Pagination中写上动态绑定的类名,使其在被点击时,按钮呈现颜色

    
    ............
    

    三十二. 产品详情页面——滚动行为

    滚动行为 | Vue Router

    • 步骤:

    1.静态组件 (详情页的组件,暂未注册为路由组件)

    当点击商品图片时,跳转到详情页面,在路由跳转的时候需要带上产品的ID给详情页面。用params传参,需占位

    2.再搞请求

    3.再再搞vuex

    4.动态展示组件

    • 操作:

    先把Detail.vue在路由文件中引用

    import Detail from '@/pages/Detail'
    ......
    //配置路由
    export default new VueRouter({
        //配置路由
        routes: [{
                // 这个的路由跳转需要带参数,所以需要占位,多写一下/:skuid
                path: "/detail/:skuid",
                component: Detail,
                meta: { show: true }
            }, 
                 ......
        ]

    Search组件

    • 滚动行为

    查看vue官方文档Vue Router里的进阶滚动行为:

    使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 vue-router 能做到,而且更好,它让你可以自定义路由切换时页面如何滚动。

    注意: 这个功能只在支持 history.pushState 的浏览器中可用。

    当创建一个 Router 实例,你可以提供一个 scrollBehavior 方法:

    const router = new VueRouter({
      routes: [...],
      scrollBehavior (to, from, savedPosition) {
        // return 期望滚动到哪个的位置
      }
    })

    举例:

    scrollBehavior (to, from, savedPosition) {
      return { x: 0, y: 0 }
    }

    三十三. 产品详情页面数据获取

    33.1 产品详情页面数据获取

    • 第一步:API文件中写获取产品详情信息的接口
    //获取detail模块数据   地址:/api/item/{ skuId }  请求方式是:get
    export const reqGoodsInfo = (skuId) => requests({ url: `/item/${ skuId }`, method: "get" })
    • 第二步:vuex文件夹中新增小仓库detail.js获取产品详情信息
    const state = {}
    const mutations = {};
    const actions = {};
    const getters = {};
    
    export default {
        state,
        mutations,
        actions,
        getters
    }

            然后将小仓库引入大仓库

    ......
    import detail from '@/store//detail';
    ......
    export default new Vuex.Store({
        //实现Vuex仓库模块式开发存储数据
        modules: {
            ......
            detail
            ......
        }
    })
    • 第三步:发请求,捞数据,存到仓库。修改完善一下仓库detail.js
    //引入api
    import { reqGoodsInfo } from "@/api";
    const state = {
        //这里data返回的是一个对象,所以应该写成空对象
        goodInfo: {}
    }
    const mutations = {
        GETGOODINFO(state, goodInfo) {
            state.goodInfo = goodInfo;
        }
    };
    const actions = {
        //获取产品信息的action
        async getGoodInfo({ commit }, skuId) {
            //当用户dispatch的时候派发得到的ID要带过去
            let result5 = await reqGoodsInfo(skuId);
            if (result5.code == "200") { //表示请求成功
                commit("GETGOODINFO", result5.data);
            }
        }
    };
    const getters = {};
    
    export default {
        state,
        mutations,
        actions,
        getters
    }
    • 第四步:detail组件中派发action,写在mounted中,记得要把产品的ID带上(ID在路由的params参数中)。
    mounted(){
          //派发action获取产品详情的信息
          // console.log(this.$route.params)//{skuid: "7"},其中skuid为占位符,可先      console.log(this.$route.params)看看占位符的命名
          this.$store.dispatch('getGoodInfo',this.$route.params.skuid)
        }

    33.2 产品详情页面动态展示数据

    先在detail的小仓库中,getters简化detail.js中的数据

    //简化数据而生
    const getters = {
        categoryView(state) {
           //例:state.goodInfo初始状态下是空对象,空对象的categoryView属性值是undefined,所以应该提前多声明一个空对象。
           //当前计算出的categoryView属性值至少是一个空对象,加上一个{},这样假的报错不会有了。
           return state.goodInfo.categoryView || {};
        }
    };

    再在detail.vue中引入{mapGetters},把仓库中的数据映射到detail组件上。

    33.3 产品详情页面动态展示数据——右侧

    方法同上一个小节33.2,上一节的跟进。

    ......
    const state = {
        //这里data返回的是一个对象,所以应该写成空对象
        goodInfo: {},
        skuInfo: {}
    }
    ......
    //简化数据而生
    const getters = {
        categoryView(state) {
            //例:state.goodInfo初始状态下是空对象,空对象的categoryView属性值是undefined,所以应该提前多声明一个空对象。
            //当前计算出的categoryView属性值至少是一个空对象,加上一个{},这样假的报错不会有了。
            return state.goodInfo.categoryView || {};
        },
        skuInfo(state) {
            return state.goodInfo.skuInfo || {};
        }
    };
    ......

    Detail组件中展示使用即可

    ......
    	
          
    {{categoryView.category1Name}} {{categoryView.category2Name}} {{categoryView.category3Name}}
    ......

    {{skuInfo.skuName}}

    {{skuInfo.skuDesc}}

    ......

    三十四. Zoom放大镜展示数据—裁剪

    • 父组件Detail
    
    
    • 子组件Zoom接收
    
    
    

    三十五. detail路由组件展示商品售卖属性

    三十六. 产品售卖属性值——排他操作—裁剪

    
    

    三十七. 放大镜操作(上)

    37.1 放大镜操作——ImageList组件和Zoom组件之间的小操作

    点击小图片,周围边框变颜色。在ImageList.vue

    
    

    传递全局事件,兄弟组件Zoom.vue接收

    37.2 放大镜操作——大图片随着小图片的点击也进行改变

    Zoom组件

    笔记整理——Vue2项目尚品汇_第4张图片

    37.3 放大镜操作——遮罩层

    笔记整理——Vue2项目尚品汇_第5张图片

    • 设置遮罩层随着鼠标的移动一起移动
    
    
    • 设置右侧的大图移动情况(补充)
    
    

    三十八. 购买产品个数的操作

    
    
    

    三十九. “加入购物车”路由

    (这个路由和其他的路由不太一样)

    "加入购物车”的按钮,需要用到的操作步骤:

    1.发送请求,将产品的数据信息存储于服务器。

    2.进行路由的跳转,跳转到购物车界面。

    39.1 ”加入购物车“按钮的步骤

    • 一、写好API接口
    //获取更新某一个产品的个数
    ///api/cart/addToCart/{ skuId }/{ skuNum }
    export const reqAddOrUpdateShopCart = (skuId, skuNum) => requests({ url: `/cart/addToCart/${skuId}/${skuNum}`, method: "post" })
    • 二、完善仓库detail数据
    import { reqGoodsInfo, reqAddOrUpdateShopCart } from "@/api";
    const actions = {
            async AddOrUpdateShopCart({ commit }, { skuId, skuNum }) {
            //加入购物车返回的解构数据skuId、skuNum。
            //加入购物车后(发请求后),前台将参数带给服务器,服务器写入数据成功,并未返回其他的数据,只返回了code=200,代表这次操作成功。
            //因服务器未返回其余数据,所以不需要三连环存储数据。
            let result = await reqAddOrUpdateShopCart(skuId, skuNum);
    
        }
    }
    • 三、在Detail组件
    
    
    • 四、修改仓库detail中的内容,加入返回成功和失败的情况内容。
        //将产品添加到购物车中
        async addOrUpdateShopCart({ commit }, { skuId, skuNum }) {
            //加入购物车返回的解构数据skuId、skuNum。
            //加入购物车后(发请求后),前台将参数带给服务器,服务器写入数据成功,并未返回其他的数据,只返回了code=200,代表这次操作成功
            //因服务器未返回其余数据,所以不需要三连环存储数据。
            //注:async函数执行返回的结果一定是一个promise【要么成功,要么失败】
    
            let result6 = await reqAddOrUpdateShopCart(skuId, skuNum);
            //当前这个函数执行返回Promise
            //当code返回200时为成功
            if (result6.code == 200) {
                return "ok"
            } else {
                //返回失败时
                return Promise.reject(new Error('faile'));
            }
            // console.log(result6);
        }
    • 五、在四完成之后可以进行路由跳转,则再次改写Detail组件中的内容,用上了try{}catch{}语法。
    ......
        async addShopcar(){
          //1.发请求---将产品信息加入到数据库中(通知服务器)
          // this.$store.dispatch('addOrUpdateShopCart',{skuId:this.$route.params.skuid,skuNum:this.skuNum})调用的是仓库中的addOrUpdateShopCart函数,返回得到的是promise。
          try{
            await this.$store.dispatch('addOrUpdateShopCart',{
              skuId:this.$route.params.skuid,
              skuNum:this.skuNum
            });
            //路由跳转
          }catch(error){
            alert(error.message)
          }
    ......
    • 六、在路由router文件夹中引入AddCartSuccess组件的路由
    import AddCartSuccess from '@/pages/AddCartSuccess'
    export default [
    ......
    	{
            path: "/addcartsuccess",
            component: AddCartSuccess,
            //meta是显示底下的floor组件的
            meta: { show: true }
        },
        ......
    ]

    39.2 路由传递参数结合会话存储(成功路由跳转与参数传递)

    addCartSuccess查看详情

    • HTML5新增的功能:本地存储和会话存储。

    本地存储和会话存储不可以存对象。

    所以这里用sessionStorage.setItem("SKUINFO",JSON.stringify(this.skuInfo));先把对象设置转为字符串,存到本地存储中。

    再在所需的组件AddCartSuccess组件里写computed

    Detail组件跳转的地方补写一下跳转路径的代码

    39.3 购物车静态组件与修改

    查看购物车ShopCart

    添加路由

    import ShopCart from '@/pages/ShopCart'
    export default [{
            path: "/shopcart",
            component: ShopCart,
            //meta是显示底下的floor组件的
            meta: { show: true }
        },
    ]

    AddCartSuccess组件添加路由跳转

    去购物车结算

    39.4 UUID游客身份获取购物车数据

    静态组件写完了,就发请求,获取购物车数据,操作vuex三连环、组件获取数据展示数据

    写接口——>写仓库——>写组件

    • 一、补写数据仓库detail
    import { reqCartList } from "@/api";
    const state = {};
    const mutations = {};
    const actions = {
        //获取购物车列表数据
        async getCartList({ commit }) {
            let result7 = await reqCartList();
            //测试是否能获取个人购物车数据
            console.log(result7);
        }
    };
    const getters = {};
    
    export default {
        state,
        mutations,
        actions,
        getters
    }
    • 二、捞购物车的数据。在ShopCart组件中补:
    ......
    	mounted(){
          this.getData();
        },
        methods:{
          //获取个人购物车数据
          getData(){
            this.$store.dispatch('getCartList')
          }
        }
    ......
    • 三、现在仓库detail中写
    //封装游客身份模块UUID——>生成一个随机字符串(生成之后不能再变化)
    import { getUUID } from '@/utils/uuid_token'
    const state = {
        //游客临时身份
        uuid_token: getUUID()
    }
    • 四、uuid_token.js文件用uuid随机生成
    import { v4 as uuidv4 } from 'uuid';
    //生成一个随机字符串,且每次执行不能发生变化,游客的身份持久保持
    export const getUUID = () => {
        //先从本地存储获取uuid(看看本地存储中是否有)
        let uuid_token = localStorage.getItem('UUIDTOKEN');
        //如果没有
        if (!uuid_token) {
            //调用UUID的函数uuidv4()生成游客临时身份
            uuid_token = uuidv4();
            //生成之后本地存储一次
            localStorage.setItem('UUIDTOKEN', uuid_token)
        }
        //切记务必要有返回值,没有返回值则是undefined
        return uuid_token;
    }
    • 五、在API的request.js中引入仓库store、并使用请求头【Request Headers】添加uuid_token
    //在当前模块中引入store
    import store from '@/store';
    
    //请求拦截器:可以在发请求之前,可处理一些业务。
    requests.interceptors.request.use((config) => {
        //config:配置对象,对象中有一个属性很重要(即:headers请求头)
        if (store.state.detail.uuid_token) {
            //请求头添加一个字段(userTempId),命名是与后台商量好的
            config.headers.userTempId = store.state.detail.uuid_token;
        }
        nprogress.start() //③.进度条开始动
        return config;
    })

    39.5 购物车动态展示数据

    后端给的购物车的动态数据格式有些不完美

    • 在补写仓库shopcart简化其数据
    import { reqCartList } from "@/api";
    const state = {
        cartList: []
    };
    const mutations = {
        GETCARTLIST(state, cartList) {
            state.cartList = cartList;
        }
    };
    const actions = {
        //获取购物车列表数据
        async getCartList({ commit }) {
            let result7 = await reqCartList();
            //测试是否能获取个人购物车数据
            console.log(result7);
            if (result7.code == 200) {
                commit("GETCARTLIST", result7.data)
            }
        }
    };
    const getters = {
        cartList(state) {
            return state.cartList[0] || {}
        }
    };
    
    export default {
        state,
        mutations,
        actions,
        getters
    }
    • 修改ShopCart组件
    
    

    39.6 处理产品数量、修改购物车产品的数量完成

    处理按钮

    
    

    39.7 修改产品个数【对函数进行节流】

    这里可以用节流的方式,防止发生卡顿现象。

    39.8 删除购物车产品的操作

    写接口——>写仓库——>写组件

    • 在APIindex.js写接口
    //获取删除购物车产品的接口
    // URL:/api/cart/deleteCart/{skuId}  method:delete
    export const reqDeleteCartById = (skuId) => requests({ url: `/cart/deleteCart/${skuId}`, method: 'delete' });
    • store仓库写仓库
    const actions = {
        ......
    	//删除购物车某一个产品(没有返回的信息,所以三连环不需要写完整)
        async deleteCartListBySkuId({ commit }, skuId) {
            let result8 = await reqDeleteCartById(skuId);
            if (result8.code == 200) {
                return "ok";
            } else {
                return Promise.reject(new Error("faile"));
            }
        }
        ......
    }
    • ShopCart组件写组件
    
    

    39.9 修改产品状态

    写接口——>写仓库——>写组件

    • API的index.js写接口
    //修改商品的选中状态
    //URL: /api/cart/checkCart/{skuID}/{isChecked}
    export const reqUpdateCheckedByid = (skuId, isChecked) => requests({ url: `/cart/checkCart/${skuID}/${isChecked}`, method: 'get' })
    • store仓库写仓库
        //修改购物车某一个产品选中状态
        async updateCheckedById({ commit }, { skuId, isChecked }) {
            let result9 = await reqUpdateCheckedByid(skuId, isChecked)
            if (result9.code == 200) {
                return 'ok';
            } else {
                return Promise.reject(new Error('faile'))
            }
        }
    • ShopCart组件写组件
    
    

    四十. 删除全部选中的商品

    context其实是个小仓库。

    context的小仓库其中:

    commit【提交mutations修改state】、

    getters【计算属性】、

    dispatch【派发action】、

    state【当前仓库数据】

    注:可通过ID删除产品的接口【一次删一个】

    Promise.all([p1,p2,p3])

    其中P1|P2|P3,每一个都是Promise对象。

    若有一个Promise失败,则都失败,若都成功,则返回成功。

        //删除全部勾选的产品
        deleteAllCheckedCart({ dispatch, getters }) {
            //context:小仓库,commit【提交mutations修改state】、getters【计算属性】、dispatch【派发action】、state【当前仓库数据】
            // console.log(getters.cartList.cartInfoList)
            //获取购物车中全部的产品(是一个数组)
            let PromiseAll = [];
            getters.cartList.cartInfoList.forEach(item => {
                let promise = item.isChecked == 1 ? dispatch('deleteCartListBySkuId', item.skuId) : '';
                //将每一次返回的Promise添加到数组中
                PromiseAll.push(promise);
            });
            //PromiseAll只要全部的p1|p2......都成功,则返回结果即为成功
            //若有一个失败,则返回即为失败结果。
            //返回的结果传回给ShopCart组件
            return Promise.all(PromiseAll)
        }

    传回给ShopCart组件

       //删除全部选中的产品
        async deleteAllCheckedCart(){
          try{
          //派发一个action
          await this.$store.dispatch("deleteAllCheckedCart");
          //再发请求获取购物车列表
          this.getData();
          }catch(error){
            alert(error.message);
          }
        }

    四十一. "全部"产品的勾选状态修改

    • shopcart仓库中
        //修改全部产品的状态
        updateAllCartIsChecked({ dispatch, state }, isChecked) {
            //数组
            let promiseAll = [];
            state.cartList[0].cartInfoList.forEach((item) => {
                let promise = dispatch('updateCheckedById', { skuId: item.skuId, isChecked });
                promiseAll.push(promise);
            });
            //最终返回结果
            return Promise.all(promiseAll);
        }
    • ShopCart组件中
    
    

    四十二. 登录注册静态组件【重要】

    也可在css中配置别名@提示

    【background-image: url(~@/assets/images/icons.png);】

    42.1 注册的静态组件

    • API文件中写个接口
    export const reqGetCode = (phone) => requests({ url: `/api/user/passport/sendCode/${phone}`, method: "get" })
    • 新建仓库user
    import { reqGetCode, reqUserRegister } from "@/api"
    //登录与注册的仓库
    const state = {
        code: ""
    }
    const mutations = {
        GETCODE(state, code) {
            state.code = code;
        }
    }
    const actions = {
        //获取验证码
        async getCode({ commit }, phone) {
            let result9 = await reqGetCode(phone)
            if (result9.code == 200) {
                commit('GETCODE', result9.data);
                return 'ok'
            } else {
                return Promise.reject(new Error('faile'))
            }
        },
        //用户注册
        async userRegister({ commit }, user) {
            let result10 = await reqUserRegister(user);
            // console.log(result10)
            if (result10.code == 200) {
                return 'ok'
            } else {
                return Promise.reject(new Error('fail'))
            }
        }
    }
    const getters = {}
    
    export default {
        state,
        mutations,
        actions,
        getters
    }
    • Register组件写入
    
    

    42.2 登录的静态组件

    token(令牌):是用户唯一标识,是服务器下发的。

    vuex仓库存储数据不是持久化的

    • API文件
    //登录
    // URL:/api/user/passport/login   methods:post
    export const reqUserLogin = (data) => requests({ url: `/user/passport/login`, data, method: "post" })

    • 仓库user
    import { reqUserLogin } from "@/api"
    const state = {
        token: ""
        ......
    }
    const mutations = {
        USERLOGIN(state,token) {
            state.token = token
        }
        ......
    }
    const actions = {
        //登录页面【token】
        async userLogin({ commit }, data) {
            let result11 = await reqUserLogin(data);
            if (result11.code == 200) {
                commit("USERLOGIN", result11.data.token);
                return 'ok'
            } else {
                return Promise.reject(new Error('faile'));
            }
        }
      }
    ......
    }
    const getters = {}
    
    export default {
        state,
        mutations,
        actions,
        getters
    }
    • 组件Login
    
    

    42.3 携带token获取用户信息

    • API文件
    //获取用户信息【需要带token向服务器要用户信息】
    //URL: /api/user/passport/auth/getUserInfo   methods:get
    export const reqUserInfo = () => requests({ url: `/user/passport/auth/getUserInfo`, method: "get" })
    • 仓库user
    import { reqUserInfo } from "@/api"
    
    const mutations = {
    ......
        GETUSERINFO(state, userInfo) {
            state.userInfo = userInfo
        }
    }
    const actions = {
    ......
     //token获取用户信息(需要存储用户的信息,所以需要三连环)
        async getUserInfo({commit}) {
            let result12 = await reqUserInfo();
            if (result12.code == 200) {
                //提交用户信息
                commit("GETUSERINFO", result12.data);
                return 'ok'
            } 
        }
    }
    const getters = {}
    
    export default {
        state,
        mutations,
        actions,
        getters
    }
    • 需要在API文件中的request.js请求头中补入信息
    //请求拦截器:可以在发请求之前,可处理一些业务。
    requests.interceptors.request.use((config) => {
      ......
        //config:配置对象,对象中有一个属性很重要(即:headers请求头)
        //携带token给服务器
        if (store.state.user.token) {
            config.headers.token = store.state.user.token
        }
      ......
    })
    • 组件Home

    Header组件的展示部分进行修改

    这里用是否带有用户名来判断是否展示。

    
    

    注:vuex仓库存储数据不是持久化的,所以这里的程序写完后,登录之后再刷新,用户信息丢失。

    42.4 上一节登录业务存在的问题讲解(不完美的解决办法)

    vuex仓库存储数据不是持久化的,所以这里的程序写完后,登录之后再刷新,用户信息丢失。

    将token转存到本地存储locationstorage中进行持久化存储。

    • 写法一:

    补写仓库user

    ......
    const actions = {
     ......
        //登录页面【token】
        async userLogin({ commit }, data) {
            let result11 = await reqUserLogin(data);
            if (result11.code == 200) {
                commit("USERLOGIN", result11.data.token);
                //持久化存储token
                //写法一:持久化存储token
                localStorage.setItem("TOKEN", result11.data.token)
                return 'ok'
            } else {
                return Promise.reject(new Error("faile"));
            }
        }
     ......
        }
    ......
    • 写法二:

    进行封装,在utils文件夹下新建token.js

    //存储token
    export const setToken = (token) => {
        localStorage.setItem('TOKEN', token)
    }

    再在仓库user中引用即可

    import { setToken } from "@/utils/token";
    ......
    const actions = {
        ......
        //登录页面【token】
        async userLogin({ commit }, data) {
            let result11 = await reqUserLogin(data);
            if (result11.code == 200) {
                commit("USERLOGIN", result11.data.token);
                //持久化存储token
                //写法二:持久化存储token
                setToken(result11.data.token);
                return 'ok'
            } else {
                return Promise.reject(new Error("faile"));
            }
        }
        ......
    }
    ......

    再继续接着进行改写

    仓库user改写token的内容

    • 写法一:

    补写仓库user

    ......
    const state = {
        token: localStorage.getItem('TOKEN'),
    }
    ......
    ......
    const actions = {
        ......
        //登录页面【token】
        async userLogin({ commit }, data) {
            let result11 = await reqUserLogin(data);
            if (result11.code == 200) {
                commit("USERLOGIN", result11.data.token);
                //持久化存储token
                setToken(result11.data.token);
                return 'ok'
            } else {
                return Promise.reject(new Error("faile"));
            }
        }
        ......
    }
    ......
    • 写法二:

    进行封装,在utils文件夹下新建token.js

    //存储token
    export const setToken = (token) => {
        localStorage.setItem('TOKEN', token)
    };
    //获取token
    export const getToken = () => {
        return localStorage.getItem("TOKEN");
    }

    再在仓库user中引用即可

    import { setToken, getToken } from "@/utils/token";
    ......
    const state = {
        token: getToken(),
    }
    ......
    ......
    const actions = {
        ......
        //登录页面【token】
        async userLogin({ commit }, data) {
            let result11 = await reqUserLogin(data);
            if (result11.code == 200) {
                commit("USERLOGIN", result11.data.token);
                //持久化存储token
                setToken(result11.data.token);
                return 'ok'
            } else {
                return Promise.reject(new Error('faile'));
            }
        },
        ......
    }
    ......

    42.5 退出登录

    Header组件写退出登录的方法

    • 写API
    //退出登录
    //URL:/api/user/passport/logout   methods:get
    export const reqLogout = () => requests({ url: `/user/passport/logout`, method: "get" })
    • 仓库user
    import { reqLogout } from "@/api"
    import { removeToken } from "@/utils/token";
    const mutations = {
        //清除本地数据
        CLEAR(state) {
            //帮仓库关掉、清空用户信息
            state.token = '';
            state.userInfo = {};
            //本地存储清空
            removeToken();
        }
    }
    const actions = {
            //退出登录
        async userLogout({ commit }) {
            //知识向服务器发起一次请求,通知服务器清除token
            let result13 = await reqLogout();
            //actions将数据提交到mutations里修改state
            if (result13.code == 200) {
                commit("CLEAR");
                return 'ok';
            } else {
                return Promise.reject(new Error("faile"))
            }
        }
    }
    • 再补写Header组件
    
    

    然而到此为止,界面还不够完善,例如:未登录时,可以直接跳转到购物车信息

    四十三. 导航守卫理解

    导航守卫 | Vue Router

    “导航”表示路由正在发生改变。

    正如其名,vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的。

    记住参数或查询的改变并不会触发进入/离开的导航守卫。你可以通过观察 $route 对象来应对这些变化,或使用 beforeRouteUpdate 的组件内守卫。

    路由router文件中定义

    //配置路由
    let router = new VueRouter({
        //配置路由
        routes: routes,
        scrollBehavior(to, from, savedPosition) {
            return { y: 0 }
        }
    });
    
    //全局守卫:前置守卫(在路由跳转之间进行判断)
    router.beforeEach((to, from, next) => {
        //to:可获取到要跳转的那个路由信息
        //from:可获取到从哪个路由而来的信息
        //next:放行函数
        // 写法一:next()直接放行    写法二:next(path)放行到指定路由     写法三:next(false)若URL改变,则中断跳回from路由
        next();
    })
    
    export default router;

    仓库state中有token,所以可以直接在router文件中引入仓库staterouter文件中state拿到token

    43.1 导航守卫的判断与操作

    • 补写router文件index.js
    //配置路由
    let router = new VueRouter({
        //配置路由
        routes: routes,
        scrollBehavior(to, from, savedPosition) {
            return { y: 0 }
        }
    });
    
    //全局守卫:前置守卫(在路由跳转之间进行判断)
    router.beforeEach(async(to, from, next) => {
        //to:可获取到要跳转的那个路由信息
        //from:可获取到从哪个路由而来的信息
        //next:放行函数
        // 写法一:next()直接放行    写法二:next(path)放行到指定路由     写法三:next(false)若URL改变,则中断跳回from路由
        let token = store.state.user.token;
        //如果token是'',if(token)得到也是真,此时需要判断用户信息(用户名)
        let name = store.state.user.userInfo.name;
        //用户已经登录后
        if (token) {
            //用户已经登录了就不能去登录或注册页面,只允许停留在首页
            if (to.path == '/login' || to.path == '/register') {
                next('/home')
            } else {
                //登录但是不是去/login
                //若已存在用户名
                if (name) {
                    next();
                } else {
                    //没有用户信息,则派发action让仓库存储用户信息再跳转
                    try {
                        //获取用户信息展示成功
                        await store.dispatch('getUserInfo');
                        //放行
                        next()
                    } catch (error) {
                        //服务器过期了,token失效的情况下
                        //清除token(用了退出登录那里)
                        await store.dispatch('userLogout')
                        next('/login');
                    }
                }
            }
        } else {
            //未登录
            next();
        }
    })
    
    export default router;

    四十四. trade交易静态组件

    这里需要使用老师的账号密码才能点得进有数据的trade

    ShopCart组件中的结算业务加上点击事件,进行页面跳转

    44.1. trade交易静态组件

    • 依旧按步骤,先写API 
    • 再写trade仓库
    • 最后再在写Trade组件
    • 数据展示
    • 依旧按步骤,先写API 
    // 获取用户地址信息
    // URL: /api/user/userAddress/auth/findUserAddressList   methods:get
    export const reqAddressInfo = () => requests({ url: `/user/userAddress/auth/findUserAddressList`, method: "get" })
    
    //获取商品清单
    // URL:/api/order/auth/trade   methods:get
    export const reqOrderInfo = () => requests({ url: `/order/auth/trade`, method: "get" })
    • 再写trade仓库
    import { reqAddressInfo, reqOrderInfo } from '@/api';
    const state = {
        address: [],
        orderInfo: {}
    };
    const mutations = {
        GETUSERADDRESS(state, address) {
            state.address = address;
        },
        GETORDERINFO(state, orderInfo) {
            state.orderInfo = orderInfo;
        }
    };
    const actions = {
        //获取用户地址信息
        async getUserAddress({ commit }) {
            let result13 = await reqAddressInfo();
            if (result13.code == 200) {
                commit('GETUSERADDRESS', result13.data);
            }
            console.log(result13);
        },
        //获取商品清单数据
        async getOrderInfo({ commit }) {
            let result15 = await reqOrderInfo();
            if (result15.code == 200) {
                commit("GETORDERINFO", result15.data);
            }
        }
    };
    const getters = {};
    export default {
        state,
        mutations,
        actions,
        getters
    }
    • 最后再在写Trade组件派发
    .....
    

    44.2 用户地址信息的展示(排他思想)

    
    

    44.3 交易页面完成

    • 完善Trade组件
    
    
    • 收集买家留言
    
    

    四十五. 提交订单(不用vuex)

            因操作时账号有冲突,订单一直无法成功提交,所以暂且将if(result.code ==200)改成了if(result.code !==200)进行下一个页面的学习。暂且听了听将提交订单页面、生成二维码的内容。

    45.1 提交订单静态组件(不用vuex)

    router文件中引入路由

    import Pay from '@/pages/Pay'
    //配置路由,路由的单词都是小写的
    export default [{
            path: "/pay",
            component: Pay,
            //meta是显示底下的floor组件的
            meta: { show: true }
        },
    ]

    引入接口,写API

    //提交订单的接口
    // URL:/api/order/auth/submitOrder?tradeNo={tradeNo}    method:POST
    export const reqSubmitOrder = (tradeNo, data) => requests({ url: `/order/auth/submitOrder?tradeNo=${tradeNo}`, data, method: "post" })

    45.2 提交订单(没有vuex时,可以用的方法)

    main.js写,在main.js这里写接口,在$bus里写,已经将$API挂载在Vue.prototype的原型对象上了,就不需要其他组件一个一个引入了。

    //统一接口api文件夹里全部的请求函数
    import * as API from '@/api';
    new Vue({
        render: h => h(App),
        //引入路由之后要注册路由:以下的写法是KV键值对,一致省略V【router应小写】
        //全局事件总线$bus配置
        beforeCreate() {
            Vue.prototype.$API = API;
        },

    直接在Trade组件中使用

    
    

    【支付跳转暂时完不成,因为有账号冲突】暂且将if(result.code !==200 )进行下一个页面的学习。

    四十六. 获取订单号与展示信息

    【支付跳转暂时完不成,因为有账号冲突】暂且将if(result.code !==200 )进行学习。参考借鉴其他笔记进行整理。

    pay组件

    
    
    • 写接口api
    //获取支付信息接口
    // URL:/api/payment/weixin/createNative/{orderId}   method:get
    export const reqPayInfo = () => requests({ url: `/payment/weixin/createNative/${orderId}`, method: "get" })
    • 再在Pay组件中补写:
    
    

    四十七. 支付页面中使用Element UI以及按需引入

    React(Vue)组件库大概有:antd[PC、 antd-mobile[移动端]

    Vue组件库大概有:ElementUI[PC]、vant[移动端]

    • 一、安装Element UI

    npm install --save element-ui

    • 二、本次用按需引入

    npm install babel-plugin-component -D

    然后,将 .babelrc的文件(或babel.config.js) 修改为:

    {
      "presets": [["es2015", { "modules": false }]],
      "plugins": [
        [
          "component",
          {
            "libraryName": "element-ui",
            "styleLibraryName": "theme-chalk"
          }
        ]
      ]
    }
    • 三、ElementUI按需引入,配置文件发生变化后,项目需重启
    • 四、按需在main.js入口文件引入
    //按需引入Element-UI
    import { Button } from 'element-ui';
    //import { MessageBox } from 'element-ui';
    ......
    //使用方法一:
    Vue.component(Button.name, Button);
    //使用方法二:
    //ElementUI注册全局组件时,可写在原型上
    //Vue.prototype.$msgbox = MessageBox;
    //Vue.prototype.$alert = MessageBox.alert;
    ......
    • 五、在pay组件中的写法
    
    

    四十八. 微信支付业务(上)

    48.1 二维码生成(插件QRCODE)

    生成二维码用qrcode插件

    • 一、先进行安装qrcode插件:npm install qrcode --save
    • 二、生成二维码,补写pay组件

    48.2 获取支付订单状态

    获取API

    //获取支付订单状态
    // URL:/api/payment/weixin/queryPayStatus/{orderId}  mehods:Get
    export const reqPayStatus = (orderId) => requests({ url: `/payment/weixin/queryPayStatus/${orderId}`, method: 'get' });

    完善Pay组件

    四十九. 个人中心——二级路由搭建

    routes.js中注册center的路由

    import Center from '@/pages/Center'
    //配置路由,路由的单词都是小写的
    export default [{
            path: "/center",
            component: Center,
            //meta是显示底下的floor组件的
            meta: { show: true }
        }
    ......]

    routes.js引入它的二级路由

    import Center from '@/pages/Center'
    //引入二级路由组件
    //引入组件
    import myOrder from '@/pages/Center/myOrder'
    import groupOrder from '@/pages/Center/groupOrder'
    //配置路由,路由的单词都是小写的
    export default [{
            path: "/center",
            component: Center,
            //meta是显示底下的floor组件的
            meta: { show: true },
            //children二级路由组件
            children: [{
                    path: "myorder",
                    component: myOrder
                },
                {
                    path: "grouporder",
                    component: groupOrder
                },
                {
                    path: '/center',
                    redirect: '/center/myorder'
                }
            ],
        },
    ......]

    Center组件中写入

    五十. 我的订单

    写接口/api/order/auth/{page}/{limit}

    用到了路由导航、分页器、v-for循环等知识点。

    由于依旧无法成功支付,故本节课无法展示具体内容点。代码在myOrder组件中,可自行查看。

    
    
    
    

    五十一. 未登录的导航守卫判断(前置守卫)

    未登录的访问,无法访问交易相关(trade)、支付相关(pay、paysuccess)、用户中心相关(center),点击后只能跳转到登录页面。

    • 所以在router文件夹下的index.js对之前的内容进行修改
    //全局守卫:前置守卫(在路由跳转之间进行判断)
    router.beforeEach(async(to, from, next) => {
            if (token) {
                ......
            }else {
            //未登录的访问,无法访问交易相关(trade)、支付相关(pay、paysuccess)、用户中心相关(center),点击后只能跳转到登录页面。
            let toPath = to.path;
            //如果toPath中带有
            if (toPath.indexOf('/trade') != -1 || toPath.indexOf('/pay') != -1 || toPath.indexOf('/center') != -1) {
                //把未登录的时候去而未去成的信息,存储于地址栏【路由】中
                next('/login?redirect=' + toPath);
            } else {
                //对其他的组件访问均可放行
                next();
            }
        }
    }
    • Login组件中补写:

    五十二. 用户登录(路由独享与组件内守卫)

    全局前置守卫(常用)、全局解析守卫、全局后置钩子、路由独享守卫(常用)、组件内守卫

    52.1 路由独享守卫

    路由文件routes.js文件中写:

    export default [
        ......
            {
            path: "/pay",
            component: Pay,
            //meta是显示底下的floor组件的
            meta: { show: true },
            //路由独享守卫
            beforeEnter: (to, from, next) => {
                if (from.path == "/trade") {
                    next();
                } else {
                    next(false);
                }
            }
        },
        {
            path: "/trade",
            component: Trade,
            //meta是显示底下的floor组件的
            meta: { show: true },
            //路由独享守卫
            beforeEnter: (to, from, next) => {
                if (from.path == "/shopcart") {
                    next();
                } else {
                    next(false);
                }
            }
        },
        ......
    ]

    52.2 组件内守卫(用得不多)

    本节在paysuccess组件中书写使用

    五十三. 图片懒加载(插件:vue-lazyload)

    图片懒加载的插件:

    vue-lazyload - npm

    • 第一步:安装插件

    npm i vue-lazyload -S

    • 第二步:在入口文件main.js中引入
    //引入插件
    import VueLazyload from 'vue-lazyload'
    //引入需使用的图片
    import green from '@/assets/green.gif'
    //注册插件
    Vue.use(VueLazyload, {
            //懒加载默认的图片
            loading: green
        })
    • 第三步:使用该指令(找到需要使用图片懒加载的图片,进行替换)

    本节由Search组件使用,

    替换成

    可以了解一下jquery的懒加载自定义插件

    五十四. 表单验证(插件:vee-validate)【了解即可,看懂就行】

    • 第一步:先安装2版本的:npm i vee-validate@2 --save
    • 第二步:引用表单校验插件

    由于入口文件main.js里面的内容太多了,新建一个文件写validata.js插件:

    //vee-validate插件:表单验证区域
    import Vue from 'vue';
    import VeeValidate from 'vee-validate';
    
    //使用插件
    Vue.use(VeeValidate)
    
    //中文提示信息
    import zh_CN from 'vee-validate/dist/locale/zh_CN'
    
    //表单验证
    VeeValidate.Validator.localize('zh_CN', {
        messages: {
            ...zh_CN.messages,//转成中文
            is: (field) => `${field}必须与密码相同` //修改内容规则的message,让确认密码和密码相同
        },
        attributes: {
            phone: '手机号',
            code: '验证码',
            password: '密码',
            password1: '确认密码',
            agree: '协议'
        },
    });
    
    //自定义校验规则
    VeeValidate.Validator.extend("tongyi", {
        validate: (value) => {
            return value;
        },
        getMessage: (field) => field + '必须同意'
    })

            接着在入口文件main.js引入

    //引入表单校验插件
    import "@/plugins/validate"
    • 第三步:基本使用

    Register组件中使用:

    
    

    五十五. 路由的懒加载

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

    路由懒加载优点:高效

    路由懒加载 | Vue Router

    官方写法:

    const Foo = () => import('./Foo.vue')
    const router = new VueRouter({
      routes: [{ path: '/foo', component: Foo }]
    })
    • 按照官方写法在router.js中完整写法:
    ......
    const Foo = () => {
        return import ("@/pages/Home/index.vue")
    }
    ......
    export default [
    {
            path: "/home",
            component: Foo,
            meta: { show: true }
        },
    ]
    • 将上里面的代码简写的过程如下:
    const Foo = () => {
        return import ("@/pages/Home/index.vue")
    }
    //可以简写为:
    const Foo = () => return import ("@/pages/Home/index.vue")
    //简化替换,可将下方的component组件简写为:
    export default [
    {
            path: "/home",
            component: () => return import ("@/pages/Home/index.vue")
            meta: { show: true }
        },
    ]
    • 最终简写可直接简写得:
    ......
    export default [
    {
            path: "/home",
            component: () => return import("@/pages/Home/index.vue")//箭头函数返回Promise,只有在该组件被调用时才会使用。
            meta: { show: true }
        },
    ]
    ......

    五十六. 处理map文件、打包上线

    使用npm run build将项目打包上线。打包上线后的代码都是经过压缩加密的,若运行报错则无法准确得知是哪里的代码报错。打包上线后之后,得到dist文件夹dist文件夹里的js文件夹里有.map结尾的文件。

    .map文件可以像未加密的代码一样,准确输出是哪一行哪一列有错误。

    所以如果项目不需要,.map文件是可以手动删除的。

    也可以在项目打包上线之前,在配置文件vue.config.js文件加上productionSourceMap:false这一行代码,打包之后就不会出现.map文件项目的体积就会缩小一些。

    vue.config.js文件

    module.exports = {
        productionSourceMap: false,
        //关闭eslint
        lintOnSave: false,
    
        //代理跨域
        devServer: {
            proxy: {
                '/api': {
                    target: 'http://39.98.123.211',
                    // pathRewrite: { '^/api': '' }, //因为本次项目的接口都已经带/api了,所以这里的路径可以不用写。
                },
            },
        },
    }

    五十七. 购买服务器等操作(先了解)

    暂未购买,先进行了解

    视频中的老师建议腾讯云(因为便宜),目前有活动——星星海 云服务器

    57.1 利用xshell工具登录服务器

    linux:/根目录

    linux常用指令:cd 跳转目录、ls查看、mkdir创建目录、pwd:查看绝对路径。

    57.2 nginx反向代理

    nginx配置

    1.xshell进入根目录/etc

    2.进入etc目录,若这个目录下有一个nginx目录,进到这个目录【需提前安装好nginx】

    在etc文件下配置nginx

    3.安装nginx:yum install nginx

    4.安装完nginx服务器后,在nginx目录下,多了一个nginx.conf文件,在这个文件中进行配置。

    5.vim nginx.conf进行编辑,主要添加如下俩:

    • 解决第一个问题:为什么访问服务器IP地址即可访问到自己的项目?——在服务器上=>/root/jch/www/shangpinhui/dist;,需要进行一些配置。
    location/{
    root    /root/jch/www/shangpinhui/dist;
    index    index.html;
    try_files    $uri  $uri/    /index.html;
    }
    • 解决第二个问题:项目的数据来自于http://39.98.123.211?
    location  /api{
    proxy_pass  http://39.98.123.211;
    }

    6,nginx服务器跑起来:service nginx start

  • 你可能感兴趣的:(前端学习笔记,Vue实例,vue.js,webpack,javascript)