最终实现的大致效果如下:

这里一样采用组件封装的方式,大致如下:

组件拆分:

  • MyHeader.vue – 复用之前的(上一个案例  vue--实现购物车 时里面封装的 头部组件)
  • MyTabBar.vue – 底部导航
  • MyTable.vue – 封装表格

三个页面

  • - MyGoodsList.vue – 商品页
  • - MyGoodsSearch.vue – 搜索页
  • - MyUserInfo.vue – 用户信息页

需要安装的第三方包:

npm install less less-loader@5.0.0 -D

npm install bootstrap --save   并在main.js 引入和全局属性

npm install axios --save    并在main.js 引入和全局属性

在 components 下分别新建 MyHeader.vue、MyTabBar.vue、MyTable.vue三个组件

然后再在 src 下新建 views 目录,并在该目录下分别新建 MyGoodsList.vue、MyGoodsSearch.vue、MyUserInfo.vue 三个组件页面

在 App.vue 里先引入注册和使用头部组件并且将头部的背景颜色、字体颜色、标题传递给 MyHeader.vue ,在 MyHeader.vue 通过 props 进行接收:

App.vue,

<MyHeader :background="'blue'" :fontColor="'white'" title="TabBar案例"></MyHeader>

MyHeader.vue,

<template><div class="my-header":style="{backgroundColor: background, color: color}">{{title}}</div>
</template><script>
export default {props: {// 注意:外界在使用时,需要遵守变量名作为属性名,值的类型也要遵守background: String, // 外界传入此变量的类型为字符串color: {type: String, // 约束 color 值的类型default: '#fff', // 当前 color 的默认值(外界不传时使用默认值)},title: {type: String,required: true // 必须传入此变量的值}}
}
</script><style lang="less" scoped>
.my-header {height: 45px;line-height: 45px;text-align: center;background-color: #1d7bff;color: #fff;position: fixed;top: 0;left: 0;width: 100%;z-index: 2;
}
</style>

这样头部也就完成了,效果如下:

完成底部封装,也就是 MyTabBar.vue 导航:

将 TabBar 需要的字体图标放入 assets 里,并在 main.js 里引入

main.js,

import './assets/fonts/iconfont.css' // 引入字体图标的 css

在 MyTabBar.vue 里完成基本的结构和样式

MyTabBar.vue,

<template><div class="my-tab-bar"><div class="tab-item"><!-- 图标 --><span class="iconfont"></span><!-- 文字 --><span></span></div></div>
</template><script>
export default {}
</script><style lang="less" scoped>
.my-tab-bar {position: fixed;left: 0;bottom: 0;width: 100%;height: 50px;border-top: 1px solid #ccc;display: flex;justify-content: space-around;align-items: center;background-color: white;.tab-item {display: flex;flex-direction: column;align-items: center;}
}.current {color: #1d7bff;
}
</style>

为tabbar组件指定数据源 (数据源最少2个, 最多5个(validator) )

App.vue,

  data () {return {tabList: [{iconText: "icon-shangpinliebiao",text: "商品列表",componentName: "MyGoodsList"},{iconText: "icon-sousuo",text: "商品搜索",componentName: "MyGoodsSearch"},{iconText: "icon-user",text: "我的信息",componentName: "MyUserInfo"}]}}

将 MyTabBar.vue 在 App.vue 里引入注册和使用,并通过 v-bind 传入底部导航的数据源

App.vue,

<MyTabBar :tabList="tabList"></MyTabBar>

在 MyTabBar.vue 里通过 props 接收传入的数据并循环进行展示

MyTabBar.vue,

     <div class="tab-item" v-for="(obj, index) in tabList" :key="index"><!-- 图标 --><span class="iconfont" :class="obj.iconText"></span><!-- 文字 --><span>{{obj.text}}</span></div>props: {tabList: {type: Array,required: true,// 自定义校验规则validator(val){ // val 其实就是接到的数组if(val.length >= 2 && val.length <= 5){return true // 符合条件}else{console.error('数据源必须2~5项');return false}}}}

这样也就实现了:

实现点击底部导航完成点谁谁高亮显示效果:

给每个 TabBar 绑定点击事件, 并传入对应的索引

MyTabBar.vue,

<div class="tab-item" v-for="(obj, index) in tabList" :key="index" @click="btn(index)">

利用遍历的索引, 和点击保存的索引进行比较, 判断是否相等,若相同则给当前循环的标签设置动态class使其高亮显示,反之不高亮显示

  <div class="my-tab-bar"><div class="tab-item" v-for="(obj, index) in tabList" :key="index" @click="btn(index)":class="{current: index === selIndex}">data () {return {selIndex: 0  // 默认第一个高亮}},methods: {btn(index){this.selIndex = index // 点谁就把谁的索引值保存起来}}.current {color: #1d7bff;}

最后效果如下:

点击底部 TabBar 实现切换中间的内容组件

一个挂载点需要切换不同的组件显示需要用到动态组件或者是路由,这里以动态组件为例

将新建好的 MyGoodsList.vue、MyGoodsSearch.vue、MyUserInfo.vue 三个组件在 App.vue 里引入注册

App.vue ,

    <div class="main"><component :is="comName"></component></div>data () {return {comName: 'MyGoodsList', // 默认显示的组件}},

利用动态组件 component 里的 :is 属性来指定需要显示的组件名,默认显示 商品列表页

实现点击 TabBar 时进行显示不同的页面组件,在 MyTabBar.vue 里当点击导航时同时也需要将obj传递到点击事件里,然后在点击事件中通过 $emit 将 obj 里面的 componentName 传递给 App.vue ,在App.vue 里通过 v-on 自定义事件进行接收

MyTabBar.vue,

     <div class="tab-item" v-for="(obj, index) in tabList" :key="index" @click="btn(index, obj)":class="{current: index === selIndex}">methods: {btn(index, obj){this.selIndex = index // 点谁就把谁的索引值保存起来// 把要切换的组件名传给父类this.$emit('changeCom', obj.componentName)}}

App.vue,

<MyTabBar :tabList="tabList" @changeCom="changeConFn"></MyTabBar>methods: {changeConFn(cName){// 将 MyTabBar.vue 里选出的组件名赋予给 is 属性的 comName 即可实现组件切换this.comName = cName}}.main {padding: 45px 0 51px 0;}

最后效果如下:

完成 商品列表 页面的数据填充:

封装MyTable.vue – 准备标签和样式

MyTable.vue,

<template><table class="table table-bordered table-stripped"><!-- 表格标题区域 --><thead><tr><th>#</th><th>商品名称</th><th>价格</th><th>标签</th><th>操作</th></tr></thead><!-- 表格主体区域 --><tbody><tr ><td>1</td><td>商品</td><td>998</td><td>xxx</td><td>xxx</td></tr></tbody></table>
</template><script>
export default {name: 'MyTable'
}
</script><style scoped lang="less">
.my-goods-list {.badge {margin-right: 5px;}
}
</style>

在 MyGoodsList.vue 里引入注册和使用

MyGoodsList.vue,

<MyTable></MyTable>

大致效果如下:

在 main.js 里引入 bootstrap并配置axios的基地址

import 'bootstrap/dist/css/bootstrap.css'import axios from 'axios'
// 配置基础地址
axios.defaults.baseURL = 'https://www.escook.cn'
// 将 axios 添加到 vue 原型上,这样就可在项目的任意地方使用
Vue.prototype.$axios = axios

在 MyGoodsList.vue 里请求数据并处理,然后在 data 里定义一个变量 list 保存请求到的数据,并通过 父传子 将保存好的数据传递给 MyTable.vue

MyGoodsList.vue,

    <MyTable :arr="list"></MyTable>data () {return {list: []}},created () {// 因为 axios 已经挂载到了全局上,因此无需引入,直接使用即可this.$axios({method: 'GET',url: '/api/goods'}).then(res => {// console.log(res);this.list = res.data.data}).catch(err => {console.log(err);})}

在 MyTable.vue 里通过 props 进行接收,并遍历数据

MyTable.vue,

  props: {arr: {type: Array,default: []}}<tbody><tr v-for="obj in arr" :key="obj.id"><td>{{obj.id}}</td><td>{{obj.goods_name}}</td><td>{{obj.goods_price}}</td><td>{{obj.tags}}</td><td><button class="btn btn-danger btn-sm">删除</button></td></tr></tbody>

效果如下:

 实现允许用户自定义表格头和表格单元格内容:

之前在 MyTable.vue 里将表格头都写死了,这样不利于复用

因此为了实现可以复用,我们可以将 tr里面的内容 换成 slot 插槽

同理我们也应该将表格的主体区域 tr 里的 td 换成 slot 插槽

这样可以最大程度上实现组件复用

然后在 MyGoodsList.vue 里通过 <template v-slot:>进行自定义

MyGoodsList.vue,

  <div><MyTable :arr="list"><!-- v-slot: 等价于 # --><template #header><th>#</th><th>商品名称</th><th>价格</th><th>标签</th><th>操作</th></template><template v-slot: = 'body'><td>{{obj.id}}</td><td>{{obj.goods_name}}</td><td>{{obj.goods_price}}</td><td>{{obj.tags}}</td><td><button class="btn btn-danger btn-sm">删除</button></td></template></MyTable></div>

由于在 tbody 里需要用到 MyTable.vue 里的数据,因此需要通过 作用域插槽 将数据传递过去

MyTable.vue,

<slot name="body" :row="obj"></slot>

在 MyGoodsList.vue 里的 template 标签里接收:

MyGoodsList.vue,

      <!-- scope的值:{row: obj} --><template v-slot:body = 'scope'><td>{{scope.row.id}}</td><td>{{scope.row.goods_name}}</td><td>{{scope.row.goods_price}}</td><td>{{scope.row.tags}}</td><td><button class="btn btn-danger btn-sm">删除</button></td></template>

最后效果如下:

实现商品表格_tags铺设,使其标签列自定义显示:

在插槽里传入 td 标签

然后自定义 span 标签进行循环展示并传入样式

MyGoodsList.vue,

      <template v-slot:body = 'scope'>...<td><span v-for="(val, index) in scope.row.tags" :key="index" class="badge badge-warning">{{val}}</span></td>...</template>

效果如下:

实现点击删除按钮删除数据:

给删除按钮绑定点击事件

利用作用域插槽绑定id值,传给删除方法, 删除MyGoodsList.vue里数组里数据

MyGoodsList.vue,

 <button class="btn btn-danger btn-sm" @click="delBtn(scope.row.id)">删除</button>

通过对应的 id 查找数组里的数据对应的索引实现删除

MyGoodsList.vue,

  methods: {delBtn(id){const index = this.list.findIndex(obj => {obj.id === id})this.list.splice(index, 1)}}

效果如下:

实现商品表格_添加tab:

需求,

  1. 点击Tab, 按钮消失, 输入框出现
  2. 输入框自动聚焦
  3. 失去焦点, 输入框消失, 按钮出
  4. 监测input回车, 无数据拦截
  5. 监测input取消, 清空数据
  6. 监测input回车, 有数据添加

准备静态Tab标签按钮 – 并绑定事件,当点击按钮时,按钮消失输入框出现,这里利用 v-if 实现

MyGoodsList.vue,

      <template v-slot:body = 'scope'>...<td><inputclass="tag-input form-control"style="width: 100px;"type="text"v-if="scope.row.inputVisible"/><button v-else style="display: block;" class="btn btn-primary btn-sm add-tag"@click="scope.row.inputVisible = true">+Tag</button>...</td>...</template>

在 main.js 里通过自定义指令, 让输入框自动聚焦

main.js,

// 全局指令
Vue.directive('gfocus', {inserted(el){// console.log(el);el.focus() // 触发标签的事件方法}
})

MyGoodsList.vue,

      <template v-slot:body = 'scope'>...<td><input...v-gfocus/>...</td>...</template>

监测失去焦点事件 – 给关联的对象属性设置 – 影响标签出现/隐藏

MyGoodsList.vue,

      <template v-slot:body = 'scope'>...<td><input...@blur="scope.row.inputVisible = false"/>...</td>...</template>

监测input的回车, 判断是否有值 – 给出提示 / 添加数据

监测input的取消, 清空数据

MyGoodsList.vue,

      <template v-slot:body = 'scope'>...<td><input...@keydown.enter="enterFn(scope.row)"v-model="scope.row.inputValue"@keydown.esc="scope.row.inputValue = ''"/>...</td>...</template>methods: {// 回车enterFn(obj){if(obj.inputValue.trim().length === 0) {alert('请输入数据')return}// 将表单里的数据添加到数组里的tags里obj.tags.push(obj.inputValue)obj.inputValue = ''}},

效果如下:

本案例的最终效果和源码:

main.js,

import 'bootstrap/dist/css/bootstrap.css'
import './assets/fonts/iconfont.css' // 引入字体图标的 cssimport axios from 'axios'
// 配置基础地址
axios.defaults.baseURL = 'https://www.escook.cn'
// 将 axios 添加到 vue 原型上,这样就可在项目的任意地方使用
Vue.prototype.$axios = axios// 全局指令
Vue.directive('gfocus', {inserted(el){// console.log(el);el.focus() // 触发标签的事件方法}
})

App.vue,

<template><div><MyHeader :background="'blue'" :fontColor="'white'" title="TabBar案例"></MyHeader><div class="main"><component :is="comName"></component></div><MyTabBar :tabList="tabList" @changeCom="changeConFn"></MyTabBar></div>
</template><script>
import MyHeader from './components/MyHeader'
import MyTabBar from './components/MyTabBar'
import MyGoodsList from './views/MyGoodsList'
import MyGoodsSearch from './views/MyGoodsSearch'
import MyUserInfo from './views/MyUserInfo'
export default {components: {MyHeader,MyTabBar,MyGoodsList,MyGoodsSearch,MyUserInfo},data () {return {comName: 'MyGoodsList', // 默认显示的组件tabList: [{iconText: "icon-shangpinliebiao",text: "商品列表",componentName: "MyGoodsList"},{iconText: "icon-sousuo",text: "商品搜索",componentName: "MyGoodsSearch"},{iconText: "icon-user",text: "我的信息",componentName: "MyUserInfo"}],}},methods: {changeConFn(cName){// 将 MyTabBar.vue 里选出的组件名赋予给 is 属性的 comName 即可实现组件切换this.comName = cName}}
}
</script>
<style scoped>
.main {padding: 45px 0 51px 0;
}
</style>

MyHeader.vue,

<template><div class="my-header":style="{backgroundColor: background, color: color}">{{title}}</div>
</template><script>
export default {props: {// 注意:外界在使用时,需要遵守变量名作为属性名,值的类型也要遵守background: String, // 外界传入此变量的类型为字符串color: {type: String, // 约束 color 值的类型default: '#fff', // 当前 color 的默认值(外界不传时使用默认值)},title: {type: String,required: true // 必须传入此变量的值}}
}
</script><style lang="less" scoped>
.my-header {height: 45px;line-height: 45px;text-align: center;background-color: #1d7bff;color: #fff;position: fixed;top: 0;left: 0;width: 100%;z-index: 2;
}
</style>

MyTable.vue,

<template><table class="table table-bordered table-stripped"><!-- 表格标题区域 --><thead><tr><!-- <th>#</th><th>商品名称</th><th>价格</th><th>标签</th><th>操作</th> --><slot name="header"></slot></tr></thead><!-- 表格主体区域 --><tbody><tr v-for="obj in arr" :key="obj.id"><!-- <td>{{obj.id}}</td><td>{{obj.goods_name}}</td><td>{{obj.goods_price}}</td><td>{{obj.tags}}</td><td><button class="btn btn-danger btn-sm">删除</button></td> --><slot name="body" :row="obj"></slot></tr></tbody></table>
</template><script>
export default {name: 'MyTable',props: {arr: {type: Array,default: []}}
}
</script><style scoped lang="less">
.my-goods-list {.badge {margin-right: 5px;}
}
</style>

MyTabBar.vue,

<template><div class="my-tab-bar"><div class="tab-item" v-for="(obj, index) in tabList" :key="index" @click="btn(index, obj)":class="{current: index === selIndex}"><!-- 图标 --><span class="iconfont" :class="obj.iconText"></span><!-- 文字 --><span>{{obj.text}}</span></div></div>
</template><script>
export default {props: {tabList: {type: Array,required: true,// 自定义校验规则validator(val){ // val 其实就是接到的数组if(val.length >= 2 && val.length <= 5){return true // 符合条件}else{console.error('数据源必须2~5项');return false}}}},data () {return {selIndex: 0  // 默认第一个高亮}},methods: {btn(index, obj){this.selIndex = index // 点谁就把谁的索引值保存起来// 把要切换的组件名传给父类this.$emit('changeCom', obj.componentName)}}
}
</script><style lang="less" scoped>
.my-tab-bar {position: fixed;left: 0;bottom: 0;width: 100%;height: 50px;border-top: 1px solid #ccc;display: flex;justify-content: space-around;align-items: center;background-color: white;.tab-item {display: flex;flex-direction: column;align-items: center;}
}.current {color: #1d7bff;
}
</style>

MyGoodsList.vue,

<template><div><MyTable :arr="list"><!-- v-slot: 等价于 # --><template #header><th>#</th><th>商品名称</th><th>价格</th><th>标签</th><th>操作</th></template><!-- scope的值:{row: obj} --><template v-slot:body = 'scope'><td>{{scope.row.id}}</td><td>{{scope.row.goods_name}}</td><td>{{scope.row.goods_price}}</td><td><inputclass="tag-input form-control"style="width: 100px;"type="text"v-if="scope.row.inputVisible"v-gfocus@blur="scope.row.inputVisible = false"@keydown.enter="enterFn(scope.row)"v-model="scope.row.inputValue"@keydown.esc="scope.row.inputValue = ''"/><button v-else style="display: block;" class="btn btn-primary btn-sm add-tag"@click="scope.row.inputVisible = true">+Tag</button><span v-for="(val, index) in scope.row.tags" :key="index" class="badge badge-warning">{{val}}</span></td><td><button class="btn btn-danger btn-sm" @click="delBtn(scope.row.id)">删除</button></td></template></MyTable></div>
</template><script>
import MyTable from '../components/MyTable'
export default {components: {MyTable},data () {return {list: []}},created () {// 因为 axios 已经挂载到了全局上,因此无需引入,直接使用即可this.$axios({method: 'GET',url: '/api/goods'}).then(res => {// console.log(res);this.list = res.data.data}).catch(err => {console.log(err);})},methods: {delBtn(id){const index = this.list.findIndex(obj => {obj.id === id})this.list.splice(index, 1)},// 回车enterFn(obj){if(obj.inputValue.trim().length === 0) {alert('请输入数据')return}// 将表单里的数据添加到数组里的tags里obj.tags.push(obj.inputValue)obj.inputValue = ''}},}
</script><style>
</style>

MyGoodsSearch.vue,

<template><div>商品搜素页</div>
</template><script>
export default {}
</script><style></style>

MyUserInfo.vue,

<template><div>个人中心</div>
</template><script>
export default {}
</script><style></style>

vue--实现Tabbar相关推荐

  1. Vue(小码哥王洪元)笔记07路由案例tabbar

    1.tabbar案例01 进行了简单的布局 1.创建一个样式文件base.css body{margin:0; padding: 0;} 2.修改App.vue <template>< ...

  2. mint ui tab html,Vue Mint UI tabbar切换

    1.引入mint-ui 1).使用 vue-cli npm install -g vue-cli vue init webpack projectname 2). 部分引入 在main.js中写入: ...

  3. 最好看的TabBar(Vue)

    今天跟大家分享一个自己模仿得物的tabbar栏,用Vue写的!用到了router,实现了页面动态的跳转. 目录结构(分了3个vue  template) TabBar.vue TabBarItem.v ...

  4. uniapp可以封装组件嘛_uniapp聊天App实例|vue+uniapp仿微信界面|红包|朋友圈

    一.功能阐述 今天给大家分享的是基于UniApp+Vue+Vuex+swiper+uniPop等技术开发的仿微信原生App聊天室|仿微信聊天界面实例项目uniapp-chatroom,实现了发送图文消 ...

  5. 一步一步搭建vue项目

    1 安装步骤 创建一个目录,我们这里定义为Vue 在Vue目录打开dos窗体,输入如下命令:vue create myproject 选择自定义   4. 先选择要安装的项目,我们这里选择4个   5 ...

  6. 计算机毕业设计-基于神经网络APP-整合Vue+SpringBoot+TensorFlow框架-诗联AI

    诗联AI-基于神经网络APP-整合Vue+SpringBoot+TensorFlow框架 目录 诗联AI-基于神经网络APP-整合Vue+SpringBoot+TensorFlow框架 1.项目整体概 ...

  7. vite+ts+vue组合式api-c端-移动端项目(保姆级教学)

    文章目录 一.项目创建准备工作 1.配置vite和ts默认项 2.配置路由命名 3.配置环境源env 4.二次封装axios 5.配置反向代理 6.测试请求接口 7.配置rem,配置移动端 8.配置v ...

  8. 基于Vue开发的电商APP项目——蘑菇街app

    基于Vue开发的电商APP项目--蘑菇街 项目源码:https://github.com/Limna777/Shopmall.git 1.项目描述 2.使用的插件或第三方库 3.页面主要实现的功能 1 ...

  9. 【Vue知识点- No8.】网易云音乐案例(vant组件库的使用)

    No8.网易云音乐案例 知识点自测 知道reset.css和flexible.js的作用. 什么是组件库-例如bootstrap的作用. yarn命令的使用. 组件名字用name属性方式注册. 如何自 ...

  10. vue网易云歌单案例

    一.Vant组件的使用(注意版本号) 1.yarn add axios vant@2.12.24 vue-router@3.5.3 2.自动按需引入 yarn add babel-plugin-imp ...

最新文章

  1. OpenCV对象检测实例
  2. 谷歌重磅开源新技术:5行代码打造无限宽神经网络模型,帮助“打开ML黑匣子”...
  3. 云南大学计算机科学技术是哪个院的,云南大学计算机科学与工程系介绍
  4. 根据序列选择自回归模型(AR、ARMA、VAR、VMA、VECH)
  5. 落魄前端,整理给自己的前端知识体系复习大纲(下篇)
  6. python 两个变量同时循环_python基础篇(子非鱼)
  7. 购物中心节假日如何统计客流量分析客流量数据?
  8. 有监督学习、无监督学习、半监督学习和强化学习的总结
  9. 阅读技术书籍原著的困扰:技术书籍中的符号用英语该怎么念?
  10. Cassandra中Gossip具体实现方式
  11. php 日期转换为大写
  12. ssms连接mysql_SQL Server安装以及使用SSMS连接数据库
  13. 送你4句口诀 云存储选型不再犯难
  14. 快学Python:函数的使用
  15. 一键轻松设置项目代理
  16. 浅谈小学语文教学中的读
  17. 为什么沃尔玛等零售商会结成移动支付联盟 ?
  18. oa系统服务器地址怎么查,如何查询oa服务器地址
  19. 区块链是如何实现隐私保护的?
  20. Windows11怎么配置Java环境变量

热门文章

  1. 一个魔兽世界玩家走向成功站长历程
  2. Vue---v-bind数据绑定、事件绑定、v-for循环绑定数据、v-model数据绑定、过滤器filters、v-show、v-if及练习(demo:切换小球颜色、获取数据进行展示、显示和隐藏)
  3. MATLAB提取感兴趣ROI
  4. 大学生自学网python_适合大学生自学的网站?
  5. Magento URL rewrite
  6. MOS FET继电器(无机械触点继电器)设计输入侧电源时的电流值概念
  7. 为什么需要树这种数据结构
  8. 利用快速傅立叶变换,在频域中实现脉冲压缩的matlab仿真程序
  9. 7-7 猴子吃桃问题 (15 分)
  10. syslog日志系统——日志采集