优化:列表显示优化
This commit is contained in:
parent
830b8c594d
commit
14c4ec322f
@ -1,31 +1,116 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-container class="home-container">
|
<div class="home-page">
|
||||||
<v-row justify="center" align="center" class="home-tabs">
|
<v-container class="home-container">
|
||||||
<v-tabs v-model="activeTab" class="home-tab-bar">
|
<v-row justify="center" align="center" class="home-tabs">
|
||||||
<v-tab value="overview">Market Overview</v-tab>
|
<v-tabs v-model="activeTab" class="home-tab-bar">
|
||||||
<v-tab value="trending">Trending</v-tab>
|
<v-tab value="overview">Market Overview</v-tab>
|
||||||
<v-tab value="portfolio">Portfolio</v-tab>
|
<v-tab value="trending">Trending</v-tab>
|
||||||
</v-tabs>
|
<v-tab value="portfolio">Portfolio</v-tab>
|
||||||
</v-row>
|
</v-tabs>
|
||||||
<!-- 可滚动容器作为 v-pull-to-refresh 的父元素,组件据此判断 scrollTop 仅在顶部时才响应下拉 -->
|
</v-row>
|
||||||
<div ref="scrollRef" class="home-list-scroll">
|
<!-- 可滚动容器作为 v-pull-to-refresh 的父元素,组件据此判断 scrollTop 仅在顶部时才响应下拉 -->
|
||||||
<v-pull-to-refresh class="pull-to-refresh" @load="onRefresh">
|
<div ref="scrollRef" class="home-list-scroll">
|
||||||
<div class="pull-to-refresh-inner">
|
<v-pull-to-refresh class="pull-to-refresh" @load="onRefresh">
|
||||||
<div class="home-list">
|
<div class="pull-to-refresh-inner">
|
||||||
<MarketCard v-for="id in listLength" :key="id" :id="String(id)" />
|
<div class="home-list">
|
||||||
</div>
|
<MarketCard v-for="id in listLength" :key="id" :id="String(id)" />
|
||||||
<div class="load-more-footer">
|
</div>
|
||||||
<div ref="sentinelRef" class="load-more-sentinel" aria-hidden="true" />
|
<div class="load-more-footer">
|
||||||
<div v-if="loadingMore" class="load-more-indicator">
|
<div ref="sentinelRef" class="load-more-sentinel" aria-hidden="true" />
|
||||||
<v-progress-circular indeterminate size="24" width="2" />
|
<div v-if="loadingMore" class="load-more-indicator">
|
||||||
<span>加载中...</span>
|
<v-progress-circular indeterminate size="24" width="2" />
|
||||||
|
<span>加载中...</span>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="listLength >= maxItems" class="no-more-tip">没有更多了</div>
|
||||||
|
<v-btn
|
||||||
|
v-else
|
||||||
|
class="load-more-btn"
|
||||||
|
variant="outlined"
|
||||||
|
color="primary"
|
||||||
|
:disabled="loadingMore"
|
||||||
|
@click="loadMore"
|
||||||
|
>
|
||||||
|
加载更多
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-pull-to-refresh>
|
||||||
|
</div>
|
||||||
|
</v-container>
|
||||||
|
|
||||||
|
<footer class="home-footer">
|
||||||
|
<div class="footer-inner">
|
||||||
|
<div class="footer-top">
|
||||||
|
<div class="footer-brand">
|
||||||
|
<div class="footer-logo">
|
||||||
|
<span class="logo-mark">M</span>
|
||||||
|
<span class="logo-text">Polymarket</span>
|
||||||
|
</div>
|
||||||
|
<p class="footer-slogan">The World's Largest Prediction Market™</p>
|
||||||
|
</div>
|
||||||
|
<div class="footer-links-row">
|
||||||
|
<div class="footer-col">
|
||||||
|
<h4 class="footer-col-title">Support & Social</h4>
|
||||||
|
<ul class="footer-link-list">
|
||||||
|
<li><a href="#">Contact us</a></li>
|
||||||
|
<li><a href="#">Learn</a></li>
|
||||||
|
<li><a href="#">X (Twitter)</a></li>
|
||||||
|
<li><a href="#">Instagram</a></li>
|
||||||
|
<li><a href="#">Discord</a></li>
|
||||||
|
<li><a href="#">TikTok</a></li>
|
||||||
|
<li><a href="#">News</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="footer-col">
|
||||||
|
<h4 class="footer-col-title">Polymarket</h4>
|
||||||
|
<ul class="footer-link-list">
|
||||||
|
<li><a href="#">Accuracy</a></li>
|
||||||
|
<li><a href="#">Activity</a></li>
|
||||||
|
<li><a href="#">Leaderboard</a></li>
|
||||||
|
<li><a href="#">Rewards</a></li>
|
||||||
|
<li><a href="#">Press</a></li>
|
||||||
|
<li><a href="#">Careers</a></li>
|
||||||
|
<li><a href="#">APIs</a></li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="listLength >= maxItems" class="no-more-tip">没有更多了</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</v-pull-to-refresh>
|
<div class="footer-bottom">
|
||||||
</div>
|
<div class="footer-legal-links">
|
||||||
</v-container>
|
<a href="#">Adventure One QSS Inc. © 2026</a>
|
||||||
|
<span class="sep">/</span>
|
||||||
|
<a href="#">Privacy</a>
|
||||||
|
<span class="sep">/</span>
|
||||||
|
<a href="#">Terms of Use</a>
|
||||||
|
<span class="sep">/</span>
|
||||||
|
<a href="#">Help Center</a>
|
||||||
|
<span class="sep">/</span>
|
||||||
|
<a href="#">Docs</a>
|
||||||
|
</div>
|
||||||
|
<div class="footer-lang-social">
|
||||||
|
<v-select
|
||||||
|
v-model="footerLang"
|
||||||
|
:items="['English']"
|
||||||
|
density="compact"
|
||||||
|
hide-details
|
||||||
|
variant="outlined"
|
||||||
|
class="footer-lang-select"
|
||||||
|
/>
|
||||||
|
<div class="footer-social-icons">
|
||||||
|
<v-icon size="20">mdi-email-outline</v-icon>
|
||||||
|
<v-icon size="20">mdi-twitter</v-icon>
|
||||||
|
<v-icon size="20">mdi-instagram</v-icon>
|
||||||
|
<v-icon size="20">mdi-discord</v-icon>
|
||||||
|
<v-icon size="20">mdi-music</v-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="footer-disclaimer">
|
||||||
|
Polymarket operates globally through separate legal entities. Polymarket US is operated by QCX LLC d/b/a Polymarket US, a CFTC-regulated Designated Contract Market. This international platform is not regulated by the CFTC and operates independently. Trading involves substantial risk of loss. See our <a href="#">Terms of Service & Privacy Policy</a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@ -40,10 +125,13 @@ const maxItems = 50
|
|||||||
|
|
||||||
const listLength = ref(INITIAL_COUNT)
|
const listLength = ref(INITIAL_COUNT)
|
||||||
const loadingMore = ref(false)
|
const loadingMore = ref(false)
|
||||||
|
const footerLang = ref('English')
|
||||||
const scrollRef = ref<HTMLElement | null>(null)
|
const scrollRef = ref<HTMLElement | null>(null)
|
||||||
const sentinelRef = ref<HTMLElement | null>(null)
|
const sentinelRef = ref<HTMLElement | null>(null)
|
||||||
let observer: IntersectionObserver | null = null
|
let observer: IntersectionObserver | null = null
|
||||||
|
|
||||||
|
const SCROLL_LOAD_THRESHOLD = 280
|
||||||
|
|
||||||
function onRefresh({ done }: { done: () => void }) {
|
function onRefresh({ done }: { done: () => void }) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
listLength.value = INITIAL_COUNT
|
listLength.value = INITIAL_COUNT
|
||||||
@ -60,23 +148,39 @@ function loadMore() {
|
|||||||
}, 400)
|
}, 400)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkScrollLoad() {
|
||||||
|
const el = scrollRef.value
|
||||||
|
if (!el || loadingMore.value || listLength.value >= maxItems) return
|
||||||
|
const { scrollTop, clientHeight, scrollHeight } = el
|
||||||
|
if (scrollHeight - scrollTop - clientHeight < SCROLL_LOAD_THRESHOLD) {
|
||||||
|
loadMore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if (!sentinelRef.value || !scrollRef.value) return
|
const scrollEl = scrollRef.value
|
||||||
|
const sentinel = sentinelRef.value
|
||||||
|
if (!sentinel || !scrollEl) return
|
||||||
|
|
||||||
observer = new IntersectionObserver(
|
observer = new IntersectionObserver(
|
||||||
(entries) => {
|
(entries) => {
|
||||||
if (!entries[0]?.isIntersecting) return
|
if (!entries[0]?.isIntersecting) return
|
||||||
loadMore()
|
loadMore()
|
||||||
},
|
},
|
||||||
{ root: scrollRef.value, rootMargin: '80px', threshold: 0 }
|
{ root: scrollEl, rootMargin: `${SCROLL_LOAD_THRESHOLD}px`, threshold: 0 }
|
||||||
)
|
)
|
||||||
observer.observe(sentinelRef.value)
|
observer.observe(sentinel)
|
||||||
|
|
||||||
|
scrollEl.addEventListener('scroll', checkScrollLoad, { passive: true })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (observer && sentinelRef.value) observer.unobserve(sentinelRef.value)
|
const sentinel = sentinelRef.value
|
||||||
|
if (observer && sentinel) observer.unobserve(sentinel)
|
||||||
observer = null
|
observer = null
|
||||||
|
scrollRef.value?.removeEventListener('scroll', checkScrollLoad)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -111,15 +215,23 @@ onUnmounted(() => {
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
|
/* 占满剩余视口高度,内容多时在内部滚动 */
|
||||||
|
min-height: calc(100vh - 64px - 48px - 32px);
|
||||||
max-height: calc(100vh - 64px - 48px - 32px);
|
max-height: calc(100vh - 64px - 48px - 32px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 卡片最小宽度与 MarketCard 一致 310px;auto-fill 按可用宽度自动 1~4 列,避免第三列被裁 */
|
||||||
.home-list {
|
.home-list {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(310px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-list > * {
|
||||||
|
min-width: 0;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.load-more-footer {
|
.load-more-footer {
|
||||||
@ -136,7 +248,7 @@ onUnmounted(() => {
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
height: 1px;
|
height: 8px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
@ -152,10 +264,15 @@ onUnmounted(() => {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* When only one column fits */
|
.load-more-btn {
|
||||||
@media (max-width: 600px) {
|
text-transform: none;
|
||||||
.home-list > div {
|
margin-top: 4px;
|
||||||
flex: 1 1 100%;
|
}
|
||||||
|
|
||||||
|
/* 大屏最多 4 列,避免过宽时出现 5 列以上 */
|
||||||
|
@media (min-width: 1320px) {
|
||||||
|
.home-list {
|
||||||
|
grid-template-columns: repeat(4, minmax(310px, 1fr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,4 +332,198 @@ onUnmounted(() => {
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
.home-page {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-footer {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #374151;
|
||||||
|
color: rgba(255, 255, 255, 0.85);
|
||||||
|
margin-top: auto;
|
||||||
|
padding: 48px 24px 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-inner {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-top {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 48px;
|
||||||
|
padding-bottom: 32px;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-brand {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-logo {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-mark {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 18px;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-text {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-slogan {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-links-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 64px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-col-title {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
margin: 0 0 12px 0;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-link-list {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-link-list li {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-link-list a {
|
||||||
|
color: rgba(255, 255, 255, 0.75);
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-link-list a:hover {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-bottom {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
padding-top: 24px;
|
||||||
|
padding-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-legal-links {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-legal-links a {
|
||||||
|
color: rgba(255, 255, 255, 0.75);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-legal-links a:hover {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-legal-links .sep {
|
||||||
|
margin: 0 8px;
|
||||||
|
color: rgba(255, 255, 255, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-lang-social {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-lang-select {
|
||||||
|
max-width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-lang-select :deep(.v-field) {
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-social-icons {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-social-icons .v-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-social-icons .v-icon:hover {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-disclaimer {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: rgba(255, 255, 255, 0.6);
|
||||||
|
margin: 0;
|
||||||
|
max-width: 720px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-disclaimer a {
|
||||||
|
color: rgba(255, 255, 255, 0.85);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-disclaimer a:hover {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.home-footer {
|
||||||
|
padding: 32px 16px 24px;
|
||||||
|
}
|
||||||
|
.footer-top {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 32px;
|
||||||
|
padding-bottom: 24px;
|
||||||
|
}
|
||||||
|
.footer-links-row {
|
||||||
|
gap: 32px;
|
||||||
|
}
|
||||||
|
.footer-bottom {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user