index.vue 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. <template>
  2. <view :class="classes">
  3. <view class="nut-elevator__list" ref="listview" :style="{ height: isNaN(+height) ? height : `${height}px` }">
  4. <view class="nut-elevator__list__item" v-for="item in indexList" :key="item[acceptKey]" :ref="setListGroup">
  5. <view class="nut-elevator__list__item__code">{{ item[acceptKey] }}</view>
  6. <view
  7. class="nut-elevator__list__item__name"
  8. :class="{
  9. 'nut-elevator__list__item__name--highcolor': currentData.id === subitem.id && currentKey === item[acceptKey]
  10. }"
  11. v-for="subitem in item.list"
  12. :key="subitem['id']"
  13. @click="handleClickItem(item[acceptKey], subitem)"
  14. v-html="subitem.name"
  15. ></view>
  16. </view>
  17. </view>
  18. <view class="nut-elevator__code--current" v-show="scrollStart" v-if="indexList.length">{{
  19. indexList[currentIndex][acceptKey]
  20. }}</view>
  21. <view class="nut-elevator__bars" @touchstart="touchStart" @touchmove.stop.prevent="touchMove" @touchend="touchEnd">
  22. <view class="nut-elevator__bars__inner">
  23. <view
  24. class="nut-elevator__bars__inner__item"
  25. :data-index="index"
  26. v-for="(item, index) in indexList"
  27. :key="item[acceptKey]"
  28. @click="handleClickIndex(item[acceptKey])"
  29. >{{ item[acceptKey] }}</view
  30. >
  31. </view>
  32. </view>
  33. </view>
  34. </template>
  35. <script lang="ts">
  36. import { computed, reactive, toRefs, nextTick, ref, Ref, watch } from 'vue';
  37. import { createComponent } from '@/packages/utils/create';
  38. import { useExpose } from '@/packages/utils/useExpose/index';
  39. const { componentName, create } = createComponent('elevator');
  40. interface ElevatorData {
  41. name: string;
  42. id: number | string;
  43. [key: string]: string | number;
  44. }
  45. export default create({
  46. props: {
  47. height: {
  48. type: [Number, String],
  49. default: '200px'
  50. },
  51. acceptKey: {
  52. type: [String],
  53. default: 'title'
  54. },
  55. indexList: {
  56. type: Array,
  57. default: () => {
  58. return [];
  59. }
  60. }
  61. },
  62. emits: ['click-item', 'click-index'],
  63. setup(props: any, context: any) {
  64. const spaceHeight = 23;
  65. const listview: Ref<any> = ref(null);
  66. const state = reactive({
  67. anchorIndex: 0,
  68. listHeight: [] as number[],
  69. listGroup: [] as HTMLLIElement[],
  70. touchState: {
  71. y1: 0,
  72. y2: 0
  73. },
  74. scrollStart: false,
  75. currentIndex: 0,
  76. currentData: {} as ElevatorData,
  77. currentKey: ''
  78. });
  79. const classes = computed(() => {
  80. const prefixCls = componentName;
  81. return {
  82. [prefixCls]: true
  83. };
  84. });
  85. //重置滚动参数
  86. const resetScrollState = () => {
  87. state.anchorIndex = 0;
  88. state.listHeight = [];
  89. state.listGroup = [];
  90. state.currentIndex = 0;
  91. state.scrollStart = false;
  92. state.touchState = {
  93. y1: 0,
  94. y2: 0
  95. };
  96. };
  97. const getData = (el: HTMLElement, name: string): string | void => {
  98. const prefix = 'data-';
  99. return el.getAttribute(prefix + name) as string;
  100. };
  101. const setListGroup = (el: HTMLLIElement) => {
  102. nextTick(() => {
  103. if (!state.listGroup.includes(el) && el != null) {
  104. state.listGroup.push(el);
  105. }
  106. });
  107. };
  108. const calculateHeight = () => {
  109. let height = 0;
  110. state.listHeight.push(height);
  111. for (let i = 0; i < state.listGroup.length; i++) {
  112. let item = state.listGroup[i];
  113. height += item.clientHeight;
  114. state.listHeight.push(height);
  115. }
  116. };
  117. const scrollTo = (index: number) => {
  118. if (!index && index !== 0) {
  119. return;
  120. }
  121. if (index < 0) index = 0;
  122. if (index > state.listHeight.length - 2) index = state.listHeight.length - 2;
  123. state.currentIndex = index;
  124. listview.value.scrollTo(0, state.listHeight[index]);
  125. };
  126. const touchStart = (e: TouchEvent) => {
  127. state.scrollStart = true;
  128. let index = getData(e.target as HTMLElement, 'index');
  129. let firstTouch = e.touches[0];
  130. state.touchState.y1 = firstTouch.pageY;
  131. state.anchorIndex = +index;
  132. state.currentIndex = +index;
  133. scrollTo(+index);
  134. };
  135. const touchMove = (e: TouchEvent) => {
  136. let firstTouch = e.touches[0];
  137. state.touchState.y2 = firstTouch.pageY;
  138. let delta = ((state.touchState.y2 - state.touchState.y1) / spaceHeight) | 0;
  139. state.currentIndex = state.anchorIndex + delta;
  140. scrollTo(state.currentIndex);
  141. };
  142. const touchEnd = () => {
  143. resetScrollState();
  144. };
  145. const handleClickItem = (key: string, item: ElevatorData) => {
  146. context.emit('click-item', key, item);
  147. state.currentData = item;
  148. state.currentKey = key;
  149. };
  150. const handleClickIndex = (key: string) => {
  151. context.emit('click-index', key);
  152. };
  153. useExpose({
  154. scrollTo
  155. });
  156. watch(
  157. () => state.listGroup.length,
  158. () => {
  159. state.listHeight = [];
  160. nextTick(calculateHeight);
  161. }
  162. );
  163. return {
  164. classes,
  165. ...toRefs(state),
  166. setListGroup,
  167. listview,
  168. touchStart,
  169. touchMove,
  170. touchEnd,
  171. handleClickItem,
  172. handleClickIndex
  173. };
  174. }
  175. });
  176. </script>