1、tabbar案例01
进行了简单的布局
- 1、创建一个样式文件base.css
body{margin:0; padding: 0;}
- 2、修改App.vue
<template>
<div id="app">
<div id="tab-bar">
<div class="tab-bar-item">首页</div>
<div class="tab-bar-item">分类</div>
<div class="tab-bar-item">购物车</div>
<div class="tab-bar-item">我的</div>
</div>
</div>
</template>
<script>
export default {
name: 'App',
}
</script>
<style>
/*引入样式文件*/
@import url('./assets/style/base');
#tab-bar{display: flex; background-color: #f6f6f6; position: fixed; left:0; right: 0; bottom: 0; box-shadow: 0 -1px 1px rgba(100,100,100,0.3);}
.tab-bar-item{flex: 1; text-align: center; height: 49px;}
</style>
2、tabbar案例02
这一讲主要是对组件各种封装和嵌套,其中嵌套使用插槽的方式进行多重嵌套
模板层面(从大到小层面)
App.vue—>TabBar.vue—>TabBarItem
代码层面层面(从小到大层面)
TabBarItem.vue—>TabBar.vue—>App.vue
1、TabBarItem.vue
在tab-bar-item放入两个具名插槽(包含name属性)<slot name=“item-icon”>和<slot name=“item-text”>
<template>
<div class="tab-bar-item">
<slot name="item-icon"></slot>
<slot name="item-text"></slot>
</div>
</template>
2、TabBar.vue
在TabBar中放入一个不具名插槽
<template>
<div id="tab-bar">
<slot></slot>
</div>
</template>
2、App.vue
在最上层的模板中匹配所有插槽,注意,嵌套的div标签名是跟随id和class来的
<template>
<div id="app">
<tab-bar>
<tab-bar-item>
<img slot="item-icon" src="./assets/images/tabbar/home.svg"/>
<div slot="item-text" >首页</div>
</tab-bar-item>
<tab-bar-item>
<img slot="item-icon" src="./assets/images/tabbar/category.svg"/>
<div slot="item-text">分类</div>
</tab-bar-item>
<tab-bar-item>
<img slot="item-icon" src="./assets/images/tabbar/shopcart.svg"/>
<div slot="item-text">购物车</div>
</tab-bar-item>
<tab-bar-item>
<img slot="item-icon" src="./assets/images/tabbar/profile.svg"/>
<div slot="item-text">我的</div>
</tab-bar-item>
</tab-bar>
</div>
</template>
-
1、拷贝各种资源图标
将资源图标放在assets文件夹下的相关目录中 -
2、创建最底层的组件页面TabBarItem.vue
在页面中同时设计插槽内插入标签的样式(实际插槽内的具体div标签都是写好的,等最顶层App.vue文件创建具体标签的时候,再拷贝过去就行)
TabBarItem.vue
<template>
<div class="tab-bar-item">
<slot name="item-icon"></slot>
<slot name="item-text"></slot>
</div>
</template>
<script>
export default {
name:'TabBarItem'
}
</script>
<style>
.tab-bar-item{flex: 1; text-align: center; height: 49px; font-size: 14px;}
.tab-bar-item img{ width: 24px; height: 24px; vertical-align: middle; margin-top: 2px;}
</style>
- 2、创建中间层的组件页面TabBar.vue
在这里放入一个插槽,用来防止整个导航条的插槽
TabBar.vue
<template>
<div id="tab-bar">
<slot></slot>
</div>
</template>
<script>
export default {
name:'TabBar'
}
</script>
<style>
#tab-bar{display: flex; background-color: #f6f6f6; position: fixed; left:0; right: 0; bottom: 0; box-shadow: 0 -1px 1px rgba(100,100,100,0.3);}
</style>
- 3、修改最顶层组件页面App.vue
在这一层,要匹配所有插槽
App.vue
<template>
<div id="app">
<tab-bar>
<tab-bar-item>
<img slot="item-icon" src="./assets/images/tabbar/home.svg"/>
<div slot="item-text" >首页</div>
</tab-bar-item>
<tab-bar-item>
<img slot="item-icon" src="./assets/images/tabbar/category.svg"/>
<div slot="item-text">分类</div>
</tab-bar-item>
<tab-bar-item>
<img slot="item-icon" src="./assets/images/tabbar/shopcart.svg"/>
<div slot="item-text">购物车</div>
</tab-bar-item>
<tab-bar-item>
<img slot="item-icon" src="./assets/images/tabbar/profile.svg"/>
<div slot="item-text">我的</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 url('./assets/style/base');
</style>
- 4、文件目录
- 4、本集效果
3、tabbar案例03插槽内容动态显示
- 1、在app.vue中给图片多添加一个备选标签img。用于动态(根据标签值匹配)匹配插槽
<template>
<div id="app">
<tab-bar>
<tab-bar-item>
<img slot="item-icon" src="./assets/images/tabbar/home.svg"/>
<img slot="item-icon-active" src="./assets/images/tabbar/homeactive.svg"/>
<div slot="item-text" >首页</div>
</tab-bar-item>
<tab-bar-item>
<img slot="item-icon" src="./assets/images/tabbar/category.svg"/>
<img slot="item-icon-active" src="./assets/images/tabbar/categoryactive.svg"/>
<div slot="item-text">分类</div>
</tab-bar-item>
<tab-bar-item>
<img slot="item-icon" src="./assets/images/tabbar/shopcart.svg"/>
<img slot="item-icon-active" src="./assets/images/tabbar/shopcartactive.svg"/>
<div slot="item-text">购物车</div>
</tab-bar-item>
<tab-bar-item>
<img slot="item-icon" src="./assets/images/tabbar/profile.svg"/>
<img slot="item-icon-active" src="./assets/images/tabbar/profileactive.svg"/>
<div slot="item-text">我的</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 url('./assets/style/base');
</style>
- 2、在TableBarItem.vue修改data和模板代码
数据部分
<script>
export default {
name:'TabBarItem',
data(){
return{
isActive:true
}
}
}
</script>
模板部分
添加一个插槽,同时由于动态显示的需要,一定要把插槽上层嵌套一个div
此外绑定class属性使用对象形式 :class="{ative:isActive}",这里类名为ative:isActive
<template>
<div class="tab-bar-item">
<div v-if="!isActive" >
<slot name="item-icon"></slot>
</div>
<div v-else>
<slot name="item-icon-active"></slot>
</div>
<div :class="{ative:isActive}">
<slot slot name="item-text"></slot>
</div>
</div>
</template>
完整代码
<template>
<div class="tab-bar-item">
<div v-if="!isActive" >
<slot name="item-icon"></slot>
</div>
<div v-else>
<slot name="item-icon-active"></slot>
</div>
<div :class="{ative:isActive}">
<slot slot name="item-text"></slot>
</div>
</div>
</template>
<script>
export default {
name:'TabBarItem',
data(){
return{
isActive:true
}
}
}
</script>
<style>
.tab-bar-item{flex: 1; text-align: center; height: 49px; font-size: 14px;}
.tab-bar-item img{ width: 24px; height: 24px; vertical-align: middle; margin-top: 2px;}
.ative{
color:blue
}
</style>
效果:
4、tabbar案例04
在各个导航上添加导航跳转动作,通过路由跳转到不同的页面
- 1、创建不同的页面
在src目录中单独创建views目录,并在views目录下创建不同的页面(.vue)
<template>
<h2>分类</h2>
</template>
<script>
export default {
name:'Category'
}
</script>
<style scoped>
</style>
- 2、在路由index.js中导入路由信息
首先要导入路由
其次在设置路由映射
router/index.js
import Vue from 'vue'
import Router from 'vue-router'
//导入组件,使用懒加载的方式
const Home = () => import('../views/home/Home.vue');
const Category = () => import('../views/category/Category.vue');
const Profile = () => import('../views/profile/Profile');
const Shopcart = () => import('../views/shopcart/Shopcart');
//使用路由对象
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
redirect:'/Home'
},
{
path: '/home',
component:Home
},
{
path: '/category',
component:Category
},
{
path: '/profile',
component:Profile
},
{
path: '/shopcart',
component:Shopcart
}
],
//设置路由显示模式,history直接显示路径,哈希模式会显示一个#
mode:'history'
})
- 3、在TabeBarItem.vue中设置props用来向子组件传值
//创建一个父组件向子组件传值的props
props:{
//指定一个字符串型的link属性
path:String
},
在模板代码中添加跳转方法
<div class="tab-bar-item" @click="itemClick">
<script>
export default {
name:'TabBarItem',
//创建一个父组件向子组件传值的props
props:{
//指定一个字符串型的link属性
path:String
},
data(){
return{
isActive:true,
}
},
methods:{
itemClick(){
//路由跳转
this.$router.replace(this.path)
}
}
}
</script>
完整代码
<template>
<div class="tab-bar-item" @click="itemClick">
<div v-if="!isActive" >
<slot name="item-icon"></slot>
</div>
<div v-else>
<slot name="item-icon-active"></slot>
</div>
<div :class="{ative:isActive}">
<slot slot name="item-text"></slot>
</div>
</div>
</template>
<script>
export default {
name:'TabBarItem',
//创建一个父组件向子组件传值的props
props:{
//指定一个字符串型的link属性
path:String
},
data(){
return{
isActive:true,
}
},
methods:{
itemClick(){
//解决两次点击报错问题,连续点击导致当前路由和点击的路由相同,导致重复替换
if(this.$route.path==this.path){
return
}
//路由跳转
this.$router.replace(this.path)
}
}
}
</script>
<style>
.tab-bar-item{flex: 1; text-align: center; height: 49px; font-size: 14px;}
.tab-bar-item img{ width: 24px; height: 24px; vertical-align: middle; margin-top: 2px;}
.ative{
color:blue
}
</style>
- 3、在App.vue总组件中为每个item设置path
这里的path值是要向props传递的值
<tab-bar-item path="/home">
完整代码
<template>
<div id="app">
<tab-bar>
<tab-bar-item path="/home">
<img slot="item-icon" src="./assets/images/tabbar/home.svg"/>
<img slot="item-icon-active" src="./assets/images/tabbar/homeactive.svg"/>
<div slot="item-text" >首页</div>
</tab-bar-item>
<tab-bar-item path="/category">
<img slot="item-icon" src="./assets/images/tabbar/category.svg"/>
<img slot="item-icon-active" src="./assets/images/tabbar/categoryactive.svg"/>
<div slot="item-text">分类</div>
</tab-bar-item>
<tab-bar-item path="/shopcart">
<img slot="item-icon" src="./assets/images/tabbar/shopcart.svg"/>
<img slot="item-icon-active" src="./assets/images/tabbar/shopcartactive.svg"/>
<div slot="item-text">购物车</div>
</tab-bar-item>
<tab-bar-item path="/profile">
<img slot="item-icon" src="./assets/images/tabbar/profile.svg"/>
<img slot="item-icon-active" src="./assets/images/tabbar/profileactive.svg"/>
<div slot="item-text">我的</div>
</tab-bar-item>
</tab-bar>
<router-view></router-view>
</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 url('./assets/style/base');
</style>
5、tabbar案例05解决不同状态下不同导航图标样式的问题
需求1:不同样式不同文字颜色
需求2:动态封装样式
- 1、在文字颜色上使用动态样式绑定
使用计算属性(activeStyle)动态绑定文本的样式
<div :style="activeStyle">
<slot slot name="item-text"></slot>
</div>
- 2、添加新的props属性
props:{
//指定一个字符串型的link属性
path:String,
activeColor:{
type:String,
default:'blue'
}
},
- 3、添加新的计算属性activeStyle**
activeColor是通过app.vue的props传过来的
computed:{
isActive(){
//如果当前path在路由映射表内,则返回true(隐式返回),不在数组里,indexOf会返回-1,这是indexOf函数
return this.$route.path.indexOf(this.path)!==-1
},
activeStyle(){
//根据isActive计算属性的值,返回不同的color值
return this.isActive?{color:this.activeColor}:{}
}
},
- 4、原有.active的演示文件不用了
/*
//由于在TabeBarItem文件里直接设置activColor,则不需要这个样式了
.ative{
color:blue
}
*/
完整TabBarItem.vue文件
<template>
<div class="tab-bar-item" @click="itemClick">
<div v-if="!isActive" >
<slot name="item-icon"></slot>
</div>
<div v-else>
<slot name="item-icon-active"></slot>
</div>
<div :style="activeStyle">
<slot slot name="item-text"></slot>
</div>
</div>
</template>
<script>
export default {
name:'TabBarItem',
//创建一个父组件向子组件传值的props
props:{
//指定一个字符串型的link属性
path:String,
activeColor:{
type:String,
default:'blue'
}
},
data(){
return{
//isActive:true,
}
},
computed:{
isActive(){
//如果当前path在路由映射表内,则返回true(隐式返回),不在数组里,indexOf会返回-1,这是indexOf函数
return this.$route.path.indexOf(this.path)!==-1
},
activeStyle(){
//根据isActive计算属性的值,返回不同的color值
return this.isActive?{color:this.activeColor}:{}
}
},
methods:{
itemClick(){
//解决两次点击报错问题
if(this.$route.path==this.path){
return
}
//路由跳转
this.$router.replace(this.path)
}
}
}
</script>
<style>
.tab-bar-item{flex: 1; text-align: center; height: 49px; font-size: 14px;}
.tab-bar-item img{ width: 24px; height: 24px; vertical-align: middle; margin-top: 2px;}
/*
//由于在TabeBarItem文件里直接设置activColor,则不需要这个样式了
.ative{
color:blue
}
*/
</style>
- 5、App.vue模板中,把props的值传过去
<tab-bar-item path="/home" activeColor="blue">
<img slot="item-icon" src="./assets/images/tabbar/home.svg"/>
<img slot="item-icon-active" src="./assets/images/tabbar/homeactive.svg"/>
<div slot="item-text" >首页</div>
</tab-bar-item>
完整代码:
<template>
<div id="app">
<tab-bar>
<tab-bar-item path="/home" activeColor="blue">
<img slot="item-icon" src="./assets/images/tabbar/home.svg"/>
<img slot="item-icon-active" src="./assets/images/tabbar/homeactive.svg"/>
<div slot="item-text" >首页</div>
</tab-bar-item>
<tab-bar-item path="/category" activeColor="blue">
<img slot="item-icon" src="./assets/images/tabbar/category.svg"/>
<img slot="item-icon-active" src="./assets/images/tabbar/categoryactive.svg"/>
<div slot="item-text">分类</div>
</tab-bar-item>
<tab-bar-item path="/shopcart" activeColor="blue">
<img slot="item-icon" src="./assets/images/tabbar/shopcart.svg"/>
<img slot="item-icon-active" src="./assets/images/tabbar/shopcartactive.svg"/>
<div slot="item-text">购物车</div>
</tab-bar-item>
<tab-bar-item path="/profile" activeColor="blue">
<img slot="item-icon" src="./assets/images/tabbar/profile.svg"/>
<img slot="item-icon-active" src="./assets/images/tabbar/profileactive.svg"/>
<div slot="item-text">我的</div>
</tab-bar-item>
</tab-bar>
<router-view></router-view>
</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 url('./assets/style/base');
</style>
效果
6、整个项目完整代码
- 1、单个页面Category.vue
其他文件也一样,不再重复写
<template>
<h2>分类</h2>
</template>
<script>
export default {
name:'Category'
}
</script>
<style scoped>
</style>
- 2、index.js(路由目录下)
import Vue from 'vue'
import Router from 'vue-router'
//导入组件,使用懒加载的方式
const Home = () => import('../views/home/Home.vue');
const Category = () => import('../views/category/Category.vue');
const Profile = () => import('../views/profile/Profile');
const Shopcart = () => import('../views/shopcart/Shopcart');
//使用路由对象
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
redirect:'/Home'
},
{
path: '/home',
component:Home
},
{
path: '/category',
component:Category
},
{
path: '/profile',
component:Profile
},
{
path: '/shopcart',
component:Shopcart
}
],
//设置路由显示模式,history直接显示路径,哈希模式会显示一个#
mode:'history'
})
- 3、TabVBar.vue
<template>
<div id="tab-bar">
<slot></slot>
</div>
</template>
<script>
export default {
name:'TabBar'
}
</script>
<style>
#tab-bar{display: flex; background-color: #f6f6f6; position: fixed; left:0; right: 0; bottom: 0; box-shadow: 0 -1px 1px rgba(100,100,100,0.3);}
</style>
- 4、TabVBarItem.vue
<template>
<div class="tab-bar-item" @click="itemClick">
<div v-if="!isActive" >
<slot name="item-icon"></slot>
</div>
<div v-else>
<slot name="item-icon-active"></slot>
</div>
<div :style="activeStyle">
<slot slot name="item-text"></slot>
</div>
</div>
</template>
<script>
export default {
name:'TabBarItem',
//创建一个父组件向子组件传值的props
props:{
//指定一个字符串型的link属性
path:String,
activeColor:{
type:String,
default:'blue'
}
},
data(){
return{
//isActive:true,
}
},
computed:{
isActive(){
//如果当前path在路由映射表内,则返回true(隐式返回),不在数组里,indexOf会返回-1,这是indexOf函数
return this.$route.path.indexOf(this.path)!==-1
},
activeStyle(){
//根据isActive计算属性的值,返回不同的color值
return this.isActive?{color:this.activeColor}:{}
}
},
methods:{
itemClick(){
//解决两次点击报错问题
if(this.$route.path==this.path){
return
}
//路由跳转
this.$router.replace(this.path)
}
}
}
</script>
<style>
.tab-bar-item{flex: 1; text-align: center; height: 49px; font-size: 14px;}
.tab-bar-item img{ width: 24px; height: 24px; vertical-align: middle; margin-top: 2px;}
/*
//由于在TabeBarItem文件里直接设置activColor,则不需要这个样式了
.ative{
color:blue
}
*/
</style>
- 5、App.vue
<template>
<div id="app">
<tab-bar>
<tab-bar-item path="/home" activeColor="blue">
<img slot="item-icon" src="./assets/images/tabbar/home.svg"/>
<img slot="item-icon-active" src="./assets/images/tabbar/homeactive.svg"/>
<div slot="item-text" >首页</div>
</tab-bar-item>
<tab-bar-item path="/category" activeColor="blue">
<img slot="item-icon" src="./assets/images/tabbar/category.svg"/>
<img slot="item-icon-active" src="./assets/images/tabbar/categoryactive.svg"/>
<div slot="item-text">分类</div>
</tab-bar-item>
<tab-bar-item path="/shopcart" activeColor="blue">
<img slot="item-icon" src="./assets/images/tabbar/shopcart.svg"/>
<img slot="item-icon-active" src="./assets/images/tabbar/shopcartactive.svg"/>
<div slot="item-text">购物车</div>
</tab-bar-item>
<tab-bar-item path="/profile" activeColor="blue">
<img slot="item-icon" src="./assets/images/tabbar/profile.svg"/>
<img slot="item-icon-active" src="./assets/images/tabbar/profileactive.svg"/>
<div slot="item-text">我的</div>
</tab-bar-item>
</tab-bar>
<router-view></router-view>
</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 url('./assets/style/base');
</style>
- 6、目录结构
最终效果