refactor(ui): replace modal dialogs with toast/sheet components
Remove DialogVant/NotifyVant and add Toast and Sheet components with useToast, useReveal, and usePageTransition composables. Rework Navigation, RegisterView, and styles across both apps; rebuild dist assets. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
148b99c4a6
commit
c05352d6fd
@@ -53,7 +53,14 @@
|
||||
</template>
|
||||
</PageBar>
|
||||
|
||||
<Spinner :spinning="loadingPage" tip="درحال دریافت اطلاعات">
|
||||
<!--
|
||||
No full-screen spinner here: it covered the page with the brand logo and
|
||||
then dropped the finished results in all at once. The first load shows a
|
||||
skeleton of the result grid instead, the same size as the real cards, so
|
||||
the results replace it in place; a pull-to-refresh shows only the pull
|
||||
indicator, over the results already on screen.
|
||||
-->
|
||||
<div>
|
||||
<Filters
|
||||
v-model:filterKeys="filterKeys"
|
||||
v-show="showFilter"
|
||||
@@ -68,6 +75,22 @@
|
||||
<template #loading><Loading size="18" /></template>
|
||||
<template #success><span /></template>
|
||||
<container class="search__body">
|
||||
<div v-if="loadingPage && !allProducts.length" class="search__skeleton" aria-hidden="true">
|
||||
<div class="search__skHead">
|
||||
<span class="skeleton search__skLine search__skLine--title" />
|
||||
<span class="skeleton search__skLine search__skLine--sub" />
|
||||
</div>
|
||||
<div class="search__grid">
|
||||
<div v-for="n in SKELETON_CARDS" :key="n" class="search__skCard">
|
||||
<div class="skeleton search__skArt" />
|
||||
<div class="search__skBody">
|
||||
<span class="skeleton search__skLine search__skLine--name" />
|
||||
<span class="skeleton search__skLine search__skLine--meta" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="availableCollections.length" class="search__collections">
|
||||
<button
|
||||
v-for="item in availableCollections"
|
||||
@@ -100,20 +123,23 @@
|
||||
<Spinner :spinning="true" :indicator="true" :fixed="false" tip="" cls="my-[10px]" />
|
||||
</div>
|
||||
|
||||
<section v-else-if="!loadingPage" class="search__empty">
|
||||
<!-- Its own condition, not a v-else of the sentinel above: chained to
|
||||
it, the box also showed under the results whenever they all fit
|
||||
in the first window. Same fix as user-app-v2. -->
|
||||
<section v-if="!filteredProducts.length && !loadingPage" class="search__empty">
|
||||
<Icon icon="vuesax-linear:search-normal-1" size="26" />
|
||||
<p>محصولی با این مشخصات پیدا نشد.</p>
|
||||
</section>
|
||||
</container>
|
||||
</PullRefresh>
|
||||
</Spinner>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { onBeforeRouteLeave } from 'vue-router'
|
||||
import { PullRefresh } from 'vant'
|
||||
import { Loading, PullRefresh } from 'vant'
|
||||
import Icon from '../../components/icon/Icon.vue'
|
||||
import PageBar from '../../components/ui/PageBar.vue'
|
||||
import Spinner from '../../components/Spinner.vue'
|
||||
@@ -128,7 +154,12 @@ import { api, type ProductSummary } from '../../api/client'
|
||||
const filters = useFiltersStore()
|
||||
const nav = useNavStore()
|
||||
|
||||
const loadingPage = ref(false)
|
||||
// Starts true: the first render happens before onMounted kicks off the fetch,
|
||||
// and with false the "nothing found" box flashed up for a frame before the
|
||||
// loading state replaced it.
|
||||
const loadingPage = ref(true)
|
||||
// Two rows of the two-column grid — enough to fill the screen under the bar.
|
||||
const SKELETON_CARDS = 6
|
||||
const refreshing = ref(false)
|
||||
const allProducts = ref<ProductSummary[]>([])
|
||||
const showFilter = ref(false)
|
||||
@@ -316,8 +347,8 @@ onMounted(loadProducts)
|
||||
|
||||
.search__chip.is-on {
|
||||
color: #fff;
|
||||
background: @ink;
|
||||
border-color: @ink;
|
||||
background: @primary;
|
||||
border-color: @primary;
|
||||
}
|
||||
|
||||
.search__chip--filter.is-on {
|
||||
@@ -372,6 +403,58 @@ onMounted(loadProducts)
|
||||
gap: 11px;
|
||||
}
|
||||
|
||||
// First-load placeholder. Mirrors SectionHead + ProductCard's box model
|
||||
// (square art, 11/12/13px body padding, an 18px-radius hairline card) so
|
||||
// the real results land exactly where the placeholders were.
|
||||
.search__skHead {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 7px;
|
||||
padding-bottom: 13px;
|
||||
}
|
||||
|
||||
.search__skCard {
|
||||
background: @canvas;
|
||||
border: 1px solid @hairline;
|
||||
border-radius: 18px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.search__skArt {
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
|
||||
.search__skBody {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 11px;
|
||||
padding: 15px 12px 16px;
|
||||
}
|
||||
|
||||
.search__skLine {
|
||||
display: block;
|
||||
height: 11px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.search__skLine--title {
|
||||
width: 72px;
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
.search__skLine--sub {
|
||||
width: 52px;
|
||||
}
|
||||
|
||||
.search__skLine--name {
|
||||
width: 78%;
|
||||
height: 13px;
|
||||
}
|
||||
|
||||
.search__skLine--meta {
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
.search__more {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
Reference in New Issue
Block a user