fs-index-list.vue 4.6 KB

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