优化摸大红前台交互与主题按钮样式
列表改为滚动触底自动分页,详情与订单页铺满主内容区,按钮统一为橙色主题描边/实心样式。
This commit is contained in:
@@ -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 { ElMessage } from 'element-plus'
|
||||
import { Search, Tickets } from '@element-plus/icons-vue'
|
||||
@@ -23,12 +23,21 @@ const page = ref(1)
|
||||
const hasMore = ref(true)
|
||||
const keyword = ref('')
|
||||
const activeCategoryId = ref<number | 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 +73,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 {
|
||||
@@ -93,6 +102,26 @@ 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: null, rootMargin: '200px 0px', threshold: 0 }
|
||||
)
|
||||
nextTick(() => {
|
||||
if (loadMoreSentinel.value) loadMoreObserver?.observe(loadMoreSentinel.value)
|
||||
})
|
||||
}
|
||||
|
||||
watch(loadMoreSentinel, el => {
|
||||
loadMoreObserver?.disconnect()
|
||||
if (el) loadMoreObserver?.observe(el)
|
||||
})
|
||||
|
||||
function selectCategory(id: number | null) {
|
||||
activeCategoryId.value = id
|
||||
syncQuery()
|
||||
@@ -138,10 +167,13 @@ const activeCategoryName = () => {
|
||||
@input="onKeywordInput"
|
||||
/>
|
||||
</div>
|
||||
<el-button size="small" :icon="Tickets" @click="router.push('/mohong/orders')">
|
||||
我的订单
|
||||
</el-button>
|
||||
<el-button size="small" plain @click="router.push('/')">租号大厅</el-button>
|
||||
<button type="button" class="toolbar-btn outline" @click="router.push('/mohong/orders')">
|
||||
<el-icon><Tickets /></el-icon>
|
||||
<span>我的订单</span>
|
||||
</button>
|
||||
<button type="button" class="toolbar-btn outline" @click="router.push('/')">
|
||||
租号大厅
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -176,11 +208,17 @@ const activeCategoryName = () => {
|
||||
<div v-else v-loading="loading" class="product-grid">
|
||||
<MohongProductCard v-for="item in products" :key="item.id" :product="item" />
|
||||
</div>
|
||||
<div v-if="!loading && products.length" class="load-state">
|
||||
<el-button v-if="hasMore" :loading="loadingMore" @click="loadProducts(false)">
|
||||
加载更多
|
||||
</el-button>
|
||||
<span v-else class="end">已经到底了</span>
|
||||
<div
|
||||
v-if="products.length"
|
||||
ref="loadMoreSentinel"
|
||||
class="load-sentinel"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div v-if="loadingMore" class="load-state">
|
||||
<span class="loading-more">加载中...</span>
|
||||
</div>
|
||||
<div v-else-if="!loading && products.length && !hasMore" class="load-state">
|
||||
<span class="end">已经到底了</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -244,12 +282,20 @@ const activeCategoryName = () => {
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
width: 220px;
|
||||
height: 32px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 8px;
|
||||
height: 36px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid #eef1f5;
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
color: #94a3b8;
|
||||
transition:
|
||||
border-color 0.2s,
|
||||
box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.search-box:focus-within {
|
||||
border-color: rgba(255, 106, 0, 0.45);
|
||||
box-shadow: 0 0 0 3px rgba(255, 106, 0, 0.08);
|
||||
}
|
||||
|
||||
.search-box input {
|
||||
@@ -262,6 +308,36 @@ const activeCategoryName = () => {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 对齐顶栏客服/发布账号的主题按钮风格 */
|
||||
.toolbar-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
height: 36px;
|
||||
padding: 0 14px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid transparent;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.toolbar-btn.outline {
|
||||
border-color: rgba(255, 106, 0, 0.32);
|
||||
background: #fff8f2;
|
||||
color: #ff6a00;
|
||||
}
|
||||
|
||||
.toolbar-btn.outline:hover {
|
||||
border-color: #ff6a00;
|
||||
background: #fff3e8;
|
||||
box-shadow: 0 4px 12px rgba(255, 106, 0, 0.1);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.shop-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 140px minmax(0, 1fr);
|
||||
@@ -357,6 +433,11 @@ const activeCategoryName = () => {
|
||||
color: #17233d;
|
||||
}
|
||||
|
||||
.load-sentinel {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.load-state {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -364,6 +445,7 @@ const activeCategoryName = () => {
|
||||
padding: 12px 0 8px;
|
||||
}
|
||||
|
||||
.loading-more,
|
||||
.end {
|
||||
color: #b0b8c4;
|
||||
font-size: 12px;
|
||||
|
||||
Reference in New Issue
Block a user