IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> Vue2.0实战 -> 正文阅读

[JavaScript知识库]Vue2.0实战

目录

项目初始化

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

2.项目的其他配置

3.项目路由分析

4.完成非路由组件Header和Footer业务

5.完成路由组件搭建

6. Footer组件显示与隐藏

7.路由传参

7.3?路由传参面试题

Home模块组件拆分

1.三级联动组件

2.?完成其余静态组件

3.postman测试接口

4.axios二次封装

5.接口统一管理

6.nprogress进度条的使用

6. vuex状态管理库

7.完成TypeNav三级联动展示数据业务

8.卡顿现象

8.1 节流

8.2 防抖

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

Home模块收尾

1.?开发search模块中的TypeNav商品分类菜单(过渡动画)

2. 优化TypeNav

3.合并params和query参数

4.开发Home首页中的ListContainer组件与Floor组件

4.1?mockjs使用步骤

5.?swiper

6.ListContainer组件开发

7.Floor组件的开发

7.3?组件通信的方式

8.将轮播图拆分成公用的全局组件

Search模块开发

1.Object.assign()

2.面包屑问题

3.分页器

Detial模块开发

1.1?路由设置

登录与注册业务

1. 静态组件

2.注册业务

3.登录业务

4.token令牌

5.?登录【已token】

6.?退出登录

7.支付订单页面

8.提交订单

9.图片懒加载

9.2自定义插件

10.表单验证

11.路由懒加载

12.打包上线

12.1?nginx


项目初始化

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

node + webpack + 淘宝镜像

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

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

src文件夹(程序员源代码文件夹):
? ? -assets文件夹:一般也是放置静态资源(一般放置多个组件共用的静态资源),需要注意,放置在assets文件夹里面的静态资源,在webpack打包的时候,webpack会把静态资源当做一个模块,打包JS文件夹里
? ? -components文件夹:一般放置非路由文件(全剧组件)
? ? -App.vue:项目中唯一的根组件,Vue当中的组件(.vue)
? ? main.js:程序入口文件,整个程序的中最先执行的文件

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

package.json:记录项目信息(项目身份证)

package-lock.json:缓存性文件

README.md:说明文件

2.项目的其他配置

2.1 项目运行起来的时候让浏览器自动打开

---package.json文件中更改

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

2.2 eslint校验功能关闭

---在根目录下创建vue.config.js文件

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

比如:声明变量但是没有使用,eslint校验工具报错

2.3?src文件夹简写方式,配置别名 @

---在根目录下创建jsconfig.json文件,配置别名@,@代表的是src文件夹,这样将来文件过多找的时候方便很多。

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

3.项目路由分析

vue-router

前端所谓路由:KV键值对

key:URL(地址栏中的路径)

value:响应的路由组件

目前的路由组件:Home、Search、Login、Register

非路由组件:Header、Footer(Login、Register页面没有Footer)

4.完成非路由组件Header和Footer业务

开发项目时

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

注意1:创建组件的时候,组件结构 +?组建形式 +?图片资源
注意2:项目采用less样式,浏览器不识别less样式,需要通过less、less-loader(5版本)进行处理,变成css样式,浏览器才可以识别
注意3:如果想让浏览器认识less样式,需要在style标签加 lang = "less"?

4.1?使用组件的步骤(非路由组件)

-创建或定义

-引入

-注册

-使用

5.完成路由组件搭建

vue-router

路由组件:Home、Search、Login、Register

-components一般放置非路由组件

-pages |?view文件夹一般放置路由组件

5.1?配置路由

项目当中配置的路由一般放置router文件夹中

router中index.js

//配置路由地址
import Vue from 'vue';
import VueRouter from 'vue-router'

//使用插件
Vue.use(VueRouter);

//引入路由组件
import Home from '@/pages/Home'
import Search from '@/pages/Search'
import Login from '@/pages/Login'
import Register from '@/pages/Register'

//配置路由
export default new VueRouter ({
  routes:[
    {
      path:"/home",
      component:Home
    },
    {
      path:"/search",
      component:Search
    },
    {
      path:"/login",
      component:Login
    },
    {
      path:"/register",
      component:Register
    },
    //重定向:在项目拍起来的时候,访问/,立刻让他定向到首页
    {
      path:"*",
      redirect:"/home"
    }
  ]
})

main.js注册路由

import Vue from 'vue'
import App from './App.vue'
//引入路由
import router from '@/router'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  //注册路由:写法是KV一致省略V,而且router是小写的
  //注册路由信息:当这里书写router的时候,组件身上都拥有$route $router属性
  router
}).$mount('#app')

5.2?小结

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

1.路由组件一般放在pages |?view文件夹
? ?非路由组件一般放置component

2.路由组件一般需要router文件夹中进行注册(使用的即为组件的名字),不需要在componen里再注册了,使用时用<router-view>
? ?非路由组件在使用的时候一般都是以标签的形式使用

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

$route:一般获取路由信息(路径、query、params等)

$router:一般进行编程式导航路由跳转(push、replace)

5.3?路由跳转

路由跳转有两种形式

声明式导航router-link,可以进行路由的跳转

编程式导航push、replace,可以进行路由跳转

编程式导航:声明式导航能做的,编程式导航都能做

编程式导航除了进行路由跳转,还可以有其他业务逻辑

6. Footer组件显示与隐藏

显示或隐藏组件:v-if 、v-show

Footer组件:在Home、Search显示;在Login、Register隐藏

6.1?可以根据组件身上的$route.path?获取路由信息,判断显示与隐藏

<Footer v-show="$route.path == '/home' || $route.path == '/search'" ></Footer>

6.2?配置路由的时候,可以给路由添加原信息(meta),路由需要配置对象

{
  path:"/home",
  component:Home,
  meta:{show:true}
},

7.路由传参

7.1 路由跳转的几种方式

声明式导航:router-link(一定要有to属性),实现路由跳转

编程式导航:利用的是组件实例的$router.push / replace方法,可以实现路由跳转(可以书写自己的业务)

7.2?路由传参,参数有几种写法

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

query参数:不属于路径中的一部分,类似于ajax中的queryString? (/home?k=v&k=v)

//methods里放gosearch
goSearch() {
        //路由传参
        //第一种:字符串形式
        // this.$router.push('/search/' + this.keyword +'?k='+this.keyword.toUpperCase());
        //第二种:模板字符串
        // this.$router.push(`/search/${this.keyword}?k=${this.keyword.toUpperCase()}`);
        //第三种:对象写法
        this.$router.push({name:"search",params:{keyword:this.keyword},query:{k:this.keyword.toUpperCase()}})
      }

7.3?路由传参面试题

1.路由传递参数(对象写法)path是都可以结合params参数一起使用?

不可以,使用params参数一定要给路由设置name

2.如何指定params参数可传可不传?

比如:配置路由时已经占位了(params参数),但是路由跳转的时候就不传递

路径会出现问题?

http://localhost:8080/#/?k=QWE,按理来说他应该是 http://localhost:8080/#/search?k=QWE

如果想让params参数可传可不传,可以在配置路由的时候在参数后边加个问号

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

传递空串路径还是会出问题

这时使用undefined解决:params参数可以传递也可以不传递(空字符串) =>??'? ' ||undefinded

