Vue CLI
安装/创建
-
全局安装命令行工具
npm i @vue/cli -g vue --version
-
创建工程
vue create admin
-
自定义工程预设
- 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下自定义某些规则;
-
安装”@vue/eslint-config-prettier”
npm i @vue/eslint-config-prettier -D
-
添加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支持
-
安装
npm i sass sass-loader -D
注:
- Vue CLI有默认的sass-loader配置,无需配置webpack.loader;
- 项目运行报错
this.getOptions is not a function
:当前最新版sass-loader不兼容,卸载后安装sass-loader@8.0即可
-
样式文件
- assets/styles/reset.css(重置客户端默认样式,统一样式)
- assets/styles/variables.scss(项目中所有sass变量/混合)
- assets/styles/style.scss(全局样式文件,包括各种快速样式/原子类,icon,sprite,动画,body样式)
-
全局引入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') ] } } }
PostCSS插件
Vue CLI内置PostCSS且默认开启了autoprefixer插件,无需安装post-loader;
添加postcss-pxtorem
插件支持
-
安装插件
npm i postcss-pxtorem --save-dev
-
添加插件配置
module.exports = { css: { loaderOptions: { postcss: { plugins: [ 'postcss-pxtorem': { rootValue: 100, minPixelValue: 1, exclude: /node_modules/i } ] } }, }, };
axios支持
-
安装
npm i axios -D
-
新建
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}); }
-
增强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-plugin
和 preload
的自定义配置,这会导致问题(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);