使用Vue-cli3.x从零搭建Vue项目

809 阅读5分钟

起因

因为vue的使用广泛,经常开发Vue项目,发现每次初始化Vue项目都做了很多重复的事情,为了避免重复造轮子,故而自己写一篇用Vue-cli@3.x脚手架从零搭建vue项目的文章,对于组件封装方面有设计不好的地方希望大家多多指点。

最终效果

线上预览地址:http://129.28.151.8:3001/index.html

github地址:猛戳这里

话不多说,直接动手


安装Vue-cli

官方文档:cli.vuejs.org/zh/guide/

npm install -g @vue/cli
// 为了能直接在CMD窗口中使用Vue命令创建项目,需要全局安装Vue-cli(-g是全局的意思)

创建项目

Vue create vue-template(vue项目名)

在指定项目文件夹内打开窗口,输入该命令,直接按下回车,这里我选用默认的babel与eslint配置

生成后的vue项目

将CMD窗口切换到vue-template目录下,运行项目

npm run serve

项目跑起来之后我们需要安装vue-router实现页面的跳转

安装并初步配置vue-router

官方文档:router.vuejs.org/zh/installa…

安装vue-router一般是用npm安装,vue-cli3官方提供了一个添加插件命令,这里我们示范两种方式

方式1:

直接npm安装,安装之后需要自己手动创建router配置(当前项目使用该方式)

npm install vue-router

安装完成之后,我们在src目录下创建一个router文件夹,再在router下创建一个index.js文件,用来做router配置

+ router
    + index.js

配置1:router/index.js

// router/index.js
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)

const router = new Router({
  linkExactActiveClass: "active", // <router-link>选中的class,用来做底部tabbar选中状态样式高亮
  routes: [
    {
      path: '/',
      component: resolve => require(['@/components/HelloWorld'], resolve) 
      // 路由懒加载方式,'@/' webpack配置默认的别名,指向src/
    }
  ]
})
export default router

配置2:main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router' // 引入vue-router对象
Vue.config.productionTip = false

new Vue({
  router, // 挂载到vue的配置中
  render: h => h(App)
}).$mount('#app')

配置3:app.vue

<template>
  <div id="app">
    <router-view/>
  </div>
</template>

这样就可以通过vue-router的<router-view>容器显示HelloWrold组件了

方式2:

该方式是vue-cli3官方提供的,vue add 可以直接安装和调用 Vue CLI 插件,会生成对应的文件和自动引用

vue add router

输入命令之后会出现一条询问语句,问你路由的模式是否为history模式,这里我们直接输入n,使用hash模式

安装完成之后项目会发生以下改变

  1. src目录下新增views文件夹,里面有2个vue组件
  2. src目录下新增router.js配置文件,并且引入了views下的两个组件
  3. main.js引入了router.js并挂载在vue配置当中
  4. ...自行研究,不做过多介绍,相当于省了我们直接用npm安装的配置

安装CSS预处理器stylus

npm install stylus stylus-loader

安装完成之后在vue文件中的style标签中加入 lang="stylus" 就能够直接使用stylus语法

示例

首先我们清空HelloWorld中的HTML,之后再run serve查看效果,页面中就展示出了一个红色字体的Hello World

<template>
  <div class="hello">
    <h1>Hello World!</h1>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  }
}
</script>

<style lang="stylus" scoped>
h1 // 使用stylus语法
  color #f55
</style>

添加reset样式文件,重置浏览器默认样式

文件地址:reset.styl

// 在src/assets/目录下添加创建styl文件夹,再在styl目录下添加reset.styl样式文件
src
    assets
        + styl
            + reset.styl

// 再在main.js引入reset.styl样式文件
import '@/assets/styl/reset.styl'

封装header组件

清空components文件夹下的文件,新增wjHead文件夹,添加wjHead.vue、index.js

components
    + wjHead
        + wjHead.vue
        + index.js

wjHead.vue

<template>
  <div class="maxHeader-wrap">
      <div class="maxHeader bottom-line">
          <span v-if="isBack" onclick="history.back()">
              <i class="iconfont icon-fanhui2"></i>
          </span>
          <div class="title">{{title}}</div>
      </div>
  </div>
</template>

<script>
export default {
  name: 'wjHead',
  props: {
    isBack: { // 是否需要返回箭头,tabbar不需要返回箭头
      type: Boolean,
      default: true
    },
    title: String // header组件标题
  }
}
</script>

<!---->
<style lang="stylus" scoped>
.maxHeader-wrap
  height 45px
  .maxHeader
    height 45px
    line-height 45px
    text-align center
    background-color #fff
    color #333
    font-size 16px
    position fixed!important
    display flex
    justify-content space-between
    padding-right 15px
    align-items center
    z-index 99
    top 0
    left 0
    right 0
    span
      color #333
      height 45px
      line-height 45px
      width 45px
      text-align center
      i
        font-size 20px
    div.title
      position absolute
      left 50%
      transform translateX(-50%)
      width 100%
      z-index -1
</style>

