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.

238 lines
7.5 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view class="y-tab__pane" :data-index="index" :class="[uniquePaneClass, paneClass]" :style="[paneStyle]"
@touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd">
<!-- -->
<view class="y-tab__pane--wrap" v-if="rendered ? true : active">
<slot></slot>
</view>
</view>
</template>
<script>
import { isNull, toClass, getUid } from '../js/uitls';
import { touchMixin } from '../js/touchMixin';
export default {
name: 'yTab',
mixins: [touchMixin],
options: {
// 微信小程序中 options 选项
styleIsolation: 'apply-shared', // [微信小程序] 启动样式隔离。当使用页面自定义组件,希望父组件影响子组件样式时可能需要配置。具体配置选项参见:微信小程序自定义组件的样式
virtualHost: true // [微信小程序、支付宝小程序(默认值为 true] 将自定义节点设置成虚拟的更加接近Vue组件的表现。我们不希望自定义组件的这个节点本身可以设置样式、响应 flex 布局等,而是希望自定义组件内部的第一层节点能够响应 flex 布局或者样式由自定义组件本身完全决定
// 微信可以使用virtualHost配置/QQ/百度/字节跳动这四家小程序自定义组件在渲染时会比App/H5端多一级节点导致flex无效是否考虑在组件上增加class控制
},
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,
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是否与参照节点布局区域相离
};
},
computed: {
computedName() {
return !isNull(this.name) ? this.name : this.index;
},
// test
innerStyle() {
return {
position: 'absolute',
width: "100%",
height: "100%",
transform: `translate(${100 * this.index}%, 0px) translateZ(0px)`
}
},
// 保证唯一的样式
uniquePaneClass() {
return 'y-tab__pane' + getUid()
},
// 内容class
paneClass() {
return toClass({ 'is-active': this.active, 'is-scrollspy': this.scrollspy });
},
},
watch: {
$props: {
deep: true,
// immediate: true,
handler(newValue, oldValue) {
// 触发导航变化事件
if (this.parent) {
this.parent.updateTab({
newValue: { ...newValue, badge: this.formatBadge() },
oldValue: oldValue && { ...oldValue },
index: this.index
});
}
}
}
},
created() {
this.parent = this.getParent();
if (this.parent) {
this.parent.children.push(this);
this.parent.putTab({ newValue: { ...this.$props, badge: this.formatBadge() } });
}
},
mounted() {
// this.$nextTick(() => {
// this.observePane();
// });
},
// #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() {
return new Promise((resolve, reject) => {
this.getSelectorQuery()
.select(`.${this.uniquePaneClass}`)
.boundingClientRect()
.exec(rect => {
console.log(rect);
resolve(rect[0] || {});
});
});
},
// 卸载组件的处理
unInit() {
this.disconnectObserver('paneObserver'); //销毁观察器
if (this.parent) {
const index = this.parent.children.findIndex(item => item === this);
this.parent.children.splice(index, 1);
this.parent.tabs.splice(index, 1);
this.parent.tabRects.splice(index, 1);
}
},
//获取父元素实例
getParent(name = 'yTabs') {
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(observerName) {
const observer = this[observerName];
observer && observer.disconnect();
},
// 观察 - 标签内容滚动时定位标签项
async observePane(top) {
if (!this.scrollspy) return //非滚动导航时不创建观察器
this.disconnectObserver('paneObserver');
const paneObserver = uni.createIntersectionObserver(this, { thresholds: [0, 1] });
// 注意如果是区域滚动pane区域随着页面滚动了一部分那么y-tabs__content的top就会变化导致交互区域位置不准确
// 最好不要有这种交互布局不像Android有嵌套滑动机制页面滑动到底后无法将事件分发给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);
// 如果目标节点布局区域小于参照节点布局区域top,则说明目标节点在参照节点布局区域之上,intersectionRatio小于等于0则说明两者不相交了
this.isDisjoint = res.intersectionRatio <= 0 && res.boundingClientRect.top < res.relativeRect.top;
// 标签栏点击时触发的滚动不允许设置激活下标
if (!this.parent.lockedScrollspy) this.parent.setActivedIndexToScroll();
});
this.paneObserver = paneObserver;
},
}
};
</script>
<style lang="less" scoped>
@import url('../css/index.less');
</style>