fix(user-app): repair mojibake Persian copy and drop phone-column shell
UTF-8 UI strings in product detail/cart were Latin-1-resaved; also remove the centred desktop phone frame so fixed chrome spans full width again. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -34,23 +34,8 @@
|
||||
</style>
|
||||
<title>%BRAND_TITLE%</title>
|
||||
</head>
|
||||
<body>
|
||||
<!--
|
||||
Boot splash: same phone column as #app so desktop never flashes a
|
||||
full-bleed white sheet. Removed in main.ts once Vue mounts.
|
||||
-->
|
||||
<div
|
||||
id="app-splash"
|
||||
style="
|
||||
position: fixed;
|
||||
inset-block: 0;
|
||||
inset-inline: max(0px, calc(50vw - 215px));
|
||||
width: min(100vw, 430px);
|
||||
margin-inline: auto;
|
||||
background-color: var(--c-page, #fff);
|
||||
z-index: 0;
|
||||
"
|
||||
></div>
|
||||
<body style="background-color: #fff">
|
||||
<div class="w-screen h-screen fixed" style="background-color: var(--c-page, #fff)"></div>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
|
||||
@@ -81,23 +81,6 @@ body::-webkit-scrollbar {
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
// Centred phone column on desktop (width only). Do NOT put transform /
|
||||
// filter / perspective here — that would make position:fixed bars
|
||||
// (tabbar, PageBar) stick to #app and scroll away with the page.
|
||||
max-width: var(--app-max-width);
|
||||
margin-inline: auto;
|
||||
min-height: 100dvh;
|
||||
min-height: 100svh;
|
||||
background: @page;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@media (min-width: 431px) {
|
||||
#app {
|
||||
box-shadow:
|
||||
0 0 0 1px @hairline,
|
||||
0 28px 64px -28px rgba(20, 22, 26, 0.32);
|
||||
}
|
||||
}
|
||||
|
||||
.slideDown-enter-active,
|
||||
|
||||
@@ -343,36 +343,8 @@ p {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
// Phone column width. On desktop the shell centres at this size so the app
|
||||
// does not stretch into a loose website layout. Tab bar already sat near 420.
|
||||
:root {
|
||||
--app-max-width: 430px;
|
||||
}
|
||||
|
||||
html, body {
|
||||
overscroll-behavior-y: contain;
|
||||
background: @page;
|
||||
}
|
||||
|
||||
@media (min-width: 431px) {
|
||||
html, body {
|
||||
// Side gutters behind the centred phone column (#app max-width).
|
||||
background: @canvasSoft !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Fixed overlays teleported to <body> (outside #app) — pin them to the same
|
||||
// column. Below --app-max-width, max(0, …) collapses to full bleed.
|
||||
.app-frame-x {
|
||||
left: max(0px, calc(50vw - (var(--app-max-width) / 2)));
|
||||
right: max(0px, calc(50vw - (var(--app-max-width) / 2)));
|
||||
width: auto;
|
||||
}
|
||||
|
||||
// Ant bottom drawers (order detail, status sheet) — same phone width, centred.
|
||||
.ant-drawer-bottom > .ant-drawer-content-wrapper {
|
||||
max-width: var(--app-max-width);
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.overflow-x-auto {
|
||||
|
||||
@@ -57,8 +57,8 @@ async function leave() {
|
||||
.aab {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: max(0px, calc(50vw - (var(--app-max-width) / 2)));
|
||||
right: max(0px, calc(50vw - (var(--app-max-width) / 2)));
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 2000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -483,9 +483,7 @@ onBeforeUnmount(() => {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
// Stay in the phone column on desktop (same as #app / .app-frame-x).
|
||||
left: max(0px, calc(50vw - (var(--app-max-width) / 2)));
|
||||
right: max(0px, calc(50vw - (var(--app-max-width) / 2)));
|
||||
inset: 0;
|
||||
width: auto;
|
||||
// Fixed bars inside SingleProduct (action bar, …) stay in this column.
|
||||
transform: translateZ(0);
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
<!--
|
||||
Product detail â redesigned to match the new home-screen design language
|
||||
Product detail — redesigned to match the new home-screen design language
|
||||
(components/home/*): LESS colour tokens, BEM class names, ink surfaces with a
|
||||
wine accent, hairline borders and soft long shadows.
|
||||
|
||||
WHAT IS SHOWN IS REAL. Every figure comes from data the API genuinely
|
||||
returns today:
|
||||
- title, productCode, description, category, brand
|
||||
- the selected variant's rash weight (گرÙ
) and making charge (اجرت ٪)
|
||||
- the selected variant's rash weight (گرم) and making charge (اجرت ٪)
|
||||
- stock, from ProductVariant.qty / isPreOrder
|
||||
No rial price is printed: a finished piece's price is the live gold rate Ã
|
||||
No rial price is printed: a finished piece's price is the live gold rate ×
|
||||
weight plus wage, computed at order time and not stored on the product, so
|
||||
printing one here would be invented. Karat and clasp type are likewise absent
|
||||
from the schema and therefore absent from the page.
|
||||
|
||||
FIXED â a real bug, not a restyle. The previous version ran, at module scope:
|
||||
FIXED — a real bug, not a restyle. The previous version ran, at module scope:
|
||||
history.pushState(null, location.href)
|
||||
window.onpopstate = () => history.go(1)
|
||||
Because ant-design-vue's Drawer mounts its slot content eagerly, this fired
|
||||
@@ -25,18 +25,18 @@
|
||||
drawer and the /product/:id route both already rely on.
|
||||
-->
|
||||
<template>
|
||||
<Spinner :spinning="loading" tip="Ø¯Ø±ØØ§Ù Ø¯Ø±ÛØ§Ùت Ø§Ø·ÙØ§Ø¹Ø§Øª" v-if="flag && productDetail.productRef.value">
|
||||
<Spinner :spinning="loading" tip="درحال دریافت اطلاعات" v-if="flag && productDetail.productRef.value">
|
||||
<article class="pd">
|
||||
<!-- Gallery -->
|
||||
<div class="pd__gallery">
|
||||
<button type="button" class="pd__round pd__round--back" aria-label="بازگشت" @click="close">
|
||||
<button type="button" class="pd__round pd__round--back" aria-label="بازگشت" @click="close">
|
||||
<Icon icon="vuesax-linear:arrow-right-1" size="19" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="pd__round pd__round--fav"
|
||||
:class="{ 'is-on': favorites.isFavorite(id) }"
|
||||
aria-label="Ø¹ÙØ§ÙÙâÙ
ÙØ¯Û"
|
||||
aria-label="علاقهمندی"
|
||||
@click="toggleFav"
|
||||
>
|
||||
<Icon :icon="favorites.isFavorite(id) ? 'vuesax-linear:heart-fill' : 'vuesax-linear:heart'" size="19" />
|
||||
@@ -59,7 +59,7 @@
|
||||
type="button"
|
||||
class="pd__dot"
|
||||
:class="{ 'is-on': i === index }"
|
||||
:aria-label="`تصÙÛØ± ${i + 1}`"
|
||||
:aria-label="`تصویر ${i + 1}`"
|
||||
@click.stop="go(i)"
|
||||
/>
|
||||
</div>
|
||||
@@ -77,7 +77,7 @@
|
||||
<h1 class="pd__title">{{ title }}</h1>
|
||||
<div class="pd__sub">
|
||||
<span v-if="productCode" class="pd__code">{{ productCode }}</span>
|
||||
<span v-if="categoryTitle" class="pd__dotSep">·</span>
|
||||
<span v-if="categoryTitle" class="pd__dotSep">·</span>
|
||||
<span v-if="categoryTitle">{{ categoryTitle }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -87,31 +87,31 @@
|
||||
<!-- Headline figures -->
|
||||
<div class="pd__figures">
|
||||
<div class="pd__figure">
|
||||
<span class="pd__figureLabel">ÙØ²Ù ØªÙØ±ÛبÛ</span>
|
||||
<span class="pd__figureLabel">وزن تقریبی</span>
|
||||
<span class="pd__figureValue">
|
||||
{{ min_gram }}
|
||||
<span class="pd__figureUnit">گرÙ
</span>
|
||||
<span class="pd__figureUnit">گرم</span>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="wage != null" class="pd__figure">
|
||||
<span class="pd__figureLabel">اجرت</span>
|
||||
<span class="pd__figureLabel">اجرت</span>
|
||||
<span class="pd__figureValue">
|
||||
{{ wageLabel }}
|
||||
<span class="pd__figureUnit">درصد</span>
|
||||
<span class="pd__figureUnit">درصد</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
Compact variant chips: weight/title as pills. A tiny thumb only when
|
||||
the variant has its OWN photo (not the product fallback) â otherwise
|
||||
the variant has its OWN photo (not the product fallback) — otherwise
|
||||
every chip repeated the same large image and ate the fold.
|
||||
-->
|
||||
<section v-if="variants && variants.length" class="pd__block pd__block--variants">
|
||||
<div class="pd__blockHead">
|
||||
<h2 class="pd__blockTitle">Ø§ÙØªØ®Ø§Ø¨ تÙÙØ¹</h2>
|
||||
<h2 class="pd__blockTitle">انتخاب تنوع</h2>
|
||||
<span v-if="selectedVariant" class="pd__variantsHint">
|
||||
{{ Number(selectedVariant.rashWeightGrams) }} گرÙ
|
||||
{{ Number(selectedVariant.rashWeightGrams) }} گرم
|
||||
</span>
|
||||
</div>
|
||||
<div class="pd__variants">
|
||||
@@ -137,7 +137,7 @@
|
||||
/>
|
||||
<span class="pd__variantText">
|
||||
<span class="pd__variantTitle">{{ item.title }}</span>
|
||||
<span class="pd__variantWeight">{{ Number(item.rashWeightGrams) }} گرÙ
</span>
|
||||
<span class="pd__variantWeight">{{ Number(item.rashWeightGrams) }} گرم</span>
|
||||
</span>
|
||||
<span class="pd__variantStock" :class="stockChipClass(item)">
|
||||
{{ stockChipShort(item) }}
|
||||
@@ -150,32 +150,32 @@
|
||||
class="pd__variantsMore"
|
||||
@click="showAllVariants = !showAllVariants"
|
||||
>
|
||||
{{ showAllVariants ? 'ÙÙ
Ø§ÛØ´ Ú©Ù
تر' : `+${variants.length - VARIANT_PREVIEW_COUNT} تÙÙØ¹ دÛگر` }}
|
||||
{{ showAllVariants ? 'نمایش کمتر' : `+${variants.length - VARIANT_PREVIEW_COUNT} تنوع دیگر` }}
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<!-- Specs -->
|
||||
<section class="pd__block">
|
||||
<h2 class="pd__blockTitle">Ù
شخصات</h2>
|
||||
<h2 class="pd__blockTitle">مشخصات</h2>
|
||||
<dl class="pd__specs">
|
||||
<div class="pd__spec">
|
||||
<dt><Icon icon="vuesax-linear:weight" size="15" /> ÙØ²Ù</dt>
|
||||
<dd>{{ min_gram }} گرÙ
</dd>
|
||||
<dt><Icon icon="vuesax-linear:weight" size="15" /> وزن</dt>
|
||||
<dd>{{ min_gram }} گرم</dd>
|
||||
</div>
|
||||
<div v-if="wage != null" class="pd__spec">
|
||||
<dt><Icon icon="vuesax-linear:percentage-square" size="15" /> اجرت</dt>
|
||||
<dd>{{ wageLabel }}Ùª</dd>
|
||||
<dt><Icon icon="vuesax-linear:percentage-square" size="15" /> اجرت</dt>
|
||||
<dd>{{ wageLabel }}٪</dd>
|
||||
</div>
|
||||
<div v-if="selectedVariant" class="pd__spec">
|
||||
<dt><Icon icon="vuesax-linear:diamonds" size="15" /> تÙÙØ¹</dt>
|
||||
<dt><Icon icon="vuesax-linear:diamonds" size="15" /> تنوع</dt>
|
||||
<dd>{{ selectedVariant.title }}</dd>
|
||||
</div>
|
||||
<div v-if="categoryTitle" class="pd__spec">
|
||||
<dt><Icon icon="vuesax-linear:category" size="15" /> دستÙâØ¨ÙØ¯Û</dt>
|
||||
<dt><Icon icon="vuesax-linear:category" size="15" /> دستهبندی</dt>
|
||||
<dd>{{ categoryTitle }}</dd>
|
||||
</div>
|
||||
<div v-if="brandTitle" class="pd__spec">
|
||||
<dt><Icon icon="vuesax-linear:shop" size="15" /> Ø¨Ø±ÙØ¯</dt>
|
||||
<dt><Icon icon="vuesax-linear:shop" size="15" /> برند</dt>
|
||||
<dd>{{ brandTitle }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
@@ -183,17 +183,17 @@
|
||||
|
||||
<!-- Description -->
|
||||
<section v-if="body" class="pd__block">
|
||||
<h2 class="pd__blockTitle">ØªÙØ¶ÛØØ§Øª</h2>
|
||||
<h2 class="pd__blockTitle">توضیحات</h2>
|
||||
<p class="pd__body" :class="{ 'is-clamped': !bodyOpen }">{{ body }}</p>
|
||||
<button v-if="bodyIsLong" type="button" class="pd__more" @click="bodyOpen = !bodyOpen">
|
||||
{{ bodyOpen ? 'بستÙ' : 'اداÙ
Ù Ù
Ø·ÙØ¨' }}
|
||||
{{ bodyOpen ? 'بستن' : 'ادامه مطلب' }}
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<!-- Trust -->
|
||||
<div class="pd__trust">
|
||||
<span><Icon icon="vuesax-linear:shield-tick" size="16" /> Ø®Ø±ÛØ¯ اÙ
Ù</span>
|
||||
<span><Icon icon="vuesax-linear:verify" size="16" /> Ø§ØµØ§ÙØª Ú©Ø§ÙØ§</span>
|
||||
<span><Icon icon="vuesax-linear:shield-tick" size="16" /> خرید امن</span>
|
||||
<span><Icon icon="vuesax-linear:verify" size="16" /> اصالت کالا</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -203,7 +203,7 @@
|
||||
<button
|
||||
type="button"
|
||||
class="pd__qtyBtn"
|
||||
aria-label="Ø§ÙØ²Ø§ÛØ´"
|
||||
aria-label="افزایش"
|
||||
:disabled="!canIncrement"
|
||||
@click="increment"
|
||||
>
|
||||
@@ -213,7 +213,7 @@
|
||||
<button
|
||||
type="button"
|
||||
class="pd__qtyBtn"
|
||||
:aria-label="cartQty === 1 ? 'ØØ°Ù' : 'Ú©Ø§ÙØ´'"
|
||||
:aria-label="cartQty === 1 ? 'حذف' : 'کاهش'"
|
||||
@click="decrement"
|
||||
>
|
||||
<Icon :icon="cartQty === 1 ? 'vuesax-linear:trash' : 'vuesax-linear:minus-square'" size="16" />
|
||||
@@ -227,11 +227,11 @@
|
||||
:disabled="!is_exist"
|
||||
@click="isUserLoggedIn() ? addToCart() : (loginNotify = true)"
|
||||
>
|
||||
{{ is_exist ? 'Ø§ÙØ²ÙØ¯Ù Ø¨Ù Ø³ÙØ§Ø±Ø´' : 'ÙØ§Ù
ÙØ¬Ùد' }}
|
||||
{{ is_exist ? 'افزودن به سفارش' : 'ناموجود' }}
|
||||
<Icon v-if="is_exist" icon="vuesax-linear:arrow-left-2" size="17" />
|
||||
</button>
|
||||
<router-link v-else class="pd__cta pd__cta--ghost" :to="{ name: 'cart' }">
|
||||
Ù
Ø´Ø§ÙØ¯Ù سبد Ø®Ø±ÛØ¯
|
||||
مشاهده سبد خرید
|
||||
<Icon icon="vuesax-linear:arrow-left-2" size="17" />
|
||||
</router-link>
|
||||
</div>
|
||||
@@ -243,7 +243,7 @@
|
||||
<Teleport to="body">
|
||||
<Transition name="pd-preview">
|
||||
<div v-if="previewOpen" class="pd__preview" @click.self="closePreview">
|
||||
<button type="button" class="pd__previewClose" aria-label="بستÙ" @click="closePreview">
|
||||
<button type="button" class="pd__previewClose" aria-label="بستن" @click="closePreview">
|
||||
<Icon icon="vuesax-linear:add" size="20" />
|
||||
</button>
|
||||
|
||||
@@ -264,7 +264,7 @@
|
||||
type="button"
|
||||
class="pd__previewThumb"
|
||||
:class="{ 'is-on': i === imageIndex }"
|
||||
:aria-label="`تصÙÛØ± ${i + 1}`"
|
||||
:aria-label="`تصویر ${i + 1}`"
|
||||
@click="imageIndex = i"
|
||||
>
|
||||
<img :src="s" :alt="`${title} ${i + 1}`" draggable="false" />
|
||||
@@ -280,12 +280,12 @@
|
||||
</template>
|
||||
<template v-slot:title>
|
||||
<div class="p-8 text-justify">
|
||||
Ø¨Ø±Ø§Û Ø¯Ø³ØªØ±Ø³Û Ø¨Ù ØªÙ
اÙ
Û Ø§Ù
Ú©Ø§ÙØ§Øª ÙØ±Ù
Ø§ÙØ²Ø§Ø± {{ brand.name }}Ø Ø§Ø¨ØªØ¯Ø§ گزÛÙÙ ÙØ±Ùد/ثبت ÙØ§Ù
را ÙÙ
س Ú©ÙÛØ¯ ٠در ØµÙØ±Øª ÙØ¯Ø§Ø´ØªÙ ØØ³Ø§Ø¨ Ú©Ø§Ø±Ø¨Ø±ÛØ ثبت ÙØ§Ù
ÙÙ
اÛÛØ¯.
|
||||
برای دسترسی به تمامی امکانات نرم افزار {{ brand.name }}، ابتدا گزینه ورود/ثبت نام را لمس کنید و در صورت نداشتن حساب کاربری، ثبت نام نمایید.
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:button>
|
||||
<div class="text-center font-bold" @click="router.push({ name: 'login' })">ÙØ±Ùد/ثبت ÙØ§Ù
</div>
|
||||
<div class="text-center" @click="loginNotify = false">Ø¨ÛØ®ÛاÙ</div>
|
||||
<div class="text-center font-bold" @click="router.push({ name: 'login' })">ورود/ثبت نام</div>
|
||||
<div class="text-center" @click="loginNotify = false">بیخیال</div>
|
||||
</template>
|
||||
</DialogVant>
|
||||
</Spinner>
|
||||
@@ -309,7 +309,7 @@ import { isUserLoggedIn } from '../../utils/checkAccess'
|
||||
import loginDialogImage from '../../assets/img/empty/dialogLogin.svg'
|
||||
|
||||
// `initial`: the card's own ProductSummary when opened from a list (see
|
||||
// ProductMorph.vue) â lets the page render whole on its first frame.
|
||||
// ProductMorph.vue) — lets the page render whole on its first frame.
|
||||
const props = defineProps<{ id: string; initial?: ProductSummary | null }>()
|
||||
const emit = defineEmits<{ (e: 'closeDrawer'): void }>()
|
||||
|
||||
@@ -362,7 +362,7 @@ const qty = computed(() => productAdapter.value.qty.value)
|
||||
const isPreOrder = computed(() => productAdapter.value.isPreOrder.value)
|
||||
const is_exist = computed(() => productAdapter.value.is_exist.value)
|
||||
|
||||
// ââ Variant chips âââââââââââââââââââââââââââââââââââââââââââââââ
|
||||
// ── Variant chips ───────────────────────────────────────────────
|
||||
// Compact pills; extras behind a short toggle so weight lists don't dominate.
|
||||
const VARIANT_PREVIEW_COUNT = 8
|
||||
const showAllVariants = ref(false)
|
||||
@@ -373,13 +373,13 @@ const visibleVariants = computed(() =>
|
||||
const isOrderable = (v: ProductVariantSummary) => isVariantOrderable(v)
|
||||
|
||||
const stockChipLabel = (v: ProductVariantSummary) => {
|
||||
if ((v.qty ?? 0) > 0) return 'Ù
ÙØ¬Ùد'
|
||||
return v.isPreOrder ? 'Ù¾ÛØ´âØ³ÙØ§Ø±Ø´' : 'ÙØ§Ù
ÙØ¬Ùد'
|
||||
if ((v.qty ?? 0) > 0) return 'موجود'
|
||||
return v.isPreOrder ? 'پیشسفارش' : 'ناموجود'
|
||||
}
|
||||
/** One short glyph/word so the chip stays one line. */
|
||||
const stockChipShort = (v: ProductVariantSummary) => {
|
||||
if ((v.qty ?? 0) > 0) return 'Ù
ÙØ¬Ùد'
|
||||
return v.isPreOrder ? 'Ù¾ÛØ´' : 'â'
|
||||
if ((v.qty ?? 0) > 0) return 'موجود'
|
||||
return v.isPreOrder ? 'پیش' : '—'
|
||||
}
|
||||
const stockChipClass = (v: ProductVariantSummary) => {
|
||||
if ((v.qty ?? 0) > 0) return 'is-in'
|
||||
@@ -408,8 +408,8 @@ watch(selectedVariant, (v) => {
|
||||
if (i > -1) imageIndex.value = i
|
||||
})
|
||||
|
||||
// ââ Full-screen preview âââââââââââââââââââââââââââââââââââââââââ
|
||||
// Tapping the inline gallery (a plain tap, not a swipe â GallerySlider tells
|
||||
// ── Full-screen preview ─────────────────────────────────────────
|
||||
// Tapping the inline gallery (a plain tap, not a swipe — GallerySlider tells
|
||||
// the two apart itself and only emits `tap` for the former) opens a
|
||||
// full-screen viewer teleported to <body>, with its own GallerySlider kept in
|
||||
// lockstep via the shared `imageIndex` v-model.
|
||||
@@ -442,8 +442,8 @@ const wageLabel = computed(() => {
|
||||
})
|
||||
|
||||
const stockLabel = computed(() => {
|
||||
if (qty.value > 0) return `${qty.value} عدد Ù
ÙØ¬Ùد`
|
||||
return isPreOrder.value ? 'Ù¾ÛØ´âØ³ÙØ§Ø±Ø´' : 'ÙØ§Ù
ÙØ¬Ùد'
|
||||
if (qty.value > 0) return `${qty.value} عدد موجود`
|
||||
return isPreOrder.value ? 'پیشسفارش' : 'ناموجود'
|
||||
})
|
||||
const stockClass = computed(() => {
|
||||
if (qty.value > 0) return 'is-in'
|
||||
@@ -465,7 +465,7 @@ const cartIndex = computed(() => cart.find_index(storeProduct.value))
|
||||
const cartQty = computed(() => (cartIndex.value > -1 ? cart.items[cartIndex.value].qty : 0))
|
||||
|
||||
// Stock ceiling: a pre-order variant may exceed what is on hand, anything else
|
||||
// stops at its qty. The store clamps too â this just keeps the button honest.
|
||||
// stops at its qty. The store clamps too — this just keeps the button honest.
|
||||
const cartMaxQty = computed(() => cart.maxQty(storeProduct.value))
|
||||
const canIncrement = computed(() => cartMaxQty.value === null || cartQty.value < cartMaxQty.value)
|
||||
const increment = () => {
|
||||
@@ -485,7 +485,7 @@ const addToCart = () => {
|
||||
// Previously the "added to cart" toast fired either way, telling the customer
|
||||
// something had been added when nothing had.
|
||||
if (!cart.addToCart(storeProduct.value)) return
|
||||
toast.success('Ù
ØØµÙ٠با Ù
ÙÙÙÛØª ب٠سبد Ø®Ø±ÛØ¯ Ø§ÙØ²Ùد٠شد', { title: 'ثبت Ø³ÙØ§Ø±Ø´' })
|
||||
toast.success('محصول با موفقیت به سبد خرید افزوده شد', { title: 'ثبت سفارش' })
|
||||
}
|
||||
|
||||
const toggleFav = (e: Event) => {
|
||||
@@ -497,8 +497,8 @@ onBeforeMount(async () => {
|
||||
await productDetail.load()
|
||||
})
|
||||
|
||||
// If the preview is left open when this instance goes away â the drawer is
|
||||
// closed, or the routed page is navigated away from â the body scroll lock it
|
||||
// If the preview is left open when this instance goes away — the drawer is
|
||||
// closed, or the routed page is navigated away from — the body scroll lock it
|
||||
// set would otherwise outlive it and leave the rest of the app unscrollable.
|
||||
onBeforeUnmount(() => {
|
||||
if (lockedBody) document.body.style.overflow = ''
|
||||
@@ -515,12 +515,12 @@ onBeforeUnmount(() => {
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
background: @canvas;
|
||||
/* Room for the fixed action bar â keep in sync with .pd__bar height. */
|
||||
/* Room for the fixed action bar — keep in sync with .pd__bar height. */
|
||||
padding-bottom: 104px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* ââ Gallery âââââââââââââââââââââââââââââââââââââââââââââââ */
|
||||
/* ── Gallery ─────────────────────────────────────────────── */
|
||||
.pd__gallery {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
@@ -614,7 +614,7 @@ onBeforeUnmount(() => {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/* ââ Sheet âââââââââââââââââââââââââââââââââââââââââââââââââ */
|
||||
/* ── Sheet ───────────────────────────────────────────────── */
|
||||
.pd__sheet {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
@@ -681,7 +681,7 @@ onBeforeUnmount(() => {
|
||||
background: @hairlineSoft;
|
||||
}
|
||||
|
||||
/* ââ Figures âââââââââââââââââââââââââââââââââââââââââââââââ */
|
||||
/* ── Figures ─────────────────────────────────────────────── */
|
||||
.pd__figures {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
@@ -714,7 +714,7 @@ onBeforeUnmount(() => {
|
||||
padding-inline-start: 3px;
|
||||
}
|
||||
|
||||
/* ââ Blocks ââââââââââââââââââââââââââââââââââââââââââââââââ */
|
||||
/* ── Blocks ──────────────────────────────────────────────── */
|
||||
.pd__block {
|
||||
margin-top: 22px;
|
||||
}
|
||||
@@ -741,7 +741,7 @@ onBeforeUnmount(() => {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Compact wrapping pills â weight lists stay on-screen without card tiles. */
|
||||
/* Compact wrapping pills — weight lists stay on-screen without card tiles. */
|
||||
.pd__variants {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -916,12 +916,11 @@ onBeforeUnmount(() => {
|
||||
color: @inkFaint;
|
||||
}
|
||||
|
||||
/* ââ Action bar ââââââââââââââââââââââââââââââââââââââââââââ */
|
||||
/* ── Action bar ──────────────────────────────────────────── */
|
||||
.pd__bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: max(0px, calc(50vw - (var(--app-max-width) / 2)));
|
||||
right: max(0px, calc(50vw - (var(--app-max-width) / 2)));
|
||||
inset-inline: 0;
|
||||
z-index: 20;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -991,8 +990,8 @@ onBeforeUnmount(() => {
|
||||
border: 1px solid @primary;
|
||||
}
|
||||
|
||||
/* ââ Full-screen preview âââââââââââââââââââââââââââââââââââ */
|
||||
/* Teleported to <body>, so it sits outside .pd entirely â see the template
|
||||
/* ── Full-screen preview ─────────────────────────────────── */
|
||||
/* Teleported to <body>, so it sits outside .pd entirely — see the template
|
||||
comment on why. z-index cleared well above ant-design-vue's own Drawer
|
||||
layer (@zindex-modal) so the preview is unconditionally topmost regardless
|
||||
of which Teleport happens to mount last. */
|
||||
|
||||
@@ -157,11 +157,7 @@ const ringFillStyle = computed(() => {
|
||||
|
||||
.app-ptr__overlay {
|
||||
position: fixed;
|
||||
// Stay in the phone column on desktop (same as #app / .app-frame-x).
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: max(0px, calc(50vw - (var(--app-max-width) / 2)));
|
||||
right: max(0px, calc(50vw - (var(--app-max-width) / 2)));
|
||||
inset: 0;
|
||||
z-index: 1900;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<!-- Ported from legacy henza_pwa/src/components/ui/container.vue.
|
||||
Width follows the phone column (#app / --app-max-width), not screen-md. -->
|
||||
<!-- Ported from legacy henza_pwa/src/components/ui/container.vue -->
|
||||
<template>
|
||||
<main class="app-container relative no-scrollbar">
|
||||
<main class="px-[16px] max-w-screen-md m-auto relative no-scrollbar">
|
||||
<slot />
|
||||
</main>
|
||||
</template>
|
||||
@@ -12,13 +11,3 @@ interface Props {
|
||||
}
|
||||
withDefaults(defineProps<Props>(), {})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.app-container {
|
||||
width: 100%;
|
||||
max-width: var(--app-max-width);
|
||||
margin-inline: auto;
|
||||
padding-inline: 16px;
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
so it needs no measurement of the container width.
|
||||
-->
|
||||
<template>
|
||||
<nav class="tabbar app-frame-x" v-show="nav.showNav">
|
||||
<nav class="tabbar" v-show="nav.showNav">
|
||||
<div class="tabbar__inner" :style="{ '--count': nav.visibleNavBarRoutes.length }">
|
||||
<div
|
||||
class="tabbar__indicator"
|
||||
@@ -47,8 +47,8 @@ const indicatorOffset = computed(() => -(nav.activeLink - 1) * 100)
|
||||
<style scoped lang="less">
|
||||
.tabbar {
|
||||
position: fixed;
|
||||
// left/right from global .app-frame-x — stays viewport-fixed vertically,
|
||||
// phone-column width on desktop.
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 10;
|
||||
padding: 10px 14px calc(10px + env(safe-area-inset-bottom, 0px));
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
the bar's height, blur, hairline and icon buttons stay consistent app-wide.
|
||||
-->
|
||||
<template>
|
||||
<header class="bar app-frame-x">
|
||||
<header class="bar">
|
||||
<div class="bar__inner">
|
||||
<button v-if="back" type="button" class="bar__btn" aria-label="بازگشت" @click="onBack">
|
||||
<Icon icon="vuesax-linear:arrow-right-1" size="20" />
|
||||
@@ -80,7 +80,8 @@ function onBack() {
|
||||
.bar {
|
||||
position: fixed;
|
||||
top: var(--aab-h, 0px);
|
||||
// left/right from global .app-frame-x
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 20;
|
||||
background: alpha(@canvas, 88%);
|
||||
backdrop-filter: blur(14px);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
-->
|
||||
<template>
|
||||
<main
|
||||
class="app-scroll-fetch flex flex-col flex-1 overflow-auto no-scrollbar"
|
||||
class="flex flex-col flex-1 overflow-auto no-scrollbar w-[100vw] max-w-screen-md m-auto"
|
||||
:style="{ paddingTop: padding || transition.headerHeight + 'px' }"
|
||||
ref="scrollArea"
|
||||
>
|
||||
@@ -48,11 +48,3 @@ useInfiniteScroll(
|
||||
{ distance: 50 },
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.app-scroll-fetch {
|
||||
width: 100%;
|
||||
max-width: var(--app-max-width);
|
||||
margin-inline: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -24,9 +24,8 @@ const { toasts, dismiss } = useToast()
|
||||
.toast-host {
|
||||
position: fixed;
|
||||
top: var(--aab-h, 0px);
|
||||
left: max(0px, calc(50vw - (var(--app-max-width) / 2)));
|
||||
right: max(0px, calc(50vw - (var(--app-max-width) / 2)));
|
||||
width: auto;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 3000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
</transition>
|
||||
</router-view>
|
||||
<Navigation />
|
||||
<div class="bg-primary top-[-2rem] fixed h-[2rem] z-10 app-frame-x" />
|
||||
<div class="bg-primary top-[-2rem] fixed h-[2rem] w-full z-10" />
|
||||
</section>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -63,5 +63,3 @@ app.use(VueLazyLoad, {
|
||||
});
|
||||
app.component('container', Container);
|
||||
app.mount('#app');
|
||||
// Boot splash (index.html) matched the phone column; drop it once the app paints.
|
||||
document.getElementById('app-splash')?.remove();
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
Ported from legacy henza_pwa/src/views/cart.vue, restyled into the
|
||||
redesigned design language (see components/home/* and HomeView).
|
||||
|
||||
EVERY REAL BEHAVIOUR IS CARRIED OVER UNCHANGED â only the chrome changes:
|
||||
- "ثبت ÙÙØ§ÛÛ Ø³ÙØ§Ø±Ø´" still posts to the real POST /orders endpoint. Legacy's
|
||||
EVERY REAL BEHAVIOUR IS CARRIED OVER UNCHANGED — only the chrome changes:
|
||||
- "ثبت نهایی سفارش" still posts to the real POST /orders endpoint. Legacy's
|
||||
own `/set-order` payload had two hardcoded placeholder numbers with the
|
||||
original author's comment "how to get?!"; the real Orders API computes
|
||||
gold weight server-side from productVariantId + wagePercent, so
|
||||
@@ -19,7 +19,7 @@
|
||||
computed at order time. The honest total the cart can show is total weight,
|
||||
which is what it shows.
|
||||
|
||||
DROPPED: the old unscoped `.rtl { direction: ltr }` rule, which was dead â
|
||||
DROPPED: the old unscoped `.rtl { direction: ltr }` rule, which was dead —
|
||||
the global `.rtl` in style.less carries !important, so a non-!important rule
|
||||
could never override it. It was previously kept "for fidelity" while the
|
||||
file was a faithful port; now that the file is deliberately redesigned,
|
||||
@@ -27,9 +27,9 @@
|
||||
-->
|
||||
<template>
|
||||
<div class="cart">
|
||||
<PageBar title="سبد Ø®Ø±ÛØ¯" back-to="home" />
|
||||
<PageBar title="سبد خرید" back-to="home" />
|
||||
|
||||
<Spinner :spinning="cartData.loadingPage" tip="Ø¯Ø±ØØ§Ù Ø¯Ø±ÛØ§Ùت Ø§Ø·ÙØ§Ø¹Ø§Øª">
|
||||
<Spinner :spinning="cartData.loadingPage" tip="درحال دریافت اطلاعات">
|
||||
<container class="cart__body">
|
||||
<!-- Step 1: the basket -->
|
||||
<AppPullRefresh v-if="cartData.step === 1" v-model="refreshing" @refresh="onRefresh">
|
||||
@@ -53,9 +53,9 @@
|
||||
|
||||
<section v-else class="cart__empty">
|
||||
<Icon icon="vuesax-linear:bag-2" size="26" />
|
||||
<p>سبد Ø®Ø±ÛØ¯ Ø´Ù
ا خاÙÛ Ø§Ø³Øª.</p>
|
||||
<p>سبد خرید شما خالی است.</p>
|
||||
<button type="button" class="cart__emptyCta" @click="router.push({ name: 'home' })">
|
||||
Ø¯ÛØ¯Ù Ù
ØØµÙÙØ§Øª
|
||||
دیدن محصولات
|
||||
</button>
|
||||
</section>
|
||||
</AppPullRefresh>
|
||||
@@ -65,32 +65,32 @@
|
||||
<div class="cart__doneIcon" aria-hidden="true">
|
||||
<Icon icon="vuesax-linear:tick-circle" size="30" />
|
||||
</div>
|
||||
<h2 class="cart__doneTitle">Ø³ÙØ§Ø±Ø´ Ø´Ù
ا ثبت شد</h2>
|
||||
<h2 class="cart__doneTitle">سفارش شما ثبت شد</h2>
|
||||
<p class="cart__doneSub">
|
||||
Ø´Ù
Ø§Ø±Ù Ø³ÙØ§Ø±Ø´
|
||||
شماره سفارش
|
||||
<b>{{ cartData.orderID }}</b>
|
||||
</p>
|
||||
<p class="cart__doneHint">Ø¨Ø±Ø§Û Ù¾ÛÚ¯ÛØ±Û ÙØ¶Ø¹ÛØªØ Ø¨Ù ÙÙØ±Ø³Øª Ø³ÙØ§Ø±Ø´âÙØ§ Ù
راجع٠کÙÛØ¯.</p>
|
||||
<button type="button" class="cart__doneCta" @click="goOrders">ÙÙØ±Ø³Øª Ø³ÙØ§Ø±Ø´âÙØ§</button>
|
||||
<p class="cart__doneHint">برای پیگیری وضعیت، به فهرست سفارشها مراجعه کنید.</p>
|
||||
<button type="button" class="cart__doneCta" @click="goOrders">فهرست سفارشها</button>
|
||||
</section>
|
||||
</container>
|
||||
</Spinner>
|
||||
|
||||
<!-- Sticky summary + submit -->
|
||||
<div v-if="cart.items.length > 0 && cartData.step === 1" class="cart__bar app-frame-x">
|
||||
<div v-if="cart.items.length > 0 && cartData.step === 1" class="cart__bar">
|
||||
<div class="cart__barInner">
|
||||
<div class="cart__total">
|
||||
<span class="cart__totalLabel">جÙ
ع ÙØ²Ù</span>
|
||||
<span class="cart__totalLabel">جمع وزن</span>
|
||||
<span class="cart__totalValue">
|
||||
{{ cart.total() }}
|
||||
<i>گرÙ
</i>
|
||||
<i>گرم</i>
|
||||
</span>
|
||||
</div>
|
||||
<button type="button" class="cart__submit" @click="cartData.notify = true">ثبت ÙÙØ§ÛÛ Ø³ÙØ§Ø±Ø´</button>
|
||||
<button type="button" class="cart__submit" @click="cartData.notify = true">ثبت نهایی سفارش</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Per-item note â floating island, same language as the tab bar -->
|
||||
<!-- Per-item note — floating island, same language as the tab bar -->
|
||||
<Drawer
|
||||
v-model:visible="cartData.showPopUp"
|
||||
placement="bottom"
|
||||
@@ -104,17 +104,17 @@
|
||||
>
|
||||
<div class="sheet">
|
||||
<div class="sheet__handle" aria-hidden="true" />
|
||||
<h3 class="sheet__title">ØªÙØ¶ÛØ Ø¨Ø±Ø§Û {{ cartData.tempRow.product.title }}</h3>
|
||||
<h3 class="sheet__title">توضیح برای {{ cartData.tempRow.product.title }}</h3>
|
||||
<Form :model="formModel" name="noteForm" layout="vertical" autocomplete="off" @finish="setNote">
|
||||
<FormItem has-feedback name="note" class="sheet__field">
|
||||
<Textarea
|
||||
v-model:value="formModel.note"
|
||||
:rows="3"
|
||||
placeholder="Ù
Ø«ÙØ§Ù Ø³Ø§ÛØ² ÛØ§ Ø¬Ø²Ø¦ÛØ§Øª Ø³ÙØ§Ø±Ø´"
|
||||
placeholder="مثلاً سایز یا جزئیات سفارش"
|
||||
class="sheet__input"
|
||||
/>
|
||||
</FormItem>
|
||||
<button type="submit" class="sheet__cta">ثبت ØªÙØ¶ÛØØ§Øª</button>
|
||||
<button type="submit" class="sheet__cta">ثبت توضیحات</button>
|
||||
</Form>
|
||||
</div>
|
||||
</Drawer>
|
||||
@@ -123,10 +123,10 @@
|
||||
<template v-slot:img>
|
||||
<img :src="dialogEmpty4" alt="" class="opacity-70" />
|
||||
</template>
|
||||
<template v-slot:title>Ø¢ÛØ§ Ù
Ø·Ù
ئ٠ب٠ثبت Ø³ÙØ§Ø±Ø´ ÙØ³ØªÛد Ø</template>
|
||||
<template v-slot:title>آیا مطمئن به ثبت سفارش هستید ؟</template>
|
||||
<template v-slot:button>
|
||||
<div class="text-center text-[color:var(--c-primary)]" @click="submitOrder">بÙÙ</div>
|
||||
<div class="text-center text-[color:var(--c-inkMuted)]" @click="cartData.notify = false">Ø¨Û Ø®ÛØ§Ù</div>
|
||||
<div class="text-center text-[color:var(--c-primary)]" @click="submitOrder">بله</div>
|
||||
<div class="text-center text-[color:var(--c-inkMuted)]" @click="cartData.notify = false">بی خیال</div>
|
||||
</template>
|
||||
</DialogVant>
|
||||
|
||||
@@ -135,11 +135,11 @@
|
||||
<img :src="loginDialogImage" alt="" />
|
||||
</template>
|
||||
<template v-slot:title>
|
||||
<div class="p-8 text-justify">Ø¨Ø±Ø§Û Ø«Ø¨Øª Ø³ÙØ§Ø±Ø´ ابتدا Ø¨Ø§ÛØ¯ ÙØ§Ø±Ø¯ ØØ³Ø§Ø¨ Ú©Ø§Ø±Ø¨Ø±Û Ø®ÙØ¯ Ø´ÙÛØ¯.</div>
|
||||
<div class="p-8 text-justify">برای ثبت سفارش ابتدا باید وارد حساب کاربری خود شوید.</div>
|
||||
</template>
|
||||
<template v-slot:button>
|
||||
<div class="text-center text-[color:var(--c-primary)]" @click="router.push({ name: 'login' })">ÙØ±Ùد/ثبت ÙØ§Ù
</div>
|
||||
<div class="text-center text-danger" @click="loginNotify = false">Ø¨ÛØ®ÛاÙ</div>
|
||||
<div class="text-center text-[color:var(--c-primary)]" @click="router.push({ name: 'login' })">ورود/ثبت نام</div>
|
||||
<div class="text-center text-danger" @click="loginNotify = false">بیخیال</div>
|
||||
</template>
|
||||
</DialogVant>
|
||||
</div>
|
||||
@@ -355,7 +355,8 @@ onMounted(async () => {
|
||||
.cart__bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
// left/right from global .app-frame-x
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 5;
|
||||
background: alpha(@canvas, 92%);
|
||||
backdrop-filter: blur(14px);
|
||||
@@ -463,7 +464,7 @@ onMounted(async () => {
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Drawer teleports to body â island chrome must live outside scoped CSS. -->
|
||||
<!-- Drawer teleports to body — island chrome must live outside scoped CSS. -->
|
||||
<style lang="less">
|
||||
.cart-note-island.ant-drawer-bottom .ant-drawer-content-wrapper {
|
||||
left: 14px !important;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
-->
|
||||
<template>
|
||||
<div class="home">
|
||||
<header class="home__bar app-frame-x">
|
||||
<header class="home__bar">
|
||||
<div class="home__barInner">
|
||||
<router-link :to="{ name: 'cart' }" class="home__iconBtn" aria-label="سبد خرید">
|
||||
<Icon icon="vuesax-linear:bag-2" size="21" />
|
||||
@@ -312,7 +312,8 @@ onActivated(async () => {
|
||||
.home__bar {
|
||||
position: fixed;
|
||||
top: var(--aab-h, 0px);
|
||||
// left/right from global .app-frame-x
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 20;
|
||||
background: alpha(@canvas, 88%);
|
||||
backdrop-filter: blur(14px);
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
-->
|
||||
<template>
|
||||
<div class="dash">
|
||||
<header class="dash__bar app-frame-x">
|
||||
<header class="dash__bar">
|
||||
<div class="dash__barInner">
|
||||
<span class="dash__barSpacer" aria-hidden="true" />
|
||||
<h1 class="dash__barTitle">میز کار</h1>
|
||||
@@ -400,7 +400,8 @@ onMounted(async () => {
|
||||
.dash__bar {
|
||||
position: fixed;
|
||||
top: var(--aab-h, 0px);
|
||||
// left/right from global .app-frame-x
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 20;
|
||||
background: alpha(@canvas, 88%);
|
||||
backdrop-filter: blur(14px);
|
||||
|
||||
Reference in New Issue
Block a user