Skip to content
本文总阅读量

1、crypto-js 加密解密

JS
/* eslint-disable */
import CryptoJS from 'crypto-js'
const key = CryptoJS.enc.Utf8.parse('0102030405060708') //十六位十六进制数作为秘钥
const iv = CryptoJS.enc.Utf8.parse('0102030405060708') //十六位十六进制数作为秘钥偏移量
/**
 * aes 解密方法
 */
function AesDecrypt(word) {
  let encryptedHexStr = CryptoJS.enc.Hex.parse(word)
  let srcs = CryptoJS.enc.Base64.stringify(encryptedHexStr)
  let decrypt = CryptoJS.AES.decrypt(srcs, key, {
    iv: iv,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
  })
  let decryptedStr = decrypt.toString(CryptoJS.enc.Utf8)
  return decryptedStr.toString()
}
/**
 * aes 加密方法
 */
function AesEncrypt(word) {
  let srcs = CryptoJS.enc.Utf8.parse(word)
  let encrypted = CryptoJS.AES.encrypt(srcs, key, {
    iv: iv,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
  })
  return encrypted.ciphertext.toString().toUpperCase()
}

/**
 * base64 加密方法
 */
function Base64Encode(val) {
  let str = CryptoJS.enc.Utf8.parse(val)
  let base64 = CryptoJS.enc.Base64.stringify(str)
  return base64
}

/**
 * base64 解密方法
 */
function Base64Decode(val) {
  let words = CryptoJS.enc.Base64.parse(val)
  return words.toString(CryptoJS.enc.Utf8)
}

// 暴露接口
export default {
  AesEncrypt,
  AesDecrypt,
  Base64Encode,
  Base64Decode
}

2、localstorage 存储

JS
/**
 * 一个浏览器同时打开两个tab,localstorage和cookie是共享的,sessionstorage是不共享的
 * sessionstorage 仅在当前网页会话下有效,关闭页面或浏览器后会被清除
 * localStorage 理论上永久有效的,除非主动清除。
 */
import { validatenull } from '@/utils/validate'
const keyName = '-'
/**
 * 存储localStorage
 */
export const setStorage = ({ name, content, type } = {}) => {
  name = keyName + name
  const obj = {
    dataType: typeof content,
    content: content,
    type: type,
    datetime: new Date().getTime()
  }
  if (type) sessionStorage.setItem(name, JSON.stringify(obj))
  else localStorage.setItem(name, JSON.stringify(obj))
}
/**
 * 获取 localStorage
 */
export const getStorage = (name) => {
  name = keyName + name
  let obj = sessionStorage.getItem(name)
  if (validatenull(obj)) obj = localStorage.getItem(name)
  if (validatenull(obj)) return
  try {
    obj = JSON.parse(obj)
  } catch (e) {
    return obj
  }
  let content
  if (obj.dataType === 'string') {
    content = obj.content
  } else if (obj.dataType === 'number') {
    content = Number(obj.content)
  } else if (obj.dataType === 'boolean') {
    content = eval(obj.content)
  } else if (obj.dataType === 'object') {
    content = obj.content
  }
  return content
}
/**
 * 删除localStorage
 */
export const removeStorage = ({ name, type } = {}) => {
  name = keyName + name
  if (type) {
    sessionStorage.removeItem(name)
  } else {
    localStorage.removeItem(name)
  }
}
/**
 * 获取全部localStorage
 */
export const getAllStorage = ({ type } = {}) => {
  const list = []
  if (type) {
    for (let i = 0; i <= sessionStorage.length; i++) {
      list.push({
        name: sessionStorage.key(i),
        content: getStorage({
          name: sessionStorage.key(i),
          type: 'session'
        })
      })
    }
  } else {
    for (let i = 0; i <= localStorage.length; i++) {
      list.push({
        name: localStorage.key(i),
        content: getStorage({
          name: localStorage.key(i)
        })
      })
    }
  }
  return list
}

/**
 * 清空全部localStorage
 */
