项目逐字稿

254 阅读14分钟

1、请说一下你在项目中负责哪几个模块。在这些模块中,对你最有挑战的是哪个。

  1. 在项目中我完成了登录页、首页、组织、员工、权限、角色模块。
  2. 我认为最有挑战的有,头像上传,权限管理,行内编辑。

2、在项目中,如果后端提供的接口并不能获得正确的数据,你会如何做。

  1. 首先先用postman测试一下接口是否可以跑通,如果能收到数据则是我的接口代码写的有误,如果不能跑通可能是接口需要的数据不对,如果请求需要的数据是对的还是不能返回,会看一下是什么错误,如果是4XX可能是网络问题,如果是5XX可能是服务端的问题。

3、在项目中你是如何解决svg图标展示的。请详细说明一下问题的解决思路。

  1. 首先是在main.js中引入一个icon的组件
  2. 并且在文件内将其进行全局注册组件
  3. 用require.context方法扫描目录中的文件(接收三个参数)
    • directory {String} -读取文件的路径
    • useSubdirectories {Boolean} -是否遍历文件的子目录
    • regExp {RegExp} -匹配文件的正则
  1. 将所有的文件通过数组map映射的方法将所有的svg图片引入到项目svg的symbol中
  2. 通过user标签里的xlink:href方法将svg图标渲染到页面上,
  3. 在项目的配置文件下设置svg-sprite-loader插件。
  4. 最后直接在项目中调用

4、你在这个项目中是如何做登录权限鉴定的。

  1. 在本项目中我用到的是token进行登录权限鉴定的
  2. 当用想要访问页面我会进行token判断如果用户携带token那么我则跳转到其访问页面,如果没有携带token则会判断用户访问的网页是否存在于白名单中,如果存在则跳转到访问页,上述条件都不满足则会提示用户并跳转到登录页。
if (token) {
    // 存在token是否跳转到登录页
    if (to.path === '/login') {
      // 有token不能跳转到登录页
      next('/')
      nprogress.done()
    } else {
      if (!store.getters.userId) {
        const { roles } = await store.dispatch('user/getUserInfo')
        // console.log(roles.menus) // 登录权限列表
        // console.log(asyncRoutes) // 动态路由列表
        // 对动态路由进行筛选
        const filterRotues = asyncRoutes.filter(item => {
          // 将包含的名字进行返回成一个新的数组
          return roles.menus.includes(item.name)
        })
        // 调用commit方法函数合并静态和动态路由
        store.commit('user/setRoutes', filterRotues)
        // 404是静态路由需要放在所有路由的最后面
        router.addRoutes([...filterRotues, { path: '*', redirect: '/404', hidden: true }])
        // 让路由跳转的时候可以继续去当前的页面
        next(to.path)
      } else {
        next()
      }
    }
  } else {
    // token不存在是否在白名单内
    if (whiteList.includes(to.path)) {
      next()
    } else {
      next('/login')
      nprogress.done()
    }
  }

5、你在项目中用到了RBAC的思想,请介绍下你在项目中是如何实现RBAC的,并且为什么要使用RBAC。

RBAC就是我们常说的角色访问控制,实现的方法主要是通过给员工分配一个角色,给这个角色设置相对应的权限。

好处是方便管理,例如公司拥有的员工数量比较多,但是每个人拥有的权限不相等,如果一个一个设置非常的麻烦,而用了RBAC就可以给员工一个角色,而这个角色的权限都是设置好的,这样能够更加高效的进行管理。

6、在项目中使用到的ui框架是按需加载还是完整引入,原因是什么。

在本项目中,我使用的UI框架是完成引入的,本项目用到的UI框架比较多,不像按需导入时要在引入,这样能节省一些开发效率。如果后期打包上线时文件过大影响开发运行速度我们可以使用cdn加速。

7、你是如何做到vuex数据持久化存储的。

在vuex通过commit调用子模块存储数据方法,将获得数据存储到cookie中来达到数据持久化存储。

import { getToken, setToken, removeToken } from '@/utils/auth'
import { login, getUserMsg } from '@/api/user'
import { constantRoutes, resetRouter } from '@/router'
// 子模块数据
const state = {
  // 从缓存中读取数据
  token: getToken(),
  userInfo: {},
  routes: constantRoutes // 静态路由的数组
}