this.$router.push({name:"search",params:{keyword:''||undefinded},query:{k:this.keyword.toUpperCase()}})

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

可以,有三种写法

1.在配置路由时配置 props:true?,但是这种方法只能传递params参数

router?index.js

{
      path:"/search/:keyword?",
      component:Search,
      meta:{show:true},
      name:"search",
      //7.3路由组件能不能传递props数据
      //布尔值写法:只能传递params参数
      //props:true,
    },

search?index.vue?接收props参数

<template>
  <div>
    <h1>params参数 {{$route.params.keyword}}</h1>
    <h1>query参数 {{$route.query.k}}</h1>
    <h1>props{{keyword}}</h1>
  </div>
</template>
<script>
  export default {
    name:'',
    //路由组件可以传递props
    props:['keyword']
  }
</script>

2.对象写法:额外的给路由组件传递的一些props

props:{a:1,b:2}

3.函数写法(常用):可以params参数、query参数、通过props参数传递

props:($route) => {
        return {keyword:$route.params.keyword,k:$route.query.k}
      }

7.4?注意

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

--路由跳转有编程式导航和声明式导航

--声明式导航没有上述警告,因为vue-router底层已经处理好了

1.1 为什么编程式导航有这种问题?

--"vue-router": "3.5.3"引入了promise,push方法执行返回的而是promise,新的一轮promise没有接收到成功or失败的值,所以会有警告

1.2 解决:在push方法传参的时候加两个() => {},目的是为了传递响应的成功、失败的回调函数,可以捕获当前错误

this.$router.push({name:"search",params:{keyword:''||undefinded},query:{k:this.keyword.toUpperCase()}},() => {} ,()=> {})

但是治标不治本

1.3

this:当前组件实例(search)

this.$router属性:当前的这个属性,属性值是VueRouter类的一个实例,当在入口文件注册路由的时候,给组件实例添加的$router $route这个属性

push方法:VueRouter类的一个实例

1.4?重写push方法

//先把VueRouter原型对象的push,保存一份
let originPush = VueRouter.prototype.push;
let originReplace = VueRouter.prototype.replace;
//重写push replace方法
//第一个参数:告诉原来的push方法往哪里跳转(传递哪些参数)
//第二个参数:成功的回调  第三个参数:失败的回调
VueRouter.prototype.push = function(location,resolve,reject){
  if(resolve && reject) {
    //call apply区别
    //相同点:都可以调用函数一次,都可以篡改函数的上下文一次
    //不同点:call与apply传递参数,call传参用逗号隔开,apply传参用数组
    originPush.call(this,location,resolve,reject)
  } else {
    originPush.call(this,location,()=>{},()=>{})
  }
}
VueRouter.prototype.replace = function(location,resolve,reject){
  if(resolve && reject) {
    originReplace.call(this,location,resolve,reject)
  } else {
    originReplace.call(this,location,()=>{},()=>{})
  }
}

Home模块组件拆分

--完成静态页面

--拆分出静态组件

--获取服务器的数据进行展示

--动态业务

1.三级联动组件

--由于三级联动在Home、Search、Detail都使用到了,所以把三级联动注册为全局组件

好处:只需要注册一次,就可以在项目任意地方使用

main.js

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

2.?完成其余静态组件

HTML+CSS+资源

3.postman测试接口

经过postman工具测试,接口是没问题的

如果服务器返回的数据code字段200,代表服务器返回数据成功

整个项目,接口前缀都有api?

4.axios二次封装

XMLHttpRequest、fetch、JQ、axios

4.1 为什么要二次封装axios

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

响应拦截器:当服务器数据返回之后可以处理一些业务

4.2?项目中的API文件夹

一般用来放axios的

接口当中:路径都带有api

baseURL:“/api”? //基础路径,发请求的时候路径当中都会出现api,就不用自己写了

4.3?如果axios基础不好,可以参考npm和axios文档

5.接口统一管理

如果项目很小:完全可以在组件的盛行周期函数中发请求

如果项目很大:接口统一管理

5.1?跨域问题

跨域:协议、域名、端口号有一个不同

http://localhost:8080/? ? ?---前端本地服务器

http://39.98.123.211? ? ? ?---后台服务器

解决:JSONP、CORS、代理

代理跨域

vue.config.js

module.exports = {
  //关闭eslint
  lintOnSave:false,
  devServer: {
    //代理跨域
    proxy: {
      '/api':{
        target:'http://39.98.123.211',
        //pathRewrite:{'^/api':''},
      },
    },
  },
}

6.nprogress进度条的使用

一有请求,进度条就开始使用

安装nprogress插件再引入

request.js

//对于axios进行二次封装
import axios from "axios";
//引入进度条
//start:进度条开始 done:进度条结束
import nprogress from 'nprogress'
//引入进度条样式
import "nprogress/nprogress.css"

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

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

  //进度条开始动
  nprogress.start();

  return config;
});

//响应拦截器
requests.interceptors.response.use((res) => {
  //服务器成功的回调函数:服务器响应数据回来以后,响应拦截器可以检测到,可以做一些事情
  
  //进度条结束
  nprogress.done();


  return res.data;
},(error) => {
  //响应失败的回调函数
  //终结promise链
  return Promise.reject(new Error('false'));
});


//对外暴露
export default requests;

6. vuex状态管理库

6.1?vuex是什么

vuex官方插件,状态管理库,集中式管理项目中组件共用的数据

并不是全部的项目都需要vuex,如果项目很小,完全不需要vuex;如果项目很大,组建很多数据很多维护费劲,就需要

vuex核心概念:state、mutations、actions、getters、modules

6.2?vuex的基本使用

store/search/index.js

//search模块的小仓库

//state:仓库存储数据的地方
const state = {};

//mutations:修改state的唯一手段
const mutations = {};

//actions:处理action,可以书写自己的业务逻辑,也可以处理异步
const actions = {};

//getters:理解为计算属性,用于简化仓库数据,让组件获取仓库的数据更加方便
const getters = {};

export default {
  state,
  mutations,
  actions,
  getters
}

6.3?vuex实现模块开发

store/index.js

import Vue from 'vue'
import Vuex from 'vuex'

//需要使用插件
Vue.use(Vuex)

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

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

7.完成TypeNav三级联动展示数据业务

获取服务器真实的数据进行展示

store/home/index.js

//home模块的小仓库

import { reqCategoryList } from "@/api";

const state = {
  //state中的数据默认初始值别乱写,服务器返回的是对象就是对象,服务器返回的是数组就是数组
  categoryList:[],
};
const mutations = {
  CATEGORYLIST(state, categoryList) {
    state.categoryList = categoryList
  }
};
const actions = {
  //通过api里面的接口函数调用,向服务器发请求获取服务器数据
  async categoryList({ commit }) {
    let result = await reqCategoryList();
    if (result.code == 200) {
      commit('CATEGORYLIST', result.data)
    }
  }
};
const getters = {};

export default {
  state,
  mutations,
  actions,
  getters
}

TypeNav/index.vue

