fs-index-list.vue 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. <template>
  2. <view>
  3. <view class="fs-sidebar" @touchmove="handleMove" @touchend="handleEnd" v-if="list.length">
  4. <view class="fs-sidebar-item" :class="{primary: state.activeId === item}" v-for="(item, index) in letters" :key="index" @click="handleClick(item)">
  5. {{item}}
  6. </view>
  7. </view>
  8. <view class="fs-contact" :style="{'margin-top': showSearch ? '110rpx' : ''}">
  9. <scroll-view scroll-y :scroll-into-view="state.intoView" @scroll="scroll" class="fs-contact-list">
  10. <view v-for="item in list" :key="item.name" :id="item.name" class="fs-contact-list-item">
  11. <view class="fs-panel-title" :class="{'fs-sticky': sticky}">{{item[titleKey]}}</view>
  12. <fs-cell
  13. v-for="subitem in item[childrenKey]"
  14. :key="subitem.name"
  15. border
  16. justify="left"
  17. align="center"
  18. :link="link + '?id=' + subitem.id"
  19. @click="handleRoute(subitem)">
  20. <template #title>
  21. <fs-avatar :src="subitem[avatarKey]">{{subitem.alais}}</fs-avatar>
  22. </template>
  23. <template #value>
  24. <view class="fs-contact-hd">{{subitem[hdKey]}}</view>
  25. <view class="fs-contact-bd">{{subitem[bdKey]}}</view>
  26. </template>
  27. </fs-cell>
  28. </view>
  29. </scroll-view>
  30. </view>
  31. <view class="fs-layer" v-if="state.showLayer">{{state.activeId}}</view>
  32. </view>
  33. </template>
  34. <script setup>
  35. import { reactive } from 'vue'
  36. const letters = []
  37. for (var i = 0; i < 26; i++) {
  38. letters.push(String.fromCharCode(65 + i))
  39. }
  40. const props = defineProps({
  41. list: Array,
  42. childrenKey: {
  43. type: String,
  44. default: 'list'
  45. },
  46. titleKey: {
  47. type: String,
  48. default: 'name'
  49. },
  50. avatarKey: {
  51. type: String,
  52. default: 'src'
  53. },
  54. hdKey: {
  55. type: String,
  56. default: 'name'
  57. },
  58. bdKey: {
  59. type: String,
  60. default: 'phone'
  61. },
  62. link: String,
  63. sticky: {
  64. type: Boolean,
  65. default: true
  66. },
  67. showSearch: Boolean,
  68. })
  69. const windowHeight = uni.getSystemInfoSync().windowHeight
  70. const offsetHeight = 50
  71. const navHeight = windowHeight - offsetHeight * 2
  72. const letterPos = []
  73. const letterHeight = navHeight / letters.length
  74. letters.forEach((item, index) => {
  75. letterPos.push(offsetHeight + letterHeight * index)
  76. })
  77. const state = reactive({
  78. intoView: '',
  79. activeId: '',
  80. showLayer: false
  81. })
  82. const handleClick = (id) => {
  83. state.intoView = id
  84. state.activeId = id
  85. }
  86. const handleMove = (e) => {
  87. const y = e.touches[0].clientY
  88. for (let i = 0, len = letterPos.length; i < len; i++) {
  89. if (y >= letterPos[i] && y <= letterPos[i] + letterHeight) {
  90. state.intoView = letters[i]
  91. state.activeId = letters[i]
  92. state.showLayer = true
  93. break
  94. }
  95. }
  96. }
  97. const handleEnd = (e) => {
  98. setTimeout(() => {
  99. state.showLayer = false
  100. }, 200)
  101. }
  102. const scroll = (e) => {
  103. uni.createSelectorQuery().selectAll('.list-item').boundingClientRect(rects => {
  104. for (let i = 0; i < rects.length; i++) {
  105. let rect = rects[i]
  106. if (rect.top === 0 || rect.bottom > 0) {
  107. state.activeId = rect.id
  108. break
  109. }
  110. }
  111. }).exec()
  112. }
  113. const handleRoute = (item) => {
  114. getApp().globalData.addrbookDetail = item
  115. }
  116. </script>
  117. <style lang="scss" scoped>
  118. .fs-sidebar {
  119. display: flex;
  120. flex-direction: column;
  121. justify-content: space-between;
  122. position: fixed;
  123. top: 50%;
  124. right: 0;
  125. align-items: center;
  126. z-index: 1000;
  127. transform: translateY(-50%);
  128. &-item {
  129. padding: 6rpx 20rpx;
  130. flex-shrink: 1;
  131. font-size: 24rpx;
  132. }
  133. }
  134. .fs-sticky {
  135. position: sticky;
  136. top: 0;
  137. z-index: 100;
  138. }
  139. .fs-contact {
  140. position: fixed;
  141. top: var(--window-top);
  142. left: var(--gutter);
  143. right: 60rpx;
  144. bottom: 0;
  145. &-list {
  146. height: 100%;
  147. }
  148. &-hd {
  149. font-size: 16px;
  150. }
  151. .fs-panel-title {
  152. padding: var(--gutter);
  153. color: var(--title);
  154. text-align: left;
  155. background-color: var(--bg-color);
  156. }
  157. }
  158. .fs-layer {
  159. width: 150rpx;
  160. height: 150rpx;
  161. background: rgba(0, 0, 0, .5);
  162. border-radius: 50%;
  163. line-height: 150rpx;
  164. color: #ffffff;
  165. text-align: center;
  166. position: fixed;
  167. top: 50%;
  168. left: 50%;
  169. transform: translate(-50%, -50%);
  170. font-weight: bolder;
  171. font-size: 28px;
  172. }
  173. </style>