wjHead/index.js

import lsHead from './wjHead'

function install (Vue) { // 在mian.js中会Vue.use该文件模块,Vue内部会调用install方法并传入Vue
  Vue.component(lsHead.name, lsHead) // 注册全局组件
}
export default { install }

main.js

Vue.use(require('@/components/wjHead').default)
// 直接require这个组件,接下来就能在任意vue文件内引用该组件

APP.vue

// 这里我们直接将head添加到app.vue当中,清空APP.vue当中的CSS
<template>
  <div id="app">
    <wj-head title="首页"></wj-head>
    <router-view/>
  </div>
</template>

<script>
export default {
  name: 'app'
}
</script>

<style lang="stylus" scoped>

</style>

1px边框与全局styl变量、函数

src/assets/styl/目录下新增variable.styl、base.styl

文件地址:variable.styl,base.sty

assets
    styl
        + variable.styl
        + base.styl
          reset.styl

项目根目录下新增vue.config.js配置文件

// vue.config.js
module.exports = {
  css: {
    loaderOptions: {
      stylus: {
        import: ['~@/assets/styl/variable.styl', '~@/assets/styl/base.styl'] // 全局css变量和全局css
      }
    }
  }
}

wj-head组件当中添加了class="bottom-line",引用的base.style中的1px边框类,接下来我们看效果

新增4个Vue文件页面(3个tabbar页面,1个跳转页面,页面内容先自行填充)

// src目录下新增pages文件夹
src
    + pages
        + home.vue
        + me.vue
        + trip.vue
        + notTab.vue

再次配置vue-router文件

import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)


// tabbar页面,提取出来做for循环
const tabPages = [
  {
    path: '/',
    component: resolve => require(['@/pages/home'], resolve), // 路由懒加载方式,'@/' webpack配置默认的别名,指向src/
    meta: {
      title: '首页', // 用来做head组件 "标题" 引用
      tabbar: true, // 当前页面是否需要底部导航 "tabbar" 按钮
      notBack: true // head组件是否需要 "返回" 按钮
    }
  }, {
    path: '/trip',
    component: resolve => require(['@/pages/trip'], resolve),
    meta: {
      title: '工作空间',
      tabbar: true,
      notBack: true
    }
  }, {
    path: '/me',
    component: resolve => require(['@/pages/me'], resolve),
    meta: {
      title: '个人中心',
      tabbar: true,
      notBack: true
    }
  }
]

const router = new Router({
  tabPages, // tabbar组件配置,可以通过this.$router.options.tabPages
  linkExactActiveClass: "active", // <router-link>选中的class,用来做底部tabbar选中状态样式高亮
  routes: [
    ...tabPages,
    {
      path: '/notTab',
      component: resolve => require(['@/pages/notTab'], resolve),
      meta: {
        title: 'notTabPage',
      }
    }
  ]
})
export default router

封装tabbar组件

components目录下新增wjFoot文件夹,添加wjFoot.vue、index.js

  wjHead
      wjHead.
      index.js
+ wjFoot
    + wjFoot.vue
    + index.js

wjFoot.vue

<template>
  <footer class="top-line tabbar-footer">
    <router-link v-for="(item, index) in tabPages" :key="index" :to="item.path">
      {{item.meta.title}}
    </router-link>
  </footer>
</template>

<script>
export default {
  name: 'wjFoot',
  computed: {
    tabPages () {
      return this.$router.options.tabPages // 路由配置项,这里在router配置文件中单独抽出来了底部tabbar页面
    }
  }
}
</script>

<style lang="stylus" scoped>
.tabbar-footer 
  display: flex;
  height: 50px;
  justify-content: space-between;
  position: fixed!important;
  bottom: 0;
  width: 100%;
  left: 0;
  box-sizing: border-box;
  background-color: #fff;
  >a 
    flex: 1;
    display: flex;
    font-size: 12px;
    justify-content: center;
    color: #999;
    align-items: flex-end;
    padding-bottom: 3px;
    background-repeat: no-repeat;
    background-size: 23px;
    background-position: center 5px;
    &.active 
      color: #ed8f49;
    &:nth-of-type(1)
      background-image: url('~@/assets/image/Home.png');
    &:nth-of-type(2) 
      background-image: url('~@/assets/image/Trip.png');
    &:nth-of-type(3) 
      background-image: url('~@/assets/image/Me.png');
    &.active:nth-of-type(1) 
      background-image: url('~@/assets/image/Home_active.png');
    &.active:nth-of-type(2) 
      background-image: url('~@/assets/image/Trip_active.png');
    &.active:nth-of-type(3) 
      background-image: url('~@/assets/image/Me_active.png');
</style>

index.js

// 同wjHead/index.js一样
import lsFoot from './wjFoot'

function install (Vue) {
  Vue.component(lsFoot.name, lsFoot)
}
export default { install }

main.js

Vue.use(require('@/components/wjFoot').default) // 注册全局tabbar组件

App.vue

