目标: 作一个导航tabbarjavascript
咱们的目标是作一个导航tabbar, 要求css
要想实现上面的状况, 须要进行功能拆解:html
<template> <div id="app"> <div id="tabBar"> <div class="tabBarItem">首页</div> <div class="tabBarItem">分类</div> <div class="tabBarItem">购物车</div> <div class="tabBarItem">个人</div> </div> </div> </template>
一般body样式, 咱们将其单独定义到main.css文件中. 放在assets目录下vue
body { margin: 0px; padding: 0px; }
定义好了main.css文件, 须要将其引入到App.vue文件中java
<style> @import "./assets/main.css"; </style>
引入css文件样式使用的是@import '文件路径', 而引入js文件使用的是import '文件路径'webpack
tabBar采用的是Flex弹性布局的布局方式.web
#tabBar { display: flex; } .tabBarItem { flex: 1; text-align: center; }
上面这段代码就指定了tabBar采用的布局方式. 它会根据子元素的个数进行弹性布局. 在子元素中咱们设置每一个元素的flex: 1
表示的是在空间可用的状况下, 平均分配空间.vue-router
<style> @import "./assets/main.css"; #tabBar { display: flex; background-color: #e6e6e6; position: fixed; left: 0; right: 0; bottom: 0; box-shadow: 0 -3px 1px darkgrey; } .tabBarItem { flex: 1; text-align: center; } </style>
这里面除了有布局样式,还有底部对齐, 导航的阴影等等.npm
总结: 这样的导航不通用, 若是咱们要复用, 须要拷贝html内容, 还要拷贝css样式. 咱们能够把公共部分提成一个组件json
这个提取比较简单, 就是将咱们刚刚在App.vue中的功能提取出一个单独的组件
<template> <div id="tabBar"> <div class="tabBarItem">首页</div> <div class="tabBarItem">分类</div> <div class="tabBarItem">购物车</div> <div class="tabBarItem">个人</div> </div> </template> <script> export default { name: "tabBarItem" } </script> <style scoped> #tabBar { display: flex; background-color: #e6e6e6; position: fixed; left: 0; right: 0; bottom: 0; box-shadow: 0 -3px 1px darkgrey; } .tabBarItem { flex: 1; text-align: center; } </style>
而后, 在App.vue中引入组件
<script> import TabBar from "./components/TabBar" export default { name: 'App', components: { TabBar } } </script>
vue里面, 可使用组件的简称调用组件, 以下所示:
<div id="app"> <tab-bar></tab-bar> </div>
这样, tabBarItem的可复用性就更强了.
咱们知道tabBar除了有图片, 还有文字. 当咱们鼠标点击的时候还有对应的图片或者蚊子样式的变化.
下面咱们来实现, 改变图片和文字.
第一步: 在tabBarItem中放两张图片, 一张是未点击的, 另外一张是点击后的图片. 图片自备, 什么图均可以
<div class="tabBarItem"> <slot name="item-pic"></slot> <slot name="item-pic-active"></slot> <div ><slot name="item-name"></slot></div> </div>
如上代码: 比以前多了一个slot, 用来存放第二张图片的.
在调用
<tab-bar-item> <img slot="item-pic" src="../../assets/img/tabBar/1.jpeg"> <img slot="item-pic-active" src="../../assets/img/tabBar/4.jpeg"> <div slot="item-name">首页</div> </tab-bar-item>
这里就传了两张图片, 并指定每张图片的插槽位置
而后咱们来看看效果:
效果出来了,达到预期. 但咱们但愿:鼠标不点击,显示图一; 鼠标点击, 显示图二.
这个容易实现, 使用一个isActive变量便可
修改TabBarItem组件
<template> <div class="tabBarItem"> <div v-if="!isActive"><slot name="item-pic"></slot></div> <div v-else><slot name="item-pic-active"></slot></div> <div><slot name="item-name"></slot></div> </div> </template> <script> export default { name: "TabBarItem", data() { return { isActive: false } } } </script>
在组件脚本中定义一个变量isActive, 而后对插槽使用v-if便可实现效果. 注意v-if和v-else的写法.
这里咱们有一个约定,一般不在插槽的里面写v-if或者v-else, 由于这部份内容后面会被替换掉. 因此, 在外层包一个div
下面来看看效果:
当咱们设置isActive:false, 效果以下
当咱们设置isActive:true, 效果以下:
能够看出, 我这里面就是把四张图片调换了一下顺序. 具体什么图片不重要, 重要的是效果出来了就好.
这个就更简单了.
<template> <div class="tabBarItem"> <div v-if="!isActive"><slot name="item-pic"></slot></div> <div v-else><slot name="item-pic-active"></slot></div> <div v-bind:class="{active:isActive}"><slot name="item-name"></slot></div> </div> </template> <style scoped> ...... .active { color: red; } ...... </style>
直接绑定一个class样式, 当文字被激活时, 也就是isActive:true的时候, 文字显示红色
来看看效果:
以上就实现了tabBarItem的封装
如今tabBar导航已经完成了, 接下来, 咱们点击首页, 应该展现首页组件内容. 点击分类应该展现分类组件内容.下面咱们来具体实现这部分功能. 这就是导航的路由功能.
npm install vue-router --save
vue-router是一个运行时依赖, 因此须要加上--save参数
// 第一步: 引入vue 和 vue-router包 import Vue from 'vue' import Router from 'vue-router' // 第二步: 安装Vue-router插件 Vue.user(Router) // 第三步: 建立Router组件 const route = [{ }] const vueRouter = new Router({ route: route }) // 第四步: 导出Route组件 export default vueRouter
import Vue from 'vue' import App from './App' import router from './router' Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', router, render: h => h(App) })
一般, 咱们在components目录下放的都是全部公共组件模块. 那么页面相关的模块, 咱们会在单首创建一个文件夹, 文件夹的名字能够叫views或者pages或者其余, 业务相关的页面都放着这个文件夹里面. 咱们的项目目录结构以下:
咱们在views下面又建立了4个目录, 分别来存放每个导航组件的路由内容.
接下来要为导航配置路由
// 先引入组件 const Home = () => import('../views/home/Home') const Cart = () => import('../views/cart/Cart') const Category = () => import('../views/category/category') const Profile = () => import('../views/profile/profile') // 而后配置路由 const routes = [{ path: "", redirect: "/home" },{ path: "/home", components: Home },{ path: "/category", components: Category },{ path: "/cart", components: Cart },{ path: "/profile", components: Profile }] const router = new VueRouter({ routes })
这里配置了4个路由, 分别路由到对应的组件, 当空路由的时候, 定位到/home路由.
路由配好了, 接下来为按钮配置点击事件.
咱们的tabBarItem组件封装之后是这样
<template> <div class="tabBarItem" v-on:click="clickItem"> <div v-if="!isActive"><slot name="item-pic"></slot></div> <div v-else><slot name="item-pic-active"></slot></div> <div v-bind:class="{active:isActive}"><slot name="item-name"></slot></div> </div> </template>
咱们在组件级别上增长一个点击事件, 但跳转的url是不固定的, 咱们要经过参数传过来.
v-on:click="clickItem"
组件间传递数据使用props属性
<script> export default { name: "TabBarItem", props:["path"], data() { return { isActive: false } }, methods: { clickItem() { this.$router.push(path); } } } </script>
在组件中定义一个props属性, 使用itemUrl进行接收. 而后在组件调用的地方传递过来参数就能够了,
在TabBar中增长参数item-url="/home", 其余三个组件调用方式相同.
<tab-bar-item path="/home" > <img slot="item-pic" src="../../assets/img/tabBar/1.jpeg"> <img slot="item-pic-active" src="../../assets/img/tabBar/4.jpeg"> <div slot="item-name">首页</div> </tab-bar-item>
最后在App.vue中增长组件展现区域
<template> <div id="app"> <router-view></router-view> <tab-bar></tab-bar> </div> </template>
来看看效果
<script> export default { name: "TabBarItem", props:["path"], computed: { isActive(){ return this.$route.path.indexOf(this.path) !== -1 } }, data() { return { // isActive: false } }, methods: { clickItem() { this.$router.push(this.path); } } } </script>
增长一个计算属性, 当路由和当前跳转路由的路径一致时,处于选中状态, 不然处于未选中状态. 效果如图:
如今, 咱们设置了当导航激活的时候, 文字显示红色, 可是...并非全部的导航激活的时候都是红色, 因此,咱们须要将其动态设置. 也就是, 经过组件从调用方传递一个参数过来.以下所示:
<tab-bar-item path="/home" activeStyle="blue"> <img slot="item-pic" src="../../assets/img/tabBar/1.jpeg"> <img slot="item-pic-active" src="../../assets/img/tabBar/4.jpeg"> <div slot="item-name">首页</div> </tab-bar-item>
增长一个属性activeStyle="blue", 对应的, 咱们须要在组件定义的位置增长一个prop属性
props: { path: String, activeStyle: { type: String, default: 'red' } },
在prop属性中增长 activeStyle的样式, 而且设置了默认样式red.
computed: { isActive(){ return this.$route.path.indexOf(this.path) !== -1 }, isActiveStyle() { return this.isActive ? {color: this.activeStyle}:{} } },
在计算属性中增长 一个属性isActiveStyle, 若是当前导航处于激活状态, 则显示样式, 不然没有任何样式
来看看效果, 主要注意文字颜色 变化:
咱们来看看App.vue文件
<template> <div id="app"> <router-view></router-view> <tab-bar> <tab-bar-item path="/home" activeStyle="blue"> <img slot="item-pic" src="./assets/img/tabBar/1.jpeg"> <img slot="item-pic-active" src="./assets/img/tabBar/4.jpeg"> <div slot="item-name">首页</div> </tab-bar-item> <tab-bar-item path="/category" activeStyle="green"> <img slot="item-pic" src="./assets/img/tabBar/2.jpeg"> <img slot="item-pic-active" src="./assets/img/tabBar/3.jpeg"> <div slot="item-name">分类</div> </tab-bar-item> <tab-bar-item path="/cart" activeStyle="pink"> <img slot="item-pic" src="./assets/img/tabBar/3.jpeg"> <img slot="item-pic-active" src="./assets/img/tabBar/2.jpeg"> <div slot="item-name">购物车</div> </tab-bar-item> <tab-bar-item path="/profile" activeStyle="purple"> <img slot="item-pic" src="./assets/img/tabBar/4.jpeg"> <img slot="item-pic-active" src="./assets/img/tabBar/1.jpeg"> <div slot="item-name">个人</div> </tab-bar-item> </tab-bar> </div> </template> <script> import TabBar from "./components/tabBar/TabBar" import TabBarItem from "./components/tabBar/TabBarItem"; export default { name: 'App', components: { TabBar, TabBarItem } } </script> <style> @import "./assets/main.css"; </style>
在模板的部分, 内容特别多, 一般App.vue的内容是很简洁的, 因此, 咱们还能够将这部分组件进行抽象
将文件抽取到MainTabBar中, 抽取之后注意图片文件以及vue组件的路径
<template> <tab-bar> <tab-bar-item path="/home" activeStyle="blue"> <img slot="item-pic" src="../../assets/img/tabBar/1.jpeg"> <img slot="item-pic-active" src="../../assets/img/tabBar/4.jpeg"> <div slot="item-name">首页</div> </tab-bar-item> <tab-bar-item path="/category" activeStyle="green"> <img slot="item-pic" src="../../assets/img/tabBar/2.jpeg"> <img slot="item-pic-active" src="../../assets/img/tabBar/3.jpeg"> <div slot="item-name">分类</div> </tab-bar-item> <tab-bar-item path="/cart" activeStyle="pink"> <img slot="item-pic" src="../../assets/img/tabBar/3.jpeg"> <img slot="item-pic-active" src="../../assets/img/tabBar/2.jpeg"> <div slot="item-name">购物车</div> </tab-bar-item> <tab-bar-item path="/profile" activeStyle="purple"> <img slot="item-pic" src="../../assets/img/tabBar/4.jpeg"> <img slot="item-pic-active" src="../../assets/img/tabBar/1.jpeg"> <div slot="item-name">个人</div> </tab-bar-item> </tab-bar> </template> <script> import TabBar from "./TabBar"; import TabBarItem from "./TabBarItem"; export default { name: "MainTabBar", components: { TabBar, TabBarItem } } </script> <style scoped> </style>
而后, 在App.vue中引入MainTabBar就能够了
<template> <div id="app"> <router-view></router-view> <main-tab-bar></main-tab-bar> </div> </template>
效果和原来是同样
当咱们的路径不少的时候, 好比上面咱们抽象组件时候, 就发现, 文件位置换了, 不少路径都要跟着变. 在vue中能够设置路径的别名, 这样咱们就不用在更换了文件位置之后更换路径了.
在build/webpack.base.conf.js文件中, 有一个resolve选项
resolve: { extensions: ['.js', '.vue', '.json'], alias: { '@': resolve('src'), } },
resolve: { extensions: ['.js', '.vue', '.json'], alias: { '@': resolve('src'), 'components': resolve('@/components'), 'assets': resolve('@/assets'), 'router': resolve('@/router'), 'views': resolve('@/views') } },
起别名的时候, 好比src/components, 路径就可使用@/components.
后面使用到这个路径的文件, 直接使用@/components就能够了
在使用的时候, 也分为几种场景
咱们在App.vue中引入了MainTabBar
以前咱们引入脚本是这么写的:
import MainTabBar from "./components/tabBar/MainTabBar";
如今咱们配置了路径别名之后, 能够省去前面的./, 直接以components开头
import MainTabBar from "components/tabBar/MainTabBar";
在style样式引用中一样有效
<style> @import "assets/main.css"; </style>
咱们直接将./省略.
好比咱们在MainTabBar.vue组件中设置导航图标的时候, 有不少的src, 以前咱们都是这么写的
<tab-bar-item path="/profile" activeStyle="purple"> <img slot="item-pic" src="../../assets/img/tabBar/4.jpeg"> <img slot="item-pic-active" src="../../assets/img/tabBar/1.jpeg"> <div slot="item-name">个人</div>
在定义了路由别名之后, 咱们可使用以下写法:
<tab-bar-item path="/home" activeStyle="blue"> <img slot="item-pic" src="~assets/img/tabBar/1.jpeg"> <img slot="item-pic-active" src="~assets/img/tabBar/4.jpeg"> <div slot="item-name">首页</div> </tab-bar-item>
也就是使用别名assets, 可是须要在前面加一个~符合.
到目前为止, 我发如今路由中引入组件, 不能使用别名, 可是可使用@符号来表明src
//const Home = () => import('@/views/home/Home') import Home from '@/views/home/Home'; const Cart = () => import('@/views/cart/Cart') const Category = () => import('@/views/category/category') const Profile = () => import('@/views/profile/profile')
一旦使用别名, 就会报错. 不管是到仍是不带都不行. 还须要继续探究
这也是一个问题
注意: 当咱们修改了配置文件webpack.base.conf.js之后, 要从新启动服务才行. 不然不生效