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:
@@ -15,11 +15,17 @@
|
||||
-->
|
||||
<template>
|
||||
<article class="oi">
|
||||
<div class="oi__art">
|
||||
<img v-if="lookup?.imageUrl" v-lazy="lookup.imageUrl" :alt="lookup?.productTitle ?? ''" />
|
||||
<div v-else class="oi__artEmpty" aria-hidden="true">
|
||||
<Icon icon="vuesax-linear:diamonds" size="18" />
|
||||
</div>
|
||||
<button
|
||||
v-if="lookup?.imageUrl"
|
||||
type="button"
|
||||
class="oi__art"
|
||||
aria-label="بزرگنمایی تصویر"
|
||||
@click="previewOpen = true"
|
||||
>
|
||||
<img v-lazy="lookup.imageUrl" :alt="lookup?.productTitle ?? ''" />
|
||||
</button>
|
||||
<div v-else class="oi__art oi__art--empty" aria-hidden="true">
|
||||
<Icon icon="vuesax-linear:diamonds" size="18" />
|
||||
</div>
|
||||
|
||||
<div class="oi__main">
|
||||
@@ -43,11 +49,29 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Teleport to="body">
|
||||
<Transition name="oi-preview">
|
||||
<div
|
||||
v-if="previewOpen && lookup?.imageUrl"
|
||||
class="oi__preview"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="پیشنمایش تصویر"
|
||||
@click.self="previewOpen = false"
|
||||
>
|
||||
<button type="button" class="oi__previewClose" aria-label="بستن" @click="previewOpen = false">
|
||||
<Icon icon="vuesax-linear:close-circle" size="22" />
|
||||
</button>
|
||||
<img class="oi__previewImg" :src="lookup.imageUrl" :alt="lookup.productTitle ?? ''" />
|
||||
</div>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { computed, ref, watch, onBeforeUnmount } from 'vue'
|
||||
import Icon from '../icon/Icon.vue'
|
||||
import { orderItemLookup } from '../../composables/useOrderItemProduct'
|
||||
import type { OrderItemDetail } from '../../api/client'
|
||||
@@ -58,6 +82,15 @@ const props = defineProps<{ value: OrderItemDetail }>()
|
||||
// nothing to wait for — the row renders complete on its first frame instead of
|
||||
// filling in after a catalogue download.
|
||||
const lookup = computed(() => orderItemLookup(props.value))
|
||||
const previewOpen = ref(false)
|
||||
|
||||
watch(previewOpen, (open) => {
|
||||
document.body.style.overflow = open ? 'hidden' : ''
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (previewOpen.value) document.body.style.overflow = ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@@ -74,9 +107,20 @@ const lookup = computed(() => orderItemLookup(props.value))
|
||||
flex: 0 0 62px;
|
||||
width: 62px;
|
||||
height: 62px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
background: @hairlineSoft;
|
||||
cursor: zoom-in;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.oi__art--empty {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: @inkFaint;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.oi__art img {
|
||||
@@ -84,6 +128,7 @@ const lookup = computed(() => orderItemLookup(props.value))
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
// Dark mode: product photos are shot on white, so they sit on a light mat
|
||||
@@ -94,14 +139,6 @@ html.theme-dark .oi__art img {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.oi__artEmpty {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: @inkFaint;
|
||||
}
|
||||
|
||||
.oi__main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
@@ -117,7 +154,8 @@ html.theme-dark .oi__art img {
|
||||
.oi__title {
|
||||
margin: 0;
|
||||
font-size: 12.5px;
|
||||
font-weight: 700;
|
||||
/* yekan_bakh has no 600 face — 600 silently maps to bold (700). */
|
||||
font-weight: 500;
|
||||
color: @ink;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
@@ -139,14 +177,61 @@ html.theme-dark .oi__art img {
|
||||
|
||||
.oi__figure {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
font-weight: 500;
|
||||
color: @inkSoft;
|
||||
}
|
||||
.oi__figure i {
|
||||
font-style: normal;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
font-weight: 400;
|
||||
color: @inkFaint;
|
||||
padding-inline-start: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
/* Teleported to body — unscoped so the overlay styles apply. */
|
||||
.oi__preview {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 2100;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 28px 18px;
|
||||
background: rgba(8, 8, 10, 0.88);
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
|
||||
.oi__previewClose {
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
inset-inline-end: 14px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
color: #fff;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.oi__previewImg {
|
||||
max-width: min(920px, 100%);
|
||||
max-height: min(80vh, 100%);
|
||||
object-fit: contain;
|
||||
border-radius: 14px;
|
||||
background: #fff;
|
||||
box-shadow: 0 18px 48px rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
.oi-preview-enter-active,
|
||||
.oi-preview-leave-active {
|
||||
transition: opacity 0.18s ease;
|
||||
}
|
||||
.oi-preview-enter-from,
|
||||
.oi-preview-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user