long-list.uvue 8.4 KB
Newer Older
H
hdx 已提交
1
<template>
H
hdx 已提交
2 3 4
  <scroll-view ref="pageScrollView" class="page" :scroll-y="true" :rebound="false"
    @startnestedscroll="onStartNestedScroll" @nestedprescroll="onNestedPreScroll">
    <view ref="header" class="search-bar">
H
hdx 已提交
5 6 7
      <input placeholder="搜索..." />
    </view>
    <view class="swiper-list">
8 9
      <scroll-view ref="tabScroll" class="swiper-tabs" :scroll-x="true" :show-scrollbar="false">
        <!-- TODO 多一层 view -->
H
hdx 已提交
10 11
        <view>
          <view class="flex-row">
12 13
            <text ref="swipertab" class="swiper-tabs-item" :class="swiperIndex==index ? 'swiper-tabs-item-active' : ''"
              v-for="(item, index) in swiperList" :key="index" @click="onTabClick(index)">
14 15
              {{item.name}}
            </text>
H
hdx 已提交
16
          </view>
17
          <view ref="indicator" class="swiper-tabs-indicator"></view>
H
hdx 已提交
18
        </view>
H
hdx 已提交
19
      </scroll-view>
20
      <swiper ref="swiper" class="swiper-view" :current="swiperIndex" @transition="onSwiperTransition"
21
        @animationfinish="onSwiperAnimationfinish">
H
hdx 已提交
22
        <swiper-item class="swiper-item" v-for="(item, index) in swiperList" :key="index">
H
hdx 已提交
23
          <long-page ref="longPage" :type="item.type" :preload="item.preload"></long-page>
H
hdx 已提交
24 25 26 27
        </swiper-item>
      </swiper>
    </view>
  </scroll-view>
H
hdx 已提交
28 29 30
</template>

<script>
H
hdx 已提交
31 32 33
  import longPage from './long-list-page.uvue';

  type SwiperTabsItem = {
H
hdx 已提交
34 35
    x : number,
    w : number
H
hdx 已提交
36 37 38 39
  }

  type SwiperViewItem = {
    type : string,
40
    name : string,
H
hdx 已提交
41
    preload : boolean,
H
hdx 已提交
42 43
  }

H
hdx 已提交
44 45 46 47 48 49 50 51 52 53 54 55
  /**
   * 根据权重在两个值之间执行线性插值.
   * @constructor
   * @param {number} value1 - 第一个值,该值应为下限.
   * @param {number} value2 - 第二个值,该值应为上限.
   * @param {number} amount - 应介于 0 和 1 之间,指示内插的权重.
   * @returns {number} 内插值
   */
  function lerpNumber(value1 : number, value2 : number, amount : number) : number {
    return (value1 + (value2 - value1) * amount)
  }

