跳转到内容

项目创建

刀刀

12/31/2024

0 字

0 分钟

搭建

  1. 创建项目

    sh
    vue create 项目名称
  2. 配置项

    • css预处理器、babel、router、vuex

    • n(哈希模式)

    • less

    • 配置的模块,单独放一个文件夹

    • 不保存预设

  3. 下载 axios

    shell
    yarn add axios
  4. 下载 Vant

    Vant 组件库配置

    具体的可以查看官方文档,地址:Vant2.x

    shell
    yarn add vant@latest-v2 -S
    shell
    yarn add babel-plugin-import -D
    js
    module.exports = {
      plugins: [
        ['import', {
          libraryName: 'vant',
          libraryDirectory: 'es',
          style: true
        }, 'vant']
      ]
    };

    重启项目即可查看效果。

配置

本项目是由 vue2 脚手架搭建,引用了 vant@2 组件库,主要做了以下几个方面:

  • VuexVuex 通过模块化管理,在大总管 index.js 文件中统一引用导出。
  • axiosaxios 二次封装,封装请求基准路径、超时时间、请求拦截器(设置请求头等)、响应拦截器(不同状态码的相应提示)等。
  • components :全局子组件一个个导入到 main.js 文件不仅导致代码量冗余,且不利于后续维护,因此统一导入到 index.js 大总管文件统一导出引用
  • mixins :部分功能函数多个页面需要使用,通过 mixins 可以让代码复用,减少冗余,方便维护。
  • filter :过滤器,统一封装,main.js 文件引入使用。

vuex

getters

为了方便使用,对部分高频使用的变量通过 getters 引用:

js
export default  {
  token: (state) => state.login.token,
  userinfo: (state) => state.login.userinfo,
}

封装挂载

vuex 模块化的好处是各个模块分工明确,统一管理,利于维护。先在 index.js 文件中导入相应的配置并导出,在 main.js 文件中导入使用。

js
import Vue from 'vue'
import Vuex from 'vuex'
import login from './modules/login'

Vue.use(Vuex)

export default new Vuex.Store({
  getters,
  modules: {
    login,
  }
})
js
import store from './store'

const vue = new Vue({
  store,
  render: h => h(App)
}).$mount('#app')

使用

可通过辅助函数 mapStatemapActionmapGetters 等获取数据使用。

axios

封装挂载

在相应的 js 文件中引入 axios 并导出,并设置基准路径、超时时间以及请求头。其中基准路径和超时时间通过外部动态导入获取:

js
import axios from 'axios'
import { BASE_URL, TIME_OUT } from '@/config'
import store from '@/store'
import router from '@/router';
import { Toast } from 'vant';

// 创建实例后修改默认值
// 创建axios实例
let instance = axios.create({
  baseURL: BASE_URL,
  timeout: TIME_OUT,
  headers: {
    post: {
      'Content-Type': 'application/x-www-form-urlencoded'
    }
  }
});

export default instance;

接下来设置请求拦截器和响应拦截器。

请求拦截器

请求拦截器主要封装以下几点:

  • 设置请求的请求头,如部分 post 请求的请求头为 application/json ,上传文件的请求头要为 multipart/form-data 等。
  • 携带 token ,获取 token 并根据后端接口文档传给相应字段。
  • (可选)显示 loading 效果,为了用户体验感更好,每次调接口时都显示 loading 效果,表示正在请求中。
js
/**
 * 请求拦截器
 * 每次请求前,如果存在token则在请求头中携带token
 */
instance.interceptors.request.use(
  config => {
    Toast.loading({
      duration: 9000,
      message: '加载中...',
      forbidClick: true,
    });
    // 判断当前post请求是否是application/json
    if (config.type && config.type === 'json') {
      config.headers['Content-Type'] = 'application/json';
    }
    if(config.url === '/zhijiao-edu/upload/save') {
      config.headers['Content-Type'] = 'multipart/form-data';
    }

    // 每次发送请求之前判断是否存在token,如果存在,则统一在http请求的header都加上token,不用每次请求都手动添加了
    // 即使本地存在token,也有可能token是过期的,所以在响应拦截器中要对返回状态进行判断
    const token = store.getters.token;
    token && (config.headers['X-Access-Token'] = token);
    return config
  },
  error => {
    return Promise.error(error);
  }
)

