feat: paginate customer orders and polish the dashboard hero
Add paged orders API and infinite scroll, short order numbers, and align the guest invite card with the logged-in hero shell. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -27,12 +27,7 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<PullRefresh v-model="refreshing" @refresh="onRefresh">
|
||||
<template #pulling><Loading size="18" /></template>
|
||||
<template #loosing><Loading size="18" /></template>
|
||||
<template #loading><Loading size="18" /></template>
|
||||
<template #success><span /></template>
|
||||
|
||||
<AppPullRefresh v-model="refreshing" @refresh="onRefresh">
|
||||
<container class="home__body" :class="{ 'is-revealed': revealed }">
|
||||
<HeroCarousel
|
||||
v-if="slides.length"
|
||||
@@ -74,7 +69,7 @@
|
||||
<p>هنوز محصولی در مجموعه ثبت نشده است.</p>
|
||||
</section>
|
||||
</container>
|
||||
</PullRefresh>
|
||||
</AppPullRefresh>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -82,7 +77,7 @@
|
||||
import { brand } from '../brand'
|
||||
import { computed, nextTick, onActivated, onMounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { Loading, PullRefresh } from 'vant'
|
||||
import AppPullRefresh from '../components/ui/AppPullRefresh.vue'
|
||||
import Icon from '../components/icon/Icon.vue'
|
||||
import { openProduct as openProductMorph } from '../composables/useProductMorph'
|
||||
import RateStrip from '../components/home/RateStrip.vue'
|
||||
@@ -94,7 +89,7 @@ import ProductCarousel from '../components/home/ProductCarousel.vue'
|
||||
import { useConfigStore } from '../stores/configStore'
|
||||
import type { HomeCategory } from '../stores/configStore'
|
||||
import { useFiltersStore } from '../stores/filtersStore'
|
||||
import type { AdminSlide, GoldPriceQuote, ProductSummary } from '../api/client'
|
||||
import { api, type AdminSlide, type GoldPriceQuote, type ProductSummary } from '../api/client'
|
||||
import { useCachedReads } from '../api/cachedReads'
|
||||
import { useCatalogStore } from '../stores/catalogStore'
|
||||
import { KEYS } from '../stores/queryCache'
|
||||
@@ -178,15 +173,18 @@ function goNewest() {
|
||||
// cached until pull-to-refresh asks the server again.
|
||||
async function loadData(force = false) {
|
||||
const [catalogResult, catsResult, priceResult, slidesResult] = await Promise.allSettled([
|
||||
// The home rails read `catalog.products`, so the catalogue is loaded
|
||||
// explicitly. It used to come in as a side effect of `memory.init()`, which
|
||||
// now reads the small facets endpoint instead of the whole catalogue — so
|
||||
// without this call the home screen would render empty rails.
|
||||
//
|
||||
// Still the unpaged list: the rails slice it locally («جدیدترین», per
|
||||
// category). Moving them onto paged reads is step 4 of
|
||||
// docs/CATALOG-PAGINATION-PLAN.md.
|
||||
catalog.load(force),
|
||||
// STUB-03: rails need a small newest page. Pull-to-refresh must drop the
|
||||
// cached catalogue first (same rule as before paging) or the screen keeps
|
||||
// showing the previous slice while gold/slides refresh.
|
||||
(async () => {
|
||||
if (force) catalog.invalidate()
|
||||
const page = await api.listProductsPaged({
|
||||
page: 1,
|
||||
limit: Math.max(REEL_LIMIT, LIST_LIMIT),
|
||||
sort: 'newest',
|
||||
})
|
||||
catalog.replace(page.rows)
|
||||
})(),
|
||||
memory.init(force),
|
||||
reads.goldPrice({ force }),
|
||||
reads.homeSlider({ force }),
|
||||
@@ -201,8 +199,13 @@ async function loadData(force = false) {
|
||||
}
|
||||
|
||||
async function onRefresh() {
|
||||
await loadData(true)
|
||||
refreshing.value = false
|
||||
try {
|
||||
await loadData(true)
|
||||
} finally {
|
||||
// Always release Vant's PullRefresh, even when a request fails — otherwise
|
||||
// the spinner sticks until a full reload (same bug Dashboard already fixed).
|
||||
refreshing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
@@ -252,25 +255,22 @@ onActivated(async () => {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
// Same chrome as components/ui/PageBar.vue — one look for every top bar.
|
||||
.home__bar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 20;
|
||||
background: alpha(@canvas, 82%);
|
||||
backdrop-filter: blur(18px) saturate(140%);
|
||||
// A hairline rule cut a hard line across the page. A very soft fade under
|
||||
// the bar separates it from the content that scrolls beneath without
|
||||
// drawing an edge.
|
||||
border-bottom: 1px solid alpha(@wine, 5%);
|
||||
box-shadow: 0 8px 20px -18px alpha(@wine, 55%);
|
||||
background: alpha(@canvas, 88%);
|
||||
backdrop-filter: blur(14px);
|
||||
border-bottom: 1px solid @hairline;
|
||||
}
|
||||
|
||||
.home__barInner {
|
||||
max-width: 768px;
|
||||
margin: 0 auto;
|
||||
padding: 10px 16px;
|
||||
padding: 11px 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -278,15 +278,15 @@ onActivated(async () => {
|
||||
}
|
||||
|
||||
.home__logo {
|
||||
// Sized to match the pill's height so the two sides of the bar carry equal
|
||||
// optical weight instead of the logo looking stranded next to it.
|
||||
height: 25px;
|
||||
// Tall enough to read at a glance; still fits the bar next to RateStrip.
|
||||
height: 36px;
|
||||
width: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.home__body {
|
||||
// Clears the fixed bar (~50px) plus a little breathing room.
|
||||
padding-top: 58px;
|
||||
// Clears the fixed bar (same offset as PageBar / CartView).
|
||||
padding-top: 76px;
|
||||
padding-bottom: 90px;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user