You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

467 lines
12 KiB
Vue

1 year ago
<!-- 导航工具栏 -->
<template>
<view :style="{
width: position != 'static' ? wininfo.windowWidth - offsetLeft * 2 + 'px' : 'auto',
left: position != 'static' ? offsetLeft + 'px' : '0px',
top: position == 'top' ? top_px + 'px' : 'auto',
bottom: position == 'bottom' ? bottom_px + 'px' : 'auto'
}" class="tm-bottomnavigation " :class="[
black_tmeme ? 'grey-darken-5' : '',
black_tmeme ? 'bk' : '',
'round-' + round,
bgTheme,
position,
border === 'top' ? 'border-t-1' : '',
border === 'bottom' ? 'border-b-1' : '',
'sheetIDX'
]">
<view :style="{ background: bgColor }" class=" flex-between py-10">
<block v-for="(item, index) in listDate" :key="index">
<view :style="{
width: listLen + '%'
}" class="flex-center" :key="index" :class="[active_selecindex == index && ani == true ? ` ani ` : ``]">
<tm-button :key="index" titl height="100%" :width="btnwidth"
:iconSize="item['iconSize'] ? item['iconSize'] : 38"
:fontSize="item['fontSize'] ? item['fontSize'] : 20" :vertical="vertical" :icon="active_selecindex==index?item.icon:(item['noIcon']||item.icon)"
:label="item.value" block :theme="active_selecindex == index ? iconColor : iconColorGrey"
:black="black_tmeme" @click.stop="onclick($event, index)" item-class="noGutter "
:font-color="active_selecindex == index ? iconColor : iconColorGrey">
<template v-slot:icon="{ data }">
<!-- #ifdef MP -->
<text v-if="false">{{offsetLeft}}</text>
<!-- #endif -->
<view style="height: 52rpx;">
<view v-if="!item['custom']" >
<view v-if="item.showDot" class="relative fulled" style="z-index: 10;">
<tm-badges :color="item.dot['color']||iconColor" v-if="!item.dot['dot']&&!item.dot['icon']" :label="item.dot.num"
:offset="[10, 0]" :dot="item.dot.dot"></tm-badges>
<tm-badges :color="item.dot['color']||iconColor" v-if="item.dot['dot']&&!item.dot['icon']"></tm-badges>
<tm-badges :color="item.dot['color']||iconColor" :offset="[10,0]" :icon="item.dot['icon']" v-if="item.dot['icon']"></tm-badges>
</view>
<tm-icons :black="black_tmeme"
:color="active_selecindex == index ? iconColor : iconColorGrey" :size="data.size"
:name="data.icon"></tm-icons>
</view>
<view v-if="item['custom'] === true"
class="tm-bottomnavigation-custom absolute flex-center" :style="{
width: '100%',
height: '100%',
minHeight: '100%',
top: '-50%',
left: '0',
flexShrink: 0
}">
<view
:class="[item['customColor'] ? item['customColor'] : (bgColor?'':'red'), black_tmeme ? 'bk' : '']"
:style="{width: '90rpx',height: '90rpx',background:bgColor?bgColor:''}" class="rounded flex-center">
<tm-icons :black="black_tmeme" :color="item['customFontColor'] || iconColorGrey"
:size="data.size" :name="data.icon"></tm-icons>
</view>
</view>
</view>
</template>
<template v-slot:default="{data}">
<text :class="[active_selecindex == index ? 'ani' : '']">{{ data }}</text>
</template>
</tm-button>
</view>
</block>
</view>
<!-- 安全区域的高度 -->
<view v-if="safe" :style="{ height: safeBottomeHeight + 'px',background: bgColor }"></view>
</view>
<!--
list:[{...}],基础属性为{icon:"图标或者图片",value:"标题"}
全部属性为除了上述基本两个属性其它全为可选
{
icon: 'icon-user-fill',
noIcon:'',//未选中时的图标不提供默认使用相同的图标icon
value: '个人中心',
iconSize: 38,
fontSize: 20,
showDot:false,//是否显示角标。
dot:{
dot:false,//是否显示点。如果是点则不显示Num
num:"",//是否显示数字。
ico:"",//是否显示图标优先级高于dot,num.
},
path: ''
}
-->
</template>
<script>
/**
* 底部导航工具条
* @property {Array} list = [] 默认[],基本属性{icon:"图标或者图片",value:"标题"}
* @property {String} bg-theme = [] 默认"white",背景颜色主题名称
* @property {String | Boolean} black = [true|false] 默认null,暗黑模式
* @property {String} bg-color = [] 默认'',自定义背景颜色
* @property {String} icon-color = [] 默认使用主题色,项目默认激活色
* @property {String} icon-color-grey = [] 默认使用主题色,项目失去焦点时的颜色
* @property {String} position = [bottom|top|static] 默认bottom,可选位置bottom|top|static
* @property {Number|String} top = [] 默认0,距离顶部的距离只有在position=='top'使用
* @property {Number|String} bottom = [] 默认0,距离底部的距离只有在position=='bottom'使用
* @property {String} border = [top|bottom] 默认top,显示上边线还是下边线可选top / bottom
* @property {String|Boolean} vertical = [true|false] 默认true,文字方向默认是竖向否则为横向
* @property {String|Number} offset-left = [] 默认0,偏移量即离左边的间距如果提供自动居中出现两边间隙
* @property {String|Boolean} safe = [true|false] 默认true,// 是否开启底部安全区域。
* @property {String|Boolean} auto-selected = [true|false] 默认true,// 是否开启自动匹配页面选中底部按钮。
* @property {Function} change 切换按钮时触发
*
*/
import tmButton from "@/tm-vuetify/components/tm-button/tm-button.vue"
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
import tmBadges from "@/tm-vuetify/components/tm-badges/tm-badges.vue"
export default {
components: {
tmButton,
tmIcons,
tmBadges
},
name: 'tm-bottomnavigation',
props: {
list: {
Array,
default: () => {
return [];
}
},
// 背景颜色主题名称
bgTheme: {
type: String,
default: 'white'
},
// 是否启用暗黑主题。
black: {
type: String | Boolean,
default: null
},
// 背景颜色,自定义。
bgColor: {
type: String,
default: ''
},
// 自定义项目文字默认激活色。
iconColor: {
type: String,
default: 'primary'
},
// 自定义项目文字默认失去焦点色。
iconColorGrey: {
type: String,
default: 'grey-lighten-1'
},
// 可选bootom / top / static
position: {
type: String,
default: 'bottom'
},
// 距离顶部的距离。默认是0,只有在position=='top'使用
top: {
type: Number | String,
default: 0
},
// 距离顶部的距离。默认是0,只有在position=='bottom'使用
bottom: {
type: Number | String,
default: 0
},
// 显示上边线还是下边线。可选top / bottom
border: {
type: String,
default: 'top'
},
// 文字方向:默认是竖向。否则为横向。
vertical: {
type: String | Boolean,
default: true
},
// 只支持圆角主题。如round-5
round: {
type: String | Number,
default: 0
},
// 偏移量。即离左边的间距。如果提供整个navbar的宽度会是100% - offsetLeft*2。
offsetLeft: {
type: String | Number,
default: 0
},
// 是否开启底部安全区域。
safe: {
type: String | Boolean,
default: true
},
// 是否开启点按动画,默认开启。
ani: {
type: String | Boolean,
default: true
},
// 是否自动匹配页面选中底部按钮?
autoSelected: {
type: String | Boolean,
default: true
},
activeIndex:{
type:Number,
default:0
}
},
computed: {
black_tmeme: function() {
if (this.black !== null) return this.black;
return this.$tm.vx.state().tmVuetify.black;
},
top_px: function() {
return this.top;
},
bottom_px: function() {
return this.bottom;
},
wininfo: function() {
return uni.getSystemInfoSync();
},
listLen: function() {
if (this.listDate.length == 0) return 1;
return 100 / this.listDate.length;
},
active_selecindex:function(){
if(!this.autoSelected) return this.slectedIndx;
return this.$tm.vx.state().tmVuetify.tmVueTifly_pagesIndex;
}
},
created() {
uni.hideTabBar({animation:false})
},
data() {
return {
slectedIndx: -1, //默认激活的值。
btnwidth: 0,
safeBottomeHeight: 0,
listDate: [],
pages:[]
};
},
watch:{
list:{
deep:true,
handler(){
let t = this;
this.$nextTick(function() {
let qr = uni.createSelectorQuery().in(this);
qr.select('.sheetIDX')
.boundingClientRect()
.exec(e => {
t.btnwidth = e[0].width / 5 + 'px';
t.listItem();
});
});
}
},
activeIndex:function(){
if(!this.autoSelected) this.slectedIndx = this.activeIndex;
}
},
mounted() {
if(!this.autoSelected) this.slectedIndx = this.activeIndex;
setTimeout(function(){
uni.hideTabBar({animation:false})
},350)
let t = this;
this.$nextTick(function() {
uni.hideTabBar({animation:false})
let qr = uni.createSelectorQuery().in(this);
qr.select('.sheetIDX')
.boundingClientRect()
.exec(e => {
t.btnwidth = e[0].width / 5 + 'px';
t.listItem();
});
});
},
methods: {
autoxz(){
let sy = uni.getSystemInfoSync();
// #ifdef MP
this.safeBottomeHeight = sy.screenHeight - sy.safeArea.bottom;
// #endif
// #ifdef H5
this.safeBottomeHeight = sy.windowHeight - sy.safeArea.height;
// #endif
// #ifdef APP
this.safeBottomeHeight = Math.abs(sy.safeArea.bottom - sy.safeArea.height);
// #endif
if(!this.autoSelected){
return;
}
let pageRoute = this.$tm.vx.state().tmVuetify.tmVueTifly_pages;
let index = this.listDate.findIndex((el,index)=>{
let url = el?.path||"";
url = url.split('?')[0]||"";
return url == pageRoute
})
if(index>-1){
this.slectedIndx = index;
this.$tm.vx.commit('setPageNowIndex',index)
this.$emit('change', {
index: index,
item: this.listDate[index]
});
}
},
listItem() {
let mod = {
icon: 'icon-user-fill',
value: '个人中心',
iconSize: 38,
fontSize: 20,
showDot: false, //是否显示角标。
dot: {
dot: false, //是否显示点。如果是点则不显示Num
num: '', //是否显示数字。
ico: '' //是否显示图标优先级高于dot,num.
},
path: '',
openType:'switchTab'
};
this.$nextTick(function() {
let tm = [];
let ls = this.$tm.deepClone(this.list);
ls.forEach((item, index) => {
tm.push({
...mod,
...item
});
});
this.listDate = tm;
this.autoxz();
});
},
onclick(e, index) {
let t = this;
this.slectedIndx = index;
this.$tm.vx.commit('setPageNowIndex',index)
let item = this.listDate[index];
this.$emit('change', {
index: index,
item: item
});
if (item?.path) {
let oldPath = item['path']||"";
oldPath = oldPath.split("?")[0]||"";
let pages = getCurrentPages();
let url = pages[pages.length-1].route;
if(url[0]!='/') url = '/' + url;
url = url.split('?')[0]||"";
if(url==oldPath) return;
// #ifdef MP || APP
try{
uni.vibrateShort()
}catch(e){
// ...
}
// #endif
switch (item['openType']) {
case 'switchTab':
uni.switchTab({
url: item.path,
fail: (e) => {
console.log(e);
}
})
break;
case 'redirect':
uni.redirectTo({
url: item.path
})
break;
case 'reLaunch':
uni.reLaunch({
url: item.path
})
break;
case 'navigateBack':
uni.navigateBack({
url: item.path
})
break;
default:
uni.navigateTo({
url: item.path
})
break;
}
}
}
}
};
</script>
<style>
page,
body {
padding-bottom: 167rpx;
}
</style>
<style lang="scss" scoped>
.tm-bottomnavigation {
// animation: scalse 0.4s linear;
&.bottom {
position: fixed;
bottom: 0;
left: 0;
z-index: 450;
}
&.top {
position: fixed;
z-index: 450;
top: 0;
left: 0;
}
}
.ani {
animation: scalse 0.4s linear;
}
@keyframes scalse {
0% {
transform: scale(0.9);
}
50% {
transform: scale(1.1);
}
100% {
transform: scale(1);
}
}
</style>