vue项目开发日记(监修中)
SPH 项目笔记
项目开发前的准备
- [脚手架初始化项目]
- 准备 node + webpack + cnpm(有梯子也可以) 的开发环境。
- 命令行执行
vue create app
创建脚手架。
- 项目文件夹介绍:
- [build]index.js 是
webpack
配置文件,几乎不动这个文件。 - [mock]数据来源的文件夹,模拟一些假的数据由
mockjs
实现的。但实际开发的时候,利用的是真是接口真数据。所以该文件夹仅用于测试项目。 - [node_modules] 项目依赖的模块。
- [public]用于存放 ico 图标,静态页面,public 文件夹里面经常放置一些静态资源,而且在项目打包的时候
webpack
不会编译这个文件夹,会被原封不动的打包到dist
文件夹里面。 - src
- [api]封装了请求相关的接口。
- [assets]文件夹: 里面放置些静态资源(一般共享的),放在 aseets 文件夹里面静态资源,在 webpack 打包的时候,会进行编译。
- [components]一般放置非路由组件或全局组件。
- [icons]放置 svg 矢量图等格式的图标。
- [layout]放置一些组件与混入,混入写的
ResizeHandler.js
大概写的是根据设备响应式调整页面大小。组件部分都是[???] - [router]配置路由。
- [store]vuex业务逻辑与响应式数据的管理,其中
getters.js
不参与模块化得原因是:getters
配置项是在其他所有模块中起相同作用,可以理解为公共模块,所以单独拿出来。 - [style]与样式先关的文件。
- [utils]共享工具脚本库
- [views]放置的是路由组件,也可以命名为
pages
,但建议views
。 - App.vue: 根组件
- main.js: 项目入口文件,程序最先执行的文件。
- permission.js: 导航守卫配置文件。
- settings: 项目配置项文件。
- babel.config.js:
babel
的配置文件。 - package.json: 相当于项目的“身份证”,保存着项目的名字版本等信息,以及各种依赖信息,配置
"serve": "vue-cli-service serve --open"
可以运行项目的时候直接在浏览器里打开。 - package-lock.json: 缓存性文件,记录项目依赖的来源。
- [build]index.js 是
- [项目初始化配置]
- 根目录创建
vue.config.js
文件,配置lintOnSave: false
可关闭语过度语法校验。 - 根目录创建
jsconfig.json
文件,配置src/
路径的别名@/*
(详情参见jsconfig.json
文件)
- 根目录创建
- [项目开发流程]
- 通过原型图或者需求,先拆分组件,组件命名不能与
html
标签名冲突。 - 将拆分出的组件进行路由分析
- 页面布局中,发生变化的往往被拆成路由组件,观察地址栏路由与中上下结构对应的关系。
- 路由组件有:
Home
,Search
(搜索结果展示),Login
,Register
。 - 全局组件有:
Header
(搜索页、主页、登录、注册都有),Footer
(登录注册页面没有)。
- 搭建路由组件根据路由分析的结果,新建
views/pages
文件夹,在内部新建前面分析的四个路由组件。- 编程式路导航由与跳转路由导航的本质区别,编程式路由导航在切换路由前,可以进行业务逻辑操作。而跳转路由导航仅仅只能切换路由,不能干别的。
- 测试完一级路由组件切换没毛病后,着手路由组件实际的开发
- 获取服务器数据进行响应式呈现,组件共用数据用
vuex
管理,组件自用数据则放在自身组件。 - 从绑定事件开始写,然后逐步完成业务逻辑。
- 通过原型图或者需求,先拆分组件,组件命名不能与
项目正式开发
-
[静态组件开发]:
- 别忘了加上图片等静态资源(如果是拆分静态页面不加引入的资源编译会报错)。
- 别忘了用
reset.css
清理浏览器默认样式,在index.html
中引入即可。 - 碰见拆分的组件样式是
.less
格式,则需要npm install --save less less-loader@5
(需要安装老版本 5.x 才可兼容 vue2.x)安装less
的编译插件,这样才能编译成浏览器认识的css
代码。
-
[路由组件开发]:
- 配置路由:新建
routers
文件夹,里面新建index.js
写入路由配置。并且注意设置初始/
或*
路由的重定向。 - 安装路由:然后别忘了在
main.js
里面安装路由配置。 - 使用路由:在
App.vue
里面引入并使用路由组件。 - 易犯错误:在
App.vue
里面安装路由的时候下面的代码是错误写法:
import vueRouter from "@/router"; // 正确👉 import router from '@/router' new Vue({ render: (h) => h(App), vueRouter, // 正确👉 router }).$mount("#app");
- 配置路由:新建
-
[路由组件的显示与隐藏]:
Footer
组件在登录注册时要被隐藏。- 思路:给每个路由组件设置
meta
原信息并且使用指令v-show
对Footer
组件进行隐藏。
-
路由传参坑点:使用对象传参数
- 传
query
对象时,路由不用命名直接通过props()
函数发给下一个路由组件。 - 传
params
对象时,路由不但要设置占位符而且还要命名才行。然后通过props: true
发给下一个路由组件。
- 传
-
[路由传参相关面试题]:
- 路由传递参数(对象写法)
path
属性是否可以结合params
参数对象一起使用?---不能 - 如何指定
params
参数可传可不传?- 指定了占位符要传
params
,如果不传,地址栏会吞掉路由。解决办法:占位符后面加上一个?
即可。加?
的原理是正则表达式中?
后面的字符出现 0 次或 1 次。
- 指定了占位符要传
params
参数可以传递也可以不传递,但是如果传递是空串,如何解决?- 指定了占位符要传
params
,如果传入空串,地址栏也会吞掉路由。解决办法:params: { ''||undefined}
在要传入的参数后面加上或运算符和undefined
。
- 指定了占位符要传
- 路由组件能不能传递
props
数据?---能,三种写法:布尔值、对象、函数。
- 路由传递参数(对象写法)
-
[重新封装]
$route.push
和$route.replace
- 目的:解决编程式路由导航,多次执行路由跳转会报错的行为。
- 原因:
$route.push()
和$route.replace()
返回的是一个promise
对象,而我们在使用push
和replace
方法时并没有传入余下的resolve
和reject
参数,所以会报错。 - 解决方法:重新封装---详情见
@/router/index.js
里面的重写push/replace
。
Home 模块开发
- 三级联动组件是 Home, Search, Details 共用的组件,所以要拆分为全局组件。
- 全局组件在
main.js
中进行注册。后面在其他组件中使用无需再引入。 - 搬砖:Home 模块的其他子组件按照拆分组件的流程进行,最后会得到整个静态组件。
二次封装 axios
- 为何要二次封装
axios
?- 将
axios
二次封装成请求拦截器,可以在发送请求前处理一些业务。 - 将
axios
二次封装成响应拦截器,可以在得到响应后处理一些业务。 - 最终目的是为 API 统一管理服务,也就是会被引入
api/index.js
- 将
API 的统一管理
- 利用二次封装的
axios
创建api/index.js
模块,对所有接口进行统一管理。 - 模块设计:把每个接口写成函数,然后再暴露。这样不论哪个组件像使用这个接口发请求直接,引入此模块再调用此接口函数即可。
- [建议]:开发中小的项目建议直接利用钩子函数
mounted
发请求,不用做 API 统一管理。而大的项目必须写 API 管理模块,才能有效管理成百的接口。
解决跨域问题
- 利用脚手架代理服务器:
vue.config.js
配置项devServer
,详情看文件代码。 - 其他方法:jsonp, cros
- [易犯错误]:配置了
vue.config.js
代理服务器,但未重新npm run serve
。
开发进度条
- 安装:进度条使用一个插件库
nprogress
,先npm i nprogress
下载。 - 使用:在
@/api/request.js
的拦截器里使用。先引入nprogress
插件,再引入nprogress.css
。然后在请求拦截器中调用nprogress.start()
让进度条动起来,再在响应拦截器中响应成功时调用nprogress.done()
,完成进度条动画。
vuex 状态管理器
- 安装:
npm i vuex@3
安装 3.x 版本是为了兼容 vue2.x。 - 配置:参考张天禹教程的
README.md
中vuex
配置流程。 - 使用:大项目必定 vuex 模块化开发,中小项目视情况可非模块开发,微小项目可不用 vuex。
完善分类列表
开发一级分类列表的选中背景色
- 第一种方式:伪类选择器
hover
添加样式。 - 第二种方式:化简为繁,绑定鼠标事件
mouseenter
,mouseleave
。- 原理:利用 事件委派 | 事件委托 添加一个父元素
<div>
,给父元素盒子分派事件 - 然后给一级分类列表元素绑定一个
:class="{cur: currentIdx === idx}"
利用添加类名来展示样式。
- 原理:利用 事件委派 | 事件委托 添加一个父元素
- 开发二三级分类列表中的显示与隐藏:
- 原理:同上原理
- 不同的时二三级分类列表元素绑定的是
:style="{ display: currentIdx === idx ? 'block' : 'none' }
- 我的博客园-用事件委托优化性能的好处
浏览器的卡顿现象[使用频率高,面试也会问]
- 问题:用户输入过快,浏览器接收用户输入不完整(俗称反应不过来),类似的有鼠标瞬间划过菜单或列表,但被触发的只有几个选项。
- 卡顿现象原理:
- 正常:事件触发非常频繁,而且每一次的触发,回调函数都要去执行(如果时间很短,而回调函数内部有计算,那么很可能出现浏览器卡顿)
- 解决办法:
- [防抖]在规定的间隔时间范围内不会重复触发回调,只有大于这个时间间隔才会触发回调,把频繁触发变为少量触发。
- [方法]使用
lodash
插件提供的_.debounce(func, [wait=0], [options=])
方法。自己也可以封装,但得深刻理解闭包和延迟器。
- [方法]使用
- [节流]前面的所有的触发都被取消,最后一次执行在规定的时间之后才会触发,也就是说如果连续快速的触发只会执行一次。比如轮播图,为了防止用户连续点击切换过快,可以通过防抖设置 0.1 秒内只能触发一次切换。
- [方法]使用
lodash
插件提供的_.throttle(func, [wait=0], [options=])
方法。自己也可以封装,但得深刻理解闭包和延迟器。
- [方法]使用
- [防抖]在规定的间隔时间范围内不会重复触发回调,只有大于这个时间间隔才会触发回调,把频繁触发变为少量触发。
- [实际解决一级分类列表卡顿现象]
node_modules
里已经有库依赖过lodash
,所以不需要重复下载了,直接引用即可。 - 按需引入,优化项目打包体积
import { debounce } from "lodash"
三级分类列表组件的路由和传参
- [使用 router-link 做路由跳转],可以完成路由跳转和传参的业务,但是!
- [使用编程式路由导航做路由跳转],可以完成路由跳转和传参的业务以及其他业务逻辑。
- [方法 1]利用编程式路由导航 + 事件委派,可以给父元素只绑定一个事件,利用事件冒泡让所有子元素可以触发此事件。
- [存在的小问题]:我们的需求是,用户点击三级分类列表
<a>
标签才冒泡,但是其他子节点<h3>
,<dt>
,<dl>
,<em>
也会触发冒泡 - [解决小问题]:给
<a>
标签添加自定义属性:dataCName="c1.CategoryName"
和:dataCLevel="c1.categoryId"
,二三级以此类推。原理相当于服务器得知道你是“几年级,什么名字”,才能返回对应的详情页数据。
- [存在的小问题]:我们的需求是,用户点击三级分类列表
- [方法 1]利用编程式路由导航 + 事件委派,可以给父元素只绑定一个事件,利用事件冒泡让所有子元素可以触发此事件。
三级分类列表的显示与隐藏
- 给
<h2>
绑定鼠标移入移出事件,利用路由meta
和路由路径判断分类列表的一开始是显示还是隐藏。
合并参数
- [需求或目的]类似淘宝分类列表和搜索的关系,点击搜索会携带分类列表的数据帮助数据筛选。
- 解决方法:利用
$route
获取分类列表的数据,然后在切换路由前添加至请求的location
对象。
知识点总结
-
知识点和思路
- [解决跨域问题]代理服务器, cros, jsonp, 图片探测。
- [浏览器卡顿问题]回调函数的防抖与节流。
- [路由跳转的两个方式]:声明式导航(router-link) 、编程式导航。
- [事件委派+自定义属性]解决三级分类列表编程式导航跳转问题。
- [二次封装 axios]请求拦截器、响应拦截器。
-
面试高频问题
- 路由传参相关面试题 👆
- [面试频率很高]回调函数的防抖与节流。
- [解决跨域问题]代理服务器, cros, jsonp, 图片探测。
开发 ListContainer 组件(Floor 组件同理)
使用 mock
- mockjs拦截
ajax
请求,生成随机数据,响应这些生成的模拟数据(mockjs 文档)。 - 大概用法:
- npm 下载依赖。
- 新建
@/mock
文件夹,把要返回的静态数据写成json
文件放进去。 - 把静态资源(图片等等)放进
public/images
文件下,因为public/
相当于项目根目录。 - 配置
mockServer.js
:先引入mockjs
工具库,再引入第 1 步的静态数据,然后写Mock.mock('mock/banner', {code: 200, data: banner});
配置mockServer
响应。 - 配置
mockRequests.js
:把requests.js
代码复制进来,然后修改这个配置:baseURL: "/mock"
。相当于mock
版本的二次封装axios
。 - 把
mockRequests.js
引入api/index.js
,然后请求mock
数据就得export const reqBanner = () => mockRequests({ method: 'get', url: '/banner',})
。其他mock
请求以此类推。
使用 swiper
- 下载依赖
npm i swiper@5.2.0
和npm i vue-awesome-swiper@4.1.1
,这俩插件必须下载指定的版本,因为swiper
是个怪东西,大版本更新后语法变化得比较多,再加之中文文档内容不够全,所以坑很多。若是下载swiper@5.x
到vue@2.x
脚手架多半会报错。 - 全局配置:先在
main.js
中全局引入并安装vue-awesome-swiper
,还要引入swiper/css/swiper
样式 - 轮播图+左右按钮+分页按钮参考路由组件
ListContainer
内的代码。 - [$nextTick 解决]
$nextTick
类似于(胜过)生命钩子updated
只不过操作 DOM 也可以使用它,并且它比updated
更自由。 - 基本用法建议参考基于 vue2 使用 vue-awesome-swiper 轮播图(踩坑记录)
开发 Search 组件
- 发请求时用
Object.assign()
对请求对象进行浅复制,目的是按服务器需求补全整理好所有请求参数,请求参数可设置为undefined
。 -
- 面包屑容器:
Search
组件data
配置项内写一个bread: {}
。 - 缓存面包屑:只要路由一更新发起新的请求,就立刻更新面包屑缓存。因为此时的路由必然是更新过的,并且
bread
缓存路由的数据必须使用浅复制,因为后面会修改bred
,但路由的数据只读。 - 展示面包屑:使用
v-if
判定面包屑的是否存在来展示某条面包屑。 - 移除面包屑:绑定事件,给回调传入要删除的面包屑名字。
- 回调逻辑:先封装一个编程时路由导航
push
的函数,用于写入新的路由信息。接下来用switch
判断包屑名字来清理不需要的面包屑。
- 面包屑容器:
- [开发排序]
开发 Search 组件的分页器(重点功能)
- [分析页码器需要的组件数据]由于一个企业项目可能数据有上万条甚至更多,所以不需要前端一次性拿完所有数据再分页展示。而是通过分页器按需请求一页一页的数据进行展示。一个分页器至少需要三个关键数据:页码
pageNo
(当前所在页)、数据总数total
、一页展示数据的条数pageSize
。- [需要前端计算的一个数据]页码总数
pageTotal
。总页数 = 数据总数 / 一页展示数据的条数。 - [分页器设计的特点]分页器一般连续的页码(页码小方块)是奇数个
continues
:5 或者 7。因为奇数对称,美观。 - 总结一共需要这些数据:页码
pageNo
, 数据总数total
, 一页展示数据的条数pageSize
, 页码总数pageTotal
(计算出的), 连续的页码个数continues
。
- [需要前端计算的一个数据]页码总数
- [分页器开发思路]
- 开发前期不需要用分页器数据和服务器通信,只需要用假数据本地一步一步调试成功即可。
- [分页特殊情况处理]如果是 5 个分页器(中括号标记当前页):
- 起始页溢出
- 当前页
pageNo = 1
,那么分页器:-1 0 [1] 2 3,修正为 [1] 2 3 4 5 - 当前页
pageNo = 2
,那么分页器:0 1 [2] 3 4 修正为 1 [2] 3 4 5 - 解决办法:判断起始页如果小于 1,就修正页码器。
- 当前页
- 结尾页溢出
- 结尾页
end = 33
,总页数paegTotal = 31
那么分页器:29 30 [31] 32 33,修正为 27 28 [29] 30 31 - 解决办法:结束页大于总页数,就修正页码器。
- 结尾页
- 起始页溢出
- 分页器动态展示(分为上中下)
- 中间部分需要
vbfor
遍历数字,建议单独写一个数组在computed
里用于遍历,而不是老师那种v-for
和v-if
一起使用。 - 页码按钮动态样式通过当前页码
pageNo
,是否等于页码按钮的值来确定。 - 绑定页码点击事件,给回调传递一个自定义属性
:data-pageId="按钮页码值"
- 通过回调更新路由发请求,拿对应页面商品数据。
- 上一页、下一页按钮通过
pageNo - 1
,pageNo ```js + 1
来处理,具体逻辑看代码全局页码组件 - 遗忘了个小功能:是否禁用上一页、下一页按钮,是的话
disabled: true
。
- 中间部分需要
开发 Detail 组件
-
先注册
/detail
路由,然后别忘了写占位符,后面页面跳转传商品 ID 会用到。 -
- 解决办法:原来是把
:to
写成:go
,我是 SB
- 解决办法:原来是把
-
滚动行为控制:通过路由器配置
scrollBehavior
控制跳转此路由时网页的纵向、横向位置。scrollBehavior (to, from, savedPosition) { // savedPosition 意思是维持上个 页面位置 return savedPosition || { x: 0, y: 0 } }
-
商品详情页轮播图小组件中,轮播图配置
loop: true
不能和点击事件共用。因为loop: true
配置项会波坏数组结构,让被循环出的元素无法绑定点击事件。 -
[处理非法输入]针对加购数量做输入验证:
-
当用户输入非法字符
NaN
重置为 1。 -
当用户输入小于零(负数)的时候取反再
parseInt()
取整。 -
其他正常输入(就算输入小数)
parseInt()
则取整。 -
自己写的仿京东输入限制代码:
//绑定 keyup 事件 changekuNum(e) { // 非法和空串重置 1 let val = e.target.value; if (isNaN(val) || val == "") return (this.skuNum = 1); // 负数取正整 if (val < -1) return (this.skuNum = parseInt(-val)); // 正数取整 this.skuNum = parseInt(val); },
-
-
加入购物车成功组件的数据需要会话存储,也就是
sessionStorage
-
面试高频问题
- [本地存储和会话存储]
- 本地存储
localStorage
持久存储 - 会话存储
sessionStorage
非持久存储
- 本地存储
- [本地存储和会话存储]
开发购物车组件(重点)
- [功能和数据决定项目结构]购物车组件不用新建状态库,因为
Detail
库的商品详情数据和游客临时id
,在点击加入购物车时,购物车组件也会访问商品详情数据。 - [身份验证]
uuid
,一个好用的唯一id
生成库。 - [封装游客身份模块]新建一个
uuid_token.js
用于$store
的ShopCart
模块做身份验证。 - [单例模式-游客身份]设计思路:工具模块中引入
import { v4 as uuidv4 } from 'uuid'
,先去localStorage
拿游客身份id
没有就新生成一个,再setItem()
写入localStorage
长期保存。最后别忘return uuid_token
。 -
第一次听说的 API:Array.prototype.every()
- [加购数量的增减]:三个 DOM 绑定同一个事件,派发同一个
action
。逻辑小繁琐,我用了switch
判定其中一个变量来确定触发的哪个事件。这个需求是限制用户输入的同时还要拿到输入框之前的值,以及输入框新的值(或新旧之差),再计算出差值用于派发请求更新商品加购数量。代码详情 -
- 一定要在请求成功(返回的
promise
)中的onResolved
回调中再发拿数据的请求,因为??? - 这种任务,
actions
里的回调请求成功,一定要返回非空字符串和请求失败抛出一个错误。这样组件才能通过.then()
和.catch()
知道任务成功没,成功马上捞数据渲染到页面上。 - 代码详情
- 一定要在请求成功(返回的
- 删除选中功能稍微有点麻烦,是因为只有删除一个商品的接口。解决思路:派发一个
action
,然后调用 N 次单删接口删除所选商品。 - [总结]购物车组件和搜索组件的内容写得不错,值得细细品味。
注册登录组件开发(重点)
-
登录注册模块合并为
User
,参考依据:数据相似度高。 -
[登录验证码],前端只管发请求拿验证码,让用户输入。提交表单给服务器的时候带上就行。
-
[表单验证]统一处理
-
[token]令牌验证:
- 相当于服务器分配给用户客户端的一个
uuid
- 客户端需要通过
token
验证,才能拿服务器数据。 vuex
仓库数据非持久化,一刷新或者路由跳转就没有了。token
需要长期存在,所以必须存储在localStorage
里- 用户登陆后的操作请求携都会携带
token
,如果过期用户操作请求会失败并跳转至登录页让用户重新登录获取新的token
。
- 相当于服务器分配给用户客户端的一个
-
[携带 token]拦截器
config.hearders.token = localStorage.sph_token
给请求头设置token
。- 用户登录成功并跳转至
Home
页,Home
页一挂载就可以请求用户信息,并把用户信息保存到User
库,这样所有组件都可以访问到用户信息了。 - 如果 token 过期肯定是后台返回过期的状态码,然后判断状态码去清除本地存储 token,让用户重新登录。
- [token 过期]使用
Token
方式调用 OCR 服务返回 401 状态码,表示Token
已经过期。 - 有 token 就一定登录了?
- 用户登录成功并跳转至
-
[退出登录]退出登录必须发请求告诉服务器销毁 token,然后清空本地
token
和仓库的用户信息。 -
[路由守卫]:
- 用户登录后,不能再往登录页跳转了。
login
路由独享守卫,进行限制。 - [全局路由守卫的用处]针对
token
检查三件事:有没有token
、token
是否过期、还要判断没有用户信息?
- 用户登录后,不能再往登录页跳转了。
-
- next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
- next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
- next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: 'home' 之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项。
- next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。
-
[统一表单验证]:
-
安装
vee-validate
插件(plugins 独立安装) -
配置方法:
import vue from "vue"; import veeValidate from "vee-validate"; // 引入中文 import zh_CN from "vee-validate/dist/locale/zh_CN"; vue.use(veeValidate); veeValidate.Validator.localize("zh_CN", { messages: { ...zh_CN.messages, // 提示信息设置为中文 is: (field) => `${field}必须与密码相同`, // 修改内置规则的 message,确认密码相同。 }, // 通过组件中的输入元素 name 属性去绑定以下属性。比如 name="phone" attributes: { // 设置输入框提示的字段名字为中文 phone: "手机号", code: "验证码", password: "密码", twicePassword: "确认密码", isChecked: "协议", }, });
-
组件中使用:
<input type="text" placeholder="请输入你的手机号" v-model="phone" name="phone" v-validate="{ required: true, regex: /^1\d{10}$/ }" :class="{ invalid: errors.has('phone') }" /> <span class="error-msg">{{ errors.first("phone") }}</span>
- 用到的正则:手机号
/^1\d{10}$/
、密码/^[0-9A-Za-z]{8,20}$/
、
-
登录业务逻辑核心---全局路由守卫判定登录与否后的一系列操作
// *前置全局守卫(登录业务逻辑核心)
router.beforeEach( (to, from, next) => {
// *判断登录页以外的其他路由
// 如果登录
if (localStorage.getItem('sph_token')) {
// 就可以跳转到除了登录页的所有页面
// *别忘了发请求,拿用户数据(之前我写在app/home组件的作废,
// *原因之前的逻辑,退出登陆后(token清空)登录home都页(会请求用户数据)进不去)
store.dispatch('User/getUserInfo')
.then(res => {
// 成功获取到用户数据
if (res.code == 200) {
// 放行
return next();
}
// 获取用户数据失败,提醒用户
return vm.$message({
message: '获取用户数据失败',
type: 'error'
})
})
.catch(err => console.log(err))
}
// *如果没登录,用户个人数据的页面无法跳转
else {
// 想去哪儿
let wantToGo = to.path;
// *包含这些 关键词 的禁地不能去
let arr = ['trade', 'pay', 'center', 'shopcart']
// *只要想去禁地,没门儿!
if (arr.some(noWay => wantToGo.indexOf(noWay) != -1)) {
// 提醒登录
vm.$message({
message: '用户未登录',
type: 'error'
})
// *滚回去登录,成功后会重定向之前想去的地儿
next(`/login?redirect=${wantToGo}`)
} else {
// *没去禁地放行
next()
}
}
})
支付模块开发(重点)
- [element-UI]按需引入:
- 支付的业务逻辑与交互逻辑。
- 业务逻辑:
- 点击购物车结算按钮,跳转至提交订单页。
- 如果购物车为空则结算按钮为不可选。
- 用户选择地址和支付方式后,点击提交订单跳转至支付页面
- 然后在支付页面完成付钱。
- 交互逻辑:
- 点击支付后弹出二维码,让用户支付。
- 如果支付成功则自动跳转至支付成功页面,并弹出---成功提示信息。
- 如果支付失败则弹出---失败提示信息。
- 如果支付过程中用户无法点击点击---已支付,但可以点击---支付遇到问题。
- 业务逻辑:
图片懒加载和路由懒加载
- 该功能由插件
vue-lazyload
提供 - 路由懒加载其实就是路由按需引入
- 当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。
- 语法
{path: xxx, component: () => import('@/views/xxx') }
完结撒花
- 项目打包中的.map可以消除掉,通过设置
技巧总结
- [开发套路]:
- 先写静态页面。
- 静态页面拆分成静态组件。
- 新增路由,API 发请求。
- vuex 三连发。
- 组件获取仓库数据,动态展示数据。
- 组件功能:筛选、排序、面包屑、上传、下载等等
- [性能优化]只需要请求一次的数据,请求放在
App
组件里发。 - [性能优化]需要复用的组件请求数据,放在父组件里发送该请求,因为子组件要复用,父组件替它拿到数据后
v-for
遍历数据复用子组件。数据可以通过props
传递。复用可以节省资源开支。 - [减少代码]
vuex
的getters
配置项的最佳使用场景:请求服务器返回的数据里面层级过多时,可以通过getters
提前写好计算属性方便所有组件享用。 - [减少代码]请求参数键名一定要和服务器接收参数键名保持一致。
- [性能优化]请求参数默认值设置为
undefined
可以保证,参数为空时,请求不会携带该参数,节省网络带宽。 - [逻辑优化]项目中组件使用频繁的数据就放在它自身
data
即可,不要放在vuex
- [前端必须掌握]轮播图、分页器、日历。
- [知识点回顾]
v-if
是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。相比之下,v-show
就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。- [延申知识点]
vuex
里如果拿state
或者getters
里的数据报错,就有可能是其初始值是undefined
。所以需要返回值 || {}
这种方式默认返回非undefined
值。
- [延申知识点]
- [经验之谈]数组统计,使用场景:数组内部的值做运算得到最终结果。举例:购物车结算总价。
- [经验之谈]节流器和防抖器内写匿名函数,不是箭头函数
- [经验之谈]异步请求一定要用
async..await
去控制异步请求,当数据拿到后才会通知组件渲染数据。 - [经验之谈] CSS 使用快捷路径
@/
需要加上~
,也就是~@/
。 - [趣味知识]手机验证码是公司买运营商的短信验证服务,后端老哥通过服务商提供的接口编辑短信信息,提供用户号码。按理来讲是获取验证码的这个接口,把验证码返回。但是正常情况,后台把验证码发到用户手机上。
- [心得体会]前端做的交互界面,实质上是帮助用户和服务器数据通信,做的一层缓冲和一架桥梁。本质目的是帮助用户便捷的发送数据(通过请求发送各种用户操作数据),帮助服务器直观、美观的展示数据,帮助服务器认识用户。
- [经验之谈]搜索历史放
locoStorage
中最佳。 - [减少代码]如果不通过
vuex
发请求,那么可以通过全局。 - [心得体会]
promise
是非常棒的异步处理技术,尤其是对API请求操作来说,简直就是福音。只需简单的链式调用就可以完成多个关联异步操作,并且一旦某个环节失败就立马被catch
捕获,进入失败处理程序。 - [经验之谈]每个路由都有同的样验证行为,那么就可以将该验证行为写到全局前置路由守卫中。
项目遇到的坑
- [已解决问题]三级分类列表跳转搜索页时,由于结构刚渲染出来用户点击分类选项,导致路由携带的 query 参数为空。
- [问题原因所在]事件委托导致所有被点击的子元素都会触发
@click
的回调,除了<a>
其他元素是没有设置自定义属性的,所以触发事件调用回调时e.target.dataset
根本就拿不到数据。---2022 年 10 月 03 日 22:33:59 - 解决办法利用自定义属性的有无判定是否
<a>
元素触发的点击事件。代码:if (!e.target.dataset.cname) return;
。---2022 年 10 月 03 日 22:47:42
- [问题原因所在]事件委托导致所有被点击的子元素都会触发
- [已解决问题]通过路由
$route
来触发watch
监听是可以的,但和$route
普通对象不一样。$route
对象只可读,但要想通过修改$route
对象的属性来触发watch
,就自能通过$router.push()
方法携带一个新的location
对象。 - [已解决问题]用
Object.assign()
或者{ ...obj }
进行浅复制后,和初始对象就完全没有引用关系了。也就是说,你不能用浅复制进行响应式数据的绑定。 - [已解决问题]watch 监听不到
$route
中的 query 对象中数组参数的改变,因为数组必须靠arr.push
,arr.pop
去触发watch
的监听。可惜$route
对象只读,不可能用数组方法去修改$route
对象。这基本无解,只能选择其他办法。- 解决办法欺骗数据
query: {...this.bread.query, liar: Math.random() + Math.random()}
加了两个随机数,欺骗 watch 让它给爷重新更新路由发请求 。不知道工作中这么做会不会被打(狗头)
- 解决办法欺骗数据
- [已解决问题]拿
vuex
中库的数据,如果报错数据undefined
。那么多半是渲染时就读取了该数据,并且是异步请求拿的该数据。所以该数据在beforeMount
阶段(渲染)初始值为空或undefined
。- 解决办法:如果是直接访问
props
对象上的数据,层层传递时每层级的数据用用或运算符给个初始值。 - 提示:另外如果是使用
v-if
,v-for
访问props
对象上的数据,那么无需层层指定初始值,因为v-if
,v-for
会等待真正的数据返回时才开始工作。所以不会出现该问题。
- 解决办法:如果是直接访问
actions
内的回调只接收一个参数或对象(多个参数)进行传参。所以多个参数可以用解构赋值传参,接参。- [已解决问题]编程式路由
a
标签里面不要写href="#"
否则会把你的路由重置得一干二净。