起因
因为vue的使用广泛,经常开发Vue项目,发现每次初始化Vue项目都做了很多重复的事情,为了避免重复造轮子,故而自己写一篇用Vue-cli@3.x脚手架从零搭建vue项目的文章,对于组件封装方面有设计不好的地方希望大家多多指点。
最终效果
github地址:猛戳这里
话不多说,直接动手
安装Vue-cli
npm install -g @vue/cli
// 为了能直接在CMD窗口中使用Vue命令创建项目,需要全局安装Vue-cli(-g是全局的意思)
创建项目
Vue create vue-template(vue项目名)
在指定项目文件夹内打开窗口,输入该命令,直接按下回车,这里我选用默认的babel与eslint配置
将CMD窗口切换到vue-template目录下,运行项目
npm run serve
项目跑起来之后我们需要安装vue-router实现页面的跳转
安装并初步配置vue-router
安装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模式
- src目录下新增views文件夹,里面有2个vue组件
- src目录下新增router.js配置文件,并且引入了views下的两个组件
- main.js引入了router.js并挂载在vue配置当中
- ...自行研究,不做过多介绍,相当于省了我们直接用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
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轮播图插件
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项目的同学,可以自己动手尝试一下,实践出真知,有不懂的也可以下方留言,看到会第一时间解答。