前言
刚巧遇到有需要重构的项目,又要重新copy一份vue项目来。由于之前没有记录重构过程,故又要重新折腾一翻。
于是想起汇总一篇博客,方便自己下次快速copy,也希望能帮助到圈子的朋友们。
该教程适合0~2年工作经验的前端工作者们;
该项目框架搭建过程,适合中小企业,门户,商城网站等。
构建
1)新建项目
全局安装
全局安装webpack: npm install webpack webpack-cli -g
全局安装vue-cli: npm install --global vue-cli
新建项目
进入到对应项目地址,新建一个my_store目录:vue init webpack my_store
再选择路由所需,可参考笔者截图所需。
此时,一个完整的vue-cli的项目已经新建改造完成。
2)嵌入项目插件
根据个人项目所需,引入对应的插件。如常用的组件库,element ui等。
有一个需要养成的习惯。如安装完组件库element,要同时修改嵌入element的配置、如安装玩webpack的插件,也应该及时安装对应的less跟pulgus等。
本文以最简单的less插件为例,安装完less,及时安装编译器less-loader。以及webpack的修改:
npm install less --save npm install less-loader --save
这里笔者遇到less上的几个坑,顺便分享一下。
此时我们可能有用到公用less文件,可通过sass-resources-loader获取公用文件变量。
我们将原来的utils.js对less的解析,修改一下,从原来的 generateLoaders(‘less’),修改成我们自定义的loader。 新建一个lessResourceLoader,
看了一下generateLoaders源码,就是讲cssLoader跟less-loader编译一下,然后,判断extract暴露出去。 那么我们的lessResourceLoader也同理,只需要将引入的地方修改即可。代码如下:
less: generateLoaders('less'),
修改成
less: lessResourceLoader(),
function lessResourceLoader() {
var loaders = [
cssLoader,
'less-loader',
{
loader: 'sass-resources-loader',
options: {
resources: [
path.resolve(__dirname, "../src/assets/less/common.less"),
]
}
}
];
if (options.extract) {
return ExtractTextPlugin.extract({
use: loaders,
fallback: 'vue-style-loader'
})
} else {
return ['vue-style-loader'].concat(loaders)
}
}
不过less有个坑点,可能会存在版本号问题。如果此时编译还不成功,可以尝试修改一下less的版本。
package.json中
"less": "^3.9.0",
"less-loader": "^5.0.0",
3)路由钩子设置
先看看官方自带的router,他直接把路由通过一个对象暴露出去。几个页面的项目,的确如此最简单。但是上百个页面的项目,将会十分杂乱。我们要在前期做好项目的准备。
我们新建一个,routesConfig文件,
const routesConfig = [{
path: '/',
name: 'index',
meta: {
title: process.env.TITLE_NAME + '首页',
},
component: (resolve) => require(['./views/index'], resolve)
}, {
path: '/productList',
name: 'productList',
meta: {
title: process.env.TITLE_NAME + '商品列表',
},
component: (resolve) => require(['./views/producList/producList'], resolve)
}
]
export default routesConfig
这里写了两个页面为案例。有两个值得细节值得注意的地方:
- 巧用meta来只控制页面,比如上述把标题的名称放在meta的tile属性中,我们下述配合钩子函数即可实现。同理,用户权限控制等,都可以通过这里的变量来设置登录权限。
- 我们看到resolve去加载页面。为什么不直接使用require或者import呢?因为使用require或者import,等于同步加载代码,可能在本地开发体验不出来,但是一旦打包,用户打开将加载所有页面,想想一百个页面同时加载是多么可怕的事情。所以,我们这里要用到异步加载。
接下来,我们要把routesConfig放在我们的router中使用。 首先为什么要把routesConfig跟router脱离开呢?这里以防以后业务扩展。例如我们的商城后续可能还有会员系统,那我们可以单独创建多一个routerMebmerConfig, 这里就可以跟原来的区分开。
然后,我们写一个简单的路由钩子。vue的全局路由钩子,只有两个,分别为beforeEach,afterEach。字面意思很清楚,一个是进来之前,一个是进去之后。场景也很好分析,
例如,进去之前,我们要检测我们是否登录。例如,进入之后,我们所有页面,都要异步加载系统消息提示。
router.beforeEach((to, from, next) => {
//next() //如果要跳转的话,一定要写上next()
//next(false) //取消了导航
next() //正常跳转,不写的话,不会跳转
})
结合以上两点分析,我们来看看我们修改的router.js的代码:
import Vue from 'vue'
import Router from 'vue-router'
import routerConfig from './routesConfig.js'
const router = new VueRouter({
// routes: { ...routerConfig, ...routerMebmerConfig } 如果未来有会员系统routerMebmerConfig
routes: routerConfig
})
router.beforeEach((to, from, next) => { //beforeEach是router的钩子函数,在进入路由前执行
if (to.meta.title) { //判断是否有标题
document.title = to.meta.title
}
next()//执行进入路由,如果不写就不会进入目标页
})
export default router
4)常用工具封装
路由修改完成后,此时我们开始写页面。发现写页面之前,公用方法还未引入。
考虑到,项目配置文件的统一,团队的协同,笔者通常把封装的工具统一放入一个文件中。我们新建utils目录。
写法1)暴露文件,然后嵌在原型中。
export default {
uIsPhone,//公用判断手机号防范
uGetCurrentDate,//公用获取系统时间格式方法,
}
/**
* 验证手机号
*/
const uIsPhone = (phone) => {
if ( !phone || phone.length < 11) {
return false;
} else {
return (!/^1[0-9]\d{4,9}$/.test(phone));
}
}
/**
* 获取当前时间 type = 1 精确到天 type = 2 精确到分
*/
const uGetCurrentDate = (type = 2)=> {
const now = new Date();
return type === 1 ? time = now.getFullYear() + "-" + now.getMonth() + "-" + now.getDate()
: now.getFullYear() + "-" + now.getMonth() + "-" + now.getDate() + " " + now.getHours() + ":" + now.getMinutes() + ":" + now.getSeconds();
}
这里也有注意的地方:
- export default写在最上边,方便参与项目开发进入可快速看到api。
- 使用匿名函数写法,可更保证方法的使用
我们在全局的时候引入他。如在main.js新建实例之前,我们把utils对象暴露给windons。
import utils form ‘./utils/utils’; Vue.prototype.utils = utils
写法2)直接扩展vue原型
当然,我们可以借助vue这个实例进行扩展,这样我们在项目中,也可以随时使用对应的utils方法。
const utils = {};
utils.install = function (Vue) {
/**
* 验证手机号
*/
Vue.prototype.uIsPhone = (phone, ) => {
if (!phone || phone.length < 11) {
return false;
} else {
return (!/^1[0-9]\d{4,9}$/.test(phone));
}
}
/**
* 获取当前时间 type = 1 精确到天 type = 2 精确到分
*/
Vue.prototype.uGetCurrentDate = (type) => {
const now = new Date();
return type === 1 ? time = now.getFullYear() + "-" + now.getMonth() + "-" + now.getDate() :
now.getFullYear() + "-" + now.getMonth() + "-" + now.getDate() + " " + now.getHours() + ":" + now.getMinutes() + ":" + now.getSeconds();
}
}
export default utils;
接下来,因为工具类,相对较小,且经常使用。我们在全局引入他。在我们的main文件,把vue实例new出来之前,先把vue的实例改了。
import utils form ‘./utils/utils’
Vue.use(utils);
5)公用组件封装
考虑到,项目配置文件的统一,团队的协同,笔者通常,把一个项目的公用组件,统一放在一个文件夹。
组件,可分为局部组件,以及全局组件。也可以分为,业务组件,功能组件。
这里简单写一个业务上的全局组件。举个最简单的栗子,商城文件的头部跟底部,这基本上是全局大多数页面用到的。如果每一个去引入,项目将low一个档次,所以我们用全局的方法引入。
我们新建一个头部文件components\common\common_header:
<template>
<div class="header_box">
<div class="pageContent flex_box">
<div class="p_flex">
<div>商城首页</div>
<div>官网</div>
<div>作业平台</div>
</div>
<div class="p_flex">
<div>购物车</div>
<div>我的收藏</div>
<div>订单查询</div>
<div class="f_tag">**商城,欢迎你</div>
</div>
</div>
</div>
</template>
<style scoped lang="less">
.header_box {
height: 50px;
background: #000000;
width: 100%;
color: white;
.flex_box {
display: flex;
flex-direction: row;
line-height: 50px;
justify-content: space-between;
.p_flex {
display: flex;
div{
margin-right: 30px;
}
.f_tag {
margin: 0 0 0 120px;
}
}
}
}
</style>
再新建common_header.js
import CommonHeader from './common_header.vue';
const commonHeader = {
install: function (Vue) {
Vue.component('CommonHeader', CommonHeader)
}
}
export default commonHeader;
这样,我们再main.js引入组件,即为全局组件。
import commonHeader from './components/common/common_header';
Vue.use(commonHeader);
//对应页面代码直接使用,无需引入。
<template>
<commonHeader></commonHeader>
</template>
6)本地代理设置
再来一下,开发前必要重大环节,本地代理。首先,本地代理,通常针对开发环境,那么我们的目标也明确了,即是webpack.dev.conf.js的中的proxy属性。我们在该文件的devServer,proxy属性,这里发现,他原来已经配置到config.dev.proxyTable去。而proxyTable默认为空。如下图:
我们修改成独立文件,新建bulid/proxyConfig.js
module.exports = {
proxy: {
'/api': { //将www.exaple.com印射为/apis
target: process.env.BASE_URL, // 接口域名
secure: false, // 如果是https接口,需要配置这个参数
changeOrigin: true, //是否跨域
pathRewrite: {
'^/api': process.env.BASE_URL //需要rewrite的
}
},
'/mdapi': {
target: process.env.MD_URL
secure: false,
changeOrigin: true,
pathRewrite: {
'^/mdapi': process.env.MD_URL
}
}
}
}
process.env的变量,可再config的env文件设置。
再修改一下入口即可:
const proxyConfig = require('../build/proxyConfig')
// proxy: config.dev.proxyTable,
proxy: proxyConfig,
7)网络请求封装
网络请求工具中,当前最受欢迎无疑是axios。这里就不再对比ajax以及fetch。
axios的用法十分简单,但是基于项目的本身的业务,我们可能需要适当的扩展一下,举一些简单的应用场景:
- 项目如果用restful接口规范,我们需要及时捕捉异常去修复。比如无权限403时,可以提示一下页面重新登录,这时候就可以使用axios.interceptors.response进行拦截。
- 统一请求头,与服务端进行通信。如传递token等,我们插入header中。
- 自动判断请求协议的。如http访问,则所有接口用http。反之https,则https。
这里笔者提供一个去除公司业务之后的一个简版请求作为参考:
axios.interceptors.request.use(
config => {
if (window.location.protocol === 'https:') {
config.url = config.url.replace("http:", "https:");
}
return config;
}
)
axios.interceptors.response.use(
response => {
return Promise.resolve(response);
},
error => {
if (error.response.status) {
switch (error.response.status) {
case 401:
message.error('账号超时,请重新登录');
hashHistory.push('/')
break;
}
}
return Promise.resolve(error.response);
}
);
const uGet = ({ url, data }) => {
const headers = {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer ' + common.cGetToken(),
// 'Token': 'Bearer ' + common.cGetToken()
};
return axios.get(url, {
params: data,
headers
}).then(function (response) {
if (response != null) {
return response
}
return Promise.reject(new Error('FETCH_NOW_PLAYING failure'))
}).catch(function (error) {
console.log(error)
});
}
const uPost = ({ url, query }) => {
const headers = {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer ' + common.cGetToken(),
};
const _url = query ? `${url}?${query}` : `${url}`;
return axios.post(_url, { headers })
.then(function (response) {
if (response != null) {
return response
}
return Promise.reject(new Error('failure'))
}).catch(function (error) {
console.log(error)
})
}
8)环境区分配置
啥?环境不是区分好了么。是滴,vue-cli已经帮我们做好了开发环境,正式环境。
是滴。vue-cli的确提供了开发环境,正式环境。但我们实际中还是需要二次改造。原因很简单,我们项目不仅仅只有两个环境。
例如公司有两台服务器,分别为测试服务端,线上服务端。
那么我们的前端环境,将有四个。“本地调试测试服务端”,“本地调试线上服务端”,“线上环境测试服务端”,“线上环境线上服务端”
来看package.json文件:
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"build": "node build/build.js",
我们这里都昨晚测试服务端。再来个线上服务端。
"devOnLine": "webpack-dev-server --inline --progress --config build/webpack.devOnLine.conf.js",
"buildOnLine": "node build/buildOnLine.js",
此时分别拷贝原来的webpack.dev.conf.js跟build.js到webpack.devProd.conf.js跟buildProd.js.再修改引入的环境变量。即可分为4个环境。
文章结束
该文章难度相对简单,可能存在部分一些简单的说明(我觉得大家都懂),如果不明白,可以评论,可以第一时间给你回复。如果帮到你,希望给个赞支持一下!
版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: 逐步前行 原文链接:https://juejin.im/post/6865848252823142414