目录
一、vue-cli脚手架初始化项目各文件夹
二、 项目的其他配置
2.1 项目运行,让浏览器自动打开
2.2 eslint校验功能(各种规范的报错)的关闭
2.3 src文件夹简写方法,配置别名
2.4 样式的引入
三. 本次项目路由的分析
五. 路由组件的搭建
5.1 配置路由
5.2 路由组件与非路由组件的区别?
5.3 路由的跳转的两种形式
5.4 重定向
6.1 v-if和v-show的区别
七. 路由传参
7.1 路由跳转的两种形式
7.2 路由传参(params、query)
7.3 路由传参相关问题
八. 重写push和replace
8.1 为什么编程式导航进行路由跳转时,会有这种警告错误?
九. Home首页组件拆分业务分析
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实现模块式开发的大致概念
十六. 完成三级联动动态背景颜色
十七. 函数的防抖与节流【lodash插件】
17.1 卡顿现象引入函数的防抖与节流
17.2 函数防抖的理解
17.3 函数节流的理解
十八. 三级联动
18.1 三级联动中的节流
18.2 三级联动中的路由跳转分析
18.3 用编程式导航 + 事件的委派
十九. Search模块
19.1 Search模块商品模块分类与过渡动画
19.3 合并query和params参数
19.4 mockjs模拟数据
二十. swiper的基本使用
20.1 Banner实现轮播图的第一种方法
二十一. 轮播图通过watch+nectTick解决问题
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的笔记进行二次整理。
方便以后查找,同时有需要改正或补充的,还请大家指教。
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文件:说明性文件。
在package.json文件中配置
{
......
"scripts": {
"serve": "vue-cli-service serve --open",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
......
eg:声明了变量,但未进行使用,则eslint校验工具会报错
module.exports = {
//关闭eslint
lintOnSave: false
}
①.jsconfig.json配置别名,可用@提示
【@代表的是src文件夹,常在js文件中使用。"exclude"表示“除了......”,意为配置的别名不能在"node_modules"和"dist"使用】
{
"compilerOptions":{
"baseUrl":"./",
"paths":{
"@/*":["src/*"]
}
//表示不能在"node_modules"和"dist"中使用这种配置的别名。
"exclude":["node_modules","dist"]
},
②.也可在.css中配置别名@提示 【在@前面加上~这个符号】
在public文件夹下的index.html中引入同级reset.css样式
vue-router 前端的路由:KV键值对。
key:URL(地址栏中的路径) value:相应的路由组件 注:本次项目是上中下结构
Home首页路由组件、Search搜索路由组件、login登录路由组件、Refister注册路由组件
Header组件:【存在于首页、搜索页】 Footer组件:【存在于首页、搜索页,但登录和注册页面没有】
本次项目主要关注业务、逻辑。
需通过less、less-loader【安装版本五npm install --save less less-loader@5
】进行处理less,将其变为css样式。
若想让组件识别less样式,需在style标签上加上lang=less。
经过上面的分析,本次项目的路由组件有四个:
Home首页、Search搜索、Login登录、Register注册。
components文件夹:经常放置非路由组件(共用的全局组件).
pages或views文件夹:经常放置路由组件
项目当中配置的路由一般放置在router文件夹中的index.js里。路由配置好之后在入口文件main.js中引入。
注册完路由,不管是路由组件、还是非路由组件,它们身上都会有$route、$router属性。
$route:一般获取路由信息【路径、query、params等】
$router:一般进行编程式导航,进行路由跳转【push或replace(其中一个是有缓存记忆的)】
路由组件 | 非路由组件(普通组件) |
一般放置在pages或views文件夹中 | 一般放置在components文件夹中。 |
一般需要在router文件夹中进行注册 (使用的名字即为组件的名字,例如router文件中的index.js的写法) 在App.vue中展示时, 用的是 |
一般以标签的形式使用 (例如App.vue里的 |
① 声明式导航:router-link
② 编程式导航:push或replace
编程式导航:声明式导航能做的,编程式导航都能做;而且编程式导航不仅可以进行路由跳转,还做一些其他业务逻辑。
redirect重定向:在项目跑起来时就访问该页面(该方法可以立马让其定向到首页)
在router的index.js里书写重定向代码:
//redirect重定向:在项目跑起来时就访问该页面(该方法可以立马让其定向到首页)
//配置路由,路由的单词都是小写的
export default [
......
{
path: '*',
redirect: './home'
}
......
]
显示or隐藏,可用v-if或者v-show。两者都是动态显示DOM元素。
v-if初始化较快,但代价较高;v-show初始化较慢,但切换成本低。
v-if | v-show |
---|---|
动态向Dom内添加或删除DOM元素 | 通过设置DOM元素的display样式属性控制显示或隐藏 |
切换有一个局部编译/卸载的过程,过程中可能会销毁和重建内部的事件监听和子组件。 | 单纯地基于css切换 |
惰性的; 初始值为假时,不作任何操作;只有在条件第一次为真时才开始局部编译。被缓存后,再次切换时进行局部卸载。 |
无论首次条件是否为真,都可被编译;被缓存且DOM元素保留。 |
有更高的切换消耗 | 有更高的初始化渲染消耗 |
适合运营条件不大可能改变的场景 | 适合频繁切换的场景 |
本次项目在Home首页、Search搜索显示Footer组件,但是在Login登录、Register注册时隐藏Footer组件。
可根据组件身上的$route获取当前路由的信息,通过路由路径判断Home和Footer这俩非路由组件的显示与隐藏。
配置路由时,可给路由添加路由元信息【meta】,注意路由配置对象的key不能乱写。
在APP.vue文件中:
在router文件的index.js中配置【meta】:
//配置路由
export default new VueRouter({
routes: [{
path: "/home",
component: Home,
meta: { show: true }
},{
path: "/search",
component: Search,
meta: { show: true }
},{
path: "/login",
component: Login,
meta: { show: false }
},{
path: "/register",
component: Register,
meta: { show: false }
},
//重定向:在项目跑起来时就访问该页面(该方法可以立马让其定向到首页)
{
path: '*',
redirect: './home'
}
]
})
了解URL语法格式
https://blog.csdn.net/Thaley/article/details/122286201
格式:protocol :// hostname[:port] / path / [;parameters][?query]#fragment
[ ]中的内容可有可无
即:协议 :// 主机名[:端口号] / 路径 / [;参数][?查询]#信息片断
eg:A -> B
①声明式导航:router-link(务必要有to属性),
②编程式导航:利用组件实例的$router.push或replace方法,可实现路由的跳转,也可书写一些自己的业务逻辑。
①params参数:属于路径的一部分,需注意,在配置路由的时候,需要占位。
如路径的path:"/search/:keyword",/:keyword即为params参数的占位符。
②query参数:不属于路径的一部分,类似于ajax中的queryString /home?k=v&kv=, 不需占位。
params参数 | query参数 |
属于路径的一部分,需要占位 | 不属于路径的一部分, 不需占位 |
用name来引入路由 | 用path来引入路由 |
取值用法:this.$route.params.name | 取值用法this.$route.query.name |
有些类似于post,在浏览器地址栏中不显示参数,所以params传值相对安全一些。 | 类似于我们ajax中get传参,在浏览器地址栏中显示参数。 |
传值一刷新就没。 | 传值刷新还存在。 |
问1. 路由传参(对象的写法)path是否可以结合params参数一起使用?
不能。路由跳转传参时,对象写法可为name、path形式,但是path这种写法不能与params参数一起使用。而且,路径参数缺失则无法匹配path里的占位符。
//错误写法【错误写法!】
this.$router.push({
path:"search",
params:{keyword:this.keyword},
query:{k:this.keyword.toUpperCase()}
})
//应改写成:
this.$router.push({
name:"search",
params:{keyword:this.keyword},
query:{k:this.keyword.toUpperCase()}
})
问2. 如何指定params参数可传可不传?
若路由要求传params参数,但未传,则URL会产生问题。
......
methods:{
this.$router.push(
{name:"search",
query:{
k:this.keyword.toUpperCase()//由其他程序可得,调用toUpperCase()返回ABC
}
}
)
},
......
// 上面的代码,得到的地址为:http://localhost:8080/#/?k=ABC 此地址缺少了/search。
......
methods:{
this.$router.push(
{name:"search",
params:{
keyword:this.keyword},
query:{
k:this.keyword.toUpperCase()//由其他程序可得,调用toUpperCase()返回ABC
}
}
)
},
......
// 则得到的地址为:http://localhost:8080/#/search/abc?k=ABC 此地址有/search
如何指定params参数可传或可不传?
在配置路由(router文件中的index.js时),改变path,在占位的后面加上一个问号?
即:path: "/search/:keyword?"
【这里的?表示params可传递或可不传递(和正则表达式雷同,?代表出现次数为0次或1次,即可有可无)】
//配置路由
export default new VueRouter({
//配置路由
routes: [
{
path: "/search/:keyword?",
component: Search,
meta: { show: true },
name: "search"
},
]
})
问3. params参数可传递也可不传递,但若传递空字符串,如何解决?
params传递空字符串也会产生URL问题。可通过在params传递的空字符串后面加上||undefined
进行解决。
......
methods:{
this.$router.push(
{name:"search",
params:{
keyword:''||undefined},
query:{
k:this.keyword.toUpperCase()
}
}
)
},
......
问4. 路由组件能否传递props数据?若有,有几种?
可以。有三种写法:
this.$router.push({
path:"search",
params:{keyword:this.keyword},
query:{k:this.keyword.toUpperCase()}
})
props: true
props: { a: 1, b: 2 }
props: ($route) => {
return { keyword: $route.params.keyword, k: $route.query.k }
}
引用路由传参的三种方法 - 醉温柔 - 博客园
点击当前页的某个按钮跳转到另外一个页面去,并将某个值带过去
查看详情
- 第一种方法:页面刷新数据不会丢失。直接调用$router.push 实现携带参数的跳转。
methods:{ insurance(id) { //直接调用$router.push 实现携带参数的跳转 this.$router.push({ path: `/particulars/${id}`, }) }
需要对应路由配置如下:
{ path: '/particulars/:id', name: 'particulars', component: particulars }
可以看出需要在path中添加/:id来对应 $router.push 中path携带的参数。在子组件中可以使用来获取传递的参数值
另外页面获取参数如下this.$route.params.id
- 第二种方法:页面刷新数据会丢失。通过路由属性中的name来匹配路由,通过params来传递参数。
methods:{ insurance(id) { this.$router.push({ name: 'particulars', params: { id: id } }) }
对应路由配置: 注意这里不能使用:/id来传递参数了,因为组件中,已经使用params来携带参数了。
{ path: '/particulars', name: 'particulars', component: particulars }
子组件中: 这样来获取参数
this.$route.params.id
- 第三种方法:使用path来匹配路由,然后通过query来传递参数。
这种情况下 query传递的参数会显示在url后面?id=?
methods:{ insurance(id) { this.$router.push({ path: '/particulars', query: { id: id } }) }
对应路由配置:
{ path: '/particulars', name: 'particulars', component: particulars }
对应子组件: 这样来获取参数
this.$route.query.id
--路由跳转的两种形式:①.声明式导航、②.编程式导航
为什么编程式路由跳转到当前路由(参数不变),多次执行会抛出NavigationDuplicated的警告错误?
--声明式导航没有这类问题,因为vue-router底层已经处理好了。
因为最新版本的vue-router引入了promise,promise需要传递成功和失败两个参数。
1.而push的返回是一个promise,所以通过给push方法传递相应的成功、失败的回调函数,可捕获到当前错误,可以解决。
this.$router.push({
name:"search",
params:{keyword:this.keyword},
query:{k:this.keyword.toUpperCase()}
},()=>{},()=>{})
// ()=>{},()=>{}表示执行成功和执行失败的回调函数
2.通过底部的代码,也可实现解决错误。
//若有成功和失败的回调,有则返回这俩值,无则自己手写() => {}
//先把VueRouter原型对象的push先保存一份
let originPush = VueRouter.prototype.push;
//先把VueRouter原型对象的replace先保存一份
let originReplace = VueRouter.prototype.replace;
//重写push|replace
//第一个参数location:告诉原来的push方法,你往哪里跳转(传递哪些参数)
//第二个参数resolve:成功回调。
//第三个参数reject:失败回调。
//call|apply区别
//相同点:都可调用函数一次,都可篡改函数的上下文一次。
//不同点:call与apply传递参数:call传递参数用逗号隔开,apply方法执行,传递数组。
VueRouter.prototype.push = function(location, resolve, reject) {
if (resolve && reject) {
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首页被拆分为了七个部分。
完成步骤:
-- 先把静态(css、html)页面完成。
-- 拆分出静态组件。
-- 获取服务器的数据进行展示。
-- 若有js的动态业务,则将其完成即可。
——由于三级联动,在Home、Search、Detai都有使用,故把三级联动注册为全局组件。优点:只需注册一次,即可在项目任意地方使用。
——本次讲解的组件是属于Home模块下的组件,所以暂时在pages的Home文件夹下新建个文件夹(这里取名为TypeNav)
需要耐心、需要注意:
HTML + CSS + 图片静态资源 —————信息的【结构、样式、图片资源】,
其文件的名字、文件引用路径需要一一对应
利用postman工具测试接口是否正常
--通过postman工具测试,接口无问题。
--若服务器返回的数据code字段为200(此项目设置的),代表服务器返回数据成功。
--整个项目,接口前缀会有/api字样
npm官网中的axios文档:axios - npm
axios文档网:axios中文文档|axios中文网 | axios
封装的方法有:XMLHttpRequest、fetch、JQ、axios
本次项目在api文件夹下的request.js文件进行axios的二次封装。
为了请求拦截器、响应拦截器。
**请求拦截器**:可以在发请求之前,可处理一些业务。
**响应拦截器**:当服务器数据返回后,可处理一些事情。
安装axios : npm install axios
使接口中,路径都带有/api:*baseURL:"/api"
//利用axios对axios进行二次封装
//故先引入axios
import axios from 'axios'
//1.利用axios对象的方法create,创建一个axios实例
//2.request就是axios,但需要稍微进行配置。
const requests = axios.create({
//配置对象
//baseURL基础路径,发请求时,路径当中会出现api
baseURL: "/api",
//timeout代表请求超时的时间
timeout: 5000,
});
//请求拦截器:可以在发请求之前,可处理一些业务。
requests.interceptors.request.use((config) => {
//config:配置对象,对象中有一个属性很重要(即:headers请求头)
return config;
})
//响应拦截器
requests.interceptors.request.use((res) => {
//响应成功的回调函数:服务器响应数据回来之后,响应拦截器可检测到,可做一些事情
return res.data;
}, (error) => {
//响应失败的回调函数
return Promise.reject(new Error('faile'));
});
//对外暴露
export default requests;
当项目很小时:完全可以在组件的生命周期函数中发请求。
当项目很大时:axios.get('xxx')
接口一般写在API文件夹的index.js文件中。
//当前模块的功能:API接口统一管理
//用到了二次封装的requests,需要将其引入
import requests from "./request";
//三级联动接口
//WORD接口文档中已经说明三级联动的接口为
// /api/product/getBaseCategoryList methods为get 无参数
//发请求:axios发请求返回的结果是Promise对象
export const reqCategoryList = () => {
//二次封装时,已经写了/api,所以这里不用再写/api了
return requests({
url: '/product/getBaseCategoryList',
method: 'get' })
}
//上面代码=>箭头函数的简写形式
/* export const reqCategoryList = () => requests({
url: '/product/getBaseCategoryList', method: 'get'
}) */
export const reqCategoryList = () => requests.get('/product/getBaseCategoryList')
写好接口,即可再书写仓库中的内容
(服务器与服务器之间是没有跨域问题的,只有浏览器与浏览器之间才有)
问1:什么是跨域?
协议、域名、端口号不同请求,称之为跨域。
`webpack.config.js`文件实质就是`vue.config.js`
`target`是要获取的那台服务器的IP地址
问2:跨域的解决方案是什么?
JSONP方法、CROS方法、代理(Proxy)方法【较为常用】
安装nprogress:npm install --save nprogress
start:进度条开始
done:进度条结束
在API文件夹里的request.js文件中写:
//利用axios对axios进行二次封装
//故先引入axios
import axios from 'axios'
//①.引入进度条
import nprogress from 'nprogress';
// console.log(nprogress),输出的内容其中start:表示进度条开始;done:表示进度条结束
//②.引入进度条样式
import "nprogress/nprogress.css";
//1.利用axios对象的方法create,创建一个axios实例
//2.request就是axios,但需要稍微进行配置。
const requests = axios.create({
baseURL: "/api",
timeout: 5000,
});
requests.interceptors.request.use((config) => {
nprogress.start() //③.进度条开始动
return config;
})
//响应拦截器
requests.interceptors.response.use((res) => {
nprogress.done() //③.进度条结束
return res.data;
}, (error) => {
return Promise.reject(new Error('faile'));
});
//对外暴露
export default requests;
进度条的颜色可修改,在nprogress.css中修改即可。
是官方提供的一个插件,状态管理库,集中式管理项目中组件共用的数据。
但并不是全部项目都需要Vuex,若项目很小,完全不需要;若项目很大、组件很多、数据很多,数据维护很费劲,则需要使用Vuex。Vuex可以模块化开发。
安装Vuex:npm install --save vuex
const state = {};
const mutations = {};
const actions = {};
const getters = {};
//写完接口,接着就写小仓库
import { reqCategoryList } from '@/api';
const state = {};
const mutations = {};
const actions = {};
const getters = {};
//默认暴露
export default {
state,
mutations,
actions,
getters
}
dispatch:含有异步操作,数据提交至actions,可用于向后台提交数据。
this.$store.dispatch('action', payload);
commit:同步操作,数据提交至 mutations ,可用于读取用户信息写到缓存里。
this.$store.commit('add',1);
项目很大、组件很多、数据很多,数据维护很费劲,则需要使用Vuex,Vuex可以模块化开发(把大仓库变成小仓库,按模块式进行存储)。
1.可以先给每一个组件模块来一个小仓库。
2.再将每个小仓库引入到store文件夹里的index.js(大仓库)中,用modules对外暴露,实现Vuex仓库模块式开发存储数据。
import Vue from 'vue';
import Vuex from 'vuex'
//需要使用插件一次
Vue.use(Vuex);
//引入小仓库
import home from '@/store/home';
//对外暴露Store类的一个实例
export default new Vuex.Store({
//实现Vuex仓库模块式开发存储数据
modules: {
home,
}
})
前提已经将axios二次封装好了,vuex也准备好了,vuex中的模块化也准备好了。
注:项目中所有的全局组件的文件最好都放在components文件夹中
多练练一眼识别几层分类的思想。
state 相当于 data(){return{}} 区域定义属性;
mutations 相当于 created() 或 mounted() 调用方法;
actions 相当于 methods 定义方法;
getters 相当于 computed 是为了简化数据而生的。
.item:hover{
background:skyblue;
}
全部商品分类
......
......
二、三级分类应有一个动态的样式,动态地显示和隐藏。
判断条件:只要谁有类名,谁就应该显示;currentIndex是否等于当前的索引值index。
:style="{display:currentIndex==index?'block':'none'}"
代码如下:
......
全部商品分类
......
......
十七. 函数的防抖与节流【lodash插件】
17.1 卡顿现象引入函数的防抖与节流
● 卡顿:由于用户的行为过快,导致浏览器反应不过来。若当前回调函数中有一些大量业务,则可能出现卡顿现象。
○ 正常情况下(用户慢慢操作):鼠标一进入每一个一级分类的h3,触发鼠标进入事件,可正常执行。
○ 非正常情况下(用户操作过快):本身全部的一级分类都应该触发鼠标进入事件,但经测试,只有部分h3触发了。
● 防抖:前面的所有的触发都被取消,最后一次执行在规定的时间之后才会触发,即若连续快速地触发,也只会执行最后一次。
● 节流:在规定的间隔时间范围内不会重复触发回调,只有大于这个事件间隔才会触发回调,把频繁触发变为少量触发,使浏览器有充分时间解析代码 。
17.2 函数防抖的理解
● 防抖:前面的所有的触发都被取消,最后一次执行在规定的时间之后才会触发,即若连续快速地触发,也只会执行最后一次。
安装lodash.js:npm install --save lodash
防抖事例代码_.debounce:
input.oninput = _.debounce(function() {
console.log('ajax发请求')
}, 1000)//放个延时器
防抖事例进行使用:
防抖
请输入搜索内容:
17.3 函数节流的理解
● 节流:在规定的间隔时间范围内不会重复触发回调,只有大于这个事件间隔才会触发回调,把频繁触发变为少量触发,使浏览器有充分时间解析代码 。
安装lodash.js:npm install --save lodash
节流事例用代码_.throttle:
button.onclick = _.throttle(function() {
//节流:目前这个回调函数5s执行一次
//加入这里有很多的业务代码,是可以给浏览器充裕的时间进行解析的。
count++;
span.innerHTML = count;
console.log('执行');
}, 5000);
节流事例进行使用:
节流
我是计数器0
十八. 三级联动
18.1 三级联动中的节流
throttle回调函数别用箭头函数,否则可能出现上下文this的问题。
......
methods: {
..........
changeIndex:throttle(function(index) {
//index:鼠标移上某一个一级分类的元素的索引值
this.currentIndex = index;
},50),
.......
}
......
18.2 三级联动中的路由跳转分析
三级联动用户的分类:一级分类、二级分类、三级分类
Home模块跳转到Search模块,一级会把用户选中的产品(产品名字、产品ID)在路由跳转的时候进行传递。
路由跳转的两种方式:
①.声明式导航:router-link
在本次项目中,router-link容易出现卡顿问题。
②.编程式导航:push|replace
本次较好的解决方案:编程式导航 + 事件委派
18.3 用编程式导航 + 事件的委派
解决方案:用编程式导航 + 事件委派 完成三级联动的路由跳转与传递参数的业务
利用事件委派存在一些问题以及解决方法:
①.点击的不一定都是a标签
利用自定义属性data-categoryname解决,区分是否为a标签,带有data-categoryname这样的节点【一定是a标签】。
②.如何获取子节点参数【产品名、产品id】
节点有一个属性dataset属性,可获取节点的自定义属性与属性值。
通过categoryname区分是否为a标签,通过category1id、category2id、category3id区分是否为一级、二级、三级标签。再通过函数中传入的event参数,获取当前点击事件,通过event.target属性获取当前节点,通过dataset属性获取节点属性信息。
......
......
......
通过categoryname区分是否为a标签,通过category1id、category2id、category3id区分是否为一级、二级、三级标签。再通过函数中传入的event参数,获取当前点击事件,通过event.target属性获取当前节点,通过dataset属性获取节点属性信息。
........
{{ c1.categoryName }}--{{ index }}
........
十九. Search模块
19.1 Search模块商品模块分类与过渡动画
全局组件可直接使用,不需引用
Search模块中 typeNav三级联动商品分类菜单 的过渡动画效果
过渡动画:前提组件、元素务必要有v-if或v-show指令才可以进行过渡动画。
1).判断是否为某个路由组件(可用路径判断)
......
......
......
......
2).过渡动画:
......
..........
......
19.2 typeNav商品分类列表的优化
在App.vue文件中
mounted(){
//这里的mounted只会执行一次,可把重复执行的,且只需执行一次即可的内容提前到这里写。
//通知Vuex发请求,获取数据,存储于仓库当中。
//派发一个action,获取商品分类的三级列表数据
this.$store.dispatch("categoryList");
}
19.3 合并query和params参数
- 如果有query没有params:
if(this.$route.params){
location.params = this.$route.params;
//动态给location配置对象添加query属性
location.query = query
//将其加入路由跳转
this.$router.push(location);
}
- 如果有params没有query:
if(this.$route.query){
let location ={name:"search",params:{keyword:this.keyword || undefined}};
location.query = this.$route.query;
this.$router.push(location);
}
19.4 mockjs模拟数据
mockjs只在前端页面生成数据,不会向后端发送请求。
安装mockjs:npm install --save mockjs
使用步骤:
1). 在项目中,src文件夹中创建mock文件夹。API文件夹中利用axios对axios进行二次封装创建mockAjax.js文件,mockAjax.js文件中的路径baseURL的值记得要更改,这里按需改成了/mock。
//API文件夹下的mockAjax.js文件
//1.利用axios对象的方法create,创建一个axios实例
//2.request就是axios,但需要稍微进行配置。
const requests = axios.create({
//配置对象
//baseURL基础路径,发请求时,路径当中会出现api
baseURL: "/mock",
//timeout代表请求超时的时间
timeout: 5000,
});
2). 准备JSON数据(在mock文件夹仲创建相应的JSON文件,数据从word文档中获取即可)————格式化一下,别留空格。
3).把mock数据需要的图片放置到public文件夹中【public文件夹在打包时,会把相应的资源原封不动打包到dist文件夹中】
4).在mock文件夹中创建的mockServe.js通过mockjs插件实现模拟数据。
//先引入mockjs模块
import Mock from 'mockjs'
//把JSON数据格式引进来
// webpack默认对外暴露的有:图片、JSON数据格式
import banner from './banner.json'
import floor from './floor.json'
//mock数据:第一个参数:请求地址 第二个参数:请求数据
Mock.mock("/mock/banner", { code: 200, data: banner }); //模拟首页大的轮播图的数据
Mock.mock("/mock/floor", { code: 200, data: floor });
5).mockServe.js文件需要在入口文件main.js中引入(至少需要执行一次,才能模拟数据)。
webpack默认对外暴露的有:图片、JSON数据格式
19.5 获取banner轮播图的数据
用上了vuex中的三连环
- 三连环:在actions里面提交mutations,让mutations进来修改state。
①.在仓库store文件夹
里的home文件
中使用vuex三连环
//引入API
import {reqGetBannerList } from '@/api/index'
//home模块的小仓库
//vuex的四大核心概念必须要有
const state = {
//state中数据的默认初始值是【根据接口返回值初始化的】,服务器返回对象则state默认值为空对象,服务器返回数组则state默认值返回空数组。
getBannerList: []
};
const mutations = {
GETBANNERLIST(state, getBannerList) {
state.getBannerList = getBannerList
}
};
const actions = {
//三级联动,通过API里面的接口函数调用,向服务器发请求,获取服务器的数据。
//获取首页轮播图的数据
async getBannerList({ commit }) {
const result2 = await reqGetBannerList();
if (result2.code == 200) {
commit('GETBANNERLIST', result2.data)
}
}
};
const getters = {}
export default {
state,
mutations,
actions,
getters
}
②.在ListContainer文件
中获取轮播图数据,通过计算属性computed获取
二十. swiper的基本使用
swiper文档,阅读使用:new Swiper(swiperContainer, parameters)_Swiper参数选项
使用步骤:
第一步:加载插件,引入js和css包(swiper.js和swiper.css)。
第二步:在newSwiper实例之前,页面中的结构必须要先提前准备好。
第三步:(页面中务必要有结构) new Swiper(swiperContainer, parameters);第一个参数可以是字符串,也可以是真实的DOM节点
举例说明:
Document
20.1 Banner实现轮播图的第一种方法
安装swiper
:npm 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通过自定义属性list给子组件Floor传递数据
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进行引入使用。
{{attr.attrName}}
二十五. 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的方法
......
-
{{ searchParams.categoryname
}}×
......
把带给服务器是参数置空了,还要向服务器发请求。
带给服务器参数说明可有可无的:若属性值为空的字符串还是会把相应的字段带给服务器。但是把相应的字段变为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("......")
......
-
{{ searchParams.keyword }}×
......
- 第三步:在
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组件:
......
{{trademark.tmName}}
......
- 然后:修改、补充Search模块里的内容
......
-
{{ searchParams.trademark.split(":")[1]
}}×
......
二十七.平台售卖属性的操作
27.1 子组件传给父组件(用自定义事件)
- 首先:在
Search
的子组件SearchSelector组件
中补充自定义事件@click="attrInfo(attr,attrvalue)"
和attrInfo(attr,attrValue)
的方法:
......
{{attrvalue}}
......
- 其次:在
Search组件
中补充:点击事件@attrInfo="attrInfo"
和方法attrInfo(attr,attrValue)
,即可。
......
......
......
27.2 将平台售卖属性放置面包屑(数组去重)
需要遍历,所以用v-for,而不用v-if。
- 在Search组件中整理好数据、增加
平台的售卖属性值的面包屑:
......
{{ attrValue.split(":")[1]}}×
......
- 需要进行数组去重!!:
- 删除平台的售卖属性值的面包屑。
......
{{ attrValue.split(":")[1] }}×
......
二十八. 排序操作(上)
在接口文档中,
- 问题一: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模块中传数据。
第二步:在子组件分页器Pagination的index.js中用props接收父传来的数据
export default {
name: "Pagination",
props:['pageNo','pageSize','total','continues']
};
- 二).分页器(分页器逻辑)
自定义分页器,在开发时先用自己传递的假数据进行调试,调试成功后再用服务器数据。
对于分页器而言,需要算出连续页面起始数字和结束数字。
分页器上面的数字的业务逻辑(用computed):
pageNo为当前页;
continues为连续的页数;
totalPage为总页数;
start为起始页数;
end为结束页数。
......
{{startNumAndEndNum}}------{{pageNo}}
......
31.2 分页器的动态展示(分页器按钮显示的逻辑)
分页器动态展示,可分为上中下
【中间部分】
v-for可以遍历:数组|数字|字符串|对象
分页器页码按钮显示逻辑:
{{startNumAndEndNum}}------{{pageNo}}
31.3 分页器的完成
将子组件Pagination中的业务传给父组件Search。
子传父:
- 第一步:在父组件Search中进行注册自定义事件
@getPageNo="getPageNo"
- 第二步:在父组件中Search中定义自定义事件
@getPageNo="getPageNo"
的回调事件
- 第三步:在子组件Pagination中用
@click="$emit('getPageNo',????)"
编写事件的触发
{{startNumAndEndNum}}------{{pageNo}}
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路由组件展示商品售卖属性
- {{spuSaleAttr.saleAttrName}}
- {{spuSaleAttrValue.saleAttrValueName}}
......
三十六. 产品售卖属性值——排他操作—裁剪
......
{{ spuSaleAttrValue.saleAttrValueName }}
......
三十七. 放大镜操作(上)
37.1 放大镜操作——ImageList组件和Zoom组件之间的小操作
点击小图片,周围边框变颜色。在ImageList.vue
中
......
......
传递全局事件,兄弟组件Zoom.vue
接收
37.2 放大镜操作——大图片随着小图片的点击也进行改变
在Zoom组件
中
37.3 放大镜操作——遮罩层
- 设置遮罩层随着鼠标的移动一起移动
......
......
- 设置右侧的大图移动情况(补充)
......
......
三十八. 购买产品个数的操作
......
......
三十九. “加入购物车”路由
(这个路由和其他的路由不太一样)
"加入购物车”的按钮,需要用到的操作步骤:
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组件
中
......
......
......
{{totalPrice}}
39.6 处理产品数量、修改购物车产品的数量完成
处理按钮
......
-
+
......
39.7 修改产品个数【对函数进行节流】
这里可以用节流的方式,防止发生卡顿现象。
39.8 删除购物车产品的操作
写接口——>写仓库——>写组件
- 在API
的index.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文件
中引入仓库state
,在router文件中
向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 用户地址信息的展示(排他思想)
......
收件人信息
{{address.consignee}}
{{address.fullAddress}}
{{address.phoneNum}}
默认地址
......
寄送至:
{{userDefaultAddress.fullAddress}}
收货人:{{userDefaultAddress.consignee}}
{{userDefaultAddress.phoneNum}}
44.3 交易页面完成
- 完善
Trade组件
......
商品清单
-
-
{{order.skuName}}
7天无理由退货
-
¥{{order.orderPrice}}.00
- X{{order.skuNum}}
- 有货
......
{{orderInfo.totalNum}}件商品,总商品金额
¥{{orderInfo.totalAmount}}.00
返现:
0.00
运费:
0.00
......
应付金额: ¥{{orderInfo.totalAmount}}.00
......
- 收集买家留言
......
买家留言:
......
四十五. 提交订单(不用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组件
中
......
请您在提交订单4小时之内完成支付,超时订单会自动取消。订单号:{{orderId}}
......
- 写接口
api
//获取支付信息接口
// URL:/api/payment/weixin/createNative/{orderId} method:get
export const reqPayInfo = () => requests({ url: `/payment/weixin/createNative/${orderId}`, method: "get" })
- 再在
Pay组件
中补写:
......
应付金额:¥{{payInfo.totalFee}}
......
四十七. 支付页面中使用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组件
中,可自行查看。
我的订单
......
2017-02-11 11:59 订单编号:7867473872181848
{{ order.consignee }}
- 总金额¥ {{ order.totalAmount }}.00
- 在线支付
{{ order.orderStatusName }}
......
五十一. 未登录的导航守卫判断(前置守卫)
未登录的访问,无法访问交易相关(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组件
中使用:
......
{{ errors.first("phone") }}
{{ errors.first("code") }}
{{ errors.first("password") }}
{{ errors.first("password1") }}
同意协议并注册《尚品汇用户协议》
{{ errors.first("agree") }}
......
五十五. 路由的懒加载
当打包构建应用时,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