项目面试
农产品
前后端联调
我们会接入同一个局域网,后端给前端一个ip地址,能ping通说明已经打通。然后在项目中配置host。Webpack里配置proxy,target改为后端的ip地址和端口号。
微信小程序原理
- 小程序本质就是一个单页面应用,所有的页面渲染和事件处理,都在一个页面内进行,但又可以通过微信客户端调用原生的各种接口;
- 它的架构,是数据驱动的架构模式,它的UI和数据是分离的,所有的页面更新,都需要通过对数据的更改来实现;
- 它从技术讲和现有的前端开发差不多,采用JavaScript、WXML、WXSS三种技术进行开发;
- 功能可分为webview和appService两个部分;
- webview用来展现UI,appService有来处理业务逻辑、数据及接口调用;
- 两个部分在两个进程中运行,通过系统层JSBridge实现通信,实现UI的渲染、事件的处理等。
小程序开发中遇到的问题
微信小程序登录授权
调用wx.login接口获取登录凭证code。发送code给后端,后端通过code,appid,appsecret调用微信接口,返回openid和sessionkey,将openid和sessionkey生成token返回给前端,前端把后端的token缓存起来。
小程序分包
分包指的是把一个完整的小程序项目,按照需求划分为不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载。可以优化小程序首次启动的下载时间
在多团队共同开发时可以更好的解耦协作。app.json subpackages 字段声明项目分包结构
下拉分页如何实现的?
页面处理函数OnReachBottom
几个主要参数pagesize(一页多少条) currentPage(当前页数)totalPage(总页数) isCanload(是否能继续加载) list:[]
去请求接口的时候就要传pagesize还有currentPage过去,每次加载成功就将结果拼接到list,当前页数+1
长安蓝牙app
uniapp原理
uni-app 在非H5端运行时,从架构上分为逻辑层和视图层两个部分。逻辑层负责执行业务逻辑,也就是运行js代码,视图层负责页面渲染。
虽然开发者在一个vue页面里写js和css,但其实,编译时就已经将它们拆分。
逻辑层
运行在一个独立的jscore里的,它不依赖于本机的webview
- 无法运行window、document、navigator、localstorage等浏览器专用的js API
- jscore就是一个标准js引擎,标准js是可以正常运行的,uni-app的App端和小程序端的js引擎,其实是在jscore上补充了一批手机端常用的JS API
视图层
h5和小程序平台,以及app-vue,视图层是webview。而app-nvue的视图层是基于weex改造的原生渲染视图。
关于webview,iOS上默认是WKWebview,Android在APP端默认是Android system webview,因此app-vue会有手机浏览器的css兼容问题
https://zhuanlan.zhihu.com/p/377807139?utm_id=0
https://uniapp.dcloud.net.cn/tutorial/
蓝牙开发流程
https://blog.csdn.net/qq_41248093/article/details/105807361
过程中遇到的问题:双指操作冲突的问题
方向控制轮盘和速度控制条不能同时按,可能一个会影响另一个。观察发现一个组件的垂直位置会随着另一个组件的位置移动。猜测可能是由触发点的屏幕坐标冲突引起的。思考uniapp是否支持双指操作,如果每次只能触发一个坐标,两个组件同时移动时坐标自然会发生冲突。查阅uniapp文档,没有找到相关说明,就去看了下安卓开发的文档,看是否能得到什么启发。发现多指操作相关文档中有一个e.touches数组,就是手指触摸屏幕会获取到一个event对象,event.touches是一个存放屏幕触发点的数组,多指操作时会将所有触发点坐标存进这个数组。在控制台打印出来触摸的event发现uniapp也存在这个数组,所以双指操作是支持的。本来这种情况的话,每次触发一个组件的时候取出相应的坐标就可以了,问题棘手的点在于新触发的点坐标会被加入到数组最前面,那么之前根据index取到的坐标就会发生改变,且同时按下的话无法确定坐标在数组中的位置。这个时候我就想,能不能通过坐标不同的特点去确定在数组中的位置,然后就想到了利用组件所在位置不同的特性,通过比较数组中x坐标的大小确定两个组件对应坐标在数组中的位置,从而去改变两个组件在touches数组中对应index的值。在一个组件中的触发方法中改变自身index很容易,但也要同时改变另一个组件的index,这就需要用到组件通信的方法。兄弟组件通信的方法有很多,我选择了vuex状态管理的方式,将两个组件的touchindex存入store的状态里面,当任意一个组件被触摸时,如果长度大于1,先判断组件坐标在数组中的位置,再通过commit方法更新store里面两个index的值,从而到达加入坐标点后动态更新index的效果。
动画特效如何做到的?
使得连接和断开的效果更加平滑。在标题绑定一个动态的class,@keyframes+动画名字创建一个动画,设置0%,30%,100%位置的样式,30%的时间后通过color:transparent变透明,transform: translateY(100%)将标题隐藏到下面,100%时标题文字已经改变,再设置为transform: translateY(0)复原到原来位置。只要规定好animation-duration时间即可。