export const clearStorage = ({ type } = {}) => {
  if (type) {
    sessionStorage.clear()
  } else {
    localStorage.clear()
  }
}

3、router 路由

js
import router from './index'
import Store from '@/store/index'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'

router.beforeEach((to, from, next) => {
  NProgress.start()
  // 进入404
  if (from.name === '404' || to.path === '/404') next()

  // 进入不存在路由
  if (to.path !== '/404' && to.matched.length === 0) {
    next({
      path: '/404'
    })
  }
  // 权限判断
  if (to.matched.some((res) => res.meta.requireAuth)) {
    // 获取用户信息
    let userInfo = Store.state.stoUser
    // 找不到 则local重载用户信息
    if (!userInfo) {
      next({
        path: '/login'
      })
    }

    // 判断进入页面是否需要权限
    if (!to.meta.auth) {
      next()
      return
    }
    // 需要权限并且有权限进入
    if (userInfo.vue_role?.web_auth_arr.includes(to.meta.auth)) {
      next()
    } else {
      // 需要权限但无权限进入
      next({ name: '403' })
    }
  } else {
    next()
  }
})

router.afterEach(() => {
  NProgress.done()
})

4、api 请求

JS
import axios from 'axios'
import store from '@/store'
import AES from '@/utils/aesUtil'
import VueConfig from '@/config/config'
import sign from '@/utils/sign'
// 框架
import { message } from 'ant-design-vue'

let URL_PREFIX = `${process.env.VUE_APP_BASE_URL}/master_web`

// 创建实例
let instance = axios.create({
  baseURL: URL_PREFIX,
  timeout: 1000,
  headers: { 'X-Custom-Header': 'foobar' }
})

let isRefreshing = false // 标记是否正在刷新 token
let requests = [] // 存储待重发请求的数组

// Post请求
let POST = function (url, data, options) {
  let code, result
  return new Promise(function (resolve, reject) {
    instance
      .post(url, data, options)
      .then((res) => {
        code = res.data.code
        result = res.data.result
        if (code !== 200) throw codeStatus(code, result)
        resolve(result)
      })
      .catch((err) => {
        message.error(err)
        reject(err)
      })
  })
}

// code 详情
const codeStatus = function (code, result) {
  let errInfo = '请求接口出错'
  // 默认常见类型
  switch (code) {
    case 403:
      errInfo = '无权限请求'
      break
    case 404:
      errInfo = '找不到页面'
      break
    case 412:
      errInfo = '请求头出错'
      break
    case 500:
      errInfo = '服务器请求出错'
      break
    default:
      break
  }

  // 重定向 错误信息
  if (typeof result === 'string') {
    errInfo = result
    if (result.indexOf('登录') !== -1) routerRedirect('/login', 2000)
  }

  // 返回错误信息
  return errInfo
}

// 刷新令牌
const refreshToken = async function () {
  return POST('/refresh_token', {
    account: store.state.stoUser?.account
  })
}

// 路由重定向 跳转登录页
const routerRedirect = function (rUrl, time) {
  // 跳转页面
  setTimeout(() => {
    router.replace({
      path: rUrl
    })
  }, time)
}

// 添加请求拦截器
instance.interceptors.request.use(
  (config) => {
    // Token
    config.headers.token = store.state.stoUser?.token || null
    config.headers.refToken = store.state.stoUser?.refToken || null

    // Sign
    if (VueConfig.IsOpenSign) {
      const { vueSignature, vueTimestamp, vueNonce } = sign.signatureInterface(config.data)
      config.headers['vue-signature'] = vueSignature
      config.headers['vue-timestamp'] = vueTimestamp
      config.headers['vue-nonce'] = vueNonce
    }

    // AES
    if (VueConfig.IsOpenAesParams) {
      data = { reqAes: AES.AesEncrypt(JSON.stringify(data)) } || { reqAes: undefined }
    }

    return config
  },
  (error) => {
    // 对响应错误做点什么
    return Promise.reject(error)
  }
)

