123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576 |
- <template>
- <view v-if="show" class="tn-tabbar-class tn-tabbar" @touchmove.stop.prevent="() => {}">
- <!-- tabbar 内容-->
- <view
- class="tn-tabbar__content"
- :class="{
- 'tn-tabbar--fixed': fixed,
- 'tn-safe-area-inset-bottom': safeAreaInsetBottom,
- 'tn-tabbar--shadow': shadow
- }"
- :style="{
- height: height + 'rpx',
- backgroundColor: bgColor
- }"
- >
- <!-- tabbar item -->
- <view
- v-for="(item, index) in list"
- :key="index"
- class="tn-tabbar__content__item"
- :id="`tabbar_item_${index}`"
- :class="{'tn-tabbar__content__item--out': item.out}"
- :style="{
- backgroundColor: bgColor
- }"
- @tap.stop="clickItemHandler(index)"
- >
- <!-- tabbar item的图片或者icon-->
- <view :class="[itemButtonClass(index)]"
- :style="[itemButtonStyle(index)]"
- >
- <image
- v-if="isImage(index)"
- :src="elIcon(index)"
- mode="scaleToFill"
- class="tn-tabbar__content__item__image"
- :style="{
- width: `${item.iconSize || iconSize}rpx`,
- height: `${item.iconSize || iconSize}rpx`
- }"
- ></image>
- <view
- v-else
- class="tn-tabbar__content__item__icon"
- :class="[`tn-icon-${elIcon(index)}`,elIconColor(index, false)]"
- :style="{
- fontSize: `${item.iconSize || iconSize}rpx`,
- color: elIconColor(index)
- }"
- ></view>
-
- <!-- 角标-->
- <tn-badge
- v-if="!item.out && (item.count || item.dot)"
- :dot="item.dot || false"
- backgroundColor="tn-bg-red"
- fontColor="#FFFFFF"
- :radius="item.dot ? 14 : 0"
- :fontSize="14"
- padding="2rpx 4rpx"
- :absolute="true"
- :top="2"
- >
- {{ $t.number.formatNumberString(item.count) }}
- </tn-badge>
- </view>
-
- <!-- tabbar item的文字-->
- <view
- class="tn-tabbar__content__item__text"
- :class="[elColor(index, false)]"
- :style="{
- color: elColor(index),
- fontSize: `${fontSize}rpx`
- }"
- >
- <text class="tn-text-ellipsis">{{ item.title }}</text>
- </view>
- </view>
-
- <!-- item 突起部分 -->
- <view
- v-if="outItemIndex !== -1"
- class="tn-tabbar__content__out"
- :class="[{
- 'tn-tabbar__content__out--shadow': shadow
- }, animation && value === outItemIndex ? `tn-tabbar__content__out--animation--${animationMode}` : '']"
- :style="{
- backgroundColor: bgColor,
- left: outItemLeft,
- width: `${outHeight}rpx`,
- height: `${outHeight}rpx`,
- top: `-${outHeight * 0.3}rpx`
- }"
- @tap.stop="clickItemHandler(outItemIndex)"
- ></view>
- </view>
-
- <!-- 防止tabbar塌陷 -->
- <view class="tn-tabbar__placeholder" :class="{'tn-safe-area-inset-bottom': safeAreaInsetBottom}" :style="{
- height: `calc(${height}rpx)`
- }"></view>
- </view>
- </template>
- <script>
- export default {
- name: 'tn-tabbar',
- props: {
- // 绑定当前被选中的current值
- value: {
- type: [String, Number],
- default: 0
- },
- // 是否显示
- show: {
- type: Boolean,
- default: true
- },
- // 图标列表
- list: {
- type: Array,
- default() {
- return []
- }
- },
- // 高度,单位rpx
- height: {
- type: Number,
- default: 100
- },
- // 突起的高度
- outHeight: {
- type: Number,
- default: 100
- },
- // 背景颜色
- bgColor: {
- type: String,
- default: '#FFFFFF'
- },
- // 图标大小
- iconSize: {
- type: Number,
- default: 40
- },
- // 字体大小
- fontSize: {
- type: Number,
- default: 24
- },
- // 激活时的颜色
- activeColor: {
- type: String,
- default: '#01BEFF'
- },
- // 非激活时的颜色
- inactiveColor: {
- type: String,
- default: '#AAAAAA'
- },
- // 激活时图标的颜色
- activeIconColor: {
- type: String,
- default: '#01BEFF'
- },
- // 非激活时图标的颜色
- inactiveIconColor: {
- type: String,
- default: '#AAAAAA'
- },
- // 激活时的自定义样式
- activeStyle: {
- type: Object,
- default() {
- return {}
- }
- },
- // 是否显示阴影
- shadow: {
- type: Boolean,
- default: true
- },
- // 点击时是否有动画
- animation: {
- type: Boolean,
- default: false
- },
- // 点击时的动画模式
- animationMode: {
- type: String,
- default: 'scale'
- },
- // 是否固定在底部
- fixed: {
- type: Boolean,
- default: true
- },
- // 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距
- safeAreaInsetBottom: {
- type: Boolean,
- default: false
- },
- // 切换前回调
- beforeSwitch: {
- type: Function,
- default: null
- }
- },
- computed: {
- // 当前字体的颜色
- elColor() {
- return (index, style = true) => {
- let currentItem = this.list[index]
- let color = ''
- if (index === this.value) {
- color = currentItem['activeColor'] || this.activeColor
- } else {
- color = currentItem['inactiveColor'] || this.inactiveColor
- }
- // 判断是否获取内部样式
- if (style) {
- if (this.$t.color.getFontColorStyle(color) !== '') {
- return color
- } else {
- return ''
- }
- } else {
- if (this.$t.color.getFontColorStyle(color) === '') {
- return color
- } else {
- return ''
- }
- }
- }
- },
- // 当前图标的颜色
- elIconColor() {
- return (index, style = true) => {
- let currentItem = this.list[index]
- let color = ''
- if (index === this.value) {
- color = currentItem['activeIconColor'] || this.activeIconColor
- } else {
- color = currentItem['inactiveIconColor'] || this.inactiveIconColor
- }
- // 判断是否获取内部样式
- if (style) {
- if (this.$t.color.getFontColorStyle(color) !== '') {
- return color
- } else {
- return ''
- }
- } else {
- if (this.$t.color.getFontColorStyle(color) === '') {
- return color + ' tn-tabbar__content__item__icon--clip'
- } else {
- return ''
- }
- }
- }
- },
- // 当前的图标
- elIcon() {
- return (index) => {
- let currentItem = this.list[index]
- if (index === this.value) {
- return currentItem['activeIcon']
- } else {
- return currentItem['inactiveIcon']
- }
- }
- },
- // 突起部分item button对应的类
- itemButtonClass() {
- return (index) => {
- let clazz = ''
- if (this.list[index]['out']) {
- clazz += 'tn-tabbar__content__item__button--out'
- if (this.$t.color.getFontColorStyle(this.activeIconColor) === '') {
- clazz += ` ${this.activeIconColor}`
- }
- if (this.value === index) {
- clazz += ` tn-tabbar__content__item__button--out--animation--${this.animationMode}`
- }
- } else {
- clazz += 'tn-tabbar__content__item__button'
- if (this.value === index) {
- clazz += ` tn-tabbar__content__item__button--animation--${this.animationMode}`
- }
- }
- return clazz
- }
- },
- // 突起部分item button样式
- itemButtonStyle() {
- return (index) => {
- let style = {}
- if (this.list[index]['out']) {
- if (this.$t.color.getFontColorStyle(this.activeIconColor) !== '') {
- style.backgroundColor = this.activeIconColor
- }
- style.width = `${this.outHeight - 35}rpx`
- style.height = `${this.outHeight - 35}rpx`
- style.top = `-${this.outHeight * 0.15}rpx`
-
- return style
- }
- return style
- }
- },
- // 判断图标是否为图片
- isImage() {
- return (index) => {
- const icon = this.list[index]['activeIcon']
- // 只有包含了'/'就认为是图片
- return icon.indexOf('/') !== -1
- }
- }
- },
- data() {
- return {
- // 当前突起的位置
- outItemLeft: '50%',
- // 当前设置了突起按钮的index
- outItemIndex: -1,
- // 每一个item的信息
- tabbatItemInfo: []
- }
- },
- watch: {
-
- },
- created() {
- this.getOutItemIndex()
- },
- mounted() {
- this.$nextTick(() => {
- this.getTabbarItem()
- })
- },
- methods: {
- // 获取每一个item的信息
- getTabbarItem() {
- let query = uni.createSelectorQuery().in(this)
- // 遍历获取信息
- for (let i = 0; i < this.list.length; i++) {
- query.select(`#tabbar_item_${i}`).fields({
- size: true,
- rect: true
- })
- }
- query.exec(res => {
- if (!res) {
- setTimeout(() => {
- this.getTabbarItem()
- }, 10)
- return
- }
- this.tabbatItemInfo = res.map((item) => {
- return {
- left: item.left,
- width: item.width
- }
- })
- this.updateOutItemLeft()
- })
- },
- // 获取突起Item所在的index(如果存在)
- getOutItemIndex() {
- this.outItemIndex = this.list.findIndex((item) => {
- return item.hasOwnProperty('out') && item.out
- })
- },
- // 点击底部菜单时触发
- async clickItemHandler(index) {
- if (this.beforeSwitch && typeof(this.beforeSwitch) === 'function') {
- // 执行回调,同时传入索引当作参数
- // 在微信,支付宝等环境(H5正常),会导致父组件定义的函数体中的this变成子组件的this
- // 通过bind()方法,绑定父组件的this,让this的this为父组件的上下文
- let beforeSwitch = this.beforeSwitch.bind(this.$t.$parent.call(this))(index)
- // 判断是否返回了Promise
- if (!!beforeSwitch && typeof beforeSwitch.then === 'function') {
- await beforeSwitch.then(res => {
- // Promise返回成功
- this.switchTab(index)
- }).catch(err => {
-
- })
- } else if (beforeSwitch === true) {
- this.switchTab(index)
- }
- } else {
- this.switchTab(index)
- }
- },
- // 切换tab
- switchTab(index) {
- // 发出事件和修改v-model绑定的值
- this.$emit('change', index)
- this.$emit('input', index)
- },
- // 设置突起的位置
- updateOutItemLeft() {
- // 查找出需要突起的元素
- const index = this.list.findIndex((item) => {
- return item.out
- })
- if (index !== -1) {
- this.outItemLeft = this.tabbatItemInfo[index].left + (this.tabbatItemInfo[index].width / 2) + 'px'
- }
- }
- }
- }
- </script>
- <style lang="scss" scoped>
-
- .tn-tabbar {
-
- &__content {
- box-sizing: content-box;
- display: flex;
- flex-direction: row;
- align-items: center;
- position: relative;
- width: 100%;
- z-index: 1024;
-
- &__out {
- position: absolute;
- z-index: 4;
- border-radius: 100%;
- left: 50%;
- transform: translateX(-50%);
-
- &--shadow {
- box-shadow: 0rpx -10rpx 30rpx 0rpx rgba(0, 0, 0, 0.05);
-
- &::before {
- content: " ";
- position: absolute;
- width: 100%;
- height: 50rpx;
- bottom: 0;
- left: 0;
- right: 0;
- margin: auto;
- background-color: inherit;
- }
- }
-
- &--animation {
- &--scale {
- transform-origin: 50% 100%;
- animation:tabbar-content-out-click 0.2s forwards 1 ease-in-out;
- }
- }
- }
-
- &__item {
- flex: 1;
- display: flex;
- flex-direction: column;
- justify-content: flex-end;
- align-items: center;
- height: 100%;
- position: relative;
-
- &__button {
- margin-bottom: 10rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- position: relative;
-
- &--out {
- margin-bottom: 10rpx;
- border-radius: 50%;
- position: absolute;
- display: flex;
- justify-content: center;
- align-items: center;
- z-index: 6;
-
- &--animation {
- &--scale {
- transform-origin: 50% 100%;
- animation:tabbar-item-button-out-click 0.2s forwards 1;
- }
- }
- }
-
- &--animation {
- &--scale {
- .tn-tabbar__content__item__icon, .tn-tabbar__content__item__image {
- transform-origin: 50% 100%;
- animation:tabbar-item-button-click 0.2s forwards 1;
- }
- }
- }
- }
-
- &__icon {
-
- &--clip {
- -webkit-background-clip: text;
- color: transparent !important;
- }
- }
-
- &__text {
- width: 100%;
- font-size: 26rpx;
- line-height: 28rpx;
- text-align: center;
- margin-bottom: 10rpx;
- z-index: 10;
- transition: all 0.2s ease-in-out;
- }
-
- &--out {
- height: calc(100% - 1px);
- }
- }
- }
-
- &--fixed {
- position: fixed;
- bottom: 0;
- left: 0;
- right: 0;
- }
-
- &--shadow {
- box-shadow: 0rpx 0rpx 30rpx 0rpx rgba(0, 0, 0, 0.07);
- }
- }
-
- /* 点击动画 start */
-
- @keyframes tabbar-item-button-click{
- from{
- transform: scale(0.8);
- }
- to{
- transform: scale(1);
- }
- }
-
- @keyframes tabbar-item-button-out-click {
- 0%{
- transform: translateY(0) scale(1);
- }
- 50%{
- transform: translateY(-10rpx) scale(1.2);
- }
- 100%{
- transform: translateY(0) scale(1);
- }
- }
-
- @keyframes tabbar-content-out-click {
- 0%{
- transform: translateX(-50%) translateY(0) scale(1);
- }
- 50% {
- transform: translateX(-50%) translateY(-10rpx) scale(1.1);
- }
- 100% {
- transform: translateX(-50%) translateY(0) scale(1);
- }
- }
-
- /* 点击动画 end */
- </style>
|