<template>
  <div class="type-nav">
    <div class="container">
      <h2 class="all">全部商品分类</h2>
      <nav class="nav">
        <a href="###">服装城</a>
        <a href="###">美妆馆</a>
        <a href="###">尚品汇超市</a>
        <a href="###">全球购</a>
        <a href="###">闪购</a>
        <a href="###">团购</a>
        <a href="###">有趣</a>
        <a href="###">秒杀</a>
      </nav>
      <div class="sort">
        <div class="all-sort-list2">
          <div class="item" v-for="(c1,index) in categoryList" :key="c1.categoryId">
            <h3>
              <a href="">{{c1.categoryName}}</a>
            </h3>
            <div class="item-list clearfix">
              <div class="subitem" v-for="(c2,index) in c1.categoryChild" :key="c2.categoryId">
                <dl class="fore">
                  <dt>
                    <a href="">{{c2.categoryName}}</a>
                  </dt>
                  <dd>
                    <em v-for="(c3,index) in c2.categoryChild" :key="c3.categoryId">
                      <a href="">{{c3.categoryName}}</a>
                    </em>
                  </dd>
                </dl>
              </div>
            </div>
          </div>
          
        </div>
      </div>
    </div>
  </div>
</template>
<script>
  import {mapState} from 'vuex';
  export default {
    name: 'TypeNav',
    //组件挂载完毕,可以向服务器发请求
    mounted() {
      //通知vuex发请求,获取数据,存储于仓库当中
      this.$store.dispatch('categoryList');
    },
    computed: {
      ...mapState({
        //右侧需要的是一个函数,当使用这个计算属性的时候,右侧函数会立即执行
        //注入一个参数state,其实是大仓库中的数据
        categoryList:(state) => state.home.categoryList,
        
      })
    }
  }
</script>

7.1?完成一级分类动态添加背景颜色

解决1:采样样式完成(:hover)

解决2:通过JS完成

设置响应式数据currentIndex为-1,用来获取鼠标是在一级分类的哪一块(有index记录)

用?<h3 @mouseenter="changeIndex(index)">?获取鼠标index

采用changeIndex方法和leaveIndex方法来改变currentIndex的值

以?:class="{cur:currentIndex===index}"? 动态判断要不要加cur样式

index.vue

7.2?通过JS控制二三级商品分类的显示与隐藏

最开始的时候是通过CSS样式?display:block | none?显示与隐藏二三级商品分类

8.卡顿现象

正常:事件触发非常频繁,而且每一次的触发,回调函数都要去执行(如果事件很多,而回调函数内部都有计算,那么很可能出现浏览器卡顿)

lodash插件

封装函数的防抖与节流【闭包+延迟器】

lodash函数对外暴露的是 _函数

8.1 节流

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

8.2 防抖

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

8.3?完成三级联动节流的操作

index.vue

//节流
      changeIndex:throttle(function(index) {
        this.currentIndex = index;
      },50),

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

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

路由跳转:声明式导航:router-link ;?编程式导航:push、params

三级联动如果使用声明式导航router-link,可以实现路由的跳转与传递参数,但是会出现卡顿现象

router-link:是一个组件。当服务器的数据返回之后,循环出很多的router-link罪案【创建组件实例,把虚拟DOM转化为真实DOM】 ->?创建组件实例的时候会消耗很多内存

综上:最好的方法是利用编程式导航+事件委派(不在每一个a标签加click,在父标签加click)

9.1?利用事件委派会出现问题:1.无法确定点击的是a标签 2.如何获取参数

1.在a标签中添加自定义属性?:data-categoryName="c1.categoryName",其余节点无

2.在a标签中添加?:data-categoryId="c1.categoryId"?判断是几级标签

goSearch(event) {
        //最好的解决方案是利用编程式导航+事件委派
        //利用事件委派会出现问题:1.无法确定点击的是a标签 2.如何获取参数
        let element = event.target
        //获取到当前触发这个事件的节点【h3、a、dt】,需要带有data-categoryname这样的节点
        //节点有一个属性dataset属性:可以获取节点的自定义属性与属性值
        let {categoryname,category1id,category2id,category3id} = element.dataset;
        //如果标签身上有categoryname,一定是a标签
        if(categoryname) {
          //整理路由跳转的参数
          let location = {name:'search'};
          let query = {categoryName:categoryname}
          //区分a标签是几级
          if(category1id) {
            query.category1Id = category1id;
          }else if(category2id) {
            query.category2Id = category2id;
          }else {
            query.category3Id = category3id;
          }
          //整理参数
          location.query = query;
          this.$router.push(location)
        }
      }
    },
<em v-for="(c3,index) in c2.categoryChild" :key="c3.categoryId">
   <a :data-categoryName="c3.categoryName" :data-category3Id="c3.categoryId">{{c3.categoryName}}</a>
</em>

Home模块收尾

1.?开发search模块中的TypeNav商品分类菜单(过渡动画)

在TypeNav/index.js中加入代码,判断路径与否显示与否

 mounted() {
      //通知vuex发请求,获取数据,存储于仓库当中
      this.$store.dispatch('categoryList');
      //当组件挂载完毕,让show属性变为false
      //如果不是Home路由组件就隐藏
      if(this.$route.path !== "/home") {
        this.show = false;
      }
    },
    computed: {
      ...mapState({
        //右侧需要的是一个函数,当使用这个计算属性的时候,右侧函数会立即执行
        //注入一个参数state,其实是大仓库中的数据
        categoryList: (state) => state.home.categoryList,
      })
    },
    methods: {
      //节流
      changeIndex: throttle(function (index) {
        this.currentIndex = index;
      }, 50),

      //一级分类鼠标移出的事件回调
      leaveIndex() {
        this.currentIndex = -1;
        //当鼠标离开的时候让商品分类列表隐藏
        if(this.$route.path !== "/home") {
          this.show = false;
        }
      },
      enterShow() {
        this.show = true;
      }
    },
  }

过渡动画

前提:组件或者元素务必要有v-if或者v-show

      /*过渡动画样式*/
      /*进入的开始状态*/
      .sort-enter {
        height: 0px;
      }

      .sort-enter-to {
        height: 461px;
      }

      /*定义动画时间、速率*/
      .sort-enter-active {
        transition: all .3s linear;
      }

2. 优化TypeNav

从性能角度出发

让TypeNav中的请求只发送一次

把之前在TypeNav中的发送请求功能放到App.vue里

3.合并params和query参数

使点击三级导航and搜索按钮时的参数都能够传递

Header/index.vue

TypeNav/index.vue

4.开发Home首页中的ListContainer组件与Floor组件

注意:服务器返回的数据只有商品菜单分类,ListContainer和Floor中没有服务器返回的数据,使用mock

前端插件地址:https://docschina.org/

mock数据(模拟):如果想模拟数据,要使用mock.js插件。mock数据不会和服务器进行任何的通信

4.1?mockjs使用步骤

1.在项目src文件夹中创建mock文件夹

2.准备json数据(mock文件夹中创建响应的JSON文件) ---格式化一下,别有空格

3.把mock数据需要的图片放置到public文件夹中【public文件夹在打包的时候,会把响应的资源原封不动打包到dist文件夹中】

4.创建mockServer.js,通过mockjs插件实现模拟数据

//JSON数据格式没有对外暴露,但是可以引入
//webpack默认对外暴露的:图片,JSON数据格式

5.mockServer.js文件在入口文件中引入(至少要执行一次才能模拟数据)

