Vue项目搭建做的一些事

986 阅读5分钟

代码规范

请参考 html规范 css规范 js规范

项目配置

一.基础配置(指定应用上下文,端口号---vue.config.js)

const port = 7070;
module.exports = {
    publicPath: '/best-practice', // 部署应用包时的基本 URL 
    devServer: {
        port, 
    }
};

二.配置webpack: configureWebpack

  1. 设置一个组件存放路径的别名---vue.config.js
const path=require('path')
module.exports = {
  configureWebpack: {
    resolve: {
      alias: {
        comps: path.join(__dirname, 'src/components'),
      }
} }
}
  1. 设置一个webpack配置项用于⻚面title---vue.config.js
const path=require('path')
module.exports = {
  configureWebpack: {
    resolve: {
        alias: {
        comps: path.join(__dirname, 'src/components'),
      }
    } 
  }
}

// 在宿主⻚面使用lodash插值语法使用它,./public/index.html
// <title><%= webpackConfig.name %></title>
  1. 基于环境有条件地配置---vue.config.js
// 传递一个函数给configureWebpack
// 可以直接修改,或返回一个用于合并的配置对象 
configureWebpack: config => {
  config.resolve.alias.comps = path.join(__dirname, 'src/components')
  if (process.env.NODE_ENV === 'development') {
    config.name = 'vue项目最佳实践' }
    else {
    config.name = 'Vue Best Practice'
  }
}

三.配置webpack: chainWebpack

webpack-chain称为链式操作,可以更细粒度控制webpack内部配置。(示范svgicon引入)

  1. 下载图标,存入src/icons/svg中
  2. 安装依赖:svg-sprite-loader
npm i svg-sprite-loader -D
  1. 修改规则和新增规则---vue.config.js
// resolve定义一个绝对路径获取函数 
const path = require('path')
function resolve(dir) {
  return path.join(__dirname, dir)
}

chainWebpack(config) {
// 配置svg规则排除icons目录中svg文件处理
// 目标给svg规则增加一个排除选项exclude:['path/to/icon']
config.module.rule("svg")
      .exclude.add(resolve("src/icons"))
// 新增icons规则,设置svg-sprite-loader处理icons目录中的svg
config.module.rule('icons')
.test(/\.svg$/)
.include.add(resolve('./src/icons')).end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
  .options({symbolId: 'icon-[name]'})
}
  1. 使用---App.vue
<template>
  <svg>
    <use xlink:href="#icon-wx" />
  </svg>
</template>
<script>
  import '@/icons/svg/wx.svg'
</script>
  1. 自动导入
// 创建 icons/index.js
const req = require.context('./svg', false, /\.svg$/)
req.keys().map(req);
// 创建SvgIcon组件,components/SvgIcon.vue
<template>
  <svg :class="svgClass" v-on="$listeners">
    <use :xlink:href="iconName"/>
  </svg>
</template>
<script>
  export default {
    name: 'SvgIcon',
    props: {
      iconClass: {
        type: String,
        required: true
      },
      className: {
        type: String,
        default: ''
      }
    },
    computed: {
      iconName() {
        return `#icon-${this.iconClass}`
      },
      svgClass() {
        if (this.className) {
          return 'svg-icon ' + this.className
        } else {
          return 'svg-icon'
        }
      }
    }
  }
</script>

<style scoped>
  .svg-icon {
    width: 1em;
    height: 1em;
    vertical-align: -0.15em;
    fill: currentColor;
    overflow: hidden;
  }
</style>
// 在最外层导入--- main.js
import './icons'

四.环境变量和模式

如果想给多种环境做不同配置,可以利用vue-cli提供的模式。默认 有 development 、 production 、 test 三种模式,对应的,它们的配置文件形式 是 .env.development 。

  1. 定义一个开发时可用的配置项,创建.env.dev
# 只能用于服务端 
name='曾小白'
# 可用于客户端 
VUE_APP_DONG=candy-曾小白
  1. 修改mode选项覆盖模式名称---package.json
// --mode的默认值是development
"serve": "vue-cli-service serve --mode dev"  

权限控制

路由分为两种: constantRoutes和asyncRoutes,前者是默认路由可直接访问,后者中定义的路由需要先登录,获取⻆色并过滤后动态加入到Router中。

  1. 前端根据后端返回的角色信息进行过滤,可以看一下我的实现思路流程图
  2. 异步获取路由表,当用户登录后向后端请求可访问的路由表,从而动态生成可访问⻚面,将后端返回路由表中组件名称和本地的组件映射
 
