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.

316 lines
11 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>
<!-- 支付宝小程序使用$u.getRect()获取组件的根元素尺寸所以在外面套一个"壳" -->
<view>
<view class="u-index-bar">
<slot />
<view v-if="showSidebar" class="u-index-bar__sidebar" @touchstart.stop.prevent="onTouchMove" @touchmove.stop.prevent="onTouchMove"
@touchend.stop.prevent="onTouchStop" @touchcancel.stop.prevent="onTouchStop">
<view v-for="(item, index) in indexList" :key="index" class="u-index-bar__index" :style="{zIndex: zIndex + 1, color: activeAnchorIndex === index ? activeColor : ''}"
:data-index="index">
{{ item }}
</view>
</view>
<view class="u-indexed-list-alert" v-if="touchmove && indexList[touchmoveIndex]" :style="{
zIndex: alertZIndex
}">
<text>{{indexList[touchmoveIndex]}}</text>
</view>
</view>
</view>
</template>
<script>
var indexList = function() {
var indexList = [];
var charCodeOfA = 'A'.charCodeAt(0);
for (var i = 0; i < 26; i++) {
indexList.push(String.fromCharCode(charCodeOfA + i));
}
return indexList;
};
/**
* indexList 索引列表
* @description 通过折叠面板收纳内容区域,搭配<u-index-anchor>使用
* @tutorial https://www.uviewui.com/components/indexList.html#indexanchor-props
* @property {Number String} scroll-top 当前滚动高度,自定义组件无法获得滚动条事件,所以依赖接入方传入
* @property {Array} index-list 索引字符列表数组默认A-Z
* @property {Number String} z-index 锚点吸顶时的层级默认965
* @property {Boolean} sticky 是否开启锚点自动吸顶默认true
* @property {Number String} offset-top 锚点自动吸顶时与顶部的距离默认0
* @property {String} highlight-color 锚点和右边索引字符高亮颜色(默认#2979ff
* @event {Function} select 选中右边索引字符时触发
* @example <u-index-list :scrollTop="scrollTop"></u-index-list>
*/
export default {
name: "u-index-list",
props: {
sticky: {
type: Boolean,
default: true
},
zIndex: {
type: [Number, String],
default: ''
},
scrollTop: {
type: [Number, String],
default: 0,
},
offsetTop: {
type: [Number, String],
default: 0
},
indexList: {
type: Array,
default () {
return indexList()
}
},
activeColor: {
type: String,
default: '#2979ff'
}
},
created() {
// #ifdef H5
this.stickyOffsetTop = this.offsetTop ? uni.upx2px(this.offsetTop) : 44;
// #endif
// #ifndef H5
this.stickyOffsetTop = this.offsetTop ? uni.upx2px(this.offsetTop) : 0;
// #endif
// 只能在created生命周期定义children如果在data定义会因为循环引用而报错
this.children = [];
},
data() {
return {
activeAnchorIndex: 0,
showSidebar: true,
// children: [],
touchmove: false,
touchmoveIndex: 0,
}
},
watch: {
scrollTop() {
this.updateData()
}
},
computed: {
// 弹出toast的z-index值
alertZIndex() {
return this.$u.zIndex.toast;
}
},
methods: {
updateData() {
this.timer && clearTimeout(this.timer);
this.timer = setTimeout(() => {
this.showSidebar = !!this.children.length;
this.setRect().then(() => {
this.onScroll();
});
}, 0);
},
setRect() {
return Promise.all([
this.setAnchorsRect(),
this.setListRect(),
this.setSiderbarRect()
]);
},
setAnchorsRect() {
return Promise.all(this.children.map((anchor, index) => anchor
.$uGetRect('.u-index-anchor-wrapper')
.then((rect) => {
Object.assign(anchor, {
height: rect.height,
top: rect.top
});
})));
},
setListRect() {
return this.$uGetRect('.u-index-bar').then((rect) => {
Object.assign(this, {
height: rect.height,
top: rect.top + this.scrollTop
});
});
},
setSiderbarRect() {
return this.$uGetRect('.u-index-bar__sidebar').then(rect => {
this.sidebar = {
height: rect.height,
top: rect.top
};
});
},
getActiveAnchorIndex() {
const {
children
} = this;
const {
sticky
} = this;
for (let i = this.children.length - 1; i >= 0; i--) {
const preAnchorHeight = i > 0 ? children[i - 1].height : 0;
const reachTop = sticky ? preAnchorHeight : 0;
if (reachTop >= children[i].top) {
return i;
}
}
return -1;
},
onScroll() {
const {
children = []
} = this;
if (!children.length) {
return;
}
const {
sticky,
stickyOffsetTop,
zIndex,
scrollTop,
activeColor
} = this;
const active = this.getActiveAnchorIndex();
this.activeAnchorIndex = active;
if (sticky) {
let isActiveAnchorSticky = false;
if (active !== -1) {
isActiveAnchorSticky =
children[active].top <= 0;
}
children.forEach((item, index) => {
if (index === active) {
let wrapperStyle = '';
let anchorStyle = {
color: `${activeColor}`
};
if (isActiveAnchorSticky) {
wrapperStyle = {
height: `${children[index].height}px`
};
anchorStyle = {
position: 'fixed',
top: `${stickyOffsetTop}px`,
zIndex: `${zIndex ? zIndex : this.$u.zIndex.indexListSticky}`,
color: `${activeColor}`
};
}
item.active = active;
item.wrapperStyle = wrapperStyle;
item.anchorStyle = anchorStyle;
} else if (index === active - 1) {
const currentAnchor = children[index];
const currentOffsetTop = currentAnchor.top;
const targetOffsetTop = index === children.length - 1 ?
this.top :
children[index + 1].top;
const parentOffsetHeight = targetOffsetTop - currentOffsetTop;
const translateY = parentOffsetHeight - currentAnchor.height;
const anchorStyle = {
position: 'relative',
transform: `translate3d(0, ${translateY}px, 0)`,
zIndex: `${zIndex ? zIndex : this.$u.zIndex.indexListSticky}`,
color: `${activeColor}`
};
item.active = active;
item.anchorStyle = anchorStyle;
} else {
item.active = false;
item.anchorStyle = '';
item.wrapperStyle = '';
}
});
}
},
onTouchMove(event) {
this.touchmove = true;
const sidebarLength = this.children.length;
const touch = event.touches[0];
const itemHeight = this.sidebar.height / sidebarLength;
let clientY = 0;
clientY = touch.clientY;
let index = Math.floor((clientY - this.sidebar.top) / itemHeight);
if (index < 0) {
index = 0;
} else if (index > sidebarLength - 1) {
index = sidebarLength - 1;
}
this.touchmoveIndex = index;
this.scrollToAnchor(index);
},
onTouchStop() {
this.touchmove = false;
this.scrollToAnchorIndex = null;
},
scrollToAnchor(index) {
if (this.scrollToAnchorIndex === index) {
return;
}
this.scrollToAnchorIndex = index;
const anchor = this.children.find((item) => item.index === this.indexList[index]);
if (anchor) {
this.$emit('select', anchor.index);
uni.pageScrollTo({
duration: 0,
scrollTop: anchor.top + this.scrollTop
});
}
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-index-bar {
position: relative
}
.u-index-bar__sidebar {
position: fixed;
top: 50%;
right: 0;
@include vue-flex;
flex-direction: column;
text-align: center;
transform: translateY(-50%);
user-select: none;
z-index: 99;
}
.u-index-bar__index {
font-weight: 500;
padding: 8rpx 18rpx;
font-size: 22rpx;
line-height: 1
}
.u-indexed-list-alert {
position: fixed;
width: 120rpx;
height: 120rpx;
right: 90rpx;
top: 50%;
margin-top: -60rpx;
border-radius: 24rpx;
font-size: 50rpx;
color: #fff;
background-color: rgba(0, 0, 0, 0.65);
@include vue-flex;
justify-content: center;
align-items: center;
padding: 0;
z-index: 9999999;
}
.u-indexed-list-alert text {
line-height: 50rpx;
}
</style>