Compare commits
13 Commits
4d42d9a6d0
...
e29a718b2f
| Author | SHA1 | Date | |
|---|---|---|---|
| e29a718b2f | |||
| c8501c9895 | |||
| 3370f0507d | |||
| 7588498452 | |||
| aff114afdd | |||
| 7222598961 | |||
| c05953b84b | |||
| 091a3eb5c9 | |||
| ef46ec832e | |||
| cc5565ffaf | |||
| 95d8c75500 | |||
| 72ea3f4305 | |||
| d5d311c2b2 |
1104
docs/superpowers/specs/2026-05-22-sms-register-design.md
Normal file
1104
docs/superpowers/specs/2026-05-22-sms-register-design.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,52 +1,24 @@
|
||||
<template>
|
||||
<scroll-view
|
||||
class="waterfall-scroll"
|
||||
:style="scrollStyle"
|
||||
scroll-x
|
||||
:show-scrollbar="false"
|
||||
:scroll-left="isIOS ? undefined : scrollLeft"
|
||||
: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)"
|
||||
>
|
||||
<scroll-view class="waterfall-scroll" :style="scrollStyle" scroll-x :show-scrollbar="false"
|
||||
:scroll-left="isIOS ? undefined : scrollLeft" :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)" />
|
||||
|
||||
<!-- 封面图 -->
|
||||
<image
|
||||
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' }"
|
||||
/>
|
||||
<image 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] }"
|
||||
: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] }"
|
||||
: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] }"
|
||||
: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] }"
|
||||
:style="{ width: card.w + 'px', height: card.h + 'px', borderRadius: card.radius + 'px' }" />
|
||||
|
||||
<!-- 底部点赞数 -->
|
||||
<view class="wf-card-footer">
|
||||
@ -184,6 +156,10 @@ const startAutoScroll = () => {
|
||||
scrollUpdateTimer = setInterval(() => {
|
||||
if (!isComponentMounted || userInteracting || isLoadingMore) return
|
||||
autoScrollPos += AUTO_SCROLL_SPEED_ANDROID
|
||||
// 滚到头时重置到 0,实现无缝循环
|
||||
if (autoScrollPos >= totalWidth.value) {
|
||||
autoScrollPos = 0
|
||||
}
|
||||
scrollLeft.value = autoScrollPos
|
||||
|
||||
// 预加载
|
||||
@ -358,6 +334,13 @@ class WaterfallLayout {
|
||||
// 列宽固定 = rowH × 9/16,严格竖长 9:16,span 只影响高度
|
||||
this.colW = Math.round(this.rowH * 4 / 3)
|
||||
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) {
|
||||
@ -392,10 +375,28 @@ class WaterfallLayout {
|
||||
let i = 0
|
||||
while (i < remaining.length) {
|
||||
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,尝试下一个
|
||||
if (sum + rawSpan > ROWS) {
|
||||
i++
|
||||
// 已检查完所有卡片仍无法放入 → 当前列已满但未凑满 ROWS,强开新列
|
||||
if (i >= remaining.length && col.length > 0) {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
@ -404,6 +405,15 @@ class WaterfallLayout {
|
||||
col.push(item)
|
||||
sum += rawSpan
|
||||
|
||||
// 非 N 卡重置连续计数;N 卡累加计数
|
||||
if (!item._blank) {
|
||||
if (this._isNLevel(item)) {
|
||||
this.consecutiveNCount++
|
||||
} else {
|
||||
this.consecutiveNCount = 0
|
||||
}
|
||||
}
|
||||
|
||||
// 如果已经达到 ROWS,结束这列
|
||||
if (sum >= ROWS) break
|
||||
}
|
||||
@ -413,6 +423,13 @@ class WaterfallLayout {
|
||||
const item = remaining.shift()
|
||||
col.push(item)
|
||||
sum = item.span
|
||||
if (!item._blank) {
|
||||
if (this._isNLevel(item)) {
|
||||
this.consecutiveNCount++
|
||||
} else {
|
||||
this.consecutiveNCount = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
colIndex++
|
||||
@ -437,7 +454,7 @@ class WaterfallLayout {
|
||||
for (const u of colUsers) {
|
||||
const span = u.span || 1
|
||||
const { w, h } = this._cardSize(span)
|
||||
if (!u._pad) {
|
||||
if (!u._pad && !u._blank) {
|
||||
result.push({ ...u, left: colX, top: curY, w, h, radius: 8 })
|
||||
}
|
||||
curY += h + this.gap
|
||||
@ -450,6 +467,7 @@ class WaterfallLayout {
|
||||
// 计算全部卡片布局(重置状态)
|
||||
compute(users) {
|
||||
this.curX = 0
|
||||
this.consecutiveNCount = 0
|
||||
const columns = this._groupIntoColumns(users)
|
||||
const result = []
|
||||
for (const col of columns) {
|
||||
@ -508,9 +526,9 @@ const cardStyle = (card) => ({
|
||||
|
||||
const borderStyle = (card) => ({
|
||||
position: 'absolute',
|
||||
inset: `-${BORDER_W}px`,
|
||||
borderRadius: (card.radius + BORDER_W) + 'px',
|
||||
background: BORDER_COLORS[Math.abs(card.id) % BORDER_COLORS.length],
|
||||
inset: `-${BORDER_W}px`,
|
||||
zIndex: 0,
|
||||
opacity: 0.85,
|
||||
})
|
||||
@ -566,7 +584,7 @@ const batchGetPresignedUrls = async (urls) => {
|
||||
}
|
||||
return map
|
||||
}
|
||||
} catch (_) {}
|
||||
} catch (_) { }
|
||||
return {}
|
||||
}
|
||||
|
||||
@ -641,7 +659,7 @@ const loadUsers = async () => {
|
||||
span: item.span ?? null,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
allUsers.value = withData
|
||||
cards.value = layout.compute(withData)
|
||||
totalWidth.value = layout.getTotalWidth()
|
||||
@ -731,6 +749,12 @@ const appendMore = async () => {
|
||||
cards.value = [...cards.value, ...placed]
|
||||
totalWidth.value = layout.getTotalWidth()
|
||||
|
||||
// totalWidth 变化后重启 iOS CSS 动画,确保新宽度生效
|
||||
if (isIOS && !iosScrollPaused.value) {
|
||||
stopIOSAutoScroll()
|
||||
startIOSAutoScroll()
|
||||
}
|
||||
|
||||
} else {
|
||||
// 没有可追加的数据,标记失败停止
|
||||
appendFailed = true
|
||||
@ -774,7 +798,7 @@ const handleCardClick = (card) => {
|
||||
});
|
||||
} else {
|
||||
// 第一次点击,单击跳转
|
||||
if(card.id){
|
||||
if (card.id) {
|
||||
cardTapTimers[card.id] = setTimeout(() => {
|
||||
delete cardTapTimers[card.id];
|
||||
uni.navigateTo({ url: `/pages/asset-detail/asset-detail?asset_id=${card.id}` });
|
||||
@ -915,7 +939,7 @@ onUnmounted(() => {
|
||||
try {
|
||||
uni.offAppShow(onAppShowHandler)
|
||||
uni.offAppHide(onAppHideHandler)
|
||||
} catch (_) {}
|
||||
} catch (_) { }
|
||||
}
|
||||
})
|
||||
|
||||
@ -999,8 +1023,11 @@ const loadUsersAndStartScroll = () => {
|
||||
}
|
||||
|
||||
@keyframes iosAutoScroll {
|
||||
|
||||
/* from { transform: translateX(0); } */
|
||||
to { transform: translateX(var(--scroll-dist)); }
|
||||
to {
|
||||
transform: translateX(var(--scroll-dist));
|
||||
}
|
||||
}
|
||||
|
||||
.wf-card {
|
||||
@ -1010,6 +1037,7 @@ const loadUsersAndStartScroll = () => {
|
||||
transform-origin: top center;
|
||||
overflow: visible;
|
||||
pointer-events: visible;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
.wf-card:active {
|
||||
@ -1019,6 +1047,7 @@ const loadUsersAndStartScroll = () => {
|
||||
.wf-card-border {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
will-change: opacity;
|
||||
}
|
||||
|
||||
.wf-card-img {
|
||||
@ -1046,10 +1075,9 @@ const loadUsersAndStartScroll = () => {
|
||||
|
||||
.wf-likes-wrap {
|
||||
background: linear-gradient(to bottom right,
|
||||
#F0E4B1 0%,
|
||||
#F08399 50%,
|
||||
#B94E73 100%
|
||||
);
|
||||
#F0E4B1 0%,
|
||||
#F08399 50%,
|
||||
#B94E73 100%);
|
||||
border-radius: 999rpx;
|
||||
padding: 2rpx 10rpx;
|
||||
display: flex;
|
||||
@ -1099,10 +1127,10 @@ const loadUsersAndStartScroll = () => {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1.1);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user