// 前端组件名和组件映射表 const map = {
//xx: require('@/views/xx.vue').default // 同步的方式
  xx: () => import('@/views/xx.vue') // 异步的方式
  }
// 服务端返回的asyncRoutes 
const asyncRoutes = [
  { path: '/xx', component: 'xx',... }
]
// 遍历asyncRoutes,将component替换为map[component] 
function mapComponent(asyncRoutes) {
  asyncRoutes.forEach(route => {
    route.component = map[route.component];
    if(route.children) {
      route.children.map(child => mapComponent(child))
    }
  }) 
}
mapComponent(asyncRoutes)
  1. 按钮权限,⻚面中某些按钮、链接有时候需要更细粒度权限控制,这时候可以封装一个指令v-permission,自定义指令参考,该指令只能删除挂载指令的元素,对于那些额外生成的和指令无关的元素无能为力--- /src/directives/permission.js
import store from "@/store";

const permission = {
  inserted(el, binding) {
    // 获取指令的值:按钮要求的角色数组
    const { value:pRoles } = binding;
    // 获取用户角色
    const roles = store.getters && store.getters.roles;

    if (pRoles && pRoles instanceof Array && pRoles.length > 0) {      
      // 判断用户角色中是否有按钮要求的角色
      const hasPermission = roles.some(role => {
        return pRoles.includes(role);
      });

      // 如果没有权限则删除当前dom
      if (!hasPermission) {
        el.parentNode && el.parentNode.removeChild(el);
      }
    } else {
      throw new Error(`需要指定按钮要求角色数组,如v-permission="['admin','editor']"`);
    }
  }
};

export default permission;

main.js引入

import vPermission from "./directives/permission";
Vue.directive("permission", vPermission);

App.vue中使用

<button v-permission="['admin', 'editor']">editor button</button>
<button v-permission="['admin']">admin button</button>
  1. 按钮权限在v-permission不能操作的时候,使用v-if
  2. 自动生成导航菜单,导航菜单是根据路由信息并结合权限判断而动态生成的。它需要对应路由的多级嵌套,所以要用到递归组件。

封装request

对axios做一次封装,统一处理配置、请求和响应拦截

  1. 安装
npm i axios -S
  1. 创建@/utils/request.js
import Axios from "axios";
import { MessageBox, Message } from "element-ui";
import store from "@/store";

// 创建axios实例
const axios = Axios.create({
  baseURL: process.env.VUE_APP_BASE_API, // url基础地址,解决不同数据源url变化问题
  // withCredentials: true, // 跨域时若要发送cookies需设置该选项
  timeout: 5000 // 超时
});

// 请求拦截
axios.interceptors.request.use(
  config => {
    // do something
    const token = localStorage.getItem('token')
    if (token) {
      // 设置令牌请求头
      config.headers["Authorization"] = 'Bearer ' + token;
    }
    return config;
  },
  error => {
    // 请求错误预处理
    //console.log(error) // for debug
    return Promise.reject(error);
  }
);

// 响应拦截
axios.interceptors.response.use(
  // 通过自定义code判定响应状态,也可以通过HTTP状态码判定
  response => {
    // 仅返回数据部分
    const res = response.data;

    // code不为1则判定为一个错误
    if (res.code !== 1) {
      Message({
        message: res.message || "Error",
        type: "error",
        duration: 5 * 1000
      });

      // 假设:10008-非法令牌; 10012-其他客户端已登录; 10014-令牌过期;
      if (res.code === 10008 || res.code === 10012 || res.code === 10014) {
        // 重新登录
        MessageBox.confirm(
          "登录状态异常,请重新登录",
          "确认登录信息",
          {
            confirmButtonText: "重新登录",
            cancelButtonText: "取消",
            type: "warning"
          }
        ).then(() => {
          store.dispatch("user/resetToken").then(() => {
            location.reload();
          });
        });
      }
      return Promise.reject(new Error(res.message || "Error"));
    } else {
      return res;
    }
  },
  error => {
    //console.log("err" + error); // for debug
    Message({
      message: error.message,
      type: "error",
      duration: 5 * 1000
    });
    return Promise.reject(error);
  }
);

export default axios;

使用

import axios from '@/utils/request'

export function login(data) {
  return axios.post('/user/login', data)
}

export function getInfo() {
  return axios.get('/user/info')
}
  1. 设置VUE_APP_BASE_API环境变量,创建.env.development文件

  2. 编写服务接口,创建@/api/user.js

解决跨域

如果请求的接口在另一台服务器上,开发时则需要设置代理避免跨域问题

项目测试

主要介绍一些单元测试写法以及测试报告查看等