# webadmin_ts_vue3 **Repository Path**: tagoo/webadmin_ts_vue3 ## Basic Information - **Project Name**: webadmin_ts_vue3 - **Description**: vue3+ts+elementplus 项目运营平台 - **Primary Language**: TypeScript - **License**: Not specified - **Default Branch**: devlop - **Homepage**: http://www.yuguoxy.com/huizhi - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2025-08-20 - **Last Updated**: 2025-08-20 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ### 汇智在线运营系统 汇智在线运营系统是公司项目管理的核心平台,集中管理所有项目模块。系统提供项目查看、模块管理、动态权限分配等功能,内置菜单管理、角色管理、用户管理及操作日志等基础模块。目前已集成商城系统、OA办公系统、医疗系统、万声音乐系统,未来将持续扩展更多模块。 #### 技术栈 - **前端框架**: Vue3 + TypeScript - **状态管理**: Pinia - **路由管理**: Vue Router - **UI组件库**: Element Plus - **网络请求**: Axios - **样式处理**: Normalize.css #### 项目框架搭建 ##### 方式一:从零搭建 1. 生成项目骨架 ```bash npm init vue@latest ``` 2. 安装依赖库 ```bash npm install vue-router pinia element-plus axios normalize.css ``` ##### 方式二:使用初始模板 1. 拉取模板代码 ```bash git clone -b vue-template https://gitee.com/yuguoxy/h5project_template.git ``` 2. 安装依赖并运行 ```bash npm i npm run dev ``` #### 通用模块功能 ##### 登录模块 ![1691547866118](./media/1691547866118.png) **功能亮点**: - **双重密码验证**:输入密码时需二次确认,减少误操作。 - **智能验证码**:连续输错3次密码后触发验证码,防止恶意攻击。 - **记住密码**:加密存储密码至本地(使用`crypto-js`加密),下次自动填充。 - **登录拦截**:未登录用户访问主界面时,自动跳转至登录页。 **业务流程**: ![1691549904060](./media/1691549904060.png) **核心代码**: 后台管理系统开发笔记.mmap ##### 主界面 ![1691550437291](./media/1691550437291.png) **功能组件**: - **动态侧边栏**:支持菜单折叠,扩大内容展示区域。 - **权限菜单**:根据用户角色动态加载菜单(递归组件实现)。 - **标签页导航**:快速切换页面,支持单页/批量关闭。 - **数据看板**:集成ECharts实时图表、公告栏、访问统计等。 - 个人中心 - 显示用户昵称、头像, 设置管理,退出登录功能 - 面包屑导航 - 显示当前导航位置 ##### 权限模块 ![1691561680260](./media/1691561680260.png) **动态菜单实现**: 1. 用户登录后获取角色ID。 2. 根据角色ID查询菜单列表,将一维数据转换为树形结构。 3. 动态注册路由,确保菜单与路由匹配。 4. 刷新页面时自动重建路由,解决白屏问题。 - 菜单权限 - 根据登录不同用户角色,显示不同菜单列表 1. 描述菜单权限 - 登录成功进入主界面,主界面左侧显示菜单列表, 在以前的系统中菜单列表是写死的,路由数据也是写死的,称为静态路由。 - 超级管理员和普通人员登录进来看到的菜单列表都是一样的。 - 希望实现动态的菜单列表,根据登录的不同用户角色显示不一样的菜单列表,动态添加菜单路由。 1. 实现流程 - 用户登录成功获取用户角色id - ​ 根据角色id获取动态菜单列表 ​ - 前端根据动态菜单列表路径path值,创建组件 比如: path:'/good/add' vue项目目录下创建: ​ src ​ -|views ​ -|good ​ add.vue ​ - 动态菜单列表生成动态路由数据,动态添加到router ​ 递归遍历菜单列表, 映射 path->component ​ webpack实现和vite实现不一样 ​ router.addRoute(route) ​ - 保存菜单列表数据到store ​ - 主界面获取store中菜单列表数据遍历显示动态菜单 ​ 3. 重难点 ​ - 递归遍历菜单列表, 映射 path->component ​ - 后台返回菜单列表是一组数组,需要将一维菜单数组转树型结构菜单数组 ​ - 添加动态路由 ​ ​ 4. 问题: 刷新页面,出现白屏 ​ 原因: 刷新页面最新加载应用, 动态路由消失。 因为path对应的component组件保存在内存中。 ​ 解决方法: 刷新页面,重新递归生成component添加动态路由 - 按钮资源权限 更细粒度去控制模块对应功能,比如,商品模块中有添加商品,删除商品,编辑商品,搜索商品等功能 按钮资源权限可以动态控制用户是否具有上述某些功能, 如:超级管理员具有模块所有功能, 普通用户只具有查看商品功能,不具有删除,添加功能 按钮权限实现 1. 描述按钮权限 ​ 按钮权限也叫功能权限,控制用户是否具有添加,编辑,删除等功能操作权限。 ​ 比如: 在产品列表界面,有添加,编辑,删除产品功能,不同角色用户动态控制功能是否可用。 ​ 如果登录用户不具有删除功能权限,则不显示删除按钮。 1. 实现流程 - 用户登录成功,根据角色id 获取按钮资源权限列表数据. ​ 比如: ['UserAdd','UserEdit','UserDelete'] 保存store - 自定义按钮权限指令 v-permission ​ 需要设置按钮权限的按钮元素添加指令 ```html 删除 ``` - 实现自定义指令 - 获取store保存的按钮权限列表数据 - 获取当前按钮元素v-permission指令内容 - 判断指令内容是否在列表数据中,如果存在,具有此按钮权限,不存在移除按钮 ​ 1. 重难点 - 自定义指令封装 - 网络获取资源列表数据 - 保存到pinia持久存储 ##### token鉴权 ![1691563381226](./media/1691563381226.png) 1. token介绍 ​ token鉴权是服务端验证请求用户身份的一种技术。 ​ token称为令牌, 是加密后的一串文本字符串, 携带用户信息,过期时间等。 1. token鉴权流程 ​ 具体操作: ​ 前端: 用户登录成功,获取服务端返回token令牌,保存至本地, 后续请求需要鉴权的接口携带token ​ 后端: 服务端生成token, 接收前端发送的token, 服务端验证token成功,响应数据 1. 项目使用 ​ 后端,使用jwt中件间技术生成token和验证token ​ 在全局中间件函数中拦截请求,进行token鉴权. ​ 1. 过滤不需要鉴权的接口 ​ 2. 判断是否携带token ​ 3. 验证token,判断是否过期 ​ 前端: 保存token到localStorage中 ​ 在axios请求拦截器中设置token到authrization请求头 ​ 可以加白名单,需要token的接口才携带 ##### 网络模块 - 网络模块主要任务是调用后端接口获取数据,功能较稳定,封装之后可以在多个项目中使用。 - 公司网络模块封装是基于axios网络进行二次封装. - axios是一个基于Promise的网络库,具有请求拦截和响应拦截等特性 - 在请求拦截器中 封装统一token鉴权,实现将用户登录成功的token设置到Authorization 请求头发送给后端,后端验证成功响应数据. - 在响应拦截器中 封装统一错误处理,根据返回状态码304,404,405,403,500,505等给出相应提示.200状态码时过滤响应数据res.data ##### 组件封装 在公司后台管理系统项目开发过程中重复的功能界面,抽离封装成组件,大部份在elementPlus组件基础上二次封装 项目组件分为通用公共组件和业务组件 - 通用公共组件 - 可以在多个项目复用的组件 - 菜单组件 - 动态显示菜单列表 - 菜单项递归组件 - 根据数据层级递归显示 - 收缩菜单组件 - 实现菜单侧边栏收缩 - 面包屑组件 - 导航定位当前路由位置 - 标签页组件 - 方便快速切换菜单导航,标签项能单独关闭,关闭所有和其它 - 表单组件 - 参考第三方表单组件封装 - 表格组件 - 参考第三方表格组件封装 - 列表显示 - 搜索功能 - 功能区域 - 分页区域 - 业务组件 - 项目具体业务相关组件 - 个人中心组件 - 登录组件 - 产品添加编辑组件 - 图片上传组件 - 图片格式大小验证 图片预览功能 ##### 无感刷新 - **双Token机制**:短Token(Access Token)用于日常请求,长Token(Refresh Token)用于续期。 - **自动续期**:短Token过期时,自动用长Token获取新Token,用户无感知。 token无感刷新是保证系统安全性和用户体验平衡的一种安全技术。 - 首先需要了解下token是什么? ​ Token令牌简单来说是服务端为验证用户身份而生成的一串有时效性信息的字符串。 ​ 当用户第一次登录后,服务器生成一个Token便将此Token发送给客户端, 以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。 - Token作为用户获取受保护资源的凭证,必须设置一个过期时间,否则一次登录便可永久使用,认证功能就失去了意义。 ​ 但是矛盾在于:过期时间设置得太长,用户数据的安全性将大打折扣; 过期时间设置得太短,用户就必须每隔一段时间重新登录,以获取新的凭证,这会极大挫伤用户的积极性。 - 利用长短token,Refresh Token(长token)与 Access Token(短token) 这一概念来平衡Token安全性和用户体验。 ​ 具体实现: 用户登录后,服务端返回两个token一个长token(refreshToken)一个短token(accessToken),当短token过期, 用户凭长token到服务端重新认证,刷新短token时间, 只有当长token过期,再重新登录。 - 比如: 用户登录后一直使用系统,这时短token过期,程序自动使用长token调用刷新接口刷新短token继续使用。如果用户一段时间未使用系统,超过长token过期时间,这时使用系统需重新登录。这样解决,系统使用过程中只要一直使用不会重新登录,过一段时间不使用,才会重新登录。 - 具体实现参考: doc目录/无感刷新token.mmap ##### 屏幕适配 待完善 ##### 大文件上传 - **分片上传**:将文件切分为多个片段并行上传。 - **断点续传**:记录上传进度,网络中断后可从断点继续。 #### 电商模块 ##### 产品模块 ![1691563478118](./media/1691563478118.png) - 添加产品 - 编辑产品 - 产品图片上传 - 产品列表显示 - 产品分页 - 删除产品 - 批量删除产品 - 搜索产品 ##### 其它模块 - 待完善 #### 项目难点 ##### 刷新主界面数据消失白屏 - 描述: 登录成功进入主界面之后,可以正常操作系统,但是,刷新界面时,所有数据消失白屏 - 问题原因: - 刷新界面(启重后台系统-重新从入口文件开始执行),动态添加的菜单路由消失(动态路由保存在内存,菜单数据也是保存内存中) - 动态添加的菜单数据路由数据是登录成功动态添加,刷新界面没有走登录动态添加路由流程 - 解决问题方案 - 根组件App.vue调用添加动态路由代码 ##### 首屏白屏性能优化 1. 首屏为什么要做性能优化? ​ 1.1 加载应用时,用户端会等待整个应用程序包响应到本地后,浏览器再进行渲染显示 ​ 1.2 第一次加载后浏览器缓冲机制会对响应的应用程序资源进行缓存 ​ 1.3 首次加载应用,如果应用程序包较大,程序资源包响应到本地时间会较长,会出现白屏现象 1. 如何做性能优化? ​ 2.1 减小程序包的大小 ​ 1. 服务端gzip压缩开启 ​ 2. UI组件按需引入 ​ 3. script标签异步加载模块 ​ 4. 懒加载技术,路由懒加载,图片懒加载 ​ 5. 图片压缩 ​ 2.2 提升速度 ​ 1. 利用cdn技术对资源进行缓存提速 ​ 2.3 减少请求次数 ​ 1. 雪碧图合成小图标,减少请求次数 ​ 2. 小图片使用base64编码嵌入html中请求 1. 如何确定异步加载那些模块? ​ Webpack Bundle分析 1. 感性优化 ​ 骨架屏 loading效果 ##### 微信支付流程 家具电商系统,在线微信支付功能实现 ![1691742171830](./media/1691742171830.png) - 首先到微信支付平台开通微信支付, 填写相关信息; 如: 微信支付成功通知商家的后端接口地址. - 支付业务流程: - 用户点击购买,调用商家后端生成订单,商家后台调用微信支付平台生成预订单 - 用户发起支付,唤起微信支付二维码界面,同时向后端发起支付请求后端监听支付状态 - 用户扫码支付,唤起微信支付界面,微信平台支付成功,通知后端更新订单状态 小程序支付流程 ​ ![1691744683983](./media/1691744683983.png) - 用户点击购买, 调用后端下单接口,后端调用微信支付平台下单接口,生成预订单 - 发起支付, 调用 wx.rquestPayment支付接口,唤起小程序支付界面,输入支付账户信息 - 处理支付结果, wx.rquestPayment支付接口回凋函数处理支付结果 ##### 富文本框tinymce - 业务: 日志模块添加日志信息 - 技术栈: tinymce - 使用方式 - 官网下载包,采用script方式引入,未使用npm - npm 方式,打包时会一起编译打包,安装包过大,容易引起用户首次加载白屏 - script方式, tinymce包不会打进安装,用户使用时异步下载tinymce使用 - 具体用法: - 定义textarea多行文本框区块占位 - tinymce对象初始化, 初始时指定textarea元素和其它配置信息 - 遇到问题 - 切换界面时,富文本框显示为textarea,手动刷新后再显示富文本框 - 解决办法: 初始时指定textarea元素ID 使用动态的ID名称,id = 'vue-tinymce-' + Date.now() - 原因: 富文本框存在缓存 ##### 图表库echarts - 业务 : 首页柱状图形式展示产品销量 - 技术栈: echarts - 用法: - 定义一个div区块存放echarts图表 - 调用init方法传入div区块初始化echarts返回 echart实例对象 - 调用setOptions设置图表配置 - 图表配置 必须配置x横坐标,series 数据,还有标题y坐标,工具条,鼠标移入效果等. - 关键点: 后端获取接口数据,与series配置数据匹配。 - 遇到问题 - 屏幕尺寸变化时图表库不能自适应 - 解决方法 - 使用ResizeObserver 监听单个区块元素变化 效率更高(未使用 window对象resize事件) - 尺寸变化调用myChart.resize() 改变图表大小自适应 - 引入防抖技术优化频繁执行回调 ##### 百度地图 JavaScript API提供在Web端获取当前位置信息的方法,融合了浏览器定位、IP定位、安卓定位SDK辅助定位等多种手段,提供了获取当前准确位置 - 业务: 移动端app显示附近商家位置, 点击商家显示商家信息, - 技术栈: 百度地图 - 关键技术: 展示地图, 定位, 标点, 弹出信息, (划线路未实现) 注意: 1.由于Chrome、iOS10以上系统等已不再支持非安全域的浏览器定位请求,为保证定位成功率和精度,请尽快升级您的站点到HTTPS。 2.由于浏览器原生定位成功率并不高,我们提供ip和安卓sdk定位进行辅助 #### 面试问题 ##### 虚拟dom 虚拟dom本质上是一个javascript对象,是对真实dom的映射. 真实dom经过浏览器渲染重排重绘后被显示.每次更改真实dom都会引起重排重绘. vue框架为了提升渲染效率,将真实dom映射成虚拟dom, 开发时对虚拟dom进行操作,由vue框架通过diff算法将操作映射到真实dom,减少重排重绘次数,达到提升效率目的 ##### 为什么会出现Proxy代理 准备知识 - 介绍proxy ​ proxy代理对象,用于代理目标对象。 利用proxy捕获器实现对目标对象的统一拦截处理 ​ 常见的捕获器有13种. 如:get和set ​ 当通过proxy获取目标对象值时触发get方法, 当设置值时触发set方法 - Object.defineproperty()介绍 ​ Object.defineproperty在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象 ​ 当操作Object.defineproperty定义对象属性时会触发访问器 ​ 获取属性值是触发get,设置属性值时触发set - proxy与Object.defineproperty区别 ​ Proxy是对整个对象的代理,而Object.defineProperty只能代理某个属性。 ​ 对象上新增属性,Proxy可以监听到,Object.defineProperty不能。 ​ 数组新增修改,Proxy可以监听到,Object.defineProperty不能。 - 应用 ​ vue2使用的是Object.defineproperty ​ vue3使用proxy 回答 - 引入vue框架 ​ 当前流行的前端框架vue使用proxy实现响应性 ​ 编程方式由原生操作dom节点转变为操作数据,数据变化后由框架自动更新界面 - 介绍vue2响应式原理 ​ vue2中采用“数据劫持”结合“发布者-订阅者”模式的方式,通过“Object.defineProperty()”方法来劫持各个属性的setter,getter, ​ 在数据变动时发布消息给订阅者,触发相应的监听回调。 - vue2响应式缺点-及proxy优点对比 ​ Object.defineProperty()只能代理一个属性,需要循环遍历对象,Proxy可以代理整个对象. ​ 对象上新增属性,Proxy可以监听到,Object.defineProperty不能。 ​ 数组新增修改,Proxy可以监听到,Object.defineProperty不能。 - 结论 ​ vue3中使用proxy ##### typescript与javascript比优势是什么? TypeScript 是JavaScript 的超集,提供了类型约束,因此更可控、更容易重构、更适合大型项目、更容易维护项目。 1、更好的代码可读性和可维护性:TypeScript 强制使用类型注解,可以在编写代码时提供更好的代码提示和自动补全。类型注解还可以让代码更加清晰易懂,降低代码维护成本。 2、更好的代码可靠性和健壮性:TypeScript 的静态类型检查能够避免很多常见的编程错误,并在编译时发现很多隐藏的错误。这可以让代码更加可靠和健壮,减少代码运行时的错误和异常。 3、更好的代码组织和重构:TypeScript 支持模块化编程和命名空间,可以让代码更加有序、可维护。此外,TypeScript 还支持接口和抽象类等特性,可以更好地组织和抽象代码,促进代码重用和重构。 4、更好的集成和生态支持:TypeScript 可以和 JavaScript 无缝集成,可以直接调用 JavaScript 库和框架。同时,由于 TypeScript 使用广泛,有很多相关的工具和生态支持,例如编辑器插件、构建工具等。 ##### axios理解 axios是一个基于Promise的HTTP客户端,用于浏览器和Node.js平台中发送HTTP请求。它支持所有现代浏览器(包括IE8及以上版本),并且可以使用Promise API、拦截请求和响应、转换请求数据和响应数据等功能。 axios具有以下特点: 1. 支持Promise API:可以方便地处理异步操作和链式调用。 2. 支持请求和响应拦截:可以在请求或响应被处理前进行拦截,从而实现诸如自动添加token、路由拦截等功能。 3. 支持转换请求和响应数据:可以在请求或响应被处理前对其进行转换,例如将JSON格式的请求数据自动转化为FormData格式。 4. 支持取消请求:可以通过取消请求来减少无用的网络请求,提高性能。 5. 支持客户端和服务端同时使用:可以在Node.js平台中使用,使得客户端和服务端之间的操作更加统一。 总之,axios是一个功能强大、易于使用、可扩展性强的HTTP客户端库,在Vue和React等前端框架中广泛应用。 ##### Vue 3.0 所采用的 Composition Api 与 Vue 2.x使用的Options Api 有什么区别? Vue 3.0 中的 Composition API 是一种不同于 Vue 2.x 中的 Options API 的新的编程模式。主要区别包括: - 组合 API 通过在单个函数中组合各种逻辑来编写组件,而选项 API 使用分散在组件选项中的各种属性和方法来编写组件。 - 组合 API 提供了更好的代码分离,使得逻辑更为清晰,同时在进行复用方面更为灵活。 - 组合 API 将组件逻辑拆分为可重用和可测试的函数,而不是在一个大型对象中定义选项来描述组件行为。 - 组合 API 使得在不同的组件中共享逻辑更加容易。 - 组合 API 是基于函数的API,而选项API是基于对象的API。这意味着编写组件时,您可以使用函数式编程模式来生成封装的响应式状态和计算属性。 ##### 项目上线流程 ​ 项目开发完成后需要部署到生产环境及部署上线提供给用户使用. ​ 在部署生产环境上线前,开发人员要在开发环境部署测试,比如我会, - 先用npm run build命令打包项目 - 拷贝项目包到本地nginx服务器 html目录, 修改config配置文件指向项目包目录 - 启动nginx测试. - 如果线上测试遇到问题,使用sourcemap 技术及源码映射技术找到线上出错位置修改. - 测试完没问题交给后端运维部署测试环境, 经测试人员测试完再部署生产环境 ​ #### 模拟面试 ##### 面试官提问环节 1. 请自我介绍一下? 2. 请介绍下你最近做的项目? 3. 项目成员组成,前后端怎么配合? 4. 哪些功能是你做的? 5. 遇到哪些印象比较深的技术难题,如何解决的? 6. 团队的开发流程说一下? 7. 项目中收获有那些? ##### 自我介绍 面试官您好! 我叫xx,从事前端开发x年了, 上家公司是家外包(自研)公司,主要从事移动端App开发, 小程序开发,pc端后端系统开发。技术栈主要是vue和小程序方向方向,能进行独立开发,react方向也做过一些模块开发. ##### 项目介绍 最近做的项目是个 xx小程序(xx后台管理系统)是医疗还是电商还是旅游做什么的项目介绍说一说? - xx小程序使用 uniapp+uview实现 - xx后台管理系统 vue3+elementplus实现 实现的功能有: 1. 账户密码登录, (小程序端手机号登录) 2. 配置微信支付,实现在线缴费 3. 负责整个产品模块,显示产品列表,产品添加,编辑,产品搜索,产品删除,产品分页,产品分类下拉选择,产品图片预览上传 4. 参与项目框架搭建,动态权限菜单,按钮资源菜单实现 5. 实现网络模块封装优化,token鉴权和统一错误处理 6. 配合代码检查工具格式化工具,重构代码,优化性能 7. 参与了组件封装. 实现图片预览上传组件,产品添加编辑弹框组件,主界面收缩菜单组件,菜单列表组件封装 ### javascript面试题 #### 1. this指向 - 知识点: 1. 普通函数, 定时器, 自执行函数, this->window对象 注:非严格模式, 严格模式值undefined 2. 对象方法this->当前对象 3. 事件处理函数this-> 事件源 ​ 注: this 和函数的调用方式有关系,和函数的定义方式没有关系 1. 改变this指向方法 - call - apply - bind 注:call和apply都是funcition原型上的方法, 每一个函数作为funciton的实例都可以调用这两个方法而这两个方法执行的目的都是改变函数中this的指向的, 让函数执行并且改变函数中指向的,唯一的区别是call是一个一个传,apply是以数组的方式 - 题1: ```js var foo = { bar: function () { console.log(this.baz); }, baz: 1 }; //注: ; 不要忘了 // 自执行函数, 定义和执行一起 (function () { console.log('arguments ',arguments); arguments[0]() // 函数体代码 获取第一个参数执行 // 自执行函数this->window })(foo.bar) 输出: undefined ``` 考查: this指向, 函数参数, arguments,自执行函数和对象语法 - 题2: ```js //let 定义不会变量提升,不会变成window下的属性,运行结果就是undefined,var定义会将变量提升为window的属性打印就是10 // var num = 10; let num = 10 let obj = { num: 20, run: function () { console.log(this.num); }, }; obj.run();//this是obj let { run } = obj; run()//this是window console.log(num);//打印的是window下的num console.log(obj.num); ``` 考查: this指向结合变量提升, let块作用域 - 题3: ```js var len = 117 let func = { len: 935, showLen: function () { console.log(this.len) }, show: function () { let _this = this; (function (cb) { cb.call(_this) })(this.showLen) }, } func.show() 结果: 935 ``` 考查: this 指向,改变this指向方法 #### 2. 事件轮循 ![1691723863595](./media/1691723863595.png) - 题1:看看这段代码会依次输出什么? ```js console.log('aaa') setTimeout(() => { console.log('bbb') }, 1000) const start = new Date() while (new Date() - start < 3000) { } setTimeout(() => { console.log('ccc') }, 0) const p1 = new Promise((resolve, reject) => { console.log('ddd') resolve() console.log('eee') }) p1.then(() => { console.log('ggg') }) console.log('fff') 输出: aaa ddd eee fff ggg ccc bbb ``` 考查: js事件轮循机制, 宏任务与微任务 - 题2: 看看这段代码会依次输出什么? ```js async function async1() { console.log('async1 start'); await async2(); console.log('async1 end'); } async function async2() { console.log('async2'); return 'async2 >>>' } console.log('script start'); setTimeout(function () { console.log('setTimeout'); }, 0) async1(); new Promise(function (resolve) { console.log('promise1'); resolve(); console.log('promise2') }).then(function () { console.log('promise3'); }); console.log('script end'); 输出: script start async1 start async2 promise1 promise2 script end async1 end promise3 setTimeout ``` #### 3. 排序问题 - 知识点: - sort() 方法用原地算法对数组的元素进行排序,并返回数组。默认排序顺序是在将元素转换为字符串,然后比较它们的 UTF-16 代码单元值序列时构建的 - 语法: arr.sort([compareFunction]) compareFunction可选 用来指定按某种顺序进行排列的函数。如果省略,元素按照转换为的字符串的各个字符的 Unicode 位点进行排序。 - 题1 下列对象按名称排序 ```js var person = [ { name: 'Bom', age: 12 }, { name: 'Jack', age: 10 }, { name: 'Bob', age: 22 }, { name: 'Ba', age: 5 }, { name: 'Tony', age: 25 }, ] ``` - 方法一 ```js var person = [ { name: 'Bom', age: 12 }, { name: 'Jack', age: 10 }, { name: 'Bob', age: 22 }, { name: 'Ba', age: 5 }, { name: 'Tony', age: 25 }, ] // 定义sort比较函数 let compareFun = (m,n)=>{ if(Object.prototype.toString.call(m) == '[object Object]'){ m = m.name n = n.name } let a = m.substring(0,1) let b = n.substring(0,1) if(a < b){ return -1 } if(a > b){ return 1 } return compareFun(m.substring(1),n.substring(1)) } person.sort(compareFun) console.log(person); ``` - 方法二 ```js /** * 英语则是根据字母排序的, * 汉字根据汉字拼音来排序。 * a.localCompare(b) – 升序 * b.localCompare(a) – 降序 */ person.sort(function(a, b) { return (a.name).localeCompare(b.name) }) console.log(person); ``` #### 4. 树型结构遍历 - 题1 根据key查找对应label ```js const menuItems = [ { key: 'sys', label: '系统管理', children: [ { key: 'sys/menu', label: '菜单管理' }, { key: 'sys/role', label: '角色管理' }, ], }, { key: 'shop', label: '商城系统', children: [ { key: 'shop/banner', label: 'banner' }, {key: 'shop/category', label: '商品分类'}, { key: 'shop/goods', label: '商品管理'}, ], }, { key: 'oa', label: 'OA办公系统', children: [ { key: 'oa/list', label: '回复管理' }, { key: 'oa/lg', label: '日志管理' }, { key: 'oa/reply', label: '日志回复' }, ], }, ] /** * 根据key查找对应label * arr instanceOf Array * Object.Prototype.toString().call() === [object Array] * @param {*} list * @param {*} key */ const findLabelByKey = (list, key) => { for (let i = 0; i < list.length; i++) { let item = list[i] // 菜单项 if (item.key === key) { return item.label } if (item.children && Array.isArray(item.children)) { // 递归调用 const subLabel = findLabelByKey(item.children, key) if (subLabel) { return subLabel } } } } const label = findLabelByKey(menuItems, 'shop') console.log(label) ``` - 题2 一维数组 转树型结构 ```js const arr = [ {id: 4, parent_id: 1, node_type: 0, label: '菜单管理',path: '/sys/menu', sort: 103}, {id: 2, parent_id: 1, node_type: 0,label: '角色管理',path: '/sys/role',sort: 102}, {id: 3, parent_id: 1,node_type: 0,label: '用户管理', path: '/sys/user',sort: 101}, {id: 1, parent_id: 0, node_type: 0,label: '系统管理', path: '/main_sys',sort: 100}, {id: 8,parent_id: 5,node_type: 0,label: 'Banner',path: '/shop/banner',sort: 99 }, {id: 7,parent_id: 5,node_type: 0,label: '商品分类',path: '/shop/category',sort: 98}, {id: 6,parent_id: 5,node_type: 0,label: '商品管理',path: '/shop/goods',sort: 97}, {id: 10, parent_id: 5,node_type: 0,label: '订单管理', path: '/shop/order', sort: 95 }, {id: 9,parent_id: 5,node_type: 0,label: '会员管理',path: '/shop/member', sort: 94}, {id: 5,parent_id: 0,node_type: 0,label: '商城系统',path: '/main_shop', sort: 90 }, {id: 12,parent_id: 0, node_type: 0, label: 'OA办公系统',path: '/main_oa', sort: 80}, {id: 16, parent_id: 0, node_type: 0,label: '万声音乐',path: '/main_ws',sort: 70}, {id: 38, parent_id: 0,node_type: 0,label: '图库', path: '/tk',sort: 1}, {id: 13,parent_id: 12,node_type: 0,label: '员工管理', path: '/user/list',sort: 1}, {id: 17,parent_id: 16, node_type: 0, label: '音乐管理',path: '/music/list',sort: 1}, {id: 40,parent_id: 38, node_type: 0,label: '风景图', path: '/fj/list',sort: 1}, {id: 37,parent_id: 5,node_type: 0,label: '商家管理',path: '/shop/store',sort: 1}, {id: 14,parent_id: 12,node_type: 0,label: '日志管理',path: '/log/list',sort: 1}, {id: 18,parent_id: 16,node_type: 0,label: '歌手管理',path: '/singer/list',sort: 1}, {id: 39,parent_id: 5, node_type: 0, label: '商家地图',path: '/shop/mp',sort: 1}, {id: 15,parent_id: 12,node_type: 0,label: '日志回复',path: '/reply/list',sort: 1}, {id: 19,parent_id: 16,node_type: 0,label: '歌单管理',path: '/song/list', sort: 1}, ] const getThreeMenu = (rootList,id,menuList)=>{ rootList.forEach(item=>{ if(item.parent_id === id){ menuList.push(item) } }) menuList.forEach(item=>{ item.children = [] getThreeMenu(rootList,item.id,item.children) if(item.children.length === 0){ delete item.children } }) return menuList } const menuList = getThreeMenu(arr,0,[]) console.log(menuList); ``` ### hr面试 **放心大胆的去准备** 1. 提前准备好面试问题及回答 2. 保持自信和放松 3. 多面,学学渣男,就算有些公司不喜欢,但是缺乏面试技巧的时候练练手 4. 脸皮厚一点,不要太老实,适当吹吹,但是要注意圆的回来哦,圆不回来的可以提前找专业的人模拟对练 **面试不要问啥答啥,要知道问这个问题背后的意图** 1. 例如,问你还有接到其他公司的offer吗 面试官在考察什么:你的入职意向度+录用你的成本+衡量你的市场竞争力 2. 你为什么选择我们公司 你的意向性+稳定性+考虑工作的主要关键点 千万不要说你要我来面试,我刚好没工作,所以就来了 #### 面试准备 **1.提早准备** 天上不会掉馅饼,机会总是留给有准备的人。面试也是门技术活,也是可以总结经验,也是有诀窍的。很多面试题目大同小异,提前准备能够帮你解决80%的问题 **2、熟悉简历** 有部分同学写完简历就忘得一干二净,面试时被问得面红耳赤,但是你要知道,有很多面试官是通过简历来提问的 **3.制作作品集** 作品集是比较直观的去证明的实力,用作品和实际数据讲话,最能打动面试官 **4.多模拟** 多模拟面试,打磨面试答案,包括语音 语调等等,形成肌肉记忆 #### 问题目录 1. 3分钟自我介绍 2. 作为应届生,你如何胜任这份工作 3. 你如何理解[前端]这个岗位 4. 最有挑战的一件事 5. 最困难的一件事 6. 你的职业规划是什么 7. [为什么从上一家公司离职](./hr/为什么从上一家公司离职.html) 8. [你的缺点是](./hr/你有什么优缺点.html) 9. 你没有被录用怎么办 10. 谈一次失败的经历 11. 你认为你最大的优点是什么? 12. 我们为什么要录取你 13. 你的兴趣爱好 14. 你的工作业绩很好﹐得到了领导的认可,但是同事却孤立你,你会如何处理? 15. 为什么会申请这个岗位 16. 你怎么看待加班? 17. 你通常怎么对待别人对你的负面评价? 18. 可以谈一下你的家庭吗 19. 对于跳槽,你怎么看 20. 眼下对你来说,生活中最重要的事情是什么? 21. 如果你可以顺利入职,你会怎么适应新环境/开展工作? 22. 对于你申请的这个岗位,你觉得你欠缺什么? 23. 当你与上级的意见不一致﹐你会怎么处理? 24. 当你的客户明显刁难你时,你会如何应对? 25. 你的简历显示你参加了丰富的课外活动,你是如何平衡学业和业务活动的? 26. 请你用三个词概括下你自己? 27. 你的期望薪资是多少? 28. 实习中你遇到的最艰巨的事情是什么 29. 是否有男女朋友,不在同一个城市会如何平衡 30. 你怎么看待我们公司最近的负面评价? 31. 你会如何处难工作中的压力? 32. 如果你在工作中出现失误﹐给本公司造成了经济损失,你会怎么办? 33. 上司交给一份不属于你的工作,你会怎么处理? 34. 父母对你的工作、考试的态度(不会影响是否录取你,薪酬待遇的预期,稳定性、个性) 35. 父母做什么工作的 36. 如果有前辈,但是需要你当管理者,你会怎么办 37. 怎么看待这种很碎很杂的工作 38. 之前面试过什么工作 39. [你还有什么要问我的吗?](./hr/还有什么问我的吗.html) ​ #### 疑惑问题 - **明明感觉面试表现很好,面试官也很满意,可为啥就没有后续呢** 1. 其实面试的时候并不是聊得越好,时间越久就越有戏,聊得越久,就越容易发现缺点,聊得越开心,人在放松的状态下也更容易暴露缺点 2. 用人部门没有通过 HR是辅助用人部门判断一个面试者基本的素质和通识能力的,但还要一个重要职能是给用人部门报告可能存在的风险点,例如跳槽多不稳定,已婚未孕,这些都有可能导致面试没有后续 1. 企业并非真实招人 不排除有些企业为了了解市场行情,约你去面试,问你很多专业问题,问你对行业的看法和观点,过去的工作经验和方法。甚至一来就问你一个实际问题,怎么解决,你为了展现自己的实力,滔滔不绝,头头是道,你以为相谈甚欢,但是实际上这个公司已经在你的谈话中得到了他想要的东西 1. 也有可能确实聊完后慢慢验证发现你与岗位不匹配 可能一开始看你的简历很合适,人也很彬彬有礼,但是聊完后发现你所做的项目深度没有达到公司的要求,或者说文化不匹配等等 1. 薪资高于岗位预算 HR跟你聊得很好,但是你的薪酬高于岗位的预算,HR预估了谈判成功的可能性较低,所以没有后续。很多宝子说我就是按照职位要求上面写的薪酬范围去报的薪酬呀,那你还是too young too simple。岗位要求如果写的是12-18k,你以为几个人可以拿到16k-18k的薪酬 - **Hr已读不回怎么办** HR要完简历但是没有下文,通常是有四种原因,其实前面三种情况呢,基本上是没救,我们求职者唯一能扭转局面的就是在第四种情况,看完还有疑问的欢迎私信我。 1. **第一种**情况跟岗位要求本身有关,你的简历上有明显的硬伤,不符合招聘的要求,比如说跳槽太频繁啦,年龄太大啦,学历不达标等等。 2. **第二种**情况跟企业招聘方有关,公司可能并不是真的在招人,发布岗位呢,只是为了收集简历,暂时确保他的这个池子里面有足够的鱼,到真正要用的时候就能快速响应。 3. **第三种**情况跟HR有关系,可能这个岗位啊其实已经招到人了,但是HR呢,没有来得及关闭网上的岗位 4. **第四种**情况跟简历的质量有关,你的简历写的整体很平淡,重点跟你的优势不突出,导致HR很难记住你,也不会把你从几十个甚至几百个简历里面挑出来给你面试。当然也有可能是HR的工作临时被打断了,还没来得及回复 - **投了一堆简历,还是找不到工作** **不要用战术上的勤奋去掩盖战略上的懒惰** 多投递是为了增加你的成功率,因为offer量取决于面试量,面试量取决于投递量,但是这不是让你去毫无目的的乱投 接下来我要说两点,同学们认真反复思考,按照这两点做,还是找不到工作算我输 1. **学会站在公司的立场上去思考自己的简历**,复盘自己的面试,你的简历能让人眼前一亮吗,你的面试表现专业度、意向度都展现出来了吗。(有的小伙伴为了让简历高大上,特意写一下晦涩难懂的长句,你真当面试官是傻子吗,看不出来,简历可以适当包装,但是过度就不行) 2. **多投**没有错,但是一直数据不好,不妨静下心来复盘自己是投递方向错了,还是简历需要调整,还是面试表现不好**,找到方向再出发,在正确的道路上走可能会比较慢,但是总会到达成功的彼岸,在错误的道路上,一味盲目乱投,只会让你距离上岸越来越远 - **占时找不到工作别慌,可以做以下9件事** 1. 扩充自己的招聘渠道,除了校招渠道可以多试试其他求职平台,例如智联、前程、boss;多投递 2. 复盘投递的公司,岗位,面试经过,有可能是岗位定位压根不适合你,也有可能需要打磨面试技巧 3. 修改简历,修改自己的简历 4. 修改各平台打招呼的话术 5. 提前准备好面试常见问题,面试就是开卷考试,重在[考前准备] 6. 充实自己,每天除了找工作的时间,可以看看专业书籍、锻炼身体、看行业报告或通过第三方了解目前求职环境 7. 寻找兼职或做做小副业,哪怕是写写公众号,我好几年前也是因为找不到工作下海鲜市场做职业规划,没想到机缘巧合之下,副业居然超过了主业 8. 打扫布置家里,从心理学的角度,外在就是你内在的映射,你在打扫布置家里的时候,其实也是慢慢梳理杂乱头绪的时间,我经常在打扫卫生的时候蹦出灵感 9. 相信吸引力法则,不要想我怎么还找不到工作呀,要去想我就快找到一份适合的工作了 #### **简历评测** **怎样才能拥有让HR眼前一亮的简历呢,如果给你的简历打分,你打多少分** **1. 你的简历是否重点突出,而不是写成一大段是** A:绝大数是,能够看到重点清晰,核心能力-10分 B:比较清晰-6分 C:一般-2分 D:完全混乱-0分 **2. 简历排版是否简洁干净,是否排版整齐,各模块区分明显** A:简洁干净、整齐,各模块区分明显-10分 B:排版干净,整洁,对齐一般-6分 C:一般 -2分 D简历花里胡哨,排版混乱-0分 **3. 简历是否数据化了** A:基本都量化了量化数据4个以上-10分 B:量化数据3个-6分 C:量化数据1-2个-2分 D:基本无量化,大量的丰富、沟通能力佳等偏主观词语-0分 **4. 简历中说明结果的有多少条** A:6条以上-10分 B:3-5条-7分 C:基本没有,但是又1到2个-3分 D:完全没有,全是工作内容 **5:简历是否有具体针对某个具体的岗位进行了关键词调整** A:有-10分 B:没有,看感觉来,可能有部分关键词-6分 C:没有,一份简历投遍天下所有岗位 **6、自我评价是否有比较客观性的总结的词语,还是整篇都是沟通能力强,有责任心** A:完全有-10分 B:有一些-8分 C:完全没有,都是偏主观的词语-0分 **7.教育背景、荣誉奖项、竞赛实践、社团经历** A: 教育背景有明确的排名绩点,实习经验分主次,重点突出、竞赛实践和社团有针对性的突出并分类-10分 B: 教育背景加上很多课程名称、竞赛奖项、荣誉、社团有一些取舍-6分 C: 所有经历一股脑全写上,甚至没有按照时间先后顺序进行排序也没有分类-3分 D: 荣誉奖项、竞赛、社团全无-0分 ### 前端工程化 > 改进开发、构建、发布等前端工程化体系和组件化建设,持续提升研发效率 #### 开发环境 1. 开发环境和生产环境配置 项目开发过程中,我们通过 npm run dev 命令执行的代码环境称为开发环境,是程序开发时使用的环境 使用npm run build 命令打包代码执行环境称为生产环境,部署到外网正式环境使用 2. 生产环境和开发环境使用的接口资源是不一样的,如何区分? 根据开发和生产环境分别加载.env.development 和 .env.production配置信息 区分 ```json # 页面标题 VITE_APP_TITLE = 汇智管理系统 # 开发环境配置 VITE_APP_ENV = 'development' # 开发环境根地址 VITE_APP_BASE = 'http://43.136.34.132:8089' # 汇智管理系统/开发环境 VITE_APP_BASE_API = '/dev-api' ``` ```js 通过 import.meta.env.VITE_APP_BASE 读取变量值 const base = import.meta.env.VITE_APP_BASE ``` 在vite.config.js文件中 ```js const env = loadEnv(mode, process.cwd()) const { VITE_APP_ENV, VITE_APP_BASE,VITE_APP_BASE_API } = env ``` ```js import { defineConfig, loadEnv } from 'vite' // https://vitejs.dev/config/ export default defineConfig(({ mode, command }) => { const env = loadEnv(mode, process.cwd()) const { VITE_APP_ENV, VITE_APP_BASE,VITE_APP_BASE_API } = env return { base: VITE_APP_ENV === 'production' ? '/huizhi' : '/', // router同步更新 server: { host: '0.0.0.0', port: 8080, open: true, cors: true, // Load proxy configuration from .env.development proxy: { // '/dev-api': { // target: 'http://43.136.34.132:8089', // changeOrigin: true, // rewrite: p => p.replace(/^\/dev-api/, '') // }, // '/prod-api': { // target: 'http://43.136.34.132:8082', // changeOrigin: true, // rewrite: p => p.replace(/^\/prod-api/, '') // } [VITE_APP_BASE_API]: { target: VITE_APP_BASE, changeOrigin: true, rewrite: p => p.replace(new RegExp('^'+ VITE_APP_BASE_API), '') } } }, }) ``` 1. 代理服务器配置 项目配置到外网nginx环境 ```js server { listen 3001; server_name localhost; location / { root html; index index.html index.htm; } # 当请求url地址包含 /prod-api时,重定向到proxy_pass指定地址 # 如请求: http://localhost:3000/prod-api/api/goodlist # 代理后: http://43.136.34.132:8082/api/goodlist location /prod-api/ { proxy_pass http://43.136.34.132:8082/; } ``` ### 前端监控 #### 1.为什么做前端监控 - 更快发现问题和解决问题 - 做产品的决策依据 - 提升前端工程师技术深度和广度,打造简历亮点 - 为业务扩展提供更多可能性 #### 2. 需要监控什么? - 错误统计 首先,我们的代码发布到线上总是会发生奇奇怪怪的错误,错误原因也五花八门,可能是浏览器兼容问题,可能是代码里面没做兜底,也可能是后端接口挂掉了等等错误,可能随便一个错误都会影响用户的使用,所以对线上进行错误监控显的尤为重要,能够让我们第一时间去响应报错并解决。 - 行为日志埋点 对于一些常见的电商app,比如淘宝,都有一套自己的用户行为分析的系统,分析用户浏览时间比较长的页面有哪些,常点击的按钮有哪些等等行为,通过分析用户的这些行为去制定不同的策略引导你购物,这些都可以通过前端埋点去实现对用户行为的监控。 - PV/UV统计 我们上线那么多的前端页面,肯定特别想知道我们的用户对哪个页面的访问次数比较多,也想知道每天有多少的用户访问我们的系统,这就需要用到PV,UV的统计 #### 3. 怎么实现 - 接入现成的 1. sentry https://docs.sentry.io/ 2. fundebug https://www.fundebug.com/ - 自己封装 - monitor-sdk 1. package.json文件配置monitor-sdk路径 ```json "dependencies": { "axios": "^1.2.0", "monitor-sdk": "file:../monitor-sdk", }, ``` 1. 执行 npm i monitor-sdk -S 或者 npm link 在项目里面 `npm link monitor-sdk` 即可安装到项目中 2. main.js文件初始化 3. ```js import { init } from 'monitor-sdk' init({ appId: 'huizhi00011', // appId userId: 'hz00011', // userId reportUrl: 'http://localhost:3009/report/actions', // 请求的后端地址 delay: 0, // 延时上报的时间 autoTracker: false, // 自动埋点 hashPage: false, // 是否为hash路由,为fasle的话则默认为history路由 errorReport: true, // 是否开启错误监控 }); const app = createApp(App) ``` ​ #### 4. 日志服务 > 日志服务SLS是云原生观测与分析平台,为Log、Metric、Trace等数据提供大规模、低成本、实时的平台化服务。日志服务一站式提供数据采集、加工、查询与分析、可视化、告警、消费与投递等功能,全面提升您在研发、运维、运营、安全等场景的数字化能力。 - 开通日志服务 https://help.aliyun.com/zh/sls/?spm=a2c4g.11186623.0.0.4d65774e7sl00r - 创建日志项目和日志库 https://sls.console.aliyun.com/lognext/project/yuguo-huizhi/logsearch/huizhi-logstore ![1696672473593](./media/1696672473593.png) - 上报接口 PutWebtracking https://help.aliyun.com/zh/sls/developer-reference/putwebtracking?spm=a2c4g.11186623.0.0.34e26ab0moBpjQ ![1696672580810](./media/1696672580810.png) - 代码 ```js /** * 上报 * @param {*} type * @param {*} params */ export function lazyReport(type, params) { const appId = window["_monitor_app_id_"]; const userId = window["_monitor_user_id_"]; const delay = window["_monitor_delay_"]; const bodyParams = { __topic__: "上报的数据", __source__: "来源" + type, __logs__: [ { message: params.data, time: new Date().getTime()+'', currentPage: window.location.href, ua: navigator.userAgent, // ua信息 }, ], }; report(bodyParams); // ----- ajax ------ const host = "yuguo-huizhi.cn-guangzhou.log.aliyuncs.com"; const project = "yuguo-huizhi"; const logStore = "huizhi-logstore"; const url1 = `http://${project}.${host}/logstores/${logStore}/track`; const xhr = new XMLHttpRequest(); let body = JSON.stringify(data); xhr.open("POST", url1, true); xhr.setRequestHeader("Content-Type", "application/json"); xhr.setRequestHeader("x-log-apiversion", "0.6.0"); xhr.setRequestHeader("x-log-bodyrawsize", body.length); xhr.onload = function () { console.log(xhr.response); }; xhr.onerror = function (error) { console.log(error); }; xhr.send(body); ```