H
hdx 已提交
56
  export default {
H
hdx 已提交
57 58 59
    components: {
      longPage
    },
H
hdx 已提交
60 61
    data() {
      return {
62 63 64
        swiperList: [
          {
            type: 'UpdatedDate',
H
hdx 已提交
65 66
            name: '最新上架',
            preload: true
67 68 69 70 71 72 73 74 75 76 77 78 79 80
          } as SwiperViewItem,
          {
            type: 'FreeHot',
            name: '免费热榜'
          } as SwiperViewItem,
          {
            type: 'PaymentHot',
            name: '付费热榜'
          } as SwiperViewItem,
          {
            type: 'HotList',
            name: '热门总榜'
          } as SwiperViewItem
        ] as SwiperViewItem[],
H
hdx 已提交
81
        swiperIndex: -1,
H
hdx 已提交
82
        $pageScrollView: null as null | INode,
H
hdx 已提交
83
        $headerHeight: 0,
84
        $animationFinishIndex: 0,
H
hdx 已提交
85 86
        $tabScrollView: null as null | INode,
        $indicatorNode: null as null | INode,
H
hdx 已提交
87
        $swiperWidth: 0,
88
        $swiperTabsRect: [] as SwiperTabsItem[]
H
hdx 已提交
89 90
      }
    },
H
hdx 已提交
91
    onReady() {
H
hdx 已提交
92
      this.$pageScrollView = this.$refs['pageScrollView'] as INode;
93
      this.$headerHeight = (this.$refs["header"] as INode).offsetHeight
H
hdx 已提交
94
      this.$swiperWidth = (this.$refs["swiper"] as INode).getBoundingClientRect().width
H
hdx 已提交
95 96 97
      this.$tabScrollView = this.$refs.get('tabScroll') as INode
      this.$indicatorNode = this.$refs.get('indicator') as INode
      this.cacheTabItemsSize()
H
hdx 已提交
98
      this.setSwiperIndex(0, true)
H
hdx 已提交
99
    },
100
    onPullDownRefresh() {
H
hdx 已提交
101 102 103
      (this.$refs["longPage"]! as ComponentPublicInstance[])[this.swiperIndex].$callMethod('refreshData', () => {
        uni.stopPullDownRefresh()
      })
104
    },
H
hdx 已提交
105
    methods: {
H
hdx 已提交
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
      // TODO
      onStartNestedScroll(event : StartNestedScrollEvent) : boolean {
        return true
      },
      onNestedPreScroll(event : NestedPreScrollEvent) {
        const deltaY = event.deltaY
        const scrollTop = this.$pageScrollView!.scrollTop

        /// 优先处理父容器滚动,父容器不能滚动时在滚动子
        // 向上滚动
        if (deltaY > 0) {
          // 如果父容器 header scrollTop < offsetHeight,先滚动父容器
          if (scrollTop < this.$headerHeight) {
            const difference = this.$headerHeight - scrollTop - deltaY
            if (difference > 0) {
              this.$pageScrollView!.scrollBy(event.deltaX, deltaY)
              event.consumed(event.deltaX, deltaY)
            } else {
              const top : number = deltaY + difference
              event.consumed(event.deltaX, top.toFloat())
              this.$pageScrollView!.scrollBy(event.deltaX, top.toFloat())
            }
          }
        } else if (deltaY < 0) {
          // 向下滚动,如果父容器 scrollTop > 0,通知子被父容器消耗,然后滚动到 0
          if (scrollTop > 0) {
            event.consumed(event.deltaX, deltaY)
            this.$pageScrollView!.scrollBy(event.deltaX, deltaY)
          }
        }
      },
H
hdx 已提交
137 138 139 140
      onTabClick(index : number) {
        this.setSwiperIndex(index, false)
      },
      onSwiperTransition(e : SwiperTransitionEvent) {
141 142 143
        // 微信 skyline 每项完成触发 Animationfinish,偏移值重置
        // 微信 webview 全部完成触发 Animationfinish,偏移值累加
        // 在滑动到下一个项的过程中,再次反向滑动,偏移值递减
H
hdx 已提交
144
        // uni-app-x 和微信 webview 行为一致
145

H
hdx 已提交
146
        const offset_x = e.detail.dx
H
hdx 已提交
147

H
hdx 已提交
148
        // 计算当前索引并重置差异
H
hdx 已提交
149 150 151
        const current_offset_x = offset_x % this.$swiperWidth
        const current_offset_i = offset_x / this.$swiperWidth
        const current_index = this.$animationFinishIndex + current_offset_i.toInt()
H
hdx 已提交
152

H
hdx 已提交
153
        // 计算目标索引及边界检查
H
hdx 已提交
154
        let move_to_index = current_index
155 156 157 158
        if (current_offset_x > 0 && move_to_index < this.swiperList.length - 1) {
          move_to_index += 1
        } else if (current_offset_x < 0 && move_to_index > 0) {
          move_to_index -= 1
H
hdx 已提交
159 160
        }

161
        // 计算偏移百分比
H
hdx 已提交
162
        const percentage = Math.abs(current_offset_x) / this.$swiperWidth
163

164 165
        // 通知更新指示线
        this.updateTabIndicator(current_index, move_to_index, percentage)
166

H
hdx 已提交
167
        // 首次可见时初始化数据
168
        this.initSwiperItemData(move_to_index)
H
hdx 已提交
169 170
      },
      onSwiperAnimationfinish(e : SwiperAnimationFinishEvent) {
H
hdx 已提交
171
        this.setSwiperIndex(e.detail.current, true)
172
        this.$animationFinishIndex = e.detail.current
H
hdx 已提交
173
      },
H
hdx 已提交
174
      cacheTabItemsSize() {
175
        this.$swiperTabsRect.length = 0
H
hdx 已提交
176 177 178
        const tabs = this.$refs["swipertab"] as INode[]
        tabs.forEach((node) => {
          this.$swiperTabsRect.push({
H
hdx 已提交
179 180
            x: node.offsetLeft,
            w: node.offsetWidth
H
hdx 已提交
181 182 183
          } as SwiperTabsItem)
        })
      },
H
hdx 已提交
184
      setSwiperIndex(index : number, updateIndicator : boolean) {
H
hdx 已提交
185
        if (this.swiperIndex === index) {
H
hdx 已提交
186 187
          return
        }
188

H
hdx 已提交
189
        this.swiperIndex = index
H
hdx 已提交
190

191
        this.initSwiperItemData(index)
192

H
hdx 已提交
193
        if (updateIndicator) {
194
          this.updateTabIndicator(index, index, 1)
H
hdx 已提交
195
        }
196
      },
197 198 199 200
      updateTabIndicator(current_index : number, move_to_index : number, percentage : number) {
        // 计算指示线
        const current_size = this.$swiperTabsRect[current_index]
        const move_to_size = this.$swiperTabsRect[move_to_index]
H
hdx 已提交
201 202
        const indicator_line_x = lerpNumber(current_size.x, move_to_size.x, percentage)
        const indicator_line_w = lerpNumber(current_size.w, move_to_size.w, percentage)
203 204

        // 更新指示线
H
hdx 已提交
205
        const x = indicator_line_x + indicator_line_w / 2
206 207
        this.$indicatorNode?.style?.setProperty('transform', `translateX(${x}px) scaleX(${indicator_line_w})`)

H
hdx 已提交
208
        // 滚动到水平中心位置
209 210
        const scroll_x = x - this.$swiperWidth / 2
        this.$tabScrollView?.setAttribute('scrollLeft', scroll_x)
211
      },
212
      initSwiperItemData(index : number) {
213 214
        if (!this.swiperList[index].preload) {
          this.swiperList[index].preload = true;
H
hdx 已提交
215
          (this.$refs["longPage"]! as ComponentPublicInstance[])[index].$callMethod('loadData', null)
H
hdx 已提交
216
        }
H
hdx 已提交
217 218 219 220 221 222 223 224 225 226
      }
    }
  }