<!--Head与Foot配置全部都在router配置中获取-->
<template>
  <div id="app">
    <wj-head v-if="title" :isBack="isBack" :title="title"></wj-head>
    <router-view></router-view>
    <wj-foot v-if="showTabbar"></wj-foot>
  </div>
</template>

<script>
export default {
  name: 'app',
  computed: {
    title () {
      return this.$route.meta.title
    },
    isBack () {
      return !this.$route.meta.notBack
    },
    showTabbar () {
      return this.$route.meta.tabbar
    }
  }
}
</script>

<style>
</style>

配置完成之后基本上页面切换效果已经出来了,我们在4个page页面单独写点数据就能看到以下效果

<!--home.vue,其他页面类似-->
<template>
  <div>
    home
    <router-link to="/notTab" tag="h1">跳转一个没有底部tabbar的页面</router-link>
  </div>
</template>

在这里的notTab页面左上角的返回键没有显示,但是我们点击的时候已经返回到首页了,因为我们需要引入iconfontCSS文件

public/index.html

<link rel="stylesheet" href="http://at.alicdn.com/t/font_1307698_e8pp8mc8yq9.css">

引入之后图标就正常显示了

安装vue-awesome-swiper轮播图插件

官方文档:surmon-china.github.io/vue-awesome…

npm文档:www.npmjs.com/package/vue…

Swiper官方文档(配置项参考):www.swiper.com.cn/api/start/n…

npm install vue-awesome-swiper

引用

这里我们直接局部组件引用,不在全局注册

main.js

import 'swiper/dist/css/swiper.css'

home.vue(新增页面nav导航)

<template>
  <div>
    <swiper :options="swiperOption" ref="mySwiper">
      <swiper-slide v-for="(src, index) in slideList" :key="index">
        <img :src="src">
      </swiper-slide>
      <div class="swiper-pagination" slot="pagination"></div>
    </swiper>
    <nav>
        <router-link :to="item.link" v-for="(item, index) in navList" :key="index">
            <div class="circular">
                <i :class="['iconfont', item.icon]"></i>
            </div>
            <p>{{item.name}}</p>
        </router-link>
    </nav>
  </div>
</template>

<script>
import { swiper, swiperSlide } from 'vue-awesome-swiper'
export default {
  name: 'online',
  data () {
    return {
      swiperOption: {
        autoplay: {
          delay: 2000
        },
        loop: true,
        pagination: {
          el: '.swiper-pagination'
        }
      },
      slideList: [
        require('@/assets/image/slide1.png'),
        require('@/assets/image/slide2.png'),
        require('@/assets/image/slide3.png'),
        require('@/assets/image/slide4.png'),
        require('@/assets/image/slide5.png')
      ], 
      navList: [
        {
            link: '/notTab',
            name: '商店',
            icon: 'icon-icon_qianbao'
        }, {
            link: '/notTab',
            name: '指南',
            icon: 'icon-icon_luru'
        }, {
            link: '/notTab',
            name: '特惠',
            icon: 'icon-icon_liwu'
        }, {
            link: '/notTab',
            name: '精选',
            icon: 'icon-icon_huangguanhuiyuan'
        }
      ]
    }
  },
  computed: {
    swiper() {
      return this.$refs.mySwiper.swiper
    }
  },
  components: {
    swiper,
    swiperSlide
  }
}
</script>

<style lang="stylus" scoped>
// 因为当前style标签属性上添加了scoped,局部组件样式,如果当前样式想影响子组件内的样式,需要加>>>样式穿透符
>>>.swiper-pagination-bullet-active
  background-color #ed8f49
.swiper-container
  height 170px
  margin 10px
  border-radius 8px
  box-shadow 0px 1px 8px 0px #ccc
  img
    height 100%
    width 100%
    object-fit cover
nav
  display flex
  padding 15px 7px
  // border-bottom 5px solid #f5f5f5
  a
    flex 1
    display flex
    flex-direction column
    justify-content center
    align-items center
    font-size 13px
    color: #666
    .circular
      height 40px
      width 40px
      border-radius 50%
      background-color red
      display flex
      justify-content center
      align-items center
      color #fff
      margin-bottom 6px
      .iconfont
        font-size 26px
    p
      color #8a8a96
    &:nth-of-type(1) .circular
      background-color #4AD1CD
    &:nth-of-type(2) .circular
      background-color #FF9393
    &:nth-of-type(3) .circular
      background-color #ff75a0
    &:nth-of-type(4) .circular
      background-color #ff9f60
</style>

最后

到这里其实就已经能看到文章开头的效果了,都是一些简单配置,能直接搭建初始vue项目,以后写项目不用过多浪费时间在搭建项目了,当然这只是简单的Vue项目,如果我们遇到复杂一点的,也能单独抽出来,日积月累,增强开发效率,有更多的时间用来专注项目业务逻辑处理。

如果有哪里写的不好的,希望大家多多指点,没有自己从零开始搭建过vue项目的同学,可以自己动手尝试一下,实践出真知,有不懂的也可以下方留言,看到会第一时间解答。