横竖屏切换样式出现混乱的问题。
根本原因是横竖屏切换时屏幕的长宽没有成功切换回来,如果使用到需要根据屏幕尺寸动态计算的单位如rpx则会出现问题。社区之前有一些这个问题的讨论,这是uniapp的一个漏洞,单位计算和屏幕尺寸更新的顺序没有处理好,一直没有很好的解决方案。我采用的解决方式是加入一个中间页,先跳转到中间页再切换横竖屏再跳转到目标页,并在切换过程中加入加载中提示框,关闭掉跳转动画保证切换时间足够短,以免给用户带来不好的体验。

Rem自适应
rem是相对单位,值基于根元素设置的字体尺寸(默认16px),em是相对单位,值相对于当前对象内文本的字体尺寸。怎么设置?一是在蓝湖上自动计算,二是通过插件。px自动化转rem需要什么插件? 答案是postcss-pxtorem。Plugins中写配置:设置根子体大小,匹配的属性,忽略的文件,最小转换值等。原理:根据屏幕大小动态改变根元素的字体大小。为了达到再不同设备宽度下对应不同的基准值,要计算一个比例值document.documentElement.clientWidth/设计稿宽度,动态计算不同屏幕下的根元素字体大小。
document.documentElement.style.fontSize = baseSize * scale + ‘px’
媒体查询@media可以针对不同的屏幕尺寸设置不同的样式,从而实现响应式的布局。我使用媒体查询设置不同设备的字体大小。图片的话使用src-set属性,媒体查询根据不同屏幕大小去设置不同的背景图像@2x/3x图。375 480 768 1400
怎么样去实现的移动端视口兼容?
主要用的是媒体查询和rem的方案。
饭店管理系统:
使用promise+axios封装网络请求,使用拦截器统一添加token,响应拦截器统一处理失败结果。
角色权限控制的整个流程中,前端整个流程步骤应是首先展示无需登录的默认页面(比如404页面、登录页、注册页),然后在登录或浏览器刷新时调用后端接口拿到后端给的该账户的权限数据,然后将数据注入到系统中,整个系统拿到权限数据后就开始对页面的展现内容以及页面导航进行生成,最终生成一个只展示当前用户所拥有对应权限的系统。从而达到整个角色权限的控制。

怎么联调的?bug定位?
登录逻辑
用户输入用户名和密码,前端获取到放入表单数据通过接口传给后端,获得响应的数据,判断code的类型,如果登录成功就获取data中的access_token,调用vuex中的异步方法更新token,如果token不为空,就缓存token到缓存中,有remember使用localstorage,否则使用sessionstorage。然后调用查询用户信息的接口,拿到res中的data作为user信息存储到sessionStorage中。
路由守卫router.beforeEach() 。更新user/role等信息。如果路由在白名单就放行,否则去缓存中拿token,判断是否已经注册动态路由(菜单),没有就通过user获取,然后放行。如果检查到token为空值则跳转到登录页面。getMenus方法会生成动态路由,通过menuToRoutes(menus, importComponent)转换。
路由守卫和请求拦截的区别:路由守卫只是前端做判断检查请求头是否带token,并不能判断token是否失效。
Axios请求拦截器在每次发送请求前判断本地存储或vuex是否存在token,存在则将token统一添加到http请求头,在响应拦截器中判断token是否过期,以及对各种错误信息进行判断。
https://juejin.cn/post/6844903478880370701#heading-2