// 添加响应拦截器
instance.interceptors.response.use(
  (response) => {
    if (VueConfig.IsOpenAesDec) {
      if (typeof response.data.result !== 'string' || !JSON.parse(AES.AesDecrypt(response.data.result))) {
        throw '尝试解密失败/数据格式不对'
      }
      response.data.result = JSON.parse(AES.AesDecrypt(response.data.result))
    }

    // 对响应数据做点什么
    if (response.data?.code === 4002 && !response.config.url.includes('/refresh_token')) {
      const { config } = response
      if (!isRefreshing) {
        isRefreshing = true
        return refreshToken()
          .then((res) => {
            // 获取存储用户信息
            let userInfo = store.state.stoUser
            userInfo.token = res.token

            // 更新用户token存储信息
            store.dispatch('saveUser', userInfo)
            config.headers.token = res.token

            // token 刷新后将数组的方法重新执行
            requests.forEach((cb) => cb(token))
            requests = [] // 重新请求完清空

            // 再次执行请求
            return instance(config)
          })
          .catch((err) => {
            console.log('抱歉,您的登录状态已失效,请重新登录!')
            return Promise.reject(err)
          })
          .finally(() => {
            isRefreshing = false
          })
      } else {
        // 返回未执行 resolve 的 Promise
        return new Promise((resolve) => {
          // 用函数形式将 resolve 存入,等待刷新后再执行
          requests.push((token) => {
            config.headers.token = token
            resolve(instance(config))
          })
        })
      }
    }

    return response
  },
  (error) => {
    return Promise.reject(error)
  }
)

export { POST }

5、常用 babel 配置(包含 ES2015+(ES6+)的语法等)

babel-preset-env 是一个智能预设,允许你使用最新的 JavaScript,而无需微观管理目标环境需要哪些语法转换(以及可选的浏览器 polyfill)。这既让你的生活更轻松,也让 JavaScript 包更小!

npm install @babel/cli @babel/core @babel/preset-env --save-dev

babel.config.js 配置文件

js
module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        modules: false, // 默认auto,将此设置为 false 将保留 ES 模块。仅当你打算将原生 ES 模块发送到浏览器时才使用此选项
        targets: {
          // 份额大于0.25%,最新的两个版本,ie>=8 , 排除废弃版本, (83.8%)
          browsers: ['>0.25%', 'last 2 versions', 'ie >= 8', 'not dead']
        },
        useBuiltIns: 'usage', // 按需引入必要的 polyfill 来兼容目标浏览器。
        corejs: 3 // babel只转换语法,而数组的includes方法是属于ES6新增的API,所以需要corejs转换
      }
    ]
  ],
  plugins: []
}

6、节流防抖

节流

js
// 函数节流
var canRun = true
document.getElementById('throttle').onscroll = function () {
  if (!canRun) {
    //判断是否已空闲,如果在执行中,则直接return
    return
  }

  canRun = false
  setTimeout(function () {
    console.log('函数节流')
    canRun = true
  }, 1000)
}
  • 限制规定时间内只触发的次数

防抖

js
// 函数防抖
document.getElementById('debounce').onscroll = function () {
  clearTimeout(timer) // 清除未执行的代码,重置回初始化状态

  timer = setTimeout(function () {
    console.log('函数防抖')
  }, 1000)
}
  • 限制规定时间内连续频繁触发

防抖、节流另一种实现方法

js
// 防抖
export const debounce = (() => {
    let timer = null
    return(callbackwait = 800=> {
        timer && clearTimeout(timer)
        timer = setTimeout(callback, wait)
  }
})()


// 节流
export const throttle = (() => {
    let last = 0
    return(callbackwait = 800=> {
        let now = + new Date()
        if (now - last > wait) {
          callback()
          last = now
        }
    }
})()

// 使用示例
  loadList() {
    debounce(() => {
        console.log('防抖,0.5s内只要最后一次触发')
    }, 500)
    throttle(() => {
        console.log('节流,0.5s内只能触发一次')
    }, 500)
  }