绩效考核手机版
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

280 lines
8.4 KiB

3 years ago
<template>
<view :id="$sUid" class="s-index-list" :class="custom_class" :style="custom_style">
<slot />
<!-- 右边索引滑块 -->
<view
v-if="indexList.length"
class="s-index-list__sidebar"
:style="sidebar_style"
@touchstart.stop.prevent="onTouchMove"
@touchmove.stop.prevent="onTouchMove"
@touchend.stop.prevent="onTouchStop"
@touchcancel.stop.prevent="onTouchStop"
>
<view
v-for="(value, index) of indexList"
:key="index"
class="s-index-list__index"
:class="{ 's-index-list__index--active': activeIndexValue === value }"
:style="{ color: activeIndexValue === value ? activeColor : '' }"
>
{{ value }}
</view>
</view>
<!-- 滑动索引时提示 -->
<view v-if="alertIndexValue" class="s-index-list__alert" :style="alert_style">{{ alertIndexValue }}</view>
</view>
</template>
<script>
import componentMixin from '../../mixins/componentMixin';
function getDefaultIndexList() {
const indexList = [];
const charCodeOfA = 'A'.charCodeAt(0);
for (let i = 0; i < 26; i++) {
indexList.push(String.fromCharCode(charCodeOfA + i));
}
return indexList;
};
/**
* s-index-list 索引列表
* @description 需要监听页面的onPageScroll事件给组件传入scrollTop
* @property {Array<String|Number>} indexList 索引字符列表默认[A-Z]
* @property {Boolean} sticky 是否开启锚点自动吸顶
* @property {Number|String} scrollTop 当前滚动高度自定义组件无法获得滚动事件所以依赖接入方传入
* @property {Number|String} offsetTop 锚点自动吸顶时与顶部的距离单位rpx
* @property {Number|String} zIndex 锚点吸顶时的层级
* @property {String} activeColor 锚点和右边索引字符高亮颜色
* @property {String|Object} sidebarStyle 右边索引条的样式
* @property {Boolean} showAlert 右边索引滑动时是否显示弹框提示
* @property {String|Object} alertStyle 弹框提示样式
* @event {Function} select 选中右边索引字符时触发
* @example <s-index-list :scroll-top="scrollTop"></s-index-list>
*/
export default {
name: 'SIndexList',
mixins: [componentMixin],
props: {
indexList: {
type: Array,
default: () => getDefaultIndexList(),
},
sticky: Boolean,
scrollTop: {
type: Number,
default: -1,
},
offsetTop: {
type: [Number, String],
default: 0,
},
zIndex: [Number, String],
activeColor: String,
sidebarStyle: [String, Object],
showAlert: {
type: Boolean,
default: true,
},
alertStyle: [String, Object],
},
data: () => ({
windowTop: 0,
activeAnchorIndex: -1,
touchmove: false,
touchmoveIndex: -1,
anchorIndexList: [],
}),
computed: {
offset_top() {
return this.$toPx(this.offsetTop);
},
stickyTop() {
return this.offset_top + this.windowTop;
},
nextZindex() {
const zIndex = parseInt(this.zIndex);
return zIndex > 0 ? zIndex + 1 : '';
},
sidebar_style() {
return this.$mergeStyle({
zIndex: this.nextZindex,
}, this.sidebarStyle);
},
alert_style() {
return this.$mergeStyle({
zIndex: this.nextZindex,
}, this.alertStyle);
},
alertIndexValue() {
return this.showAlert && this.touchmove ? this.indexList[this.touchmoveIndex] : '';
},
activeIndexValue() {
return this.anchorIndexList[this.activeAnchorIndex];
},
refreshState() {
return [
this.scrollTop,
this.offsetTop,
this.sticky,
this.indexList,
];
},
},
watch: {
refreshState() {
this.refresh();
},
},
created() {
const systemInfo = uni.getSystemInfoSync();
this.windowTop = systemInfo.windowTop || 0;
this.anchorList = [];
},
methods: {
clearTimer() {
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
},
refresh() {
this.clearTimer();
this.timer = setTimeout(() => {
this.timer = null;
this.setRect().then(this.onScroll);
});
},
setRect() {
return Promise.all([
this.setListRect(),
this.setSiderbarRect(),
this.setAnchorsRect(),
]);
},
setListRect() {
return this.$getRect(`#${this.$sUid}`).then(rect => {
Object.assign(this, {
height: rect.height,
top: rect.top + this.scrollTop,
});
});
},
setSiderbarRect() {
return this.$getRect(`#${this.$sUid} .s-index-list__sidebar`).then(rect => {
if (rect) {
this.sidebar = {
height: rect.height,
top: rect.top,
};
}
});
},
setAnchorsRect() {
return Promise.all(this.anchorList.map(anchor => {
return anchor.$getRect(`#${anchor.$sUid}`).then(rect => {
Object.assign(anchor, {
width: rect.width,
height: rect.height,
top: rect.top - this.offset_top,
});
});
}));
},
getActiveAnchorIndex() {
const { anchorList, sticky } = this;
for (let i = anchorList.length - 1; i >= 0; i--) {
const preAnchorHeight = i > 0 ? anchorList[i - 1].height : 0;
const reachTop = sticky ? preAnchorHeight : 0;
if (Math.floor(reachTop) >= Math.floor(anchorList[i].top)) {
return i;
}
}
return -1;
},
onScroll() {
const { scrollTop, sticky, stickyTop, anchorList } = this;
if (!anchorList.length) return;
const activeAnchorIndex = scrollTop > -1 ? this.getActiveAnchorIndex() : -1;
this.activeAnchorIndex = activeAnchorIndex;
if (sticky) {
const isActiveAnchorSticky = activeAnchorIndex > -1 ? anchorList[activeAnchorIndex].top <= 0 : false;
anchorList.forEach((item, index) => {
if (index === activeAnchorIndex) {
const wrapStyle = {};
const anchorStyle = {
color: this.activeColor,
};
if (isActiveAnchorSticky) {
Object.assign(wrapStyle, {
height: `${item.height}px`,
});
Object.assign(anchorStyle, {
position: 'fixed',
width: `${item.width}px`,
top: `${stickyTop}px`,
zIndex: this.zIndex,
});
}
item.active = true;
item.prevActive = false;
item.wrapStyle = wrapStyle;
item.anchorStyle = anchorStyle;
} else if (index === activeAnchorIndex - 1) {
const currentAnchor = anchorList[index];
const currentOffsetTop = currentAnchor.top;
const targetOffsetTop = index === anchorList.length - 1 ? this.top : anchorList[index + 1].top;
const parentOffsetHeight = targetOffsetTop - currentOffsetTop;
const translateY = parentOffsetHeight - currentAnchor.height;
const anchorStyle = {
position: 'relative',
transform: `translate3d(0, ${translateY}px, 0)`,
color: this.activeColor,
zIndex: this.zIndex,
};
item.active = true;
item.prevActive = true;
item.anchorStyle = anchorStyle;
} else {
item.active = false;
item.prevActive = false;
item.anchorStyle = '';
item.wrapStyle = '';
}
});
}
},
onTouchMove(event) {
const length = this.indexList.length;
if (!length) return;
this.touchmove = true;
const touch = event.touches[0];
const itemHeight = this.sidebar.height / length;
const clientY = touch.clientY;
const index = Math.max(0, Math.min(Math.floor((clientY - this.sidebar.top) / itemHeight), length - 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.anchorList.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" src="./index.scss"></style>