怎么进行权限验证的呢?
因为http是无状态的,Cookie是维持http请求状态的最便捷的方式
Cookie的配置(http返回的一个set-cookie头用于向浏览器写入一条cookie,):
domain/path :哪些域名和路径要带上这个cookie
expires/max-age:前者指定一个到期时间,如果是none则只在当前会话有效。Max-age指定从现在开始cookie存在的秒数。后者优先级高。
Secure/httponly:前者指定浏览器只有在加密协议HTTPS下才能将这个cookie发送到服务器。Httponly指定cookie无法通过js脚本拿到。(Document.cookie),只有在浏览器发出http请求时才会带上cookie。

浏览器登录发送账号密码,服务端查用户库,校验成功就将用户登录状态存为session到库,生成一个sessionId,通过登录接口返回,将sessionId set到cookie上,之后浏览器再请求业务接口,sessionId随cookie带上,服务端拿到sessionid去session库中查询,校验成功做正常的业务处理。Session一般可能包括用户信息、session状态等,可以存储在内存(服务重启就无)、数据库(性能不高)、redis中(redis比较好,内存型数据库,以key-value的形式存储,访问快)。有一个问题是通常服务端是集群,用户请求过来会走一次负载均衡,不一定打到哪台机器上,可能会出现用户后续接口请求到的机器和他登录请求的机器不一致。存储角度用redis就可以解决,从分布角度使用nginx让相同IP的请求在负载均衡时都达到同一台机器。
Token方案:

Token方案鉴权流程:
浏览器登录发送账号密码,服务器校验后获得用户信息,将用户信息、token配置编码成token,通过set到cookie上返回给浏览器。
之后用户请求业务接口,通过cookie携带token,接口校验token有效性,进行正常业务接口处理。(除了cookie,还可以将token存入本地缓存)。为了安全考虑,引入了jwt(json web token)标准,他定义了一种传递json信息的方式,通过加密算法、数字签名,生成安全性高的token字符串,防止token被伪造和篡改。
1.Header(头) 作用:记录令牌类型、加密算法等 例如:{“alg”:”HS256”,”type”,”JWT}
2.Payload(有效载荷)作用:携带一些用户信息 例如{“userId”:”1”,”username”:”mayikt”}
3.Signature(签名)作用:防止Token被篡改、确保安全性 例如 计算出来的签名,一个字符串(通过私有的key,以及头部和负载的信息计算而成)
Access_token是业务接口用来鉴权的token,但是权限敏感的业务,tokne有效期太短,经常过期,用户体验不好。有一种方式是在来一个专门生成access_tokne的token,即refreshj_token,有效期更长。
单点登录要求不同域下的系统「一次登录,全线通用」,通常由独立的 SSO 系统记录登录状态、下发 ticket,各业务系统配合存储和认证 ticket

(jsonp跨域)

Axios封装:
项目中划分出独立的API层,有一个封装的总的API接口,其他模块调用总方法再封装各自模块下的API。各个模块的API通过export暴露,在需要的页面import引入。
首先要在axios的总配置文件中将axios二次封装成一个函数 ,设置好baseURL统一的请求前缀,timeout超时时长,用一个函数接收好返回。功能:取消重复请求、自定义是否显示loading、自动携带token、错误请求的提示、简化数据响应结构。
取消重复请求:cancelToken对象。new axios.CancelToken()给每个请求带上一个专属canceltoken,对象通过接收一个cancel函数作为参数,用变量接收后可以随时调用。收集正在请求中的接口(pending),将他们依次放入队列中,如果相同接口再次被触发则直接取消正在请求中的接口并从队列中删除。如果接口返回结果就从队列中删除。
权限管理问题:
输入用户名密码,向后端发起验证,验证通过返回token,token可以存储在cookie或者localstorage中。前端会有一个路由表,表示每一个路由可以访问的权限。用户登录之后,根据token再去拉取一个userinfo的接口获取用户的详细信息,再拿到里面的role,调用生成路由的方法,根据role动态计算出对应有权限的路由,通过router.addRoutes动态挂载路由。,登录成功后,在router.beforeEach中拦截路由,判断是否已经获取token,没有就跳转登录,有就看是否已经拉取完用户信息,没拉取就发送请求去后端拉取。
创建vue实例时就将vue-router挂载,只挂载一些不用权限的共用页面。另外有一个异步挂载的路由表,meta标签标识需要的权限,通过filter和some方法计算得到。
常见的问题
https://juejin.cn/post/6844904082050646023