// 调用子模块
const mutations = {
  getToken(state, payload) {
    state.token = payload
    // 将token永久存储
    setToken(payload)
  },
  removeToken(state) {
    // 将vuex和cookie存储删除
    state.token = ''
    removeToken()
  },
  setUserInfo(state, payload) {
    state.userInfo = payload
  },
  setRoutes(state, newRoutes) {
    state.routes = [...constantRoutes, ...newRoutes] // 合并静态和动态路由
  }
}

// 子模块异步获得token
const actions = {
  async login(context, data) {
    // console.log(data)
    // 调用接口
    const newData = await login(data)
    // console.log(res)
    // 将token存到state
    context.commit('getToken', newData)
  },
  // 子模块获得用户信息
  async getUserInfo(context) {
    const res = await getUserMsg()
    context.commit('setUserInfo', res)
    return res
  },
  logout(context) {
    context.commit('removeToken')
    context.commit('setUserInfo', {})
    resetRouter()
  }
}

export default {
  namespaced: true,
  state,
  mutations,
  actions
}

8、你在项目中的什么位置获取的用户具体资料,理由是什么。

在项目中我把获取用户信息的资料写在了一个Navbar组件中,因为因为这个组件在每个功能页面都需要显示,这样可以避免重复开发.

9、你在生产环境和开发环境都是如何解决跨域问题的。

  • 都是通过服务器代理的方法来解决跨域问题
  1. 开发环境
    1. 在vue的config文件配置下添加上proxy属性将,并将里面的target网址改成访问的网址
    2. 在env下的开发环境将config文件下配置的target网址赋值到VUE的基础API上
    3. 最好在请求的基地址上找到process.env的原型上将env.下的开发环境的基础API从而解决开发环境的跨域问题
  1. 生产环境
    1. 在nginx中进行打包上线,会出出现跨域问题,所以在nginx里面的conf文件夹里的nginx.conf文件添加上 location /prod-api {

proxy_pass 地址

}来解决生产环境下的跨域问题

10、你在项目中用到axios了吗,你是如何进行的封装。

  1. 将axios请求的基地址进行封装,这样能在访问的时候减少接口的复杂程度
  2. 封装了请求拦截器 请求拦截器就是让用户在发送请求事自动携带token,来保证用户是正常操作并且正常访问
  3. 封装了响应拦截器 响应拦截器就是服务端在响应数据可能会出现请求失败的情况则提示信息,还有可能是响应成功但是业务错误,比如登录的时候账号或密码输入错误就是请求响应成功,但是业务错误则需要提示用户信息。
import axios from 'axios'
import store from '@/store'
import { Message } from 'element-ui'
import router from '@/router'

// 设置基地址
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API,
  timeout: 10000
})

// 设置请求拦截器
service.interceptors.request.use((config) => {
  // 发起请求统一携带token
  if (store.getters.token) {
    config.headers.Authorization = `Bearer ${store.getters.token}`
  }
  return config
}, (error) => {
  return Promise.reject(error)
})

// 设置响应拦截器
service.interceptors.response.use((response) => {
  // 响应回来的数据 message, success, data
  // 二进制流
  if (response.data instanceof Blob) return response.data
  const { message, success, data } = response.data
  // 相应成功但是密码或账号错误(业务错误)
  if (success) {
    // console.log(data.roles.menus)
    return data
  } else {
    Message.error(message)
    return Promise.reject(new Error(message))
  }
}, async(error) => {
  // console.dir(error.response.status)
  // 401状态为token失效
  if (error.response.status) {
    Message.warning('登录过期,请重新登陆')
    await store.dispatch('user/logout')
    router.push('/login')
    return Promise.reject(error)
  }
  // 响应不通
  Message.error(error.message)
  return Promise.reject(error)
})

export default service

11、你在项目中的组件分成哪几个等级,是如何区分的。

  • 全局组件和局部组件
  • 全局组件是可能需要其他组件需要多次引入
  • 局部组件可能只是这一个组件需要引入

12、你的项目中token是存储在什么位置,你又是如何解决token失效的。

