long-list-nested.uvue 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. <template>
  2. <scroll-view ref="pageScrollView" class="page" :bounces="false" type="nested">
  3. <nested-scroll-header>
  4. <swiper ref="header" indicator-dots="true" circular="true">
  5. <swiper-item v-for="i in 3" :item-id="i">
  6. <image src="/static/shuijiao.jpg" style="width:100% ;height: 240px;"></image>
  7. </swiper-item>
  8. </swiper>
  9. </nested-scroll-header>
  10. <nested-scroll-body style="height: 100%;">
  11. <view style="flex: 1;">
  12. <scroll-view ref="tabScroll" class="swiper-tabs" direction="horizontal" :show-scrollbar="false">
  13. <view class="flex-row" style="align-self: flex-start;">
  14. <text ref="swipertab" class="swiper-tabs-item" :class="swiperIndex==index ? 'swiper-tabs-item-active' : ''"
  15. v-for="(item, index) in swiperList" :key="index" @click="onTabClick(index)">
  16. {{item.name}}
  17. </text>
  18. </view>
  19. <view ref="indicator" class="swiper-tabs-indicator"></view>
  20. </scroll-view>
  21. <swiper ref="swiper" class="swiper-view" :current="swiperIndex" @transition="onSwiperTransition"
  22. @animationfinish="onSwiperAnimationfinish">
  23. <swiper-item class="swiper-item" v-for="(item, index) in swiperList" :key="index">
  24. <long-page ref="longPage" :id="item.id" :type="item.type" :preload="item.preload"></long-page>
  25. </swiper-item>
  26. </swiper>
  27. </view>
  28. </nested-scroll-body>
  29. </scroll-view>
  30. </template>
  31. <script>
  32. import longPage from '../long-list/long-list-page.uvue';
  33. type SwiperTabsItem = {
  34. x : number,
  35. w : number
  36. }
  37. type SwiperViewItem = {
  38. type : string,
  39. name : string,
  40. id : string,
  41. preload : boolean,
  42. }
  43. /**
  44. * 根据权重在两个值之间执行线性插值.
  45. * @constructor
  46. * @param {number} value1 - 第一个值,该值应为下限.
  47. * @param {number} value2 - 第二个值,该值应为上限.
  48. * @param {number} amount - 应介于 0 和 1 之间,指示内插的权重.
  49. * @returns {number} 内插值
  50. */
  51. function lerpNumber(value1 : number, value2 : number, amount : number) : number {
  52. return (value1 + (value2 - value1) * amount)
  53. }
  54. export default {
  55. components: {
  56. longPage
  57. },
  58. data() {
  59. return {
  60. swiperList: [
  61. {
  62. type: 'UpdatedDate',
  63. name: '最新上架',
  64. id: "list-id-1",
  65. preload: true
  66. } as SwiperViewItem,
  67. {
  68. type: 'FreeHot',
  69. name: '免费热榜',
  70. id: "list-id-2",
  71. preload: false
  72. } as SwiperViewItem,
  73. {
  74. type: 'PaymentHot',
  75. name: '付费热榜',
  76. id: "list-id-3",
  77. preload: false
  78. } as SwiperViewItem,
  79. {
  80. type: 'HotList',
  81. name: '热门总榜',
  82. id: "list-id-4",
  83. preload: false
  84. } as SwiperViewItem
  85. ] as SwiperViewItem[],
  86. swiperIndex: 0,
  87. pageScrollView: null as null | UniElement,
  88. headerHeight: 0,
  89. animationFinishIndex: 0,
  90. tabScrollView: null as null | UniElement,
  91. indicatorNode: null as null | UniElement,
  92. swiperWidth: 0,
  93. swiperTabsRect: [] as SwiperTabsItem[],
  94. nestedScrollChildId: ""
  95. }
  96. },
  97. onReady() {
  98. this.pageScrollView = this.$refs['pageScrollView'] as UniElement;
  99. this.headerHeight = (this.$refs['header'] as UniElement).offsetHeight
  100. this.swiperWidth = (this.$refs['swiper'] as UniElement).getBoundingClientRect().width
  101. this.tabScrollView = this.$refs['tabScroll'] as UniElement
  102. this.indicatorNode = this.$refs['indicator'] as UniElement
  103. this.cacheTabItemsSize()
  104. this.updateTabIndicator(this.swiperIndex, this.swiperIndex, 1)
  105. },
  106. onPullDownRefresh() {
  107. (this.$refs["longPage"]! as ComponentPublicInstance[])[this.swiperIndex].$callMethod('refreshData', () => {
  108. uni.stopPullDownRefresh()
  109. })
  110. },
  111. methods: {
  112. onTabClick(index : number) {
  113. this.setSwiperIndex(index, false)
  114. },
  115. onSwiperTransition(e : SwiperTransitionEvent) {
  116. // 微信 skyline 每项完成触发 Animationfinish,偏移值重置
  117. // 微信 webview 全部完成触发 Animationfinish,偏移值累加
  118. // 在滑动到下一个项的过程中,再次反向滑动,偏移值递减
  119. // uni-app-x 和微信 webview 行为一致
  120. const offset_x = e.detail.dx
  121. // 计算当前索引并重置差异
  122. const current_offset_x = offset_x % this.swiperWidth
  123. const current_offset_i = offset_x / this.swiperWidth
  124. const current_index = this.animationFinishIndex + parseInt(current_offset_i + '')
  125. // 计算目标索引及边界检查
  126. let move_to_index = current_index
  127. if (current_offset_x > 0 && move_to_index < this.swiperList.length - 1) {
  128. move_to_index += 1
  129. } else if (current_offset_x < 0 && move_to_index > 0) {
  130. move_to_index -= 1
  131. }
  132. // 计算偏移百分比
  133. const percentage = Math.abs(current_offset_x) / this.swiperWidth
  134. // 通知更新指示线
  135. if (current_index != move_to_index) {
  136. this.updateTabIndicator(current_index, move_to_index, percentage)
  137. }
  138. // 首次可见时初始化数据
  139. this.initSwiperItemData(move_to_index)
  140. },
  141. onSwiperAnimationfinish(e : SwiperAnimationFinishEvent) {
  142. this.setSwiperIndex(e.detail.current, true)
  143. this.animationFinishIndex = e.detail.current
  144. },
  145. cacheTabItemsSize() {
  146. this.swiperTabsRect.length = 0
  147. const tabs = this.$refs["swipertab"] as UniElement[]
  148. tabs.forEach((node) => {
  149. this.swiperTabsRect.push({
  150. x: node.offsetLeft,
  151. w: node.offsetWidth
  152. } as SwiperTabsItem)
  153. })
  154. },
  155. setSwiperIndex(index : number, updateIndicator : boolean) {
  156. if (this.swiperIndex === index) {
  157. return
  158. }
  159. this.swiperIndex = index
  160. this.initSwiperItemData(index)
  161. if (updateIndicator) {
  162. this.updateTabIndicator(index, index, 1)
  163. }
  164. },
  165. updateTabIndicator(current_index : number, move_to_index : number, percentage : number) {
  166. const current_size = this.swiperTabsRect[current_index]
  167. const move_to_size = this.swiperTabsRect[move_to_index]
  168. // 计算指示线 左边距 和 宽度 在移动过程中的线性值
  169. const indicator_line_x = lerpNumber(current_size.x, move_to_size.x, percentage)
  170. const indicator_line_w = lerpNumber(current_size.w, move_to_size.w, percentage)
  171. // 更新指示线
  172. // #ifdef APP
  173. const x = indicator_line_x + indicator_line_w / 2
  174. this.indicatorNode?.style?.setProperty('transform', `translateX(${x}px) scaleX(${indicator_line_w})`)
  175. // #endif
  176. // #ifdef WEB
  177. // TODO chrome windows系统 transform scaleX渲染bug
  178. const x = indicator_line_x
  179. this.indicatorNode?.style?.setProperty('width', `${indicator_line_w}px`)
  180. this.indicatorNode?.style?.setProperty('transform', `translateX(${x}px)`)
  181. // #endif
  182. // 滚动到水平中心位置
  183. const scroll_x = x - this.swiperWidth / 2
  184. if (this.tabScrollView !== null) {
  185. this.tabScrollView!.scrollLeft = scroll_x
  186. }
  187. },
  188. initSwiperItemData(index : number) {
  189. if (!this.swiperList[index].preload) {
  190. this.swiperList[index].preload = true;
  191. (this.$refs["longPage"]! as ComponentPublicInstance[])[index].$callMethod('loadData', null)
  192. }
  193. //swiper页面切换需要重新赋值嵌套滚动子元素的id
  194. this.nestedScrollChildId = this.swiperList[index].id
  195. }
  196. }
  197. }
  198. </script>
  199. <style>
  200. .flex-row {
  201. flex-direction: row;
  202. }
  203. .page {
  204. flex: 1;
  205. }
  206. .search-bar {
  207. padding: 10px;
  208. }
  209. .swiper-list {
  210. height: 100%;
  211. /* #ifdef WEB */
  212. flex: 1;
  213. /* #endif */
  214. }
  215. .swiper-tabs {
  216. background-color: #ffffff;
  217. flex-direction: column;
  218. }
  219. .swiper-tabs-item {
  220. color: #555;
  221. font-size: 16px;
  222. padding: 12px 25px;
  223. white-space: nowrap;
  224. }
  225. .swiper-tabs-item-active {
  226. color: #007AFF;
  227. }
  228. .swiper-tabs-indicator {
  229. width: 1px;
  230. height: 2px;
  231. background-color: #007AFF;
  232. }
  233. .swiper-view {
  234. flex: 1;
  235. }
  236. .swiper-item {
  237. /* flex: 1; harmony 高度异常 */
  238. height: 100%;
  239. }
  240. </style>