Vue个人项目记录

234 阅读5分钟

npm对应yarn命令

1658992332839.png

解决跨域问题

vue-cil代理服务器

  1. 修改配置文件.env.development
//.env.development 中的VUE_APP_BASE_API改成 /api
​
# just a flag
ENV = 'development'# base api
VUE_APP_BASE_API = '/api'
  1. vue-config配置module.exports
devServer: {
    proxy: {
      // 这里的api 表示如果我们的请求地址有/api的时候,就出触发代理机制
      '/api': {
        target: 'http://ihrm.itheima.net/', // 我们要代理请求的地址
        changeOrigin: true //是否跨域
      }
    }
  },

登录模块设计流程

  1. 设计表单、登录按钮等布局
  2. 表单绑定data数据对象
  3. 表单校验
  4. 处理登录按钮事件,点击登录先进行兜底校验(可封装在utils中的validate.js中),校验通过再执行下面的:
  5. 根据接口文档写好请求:写封装在api文件夹的js中
  6. 随后发送请求登录(判断是否有数据需要存入vuex,有则使用vuex请求)
  7. 将token存入并持久化,持久化可使用cookies
  8. 跳转至登录后页面

退出登录流程

  1. store设置好actions:

    • 清除用户信息,
    • 清除token,
    • 跳转登录页,
    • 提示
  2. 设置退出登录按钮事件,执行上面的actions

登录未遂设置

退出登录时

  1. 退出登录时跳转并携带当前路由

    // 跳转并携带退出前路由
    // ?后面的redirect名字为自己定义的,=后面的参数会存到route.query中
    this.$router.replace(`/login?redirect=${encodeURIComponent(this.$route.fullPath)}`)
    
  2. 在登录跳转判断route.query.replace有路径则跳转至这里,否则跳转至首页

    // 跳转 :判断有携带地址则跳转携带地址,否则跳转首页
    this.$router.push(this.$route.query.redirect || '/')
    

token过期退出登录时

  1. token过期退出登录时携带当前路由

    // 跳转并携带当前页面路径
    router.replace(`/login?redirect=${encodeURIComponent(router.currentRoute.fullPath)}`)
    
  2. 在登录跳转判断route.query.replace有路径则跳转至这里,否则跳转至首页

    // 跳转 :判断有携带地址则跳转携带地址,否则跳转首页
    this.$router.push(this.$route.query.redirect || '/')
    

让axios少一层data

// 响应拦截器
instance.interceptors.response.use(function(response) {
  // 对响应数据做点什么
  const res = response.data
  return res
}, function(error) {
  // 对响应错误做点什么
  return Promise.reject(error)
})

让路由跳转有进度条

使用nprogress

安装

npm install --save nprogress

直接引入js、css或者通过cdn引入。

<script src='nprogress.js'></script>
<link rel='stylesheet' href='nprogress.css'/>

使用

直接调用 start()或者done()来控制进度条。

NProgress.start();
NProgress.done();

可以通过调用 .set(n)来设置进度,n是0-1的数字。

NProgress.set(0.0);     // Sorta same as .start()
NProgress.set(0.4);
NProgress.set(1.0);     // Sorta same as .done()

页面token判断

跳转路由时判断token是否存在

  1. 将请求获得得token存入state中,并存入Cookies中,将state中的值设置为cookies存入的函数,否则刷新页面则会失效
  2. 判断如果有token:访问的是login页面则直接跳转到主页,不是登录也则放行
  3. 如果没有token:判断是白名单的直接放行,否则跳转至注册页
// 路由白名单
const whiteList = ['/login']
// 路由前置守卫
router.beforeEach(async(to, from, next) => {
  // 进度条开始
  NProgress.start()  
  // 获取vuex中getters的token
  const token = store.getters.token
  // 判断是否有token
  if (token) {
    // 有token访问的是login
    if (to.path === '/login') {
      // 跳转至主页
      next('/')
      NProgress.done()
    } else {
      // 放行
      next()
      NProgress.done()
    }
  } else {
    // 如果没有token
    // 判断访问的如果是白名单的地址
    if (whiteList.some(item =>  item === to.path )) {
      // 放行
      next()
      NProgress.done()
    } else {
      // 跳转至登录页
      next('/login')
      NProgress.done()
    }
  }
})
​
// 路由后置守卫
router.afterEach(() => {
  // 进度条结束
  NProgress.done()
})