https://juejin.cn/post/6844903478880370701#heading-7
前端主要是对页面访问权限的控制,更具体的业务权限控制是交给后端的。用户输入用户名密码,发送到后端验证,验证通过之后,后端返回一个token,前端拿到存入到缓存中,如果用户记住密码就存入localStorage,否则存入sessionstorage。首先创建vue实例的时候是挂载一个基本的静态路由比如登录页面这种所有用户都可以访问的路由。之后就要在路由前置守卫里判断是否已经获得token和是否已经拉取了用户信息,没有用户信息就去后端请求一个获取用户信息的接口,用户信息中有一个role数组存放着这个用户的权限,拿到role就去调用生成路由的方法,前端存放有一个路由表,就根据role和路由表各个路由权限的对比过滤出有权限的路由,生成一个用户可访问的路由表,然后通过router.addRoutes动态添加可访问路由,使用vuex管理路由表,根据vuex中可访问路由渲染侧边栏组件。




To中断当前导航,重新进入当前钩子
思考:vuex刷新页面后数据丢失。路由中没有动态路由的信息导致。
信贷系统
微前端qiankun的问题
适用于大型项目,拆分成子应用。微前端就是在路由变化的时候,加载对应应用的代码,并在容器内跑起来。
qiankun 是 function + proxy + with
样式隔离的两种方案:
- 严格沙箱
在加载子应用时,添加strictStyleIsolation: true
属性,实现形式为将整个子应用放到Shadow DOM
内进行嵌入,完全隔离了主子应用
缺点:
- 子应用的弹窗、抽屉、popover因找不到主应用的body会丢失,或跑到整个屏幕外(具体原因作者并未详细研究)
- 主应用不方便去修改子应用的样式
- 实验性沙箱
在加载子应用时,添加experimentalStyleIsolation: true
属性,实现形式类似于vue中style标签中的scoped属性,qiankun会自动为子应用所有的样式增加后缀标签,如:div[data-qiankun-microName]
缺点:
- 子应用的弹窗、抽屉、popover因插入到了主应用的body,所以导致样式丢失或应用了主应用了样式
项目用了后者,出现样式污染问题。弹窗抽屉之类的组件使用了v-transfer-dom指令挂载到子应用的DOM元素上
https://github.com/umijs/qiankun/issues/1316
难点:table组件功能扩展–自适应列宽
基于element,因为在默认情况下表格的宽度会平均分配给剩余的列。内容宽度大于默认列宽的时候就会出现换行。所以之前每个table列都要手动设置宽度。而新的需求是根据内容为每一列获取一个合适的宽度而不换行。难点主要在于如何得到合适的列宽,怎么获取每列元素的宽度、在什么时机获取,以及如何处理细节和负面影响。
大体思路就是:根据渲染后的元素宽度去动态的进行列宽计算。
首先考虑获取列宽的时机:肯定要在接口返回数据并更新dom之后才能获取到。根据vue的异步更新机制,对数据得到修改并不会马上更新视图,而是要将所有修改放到一个异步队列,至少是在事件循环的一个tick结束后再依次执行修改。所以要将获取列宽的函数写道nextTick里面去,这样我们拿到的一点是更新之后的dom。
然后考虑如何计算内容宽度:渲染后的每个单元格有个.cell
类,用white-space: nowrap; overflow: auto;
设置为不允许换行,内容超出后可滚动,同时设置display: inline-block;
以便计算实际内容宽度。这样,最终的宽度可通过.cell
元素的scrollWidth
属性得到。
然后就是以怎样的方式去获取到合适的列宽:el-table
的表头和内容分别用了一个原生table
,通过colgroup
设置每列的宽度。col
的name
属性值和对应的 td
的class
值是一致的,这样就可以遍历对应列的所有单元格,用max函数找出宽度最大的单元格,用它的内容宽度加上一个边距作为该列的宽度。
另外需要注意一些细节的处理比如内容为空的情况、排除掉用户已设置列宽的列,等一些情况处理。最后发现功能实现了但是列宽有时会在一个瞬间出现抖动的情况,这一部分为了用户体验,把loading关闭的时间延后了,在调整列宽的代码后再加了一个nextTick,里面放关闭loading的代码。一个总体的实现思路就是这样。
代码: