vue

Vue CLI配置

1.基于webpack构建,并带有合理的默认配置;2.开发环境服务3.自带预设的CLI插件

Posted by page on November 1, 2021

Vue CLI

安装/创建

  1. 全局安装命令行工具

    npm i @vue/cli -g
    vue --version
    
  2. 创建工程

    vue create admin
    
  3. 自定义工程预设

    • eslint-Airbnb增强版:默认包含Vue2,babel,router,vuex,eslint(常用选择)
    • Vue2默认版:包含Vue2,babel,eslint
    • Vue3默认版:包含Vue3,babel,eslint
    • Manually select feature:支持自定义vue版本,eslint规范,style预处理器等预设的组合

环境变量

预想项目模式: devlopment,sit, production

node命令配置

"scripts": {
  "lint": "vue-cli-service lint",
  "serve": "vue-cli-service serve --mode development",
  "dev": "vue-cli-service serve --mode development",
  "sit": "vue-cli-service build --mode sit",
  "build": "vue-cli-service build --mode production"
}

环境变量文件

  • .env.development
  • .env.sit
  • .env.production

注: 关于Vue CLI模式与环境配置关系

默认有development,test,production3个模式(mode),分别对应3个环境,3个webpack环境配置;Vue CLI会自动根据当前环境注入env.环境名文件的环境变量;

值得注意的是,可以自定义更多基于指定模式的环境,如vue-cli-service build --mode sit基于production模式配置的sit环境名,自动注入.env.sit环境变量文件;

vue.config.js

路径别名

module.exports = {
  // 目录别名配置
  configureWebpack: {
    resolve: {
      alias: {
        // 路径别名
        '@assets': '@/assets',
        '@components': '@/components',
        '@views': '@/views',
        ...
      },
    },
  }
};
// 默认"@"即项目根目录别名

注:使用别名引入时,eslint提示错误:Unable to resolve path to module "xxx";配置eslint忽略对@开头的别名路径检测

rules: {
    "import/no-unresolved": [
      2,
      {
        "ignore": ["^@"]
      }
    ]
}

babel配置

默认已使用 @vue/cli-plugin-babel 插件配置babel,详见 @vue/cli-plugin-babel

支持新增 babel.config.js 自定义babel配置

注:

默认配置下 babel-loader 会排除内部依赖即 node_modules 内文件。如希望编译转换依赖的模块, vue.config.js 需添加 transpileDependencies 选项,如transpileDependencies: ['ant-design-vue']

eslint配置

当前eslint规范主要为”@vue/airbnb”,我们可以加上”@vue/prettier”规范拓展包,并在rules下自定义某些规则;

  1. 安装”@vue/eslint-config-prettier”

    npm i @vue/eslint-config-prettier -D
    
  2. 添加prettier规范与自定义rules

    module.exports = {
      root: true,
      env: {
        node: true
      },
      extends: ["plugin:vue/essential", "@vue/airbnb", "@vue/prettier"],
      parserOptions: {
        parser: "babel-eslint"
      },
      rules: {
          "no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
         "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
        "import/no-unresolved": [    // 不解析以@开头的别名引用路径
          2,
          {
            ignore: ["^@"]
          }
        ],
        "import/extensions": ["off"] // 关闭解析引用模块文件后缀名
      }
    };
    

ElementUI支持

  • element官方文档引入教程,此处要求按需引入组件;
  • 推荐Vue CLI插件方式,不需要自己手写按需引入逻辑;执行vue add element命令,选择按需引入选项,此时项目自动安装element-ui包,自动生成plugins/element.js文件并在main.js引入,自动配置babel;
// element.js
import Vue from 'vue';
import {
    ......
    Message,
    Loading
} from 'element-ui';

......
Vue.use('xxx');
// 自定义 (不对其Vue.use)
Vue.prototype.$message = $message;
Vue.prototype.$loading = $loading;

// $message方法
function $message(options) {
  if (typeof options === 'string') options = { message: options };
  options.duration = options.duration || 1600;
  Message(options);
}

// $message.type方法:仅弹出唯一Message
// @params: String | Object
['success', 'error', 'warning', 'info'].forEach((type) => {
  $message[type] = function (msgStr) {
    Message.closeAll();
    const options = typeof msgStr === 'string' ? { message: msgStr } : msgStr;
    options.type = type;
    Message(options);
  };
});

// $loading:异步时自动开启/关闭
// @params: Function | Promise
function $loading(handle, options) {
  if (typeof options === 'string') options = { text: options };
  options = { ...options, background: options?.target ? '' : 'rgba(0, 0, 0, 0.7)' };
  const loadingInstance = Loading.service(options);
  handle = handle instanceof Function ? handle() : handle;
  return handle.finally(() => {
    loadingInstance.close();
  });
}