跳转路由时判断token是否存在

  1. 在响应拦截器中错误中判断错误code是否为token过期

  2. 如果过期:

    • 删除token
    • 删除用户信息
    • 跳转并携带当前页面路径
    // 响应拦截器
    service.interceptors.response.use(
      (response) => {
        return response.data
      },
      (error) => {
        // 如果过期:删除token 删除用户信息 跳转并携带当前页面路径
        if (error.response && error.response.data && error.response.data.code === 10002) {
          // 提醒
          Message.error('登录过期,请重新登录')
          // 删除token
          store.commit('user/REMOVE_TOKEN')
          // 删除用户信息
          store.commit('user/RESET_STATE')
          // 跳转并携带当前页面路径
          router.replace(`/login?redirect=${encodeURIComponent(router.currentRoute.fullPath)}`)
        } else {
          Message.error({ message: error || '出现错误,请稍后再试' })
        }
        console.dir(error)
        return Promise.reject(error)
      }
    )
    

路由前置统一设置请求头token

  1. 将vuex中state的token放在getters中

  2. 请求拦截器设置:

    // 请求拦截器
    service.interceptors.request.use(
      (config) => {
        const token = store.getters.token
        if (token) {
          config.headers.Authorization = `Bearer ${token}`
        }
        return config
      },
      (error) => {
        return Promise.reject(error)
      }
    )
    

导航栏logo控制

  1. 是否显示设置在src下的settings.js中
  2. 存入vuex中的state中
  3. 在logo组件的v-if等于vuex中的state

获取用户信息

  1. 封装获取用户请求
  2. 在前置路由守卫调用请求,存入vuex中

全局组件统一封装

如果希望在所有页面都要用, 那么就要全局注册组件,如果有很多个组件, 都要全局注册, 那么又会在main.js 中写很多内容

  1. Vue.use 可以接收一个对象, Vue.use(obj)
  2. 对象中需要提供一个 install 函数
  3. install 函数可以拿到参数 Vue, 且将来会在 Vue.use 时, 自动调用该 install 函数

index.js统一引入组件

import PageTools from './PageTools'export default {
  install(Vue) {
    Vue.component('PageTools', PageTools)
  }
}

入口文件注册插件(main.js)

import Components from './components'
Vue.use(Components)

封装过滤器

  1. src目录下创建filters/index.js文件

    // 导入dayjs
    import dayjs from 'dayjs'
    // 格式化组件过滤器
    export function formatData(value, str = 'YYYY年MM月DD日') {
      return dayjs(value).format(str)
    }
    
  2. 入口文件main.js全局注册过滤器

    // 引入过滤器
    import * as filters from '@/filters'
    Object.keys(filters).forEach(key => {
      // 注册过滤器
      Vue.filter(key, filters[key])
    })
    

