18. vue-router案例-tabBar导航
目标: 做一个导航tabbar
一. 分析
我们的目标是做一个导航tabbar, 要求
- 这个导航不仅可以在一个页面使用, 可以在多个页面通用
- 每个页面的样式可能不一样
- 每个页面的图标, 文字可能不一样
- 每个页面导航的个数可能不一样
要想实现上面的情况, 需要进行功能拆解:
- 提炼出一个通用的tabBar, 然后在里面定义插槽, 根据需要放入tabBarItem,
- tabBarItem里面包含图片, 文字. 图片和文字也是插槽, 不同的tabBarItem显示的图片的文字都有可能不同.
- tabBarItem的数据结构需要定义为一个json, 指定跳转的url
二. 框架实现
1. 通常我们如何实现?
第一步: 在App.vue中定义一段HTML, 外层div的id是tabBar, 内层div的class标签属性是tabBarItem.
<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样式.
通常body样式, 我们将其单独定义到main.css文件中. 放在assets目录下
body {
margin: 0px;
padding: 0px;
}
定义好了main.css文件, 需要将其引入到App.vue文件中
<style>
@import "./assets/main.css";
</style>
引入css文件样式使用的是@import '文件路径', 而引入js文件使用的是import '文件路径'
第三步: 定义tabBar样式
tabBar采用的是Flex弹性布局的布局方式.
#tabBar {
display: flex;
}
.tabBarItem {
flex: 1;
text-align: center;
}
上面这段代码就指定了tabBar采用的布局方式. 它会根据子元素的个数进行弹性布局. 在子元素中我们设置每个元素的flex: 1
表示的是在空间可用的情况下, 平均分配空间.
第四步: 定义其他样式
<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>
这里面除了有布局样式,还有底部对齐, 导航的阴影等等.
总结: 这样的导航不通用, 如果我们要复用, 需要拷贝html内容, 还要拷贝css样式. 我们可以把公共部分提成一个组件
2. 抽象tabBarItem组件
第一步: 在components中新建一个组件tabBarItem
这个提取比较简单, 就是将我们刚刚在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的可复用性就更强了.
3. 完善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参数
第二步: 创建router文件夹, 并创建index.js文件
// 第一步: 引入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
第三步: 在main.js文件中引入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'),
}
},
- extensions: 表示引入路径的时候可以省略扩展名
- alias: 表示给路径起一个别名. resolve('src')的含义是给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就可以了
在使用的时候, 也分为几种场景
- 使用import引入组件中的路径
- 没有import, 比如图片路径
- 在路由导航中的import 路径
1. 使用import引入组件
我们在App.vue中引入了MainTabBar
之前我们引入脚本是这么写的:
import MainTabBar from "./components/tabBar/MainTabBar";
现在我们配置了路径别名以后, 可以省去前面的./, 直接以components开头
import MainTabBar from "components/tabBar/MainTabBar";
在style样式引用中同样有效
<style>
@import "assets/main.css";
</style>
我们直接将./省略.
2. 在图片等非import中引入
比如我们在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, 但是需要在前面加一个~符合.
3. 路由中的路径
到目前为止, 我发现在路由中引入组件, 不能使用别名, 但是可以使用@符号来代表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以后, 要重新启动服务才行. 否则不生效
18. vue-router案例-tabBar导航的更多相关文章
- [Vue 牛刀小试]:第十四章 - 编程式导航与实现组件与 Vue Router 之间的解耦
一.前言 在上一章的学习中,通过举例说明,我们了解了 Vue Router 中命名路由.命名视图的使用方法,以及如何通过 query 查询参数传参,或者是采用 param 传参的方式实现路由间的参数传 ...
- Vue Router的导航解析过程
在我没读官方的vue router文档之前,我怎么也没想到路由的解析过程竟然有12步. 12步如下: 导航被触发. 在失活的组件里调用离开守卫beforeRouteLeave . 调用全局的 befo ...
- Vue Router 路由守卫:完整的导航解析流程
完整的导航解析流程 1 导航被触发. 2 在失活的组件里调用离开守卫. 3 调用全局的 beforeEach 守卫. 4 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+). ...
- Vue Router详细教程
1.什么是路由 1.1路由简介 说起路由你想起了什么?路由是一个网络工程里面的术语. 路由(routing)就是通过互联的网络把信息从源地址传输到目的地址的活动. --- 维基百科 额,啥玩意? 没听 ...
- 七. Vue Router详解
1. 认识路由 1.1 路由概念 路由是什么? 路由是一个网络工程里面的术语. 路由(routing)就是通过互联的网络把信息从源地址传输到目的地址的活动 --- 维基百科 路由器提供了两种机制:路由 ...
- python 全栈开发,Day91(Vue实例的生命周期,组件间通信之中央事件总线bus,Vue Router,vue-cli 工具)
昨日内容回顾 0. 组件注意事项!!! data属性必须是一个函数! 1. 注册全局组件 Vue.component('组件名',{ template: `` }) var app = new Vue ...
- Vue Router路由守卫妙用:异步获取数据成功后再进行路由跳转并传递数据,失败则不进行跳转
问题引入 试想这样一个业务场景: 在用户输入数据,点击提交按钮后,这时发起了ajax请求,如果请求成功, 则跳转到详情页面并展示详情数据,失败则不跳转到详情页面,只是在当前页面给出错误消息. 难点所在 ...
- Vue 2.0 + Vue Router + Vuex
用 Vue.js 2.x 与相配套的 Vue Router.Vuex 搭建了一个最基本的后台管理系统的骨架. 当然先要安装 node.js(包括了 npm).vue-cli 项目结构如图所示: ass ...
- Vue.js 2.x笔记:路由Vue Router(6)
1. Vue Router简介与安装 1.1 Vue Router简介 Vue Router 是 Vue.js 官方的路由管理器.它和 Vue.js 的核心深度集成,构建单页面应用. Vue Rout ...
随机推荐
- how to convert SVG shapes to polygon
how to convert SVG shapes to polygon 如何将 svg 的 rect 转换成 polygon rect.circle.ellipse.line.polyline.po ...
- NGK公链:夯实基础设施 实现产业大规模应用
当前,区块链已经成为全球技术角逐的前沿,大国及科技巨头竞相在该领域布局,引导区块链服务实体经济,激发市场经济活力.据市场相关研究机构预测,2020年,基于区块链的业务将达到1000亿美元. 对于区块链 ...
- 随手一记,关于Java日期时间格式化
在Java中,我们大多数情况下格式化日期都是使用simpleDateFormat,比如把一个日期格式化成:2019-12-31的形式,我们一般定义模板为:yyyy-MM-dd的形式. 我们需要注意的是 ...
- Jmeter beanshell编程实例
1.引言 BeanShell是一种小型的,免费的,可嵌入的符合Java语法规范的源代码解释器,具有对象脚本语言特性. 在Jmeter实践中,由于BeanShell组件较高的自由度,通常被用来处理较为复 ...
- c#winform主题实现的一个方法
winform的主题实现没有bs里面那么舒服,下面做了一个简单实现,记录一下. 1.一个接口,需要做主题的控件.窗体都要实现这个接口 /// <summary> /// 使用主题的控件.窗 ...
- ElementUI使用总结
首先声明,我这总结的官网都有,只是将自己使用时遇到的问题,重新记录一下,官网地址:https://element.eleme.cn/ 1.表格内指定行数给定不同样式(类似于隔行变色,也能叫指定行数不同 ...
- 将日志发送到log日志文件中
log.debug("toUser:"+toUser+",subject:"+subject+",content:"+content);
- 微信小程序:解决小程序中有些格式如webpiPhone手机暂不支持的问题
问题:小程序中有些格式是iPhone手机暂不支持的,如goods_introduce中的webp格式,在小程序的模拟器中是可以正常显示webp格式的,但是一旦你做真机调试,很可能某些iPhone手机是 ...
- clipse中mybatis的xml配置文件代码提示
编写mybatis的xml文件时,没有代码提示会很麻烦,是有解决办法的: 按下图打开 点击右上角的Add按钮,添加配置,配置如下,添加后点击OK: Location:http://mybatis.or ...
- Vim的基本命令
Vi vi的两种模式 ①commad命令模式:无法输入任何东西,需要按下i进入编辑模式 ②edit编辑模式:按下esc退出到命令模式,在命令模式下按下wq [文件名] 可以退出并且成功的保存 //一些 ...