516 lines
10 KiB
Vue
516 lines
10 KiB
Vue
|
|
<template>
|
|||
|
|
<view class="m-tabbar-box" :style="tabbarBoxStyle" v-if="isShowTabBar">
|
|||
|
|
<view class="m-tabbar__fill" v-if="fill || native" :class="{'m-tabbar__safe': (safeBottom || native)}"
|
|||
|
|
:style="tabbarFillStyle" />
|
|||
|
|
<view id="m-tabbar" class="m-tabbar"
|
|||
|
|
:class="{'fixed': (fixed || native), 'm-tabbar__safe': (safeBottom || native)}" :style="tabbarStyle">
|
|||
|
|
<view class="m-tabbar__border" v-if="borderStyle === 'black'" />
|
|||
|
|
<!-- tabbar border -->
|
|||
|
|
<view class="m-tabbar__flex">
|
|||
|
|
<view class="m-tabbar__item" @click="_tabChange(index)" v-for="(item, index) in tabbarList" :key="index"
|
|||
|
|
:class="{
|
|||
|
|
'm-tabbar__item__active': index === currentIndex,
|
|||
|
|
}">
|
|||
|
|
<slot :name="`tabbar_index_${index}`">
|
|||
|
|
<view class="m-tabbar__icon">
|
|||
|
|
<view class="m-tabbar__badge" v-if="item.dot">{{item.dot}}</view>
|
|||
|
|
<image :src="currentIndex === index ? item.selectedIconPath : item.iconPath"
|
|||
|
|
class="m-tabbar__icon_img" />
|
|||
|
|
</view>
|
|||
|
|
<view class="m-tabbar__label"
|
|||
|
|
:style="{'color': index === currentIndex ? tabbarConfig.selectedColor : tabbarConfig.color }">
|
|||
|
|
{{ item.text }}
|
|||
|
|
</view>
|
|||
|
|
</slot>
|
|||
|
|
</view>
|
|||
|
|
<!-- tabbar item -->
|
|||
|
|
</view>
|
|||
|
|
<!-- tabbar item box -->
|
|||
|
|
</view>
|
|||
|
|
<!-- tabbar block -->
|
|||
|
|
</view>
|
|||
|
|
<!-- tabbar box -->
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
// style转为string
|
|||
|
|
const obj2strStyle = (obj) => {
|
|||
|
|
let style = ''
|
|||
|
|
for (let key in obj) {
|
|||
|
|
style += `${key}:${obj[key]};`
|
|||
|
|
}
|
|||
|
|
return style
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 自动填充路径
|
|||
|
|
const padFirstSymbol = (str, smb) => {
|
|||
|
|
if (str.startsWith(smb) || str.startsWith('http')) {
|
|||
|
|
return str
|
|||
|
|
}
|
|||
|
|
return `/${str}`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 替换原生路径问题
|
|||
|
|
const replaceTabbarList = (list) => {
|
|||
|
|
if (!list.length > 0) {
|
|||
|
|
return []
|
|||
|
|
}
|
|||
|
|
return list.map(item => {
|
|||
|
|
if (item.iconPath) {
|
|||
|
|
item.iconPath = padFirstSymbol(item.iconPath, '/')
|
|||
|
|
}
|
|||
|
|
if (item.pagePath) {
|
|||
|
|
item.pagePath = padFirstSymbol(item.pagePath, '/')
|
|||
|
|
}
|
|||
|
|
if (item.selectedIconPath) {
|
|||
|
|
item.selectedIconPath = padFirstSymbol(item.selectedIconPath, '/')
|
|||
|
|
}
|
|||
|
|
return item
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 设置缓存,方便重复调用方法
|
|||
|
|
const setTabbarStorage = (key, value, name) => {
|
|||
|
|
try {
|
|||
|
|
uni.setStorageSync(`mTab_${key}_${name}`, value)
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error(e)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 读取缓存
|
|||
|
|
const getTabbarStorage = (key, name) => {
|
|||
|
|
try {
|
|||
|
|
const value = uni.getStorageSync(`mTab_${key}_${name}`)
|
|||
|
|
if (value) {
|
|||
|
|
return value
|
|||
|
|
}
|
|||
|
|
return ''
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error(e)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
import PageConfig from '@/pages.json'
|
|||
|
|
|
|||
|
|
export default {
|
|||
|
|
emits: ['change', 'click'],
|
|||
|
|
props: {
|
|||
|
|
// 使用原生配置自动生成
|
|||
|
|
native: {
|
|||
|
|
type: Boolean,
|
|||
|
|
default: false
|
|||
|
|
},
|
|||
|
|
// 当前选择下标,默认0开始
|
|||
|
|
current: {
|
|||
|
|
type: [Number, String],
|
|||
|
|
default: 0
|
|||
|
|
},
|
|||
|
|
// tabbar配置项
|
|||
|
|
tabbar: {
|
|||
|
|
type: Object,
|
|||
|
|
default () {
|
|||
|
|
return {}
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
// 是否固定在底部
|
|||
|
|
fixed: {
|
|||
|
|
type: Boolean,
|
|||
|
|
default: false
|
|||
|
|
},
|
|||
|
|
// 是否自动填充底部
|
|||
|
|
fill: {
|
|||
|
|
type: Boolean,
|
|||
|
|
default: false
|
|||
|
|
},
|
|||
|
|
// 显示层级
|
|||
|
|
zIndex: {
|
|||
|
|
type: [Number, String],
|
|||
|
|
default: 9999
|
|||
|
|
},
|
|||
|
|
// 是否自动规避底部安全距离
|
|||
|
|
safeBottom: {
|
|||
|
|
type: Boolean,
|
|||
|
|
default: true
|
|||
|
|
},
|
|||
|
|
// 切换时拦截事件(有兼容性,部分平台不支持)
|
|||
|
|
beforeChange: {
|
|||
|
|
type: Function,
|
|||
|
|
default: null
|
|||
|
|
},
|
|||
|
|
// 自定义tabbar高度,单位rpx
|
|||
|
|
tabbarHeight: {
|
|||
|
|
type: [Number, String],
|
|||
|
|
default: 100
|
|||
|
|
},
|
|||
|
|
// 自定义tabbar的name,方便读取默认tabbar
|
|||
|
|
name: {
|
|||
|
|
type: String,
|
|||
|
|
default: 'custom'
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
data() {
|
|||
|
|
return {
|
|||
|
|
isShowTabBar: false, //是否显示导航
|
|||
|
|
currentIndex: 0, //当前值
|
|||
|
|
beforeData: {}, //切换前值
|
|||
|
|
initTabbarConfig: {},
|
|||
|
|
isReload: false //uni不支持computed的get set,使用reload无感重置刷新
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
watch: {
|
|||
|
|
current(val) {
|
|||
|
|
this.currentIndex = val * 1
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
computed: {
|
|||
|
|
tabbarConfig() {
|
|||
|
|
const {
|
|||
|
|
isReload,
|
|||
|
|
native,
|
|||
|
|
initTabbarConfig: tabbar
|
|||
|
|
} = this
|
|||
|
|
if(isReload){}
|
|||
|
|
// 如果是native模式,直接读取执行
|
|||
|
|
if (native) {
|
|||
|
|
if (!tabbar) {
|
|||
|
|
console.error('Native mode, Pages.json no tabbar config')
|
|||
|
|
return {
|
|||
|
|
borderStyle: 'black',
|
|||
|
|
list: []
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return tabbar
|
|||
|
|
}
|
|||
|
|
// 如果没有配置tabbar,会尝试在缓存读取
|
|||
|
|
if (!tabbar.color) {
|
|||
|
|
return this._getDefaultTabbar()
|
|||
|
|
}
|
|||
|
|
return tabbar
|
|||
|
|
},
|
|||
|
|
tabbarList() {
|
|||
|
|
const {
|
|||
|
|
isReload,
|
|||
|
|
tabbarConfig: {
|
|||
|
|
list
|
|||
|
|
}
|
|||
|
|
} = this
|
|||
|
|
if(isReload){}
|
|||
|
|
if (!list) {
|
|||
|
|
console.error('No tabbar config')
|
|||
|
|
return []
|
|||
|
|
}
|
|||
|
|
return replaceTabbarList(list)
|
|||
|
|
},
|
|||
|
|
borderStyle() {
|
|||
|
|
const {
|
|||
|
|
isReload,
|
|||
|
|
borderStyle
|
|||
|
|
} = this.tabbarConfig
|
|||
|
|
if(isReload){}
|
|||
|
|
return borderStyle
|
|||
|
|
},
|
|||
|
|
tabbarBoxStyle() {
|
|||
|
|
const {
|
|||
|
|
isReload,
|
|||
|
|
zIndex
|
|||
|
|
} = this
|
|||
|
|
if(isReload){}
|
|||
|
|
return obj2strStyle({
|
|||
|
|
'z-index': zIndex,
|
|||
|
|
})
|
|||
|
|
},
|
|||
|
|
tabbarFillStyle() {
|
|||
|
|
const {
|
|||
|
|
isReload,
|
|||
|
|
tabbarHeight
|
|||
|
|
} = this
|
|||
|
|
if(isReload){}
|
|||
|
|
return obj2strStyle({
|
|||
|
|
'height': `${tabbarHeight}rpx`
|
|||
|
|
})
|
|||
|
|
},
|
|||
|
|
tabbarStyle() {
|
|||
|
|
const {
|
|||
|
|
isReload,
|
|||
|
|
tabbarHeight,
|
|||
|
|
tabbarConfig: {
|
|||
|
|
backgroundColor
|
|||
|
|
}
|
|||
|
|
} = this
|
|||
|
|
if(isReload){}
|
|||
|
|
return obj2strStyle({
|
|||
|
|
'height': `${tabbarHeight}rpx`,
|
|||
|
|
'background-color': backgroundColor || '#fff',
|
|||
|
|
})
|
|||
|
|
},
|
|||
|
|
tabbarItemStyle() {
|
|||
|
|
const {
|
|||
|
|
isReload,
|
|||
|
|
currentIndex,
|
|||
|
|
tabbarConfig: {
|
|||
|
|
color,
|
|||
|
|
selectedColor
|
|||
|
|
}
|
|||
|
|
} = this
|
|||
|
|
if(isReload){}
|
|||
|
|
return obj2strStyle({
|
|||
|
|
'color': currentIndex ? selectedColor : color
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
created() {
|
|||
|
|
const {
|
|||
|
|
tabbar,
|
|||
|
|
native
|
|||
|
|
} = this
|
|||
|
|
const pageTabbar = PageConfig.tabBar
|
|||
|
|
this.initTabbarConfig = native ? pageTabbar : tabbar
|
|||
|
|
// console.log(this.name)
|
|||
|
|
this._initTabbar()
|
|||
|
|
},
|
|||
|
|
methods: {
|
|||
|
|
_initTabbar() {
|
|||
|
|
const {
|
|||
|
|
current,
|
|||
|
|
fill,
|
|||
|
|
native,
|
|||
|
|
tabbarList
|
|||
|
|
} = this
|
|||
|
|
this.currentIndex = current * 1
|
|||
|
|
if (native && tabbarList.length > 0) {
|
|||
|
|
const currentPage = `/${getCurrentPages()[0].route}`
|
|||
|
|
const currentIndex = tabbarList.findIndex(item => item.pagePath === currentPage)
|
|||
|
|
this.currentIndex = currentIndex
|
|||
|
|
uni.hideTabBar()
|
|||
|
|
}
|
|||
|
|
setTimeout(() => {
|
|||
|
|
this.isShowTabBar = true
|
|||
|
|
})
|
|||
|
|
this._setTabbarStorage('tabbarConfig')
|
|||
|
|
},
|
|||
|
|
_setTabbarStorage(key){
|
|||
|
|
setTabbarStorage(key, this.tabbarConfig, this.name)
|
|||
|
|
this._setReload()
|
|||
|
|
},
|
|||
|
|
_getDefaultTabbar() {
|
|||
|
|
return getTabbarStorage('tabbarConfig', this.name)
|
|||
|
|
},
|
|||
|
|
_checkMaxIndex(index) {
|
|||
|
|
const {
|
|||
|
|
tabbarConfig: {
|
|||
|
|
list
|
|||
|
|
}
|
|||
|
|
} = this
|
|||
|
|
if (list.length < 1) {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
if (!list[index]) {
|
|||
|
|
console.error('Max tabbar index')
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
return true
|
|||
|
|
},
|
|||
|
|
_setReload(){
|
|||
|
|
this.isReload = true
|
|||
|
|
setTimeout(()=>{
|
|||
|
|
this.isReload = false
|
|||
|
|
})
|
|||
|
|
},
|
|||
|
|
setTabBarBadge(obj) {
|
|||
|
|
const {
|
|||
|
|
index,
|
|||
|
|
text
|
|||
|
|
} = obj
|
|||
|
|
if (this._checkMaxIndex(index)) {
|
|||
|
|
this.tabbarConfig.list[index].dot = text
|
|||
|
|
this._setTabbarStorage('tabbarConfig')
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
setTabBarItem(obj) {
|
|||
|
|
const {
|
|||
|
|
index,
|
|||
|
|
text,
|
|||
|
|
pagePath: newPagePath,
|
|||
|
|
iconPath,
|
|||
|
|
selectedIconPath
|
|||
|
|
} = obj
|
|||
|
|
const {
|
|||
|
|
pagePath: oldPagePath
|
|||
|
|
} = this.tabbarConfig.list[index]
|
|||
|
|
if (this._checkMaxIndex(index)) {
|
|||
|
|
this.tabbarConfig.list[index] = {
|
|||
|
|
pagePath: newPagePath ? newPagePath : oldPagePath,
|
|||
|
|
text,
|
|||
|
|
iconPath,
|
|||
|
|
selectedIconPath
|
|||
|
|
}
|
|||
|
|
this._setTabbarStorage('tabbarConfig')
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
showTabBar() {
|
|||
|
|
this.isShowTabBar = true
|
|||
|
|
},
|
|||
|
|
hideTabBar() {
|
|||
|
|
this.isShowTabBar = false
|
|||
|
|
},
|
|||
|
|
reload(){
|
|||
|
|
this.initTabbarConfig = this._getDefaultTabbar()
|
|||
|
|
},
|
|||
|
|
_tabChange(index) {
|
|||
|
|
const {
|
|||
|
|
currentIndex
|
|||
|
|
} = this
|
|||
|
|
this.$emit('click', index)
|
|||
|
|
if (index === currentIndex) {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
this.beforeData = {
|
|||
|
|
newIndex: index,
|
|||
|
|
oldIndex: currentIndex,
|
|||
|
|
next: this.jumpPage
|
|||
|
|
}
|
|||
|
|
if (this.beforeChange) {
|
|||
|
|
this.beforeChange(this._jumpPage, index)
|
|||
|
|
} else {
|
|||
|
|
this._jumpPage()
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
_jumpPage() {
|
|||
|
|
const {
|
|||
|
|
native,
|
|||
|
|
beforeData: {
|
|||
|
|
newIndex: index
|
|||
|
|
},
|
|||
|
|
tabbarList: list
|
|||
|
|
} = this
|
|||
|
|
const {
|
|||
|
|
pagePath: url,
|
|||
|
|
openType
|
|||
|
|
} = list[index]
|
|||
|
|
if (url) {
|
|||
|
|
if (native) {
|
|||
|
|
uni.switchTab({
|
|||
|
|
url
|
|||
|
|
})
|
|||
|
|
} else {
|
|||
|
|
if (openType !== 'navigate') {
|
|||
|
|
this.currentIndex = index
|
|||
|
|
}
|
|||
|
|
switch (openType) {
|
|||
|
|
case 'navigate':
|
|||
|
|
uni.navigateTo({
|
|||
|
|
url
|
|||
|
|
})
|
|||
|
|
break;
|
|||
|
|
case 'redirect':
|
|||
|
|
uni.redirectTo({
|
|||
|
|
url
|
|||
|
|
})
|
|||
|
|
break;
|
|||
|
|
case 'reLaunch':
|
|||
|
|
uni.reLaunch({
|
|||
|
|
url
|
|||
|
|
})
|
|||
|
|
break;
|
|||
|
|
case 'switchTab':
|
|||
|
|
uni.switchTab({
|
|||
|
|
url
|
|||
|
|
})
|
|||
|
|
break;
|
|||
|
|
case 'navigateBack':
|
|||
|
|
uni.navigateBack({
|
|||
|
|
delta: 1
|
|||
|
|
})
|
|||
|
|
break;
|
|||
|
|
default:
|
|||
|
|
uni.reLaunch({
|
|||
|
|
url
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
this.$emit('change', index)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style lang="scss" scoped>
|
|||
|
|
.m-tabbar-box {
|
|||
|
|
position: relative;
|
|||
|
|
z-index: 9999;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.m-tabbar {
|
|||
|
|
position: relative;
|
|||
|
|
|
|||
|
|
&.fixed {
|
|||
|
|
position: fixed;
|
|||
|
|
bottom: 0;
|
|||
|
|
left: 0;
|
|||
|
|
width: 100vw;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
&__safe {
|
|||
|
|
padding-bottom: env(safe-area-inset-bottom);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.m-tabbar__fill {
|
|||
|
|
pointer-events: none;
|
|||
|
|
opacity: 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.m-tabbar__flex {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: row;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.m-tabbar__border {
|
|||
|
|
background-color: rgba(0, 0, 0, 0.33);
|
|||
|
|
width: 100%;
|
|||
|
|
height: 1rpx;
|
|||
|
|
transform: scaleY(0.5);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.m-tabbar__item {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
align-items: center;
|
|||
|
|
flex: 1;
|
|||
|
|
padding: 4px 0 2px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.m-tabbar__icon {
|
|||
|
|
width: 48rpx;
|
|||
|
|
height: 48rpx;
|
|||
|
|
margin-bottom: 6rpx;
|
|||
|
|
position: relative;
|
|||
|
|
|
|||
|
|
&_img {
|
|||
|
|
display: block;
|
|||
|
|
width: 100%;
|
|||
|
|
height: 100%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.m-tabbar__badge {
|
|||
|
|
color: #fff;
|
|||
|
|
background-color: #f00;
|
|||
|
|
border-radius: 20rpx;
|
|||
|
|
font-size: 22rpx;
|
|||
|
|
position: absolute;
|
|||
|
|
right: -25rpx;
|
|||
|
|
left: 40rpx;
|
|||
|
|
padding: 2rpx 0;
|
|||
|
|
width: 100%;
|
|||
|
|
text-align: center;
|
|||
|
|
white-space: nowrap;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.m-tabbar__label {
|
|||
|
|
font-size: 24rpx;
|
|||
|
|
}
|
|||
|
|
</style>
|