当前位置:   article > 正文

尚硅谷VUE项目实战,前端项目-尚品汇 学习笔记(更新ing)_尚硅谷大型vue项目实战-尚品汇笔记

尚硅谷大型vue项目实战-尚品汇笔记

项目学习视频链接:

尚硅谷VUE项目实战,前端项目-尚品汇

学习资源 :

后台接口: http://gmall-h5-api.atguigu.cn
百度网盘:链接: https://pan.baidu.com/s/15Ad4sJp_clhCN29RYa6dcw?pwd=5hx3 提取码: 5hx3

vuex

安装vuex :npm i vuex@3

vuex的基本结构

import { ... } from "@/api";

const state = {};
const actions = {};
const mutations = {};
const getters={};

export default {
  state,
  actions,
  mutations,
  getters,
};

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

准备

1、创建一个api文件夹,index.js用于发送请求,其中封装的request.js文件用于处理对接收到的数据进行什么样的处理;mockAjax.js封装对前端进行mock得到的数据的处理
index.js

1、使用了 get / post /delete 三种请求数据的方法;
2、对不带参数的请求 ,比如: { url: "/product/getBaseCategoryList", method: "get" };
3、带参数的请求:{ url: /item/${skuid}, method: “get” }`,用模板字符串
4、暴露方式为:分别暴露(引入:按需引入)

import request from "./request";
import mockAjax from "./mockAjax";

// http://gmall-h5-api.atguigu.cn/api/product/getBaseCategoryList
// axios请求结果默认为promise
export const reqCategoryList = () =>
  request({ url: "/product/getBaseCategoryList", method: "get" });

//mock请求
//banner组件
export const reqGetbannerList = () => mockAjax.get("/banner");
// floor组件
export const reqFloorList = () => mockAjax.get("/floor");

// 获取商品详情接口
export const reqDetailList = (skuid) =>
  request({ url: `/item/${skuid}`, method: "get" });

//添加到购物车
export const reqCartList = (skuid, skuNum) =>
  request({
    url: `/cart/addToCart/${skuid}/${skuNum}`,
    method: "post",
  });

//删除购物车数据
///api/cart/deleteCart/{skuId} DELETE
export const reqDeleteCartList = (skuId) =>
  request({
    url: `/cart/deleteCart/${skuId}`,
    method: "delete",
  });

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
request.js

axios中文文档
一个axios的二次 封装

// 二次封装
import axios from "axios";
// 进度条
import nProgress from "nprogress";
import "nprogress/nprogress.css";

import store from "@/store";

const request = axios.create({
  // `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
  // 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL
  baseURL: "/api",
  // `timeout` 指定请求超时的毫秒数。
  // 如果请求时间超过 `timeout` 的值,则请求会被中断
  timeout: 1000,
});

// 添加请求拦截器
request.interceptors.request.use(
  (config) => {
    // console.log(store);
    //获取游客用户的id
    if (store.state.detail.uuid) {
      config.headers.userTempId = store.state.detail.uuid;
    }
    // 在发送请求之前做些什么
    nProgress.start(); //进度条开始
    return config;
  },
  (error) => {
    // 对请求错误做些什么
    return Promise.reject(error);
  }
);

// 添加响应拦截器
request.interceptors.response.use(
  (response) => {
    // 2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么
    nProgress.done(); //进度条结束
    return response.data;
  },
  (error) => {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    return Promise.reject(new Error("fail"));
  }
);

export default request;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
2、创建一个store文件夹,store用于存放各个小仓库数据,比如home组件的数据home.js …购物车组件的数据shopcart.js,统一由index管理

vuex-indexjs

vuex获取数据

以购物车的后台数据为例:
1、在api/index.js中写请求 reqShopCartList

//购物车
export const reqShopCartList = () =>
  request({
    url: "/cart/cartList",
    method: "get",
  });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2、在shopcart.js 里写 请求 和 处理 的方法