响应拦截器

响应拦截器主要封装以下几点:

  • 根据状态码给予页面不同的反馈。
    • 200:请求成功,获取数据渲染
    • 401 / 403:用户未登录或用户没有该权限,跳转到登录页面提示用户登录
    • 404:接口不存在,给予提示
    • 500:服务器错误,给予提示
  • 关闭请求拦截器显示的 loading 效果。
js
// 响应拦截器
instance.interceptors.response.use(
  // 请求成功
  res => {
    Toast.clear()
    // 状态码为200
    if (res.status === 200 && res.data.code === 200) {
      return Promise.resolve(res.data)
    } else {
      errorHandle(res.data.code, res.data.msg || res.data.message);
      return Promise.reject(res)
    }
  },
  // 请求失败
  error => {
    Toast.clear()
    const { response } = error;
    if (response) {
      // 请求已发出,但是不在2xx的范围 
      errorHandle(response.status, response.data.message);
      return Promise.reject(response);
    } else {
      // 处理断网的情况
      // eg:请求超时或断网时,更新state的network状态
      // network状态在app.vue中控制着一个全局的断网提示组件的显示隐藏
      // 关于断网组件中的刷新重新获取数据,会在断网组件中说明
      if (!window.navigator.onLine) {
      } else {
        return Promise.reject(error);
      }
    }
  }
);

提示、跳转登录、状态码判断单独抽出封装为函数,更利于后期的维护!

js
/** 
 * 提示函数 
 * 禁止点击蒙层、显示一秒后关闭
 */
const tip = msg => {
  Toast({
    message: msg,
    duration: 1000,
    forbidClick: true
  });
}

/** 
* 跳转登录页
* 携带当前页面路由,以期在登录页面完成登录后返回当前页面
*/
const toLogin = () => {
  router.replace({
    path: '/login'
  });
}

/** 
 * 请求失败后的错误统一处理 
 * @param {Number} status 请求失败的状态码
 */
const errorHandle = (status, other) => {
  // 状态码判断
  switch (status) {
    // 401: 未登录状态,跳转登录页
    case 401:
      toLogin();
      break;
    // 403 token过期
    // 清除token并跳转登录页
    case 403:
      tip('登录过期,请重新登录');
      localStorage.removeItem('token');
      store.commit('loginSuccess', null);
      setTimeout(() => {
        toLogin();
      }, 1000);
      break;
    // 404请求不存在
    case 404:
      tip('请求的资源不存在');
      break;
    // 500请求失败
    case 500:
      tip(other);
      break;
    default:
      break;
  }
}

使用

接口中引入封装好的 axios ,在 index.js 大总管文件中统一导入到一个对象内,导出到 main.js 入口文件。通过 prototype 挂载到 Vue 的原型上,后续可通过 this 直接获取到。

  • 接口模块 js 文件

    js
    import axios from '@/utils/request'
    
    const data = {
      // 轮播图
      bannerApi() {
        return axios.get('/client/banner/list')
      },
    }
    
    export default data
  • JSON 格式接口发送格式

    js
    const enterprise = {
      xxxApi(data) {
        return axios.post('/xxxx/xxxx/xxxx', data, {type:'json'})
      },
    }
  • 大总管 index.js 文件

    js
    import data from './modules/data'
    
    export default {
      data,
    }
  • 入口文件

    js
    import api from './api'
    Vue.prototype.$api = api

    后续可通过 this.$api.data 获取到 data.js 内的请求。

components

子组件复用是项目代码优化中常见的形式,如何高效、低代码实现全局子组件引入也是必要的,在 index.js 大总管文件通过 install(Vue) {} 统一注册子组件并导出。入口文件引入注册即可。

  • index.js 通过 Vue.component() 注册
  • main.js 通过 Vue.use() 注册
