Compare commits
No commits in common. "e29a718b2fa5562f059c463de07dee7c518ce83d" and "4d42d9a6d08c39be90617b465b5f8663e80f6919" have entirely different histories.
e29a718b2f
...
4d42d9a6d0
File diff suppressed because it is too large
Load Diff
@ -1,24 +1,52 @@
|
|||||||
<template>
|
<template>
|
||||||
<scroll-view class="waterfall-scroll" :style="scrollStyle" scroll-x :show-scrollbar="false"
|
<scroll-view
|
||||||
:scroll-left="isIOS ? undefined : scrollLeft" :bounce="false" @scroll="onScroll" @touchstart="onTouchStart"
|
class="waterfall-scroll"
|
||||||
@touchend="onTouchEnd" @touchcancel="onTouchEnd">
|
:style="scrollStyle"
|
||||||
<view ref="waterfallInnerRef" :class="{ 'waterfall-inner': true, 'ios-css-animate': !iosScrollPaused }"
|
scroll-x
|
||||||
:style="innerStyle">
|
:show-scrollbar="false"
|
||||||
<view v-for="card in cards" :key="card.id" class="wf-card" :style="cardStyle(card)"
|
:scroll-left="isIOS ? undefined : scrollLeft"
|
||||||
@click="handleCardClick(card)">
|
:bounce="false"
|
||||||
|
@scroll="onScroll"
|
||||||
|
@touchstart="onTouchStart"
|
||||||
|
@touchend="onTouchEnd"
|
||||||
|
@touchcancel="onTouchEnd"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
ref="waterfallInnerRef"
|
||||||
|
:class="{'waterfall-inner': true, 'ios-css-animate': !iosScrollPaused}"
|
||||||
|
:style="innerStyle"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
v-for="card in cards"
|
||||||
|
:key="card.id"
|
||||||
|
class="wf-card"
|
||||||
|
:style="cardStyle(card)"
|
||||||
|
@click="handleCardClick(card)"
|
||||||
|
>
|
||||||
<!-- 渐变边框光效 -->
|
<!-- 渐变边框光效 -->
|
||||||
<view class="wf-card-border" :style="borderStyle(card)" />
|
<view class="wf-card-border" :style="borderStyle(card)" />
|
||||||
|
|
||||||
<!-- 封面图 -->
|
<!-- 封面图 -->
|
||||||
<image v-if="card.coverUrl" class="wf-card-img" :src="card.coverUrl" mode="aspectFill"
|
<image
|
||||||
:style="{ width: card.w + 'px', height: card.h + 'px', borderRadius: card.radius + 'px' }" />
|
v-if="card.coverUrl"
|
||||||
|
class="wf-card-img"
|
||||||
|
:src="card.coverUrl"
|
||||||
|
mode="aspectFill"
|
||||||
|
:style="{ width: card.w + 'px', height: card.h + 'px', borderRadius: card.radius + 'px' }"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- 光波动画层 - 外层 -->
|
<!-- 光波动画层 - 外层 -->
|
||||||
<view class="wf-like-wave wf-like-wave-outer" :class="{ 'wf-like-wave-active': likingMap[card.id] }"
|
<view
|
||||||
:style="{ width: card.w + 'px', height: card.h + 'px', borderRadius: card.radius + 'px' }" />
|
class="wf-like-wave wf-like-wave-outer"
|
||||||
|
:class="{ 'wf-like-wave-active': likingMap[card.id] }"
|
||||||
|
:style="{ width: card.w + 'px', height: card.h + 'px', borderRadius: card.radius + 'px' }"
|
||||||
|
/>
|
||||||
<!-- 光波动画层 - 内层 -->
|
<!-- 光波动画层 - 内层 -->
|
||||||
<view class="wf-like-wave wf-like-wave-inner" :class="{ 'wf-like-wave-active': likingMap[card.id] }"
|
<view
|
||||||
:style="{ width: card.w + 'px', height: card.h + 'px', borderRadius: card.radius + 'px' }" />
|
class="wf-like-wave wf-like-wave-inner"
|
||||||
|
:class="{ 'wf-like-wave-active': likingMap[card.id] }"
|
||||||
|
:style="{ width: card.w + 'px', height: card.h + 'px', borderRadius: card.radius + 'px' }"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- 底部点赞数 -->
|
<!-- 底部点赞数 -->
|
||||||
<view class="wf-card-footer">
|
<view class="wf-card-footer">
|
||||||
@ -156,10 +184,6 @@ const startAutoScroll = () => {
|
|||||||
scrollUpdateTimer = setInterval(() => {
|
scrollUpdateTimer = setInterval(() => {
|
||||||
if (!isComponentMounted || userInteracting || isLoadingMore) return
|
if (!isComponentMounted || userInteracting || isLoadingMore) return
|
||||||
autoScrollPos += AUTO_SCROLL_SPEED_ANDROID
|
autoScrollPos += AUTO_SCROLL_SPEED_ANDROID
|
||||||
// 滚到头时重置到 0,实现无缝循环
|
|
||||||
if (autoScrollPos >= totalWidth.value) {
|
|
||||||
autoScrollPos = 0
|
|
||||||
}
|
|
||||||
scrollLeft.value = autoScrollPos
|
scrollLeft.value = autoScrollPos
|
||||||
|
|
||||||
// 预加载
|
// 预加载
|
||||||
@ -334,13 +358,6 @@ class WaterfallLayout {
|
|||||||
// 列宽固定 = rowH × 9/16,严格竖长 9:16,span 只影响高度
|
// 列宽固定 = rowH × 9/16,严格竖长 9:16,span 只影响高度
|
||||||
this.colW = Math.round(this.rowH * 4 / 3)
|
this.colW = Math.round(this.rowH * 4 / 3)
|
||||||
this.curX = 0
|
this.curX = 0
|
||||||
// 跨批次追踪连续 N 级卡片数量,凑满 5 个后在第 6 个 N 卡前插入空白格
|
|
||||||
this.consecutiveNCount = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
_isNLevel(item) {
|
|
||||||
const span = item.span != null ? item.span : this._span(item.likes || 0)
|
|
||||||
return span === 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_cardSize(span) {
|
_cardSize(span) {
|
||||||
@ -375,28 +392,10 @@ class WaterfallLayout {
|
|||||||
let i = 0
|
let i = 0
|
||||||
while (i < remaining.length) {
|
while (i < remaining.length) {
|
||||||
const rawSpan = remaining[i].span
|
const rawSpan = remaining[i].span
|
||||||
const isN = remaining[i]._blank ? false : this._isNLevel(remaining[i])
|
|
||||||
|
|
||||||
// N 级连续 5 张后,第 6 个 N 卡前先插入 1-2 个空白格
|
|
||||||
if (isN && this.consecutiveNCount >= 5) {
|
|
||||||
const blankCount = Math.random() < 0.5 ? 1 : 2
|
|
||||||
for (let b = 0; b < blankCount; b++) {
|
|
||||||
if (sum + 1 <= ROWS) {
|
|
||||||
remaining.splice(i, 0, { id: idCounter++, span: 1, _blank: true, likes: 0 })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.consecutiveNCount = 0
|
|
||||||
// 空白格仅占位,不断 i,直接 break 让外层开新列处理剩余卡片
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果当前卡片加入会导致超出 ROWS,尝试下一个
|
// 如果当前卡片加入会导致超出 ROWS,尝试下一个
|
||||||
if (sum + rawSpan > ROWS) {
|
if (sum + rawSpan > ROWS) {
|
||||||
i++
|
i++
|
||||||
// 已检查完所有卡片仍无法放入 → 当前列已满但未凑满 ROWS,强开新列
|
|
||||||
if (i >= remaining.length && col.length > 0) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -405,15 +404,6 @@ class WaterfallLayout {
|
|||||||
col.push(item)
|
col.push(item)
|
||||||
sum += rawSpan
|
sum += rawSpan
|
||||||
|
|
||||||
// 非 N 卡重置连续计数;N 卡累加计数
|
|
||||||
if (!item._blank) {
|
|
||||||
if (this._isNLevel(item)) {
|
|
||||||
this.consecutiveNCount++
|
|
||||||
} else {
|
|
||||||
this.consecutiveNCount = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果已经达到 ROWS,结束这列
|
// 如果已经达到 ROWS,结束这列
|
||||||
if (sum >= ROWS) break
|
if (sum >= ROWS) break
|
||||||
}
|
}
|
||||||
@ -423,13 +413,6 @@ class WaterfallLayout {
|
|||||||
const item = remaining.shift()
|
const item = remaining.shift()
|
||||||
col.push(item)
|
col.push(item)
|
||||||
sum = item.span
|
sum = item.span
|
||||||
if (!item._blank) {
|
|
||||||
if (this._isNLevel(item)) {
|
|
||||||
this.consecutiveNCount++
|
|
||||||
} else {
|
|
||||||
this.consecutiveNCount = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
colIndex++
|
colIndex++
|
||||||
@ -454,7 +437,7 @@ class WaterfallLayout {
|
|||||||
for (const u of colUsers) {
|
for (const u of colUsers) {
|
||||||
const span = u.span || 1
|
const span = u.span || 1
|
||||||
const { w, h } = this._cardSize(span)
|
const { w, h } = this._cardSize(span)
|
||||||
if (!u._pad && !u._blank) {
|
if (!u._pad) {
|
||||||
result.push({ ...u, left: colX, top: curY, w, h, radius: 8 })
|
result.push({ ...u, left: colX, top: curY, w, h, radius: 8 })
|
||||||
}
|
}
|
||||||
curY += h + this.gap
|
curY += h + this.gap
|
||||||
@ -467,7 +450,6 @@ class WaterfallLayout {
|
|||||||
// 计算全部卡片布局(重置状态)
|
// 计算全部卡片布局(重置状态)
|
||||||
compute(users) {
|
compute(users) {
|
||||||
this.curX = 0
|
this.curX = 0
|
||||||
this.consecutiveNCount = 0
|
|
||||||
const columns = this._groupIntoColumns(users)
|
const columns = this._groupIntoColumns(users)
|
||||||
const result = []
|
const result = []
|
||||||
for (const col of columns) {
|
for (const col of columns) {
|
||||||
@ -526,9 +508,9 @@ const cardStyle = (card) => ({
|
|||||||
|
|
||||||
const borderStyle = (card) => ({
|
const borderStyle = (card) => ({
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
|
inset: `-${BORDER_W}px`,
|
||||||
borderRadius: (card.radius + BORDER_W) + 'px',
|
borderRadius: (card.radius + BORDER_W) + 'px',
|
||||||
background: BORDER_COLORS[Math.abs(card.id) % BORDER_COLORS.length],
|
background: BORDER_COLORS[Math.abs(card.id) % BORDER_COLORS.length],
|
||||||
inset: `-${BORDER_W}px`,
|
|
||||||
zIndex: 0,
|
zIndex: 0,
|
||||||
opacity: 0.85,
|
opacity: 0.85,
|
||||||
})
|
})
|
||||||
@ -584,7 +566,7 @@ const batchGetPresignedUrls = async (urls) => {
|
|||||||
}
|
}
|
||||||
return map
|
return map
|
||||||
}
|
}
|
||||||
} catch (_) { }
|
} catch (_) {}
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -659,7 +641,7 @@ const loadUsers = async () => {
|
|||||||
span: item.span ?? null,
|
span: item.span ?? null,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
allUsers.value = withData
|
allUsers.value = withData
|
||||||
cards.value = layout.compute(withData)
|
cards.value = layout.compute(withData)
|
||||||
totalWidth.value = layout.getTotalWidth()
|
totalWidth.value = layout.getTotalWidth()
|
||||||
@ -749,12 +731,6 @@ const appendMore = async () => {
|
|||||||
cards.value = [...cards.value, ...placed]
|
cards.value = [...cards.value, ...placed]
|
||||||
totalWidth.value = layout.getTotalWidth()
|
totalWidth.value = layout.getTotalWidth()
|
||||||
|
|
||||||
// totalWidth 变化后重启 iOS CSS 动画,确保新宽度生效
|
|
||||||
if (isIOS && !iosScrollPaused.value) {
|
|
||||||
stopIOSAutoScroll()
|
|
||||||
startIOSAutoScroll()
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// 没有可追加的数据,标记失败停止
|
// 没有可追加的数据,标记失败停止
|
||||||
appendFailed = true
|
appendFailed = true
|
||||||
@ -798,7 +774,7 @@ const handleCardClick = (card) => {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// 第一次点击,单击跳转
|
// 第一次点击,单击跳转
|
||||||
if (card.id) {
|
if(card.id){
|
||||||
cardTapTimers[card.id] = setTimeout(() => {
|
cardTapTimers[card.id] = setTimeout(() => {
|
||||||
delete cardTapTimers[card.id];
|
delete cardTapTimers[card.id];
|
||||||
uni.navigateTo({ url: `/pages/asset-detail/asset-detail?asset_id=${card.id}` });
|
uni.navigateTo({ url: `/pages/asset-detail/asset-detail?asset_id=${card.id}` });
|
||||||
@ -939,7 +915,7 @@ onUnmounted(() => {
|
|||||||
try {
|
try {
|
||||||
uni.offAppShow(onAppShowHandler)
|
uni.offAppShow(onAppShowHandler)
|
||||||
uni.offAppHide(onAppHideHandler)
|
uni.offAppHide(onAppHideHandler)
|
||||||
} catch (_) { }
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1023,11 +999,8 @@ const loadUsersAndStartScroll = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes iosAutoScroll {
|
@keyframes iosAutoScroll {
|
||||||
|
|
||||||
/* from { transform: translateX(0); } */
|
/* from { transform: translateX(0); } */
|
||||||
to {
|
to { transform: translateX(var(--scroll-dist)); }
|
||||||
transform: translateX(var(--scroll-dist));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.wf-card {
|
.wf-card {
|
||||||
@ -1037,7 +1010,6 @@ const loadUsersAndStartScroll = () => {
|
|||||||
transform-origin: top center;
|
transform-origin: top center;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
pointer-events: visible;
|
pointer-events: visible;
|
||||||
transform: translateZ(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.wf-card:active {
|
.wf-card:active {
|
||||||
@ -1047,7 +1019,6 @@ const loadUsersAndStartScroll = () => {
|
|||||||
.wf-card-border {
|
.wf-card-border {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
will-change: opacity;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.wf-card-img {
|
.wf-card-img {
|
||||||
@ -1075,9 +1046,10 @@ const loadUsersAndStartScroll = () => {
|
|||||||
|
|
||||||
.wf-likes-wrap {
|
.wf-likes-wrap {
|
||||||
background: linear-gradient(to bottom right,
|
background: linear-gradient(to bottom right,
|
||||||
#F0E4B1 0%,
|
#F0E4B1 0%,
|
||||||
#F08399 50%,
|
#F08399 50%,
|
||||||
#B94E73 100%);
|
#B94E73 100%
|
||||||
|
);
|
||||||
border-radius: 999rpx;
|
border-radius: 999rpx;
|
||||||
padding: 2rpx 10rpx;
|
padding: 2rpx 10rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -1127,10 +1099,10 @@ const loadUsersAndStartScroll = () => {
|
|||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user