优化摸大红前台交互与主题按钮样式

列表改为滚动触底自动分页,详情与订单页铺满主内容区,按钮统一为橙色主题描边/实心样式。
This commit is contained in:
yml2213
2026-07-16 19:40:37 +08:00
parent 6d61027318
commit 094a91c985
5 changed files with 427 additions and 99 deletions
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { onMounted, ref, watch } from 'vue'
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { showToast } from 'vant'
import MobileBottomNav from '@/components/MobileBottomNav.vue'
@@ -24,12 +24,22 @@ const loadingMore = ref(false)
const keyword = ref('')
const activeCategoryId = ref<number | null>(null)
const failedCover = ref<Record<number, boolean>>({})
const listPanel = ref<HTMLElement | null>(null)
const loadMoreSentinel = ref<HTMLElement | null>(null)
let searchTimer: ReturnType<typeof setTimeout> | null = null
let loadMoreObserver: IntersectionObserver | null = null
onMounted(async () => {
await loadCategories()
applyRouteQuery()
await loadProducts(true)
setupLoadMoreObserver()
})
onBeforeUnmount(() => {
loadMoreObserver?.disconnect()
loadMoreObserver = null
if (searchTimer) clearTimeout(searchTimer)
})
watch(
@@ -64,13 +74,13 @@ async function loadCategories() {
}
async function loadProducts(reset = true) {
if (loadingMore.value) return
if (reset) {
if (loading.value) return
loading.value = true
page.value = 1
hasMore.value = true
} else {
if (!hasMore.value) return
if (loading.value || loadingMore.value || !hasMore.value) return
loadingMore.value = true
}
try {
@@ -92,6 +102,25 @@ async function loadProducts(reset = true) {
}
}
function setupLoadMoreObserver() {
loadMoreObserver?.disconnect()
loadMoreObserver = new IntersectionObserver(
entries => {
if (!entries.some(item => item.isIntersecting)) return
if (loading.value || loadingMore.value || !hasMore.value) return
loadProducts(false)
},
{ root: listPanel.value, rootMargin: '160px 0px', threshold: 0 }
)
nextTick(() => {
if (loadMoreSentinel.value) loadMoreObserver?.observe(loadMoreSentinel.value)
})
}
watch([listPanel, loadMoreSentinel], () => {
setupLoadMoreObserver()
})
function selectCategory(id: number | null) {
activeCategoryId.value = id
syncQuery()
@@ -166,7 +195,7 @@ function showCover(item: MohongProduct) {
</button>
</aside>
<section class="list-panel">
<section ref="listPanel" class="list-panel">
<van-loading v-if="loading" class="state-loading" size="24px" vertical>加载中...</van-loading>
<van-empty v-else-if="products.length === 0" description="暂无商品" />
<div v-else class="product-list">
@@ -196,17 +225,14 @@ function showCover(item: MohongProduct) {
<span class="buy-icon">购</span>
</button>
</div>
<van-button
v-if="hasMore && products.length"
size="small"
plain
block
:loading="loadingMore"
class="load-more"
@click="loadProducts(false)"
>
加载更多
</van-button>
<div
v-if="products.length"
ref="loadMoreSentinel"
class="load-sentinel"
aria-hidden="true"
/>
<div v-if="loadingMore" class="load-tip">加载中...</div>
<div v-else-if="!loading && products.length && !hasMore" class="load-tip">已经到底了</div>
</section>
</div>
<MobileBottomNav />
@@ -379,8 +405,14 @@ function showCover(item: MohongProduct) {
font-size: 12px;
font-weight: 700;
}
.load-more {
margin: 12px;
width: calc(100% - 24px);
.load-sentinel {
width: 100%;
height: 1px;
}
.load-tip {
padding: 12px;
text-align: center;
color: #b0b8c4;
font-size: 12px;
}
</style>