SmartBeehive/uni_modules/y-tabs/components/y-tab/y-tab.vue

253 lines
9.2 KiB
Vue
Raw Normal View History

2024-06-21 18:04:01 +08:00
<template>
<view class="y-tab__pane" :data-index="index" :class="[uniquePaneClass,paneClass]" :style="[paneStyle]">
<!-- 渲染过的则不再渲染未渲染的根据激活状态进行渲染 -->
<view class="y-tab__pane--wrap" v-if="rendered ? true : active">
<slot />
</view>
</view>
</template>
<script>
/**
* y-tab 标签
* @description 选项卡组件的子组件搭配y-tabs使用
* @property {String | Number} name 标签名称作为匹配的标识符默认为标签的索引值
* @property {String} title 标题
* @property {Boolean} disabled 是否禁用标签默认false
* @property {String} dot 是否在标题右上角显示小红点优先级高于badge默认false
* @property {String | Number} badge 图标右上角徽标的内容
* @property {String | Number} badge-max-count 徽标数最大数字限制超过这个数字将变成badgeMaxCount+如果传空字符串则不设置默认99
* @property {Object} title-style 自定义标题样式
* @property {Boolean} title-class 自定义标题类名
* @property {String} icon-type 图标图案为uniapp扩展组件uni-ui下的uni-icons的type值customPrefix用法等同
* @property {String | Number} icon-size 图标大小默认16
* @property {String} custom-prefix 自定义图标
* @property {String} image-src 图片路径
* @property {String} image-mode 图片裁剪缩放的模式为uniapp内置组件->媒体组件>image下的mode属性的可选值
* @property {String} position 在有图标或图片的情况下标题围绕它们所在的位置默认right可选值lefttopbottom
*/
import { isNull, toClass, getUid } from '../js/uitls';
import { options } from '../js/const';
export default {
name: 'y-tab',
options,
props: {
title: String, // 标题
disabled: Boolean, // 是否禁用标签
dot: Boolean, // 是否在标题右上角显示小红点
badge: {
type: [Number, String],
default: ''
}, // 图标右上角徽标的内容
// 徽标数最大数字限制,超过这个数字将变成badgeMaxCount+,如果传空字符串则不设置
badgeMaxCount: {
type: [Number, String],
default: 99
},
name: [Number, String], // 标签名称,作为匹配的标识符
titleStyle: Object, // 自定义标题样式
titleClass: String, // 自定义标题类名
iconType: String, //图标图案为uniapp扩展组件uni-ui下的uni-icons的type值customPrefix用法等同
iconSize: {
type: [Number, String],
default: 16
}, //图标大小
customPrefix: String, //自定义图标
imageSrc: String, //图片路径
imageMode: {
type: String,
default: 'scaleToFill',
validator(value) {
return [
'scaleToFill',
'aspectFit',
'aspectFill',
'widthFix',
'heightFix',
'top',
'bottom',
'center',
'left',
'right',
'top left',
'top right',
'bottom left',
'bottom right'
].includes(value);
}
}, //图片裁剪、缩放的模式为uniapp内置组件->媒体组件—>image下的mode值
position: {
type: String,
default: 'right',
validator(value) {
return ['top', 'bottom', 'left', 'right'].includes(value);
}
} //如果存在图片或图标,标题围绕它们的位置
},
data() {
return {
isUnmounted: false,
index: -1, //内容卡片对应的下标
// parent: null, //父元素实例
active: false, //是否为激活状态
rendered: false, //是否渲染过
swipeable: false, //是否开启手势滑动切换
paneStyle: null, //内容样式
scrollspy: false, //是否为滚动导航模式
// paneObserver: null, //pane交叉观察器
isDisjoint: false, //当前pane是否与参照节点布局区域相离
isActiveLast: false // 最后一个pane在滚动导航模式下是否激活对应的标签项
};
},
computed: {
computedName() {
return !isNull(this.name) ? this.name : this.index;
},
unqieKey() {
return getUid();
},
// 保证唯一的样式
uniquePaneClass() {
return 'y-tab__pane' + this.unqieKey
},
// 内容class
paneClass() {
return toClass({ 'is-active': this.active, 'is-scrollspy': this.scrollspy });
},
},
watch: {
$props: {
deep: true,
// immediate: true,
handler(newValue, oldValue) {
// 更新tab
if (this.parent) {
this.parent.updateTab({
newValue: { ...newValue, badge: this.formatBadge() },
oldValue: oldValue && { ...oldValue },
index: this.index
});
}
}
}
},
created() {
this.parent = this.getParent();
},
mounted() {
if (!this.parent) return;
if (this.parent.childrens.indexOf(this) === -1) this.parent.childrens.push(this);
this.parent.putTab({ newValue: { ...this.$props, key: this.unqieKey, badge: this.formatBadge() } });
this.scrollspy = this.parent.scrollspy; // 是否为滚动导航
this.rendered = !this.parent.isLazyRender || this.scrollspy; //标记是否渲染过,非懒加载与滚动导航模式下默认渲染
},
// #ifndef VUE3
destroyed() {
if (this.isUnmounted) return;
this.unInit();
},
// #endif
// #ifdef VUE3
unmounted() {
this.isUnmounted = true;
this.unInit();
},
// #endif
methods: {
// 徽标格式化
formatBadge() {
if (!isNull(this.badge) && !isNull(this.badgeMaxCount) && this.badge > this.badgeMaxCount) {
return this.badgeMaxCount + '+';
} else {
return this.badge
}
},
// 获取查询节点信息的对象
getSelectorQuery() {
let query = null;
// #ifdef MP-ALIPAY
query = uni.createSelectorQuery();
// #endif
// #ifndef MP-ALIPAY
query = uni.createSelectorQuery().in(this);
// #endif
return query;
},
// 获取元素位置信息
getRect(selector) {
return new Promise((resolve, reject) => {
selector = `.${this.uniquePaneClass}` + (!isNull(selector) ? " " + selector : '')
this.getSelectorQuery()
.select(selector)
.boundingClientRect()
.exec(rect => {
resolve(rect[0] || {});
});
});
},
// 卸载组件的处理
unInit() {
this.disconnectObserver(); //销毁观察器
if (this.parent) {
const index = this.parent.childrens.findIndex(item => item === this);
this.parent.childrens.splice(index, 1);
this.parent.tabs.splice(index, 1);
this.parent.tabRects.splice(index, 1);
}
},
//获取父元素实例
getParent(name = 'y-tabs') {
let parent = this.$parent;
let parentName = parent.$options.name;
while (parentName !== name) {
parent = parent.$parent;
if (!parent) return false;
parentName = parent.$options.name;
}
return parent;
},
// 断掉观察,释放资源
disconnectObserver() {
this.paneObserver && this.paneObserver?.disconnect();
},
// 观察 - 标签内容滚动时定位标签项
async observePane(top) {
this.disconnectObserver();
const paneObserver = uni.createIntersectionObserver(this, { thresholds: [0, 0.01, 0.99, 1] });
// 注意如果y-tabs使用的区域滚动整个页面的布局跟随页面滚动当pane跟随页面移动了之后
// 那么y-tabs__content的top就会变化导致交互区域位置不准确可以在onPageScroll使用定时器实现滚动结束的处理重新resize一下组件创建pane的监听
// 如果pane内容超过页面的可视区域最好舍弃这种交互布局uniapp未实现Android的嵌套滑动机制页面滑动到底后无法将事件分发给scroll-view使scroll-view继承滑动
paneObserver.relativeToViewport({ top: -top }); // 到屏幕顶部的高度时触发
// 不能观察根节点 unk-vendors.js:14596 [system] Node .y-tab__pane9 is not found. Intersection observer will not trigger.
paneObserver.observe(`.${this.uniquePaneClass} .y-tab__pane--wrap`, res => {
// console.log('res:', this.title, res);
if (!this.isActiveLast) {
// 如果目标节点布局区域的top小于参照节点的top,则说明目标节点在参照节点布局区域之上intersectionRatio不大于0则说明两者不相交
this.isDisjoint = res.intersectionRatio <= 0 && res.boundingClientRect.top < res
.relativeRect.top;
} else {
// 滚动导航模式下最后一个pane完成显示但未超出可视范围顶部时是否设置相离而激活最后一个标签项
this.isDisjoint = res.intersectionRatio > 0 && res.boundingClientRect.bottom <= res
.relativeRect.bottom;
}
// 保证组件初始化完成时执行避免创建时触发一次监听器的回调函数导致执行顺序先于tabs的init方法使底部条错位:
// 标签栏点击时触发的滚动不允许设置激活下标
if (this.parent.isLoaded && !this.parent.lockedScrollspy) this.parent
.setActivedIndexToScroll();
});
this.paneObserver = paneObserver;
},
}
};
</script>
<style lang="scss" scoped>
@import '../css/index.scss';
</style>