在本项目中我把token存在了cookie里

token过期一般情况下会让后端返回一个4XX开头的错误,然后会通过响应拦截器来判断这个错误的状态码是否存在,如过存在则说明是token过期,那么就会提示用户登录过期,请重新登陆的信息,并且将之前存储的过期的token清除,跳转到登录页,让用户重新登录,获取新的token

async(error) => {
  // console.dir(error.response.status)
  // 401状态为token失效
  if (error.response.status) {
    Message.warning('登录过期,请重新登陆')
    await store.dispatch('user/logout')
    router.push('/login')
    return Promise.reject(error)
  }

13、你的行内编辑功能是如何实现的。

  1. 点击编辑获取当前的id并将行内的数据变成输入框并进行双向数据绑定,拿到之前存储数据进行数据缓存然后在进行数据回写
  2. 如果点击取消那么缓存数据清空,退出编辑状态
  3. 如果点击修改,那么将数据缓存的信息发送给服务端并重新获取数据

14、请说明项目中左侧的菜单的渲染过程。

  1. 将左侧需要渲染的菜单都挂载在router上
  2. 通过计算属性进行将所有的路由引入到组件中
  3. 判断item.hidden是否存在不存在则渲染
  4. 通过循环遍历的方法将所有的菜单渲染在左侧

15、项目中的树形结构你是如何实现的。

在项目中因为用到树形结构,但后端回来的数据不是属性结构的数据,所以封装了一个递归函数,让数据通过id和pid的关联形成树形结构

/**
 * 递归函数将树结构排序
 * @param {Array} list
 * @param {Number} num
 * @returns Array
 */
export function transListToTreeData(list, num) {
  const arr = []
  list.forEach(item => {
    if (item.pid === num) {
      arr.push(item)
      const children = transListToTreeData(list, item.id)
      if (children.length) item.children = children
    }
  })
  return arr
}

16、实际开发场景中有用过变量吗?

环境变量,有开发环境和生产环境

17、怎么根据token的有无去控制路由的跳转?

  1. 一般情况下是通过路由前置守卫根据token是否存在进行路由的跳转的
  2. 通过路由的前置守卫中提供的回调函数判断token是否存在,如果存在则可以跳转到其他路由的页面,如果不存在则会判断跳转的路由页面是否在白名单中,如果在白名单则可以跳转,如果以上情况都不满足则会自动跳到登录页。

18、在工作中有对服务端返回的数据进行过二次处理吗?

在项目中会出现部门里还有子部门的情况,接口返回的数据是所有部门的数据,没有呈现出想要的树状结构,所以我将获得数据通过子部门的pid是父部门的id使用了递归函数将其添加进相对应的部门里面。从而实现部门和子部门之间的树状结构。

19、关于项目中exce的导出实现步骤请简要描述一下。

在Excel导出中后端需要的数据是二进制流,所以我在响应拦截器中进行处理,如果响应需要的是二进制流那么就将该数据直接返回,并且用file-saver将返回的数据转换成Excel表格的形式。

20、有封装过组件吗?请介绍以下你封装过的组件。

封装添加和编辑的组件,添加和编辑的结构相等,所以共用一个组件,对回去的数据进行判断,如果没有id则是添加情况下,添加情况下需要将内容填写完毕发送请求,并通知父组件重新获取数据。 如果有id那么就是编辑情况下,编辑情况下要进行数据回写,修改完成发送请求更新数据,并通知父组件重新获取数据。

21、有封装过v-model相关的双向绑定组件吗?

在项目的及联部门组件中用到了双向数据绑定,当进入组件获取部门信息,则会将父组件的信息通过props传递到子组件上,并在子组件身上绑定value,当子组件的信息想要进行修改则向父组件发送input事件,父组件接收input事件则完成双向数据绑定

22、有做过权限相关的事情吗?

  1. 权限一般情况下是给员工分配对应的角色,再给角色分配对应的权限。
  2. 根据角色分配的权限,将动态路由进行拆分,可以将当前员工可以访问的路由筛选出来,最后将动态路由和静态路由合并到一起,则是当前用户对应的权限能访问的路由页面
  3. 最后将合并的路由在左侧的列表中渲染出来,则能实现网页内部相关权限路由的访问

23、你的这个项目中vuex是如何划分模块的?

项目中主要是划分了user模块,这个模块处理了一些所有页面需要共同的数据或者方法,如在访问其他子页面的时候都需要获得token所以将获取token的方法都放在vuex中共用,减少代码的重复使用。或者在任意页面都能退出,所以把退出登录的方法放在vuex中。

24、请说一下项目中左树右表的实现思路以及二者是如何实现联动的。

通过element-ui里的el-tree里面监听一个node-key="id"和@current-change="selectNode"当切换节点的时候能将节点的id重新赋值,并重新发情获取数据的请求,渲染到页面上。

25、针对模糊搜索你是如何进行优化的。

模糊搜索一般都是通过输入信息查找内容的,所以在监听input事件的时候我用了防抖函数来监听input事件的变化发送请求频繁导致服务器崩溃。

// 模糊查询
changeInput() {
      clearTimeout(this.timer)
      this.timer = setTimeout(() => {
        this.queryParams.page = 1
        this.getDeptList()
      }, 500)
    },

26、新增、编辑员工公用一个组件,如何区分并实现对应的功能。

组件进行编辑时需要拿到当前修改内容的id,而新增则不需要id直接进入组件即可。

27、如何根据权限筛选出动态路由。

后端返回的数据中,有用户对应的权限,我在响应回来的数据中拿到账户拥有的权限,并将拿到的权限进行筛选,拿到的权限和我动态路由进行比较有相同的则返回一个新的路由数组,这样就能筛选出动态路由

if (!store.getters.userId) {
        const { roles } = await store.dispatch('user/getUserInfo')
        // console.log(roles.menus) // 登录权限列表
        // console.log(asyncRoutes) // 动态路由列表
        // 对动态路由进行筛选
        const filterRotues = asyncRoutes.filter(item => {
          // 将包含的名字进行返回成一个新的数组
          return roles.menus.includes(item.name)
        })
        // 调用commit方法函数合并静态和动态路由
        store.commit('user/setRoutes', filterRotues)
        // 404是静态路由需要放在所有路由的最后面
        router.addRoutes([...filterRotues, { path: '*', redirect: '/404', hidden: true }])
        // 让路由跳转的时候可以继续去当前的页面
        next(to.path)

28、项目中按钮标识权限如何分配。

按钮标识权限是基于RBAC思想分配的,就是通过权限标识来分别当前用户是否有该权限,如果有该权限则能正常操作,如果没有该权限则可以将该功能隐藏或者禁用,从而达到权限标识分配的效果。

29、你的项目中为什么要将路由拆分成动态路由和静态路由。

静态路由是所有人都能访问的页面,例如首页、404等,而动态路由则是只有拥有该权限的员工才能访问的路由页面。

30、项目中是否用过自定义指令,你是如何实现的。

通过在Vue注册一个自定义指令来控制权限的分配

// 注册自定义指令 控制功能权限
Vue.directive('permission', {
  // 指令作用的元素插入dom之后执行
  inserted(el, binding) {
    // el是当前指令作用的dom元素的对象
    // binding.value v-permission="表达式"中的表达式的值
    // console.log(el, binding)
    // 拿到points里面的数组赋值给points
    const points = store.state.user.userInfo?.roles?.points || []
    if (!points.includes(binding.value)) {
      // 不存在就删除
      el.remove()
      // 禁用
      // el.disabled = true
    }
  }
})

31、vue-router中路由模式你用的哪一种,有什么区别。

默认值hash模式,和history模式

我用的是history模式,history模式没有'#'地址变化页面时会刷新

hash模式是默认值,不需要设置,有'#'地址变化页面不会刷新

32、项目上线前你都做了哪些工作。

我将项目中没有使用的mock模块删除了,减少了文件的体积。

后来element-ui、cos-js-sdk-v5.js等大型文件排除,用cdn加速的方法外链式引入减少文件的大小提高运行效率。

33、你是如何配置代理的。请说明。

在nginx文件里的nginx.conf文件里添加配置文件

location /prod-api  {
  proxy_pass https://heimahr-t.itheima.net;
}