js
// 在此统一注册components的组件
import UploadImg from './modules/UploadImg.vue'
export default {
  install(Vue) {
    Vue.component('UploadImg', UploadImg)
  }
}
js
// 注册自己的插件
import components from '@/components'
Vue.use(components)

拓展—— install

Vue有一个概念:【插件】。插件通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制——一般有下面几种:。官方文档请移步:插件

插件对外暴露一个 install 的方法,该方法第一个参数是 Vue 构造器,因此可通过该参数开发新的插件及全局注册组件等。

mixins

当同一种功能多个页面都需要使用时,每个页面都写一个相同的函数显然不是一个优解。把这些封装到 mixins 中,全局或局部引入该 mixins 文件,通过 mixins 实现同一种函数多页面复用,减少代码冗余和杂糅。

封装挂载

  • mixins/index.js 文件中导出方法事件、变量等(该文件用来全局注册,因此只存放使用频率高的事件方法)
  • 入口文件全局导入
js
export default {
  data() {
    return {
    }
  },
  created() {
  },
  methods: {
    // 本地存储
    setItem(name, value) {
      if (typeof value === 'object') {
        localStorage.setItem(name, JSON.stringify(value))
      } else {
        localStorage.setItem(name, value)
      }
    },
    // 获取本地存储
    getItem(name) {
      return localStorage.getItem(name)
    },
    // 删除本地存储
    removeItem(name) {
      localStorage.removeItem(name)
    },
    // 筛选数据字典获取数据
    filterDict(arr, v) {
      let obj = arr.find(item => item.value === v)
      return obj ? obj.text : v
    },
    // 过滤时间
    filterTime(times, type) {
      let date = new Date(times)
      const year = date.getFullYear()
      const month = date.getMonth() + 1
      const day = date.getDate()
      const hour = date.getHours()
      const minute = date.getMinutes()
      const second = date.getSeconds()

      switch (type) {
        case 'YYYY':
          return `${year}`
        case 'YYYY-MM':
          return `${year}-${month >= 10 ? month : '0' + month}`
        case 'YYYY-MM-DD':
          return `${year}-${month >= 10 ? month : '0' + month}-${day >= 10 ? day : '0' + day}`
        case 'YYYY-MM-DD hh:mm':
          return `${year}-${month >= 10 ? month : '0' + month}-${day >= 10 ? day : '0' + day} ${hour >= 10 ? hour : '0' + hour}:${minute >= 10 ? minute : '0' + minute}`
        case 'YYYY-MM-DD hh:mm:ss':
          return `${year}-${month >= 10 ? month : '0' + month}-${day >= 10 ? day : '0' + day} ${hour >= 10 ? hour : '0' + hour}:${minute >= 10 ? minute : '0' + minute}:${second >= 10 ? second : '0' + second}`
        default:
          return `${year}-${month >= 10 ? month : '0' + month}-${day >= 10 ? day : '0' + day}`
      }
    },
  }
}
js
// 全局mixin
import mixins from '@/mixins'
Vue.mixin(mixins)

使用

本项目后台是由低代码平台 jeecg-boot 开发,有用于传参的数据字典,因此有许多页面都会用到字典翻译,在 vuexgetters.js 文件中获取到登录时保存的字典数组 dict ,把字典和所要翻译的变量传递过来,返回要的翻译字段即可。

vue
<script>
import { mapGetters, mapActions } from "vuex";

export default {
  computed: {
    ...mapGetters([
      "userinfo",
      "dict",
    ]),
  },
};
</script>

<template>
	<div class="type" v-if="isRole">{{ filterDict(dict.user_role, userinfo.role) }}</div>
</template>

filters

官方文档可见:过滤器

封装挂载

创建 filters/index.js 文件,用于存放过滤器。main.js 文件全局注册过滤器。

js
export function a(e) {
 return e + 1
}
js
// 注册过滤器
import * as filters from '@/filters'
Object.keys(filters).forEach(key => {
 	Vue.filter(key, filters[key])
})

使用

vue
<template>
  <div class="home">
    <div>{{ 'daodao' | a }}</div> // daodao1
    <div>{{ b() }}</div>
 </div>
</template>