//导入请求的函数
import { reqShopCartList } from "@/api";
//查看接口文档--返回的数据类型为 对象类型 ,
//ShopcartList用于存放接收的数据
const state = {
  ShopcartList: {},
};
//请求数据
const actions = {
  //获取购物车列表
  async getShopcartList({ commit }) {
    let result = await reqShopCartList();
    // console.log(result);
    if (result.code == 200) {
      commit("SHOPCARTLIST", result.data);
    }
  },
};
//保存数据
const mutations = {
  SHOPCARTLIST(state, ShopcartList) {
    state.ShopcartList = ShopcartList;
  },
};
//简化数据  映射
const getters = {
  ShopcartList(state) {
    return state.ShopcartList[0] || [];
  },
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

3、派发请求:谁用谁发

3.1、 cartInfoList的处理,简化了数据请求,同时可能因为网络延迟有一段时间内出现 cartInfoList为undefined 那么控制台就会报错,为了避免这种情况可以预置为空数组;
3.2、发送请求:在shopcart.vue中,调用dispatch函数发送getShopcartList请求

import { mapGetters } from "vuex";
 computed: {
    ...mapGetters(["ShopcartList"]),
    cartInfoList() {
      return this.ShopcartList.cartInfoList || [];
    },
 methods:{
	getData() {
      this.$store.dispatch("getShopcartList");
    }
 },
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

3.3、除了需要接收数据的,还有另一种,只需要知道是否连接成功。大同小异,只是这里请求不需要保存数据,只需要判断响应是的结果

在action里:

// 修改购物车状态
  async updateIscheckedById({ commit }, { skuId, isChecked }) {
    let result = await reqIsChecked(skuId, isChecked);
    if (result.code == 200) {
      console.log(result);
      return "ok";
    } else {
      return Promise.reject(new Error("fail"));
    }
  },
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

在组件里

  //更新购物车中 商品的选中状态
  async updateChecked(goods, event) {
    try {
      let isChecked = event.target.checked ? 1 : 0
      await this.$store.dispatch('updateIscheckedById', { skuId: goods.skuId, isChecked: isChecked })
      this.getData()
    } catch (error) {
      alert(error.message)
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

防抖与节流

throttle(防抖)和debounce(节流)
节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效(只执行一次)
防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时(执行最后一次)
1、import throttle from 'lodash/throttle';

2、使用方法:xxx:throttle(function(参数){…},时间)

 // 避免用户点击过于频繁 防抖
        changeIndex: throttle(function (index) {
            this.currentIndex = index;
        }, 50),
 
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

路由跳转+事件委派

三级菜单里的知识点:
1、如何判断当前点击的是1/2/3级菜单?只有点击a标签才进行路由的跳转,如何判断点击的是a标签?
2、合并query后params参数;使路径包含 搜索框的搜索词keyword、1或2或3级菜单中最后的菜单等级 type和商品分名称 categoryName
3、事件委派:事件委派,把全部的子节点(h3 dl dm em )的事件委派给父节点。
3.1、动态绑定属性值
通过 :data-categoryName 来判断当前是不是a 标签,通过 :data-type1、:data-type2、:data-type3来判断是第几层菜单。

typeNav.vue
<!-- 路由跳转 -->
                    <div class="all-sort-list2" @click="goSearch">
                        <div class="item" v-for="(c1,index) in categoryList" :key="c1.categoryId">
                        <!-- 一级菜单 -->
                            <h3 @mouseenter="changeIndex(index)" :class="{current:currentIndex==index}" >
                                <a  :data-categoryName="c1.categoryName" :data-type1="c1.categoryId">{{c1.categoryName}}</a>
                             </h3>
                            <!-- 二级三级菜单 -->
                            <div class="item-list clearfix" :style="{dispaly: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-type2="c2.categoryId">{{c2.categoryName}}</a>
                                            </dt>
                                            <dd >
                                                <em v-for="(c3,index) in c2.categoryChild" :key="c3.categoryId">
                                                    <a  :data-categoryName="c3.categoryName" :data-type3="c3.categoryId">{{c3.categoryName}}</a>
                                                </em>
                                            </dd>   
                                        </dl>
                                </div>
                            </div>
                           
                        </div>  
                    </div>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
//路由跳转:编程式导航+事件委派
//问题:事件委派,把全部的子节点(h3 dl dm em )的事件委派给父节点
//如何确定只用但点击a标签才进行路由跳转
 goSearch(event) {
            //1-给每一级的a标签添加data-categoryname属性
            let ele = event.target;
            //获取当前触发的事件的节点,需要带有data-categoryname这样的节点,可以确定一定是 a标签
            let { categoryname, type1, type2, type3 } = ele.dataset;
            //注:如果a标签里面带有href="" 不会显示传递的参数
            if (categoryname) {
                //区分1 2 3 级标签
                let location = {
                    name: 'search'
                };
                let query = {
                    categoryName: categoryname
                }
                // 添加data-type(1 2 3 )来区分

                if (type1) {
                    query.type1 = type1
                } else if (type2) {
                    query.type2 = type2
                } else {
                    query.type3 = type3
                }
                //将路由参数合并 query参数
                //路由跳转categoryName,type合并到location里
                //console.log(query);
                // console.log(location);
                location.query = query;//合并location和query
                if (this.$route.params) {
                    location.params = this.$route.params
                }
                //路由跳转
                this.$router.push(location)
            }
        },
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
header.vue
goSearch() {
      // this.$router.push("./search");
      // 如果有query參數也帶過去
      let location = {
        name: "search",
        params: { keyword: this.keyword || undefined },
      };
      console.log(this.keyword);

      if (this.$route.query) {
        location.query = this.$route.query;
        this.$router.push(location);
      }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

分页器

难点在于如何分页器的要考虑的情况比较多;
连续页码数展示一般设置为5或者7;当前页码的开始页码、结束页码如何判断;以及省略号的显示与隐藏;上一页下一页分别不能在第一页和最后一页点击;实际页码数小于连续页码数(即小于5页);更新到对应页面(点击上一页、点击下一页,点击页码中的数字)
1、使用 disabled :disabled="pageNo == 1" :disabled="pageNo == totalPage"当前页码为1 或者 totalPage时
2、计算连续页码:当前页码 pageNo
2.1 总共页数小于连续页码数: start = 1; end = totalPage;
2.2 正常情况:总页数大于 连续页码数(5)
开始页码为: start = pageNo - parseInt(pagerCount / 2);
结束页码为:end = pageNo + parseInt(pagerCount / 2);
2.3 约束头部、尾部
如果 start < 1 (至于为什么小于1 ,请再看看计算方式,完全是有可能出现的情况)

      start = 1;
      end = pagerCount;
  • 1
  • 2

如果end > totalPage

      end = totalPage;
      start = totalPage - pagerCount + 1;
  • 1
  • 2

3、省略号的显示与隐藏
3.1 start 大于 2 进行展示,v-if="startAndEnd.start > 2"
3.2 end 小于 totalPage - 1 进行展示 :
3.3 不展示的情况:
假设当前页码 23 展示的连续页码 21 22 23 24 25 但结束页码的后一页为最后一页,此时就没必要展示省略号了,同样的 如果当前页码为4 展示的连续页码 2 3 4 5 6 第一页与第二页页没必要展示省略号
2

<div class="pagination">
    <!-- <h1>{{ startAndEnd }}</h1> -->
    <!-- 点击上一页,pageNo-1,如果当前页码==1,禁止上一页事件 -->
    <!-- click事件是将用户的页码数读取并传回search组件使其页active -->
    <button @click="$emit('currentPage', pageNo - 1)" :disabled="pageNo == 1">上一页</button>
    <!-- 起始页大于1 展示 -->
    <button v-if="startAndEnd.start > 1" @click="$emit('currentPage', 1)">1</button>
    <!-- 起始页大于2 展示 -->
    <button v-if="startAndEnd.start > 2">.....</button>

    <!-- 中间连续页码的地方:v-for、数组、对象、数字、字符串 -->

    <button v-for="(page, index) in startAndEnd.end" :key="index" v-show="page >= startAndEnd.start"
      @click="$emit('currentPage', page)" :class="{ active: pageNo == page }">{{ page }}</button>
    <!-- ...展示与否 与totalPage的差大于1  -->
    <button v-if="startAndEnd.end < totalPage - 1">......</button>
    <button v-if="startAndEnd.end < totalPage" @click="$emit('currentPage', totalPage)">{{ totalPage }}</button>

    <button @click="$emit('currentPage', pageNo + 1)" :disabled="pageNo == totalPage">下一页</button>

    <button style="margin-left: 30px">共 {{ total }} 条</button>
  </div>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
export default {
  name: "PageNation",
  props: ["total", "pageSize", "pageNo", "pagerCount"],
  computed: {
    //分页器一共多少页【总条数/每页展示条数】
    totalPage() {
      //向上取整数
      return Math.ceil(this.total / this.pageSize);
    },
    //底下的代码是整个分页器最重要的地方[算出连续五个数字、开头、结尾]
    startAndEnd() {
      //算出连续页码:开始与结束这两个数字
      let start = 0, end = 0;
      const { totalPage, pagerCount, pageNo } = this;
      //特殊情况:总共页数小于连续页码数
      if (totalPage < pagerCount) {
        start = 1;
        end = totalPage;
      } else {
        //正常情况:分页器总页数大于连续页码数
        start = pageNo - parseInt(pagerCount / 2);
        end = pageNo + parseInt(pagerCount / 2);
        //约束start|end在合理范围之内
        //约束头部
        if (start < 1) {
          start = 1;
          end = pagerCount;
        }
        //约束尾部
        if (end > totalPage) {
          end = totalPage;
          start = totalPage - pagerCount + 1;
        }
      }
      console.log(start, end);
      return { start, end };
    },
  },
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

页码

uuid 临时身份

1、安装uuid
2、创建utils文件夹,创建uuid_token.js文件,生成id

import { v4 as uuidv4 } from "uuid";
export const getUID = () => {
  let uuid = localStorage.getItem("UUIDTOKEN");
  //本地没有临时id
  if (!uuid) {
    // 生成
    uuid = uuidv4();
    // 本地存储一次
    localStorage.setItem("UUIDTOKEN", uuid);
  }
  return uuid;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

3、将id写到请求头里,这样任何路由下都会携带该id

/ 添加请求拦截器
request.interceptors.request.use(
  (config) => {
    // console.log(store);
    //获取游客用户的id
    if (store.state.detail.uuid) {
      config.headers.userTempId = store.state.detail.uuid;
    }
    // 在发送请求之前做些什么
    nProgress.start(); //进度条开始
    return config;
  },
  (error) => {
    // 对请求错误做些什么
    return Promise.reject(error);
  }
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

购物车功能实现:

商品数量的改变

1、购物车商品变化: 商品数量的改动 通过 - input + 进行改变,这三种行为最终值 只触发一个派发请求(将购物车 变化的的数据 上传给服务器)
2、判断用户进行哪种操作 type ,点击了 - 传给后后台的数据 -1;点击了 + 传给后台的数据 1 ,点击了input 通过 $event.target.value * 1 获取input框的数据
传给后台的数据 event.target.value*1 - goods.skuNum
3、频繁点击会出现 商品数量小于1 的情况 ,为避免这种不合理的情况 要进行防抖

 <li class="cart-list-con5">
        <a href="javascript:void(0)" class="mins" @click="handler('minus', -1, goods)"
              :diasabled="goods.skuNum == 1">-</a>
        <input autocomplete="off" type="text" value="1" minnum="1" class="itxt" v-model="goods.skuNum"
              @click="handler('change', $event.target.value * 1, goods)" />
        <a href="javascript:void(0)" class="plus" @click="handler('add', 1, goods)">+</a>
</li>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
 //商品数量的改动 通过 - input + 行为进行改变,这三种行为最终值触发一个派发请求(将购物车 变化的的数据 上传给服务器)
    // type 点击加号1 点击减号-1 input框的值 要进行类型判断
    // cart 商品的id
    handler: throttle(async function (type, disNum, goods) {
      switch (type) {
        case "add":
          disNum = 1;
          break;
        case "minus":
          //当前商品数量为1事,不能再减
          disNum = goods.skuNum > 1 ? -1 : 0;
          break;
        case "change":
          if (isNaN(disNum) || disNum < 1) {
            disNum = 0;
          } else {
            disNum = parseInt(disNum) - goods.skuNum;
          }
          break;
      }
      //派发请求
      try {
        await this.$store.dispatch("addOrupdateShopcart", {
          skuid: goods.skuId,
          skuNum: disNum,
        });

        this.getData();
      } catch (error) {
        console.log(error.message);
      }
    }, 300),
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

删除多个商品

在action里进行dispatch,promise.all 返回值为成功时,组件的回调才会返回成功。
关于promise.all
promise.all()该方法用于将多个Promise实例,包装成一个新的Promise实例。

var p = Promise.all([p1,p2,p3]);
  • 1

(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

  // 删除商品
  async deleteShopcartList({ commit }, skuId) {
    let result = await reqDeleteCartList(skuId);
    if (result.code == 200) {
      return "ok";
    } else {
      return Promise.reject(new Error("fail"));
    }
  },
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
//删除选中的商品
    async deleteAllCart() {
      try {
        //等待全部勾选商品删除以后
        await this.$store.dispatch('deleteAllCart');
        //再次获取购物车的数据
        this.getData();
      } catch (error) {
        alert('删除失败');
      }
    },
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
 //删除选中的商品
  deleteAllCart({ dispatch, getters }) {
    let arr = [];
    //获取仓库里面购物车的数据
    state.ShopcartList[0].cartInfoList.forEach((item) => {
      //商品的勾选状态是勾选的,发请求一个一个删除
      if (item.isChecked == 1) {
        let ps = dispatch("deleteShopcartList", item.skuId);
        arr.push(ps);
      }
    });
    return Promise.all(arr);
  },
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

登录 注册

简单的业务流程梳理

在做注册、登录业务的时候,先不处理表单的验证功能,在项目最后一天,在把表单如何验证,如果是那哪些插件解决【最后去处理】
正则
手机号:11
验证码:4-6
登录密码|确认密码:首字母大写、包含英文、数字、特殊字符等等。

注册&登录

1、需要确认用户已经填写【手机号、验证码、密码==确认密码】之后将【手机号、登录密码】传递给服务器

name: "Register",
    data() {
        return {
            phone: "",
            passcode: "",
            password: "",
            password1: "",
            agree: true,
        };
    },
    methods: {
        //获取验证码
        async getCode() {
            try {
                //简单的判断
                const { phone } = this;
                phone && (await this.$store.dispatch("getPasscode", phone));
                this.passcode = this.$store.state.user.passcode;
            } catch (error) {
                alert(error.message)
            }
        },
        //用户的注册信息
        async userRegister() {
            try {
                const { phone, passcode, password, password1 } = this;
                phone && passcode && password == password1 &&
                    await this.$store.dispatch("getUserRegister", {
                        phone,
                        passcode,
                        password,
                    });

                this.$router.push('/login');
            } catch (error) {

                alert(error.message)
            }
        },
    },
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

2、注册完成之后跳转到登录页面,通过向服务器发请求确认用户传递的手机号、密码;根据api文档,需要通过token对用户身份进行校验(token为唯一标识符),但vue不提供长期的数据保存业务,只要刷新页面,token就会被清除,所以要对获取到的用户信息进行本地保存。
action:

 // 登录
  async getUserLogin({ commit }, data) {
    let result = await reqLogin(data);
    if (result.code == 200) {
      commit("SETUSERTOKEN", result.data.token);
      //本地保存token
      localStorage.setItem("TOKEN", result.data.token);
      return "ok";
    } else {
      console.log(result);
      return Promise.reject(new Error(result.message));
    }
  },
  //保持用户信息到本地
  async getUserInfo({ commit }) {
    let result = await reqUserInfo();
    //将用户信息存储到store中
    if (result.code === 200) {
      //vuex存储用户信息
      commit("SETUSERINFO", result.data);
      return "ok";
    } else {
      return Promise.reject(new Error(result.message));
    }
  },
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

mutation:

//存储验证码
  GETCODE(state, passcode) {
    state.passcode = passcode;
  },
  // 存储token
  SETUSERTOKEN(state, token) {
    state.token = token;
  },
  //存储用户信息
  SETUSERINFO(state, data) {
    state.userInfo = data;
  },
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

3、同时,判断用户是否登录,以及登录之后路由可以跳转到哪?进行判断,用到了全局路由守卫,在route文件夹的index.js文件中

//设置全局导航前置守卫
router.beforeEach(async (to, from, next) => {
  let token = store.state.user.token;
  let name = store.state.user.userInfo.name;
  //1、有token代表登录,全部页面放行
  if (token) {
    //1.1登陆了,不允许前往登录页
    if (to.path === "/login") {
      next("/home");
    } else {
      //1.2、因为store中的token是通过localStorage获取的,token有存放在本地
      // 当页面刷新时,token不会消失,但是store中的其他数据会清空,
      // 所以不仅要判断token,还要判断用户信息

      //1.2.1、判断仓库中是否有用户信息,有放行,没有派发actions获取信息
      if (name) next();
      else {
        //1.2.2、如果没有用户信息,则派发actions获取用户信息
        try {
          await store.dispatch("getUserInfo");
          next();
        } catch (error) {
          //1.2.3、获取用户信息失败,原因:token过期
          //清除前后端token,跳转到登陆页面
          await store.dispatch("logout");
          next("/login");
        }
      }
    }
  } else {
    //2、未登录,首页或者登录页可以正常访问
    //未登录不能去交易相关、支付相关、个人中心
    let topath = to.path;
    if (
      topath.indexOf("/trade") != -1 ||
      topath.indexOf("/pay") != -1 ||
      topath.indexOf("/center") != -1
    ) {
    //登录之后回到之前的路径
      next("/login?redirect=" + topath);
    } else {
      //其他
      alert('请先登录');
      next('/login');
    }
  }
});

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

退出登录

退出登录之后用户身份为游客,要清除登录时保存的用户信息

//logout
  async userLogout() {
    let result = await reqLogout();
    if (result.code == 200) {
      commit("CLEAR");
      return "ok";
    } else {
      return Promise.reject(new Error(result.message));
    }
  },

//退出登录
  CLEAR(state) {
    //清除仓库相关用户信息
    state.token = "";
    state.userInfo = {};
    //本地存储令牌清空
    localStorage.removeItem("TOKEN");
  },
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

表单验证

安装插件 vee-validate @2版本

import Vue from "vue";
import VeeValidate from "vee-validate";
import zh_CN from "vee-validate/dist/locale/zh_CN"; // 引入中文 message
Vue.use(VeeValidate);

// 第二步:提示信息
VeeValidate.Validator.localize("zh_CN", {
  messages: {
    ...zh_CN.messages,
    is: (field) => `${field}必须与密码相同`, // 修改内置规则的 message,让确认密码和密码相同
  },
  attributes: {
    // 给校验的 field 属性名映射中文名称
    phone: "手机号",
    passcode: "验证码",
    password: "密码",
    password1: "确认密码",
    agree: "协议",
  },
});

//自定义校验规则
//定义协议必须打勾同意
VeeValidate.Validator.extend("agree", {
  validate: (value) => {
    return value;
  },
  getMessage: (field) => field + "必须同意",
});

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
 <div class="content">
                <label>手机号:</label>
                <input placeholder="请输入你的手机号" v-model="phone" name="phone"
                    v-validate="{ required: true, regex: /^1\d{10}$/ }" :class="{ invalid: errors.has('phone') }" />
                <span class="error-msg">{{ errors.first("phone") }}</span>
            </div>
            <div class="content">
                <label>验证码:</label>
                <input type="text" placeholder="请输入验证码" v-model="passcode" name="passcode"
                    v-validate="{ required: true, regex: /^\d{6}$/ }" :class="{ invalid: errors.has('passcode') }" />
                <button @click="getCode">获取验证码</button>
                <span class="error-msg">{{ errors.first("passcode") }}</span>
            </div>
            <div class="content">
                <label>登录密码:</label>
                <input type="password" placeholder="请输入你的登录密码" v-model="password" name="password"
                    v-validate="{ required: true, regex: /^[0-9A-Za-z]{8,20}$/ }"
                    :class="{ invalid: errors.has('password') }" />
                <span class="error-msg">{{ errors.first("password") }}</span>
            </div>
            <div class="content">
                <label>确认密码:</label>
                <input type="password" placeholder="请输入确认密码" v-model="password1" name="password1"
                    v-validate="{ required: true, is: password }" :class="{ invalid: errors.has('password1') }" />
                <span class="error-msg">{{ errors.first("password1") }}</span>
            </div>
            <div class="controls">
                <input type="checkbox" :checked="agree" name="isCheck" v-validate="{ required: true, agree: true }"
                    :class="{ invalid: errors.has('agree') }" />
                <span>同意协议并注册《尚品汇用户协议》</span>
                <span class="error-msg">{{ errors.first("agree") }}</span>
            </div>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

正则

一个合理的用户名正则表达式(菜鸟教程)
阮一峰 ECMAScript 6 (ES6) 标准入门教程 第三版 正则的扩展

一个合理的用户名正则表达式
用户名可以包含以下几种字符:

1、26 个大小写英文字母表示为 a-zA-Z。
2、数字表示为 0-9。
3、下划线表示为 _。
4、中划线表示为 -。
用户名由若干个字母、数字、下划线和中划线组成,所以需要用到 + 表示 1 次或多次出现。

根据以上条件得出用户名的表达式可以为:

[a-zA-Z0-9_-]+

路由懒加载,图片懒加载

1、什么是路由懒加载?

整个网页默认是刚打开就去加载所有页面,路由懒加载就是只加载你当前点击的那个模块。
按需去加载路由对应的资源,提高首屏加载速度(tip:首页不用设置懒加载,而且一个页面加载过后再次访问不会重复加载)。

实现原理:将路由相关的组件,不再直接导入了,而是改写成异步组件的写法,只有当函数被调用的时候,才去加载对应的组件内容。

{
    path: "/communication",
    component: () => import("@/pages/Communication/Communication"),
    children: [
      {
        path: "event",
        component: () => import("@/pages/Communication/EventTest/EventTest"),
        meta: { show: false },
      },
      {
        path: "model",
        component: () => import("@/pages/Communication/ModelTest/ModelTest"),
        meta: {
          show: false,
        },
      },
      {
        path: "sync",
        component: () => import("@/pages/Communication/SyncTest/SyncTest"),
        meta: {
          show: false,
        },
      },
      {
        path: "attrs-listeners",
        component: () =>
          import("@/pages/Communication/AttrsListenersTest/AttrsListenersTest"),
        meta: {
          show: false,
        },
      },
      {
        path: "children-parent",
        component: () =>
          import("@/pages/Communication/ChildrenParentTest/ChildrenParentTest"),
        meta: {
          show: false,
        },
      },
      {
        path: "scope-slot",
        component: () =>
          import("@/pages/Communication/ScopeSlotTest/ScopeSlotTest"),
        meta: {
          show: false,
        },
      },
    ],
  },
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

2、图片懒加载:发现一篇https://blog.csdn.net/qq_44947815/article/details/125286969
3、下载lazyload插件,并导入mainjs
在这里插入图片描述
在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/69904
推荐阅读
相关标签
  

闽ICP备14008679号