Vue搭建后台项目
参考:
https://github.com/PanJiaChen/vue-element-admin/blob/master/README.zh-CN.md
https://juejin.im/post/59097cd7a22b9d0065fb61d2
1、当项目逐渐变大之后,文件与文件直接的引用关系会很复杂,这时候就需要使用alias了。
//webpack.base.config.js alias: { 'src': path.resolve(__dirname, '../src'), 'components': path.resolve(__dirname, '../src/components'), 'api': path.resolve(__dirname, '../src/api'), 'utils': path.resolve(__dirname, '../src/utils'), 'store': path.resolve(__dirname, '../src/store'), 'router': path.resolve(__dirname, '../src/router') } //使用xxx.js import stickTop from 'components/stickTop' import getArticle from 'api/article'
2、ESLint
不管是多人合作还是个人项目,代码规范是很重要的。这样做不仅可以很大程度地避免基本语法错误,也保证了代码的可读性。推荐 eslint+vscode 来写 vue。
每次保存,vscode就能标红不符合eslint规则的地方,同时还会做一些简单的自我修正。安装步骤如下:
1)首先在vscode里安装eslint插件
2)点击 文件 > 首选项 > 设置 打开 VSCode 配置文件,添加如下配置。这样每次保存的时候就可以根据根目录下.eslintrc.js你配置的eslint规则来检查和做一些简单的fix。
{ "files.autoSave":"off", "eslint.validate": [ "javascript", "javascriptreact", "html", { "language": "vue", "autoFix": true } ], "eslint.options": { "plugins": ["html"] }, "eslint.autoFixOnSave": true }
3、js环境变量
process对象是 Node 的一个全局对象,提供当前 Node 进程的信息。它可以在脚本的任意位置使用,不必通过require命令加载。process.env属性返回一个对象,包含了当前Shell的所有环境变量。通常的做法是,新建一个环境变量NODE_ENV,用它确定当前所处的开发阶段,生产阶段设为production,开发阶段设为develop或staging,然后在脚本中读取process.env.NODE_ENV即可。
DefinePlugin:允许你创建一个在编译时可以配置的全局常量。这可能会对开发模式和发布模式的构建允许不同的行为非常有用。
//webpack.prod.conf.js var env = process.env.NODE_ENV === 'production' ? config.build.prodEnv : config.build.sitEnv new webpack.DefinePlugin({ 'process.env': env })
//package.json "scripts": { "dev": "node build/dev-server.js", "build:prod": "cross-env NODE_ENV=production node build/build.js", "build:sit": "cross-env NODE_ENV=sit node build/build.js" }
cross-env这个npm包使得在使用或设置环境变量时能用同一种方式适配不同的平台(以unix方式设置环境变量,然后在windows上也能兼容运行)
3、前后端交互
1)封装 axios
//request.js import axios from 'axios' import { Indicator, MessageBox } from 'mint-ui' // 创建axios实例 const service = axios.create({ baseURL: process.env.BASE_API, // api的base_url timeout: 60000 // 请求超时时间 }) // request拦截器 service.interceptors.request.use(config => { // Do something before request is sent Indicator.open('加载中...') return config }, error => { // Do something with request error Promise.reject(error) }) // respone拦截器 service.interceptors.response.use( response => { Indicator.close() const res = response.data if (res.ReturnCode !== '000000') { if (res.ReturnMsg) { MessageBox.alert(res.ReturnMsg) } else { MessageBox.alert('系统未知错误') } return Promise.reject(res) } else { return res } }, error => { Indicator.close() MessageBox.alert('太火爆了吧,稍安勿躁,亲,再试试') return Promise.reject(error) } ) export default service
//api/xxx.js import request from '@/utils/request' // 登录 export function userLogin(data) { return request({ url: 'login.do', method: 'post', data }) }
2)跨域问题
常用的方式就是 cors
全称为 Cross Origin Resource Sharing(跨域资源共享)。这种方案对于前端来说和平时发请求写法上没有任何区别,工作量基本都在后端这里。前端也是有解决方案的,dev环境也可以通过webpack-dev-server
的proxy
来解决。【webpack的dev-server 使用了非常强大的 http-proxy-middleware 包】
a、直接使用http-proxy-middleware包
//配置js1 var proxyMiddleware = require('http-proxy-middleware'); var express = require('express'); var app = express(); Object.keys(proxyTable).forEach(function (context) { var options = proxyTable[context] if (typeof options === 'string') { options = {target: options} } app.use(proxyMiddleware(options.filter || context, options)) }); //配置js2 proxyTable: { "/tt": { target: "http://localhost:8090", changeOrigin: true } }
b、使用webpack-dev-server此npm包(webpack的devServer配置
)
//webpack.dev.config.js devServer: { proxy: config.dev.proxyTable }, //配置js proxyTable: { "/portal": { target: "http://localhost:8080" } }
3)Mock 数据
mockjs:拦截请求并代理到本地模拟数据
4、权限控制
详见https://juejin.im/post/591aa14f570c35006961acac
做后台项目区别于做其它的项目,权限验证与安全性是非常重要的,可以说是一个后台项目一开始就必须考虑和搭建的基础核心功能。我们所要做到的是:不同的权限对应着不同的路由,同时侧边栏也需根据不同的权限,异步生成(动态根据用户的 role 算出其对应有权限的路由,通过 router.addRoutes 动态挂载这些路由)。
//permission.js const whiteList = ['/login']// 不重定向白名单 router.beforeEach((to, from, next) => { NProgress.start() // 开启Progress const token = store.state.user.token if (token) { // 判断是否有token if (to.path === '/login') { next({ path: '/' }) } else { store.dispatch('GenerateRoutes').then(() => { // 生成可访问的路由表 router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表 next() }) } } else { if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入 next() } else { next('/login') // 否则全部重定向到登录页! } } })
5、样式
常见的工作流程是,全局样式都写在 src/styles
目录下,每个页面自己对应的样式都写在自己的 .vue
文件之中
1)使用scoped解决样式冲突问题(基于PostCss的,加了一个作用局的概念)
//编译前 .example { color: red; } //编译后 .example[_v-f3f3eg9] { color: red; }
2)自定义 element-ui 样式
由于element-ui的样式我们是在全局引入的,所以你想在某个view里面覆盖它的样式就不能加scoped,但你又想只覆盖这个页面的element样式,你就可在它的父级加一个class,以用命名空间来解决问题。
.aritle-page{ //你的命名空间 .el-tag { //element-ui 元素 margin-right: 0px; } }
6、ECharts
管理后台图表也是常见的需求。ECharts支持webpack引入,图省事可以将ECharts整个引入;
不过ECharts还是不小的,我们大部分情况只是用到很少一部分功能,因此可以按需引入的。
// 引入 ECharts 主模块 var echarts = require('echarts/lib/echarts'); // 引入柱状图 require('echarts/lib/chart/bar'); // 引入提示框和标题组件 require('echarts/lib/component/tooltip'); require('echarts/lib/component/title');
7、使用icon图标
雪碧图,就是将多个图片合成一个图片,然后利用 css 的 background-position 定位显示不同的 icon 图标。但这个也有一个很大的痛点,维护困难。每新增一个图标,都需要改动原始图片,还可能不小心出错影响到前面定位好的图片,而且一修改雪碧图,图片缓存就失效了,久而久之你不知道该怎么维护了。
Iconfont-阿里巴巴矢量图标库—symbol引用:这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。
- 支持多色图标了,不再受单色限制。
- 通过一些技巧,支持像字体那样,通过
font-size
,color
来调整样式。 - 兼容性较差,支持 ie9+,及现代浏览器。
- 浏览器渲染svg的性能一般,还不如png。
第一步:拷贝项目下面生成的symbol代码:
引入 ./iconfont.js
第二步:加入通用css代码(引入一次就行):
<style type="text/css">
.icon {
width: 1em; height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
第三步:挑选相应图标并获取类名,应用于页面:
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-xxx"></use>
</svg>
1)创建
icon-svg组件
<!--components/Icon-svg--> <template> <svg class="svg-icon" aria-hidden="true"> <use :xlink:href="iconName"></use> </svg> </template> <script> export default { name: 'icon-svg', props: { iconClass: { type: String, required: true } }, computed: { iconName() { return `#icon-${this.iconClass}` } } } </script>
//引入svg组件 import IconSvg from '@/components/IconSvg' //全局注册icon-svg Vue.component('icon-svg', IconSvg) //在代码中使用 <icon-svg icon-class="password" />
2)使用 svg-sprite优化
a、优化原因:现在所有的 svg-sprite
都是通过 iconfont 的 iconfont.js
生成的
- 首先它是一段用js来生成svg的代码,所有图标 icon 都很不直观。你完全不知道哪个图标名对应什么图标, 每次增删改图标只能整体js文件一起替换。
- 其次它也做不到按需加载,不能根据我们使用了那些 svg 动态的生成
svg-sprite
。 - 自定义性差,通常导出的svg包含大量的无用信息,例如编辑器源信息、注释等。通常包含其它一些不会影响渲染结果或可以移除的内容。
- 添加不友善,如果我有一些自定义的svg图标,该如何和原有的
iconfont
整合到一起呢?目前只能将其也上传到iconfont
和原有的图标放在一个项目库中,之后再重新下载,很繁琐。
b、在 vue-cli
的基础上进行改造,加入 svg-sprite-loader(可以将多个 svg 打包成
svg-sprite
)
vue-cli
默认情况下会使用 url-loader
对svg进行处理,会将它放在/img
目录下,所以这时候我们引入svg-sprite-loader
会引发一些冲突(解决方案有两种,最简单的就是你可以将 test 的 svg 去掉,这样就不会对svg做处理了,当然这样做是很不友善的:你不能保证你所有的 svg 都是用来当做 icon的,有些真的可能只是用来当做图片资源的;不能确保你使用的一些第三方类库会使用到 svg)。最安全合理的做法是使用 webpack 的 exclude 和 include ,让svg-sprite-loader
只处理你指定文件夹下面的 svg,url-loaer
只处理除此文件夹之外的所有svg。//webpack.base.config.js { test: /\.svg$/, loader: 'svg-sprite-loader', include: [resolve('src/icons')], options: { symbolId: 'icon-[name]' } }, { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: 'url-loader', exclude: [resolve('src/icons')], options: { limit: 10000, name: utils.assetsPath('img/[name].[hash:7].[ext]') } }
c、自动导入
我们创建一个专门放置图标 icon 的文件夹如:@/src/icons,将所有 icon 放在这个文件夹下。之后我们就要使用到 webpack 的 require.context。require.context有三个参数(directory:说明需要检索的目录;useSubdirectories:是否检索子目录;regExp: 匹配文件的正则表达式)
//src/icons/index.js const requireAll = requireContext => requireContext.keys().map(requireContext) const svgs = require.context('./svgs', false, /\.svg$/)
//去svgs文件夹(不包含子目录)下面的找所有文件名以.svg
结尾的文件
requireAll(svgs)
3)进一步优化自己的svg-移除无用信息
iconfont 网站导出的 svg 内容已经算蛮精简的了,但你会发现其实还是与很多无用的信息,造成了不必要的冗余。好在 svg-sprite-loader也考虑到了这点,它目前只会获取 svg 中 path 的内容,而其它的信息一概不会获取。但任何你在 path 中产生的冗余信息它就不会做处理了,如注释什么的。这时候我们就要使用另一个很好用的东西了-- svgo:因为SVG文件,尤其从各种变假期导出的SVG,通常包含大量的无用信息,例如编辑器源信息,注释,因此默认或者非最优值以及其他一些不会影响渲染结果的元素可以移除或转换。