export { $message, $loading };

style预处理器

注:添加style预处理器(sass/less),此处使用sass

sass支持

  1. 安装

    npm i sass sass-loader -D
    

    注:

    • Vue CLI有默认的sass-loader配置,无需配置webpack.loader;
    • 项目运行报错this.getOptions is not a function:当前最新版sass-loader不兼容,卸载后安装sass-loader@8.0即可
  2. 样式文件

    • assets/styles/reset.css(重置客户端默认样式,统一样式)
    • assets/styles/variables.scss(项目中所有sass变量/混合)
    • assets/styles/style.scss(全局样式文件,包括各种快速样式/原子类,icon,sprite,动画,body样式)
  3. 全局引入variables.scss

    module.exports = {
       css: {
         loaderOptions: {
           sass: {
             // 全局可用的scss变量
             prependData: '@import "./src/assets/styles/variables.scss";',
            // 回调函数方式
            // 注入环境变量作为scss变量
            prependData: () => {
              let additionalData = '';
              additionalData += '@import "./src/assets/styles/variables.scss";';
              additionalData += `$staticUrl: "${process.env.VUE_APP_STATIC_URL}";`;
              return additionalData;
            }
           },
         },
       },
    };
    

    注: 如配置prependData后报错,可能由于当前sass-loader的version为9.0.0以上,配置参数prependData应修改为additionalData;

    其它方式

    module.exports = {
      pluginOptions: {
        'style-resources-loader': {
          preProcessor: 'scss',
          patterns: [
            path.resolve(__dirname, 'src/styles/variables.scss'),
            path.resolve(__dirname, 'src/styles/mixins.scss')
          ]
        }
      }
    }
    

    variables.scss/less

PostCSS插件

Vue CLI内置PostCSS且默认开启了autoprefixer插件,无需安装post-loader;

添加postcss-pxtorem插件支持

  1. 安装插件

    npm i postcss-pxtorem --save-dev
    
  2. 添加插件配置

    module.exports = {
       css: {
         loaderOptions: {
            postcss: {
              plugins: [
                'postcss-pxtorem': {
                    rootValue: 100,
                    minPixelValue: 1,
                    exclude: /node_modules/i
                }
              ]
            }
         },
       },
    };
    

axios支持

  1. 安装

    npm i axios -D
    
  2. 新建scr/api/目录

    // index.js
    import baseInterfaces from "./baseInterfaces.js";
    export default {
      ...baseInterfaces
    };
    
    // baseInterfaces.js基础接口
    import { get, post } from "./request";
    export const urls = {
        login: "/admin/login",
        register: "/admin/register"
    };
    // 登录
    export function login(params) {
        post(urls.login, params);
    }
    // 注册
    export function register(params) {
        get(urls.register, params);
    }
    
    // request.js
    import axios from "axios";
    const service = axios.create({
        baseURL: process.env.VUE_APP_BASEURL || "/",
        timeout: 24000
    });
    // 错误码统一处理
    function errorHandle(){
        code = code && String(code);
        switch(true){
            case code == "500":
            ...
        }
    }
    // 请求拦截
    service.interceptors.request.use(request => {
        ...
        return request;
    });
    // 响应拦截
    service.interceptors.response.use(response => {
        const { code, message } = response.data;
        // 后端返回错误信息
        if (code && code !== 200) {
          errorHandle(code, message);
          return Promise.reject(response.data);
        }
        return response.data;
        return response;
    });
    // get请求
    export function get(url, params, options) {
        return service.get({ url, { ...params, ...options}});
    }
    // post请求
    export function post(url, parmas, options) {
        return service.post({url, params, options});
    }
    
  3. 增强Vue

    Vue.prototype.$http = api;
    

Mock支持

目标:仅测试环境可选择mock数据,独立于项目外;测试/生产环境不引入mockjs库或掺杂mock的假数据;

// .env.development
VUE_APP_MOCK = true
// vue.config.js
devServer: {
    ...
    // mock状态
    before: process.env.VUE_APP_MOCK === "true" ? require("./src/mock/index") : null
}
// request.js mock时访问本地ip
const service = axios.create({
    baseURL: process.env.VUE_APP_MOCK ? "/" : process.env.VUE_APP_BASEURL,
    timeout: 24000
});
// /scr/mock/index.js
const baseInterfaces = require("./baseInterfaces.js");

module.exports = function(app) {
  baseInterfaces(app);
};

// /src/mock/baseInterfaces.js
const Mock = require("mockjs");

module.exports = function(app) {
  app.get("/login", function(req, res) {
    var json = {...Mock操作};
    res.json(json);
  });
  ...
};

SvgIcon支持

对于有颜色变化需求的图标,提供SvgIcon图标组件支持

添加全局组件