Excel导入

  1. 封装excel导入组件(uploadExcel)

    需要的依赖:xlsx

    <template>
      <div>
        <input
          ref="excel-upload-input"
          class="excel-upload-input"
          type="file"
          accept=".xlsx, .xls"
          @change="handleClick"
        >
        <div class="box">
          <div class="left">
            <el-button
              :loading="loading"
              style="margin-left: 16px"
              size="mini"
              type="primary"
              @click="handleUpload"
            >
              点击上传
            </el-button>
            <div class="text">(推荐下载模板文件,填写后上传)</div>
            <div class="text">点击查看文件上传要求</div>
          </div>
          <div
            class="rigth"
            @drop="handleDrop"
            @dragover="handleDragover"
            @dragenter="handleDragover"
            @click="handleUpload"
          >
            <div class="el-icon-upload" />
            <div>将文件拖入上传</div>
          </div>
        </div>
      </div>
    </template><script>
    import * as XLSX from 'xlsx'export default {
      props: {
        beforeUpload: Function, // eslint-disable-line
        onSuccess: Function // eslint-disable-line
      },
      data() {
        return {
          loading: false,
          excelData: {
            header: null,
            results: null
          }
        }
      },
      methods: {
        generateData({ header, results }) {
          this.excelData.header = header
          this.excelData.results = results
          this.onSuccess && this.onSuccess(this.excelData)
        },
        handleDrop(e) {
          e.stopPropagation()
          e.preventDefault()
          if (this.loading) return
          const files = e.dataTransfer.files
          if (files.length !== 1) {
            this.$message.error('Only support uploading one file!')
            return
          }
          const rawFile = files[0] // only use files[0]
    ​
          if (!this.isExcel(rawFile)) {
            this.$message.error(
              'Only supports upload .xlsx, .xls, .csv suffix files'
            )
            return false
          }
          this.upload(rawFile)
          e.stopPropagation()
          e.preventDefault()
        },
        handleDragover(e) {
          e.stopPropagation()
          e.preventDefault()
          e.dataTransfer.dropEffect = 'copy'
        },
        handleUpload() {
          this.$refs['excel-upload-input'].click()
        },
        handleClick(e) {
          const files = e.target.files
          const rawFile = files[0] // only use files[0]
          if (!rawFile) return
          this.upload(rawFile)
        },
        upload(rawFile) {
          this.$refs['excel-upload-input'].value = null // fix can't select the same excel
    ​
          if (!this.beforeUpload) {
            this.readerData(rawFile)
            return
          }
          const before = this.beforeUpload(rawFile)
          if (before) {
            this.readerData(rawFile)
          }
        },
        readerData(rawFile) {
          this.loading = true
          return new Promise((resolve, reject) => {
            const reader = new FileReader()
            reader.onload = (e) => {
              const data = e.target.result
              const workbook = XLSX.read(data, { type: 'array' })
              const firstSheetName = workbook.SheetNames[0]
              const worksheet = workbook.Sheets[firstSheetName]
              const header = this.getHeaderRow(worksheet)
              const results = XLSX.utils.sheet_to_json(worksheet)
              this.generateData({ header, results })
              this.loading = false
              resolve()
            }
            reader.readAsArrayBuffer(rawFile)
          })
        },
        getHeaderRow(sheet) {
          const headers = []
          const range = XLSX.utils.decode_range(sheet['!ref'])
          let C
          const R = range.s.r
          /* start in the first row */
          for (C = range.s.c; C <= range.e.c; ++C) {
            /* walk every column in the range */
            const cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })]
            /* find the cell in the first row */
            let hdr = 'UNKNOWN ' + C // <-- replace with your desired default
            if (cell && cell.t) hdr = XLSX.utils.format_cell(cell)
            headers.push(hdr)
          }
          return headers
        },
        isExcel(file) {
          return /.(xlsx|xls|csv)$/.test(file.name)
        }
      }
    }
    </script><style scoped>
    .box {
      text-align: center;
    }
    .excel-upload-input {
      display: none;
      z-index: -9999;
    }
    .rigth {
      border: 2px dashed #bbb;
      width: 400px;
      height: 180px;
      font-size: 24px;
      border-radius: 5px;
      text-align: center;
      color: #bbb;
      display: inline-block;
      border-left: 0;
    }
    ​
    .left {
      vertical-align: top;
      padding: 50px;
      border: 2px dashed #bbb;
      width: 400px;
      height: 180px;
      font-size: 24px;
      border-radius: 5px;
      text-align: center;
      color: #bbb;
      display: inline-block;
    }
    .el-icon-upload {
      font-size: 100px;
    }
    .left .text {
      font-size: 12px;
      color: #000;
      margin-top: 5px;
    }
    </style>
  2. 上述组件接受两个函数

    beforeUpload 导入前的回调 有一个实参: 文件对象

    • beforeUpload需要返回true才可继续导入成功的回调

    onSuccess 导入成功后的回调 有一个实参:一个对象,对象有两个属性results, header**

    • results 代表 表体数据
    • header 代表 表头数据
    <template>
      <div class="app-container">
        <el-card>
          <div class="title">员工导入</div>
          <el-alert
            type="warning"
            :closable="false"
            show-icon
            title="每次导入仅可添加1000名员工,姓名、手机、入职时间、聘用形式为必填项"
          />
          <div class="import">
            <!-- 导入组件 -->
            <upload-excel-component
              :on-success="handleSuccess"
              :before-upload="beforeUpload"
            />
          </div>
        </el-card>
      </div>
    </template><script>
    import UploadExcelComponent from '@/components/UploadExcel/index.vue'export default {
      name: 'UploadExcel',
      components: { UploadExcelComponent },
      data() {
        return {}
      },
      methods: {
        // 导入成功回调
        handleSuccess({ results, header }) {
          console.log(results)
          console.log(header)
        },
        // 导入前的回调,可用于判断文件大小、类型等
        beforeUpload(file) {
          console.log(file)
        }
      }
    }
    </script><style lang="scss" scoped>
    .title {
      text-align: center;
      font-size: 20px;
      font-weight: 900;
      margin-bottom: 15px;
    }
    .import {
      margin-top: 100px;
    }
    .app-container {
      background-color: #f0f2f4;
    }
    </style>
    
  3. 最后header 获取的是表头的数组

    ['入职日期', '手机号', '姓名', '转正日期', '工号']
    
  4. results 获取的表体 每一行一个对象的数组

    [{…}, {…}]
    
  5. Excel日期格式化

     formatDate(numb, format) {
          const time = new Date((numb - 25567) * 24 * 3600000 - 5 * 60 * 1000 - 43 * 1000 - 24 * 3600000 - 8 * 3600000)
          const year = time.getFullYear() + ''
          const month = time.getMonth() + 1 + ''
          const date = time.getDate() + ''
          if (format && format.length === 1) {
            return year + format + (month < 10 ? '0' + month : month) + format + (date < 10 ? '0' + date : date)
          }
          return year + (month < 10 ? '0' + month : month) + (date < 10 ? '0' + date : date)
        }
    

\