|
|
@@ -14,14 +14,18 @@
|
|
|
v-html="subitem.name"
|
|
|
></view>
|
|
|
</view>
|
|
|
+ <view class="nut-elevator__list__fixed" :style="fixedStyle" v-show="scrollY > 0" v-if="isSticky">
|
|
|
+ <span class="fixed-title">{{ indexList[currentIndex][acceptKey] }}</span>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
<view class="nut-elevator__code--current" v-show="scrollStart" v-if="indexList.length">{{
|
|
|
- indexList[currentIndex][acceptKey]
|
|
|
+ indexList[codeIndex][acceptKey]
|
|
|
}}</view>
|
|
|
<view class="nut-elevator__bars" @touchstart="touchStart" @touchmove.stop.prevent="touchMove" @touchend="touchEnd">
|
|
|
<view class="nut-elevator__bars__inner">
|
|
|
<view
|
|
|
class="nut-elevator__bars__inner__item"
|
|
|
+ :class="{ active: item[acceptKey] === indexList[currentIndex][acceptKey] }"
|
|
|
:data-index="index"
|
|
|
v-for="(item, index) in indexList"
|
|
|
:key="item[acceptKey]"
|
|
|
@@ -33,7 +37,7 @@
|
|
|
</view>
|
|
|
</template>
|
|
|
<script lang="ts">
|
|
|
-import { computed, reactive, toRefs, nextTick, ref, Ref, watch } from 'vue';
|
|
|
+import { computed, reactive, toRefs, nextTick, ref, Ref, watch, onMounted, onUnmounted } from 'vue';
|
|
|
import { createComponent } from '@/packages/utils/create';
|
|
|
import { useExpose } from '@/packages/utils/useExpose/index';
|
|
|
const { componentName, create } = createComponent('elevator');
|
|
|
@@ -57,14 +61,26 @@ export default create({
|
|
|
default: () => {
|
|
|
return [];
|
|
|
}
|
|
|
+ },
|
|
|
+ isSticky: {
|
|
|
+ type: [Boolean],
|
|
|
+ default: false
|
|
|
+ },
|
|
|
+ spaceHeight: {
|
|
|
+ type: [Number],
|
|
|
+ default: 23
|
|
|
+ },
|
|
|
+ titleHeight: {
|
|
|
+ type: [Number],
|
|
|
+ default: 35
|
|
|
}
|
|
|
},
|
|
|
emits: ['click-item', 'click-index'],
|
|
|
setup(props: any, context: any) {
|
|
|
- const spaceHeight = 23;
|
|
|
const listview: Ref<any> = ref(null);
|
|
|
const state = reactive({
|
|
|
anchorIndex: 0,
|
|
|
+ codeIndex: 0,
|
|
|
listHeight: [] as number[],
|
|
|
listGroup: [] as HTMLLIElement[],
|
|
|
touchState: {
|
|
|
@@ -74,7 +90,10 @@ export default create({
|
|
|
scrollStart: false,
|
|
|
currentIndex: 0,
|
|
|
currentData: {} as ElevatorData,
|
|
|
- currentKey: ''
|
|
|
+ currentKey: '',
|
|
|
+ scrollY: 0,
|
|
|
+ diff: -1,
|
|
|
+ fixedTop: 0
|
|
|
});
|
|
|
|
|
|
const classes = computed(() => {
|
|
|
@@ -84,18 +103,15 @@ export default create({
|
|
|
};
|
|
|
});
|
|
|
|
|
|
- //重置滚动参数
|
|
|
- const resetScrollState = () => {
|
|
|
- state.anchorIndex = 0;
|
|
|
- state.listHeight = [];
|
|
|
- state.listGroup = [];
|
|
|
- state.currentIndex = 0;
|
|
|
- state.scrollStart = false;
|
|
|
- state.touchState = {
|
|
|
- y1: 0,
|
|
|
- y2: 0
|
|
|
+ const fixedStyle = computed(() => {
|
|
|
+ return {
|
|
|
+ transform: `translate3d(0, ${state.scrollY + state.fixedTop}px, 0)`
|
|
|
};
|
|
|
- };
|
|
|
+ });
|
|
|
+
|
|
|
+ const clientHeight = computed(() => {
|
|
|
+ return listview.value.clientHeight;
|
|
|
+ });
|
|
|
|
|
|
const getData = (el: HTMLElement, name: string): string | void => {
|
|
|
const prefix = 'data-';
|
|
|
@@ -126,7 +142,7 @@ export default create({
|
|
|
}
|
|
|
if (index < 0) index = 0;
|
|
|
if (index > state.listHeight.length - 2) index = state.listHeight.length - 2;
|
|
|
- state.currentIndex = index;
|
|
|
+ state.codeIndex = index;
|
|
|
listview.value.scrollTo(0, state.listHeight[index]);
|
|
|
};
|
|
|
|
|
|
@@ -136,20 +152,20 @@ export default create({
|
|
|
let firstTouch = e.touches[0];
|
|
|
state.touchState.y1 = firstTouch.pageY;
|
|
|
state.anchorIndex = +index;
|
|
|
- state.currentIndex = +index;
|
|
|
+ state.codeIndex = +index;
|
|
|
scrollTo(+index);
|
|
|
};
|
|
|
|
|
|
const touchMove = (e: TouchEvent) => {
|
|
|
let firstTouch = e.touches[0];
|
|
|
state.touchState.y2 = firstTouch.pageY;
|
|
|
- let delta = ((state.touchState.y2 - state.touchState.y1) / spaceHeight) | 0;
|
|
|
- state.currentIndex = state.anchorIndex + delta;
|
|
|
- scrollTo(state.currentIndex);
|
|
|
+ let delta = ((state.touchState.y2 - state.touchState.y1) / props.spaceHeight) | 0;
|
|
|
+ state.codeIndex = state.anchorIndex + delta;
|
|
|
+ scrollTo(state.codeIndex);
|
|
|
};
|
|
|
|
|
|
const touchEnd = () => {
|
|
|
- resetScrollState();
|
|
|
+ state.scrollStart = false;
|
|
|
};
|
|
|
|
|
|
const handleClickItem = (key: string, item: ElevatorData) => {
|
|
|
@@ -162,6 +178,32 @@ export default create({
|
|
|
context.emit('click-index', key);
|
|
|
};
|
|
|
|
|
|
+ const listViewScroll = (e: Event) => {
|
|
|
+ let target = e.target as Element;
|
|
|
+ let scrollTop = target.scrollTop;
|
|
|
+ const listHeight = state.listHeight;
|
|
|
+ state.scrollY = scrollTop;
|
|
|
+ for (let i = 0; i < listHeight.length - 1; i++) {
|
|
|
+ let height1 = listHeight[i];
|
|
|
+ let height2 = listHeight[i + 1];
|
|
|
+ if (state.scrollY >= height1 && state.scrollY < height2) {
|
|
|
+ state.currentIndex = i;
|
|
|
+ state.diff = height2 - state.scrollY;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ state.currentIndex = listHeight.length - 2;
|
|
|
+ };
|
|
|
+
|
|
|
+ onMounted(() => {
|
|
|
+ listview.value.addEventListener('scroll', listViewScroll);
|
|
|
+ });
|
|
|
+
|
|
|
+ onUnmounted(() => {
|
|
|
+ listview.value.removeEventListener('scroll', listViewScroll);
|
|
|
+ });
|
|
|
+
|
|
|
useExpose({
|
|
|
scrollTo
|
|
|
});
|
|
|
@@ -174,9 +216,24 @@ export default create({
|
|
|
}
|
|
|
);
|
|
|
|
|
|
+ watch(
|
|
|
+ () => state.diff,
|
|
|
+ (newVal: number) => {
|
|
|
+ const listHeight = state.listHeight;
|
|
|
+ let fixedTop = newVal > 0 && newVal < props.titleHeight ? newVal - props.titleHeight : 0;
|
|
|
+ if (state.scrollY + clientHeight.value === listHeight[listHeight.length - 1]) {
|
|
|
+ if (fixedTop !== 0) fixedTop = 0;
|
|
|
+ }
|
|
|
+ if (state.fixedTop === fixedTop) return;
|
|
|
+ state.fixedTop = fixedTop;
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
return {
|
|
|
classes,
|
|
|
...toRefs(state),
|
|
|
+ fixedStyle,
|
|
|
+ clientHeight,
|
|
|
setListGroup,
|
|
|
listview,
|
|
|
touchStart,
|