</script>

<style>
  .flex-row {
    flex-direction: row;
  }

H
hdx 已提交
227
  .page {
H
hdx 已提交
228 229 230
    flex: 1;
  }

H
hdx 已提交
231
  .search-bar {
H
hdx 已提交
232 233 234
    padding: 10px;
  }

H
hdx 已提交
235
  .swiper-list {
H
hdx 已提交
236
    height: 100%;
H
hdx 已提交
237 238
  }

H
hdx 已提交
239 240
  .swiper-tabs {
    background-color: #ffffff;
H
hdx 已提交
241 242
  }

H
hdx 已提交
243 244 245
  .swiper-tabs-item {
    color: #555;
    font-size: 16px;
246
    padding: 12px 25px;
H
hdx 已提交
247 248
  }

249
  .swiper-tabs-item-active {
H
hdx 已提交
250
    color: #007AFF;
H
hdx 已提交
251 252
  }

H
hdx 已提交
253
  .swiper-tabs-indicator {
254
    width: 1px;
H
hdx 已提交
255 256
    height: 2px;
    background-color: #007AFF;
H
hdx 已提交
257 258
  }

H
hdx 已提交
259 260
  .swiper-view {
    flex: 1;
H
hdx 已提交
261 262
  }

H
hdx 已提交
263 264
  .swiper-item {
    flex: 1;
H
hdx 已提交
265
  }
H
hdx 已提交
266
</style>