从0开始搭建一个vue-cli项目以及初始化工程(献给渣们,大神绕路)__Vue.js
发布于 4 年前 作者 banyungong 1391 次浏览 来自 分享
粉丝福利 : 关注VUE中文社区公众号,回复视频领取粉丝福利

前言

刚巧遇到有需要重构的项目,又要重新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

回到顶部