// components/svg/index.js
import Vue from "vue";
import SvgIcon from "./SvgIcon.vue";

Vue.component("svg-icon", SvgIcon);

const req = require.context("./svg", false, /\.svg$/);
const requireAll = requireContext => requireContext.keys().map(requireContext);
requireAll(req);
<!-- SvgIcon.vue --> 
<template>
  <svg :class="svgClass" aria-hidden="true" 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}`;
      }
      return "svg-icon";
    }
  }
};
</script>

<style scoped>
.svg-icon {
  width: 1em;
  height: 1em;
  fill: currentColor;
  overflow: hidden;
}
</style>

svg-sprite-loader

// svg-sprite-loader
chainWebpack: (config) => {
    ......
    config.module.rule('svg').uses.clear(); // 清除默认的file-loader
    config.module
        .rule('svg')
        .include.add(path.resolve(__dirname, './src/components/SvgIcon/svg'))
        .end()
        .use('svg-sprite-loader')
        .loader('svg-sprite-loader')
        .options({
          symbolId: 'icon-[name]'
        });
}

如果你不确定把所有svg当做icon组件处理,你可以使用 config.module.rule('svg').exclude.add(path.resolve(__dirname, './src/components/SvgIcon/svg')) 取代上面的清除svg默认的file-loader代码;

Layout基本布局

admin内页基本布局包括顶部栏、侧边栏、main主内容,其中Main下放置二级router-view

  • 布局结构

    <!-- @/layout/index.vue -->
    <template>
      <el-container class="fulled-h">
        <el-header class="fulled-w bgc-darkpurple"><Header /></el-header>
        <el-container class="container-body fulled-h">
          <el-aside><Aside /></el-aside>
          <el-main class="bgc-light"> <Main /></el-main>
        </el-container>
      </el-container>
    </template>
    
  • vue router配置结构

    [
      // 首页
      {
        path: "/home",
        component: Layout,
        children: [
          {
            path: "/",
            name: "home",
            meta: {
              title: "首页"
            },
            component: () => import("@views/home/index.vue")
          }
        ]
      },
      
      // 设置
      {
        path: "/setting",
        component: Layout,
       // 二级路由
        children: [
          {
            path: "userSetting",
            component: () => import("@views/setting/userSetting/index.vue")
          },
          {
            path: "adminSetting",
            component: () => import("@views/setting/adminSetting/index.vue"),
               // 三级路由
            children: [
                path: "detail",
                component: () => import("@views/setting/adminSetting/detail.vue"),
            ]
          }
        ]
      }
    ];
    
  • 二级路由页面结构

    <template>
        <div v-if="$route.name === 'adminSetting'">
            <!-- 放置二级路由页面内容 -->
        <div>
        <!-- 三级路由占位 -->
        <router-view v-else></router-view>
    </template>
    

router模块

/router
    /modules                各模块路由配置
    auth.js                    鉴权逻辑与控制权限路由方法
    index.js
    router.config.js         全局路由配置划分
// /router/router.config.js 路由配置列表
import Layout from '@/layout/index';
import { home, ... } from './modules';

// 基本路由
export const baseRoutes = [
  {
    path: '/',
    redirect: '/home'
  },
  {
    path: '/login',
    name: 'login',
    component: () => import('@/views/login/login.vue')
  },
  {
    path: '/error',
    name: 'error',
    component: () => import('@/views/error/404.vue')
  }
];

// 404兜底路由
export const errorRoute = {
  path: '*',
  component: Layout,
  children: [
    {
      path: '/',
      name: '404',
      meta: { title: 404 },
      component: () => import('@/views/error/404.vue')
    }
  ]
};

// 所有admin路由,显示于侧边栏
export const adminRoutes = [home, ...... ];

multi-page

对于多页面项目,通过vue.config.js中pages字段配置页面入口与html模板

pages: {
  index: {
    entry: 'src/.../main.js',
    template: 'public/index.html',
    // 为了支持默认根页面与热更新有效
    // 输出文件名必须为index.html,允许附带路径
    filename: 'index.html',
    chunks: ['chunk-vendors', 'chunk-common', 'index']
  },
  sub: {
    entry: 'src/.../main.js',
    template: 'public/index.html',
    filename: 'sub.html',
    chunks: ['chunk-vendors', 'chunk-common', 'sub']
  }
}

multi-page模式下如果存在 html-pluginpreload的自定义配置,这会导致问题(https://cli.vuejs.org/zh/config/#pages)

尝试分别对页面添加插件配置

// 移除所有preload
config.plugins.delete('preload-index');
config.plugins.delete('preload-sub');

// 多页面HTML控制
config.plugin('html-index').tap(configureHtmlPlugin);
config.plugin('html-sub').tap(configureHtmlPlugin);