5.?swiper

5.1?swiper基本使用

在new Swiper实例之前,页面中的结构必须要存在

第一个参数可以是字符串也可以是真实DOM节点

5.2?swiper引入

1.引包

2.页面中结构务必要有

3.new Swiper实例【轮播图添加动态效果】

6.ListContainer组件开发

安装Swiper插件:安装5版本

在此项目中?swiper不能放到mouted中

因为dispatch当中涉及到异步语句,导致v-for遍历的时候结构还没有完全,所以不行

1.可以放update中,但是不好 =>?如果之后还有响应式数据,那更改数据swiper会重新初始化

2.放定时器里,可以但是有bug,需要时间

3.最佳解决方案:watch数据监听+nextTick

如果只用watch

//通过watch监听bannerList属性的属性值变化

//如果执行handler方法,代表组件实例身上这个属性的数据已经有了

//当前函数的执行只能保证bannerList数据已经有了,但是没办法保证v-for已经执行结束了

//v-for执行完毕才有结构【在watch中没发保证】

watch: {
      //监听bannerList数据变化
      bannerList: {
        handler(newValue, oldValue) {
          //通过watch监听bannerList属性的属性值变化
          //如果执行handler方法,代表组件实例身上这个属性的数据已经有了
          //当前函数的执行只能保证bannerList数据已经有了,但是没办法保证v-for已经执行结束了
          //v-for执行完毕才有结构【在watch中没发保证】
          //nextTick:在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取修改后的DOM
          this.$nextTick(() => {
            //当执行这个回调的时候,保证服务器的数组已经回来了,v-for执行完毕了,轮播图的结构一定有了
            var mySwiper = new Swiper(document.querySelector('.swiper-container'), {
              loop: true,
              //分页器
              pagination: {
                el: '.swiper-pagination',
                //点击圆点切换页
                clickable: true,
              },
              //前后按钮
              navigation: {
                nextEl: '.swiper-button-next',
                prevEl: '.swiper-button-prev',
              }
            })
          })
        }
      }

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

7.Floor组件的开发

7.1?floorList这个action在哪里触发

是需要在Home路由组件中触发的,不能在Floor组件内部发action,因为我们需要v-for遍历floor

7.2?v-for也可以在自定义标签中使用

<Floor v-for="(floor,index) in floorList" :key="floor.id"></Floor>

7.3?组件通信的方式

props:用于父子组件通信(3种写法)

自定义事件: @on @emit?可以实现子给父传值

全局事件总线:$bus?全能

pubsub-js:在vue中几乎不用?全能

插槽

vuex

7.4?动态展示Floor组件

Floor/index.vue

mounted() {
      var mySwiper = new Swiper(this.$refs.cur, {
        loop: true,
        //分页器
        pagination: {
          el: '.swiper-pagination',
          //点击圆点切换页
          clickable: true,
        },
        //前后按钮
        navigation: {
          nextEl: '.swiper-button-next',
          prevEl: '.swiper-button-prev',
        }
      })
    },

为什么第一次在ListContainer里就不能把轮播图放mouted里,现在就可以?

第一次书写轮播图的时候是在当前组件内部发请求,动态渲染结构【前提至少服务器数据需要回来】,因此之前写法不行

现在已经有数据了【数据是父亲通过props传过来的,在展示之前早就传过来了】,在mouted里写new?swiper运行的时候,DOM已经渲染完了

8.将轮播图拆分成公用的全局组件

Carousel/index.vue

<template>
  <div class="swiper-container" ref="cur">
    <div class="swiper-wrapper">
      <div class="swiper-slide" v-for="(carousel,index) 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>
</template>
<script>
  import Swiper from 'swiper'
  export default {
    name: 'Carousel',
    props: ['list'],
    watch: {
      list: {
        //立即监听:不管是否有变化,立即监听
        //因为list => 父亲给的时候就没有变化了,所以watch无法监听list
        immediate: true,
        handler() {
          this.$nextTick(() => {
            var 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">

</style>

ListContainer/index.vue

<Carousel :list="bannerList"></Carousel>

Floor/index.vue

? <Carousel :list="list.carouselList"></Carousel>

以后在开发项目的时候,如果看到某一个组件在很多地方都使用,可以把它变成全局组件,注册一次,可以放在任意地方使用,共用的组件 |?非路由组件放到components文件夹中

Search模块开发

1.静态页面 +?静态组件拆分

2.发请求(API)

3.vuex(三连环)

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

1.Object.assign()

合并参数

Search/index.vue

<template>
  <div>
    <TypeNav />
    <div class="main">
      <div class="py-container">
        <!--bread-->
        <div class="bread">
          <ul class="fl sui-breadcrumb">
            <li>
              <a href="#">全部结果</a>
            </li>
          </ul>
          <ul class="fl sui-tag">
            <li class="with-x">手机</li>
            <li class="with-x">iphone<i>×</i></li>
            <li class="with-x">华为<i>×</i></li>
            <li class="with-x">OPPO<i>×</i></li>
          </ul>
        </div>

        <!--selector-->
        <SearchSelector />

        <!--details-->
        <div class="details clearfix">
          <div class="sui-navbar">
            <div class="navbar-inner filter">
              <ul class="sui-nav">
                <li class="active">
                  <a href="#">综合</a>
                </li>
                <li>
                  <a href="#">销量</a>
                </li>
                <li>
                  <a href="#">新品</a>
                </li>
                <li>
                  <a href="#">评价</a>
                </li>
                <li>
                  <a href="#">价格?</a>
                </li>
                <li>
                  <a href="#">价格?</a>
                </li>
              </ul>
            </div>
          </div>
          <div class="goods-list">
            <ul class="yui3-g">
              <li class="yui3-u-1-5" v-for="(good,index) in goodsList" :key="good.id">
                <div class="list-wrap">
                  <div class="p-img">
                    <a href="item.html" target="_blank">
                      <img :src="good.defaultImg" /></a>
                  </div>
                  <div class="price">
                    <strong>
                      <em>¥</em>
                      <i>{{good.price}}</i>
                    </strong>
                  </div>
                  <div class="attr">
                    <a target="_blank" href="item.html"
                      title="促销信息,下单即赠送三个月CIBN视频会员卡!【小米电视新品4A 58 火爆预约中】">{{good.title}}</a>
                  </div>
                  <div class="commit">
                    <i class="command">已有<span>2000</span>人评价</i>
                  </div>
                  <div class="operate">
                    <a href="success-cart.html" target="_blank" class="sui-btn btn-bordered btn-danger">加入购物车</a>
                    <a href="javascript:void(0);" class="sui-btn btn-bordered">收藏</a>
                  </div>
                </div>
              </li>

            </ul>
          </div>
          <div class="fr page">
            <div class="sui-pagination clearfix">
              <ul>
                <li class="prev disabled">
                  <a href="#">?上一页</a>
                </li>
                <li class="active">
                  <a href="#">1</a>
                </li>
                <li>
                  <a href="#">2</a>
                </li>
                <li>
                  <a href="#">3</a>
                </li>
                <li>
                  <a href="#">4</a>
                </li>
                <li>
                  <a href="#">5</a>
                </li>
                <li class="dotted"><span>...</span></li>
                <li class="next">
                  <a href="#">下一页?</a>
                </li>
              </ul>
              <div><span>共10页&nbsp;</span></div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
  import SearchSelector from './SearchSelector/SearchSelector'
  import { mapGetters } from 'vuex'
  export default {
    name: 'Search',
    components: {
      SearchSelector
    },
    data() {
      return {
        searchParams: {
          //带给服务器的参数
          category1Id: '',
          category2Id: '',
          category3Id: '',
          categoryName: '',
          //输入框关键字
          keyword: '',
          //排序
          order: '',
          //分页器用参数,代表现在是第几页
          pageNo: 1,
          //每一页展示数据个数
          pageSize: 5,
          //平台售卖属性
          props: [],
          //品牌
          trademark: ''
        }
      }
    },
    beforeMount() {
      //复杂写法
      // this.searchParams.category1Id = this.$route.query.category1Id
      //简单写法:使用Object.assign:ES6新增语法,合并对象
      //在发请求之前把接口需要传递的参数进行整理,【在给服务器发请求之前,整理好参数,服务器就会返回查询的数据】
      Object.assign(this.searchParams,this.$route.query,this.$route.params)
    },
    mounted() {
      //在发请求之前带给服务器参数【searchParams参数发生变化有数值带给服务器】
      this.getData();
    },
    computed: {
      //mapGetters里面的写法:传递的是数组,因为getters计算是没有划分模块的
      ...mapGetters(['goodsList'])
    },
    methods: {
      //向服务器发请求获取search模块数据(根据参数不同返回不同的模块数据进行展示)
      //把这次请求封装为一个函数,当需要调用的时候调用即可
      getData() {
        this.$store.dispatch('getSearchList', this.searchParams)
      },
    },
    //数据监听:监听组件实例身上的属性的属性变化
    watch: {
      //监听属性
      $route(newValue,oldValue) {
        //再次发请求之前整理带给服务器的参数
        Object.assign(this.searchParams,this.$route.query,this.$route.params)
        this.getData()
        //每次请求完毕应该把响应的1/2/3级分类的id清空
        this.searchParams.category1Id = '';
        this.searchParams.category2Id = '';
        this.searchParams.category3Id = '';
      }
    }
  }
</script>

2.面包屑问题

2.1 动态开发面包屑分类中的名字

采用编程式导航路由跳转【自己跳自己】

<!--分类的面包屑-->
<li class="with-x" v-if="searchParams.categoryName">{{searchParams.categoryName}}
  <i @click="removeCategoryName">×</i>
</li>
    //删除分类名字
      removeCategoryName() {
        //带给服务器的参数都是可有可无的
        //设置为undefined就不会带给服务器了,性能更好
        this.searchParams.categoryName = undefined;
        this.searchParams.category1Id = undefined;
        this.searchParams.category2Id = undefined;
        this.searchParams.category3Id = undefined;
        this.getData()
        //地址栏也需要修改:进行路由跳转,自己跳自己
        //本意是删除query参数,如果路径有params参数则不应该删除,路由跳转应该带着
        if(this.$route.params) {
          this.$router.push({name:'search',params:this.$route.params});
        }
      },

2.2?当面包屑中的关键字清楚之后,需要让兄弟组件Header组件中的关键字消除

设计组件通信:

props:父子

自定义事件:子父

vuex:万能

插槽:父子

pubsub-js:完成

$bus:全局事件总线

SearchSelector.vue

<template>
  <div class="clearfix selector">
    <div class="type-wrap logo">
      <div class="fl key brand">品牌</div>
      <div class="value logos">
        <ul class="logo-list">
          <li v-for="(trademark,index) in trademarkList" :key="trademark.tmId" @click="tradeMarkHandler(trademark)">{{trademark.tmName}}</li>
        </ul>
      </div>
      <div class="ext">
        <a href="javascript:void(0);" class="sui-btn">多选</a>
        <a href="javascript:void(0);">更多</a>
      </div>
    </div>
    <div class="type-wrap" v-for="(attr,index) in attrsList" :key="attr.attrId">
      <div class="fl key">{{attr.attrName}}</div>
      <div class="fl value">
        <ul class="type-list">
          <li v-for="(attrValue,index) in attr.attrValueList" :key="index"  @click="attrValueHandler(attr,attrValue)">
            <a>{{attrValue}}</a>
          </li>          
        </ul>
      </div>
      <div class="fl ext"></div>
    </div>
  </div>
</template>

<script>
  import { mapGetters } from 'vuex';
  export default {
    name: 'SearchSelector',
    computed: {
      ...mapGetters(['trademarkList', 'attrsList'])
    },
    methods: {
      //品牌的事件处理函数
      tradeMarkHandler(trademark) {
        //点击品牌,整理参数,向服务器发请求获取响应的数组进行展示
        //父组件发请求:因为父组件的searchParams参数是带给服务器的参数,子组件需要把点击的信息传给父亲
        //自定义事件
        this.$emit('trademarkInfo',trademark)
      },
      attrValueHandler(attr,attrValue) {
        this.$emit('attrValueInfo',attr,attrValue)
      }

    },
  }
</script>

Search/index.vue


            <!--分类的面包屑-->
            <li class="with-x" v-if="searchParams.categoryName">{{searchParams.categoryName}}
              <i @click="removeCategoryName">×</i>
            </li>
            <!--关键字的面包屑-->
            <li class="with-x" v-if="searchParams.keyword">{{searchParams.keyword}}
              <i @click="removeKeyword">×</i>
            </li>
            <!--品牌的面包屑-->
            <li class="with-x" v-if="searchParams.trademark">{{searchParams.trademark.split(":")[1]}}
              <i @click="removeTrademark">×</i>
            </li>
            <!--属性的面包屑-->
            <li class="with-x" v-for="(attrValue,index) in searchParams.props" :key="index">{{attrValue.split(':')[1]}}
              <i @click="removeAttr(index)">×</i>
            </li>
          </ul>
        </div>

        <!--selector-->
        <SearchSelector @trademarkInfo="trademarkInfo" @attrValueInfo="attrValueInfo"/>

      
<script>
  import SearchSelector from './SearchSelector/SearchSelector'
  import { mapGetters } from 'vuex'
  export default {
    name: 'Search',
    components: {
      SearchSelector
    },
    data() {
      return {
        searchParams: {
          //带给服务器的参数
          category1Id: '',
          category2Id: '',
          category3Id: '',
          categoryName: '',
          //输入框关键字
          keyword: '',
          //排序
          order: '',
          //分页器用参数,代表现在是第几页
          pageNo: 1,
          //每一页展示数据个数
          pageSize: 5,
          //平台售卖属性
          props: [],
          //品牌
          trademark: ''
        }
      }
    },
   
    methods: {
      //向服务器发请求获取search模块数据(根据参数不同返回不同的模块数据进行展示)
      //把这次请求封装为一个函数,当需要调用的时候调用即可
      getData() {
        this.$store.dispatch('getSearchList', this.searchParams)
      },
      //删除分类名字
      removeCategoryName() {
        //带给服务器的参数都是可有可无的
        //设置为undefined就不会带给服务器了,性能更好
        this.searchParams.categoryName = undefined;
        this.searchParams.category1Id = undefined;
        this.searchParams.category2Id = undefined;
        this.searchParams.category3Id = undefined;
        this.getData()
        //地址栏也需要修改:进行路由跳转,自己跳自己
        //本意是删除query参数,如果路径有params参数则不应该删除,路由跳转应该带着
        if(this.$route.params) {
          this.$router.push({name:'search',params:this.$route.params});
        }
      },
      removeKeyword() {
        this.searchParams.keyword = undefined;
        this.getData()
        //通知兄弟组件Header清除关键字
        this.$bus.$emit('clear')
        //路由跳转
        if(this.$route.query) {
          this.$router.push({name:'search',query:this.$route.query});
        }
      },
      //自定义事件回调
      trademarkInfo(trademark) {
        //整理参数
        this.searchParams.trademark = `${trademark.tmId}:${trademark.tmName}`
        this.getData();
      },
      removeTrademark() {
        this.searchParams.trademark = undefined
        this.getData()
      },
      attrValueInfo(attr,attrValue) {
        let props = `${attr.attrId}:${attrValue}:${attr.attrName}`
        //数组去重
        if(this.searchParams.props.indexOf(props) == -1) {
          this.searchParams.props.push(props)
        }
        this.getData()
      },
      removeAttr(index) {
        this.searchParams.props.splice(index,1)
        this.getData()
      }

    },
    //数据监听:监听组件实例身上的属性的属性变化
    watch: {
      //监听属性
      $route(newValue,oldValue) {
        //再次发请求之前整理带给服务器的参数
        Object.assign(this.searchParams,this.$route.query,this.$route.params)
        this.getData()
        //每次请求完毕应该把响应的1/2/3级分类的id清空
        this.searchParams.category1Id = undefined;
        this.searchParams.category2Id = undefined;
        this.searchParams.category3Id = undefined;
      }
    }
  }
</script>

2.3?排序

order属性最多有多少种写法

1:desc
1:asc
2:desc
2:asc

通过order中是包含1还是包含2来确定active给综合还是价格,默认是1综合

考虑类名问题

isOne() {
        return this.searchParams.order.indexOf('1') !== -1
      },
      isTwo() {
        return this.searchParams.order.indexOf('2') !== -1
      }

考虑谁应该有箭头:谁有类名谁有箭头

3.分页器

为什么要分页?

电商平台展示的数据很多,采用分页,防止数据一次性加载过多

3.1?分页器的展示需要哪些数据

需要知道当前是第几页:pageNo

需要知道每一页需要展示多少条数据:pageSize

需要知道整个分页器一共有多少条数据:total-----可以知道一共有多少页

需要知道分页器连续页面个数:一般5或者7【都是奇数 =>?为了对称】

3.2?自定义分页器

在开发的时候先自己传递假的数据进行调试,直到调试成功再用服务器调试

对于分页器而言,很重要的地方是要算出连续页面的起始数据和结束数据

startNumAndEndNum() {
      const {continues,pageNo,totalPage} = this
      //先定义两个变量存储开始数据和结束数据
      let start = 0,end = 0;
      //要连续的话至少是5页,但是可能不够5页,所以要判断
      if(continues > totalPage) {
        start = 1
        end = totalPage
      } else {
        start = pageNo - parseInt(continues/2) 
        end = pageNo + parseInt(continues/2)
        if(start < 1) {
          start = 1
          end = continues
        }
        if(end > totalPage) {
          start = totalPage - continues + 1
          end = totalPage
        }
      }
    }

Pagination/index.vue

<template>
  <div class="pagination">
    <button :disabled="pageNo === 1" @click="$emit('getPageNo',pageNo-1)">上一页</button>
    <button v-if="startNumAndEndNum.start>1" @click="$emit('getPageNo',1)" :class="{active:pageNo === 1}">1</button>
    <button v-if="startNumAndEndNum.start>2">···</button>

    <!--中间部分-->
    <button v-for="(page,index) in startNumAndEndNum.end" :key="index" v-if="page>=startNumAndEndNum.start" @click="$emit('getPageNo',page)" :class="{active:pageNo == page}">{{page}}</button>
    
    <button v-if="startNumAndEndNum.end < totalPage-1">···</button>
    <button v-if="startNumAndEndNum.end < totalPage" @click="$emit('getPageNo',totalPage)" :class="{active:pageNo === totalPage}">{{totalPage}}</button>
    <button :disabled="pageNo === totalPage" @click="$emit('getPageNo',pageNo+1)" >下一页</button>
    
    <button style="margin-left: 30px">共 {{total}} 条</button>
  </div>
</template>
<script>
export default {
  name:'Pagination',
  props:['pageNo','pageSize','total','continues'],
  computed: {
    //一共多少页
    totalPage() {
      //向上取整
      return Math.ceil(this.total/this.pageSize)
    },
    //算出连续页码的起始数字和结束数字[至少5页才能连续]
    startNumAndEndNum() {
      const {continues,pageNo,totalPage} = this
      //先定义两个变量存储开始数据和结束数据
      let start = 0,end = 0;
      //要连续的话至少是5页,但是可能不够5页,所以要判断
      if(continues > totalPage) {
        start = 1
        end = totalPage
      } else {
        start = pageNo - parseInt(continues/2) 
        end = pageNo + parseInt(continues/2)
        if(start < 1) {
          start = 1
          end = continues
        }
        if(end > totalPage) {
          start = totalPage - continues + 1
          end = totalPage
        }
      }
      return {start,end}
    },
  },
}
</script>

Detial模块开发

静态组件

发请求

vuex

动态展示组件

1.1?路由设置

当点击商品的图片的时候跳转到详情页面,路由跳转的时候需要带参数(产品的ID)

登录与注册业务

登录与注册业务和git是必会技能

1. 静态组件

assets文件夹:放置全部组件共用的静态资源

在样式中也可以使用@符号【src别名】,切记在前面加上~

2.注册业务

2.1?先不处理表单验证

2.2?设置接口 -仓库三连环 -?发请求

Register/index.vue

<template>
  <div class="register-container">
    <!-- 注册内容 -->
    <div class="register">
      <h3>注册新用户
        <span class="go">我有账号,去 <a href="login.html" target="_blank">登陆</a>
        </span>
      </h3>
      <div class="content">
        <label>手机号:</label>
        <input type="text" placeholder="请输入你的手机号" v-model="phone">
        <span class="error-msg">错误提示信息</span>
      </div>
      <div class="content">
        <label>验证码:</label>
        <input type="text" placeholder="请输入验证码" v-model="code">
        <button style="width: 100px;height: 38px;" @click="getCode">获取验证码</button>
        <span class="error-msg">错误提示信息</span>
      </div>
      <div class="content">
        <label>登录密码:</label>
        <input type="password" placeholder="请输入你的登录密码" v-model="password">
        <span class="error-msg">错误提示信息</span>
      </div>
      <div class="content">
        <label>确认密码:</label>
        <input type="password" placeholder="请输入确认密码" v-model="password1">
        <span class="error-msg">错误提示信息</span>
      </div>
      <div class="controls">
        <input name="m1" type="checkbox" :checked="agree">
        <span>同意协议并注册《尚品汇用户协议》</span>
        <span class="error-msg">错误提示信息</span>
      </div>
      <div class="btn">
        <button @click="userRegister">完成注册</button>
      </div>
    </div>

    <!-- 底部 -->
    <div class="copyright">
      <ul>
        <li>关于我们</li>
        <li>联系我们</li>
        <li>联系客服</li>
        <li>商家入驻</li>
        <li>营销中心</li>
        <li>手机尚品汇</li>
        <li>销售联盟</li>
        <li>尚品汇社区</li>
      </ul>
      <div class="address">地址:北京市昌平区宏福科技园综合楼6层</div>
      <div class="beian">京ICP备19006430号
      </div>
    </div>
  </div>
</template>

<script>
  export default {
    name: 'Register',
    data() {
      return {
        //收集表单数据--手机号
        phone: '',
        //验证码
        code: '',
        //密码
        password: '',
        //确认密码
        password1: '',
        //是否同意勾选
        agree: true,
      }
    },
    methods: {
      async getCode() {
        try {
          //简单判断一下
          const { phone } = this;
          phone && await this.$store.dispatch('getCode', phone);
          this.code = this.$store.state.users.code;
        } catch (error) {
        }
      },
      async userRegister() {
        try {
          const { phone, code, password, password1 } = this
          phone && code && password == password1 && await this.$store.dispatch('userRegister', { phone, code, password })
          this.$router.push('/login')
        } catch (error) {
          alert(error.message)
        }
      },
      
    },
  }
</script>

?store/users/index.js

import {reqGetCode,reqUserRegister,reqUserLogin} from '@/api'

const state = {
  code:''
}
const mutations = {
  GETCODE(state,code) {
    state.code = code
  }
}
const actions = {
  //获取验证码
  async getCode({commit},phone) {
    //获取验证码的接口把验证码返回了,但是正常来说是把验证码发到用户手机上
    let result = await reqGetCode(phone)
    if(result.code === 200) {
      commit('GETCODE',result.data);
      return 'ok'
    } else return Promise.reject(new Error('faile'))
  },
  //用户注册
  async userRegister({commit},user) {
    let result = await reqUserRegister(user)
    if(result.code == 200) {
      return 'ok'
    } else return Promise.reject(new Error('faile'))
  },
  //用户登录[token]
  async userLogin({commit},user) {
    let result = await reqUserLogin(user)
    console.log(result);
  }
}
const getters = {}

export default({
  state,
  mutations,
  actions,
  getters
})

3.登录业务

更改表单默认action

<form>
              <div class="input-text clearFix">
                <span></span>
                <input type="text" placeholder="邮箱/用户名/手机号" v-model="phone">
              </div>
              <div class="input-text clearFix">
                <span class="pwd"></span>
                <input type="text" placeholder="请输入密码" v-model="password">
              </div>
              <div class="setting clearFix">
                <label class="checkbox inline">
                  <input name="m1" type="checkbox" value="2" checked="">
                  自动登录
                </label>
                <span class="forget">忘记密码?</span>
              </div>
              <button class="btn" @click.prevent="userLogin">登&nbsp;&nbsp;录</button>
            </form>

还是跟往常一样

组件中设置方法->派发action到仓库->仓库中三连环(如果诶呦返回值就只actions就好)请求

4.token令牌

注册---通过数据库存储用户信息(名字、密码)

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

登录接口不完美:一般在公司当中服务器只会返回token,不会返回用户名啥的。前台持久化存储token,带着token找服务器要数据

很多网站用token,比如github、gitee

token很重要,前台要存储

而且只要有token就表示登录成功了

注意:vuex仓库存储数据不是持久化的,刷新就没啦

5.?登录【已token】

需要使用请求头把token带给服务器

服务器做处理

登录之后通过判断store里是否有name来修改Header组件的? 请登录?注册 ->? 用户名?退出登录

5.1?当用户注册完成,用户登录【用户+密码】向服务器发请求(组件派发action:userlogin)

登录成功获取token,存储于仓库中(非持久化),路由跳转到homt首页

5.2?在首页当中(mouted)派发action(getUserInfo)获取用户信息,以及动态展示header组建内容

5.3?一刷新home首页获取不到用户信息(vuex非持久化存储token)

使用本地存储

问题1:但是发现如果想要数据更新及时,需要在home组件mounted中发请求,如果想要所有页面都有已登录就需要所有组件的mouted中发请求??太麻烦?于是尝试在APP.vue中的mouted中发请求,但是mouted加载过早,第一次在没有token的时候就已经加载完了,所以必须要有第二次刷新才能显示已登录,故也不可行。

问题2:如果用户在已登录的时候还非要在url中输入login,怎么办

6.?退出登录

通知服务器 ->?清token ->?清本地的数据

6.1 退出登录需要做的事情

1.需要发请求通知服务器退出登录【清除数据:token]

2.清除项目当中的数据【userInfo、token】

3.然后跳转到home页面

6.2?导航守卫

导航:表示路由正在发生变化【路由跳转】

守卫:‘看门的’

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

--->找router/index.js

--->布局守卫,当有了token【已登录】的时候就禁止调到login

路由中的登录判断

//全局守卫:前置守卫
router.beforeEach(async(to,from,next) => {
  //to:可以获取到你要跳到的路由的信息
  //from:可以获取到从哪个路由来的信息
  //next:放行 next(path):放行到指定路由 next(false)
  let token = store.state.users.token;
  //用户信息
  let userInfo = store.state.users.userInfo.name;
  if(token) {
    //用户已经登录了还想去login,不能去,停留在首页
    if(to.path=='/login') {
      next('/home')
    } else {
      //登陆了,去的不是login
      next()
      //如果用户名已有
      if(name) {next()}
      //没有用户信息,派发action让仓库存储用户信息再跳转
      else {
        //获取用户信息在首页进行展示
        try {
          await store.dispatch('getUserInfo')
          next()
        } catch (error) {
          //catch的情况:token过期了
          //清除所有的用户信息回到登录页面
          await store.dispatch('userLogout')
          next('/login')
        }
      }
    }
  }
  //未登录 ,暂时未处理完毕
  else next()

})

以游客身份还是登录身份进去的购物车显示内容是后端做的

7.支付订单页面

用账号13700000000 111111

获取交易页面的前提是用户已经登录

动态展示服务器数据

8.提交订单

8.1?点击提交订单的按钮的时候

还需要向服务器发请求,把支付的一些信息传给服务器

8.2?从此刻开始项目不再使用vuex

数据存储在组件中

8.3?项目中别在生命周期函数中async

改成!

8.4获取支付信息

8.5element-UI?按需引入

配置文件发生变化项目需要重启

微信的二维码链接要通过插件生成

使用qrcode

Pay/index.js

<template>
  <div class="pay-main">
    <div class="pay-container">
      <div class="checkout-tit">
        <h4 class="tit-txt">
          <span class="success-icon"></span>
          <span class="success-info">订单提交成功,请您及时付款,以便尽快为您发货~~</span>
        </h4>
        <div class="paymark">
          <span class="fl">请您在提交订单<em class="orange time">4小时</em>之内完成支付,超时订单会自动取消。订单号:<em>{{orderId}}</em></span>
          <span class="fr"><em class="lead">应付金额:</em><em class="orange money">¥{{payInfo.totalFee}}</em></span>
        </div>
      </div>
      <div class="checkout-info">
        <h4>重要说明:</h4>
        <ol>
          <li>尚品汇商城支付平台目前支持<span class="zfb">支付宝</span>支付方式。</li>
          <li>其它支付渠道正在调试中,敬请期待。</li>
          <li>为了保证您的购物支付流程顺利完成,请保存以下支付宝信息。</li>
        </ol>
        <h4>支付宝账户信息:(很重要,<span class="save">请保存!!!</span>)</h4>
        <ul>
          <li>支付帐号:11111111</li>
          <li>密码:111111</li>
          <li>支付密码:111111</li>
        </ul>
      </div>
      <div class="checkout-steps">
        <div class="step-tit">
          <h5>支付平台</h5>
        </div>
        <div class="step-cont">
          <ul class="payType">
            <li><img src="./images/pay2.jpg"></li>
            <li><img src="./images/pay3.jpg"></li>
          </ul>

        </div>
        <div class="hr"></div>

        <div class="payshipInfo">
          <div class="step-tit">
            <h5>支付网银</h5>
          </div>
          <div class="step-cont">
            <ul class="payType">
              <li><img src="./images/pay10.jpg"></li>
              <li><img src="./images/pay11.jpg"></li>
              <li><img src="./images/pay12.jpg"></li>
              <li><img src="./images/pay13.jpg"></li>
              <li><img src="./images/pay14.jpg"></li>
              <li><img src="./images/pay15.jpg"></li>
              <li><img src="./images/pay16.jpg"></li>
              <li><img src="./images/pay17.jpg"></li>
              <li><img src="./images/pay18.jpg"></li>
              <li><img src="./images/pay19.jpg"></li>
              <li><img src="./images/pay20.jpg"></li>
              <li><img src="./images/pay21.jpg"></li>
              <li><img src="./images/pay22.jpg"></li>

            </ul>
          </div>

        </div>
        <div class="hr"></div>

        <div class="submit">
          <a class="btn" @click="open">立即支付</a>
          <!-- <router-link class="btn" to="/paysuccess">立即支付</router-link> -->
        </div>
        <div class="otherpay">
          <div class="step-tit">
            <h5>其他支付方式</h5>
          </div>
          <div class="step-cont">
            <span><a href="weixinpay.html" target="_blank">微信支付</a></span>
            <span>中国银联</span>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
  import QRCode from 'qrcode'
  export default {
    name: 'Pay',
    data() {
      return {
        payInfo: {},
        timer: null,
        code: '',
      }
    },
    computed: {
      orderId() {
        return this.$route.query.orderId
      }
    },
    //工作的时候尽量别在生命周期函数中使用async|await
    mounted() {
      this.getPayInfo()
    },
    methods: {
      //获取支付信息
      async getPayInfo() {
        let result = await this.$API.reqPayInfo(this.orderId)
        //如果成功:在组件中存储支付信息
        if (result.code == 200) {
          this.payInfo = result.data
        }
      },
      //弹框
      async open() {
        //生成二维码图片地址
        let url = await QRCode.toDataURL(this.payInfo.codeUrl)
        this.$alert(`<img src=${url} />`, "请微信支付", {
          dangerouslyUseHTMLString: true,
          //居中
          center: true,
          //是否设置取消按钮
          showCancelButton: true,
          //取消按钮的文本内容
          cancelButtonText: "支付遇见问题",
          confirmButtonText: "已支付成功",
          //取消右上角的×
          showClose: false,
          //关闭弹框的配置
          beforeClose: (type, instance, done) => {
            //type:区分取消|确定按钮 instance:当前组件实例 done:关闭弹出框的方法
            if (type == "cancel") {
              alert('请联系管理员')
              clearInterval(this.timer)
              this.timer = null
              //关闭弹窗
              done()
            } else {
              //判断是否真的支付成功
              //开发人员为了测试就先注释掉了
              //if (this.code == 200) {
                //成功
                clearInterval(this.timer)
                this.timer = null;
                done()
                //保存支付成功的code
                this.code = result.code
                //跳转到下一页路由
                this.$router.push('/paysuccess')
              //}
            }
          }
        })
        //将来需要知道支付成功还是失败 -> 后台才知道是否成功失败
        //支付成功:路由跳转
        //支付失败:提示信息
        //定时器没有,要开启一个新的定时器
        if (!this.timer) {
          this.timer = setInterval(async () => {
            //发请求获取用户支付状态
            let result = await this.$API.reqPayStatus(this.payInfo.orderId)
            if (result.code == 200) {
              //成功
              clearInterval(this.timer)
              this.timer = null;
              //保存支付成功的code
              this.code = result.code
              //关闭弹窗
              this.$msgbox.close()
              //跳转到下一页路由
              this.$router.push('/paysuccess')
            }
          }, 1000)
        }
      }

    },
  }
</script>

8.6?订单页面设置二级路由

{
    path: "/center",
    name: 'center',
    component: Center,
    meta: { show: true },
    //二级路由
    children:[
      {
        path:'myorder',
        component:MyOrder,
      },
      {
        path:'grouporder',
        component:GroupOrder,
      },
      {
        path:'/center',
        redirect:'/center/myorder'
      }
    ]
  },

重定向:如果跳转到center?直接显示center/myorder?

布置myOrder/index.js?放置分页器

8.7?完成个人中心

分页器

8.8?设置全局守卫

未登录访问交易相关、支付相关、用户中心相关跳转到登录页面

登录重定向

 async userLogin() {
        try {
          //登陆成功
          const { phone, password } = this
          phone && password && await this.$store.dispatch('userLogin', { phone, password })
          //登录的路由组件:需要看参数中是否包含query参数,如果有跳到query参数指定的地方,如果没有跳home
          let toPath = this.$route.query.redirect || '/home'
          this.$router.push(toPath)
        } catch (error) {
          alert(error.message)
        }
      },

8.9?路由独享守卫

登录状态时路由也不能瞎跳转,不能直接home跳paysuccess

8.10? 组件内守卫

9.图片懒加载

9.1插件:vue-lazyload

图片和JSON都是默认对外暴露的

9.2自定义插件

插件对外暴露一定是个对象

Vue.js的化简应该暴露一个install方法,这个方法的第一个参数是Vue构造器,第二个参数是一个可选的选项对象

10.表单验证

插件:vee-validate@2

了解即可

表单验证规则

11.路由懒加载

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

{
    path: "/home",
    component: () => import("@/pages/Home"),
    meta: { show: true }
  },

箭头函数在用户访问的时候才会执行

12.打包上线

npm run build

项目打包后,代码都是经过压缩加密的,如果运行时报错,输出的错误信息无法准确得知是哪里的代码报错

有了map就可以像未加密的代码一样,准确的输出哪一行有错

所以该文件如果项目不需要是可以去除的

vue.config.js配置

productionSource:false

12.1?nginx

在服务器上=> /root/jch/www/shangpinhui/dist

问题1.如果用户访问IP地址,怎么能知道访问的就是dist里边的index呢?

所以需要在服务器上设置一些东西

问题2.前台项目数据来自于其他的服务器

nginx是一个高性能的HTTP和反向代理wen服务器

?

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-03-08 22:20:01  更:2022-03-08 22:24:11 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:57:44-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码