Merge remote-tracking branch 'origin/main'

This commit is contained in:
mahmood
2026-09-18 17:16:44 +03:30
73 changed files with 1035 additions and 180 deletions
+10
View File
@@ -42,6 +42,16 @@ ax.interceptors.request.use(
} else { } else {
delete config.headers['Authorization']; delete config.headers['Authorization'];
} }
// The instance-level default Content-Type is 'application/json', and axios
// only auto-sets multipart/form-data + boundary when no Content-Type is
// already present — an existing default is NOT replaced. Posting a
// FormData body (file uploads) with the JSON default left in place sends
// the raw FormData object as a JSON-labelled body, so multer/NestJS's
// FileInterceptor sees no file at all and 400s. Let axios/the browser set
// the correct multipart boundary for FormData bodies instead.
if (config.data instanceof FormData) {
delete config.headers['Content-Type'];
}
return config; return config;
}, },
(error) => Promise.reject(error), (error) => Promise.reject(error),
@@ -10,11 +10,77 @@
Fields with no column in the redesigned schema are NOT rendered rather than Fields with no column in the redesigned schema are NOT rendered rather than
collected and silently dropped — listed in docs/ADMIN-PORT-PLAN.md: collected and silently dropped — listed in docs/ADMIN-PORT-PLAN.md:
delivery_type, message_long_delivery, offer_day, has_warranty, is_exist, delivery_type, message_long_delivery, offer_day, has_warranty, is_exist,
tags, the two image uploaders, and the variant colour/rial-wage fields. tags, and the variant colour/rial-wage fields.
The image uploaders ARE rendered (unlike the note above once claimed):
Product.images is a real ProductImage[] relation and `file/create` is a real,
already-wired endpoint (used by category/slider/avatar uploads), so leaving
product images unbuilt was a gap, not a schema/endpoint limitation.
Legacy (AddProduct.vue:171-271) had 4 distinct image slots, not one generic
list — preserved here as 4 sections, each tagged with its own
ProductImageRole so the API can tell them apart:
تصویر شاخص (indicator_id) — single, site featured image
تصویر شاخص موبایل (mobile_pic) — single, mobile featured image
گالری تصاویر سایت (images) — multiple, site gallery
گالری تصاویر موبایل (images_mobile)— multiple, mobile gallery
Legacy's 5th slot ("پادکست", an audio upload) is not an image and is out of
scope here. Legacy's per-gallery-image caption field (`imageBody`) has no
column on ProductImage and is dropped, same as the other schema-less fields
noted above.
--> -->
<div id="page-user-view"> <div id="page-user-view">
<a-form layout="vertical" @submit.prevent="handleSubmit"> <a-form layout="vertical" @submit.prevent="handleSubmit">
<a-spin :spinning="loading"> <a-spin :spinning="loading">
<a-card title="تصاویر محصول" class="mb-base">
<div class="grid-2">
<a-form-item label="تصویر شاخص">
<ImageUploadInput v-model="form.indicator_id" purpose="product.indicator" placeholder="تصویر شاخص" />
</a-form-item>
<a-form-item label="تصویر شاخص موبایل">
<ImageUploadInput v-model="form.mobile_pic" purpose="product.indicator_mobile" placeholder="تصویر شاخص موبایل" />
</a-form-item>
</div>
<a-form-item label="گالری تصاویر سایت">
<div class="image-grid">
<div v-for="(url, i) in gallerySite" :key="i" class="image-grid__item">
<ImageUploadInput
:model-value="url"
purpose="product.gallery"
placeholder="تصویر"
@update:model-value="(v) => (v ? (gallerySite[i] = v) : gallerySite.splice(i, 1))"
/>
</div>
<ImageUploadInput
:model-value="null"
purpose="product.gallery"
placeholder="افزودن تصویر"
@update:model-value="(v) => v && gallerySite.push(v)"
/>
</div>
</a-form-item>
<a-form-item label="گالری تصاویر موبایل">
<div class="image-grid">
<div v-for="(url, i) in galleryMobile" :key="i" class="image-grid__item">
<ImageUploadInput
:model-value="url"
purpose="product.gallery_mobile"
placeholder="تصویر"
@update:model-value="(v) => (v ? (galleryMobile[i] = v) : galleryMobile.splice(i, 1))"
/>
</div>
<ImageUploadInput
:model-value="null"
purpose="product.gallery_mobile"
placeholder="افزودن تصویر"
@update:model-value="(v) => v && galleryMobile.push(v)"
/>
</div>
</a-form-item>
</a-card>
<a-card title="اطلاعات پایه" class="mb-base"> <a-card title="اطلاعات پایه" class="mb-base">
<div class="grid-3"> <div class="grid-3">
<a-form-item label="کد محصول"> <a-form-item label="کد محصول">
@@ -169,6 +235,7 @@ import { message } from 'ant-design-vue';
import { DeleteOutlined, EditOutlined } from '@ant-design/icons-vue'; import { DeleteOutlined, EditOutlined } from '@ant-design/icons-vue';
import { useUsersStore } from '../../../stores/users'; import { useUsersStore } from '../../../stores/users';
import api from '../../../api/client'; import api from '../../../api/client';
import ImageUploadInput from '../../../components/upload/ImageUploadInput.vue';
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
@@ -191,10 +258,17 @@ const form = reactive<Record<string, any>>({
ciny_ids: [] as string[], ciny_ids: [] as string[],
body_html: '', body_html: '',
is_active: true, is_active: true,
// ── the 2 single-image slots — see the template's image-section comment
indicator_id: null as string | null,
mobile_pic: null as string | null,
}); });
const err = reactive<Record<string, string>>({}); const err = reactive<Record<string, string>>({});
// ── the 2 gallery slots (lists of URLs), edited in memory, saved with the product
const gallerySite = ref<string[]>([]);
const galleryMobile = ref<string[]>([]);
// ── variants (edited in memory, saved with the product) // ── variants (edited in memory, saved with the product)
interface VariantRow { interface VariantRow {
__key: string; __key: string;
@@ -277,6 +351,8 @@ async function handleSubmit() {
const payload = { const payload = {
...form, ...form,
cats: form.category_id ? [form.category_id] : [], cats: form.category_id ? [form.category_id] : [],
images: gallerySite.value,
images_mobile: galleryMobile.value,
variants: variants.value.map((v) => ({ variants: variants.value.map((v) => ({
id: v.id, id: v.id,
title: v.title, title: v.title,
@@ -333,7 +409,11 @@ onMounted(async () => {
ciny_ids: data.ciny_ids ?? [], ciny_ids: data.ciny_ids ?? [],
body_html: data.body_html ?? '', body_html: data.body_html ?? '',
is_active: data.is_active ?? true, is_active: data.is_active ?? true,
indicator_id: data.indicator_id ?? null,
mobile_pic: data.mobile_pic ?? null,
}); });
gallerySite.value = data.images_gallery ?? [];
galleryMobile.value = data.images_mobile ?? [];
variants.value = (data.variants_all ?? []).map((v: any, i: number) => ({ variants.value = (data.variants_all ?? []).map((v: any, i: number) => ({
__key: v.id ?? `v-${i}`, __key: v.id ?? `v-${i}`,
id: v.id, id: v.id,
@@ -357,6 +437,11 @@ onMounted(async () => {
display: grid; display: grid;
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.image-grid {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.flex-1 { .flex-1 {
flex: 1; flex: 1;
} }
+134
View File
@@ -0,0 +1,134 @@
// One-off data fix: the 5 hand-authored demo products (product-classic-ring,
// product-woven-bracelet, product-pendant-necklace, product-hoop-earrings,
// product-full-coin) each had exactly one ProductImage row pointing at
// /img/no-items.png — a missing-image placeholder, not a real photo. This
// replaces those with a real, varied set drawn from the 15 jewelry photos the
// user pointed at (originally in "Downloads/New folder", already copied into
// storage/uploads/ under their original filenames — confirmed present before
// this script was written).
//
// Distribution, mirroring the one product in this DB that already has a full,
// correctly-roled image set (`شسی`, id cmu4ia4iq0008s4zhwrz6wy3z):
// - exactly one FEATURED (تصویر شاخص وب)
// - exactly one FEATURED_MOBILE (تصویر شاخص موبایل)
// - 2–3 GALLERY / GALLERY_MOBILE, split across both roles
//
// CHECKED, not assumed: CatalogService.listProducts() (apps/api/src/modules/
// catalog/catalog.service.ts) returns every ProductImage row unfiltered —
// there is no role-based split on the read side today. So on the user-app
// side ALL of these rows end up in ProductSummary.images and all of them are
// swipeable in the gallery/preview — the FEATURED/FEATURED_MOBILE role still
// matters (it is what the admin panel and any future "hero image" logic reads
// by role), but right now it does not narrow what the shopper-facing gallery
// shows. `sortOrder` is set to the creation index below, which is what
// actually determines swipe order today, since nothing filters by role.
//
// Assignment across the 5 products is a random, non-repeating draw per
// product from the 15-photo pool (Fisher-Yates, reseedable via IMG_SEED for a
// reproducible run) — the same photo can end up on more than one product
// across different products (15 photos split ~4 ways per product exceeds the
// pool), which is fine: this is demo data standing in for real per-product
// photography, not a claim that these are 5 distinct real items.
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
// The 15 files confirmed present in storage/uploads/ under their original
// names before this script ran.
const POOL = [
'152_5e1435ca-05a5-4146-a4ea-89726650c29c.webp',
'59_0661f668-5d1d-4c05-87fd-50fd65cd8383.webp',
'71085964.png',
'79A202-1201_01.webp',
'Brooches-Vintage-Jewelry-full-1-322_60.60-9ed8e81b.webp',
'MB116662_Front_1080p_f364da1d-6019-4ca4-8dd8-a323387634c7.webp',
'TEMP_360x360.webp',
'channel_setting_480x480.webp',
'engagement-ring-in-18k-gold-with-diamonds-nr532-white-18k-semi-white-205224.webp',
'finished-jewelry.webp',
'kisspng-earring-gold-jewellery-ring-new-22ct-yellow-gold-ring-armari-45-sku27555-5d0796cb466539.4977043815607784432884-768x768.png',
'pl-background-free-product-photography-jewelery-3.webp',
'round-cut-engagement-ring-in-18k-gold-with-diamonds-lr3011-2t-18k-x-2t-964060.webp',
'td4-aam-n0_dd28f69d-9e3c-490d-be37-1ba074737992.webp',
'w=640,h=640,fit=crop.avif',
];
const PRODUCT_IDS = [
'product-classic-ring',
'product-woven-bracelet',
'product-pendant-necklace',
'product-hoop-earrings',
'product-full-coin',
];
// Simple seeded PRNG (mulberry32) so a re-run with the same IMG_SEED is
// reproducible — useful for re-checking a specific assignment without the
// draw changing under you.
function mulberry32(seed) {
return function () {
seed |= 0;
seed = (seed + 0x6d2b79f5) | 0;
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
const seed = Number(process.env.IMG_SEED) || Date.now();
const rand = mulberry32(seed);
function shuffled(arr) {
const a = arr.slice();
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(rand() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
async function main() {
console.log(`seed=${seed}`);
for (const productId of PRODUCT_IDS) {
const product = await prisma.product.findUnique({ where: { id: productId } });
if (!product) {
console.log(`skip ${productId}: not found`);
continue;
}
// 3–5 photos per product, drawn without replacement from the pool.
const count = 3 + Math.floor(rand() * 3); // 3, 4, or 5
const picks = shuffled(POOL).slice(0, count);
// Role plan: first pick is FEATURED, second is FEATURED_MOBILE, the rest
// alternate GALLERY / GALLERY_MOBILE — same shape as the شسی product's
// existing 8-image set, just proportioned to a smaller pick count.
const roles = ['FEATURED', 'FEATURED_MOBILE'];
for (let i = 2; i < picks.length; i++) {
roles.push(i % 2 === 0 ? 'GALLERY' : 'GALLERY_MOBILE');
}
// Replace whatever this product currently has (the single no-items.png
// placeholder row) rather than appending alongside it.
await prisma.productImage.deleteMany({ where: { productId } });
for (let i = 0; i < picks.length; i++) {
await prisma.productImage.create({
data: {
id: `${productId}-img-${i + 1}`,
productId,
url: `/uploads/${picks[i]}`,
sortOrder: i,
role: roles[i],
},
});
}
console.log(`${productId}: ${picks.length} images -> ${picks.map((p, i) => `${roles[i]}:${p}`).join(', ')}`);
}
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(() => prisma.$disconnect());
@@ -0,0 +1,8 @@
-- Placeholder: the original migration.sql for this migration was lost from
-- disk (directory existed but was empty) while the database's
-- _prisma_migrations ledger already recorded it as applied and finished.
-- Recreated as an empty no-op file, then registered via
-- `prisma migrate resolve --applied`, so Prisma tooling stops blocking on
-- P3015 without re-running or guessing at SQL that already ran against this
-- database. The actual schema effect (CustomerKind.RETAIL) is present in
-- schema.prisma and already exists in the live database.
@@ -0,0 +1,12 @@
-- Placeholder: this migration is recorded as applied in the database's
-- _prisma_migrations ledger, but no migration.sql for it ever existed on
-- disk in this checkout (added Customer.firstName/lastName/productPercent
-- directly against the live DB, schema.prisma was never regenerated from it
-- as a tracked migration). Recreated as an empty no-op file purely so Prisma
-- CLI tooling (migrate status/dev) stops blocking on a missing-migration
-- error. Does not re-run or alter anything — the columns already exist live.
--
-- NOT reconciled with schema.prisma: Customer.firstName, Customer.lastName,
-- Customer.productPercent exist in the database but have no field in
-- prisma/schema.prisma's Customer model. This is a pre-existing drift,
-- separate from this change, and should be looked at on its own.
@@ -0,0 +1,12 @@
-- Placeholder: this migration is recorded as applied in the database's
-- _prisma_migrations ledger, but no migration.sql for it ever existed on
-- disk in this checkout (added Customer.parentAgentId + Customer.birthDate
-- directly against the live DB; schema.prisma was never regenerated from it
-- as a tracked migration). Recreated as an empty no-op file purely so Prisma
-- CLI tooling (migrate status/dev) stops blocking on a missing-migration
-- error. Does not re-run or alter anything — the columns already exist live.
--
-- NOT reconciled with schema.prisma: Customer.parentAgentId,
-- Customer.birthDate exist in the database but have no field in
-- prisma/schema.prisma's Customer model. This is a pre-existing drift,
-- separate from this change, and should be looked at on its own.
@@ -0,0 +1,5 @@
-- CreateEnum
CREATE TYPE "ProductImageRole" AS ENUM ('FEATURED', 'FEATURED_MOBILE', 'GALLERY', 'GALLERY_MOBILE');
-- AlterTable
ALTER TABLE "ProductImage" ADD COLUMN "role" "ProductImageRole" NOT NULL DEFAULT 'GALLERY';
+14 -3
View File
@@ -320,11 +320,22 @@ model Product {
} }
model ProductImage { model ProductImage {
id String @id @default(cuid()) id String @id @default(cuid())
productId String productId String
product Product @relation(fields: [productId], references: [id]) product Product @relation(fields: [productId], references: [id])
url String url String
sortOrder Int @default(0) sortOrder Int @default(0)
/// Legacy had 4 distinct upload slots (تصویر شاخص / تصویر شاخص موبایل /
/// گالری تصاویر سایت / گالری تصاویر موبایل) — GALLERY is the default so
/// existing rows and any generic "just attach an image" caller keep working.
role ProductImageRole @default(GALLERY)
}
enum ProductImageRole {
FEATURED
FEATURED_MOBILE
GALLERY
GALLERY_MOBILE
} }
model ProductVariant { model ProductVariant {
@@ -115,10 +115,16 @@ export class AdminProductsService {
: null, : null,
// legacy grid slot reads `ProductImage` // legacy grid slot reads `ProductImage`
ProductImage: p.images.map((i) => ({ id: i.id, image: i.url, full: i.url, url: i.url })), ProductImage: p.images.map((i) => ({ id: i.id, image: i.url, full: i.url, url: i.url })),
images: p.images.map((i) => ({ id: i.id, url: i.url, sortOrder: i.sortOrder })), images: p.images.map((i) => ({ id: i.id, url: i.url, sortOrder: i.sortOrder, role: i.role })),
// The order builder's grid reads `thumb.full` and `variants_all` directly // The order builder's grid reads `thumb.full` and `variants_all` directly
// off each row, so both travel with the list as they did in legacy. // off each row, so both travel with the list as they did in legacy. Legacy's
thumb: p.images[0] ? { full: p.images[0].url } : null, // grid thumbnail was always the featured image (تصویر شاخص) when one was
// set — fall back to the first gallery image only if no featured image
// was uploaded, rather than picking whichever image happens to sort first.
thumb: (() => {
const featured = p.images.find((i) => i.role === 'FEATURED') ?? p.images[0];
return featured ? { full: featured.url } : null;
})(),
variants_all: p.variants.map((v) => this.variantRow(v)), variants_all: p.variants.map((v) => this.variantRow(v)),
variants_count: p.variants.length, variants_count: p.variants.length,
// legacy min_gram / max_gram: the product's lightest and heaviest variant, // legacy min_gram / max_gram: the product's lightest and heaviest variant,
@@ -160,6 +166,13 @@ export class AdminProductsService {
cats: p.categoryId ? [p.categoryId] : [], cats: p.categoryId ? [p.categoryId] : [],
ciny_ids: p.collections.map((c) => c.collectionId), ciny_ids: p.collections.map((c) => c.collectionId),
variants_all: p.variants.map((v) => this.variantRow(v)), variants_all: p.variants.map((v) => this.variantRow(v)),
// The 4 image slots the product form edits, split back out by role —
// mirrors the shape AddProduct.vue posts (indicator_id / mobile_pic /
// images / images_mobile).
indicator_id: p.images.find((i) => i.role === 'FEATURED')?.url ?? null,
mobile_pic: p.images.find((i) => i.role === 'FEATURED_MOBILE')?.url ?? null,
images_gallery: p.images.filter((i) => i.role === 'GALLERY').map((i) => i.url),
images_mobile: p.images.filter((i) => i.role === 'GALLERY_MOBILE').map((i) => i.url),
}; };
} }
@@ -285,6 +298,36 @@ export class AdminProductsService {
}; };
} }
/**
* Legacy had 4 distinct upload slots (تصویر شاخص / تصویر شاخص موبایل /
* گالری تصاویر سایت / گالری تصاویر موبایل — AddProduct.vue's `indicator_id`,
* `mobile_pic`, `images`, and a mobile gallery). The form posts one URL each
* for the two featured slots and one URL array each for the two galleries;
* this flattens all four into the role-tagged rows ProductImage.create wants.
*/
private imageCreateData(data: Record<string, unknown>): { url: string; sortOrder: number; role: Prisma.ProductImageCreateInput['role'] }[] {
const rows: { url: string; sortOrder: number; role: Prisma.ProductImageCreateInput['role'] }[] = [];
const single = (v: unknown, role: Prisma.ProductImageCreateInput['role']) => {
const url = typeof v === 'string' ? v : String((v as Record<string, unknown>)?.url ?? '');
if (url) rows.push({ url, sortOrder: 0, role });
};
const gallery = (v: unknown, role: Prisma.ProductImageCreateInput['role']) => {
if (!Array.isArray(v)) return;
(v as unknown[]).forEach((item, i) => {
const url = typeof item === 'string' ? item : String((item as Record<string, unknown>)?.url ?? '');
if (url) rows.push({ url, sortOrder: i, role });
});
};
single(data.indicator_id, 'FEATURED');
single(data.mobile_pic, 'FEATURED_MOBILE');
gallery(data.images, 'GALLERY');
gallery(data.images_mobile, 'GALLERY_MOBILE');
return rows;
}
/** Legacy `POST product/create`. */ /** Legacy `POST product/create`. */
async create(data: Record<string, unknown>) { async create(data: Record<string, unknown>) {
const title = String(data.title ?? '').trim(); const title = String(data.title ?? '').trim();
@@ -295,6 +338,7 @@ export class AdminProductsService {
const variants = Array.isArray(data.variants) const variants = Array.isArray(data.variants)
? (data.variants as Record<string, unknown>[]) ? (data.variants as Record<string, unknown>[])
: []; : [];
const images = this.imageCreateData(data);
return this.prisma.product.create({ return this.prisma.product.create({
data: { data: {
@@ -311,8 +355,9 @@ export class AdminProductsService {
variants: variants.length variants: variants.length
? { create: variants.map((v) => this.variantCreateData(v)) } ? { create: variants.map((v) => this.variantCreateData(v)) }
: undefined, : undefined,
images: images.length ? { create: images } : undefined,
}, },
include: { variants: true }, include: { variants: true, images: true },
}); });
} }
@@ -371,6 +416,21 @@ export class AdminProductsService {
}); });
} }
// Images carry no downstream reference (unlike variants/OrderItem), so all
// 4 slots are applied as a straight replace: clear and recreate from
// whatever the form posted. The product form always posts all 4 image
// fields together (even as empty/absent), so any one of them present means
// this is a real submit, not a partial PATCH missing the others.
if (['indicator_id', 'mobile_pic', 'images', 'images_mobile'].some((k) => k in data)) {
const images = this.imageCreateData(data);
await this.prisma.productImage.deleteMany({ where: { productId: id } });
if (images.length) {
await this.prisma.productImage.createMany({
data: images.map((img) => ({ ...img, productId: id })),
});
}
}
return this.findOne(id); return this.findOne(id);
} }
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{d as f,o as v,c as s,l as e,q as u,g as m,h as p,f as o,F as g,r as y,I as P,b as k,e as C,s as b,H as h,j as x,_ as B}from"./index-CmYEpHK9.js";import{P as V}from"./PageBar-D80e377v.js";import{a as w}from"./Spinner.vue_vue_type_style_index_0_lang-CvgYqzhC.js";import{S as I}from"./SectionHead-L1TLZGJP.js";import{P as N}from"./ProductCard-BeFuxqqt.js";import{a as q}from"./client-WIBW0tF5.js";import"./_commonjsHelpers-Cpj98o6Y.js";import"./SingleProduct-CDKWZbYq.js";import"./NotifyVant.vue_vue_type_script_setup_true_lang-jgMMdODO.js";import"./DialogVant.vue_vue_type_style_index_0_lang-D7yryjTd.js";import"./cartStore-DSCChC_v.js";import"./dialogEmpty1-Caw7F5KL.js";import"./index-DSgZu76n.js";import"./classCallCheck-Ddn4Ym7v.js";const F={class:"coll"},H={key:0,class:"coll__grid"},S={key:1,class:"coll__empty"},j=f({__name:"CollectionView",setup(z){const l=h(),t=m(!1),n=m([]),d=p(()=>l.query.page||"مجموعه"),r=p(()=>n.value.filter(c=>c.collections.some(a=>a.collectionId===l.params.id)));return v(async()=>{t.value=!0;try{n.value=await q.listProducts()}finally{t.value=!1}}),(c,a)=>{const _=b("container");return o(),s("div",F,[e(V,{title:d.value,"back-to":"search"},null,8,["title"]),e(w,{spinning:t.value,tip:"درحال دریافت اطلاعات"},{default:u(()=>[e(_,{class:"coll__body"},{default:u(()=>[e(I,{title:"محصولات این مجموعه",subtitle:"برای دیدن جزئیات، روی محصول ضربه بزنید"}),r.value.length?(o(),s("div",H,[(o(!0),s(g,null,y(r.value,i=>(o(),x(N,{key:i.id,value:i},null,8,["value"]))),128))])):t.value?C("",!0):(o(),s("section",S,[e(P,{icon:"vuesax-linear:box",size:"26"}),a[0]||(a[0]=k("p",null,"در این مجموعه محصولی ثبت نشده است.",-1))]))]),_:1})]),_:1},8,["spinning"])])}}}),W=B(j,[["__scopeId","data-v-6fdc1b6f"]]);export{W as default}; import{d as f,o as v,c as s,l as e,q as u,g as m,h as p,f as o,F as g,r as y,I as P,b as k,e as C,s as b,H as h,j as x,_ as B}from"./index-gciDsUQi.js";import{P as V}from"./PageBar-uS5upavZ.js";import{a as w}from"./Spinner.vue_vue_type_style_index_0_lang-CkjhJnbV.js";import{S as I}from"./SectionHead-BKOd3BbV.js";import{P as N}from"./ProductCard-CoWnvkjC.js";import{a as q}from"./client-WIBW0tF5.js";import"./_commonjsHelpers-Cpj98o6Y.js";import"./SingleProduct-Bz_F3HVn.js";import"./NotifyVant.vue_vue_type_script_setup_true_lang-T1ksLsUj.js";import"./DialogVant.vue_vue_type_style_index_0_lang-BM_3o3Fn.js";import"./cartStore-BqvRThaH.js";import"./dialogEmpty1-Caw7F5KL.js";import"./index-B7DBPshJ.js";import"./classCallCheck-C6rD-0r0.js";const F={class:"coll"},H={key:0,class:"coll__grid"},S={key:1,class:"coll__empty"},j=f({__name:"CollectionView",setup(z){const l=h(),t=m(!1),n=m([]),d=p(()=>l.query.page||"مجموعه"),r=p(()=>n.value.filter(c=>c.collections.some(a=>a.collectionId===l.params.id)));return v(async()=>{t.value=!0;try{n.value=await q.listProducts()}finally{t.value=!1}}),(c,a)=>{const _=b("container");return o(),s("div",F,[e(V,{title:d.value,"back-to":"search"},null,8,["title"]),e(w,{spinning:t.value,tip:"درحال دریافت اطلاعات"},{default:u(()=>[e(_,{class:"coll__body"},{default:u(()=>[e(I,{title:"محصولات این مجموعه",subtitle:"برای دیدن جزئیات، روی محصول ضربه بزنید"}),r.value.length?(o(),s("div",H,[(o(!0),s(g,null,y(r.value,i=>(o(),x(N,{key:i.id,value:i},null,8,["value"]))),128))])):t.value?C("",!0):(o(),s("section",S,[e(P,{icon:"vuesax-linear:box",size:"26"}),a[0]||(a[0]=k("p",null,"در این مجموعه محصولی ثبت نشده است.",-1))]))]),_:1})]),_:1},8,["spinning"])])}}}),W=B(j,[["__scopeId","data-v-6fdc1b6f"]]);export{W as default};
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
.dash[data-v-b8482634]{background:#fff;min-height:100vh}.dash__bar[data-v-b8482634]{position:fixed;top:0;left:0;right:0;z-index:20;background:#ffffffd1;-webkit-backdrop-filter:blur(18px) saturate(140%);backdrop-filter:blur(18px) saturate(140%);border-bottom:1px solid rgba(28,45,86,.05);box-shadow:0 8px 20px -18px #1c2d568c}.dash__barInner[data-v-b8482634]{max-width:768px;margin:0 auto;padding:10px 16px;display:flex;align-items:center;justify-content:space-between;gap:12px}.dash__brand[data-v-b8482634]{font-size:14.5px;font-weight:800;color:#14161a}.dash__exit[data-v-b8482634]{flex:0 0 auto;width:34px;height:34px;border:0;cursor:pointer;border-radius:11px;display:grid;place-items:center;color:#7a1b33;background:#fbf2f4;transition:transform .18s ease}.dash__exit[data-v-b8482634]:active{transform:scale(.92)}.dash__body[data-v-b8482634]{padding-top:58px;padding-bottom:90px}.dash__reveal[data-v-b8482634]{opacity:0;transform:translate3d(0,14px,0) scale(.98);will-change:transform,opacity}.is-revealed .dash__reveal[data-v-b8482634]{opacity:1;transform:translateZ(0) scale(1);transition:opacity .62s cubic-bezier(.32,.72,0,1) var(--reveal-delay, 0ms),transform .62s cubic-bezier(.32,.72,0,1) var(--reveal-delay, 0ms)}@media (prefers-reduced-motion: reduce){.dash__reveal[data-v-b8482634]{opacity:1;transform:none}.is-revealed .dash__reveal[data-v-b8482634]{transition:none}}.dash__hero[data-v-b8482634]{margin-top:16px;margin-bottom:28px;padding:20px 18px 16px;border-radius:26px;background:linear-gradient(155deg,#14161a,#14161a 55%,#5e1427 130%);color:#fff;box-shadow:0 22px 40px -26px #14161a52}.dash__heroTop[data-v-b8482634]{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.dash__heroIdentity[data-v-b8482634]{display:flex;flex-direction:column;gap:3px;min-width:0}.dash__heroName[data-v-b8482634]{font-size:13.5px;font-weight:800}.dash__heroMobile[data-v-b8482634]{font-size:11px;color:#ffffff8c;direction:ltr;text-align:right}.dash__heroSeal[data-v-b8482634]{flex:0 0 auto;padding:5px 12px;border-radius:999px;font-size:10.5px;font-weight:700}.dash__heroSeal.is-debtor[data-v-b8482634]{color:#fff;background:#a33c55}.dash__heroSeal.is-creditor[data-v-b8482634]{color:#14161a;background:#ffffffe6}.dash__heroFigure[data-v-b8482634]{margin-top:20px;display:flex;align-items:baseline;gap:8px}.dash__heroNum[data-v-b8482634]{font-size:34px;font-weight:800;letter-spacing:-.5px}.dash__heroUnit[data-v-b8482634]{font-size:12px;font-weight:600;color:#ffffff9e}.dash__heroDebt[data-v-b8482634]{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:14px;padding-top:13px;border-top:1px solid rgba(255,255,255,.14);font-size:11.5px;color:#ffffff9e}.dash__heroDebt b[data-v-b8482634]{font-size:13px;font-weight:700;color:#fff}.dash__heroDebt i[data-v-b8482634]{font-style:normal;font-size:10.5px;font-weight:600;color:#ffffff9e;padding-inline-start:3px}.dash__invite[data-v-b8482634]{margin-top:16px;padding:26px 20px;border-radius:26px;background:#14161a;color:#fff;display:flex;flex-direction:column;align-items:center;text-align:center;gap:10px}.dash__inviteIcon[data-v-b8482634]{width:46px;height:46px;border-radius:50%;display:grid;place-items:center;background:#ffffff1a;margin-bottom:2px}.dash__invite h3[data-v-b8482634]{margin:0;font-size:14px;font-weight:800}.dash__invite p[data-v-b8482634]{margin:0;font-size:11.5px;color:#fff9;max-width:240px}.dash__inviteCta[data-v-b8482634]{margin-top:6px;padding:10px 22px;border:0;cursor:pointer;border-radius:999px;font-family:inherit;font-size:12px;font-weight:700;color:#14161a;background:#fff;transition:transform .18s ease}.dash__inviteCta[data-v-b8482634]:active{transform:scale(.96)}.dash__rows[data-v-b8482634]{margin-top:0;display:flex;flex-direction:column;gap:9px}.dash__row[data-v-b8482634]{display:flex;align-items:center;gap:12px;padding:13px 14px;border:1px solid #ECEDEF;border-radius:17px;cursor:pointer;background:#fff;font-family:inherit;text-align:right;transition:transform .18s ease,border-color .18s ease}.dash__row[data-v-b8482634]:active{transform:scale(.985);border-color:#7a1b3333}.dash__rowIcon[data-v-b8482634]{flex:0 0 auto;width:38px;height:38px;border-radius:12px;display:grid;place-items:center;color:#7a1b33;background:#fbf2f4}.dash__rowText[data-v-b8482634]{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.dash__rowText b[data-v-b8482634]{font-size:12.5px;font-weight:700;color:#14161a}.dash__rowText small[data-v-b8482634]{font-size:10.5px;color:#8a909c}.dash__rate[data-v-b8482634]{margin-top:14px}
@@ -1 +0,0 @@
.dash[data-v-a9e43f33]{background:#fff;min-height:100vh}.dash__bar[data-v-a9e43f33]{position:fixed;top:0;left:0;right:0;z-index:20;background:#ffffffe0;-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);border-bottom:1px solid #ECEDEF}.dash__barInner[data-v-a9e43f33]{max-width:768px;margin:0 auto;padding:11px 16px;display:flex;align-items:center;justify-content:space-between;gap:12px}.dash__barTitle[data-v-a9e43f33]{margin:0;font-size:14.5px;font-weight:800;color:#14161a}.dash__barBtn[data-v-a9e43f33]{flex:0 0 auto;width:38px;height:38px;border:0;cursor:pointer;border-radius:12px;display:grid;place-items:center;color:#7a1b33;background:#fbf2f4}.dash__barBtn[data-v-a9e43f33]:active{transform:scale(.94)}.dash__barSpacer[data-v-a9e43f33]{display:block;width:38px;height:38px}.dash__body[data-v-a9e43f33]{padding-top:76px;padding-bottom:90px}.dash__balance[data-v-a9e43f33]{padding:16px;border-radius:20px;background:#14161a;color:#fff;box-shadow:0 14px 30px -22px #14161af2}.dash__balanceHead[data-v-a9e43f33]{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.dash__eyebrow[data-v-a9e43f33]{font-size:11.5px;color:#ffffff9e}.dash__figure[data-v-a9e43f33]{padding-top:5px;font-size:25px;font-weight:800;letter-spacing:-.3px}.dash__unit[data-v-a9e43f33]{font-size:11.5px;font-weight:600;color:#ffffff9e;padding-inline-start:4px}.dash__seal[data-v-a9e43f33]{flex:0 0 auto;padding:5px 12px;border-radius:999px;font-size:10.5px;font-weight:700}.dash__seal.is-debtor[data-v-a9e43f33]{color:#fff;background:#a33c55}.dash__seal.is-creditor[data-v-a9e43f33]{color:#14161a;background:#ffffffe6}.dash__balanceRow[data-v-a9e43f33]{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:16px;padding-top:13px;border-top:1px solid rgba(255,255,255,.13)}.dash__rowLabel[data-v-a9e43f33]{font-size:11.5px;color:#ffffff9e}.dash__rowValue[data-v-a9e43f33]{font-size:13.5px;font-weight:700}.dash__rowValue i[data-v-a9e43f33]{font-style:normal;font-size:10.5px;font-weight:600;color:#ffffff9e;padding-inline-start:3px}.dash__identity[data-v-a9e43f33]{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:13px;padding-top:13px;border-top:1px solid rgba(255,255,255,.13)}.dash__name[data-v-a9e43f33]{font-size:12.5px;font-weight:700}.dash__mobile[data-v-a9e43f33]{font-size:11.5px;color:#ffffff9e;direction:ltr}.dash__invite[data-v-a9e43f33]{display:flex;align-items:center;gap:12px;padding:16px;border:1px solid #ECEDEF;border-radius:20px;background:#fafafa}.dash__inviteIcon[data-v-a9e43f33]{flex:0 0 42px;width:42px;height:42px;border-radius:50%;display:grid;place-items:center;color:#7a1b33;background:#fbf2f4}.dash__inviteCopy[data-v-a9e43f33]{flex:1;min-width:0}.dash__inviteCopy h3[data-v-a9e43f33]{margin:0;font-size:13px;font-weight:800;color:#14161a}.dash__inviteCopy p[data-v-a9e43f33]{margin:4px 0 0;font-size:11.5px;color:#8a909c}.dash__inviteCta[data-v-a9e43f33]{flex:0 0 auto;padding:9px 14px;border:0;cursor:pointer;border-radius:999px;font-family:inherit;font-size:11.5px;font-weight:700;color:#fff;background:#14161a}.dash__section[data-v-a9e43f33]{margin-top:26px}.dash__grid[data-v-a9e43f33]{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:9px}.dash__tile[data-v-a9e43f33]{display:flex;flex-direction:column;align-items:center;gap:8px;padding:14px 6px;border:1px solid #ECEDEF;border-radius:16px;cursor:pointer;background:#fff;font-family:inherit;transition:transform .18s ease}.dash__tile[data-v-a9e43f33]:active{transform:scale(.97)}.dash__tile.is-disabled[data-v-a9e43f33]{opacity:.4}.dash__tileIcon[data-v-a9e43f33]{width:38px;height:38px;border-radius:12px;display:grid;place-items:center;color:#22262e;background:#f4f5f6}.dash__tileTitle[data-v-a9e43f33]{font-size:11px;font-weight:700;color:#22262e}
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
.van-dialog__confirm,.van-dialog__cancel{display:none!important}.mDialog.van-dialog{width:min(320px,calc(100vw - 48px));border-radius:26px;background:#fff;box-shadow:0 30px 60px -24px #14161a73;overflow:hidden}.mDialog__body{padding:26px 22px 20px;display:flex;flex-direction:column;align-items:center;text-align:center}.mDialog__art{width:84px;height:84px;border-radius:50%;display:flex;align-items:center;justify-content:center;overflow:hidden;background:#fbf2f4;margin-bottom:18px}.mDialog__art img,.mDialog__art svg,.mDialog__art canvas{width:56px;height:56px;-o-object-fit:contain;object-fit:contain;opacity:1!important}.mDialog__title{font-size:14.5px;font-weight:700;line-height:1.9;color:#14161a}.mDialog__actions{width:100%;display:flex;flex-direction:column;gap:9px;margin-top:22px;font-size:13px;font-weight:700}.mDialog__actions>*{padding:12px;border-radius:14px;cursor:pointer;transition:transform .18s ease,opacity .18s ease}.mDialog__actions>*:active{transform:scale(.97)}.mDialog__actions>*:first-child{color:#fff!important;background:#14161a}.mDialog__actions>*:last-child{color:#8a909c!important;background:none}
-1
View File
@@ -1 +0,0 @@
.van-dialog__confirm,.van-dialog__cancel{display:none!important}.van-dialog__confirm .van-button__text{color:#1c2d56;font-weight:600;font-size:15px}.van-dialog__cancel .van-button__text{color:#6b7280;font-weight:600;font-size:15px}
@@ -0,0 +1 @@
import{d as l,j as n,q as c,u as _,am as d,f as r,b as t,z as a,C as o,t as i}from"./index-gciDsUQi.js";const m={class:"mDialog__body"},g={class:"mDialog__art"},u={class:"mDialog__title"},D={class:"mDialog__actions"},h=l({__name:"DialogVant",props:{timeOut:{},text1:{},text2:{},text3:{}},setup(s){return(e,x)=>(r(),n(_(d),{class:"mDialog"},{default:c(()=>[t("div",m,[t("div",g,[a(e.$slots,"img",{},()=>[o(i(s.text2),1)])]),t("div",u,[a(e.$slots,"title",{},()=>[o(i(s.text1),1)])]),t("div",D,[a(e.$slots,"button",{},()=>[o(i(s.text3),1)])])])]),_:3}))}});export{h as _};
@@ -1 +0,0 @@
import{d as i,j as n,q as r,u as d,aj as c,f,b as t,z as o,C as a,t as l}from"./index-CmYEpHK9.js";const x={class:"my-4"},u={class:"w-full flex justify-center"},m={class:"w-full flex justify-center text-[18px] font-semibold"},_={class:"grid grid-cols-2 border-t border-solid border-[#E5E7EB] pt-4 mx-2 mt-6 font-semibold text-[15px]"},g=i({__name:"DialogVant",props:{timeOut:{},text1:{},text2:{},text3:{}},setup(e){return(s,b)=>(f(),n(d(c),null,{default:r(()=>[t("div",x,[t("div",u,[o(s.$slots,"img",{},()=>[a(l(e.text2),1)])]),t("div",m,[o(s.$slots,"title",{},()=>[a(l(e.text1),1)])]),t("div",_,[o(s.$slots,"button",{},()=>[a(l(e.text3),1)])])])]),_:3}))}});export{g as _};
@@ -1 +1 @@
import{d as f,o as g,c as o,l as e,q as c,g as m,f as t,F as p,b as i,r as y,I as b,u as h,e as k,v as P,h as C,s as F,j as x,_ as B}from"./index-CmYEpHK9.js";import{P as V}from"./PageBar-D80e377v.js";import{a as w}from"./Spinner.vue_vue_type_style_index_0_lang-CvgYqzhC.js";import{S}from"./SectionHead-L1TLZGJP.js";import{P as I}from"./ProductCard-BeFuxqqt.js";import{a as N}from"./SingleProduct-CDKWZbYq.js";import{a as $}from"./client-WIBW0tF5.js";import"./_commonjsHelpers-Cpj98o6Y.js";import"./cartStore-DSCChC_v.js";import"./index-DSgZu76n.js";import"./classCallCheck-Ddn4Ym7v.js";import"./NotifyVant.vue_vue_type_script_setup_true_lang-jgMMdODO.js";import"./DialogVant.vue_vue_type_style_index_0_lang-D7yryjTd.js";import"./dialogEmpty1-Caw7F5KL.js";const R={class:"favs"},j={class:"favs__grid"},q={key:1,class:"favs__empty"},z=f({__name:"FavoritesView",setup(E){const d=P(),v=N(),a=m(!1),l=m([]),r=C(()=>l.value.filter(u=>v.isFavorite(u.id)));return g(async()=>{a.value=!0;try{l.value=await $.listProducts()}finally{a.value=!1}}),(u,s)=>{const _=F("container");return t(),o("div",R,[e(V,{title:"علاقه‌مندی","back-to":"userPage::hasSubRoute"}),e(w,{spinning:a.value,tip:"در حال دریافت اطلاعات"},{default:c(()=>[e(_,{class:"favs__body"},{default:c(()=>[r.value.length?(t(),o(p,{key:0},[e(S,{title:"ذخیره‌شده‌ها",subtitle:`${r.value.length} محصول`},null,8,["subtitle"]),i("div",j,[(t(!0),o(p,null,y(r.value,n=>(t(),x(I,{key:n.id,value:n},null,8,["value"]))),128))])],64)):a.value?k("",!0):(t(),o("section",q,[e(b,{icon:"vuesax-linear:heart",size:"26"}),s[1]||(s[1]=i("p",null,"هنوز محصولی را به علاقه‌مندی‌ها اضافه نکرده‌اید.",-1)),i("button",{type:"button",class:"favs__emptyCta",onClick:s[0]||(s[0]=n=>h(d).push({name:"home"}))}," دیدن محصولات ")]))]),_:1})]),_:1},8,["spinning"])])}}}),Y=B(z,[["__scopeId","data-v-a8e72280"]]);export{Y as default}; import{d as f,o as g,c as o,l as e,q as c,g as m,f as t,F as p,b as i,r as y,I as b,u as h,e as k,v as P,h as C,s as F,j as x,_ as B}from"./index-gciDsUQi.js";import{P as V}from"./PageBar-uS5upavZ.js";import{a as w}from"./Spinner.vue_vue_type_style_index_0_lang-CkjhJnbV.js";import{S}from"./SectionHead-BKOd3BbV.js";import{P as I}from"./ProductCard-CoWnvkjC.js";import{a as N}from"./SingleProduct-Bz_F3HVn.js";import{a as $}from"./client-WIBW0tF5.js";import"./_commonjsHelpers-Cpj98o6Y.js";import"./cartStore-BqvRThaH.js";import"./index-B7DBPshJ.js";import"./classCallCheck-C6rD-0r0.js";import"./NotifyVant.vue_vue_type_script_setup_true_lang-T1ksLsUj.js";import"./DialogVant.vue_vue_type_style_index_0_lang-BM_3o3Fn.js";import"./dialogEmpty1-Caw7F5KL.js";const R={class:"favs"},j={class:"favs__grid"},q={key:1,class:"favs__empty"},z=f({__name:"FavoritesView",setup(E){const d=P(),v=N(),a=m(!1),l=m([]),r=C(()=>l.value.filter(u=>v.isFavorite(u.id)));return g(async()=>{a.value=!0;try{l.value=await $.listProducts()}finally{a.value=!1}}),(u,s)=>{const _=F("container");return t(),o("div",R,[e(V,{title:"علاقه‌مندی","back-to":"userPage::hasSubRoute"}),e(w,{spinning:a.value,tip:"در حال دریافت اطلاعات"},{default:c(()=>[e(_,{class:"favs__body"},{default:c(()=>[r.value.length?(t(),o(p,{key:0},[e(S,{title:"ذخیره‌شده‌ها",subtitle:`${r.value.length} محصول`},null,8,["subtitle"]),i("div",j,[(t(!0),o(p,null,y(r.value,n=>(t(),x(I,{key:n.id,value:n},null,8,["value"]))),128))])],64)):a.value?k("",!0):(t(),o("section",q,[e(b,{icon:"vuesax-linear:heart",size:"26"}),s[1]||(s[1]=i("p",null,"هنوز محصولی را به علاقه‌مندی‌ها اضافه نکرده‌اید.",-1)),i("button",{type:"button",class:"favs__emptyCta",onClick:s[0]||(s[0]=n=>h(d).push({name:"home"}))}," دیدن محصولات ")]))]),_:1})]),_:1},8,["spinning"])])}}}),Y=B(z,[["__scopeId","data-v-a8e72280"]]);export{Y as default};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{d,f as o,c as i,l as a,u as c,x as P,y as S,n as b,z as k,j as v,e as y,g as u,A as B,B as E,q as p,F as C,r as L,I,b as N,h as V,s as $,_ as A}from"./index-CmYEpHK9.js";import{P as F}from"./PageBar-D80e377v.js";import{E as z,a as h}from"./Spinner.vue_vue_type_style_index_0_lang-CvgYqzhC.js";import{S as j}from"./SectionHead-L1TLZGJP.js";import{P as q}from"./ProductCard-BeFuxqqt.js";import{a as D}from"./client-WIBW0tF5.js";import"./_commonjsHelpers-Cpj98o6Y.js";import"./SingleProduct-CDKWZbYq.js";import"./NotifyVant.vue_vue_type_script_setup_true_lang-jgMMdODO.js";import"./DialogVant.vue_vue_type_style_index_0_lang-D7yryjTd.js";import"./cartStore-DSCChC_v.js";import"./dialogEmpty1-Caw7F5KL.js";import"./index-DSgZu76n.js";import"./classCallCheck-Ddn4Ym7v.js";const H={class:"flex justify-center items-center empty"},T=d({__name:"EmptyState",props:{description:{},image:{}},setup(e){return(t,s)=>(o(),i("div",H,[a(c(z),{description:"موردی یافت نشد",image:e.image,class:"h-[200px] w-[200px] opacity-70"},null,8,["image"])]))}}),G="/assets/favoritEmpty-CIK7qR49.svg",K=d({__name:"ScrollFetchSearch",props:{loading:{type:Boolean},flag:{type:Boolean},padding:{}},emits:["fetch"],setup(e,{emit:t}){const s=t,n=P(),r=u(null);return S(r,()=>{s("fetch")},{distance:50}),(l,m)=>(o(),i("main",{class:"flex flex-col flex-1 overflow-auto no-scrollbar w-[100vw] max-w-screen-md m-auto",style:b({paddingTop:e.padding||c(n).headerHeight+"px"}),ref_key:"scrollArea",ref:r},[k(l.$slots,"default"),a(h,{cls:"my-[10px]",spinning:e.loading,indicator:!0,tip:"درحال دریافت اطلاعات"},null,8,["spinning"]),e.flag?(o(),v(T,{key:0,image:c(G)},null,8,["image"])):y("",!0)],4))}});function M(){const e=B({totalPages:0,productList:[],flag:!1});return{productsData:e,getProducts:async(s,n=!1)=>{if(!n){s.value=!0;try{e.productList=await D.listProducts(),e.flag=e.productList.length<1}catch(r){console.log(r)}finally{s.value=!1}}}}}const R={class:"newest"},Z={key:0,class:"newest__grid"},J={key:1,class:"newest__empty"},g=20,O=d({__name:"NewestView",setup(e){const{productsData:t,getProducts:s}=M(),n=u(!1),r=u(!1),l=u(g),m=V(()=>t.productList.slice(0,l.value));E(async()=>{await s(n,!1)});const w=async()=>{if(l.value<t.productList.length){l.value+=g;return}await s(r,!0)};return(Q,f)=>{const x=$("container");return o(),i("div",R,[a(F,{title:"جدیدترین محصولات","back-to":"home"}),a(h,{spinning:n.value,tip:"درحال دریافت اطلاعات"},{default:p(()=>[a(K,{class:"newest__scroll",loading:r.value,flag:c(t).flag,padding:"0px",onFetch:w},{default:p(()=>[a(x,{class:"newest__body"},{default:p(()=>[a(j,{title:"تازه‌ها",subtitle:"آخرین افزوده‌های مجموعه"}),c(t).productList.length?(o(),i("div",Z,[(o(!0),i(C,null,L(m.value,_=>(o(),v(q,{key:_.id,value:_},null,8,["value"]))),128))])):n.value?y("",!0):(o(),i("section",J,[a(I,{icon:"vuesax-linear:box",size:"26"}),f[0]||(f[0]=N("p",null,"هنوز محصولی در مجموعه ثبت نشده است.",-1))]))]),_:1})]),_:1},8,["loading","flag"])]),_:1},8,["spinning"])])}}}),ue=A(O,[["__scopeId","data-v-ecd416be"]]);export{ue as default}; import{d,f as o,c as i,l as a,u as c,x as P,y as S,n as b,z as k,j as v,e as y,g as u,A as B,B as E,q as p,F as C,r as L,I,b as N,h as V,s as $,_ as A}from"./index-gciDsUQi.js";import{P as F}from"./PageBar-uS5upavZ.js";import{E as z,a as h}from"./Spinner.vue_vue_type_style_index_0_lang-CkjhJnbV.js";import{S as j}from"./SectionHead-BKOd3BbV.js";import{P as q}from"./ProductCard-CoWnvkjC.js";import{a as D}from"./client-WIBW0tF5.js";import"./_commonjsHelpers-Cpj98o6Y.js";import"./SingleProduct-Bz_F3HVn.js";import"./NotifyVant.vue_vue_type_script_setup_true_lang-T1ksLsUj.js";import"./DialogVant.vue_vue_type_style_index_0_lang-BM_3o3Fn.js";import"./cartStore-BqvRThaH.js";import"./dialogEmpty1-Caw7F5KL.js";import"./index-B7DBPshJ.js";import"./classCallCheck-C6rD-0r0.js";const H={class:"flex justify-center items-center empty"},T=d({__name:"EmptyState",props:{description:{},image:{}},setup(e){return(t,s)=>(o(),i("div",H,[a(c(z),{description:"موردی یافت نشد",image:e.image,class:"h-[200px] w-[200px] opacity-70"},null,8,["image"])]))}}),G="/assets/favoritEmpty-CIK7qR49.svg",K=d({__name:"ScrollFetchSearch",props:{loading:{type:Boolean},flag:{type:Boolean},padding:{}},emits:["fetch"],setup(e,{emit:t}){const s=t,n=P(),r=u(null);return S(r,()=>{s("fetch")},{distance:50}),(l,m)=>(o(),i("main",{class:"flex flex-col flex-1 overflow-auto no-scrollbar w-[100vw] max-w-screen-md m-auto",style:b({paddingTop:e.padding||c(n).headerHeight+"px"}),ref_key:"scrollArea",ref:r},[k(l.$slots,"default"),a(h,{cls:"my-[10px]",spinning:e.loading,indicator:!0,tip:"درحال دریافت اطلاعات"},null,8,["spinning"]),e.flag?(o(),v(T,{key:0,image:c(G)},null,8,["image"])):y("",!0)],4))}});function M(){const e=B({totalPages:0,productList:[],flag:!1});return{productsData:e,getProducts:async(s,n=!1)=>{if(!n){s.value=!0;try{e.productList=await D.listProducts(),e.flag=e.productList.length<1}catch(r){console.log(r)}finally{s.value=!1}}}}}const R={class:"newest"},Z={key:0,class:"newest__grid"},J={key:1,class:"newest__empty"},g=20,O=d({__name:"NewestView",setup(e){const{productsData:t,getProducts:s}=M(),n=u(!1),r=u(!1),l=u(g),m=V(()=>t.productList.slice(0,l.value));E(async()=>{await s(n,!1)});const w=async()=>{if(l.value<t.productList.length){l.value+=g;return}await s(r,!0)};return(Q,f)=>{const x=$("container");return o(),i("div",R,[a(F,{title:"جدیدترین محصولات","back-to":"home"}),a(h,{spinning:n.value,tip:"درحال دریافت اطلاعات"},{default:p(()=>[a(K,{class:"newest__scroll",loading:r.value,flag:c(t).flag,padding:"0px",onFetch:w},{default:p(()=>[a(x,{class:"newest__body"},{default:p(()=>[a(j,{title:"تازه‌ها",subtitle:"آخرین افزوده‌های مجموعه"}),c(t).productList.length?(o(),i("div",Z,[(o(!0),i(C,null,L(m.value,_=>(o(),v(q,{key:_.id,value:_},null,8,["value"]))),128))])):n.value?y("",!0):(o(),i("section",J,[a(I,{icon:"vuesax-linear:box",size:"26"}),f[0]||(f[0]=N("p",null,"هنوز محصولی در مجموعه ثبت نشده است.",-1))]))]),_:1})]),_:1},8,["loading","flag"])]),_:1},8,["spinning"])])}}}),ue=A(O,[["__scopeId","data-v-ecd416be"]]);export{ue as default};
@@ -1 +1 @@
import{d as a,c as n,l as o,q as i,s as c,f as _,b as e,I as r,_ as f}from"./index-CmYEpHK9.js";import{P as l}from"./PageBar-D80e377v.js";const p={class:"notif"},d={class:"notif__empty"},m=a({__name:"NotificationsView",setup(u){return(x,t)=>{const s=c("container");return _(),n("div",p,[o(l,{title:"اعلان‌ها","back-to":"home"}),o(s,{class:"notif__body"},{default:i(()=>[e("section",d,[o(r,{icon:"vuesax-linear:notification",size:"26"}),t[0]||(t[0]=e("p",null,"اعلانی برای نمایش وجود ندارد.",-1))])]),_:1})])}}}),N=f(m,[["__scopeId","data-v-44d0c9a5"]]);export{N as default}; import{d as a,c as n,l as o,q as i,s as c,f as _,b as e,I as r,_ as f}from"./index-gciDsUQi.js";import{P as l}from"./PageBar-uS5upavZ.js";const p={class:"notif"},d={class:"notif__empty"},m=a({__name:"NotificationsView",setup(u){return(x,t)=>{const s=c("container");return _(),n("div",p,[o(l,{title:"اعلان‌ها","back-to":"home"}),o(s,{class:"notif__body"},{default:i(()=>[e("section",d,[o(r,{icon:"vuesax-linear:notification",size:"26"}),t[0]||(t[0]=e("p",null,"اعلانی برای نمایش وجود ندارد.",-1))])]),_:1})])}}}),N=f(m,[["__scopeId","data-v-44d0c9a5"]]);export{N as default};
@@ -1 +1 @@
import{d as r,l as n,ac as p,ad as f,ae as h,af as w,ag as y,ah as g,a4 as x,ai as i,a3 as k,ab as N,E as S,f as V,c as v,u as P,n as b,q as _,b as l,I as B,z as I,C as z,t as C,e as O,g as E}from"./index-CmYEpHK9.js";const[T,U]=k("notify"),q=["lockScroll","position","show","teleport","zIndex"],D=w({},y,{type:i("danger"),color:String,message:x,position:i("top"),className:g,background:String,lockScroll:Boolean});var $=r({name:T,props:D,emits:["update:show"],setup(e,{emit:o,slots:t}){const a=s=>o("update:show",s);return()=>n(h,p({class:[U([e.type]),e.className],style:{color:e.color,background:e.background},overlay:!1,duration:.2,"onUpdate:show":a},f(e,q)),{default:()=>[t.default?t.default():e.message]})}});const j=N($),A={key:0,class:"flex"},F={class:"w-full text-white my-2"},G={class:"w-full flex content-end"},J=r({__name:"NotifyVant",props:{modelValue:{type:Boolean},timeOut:{},text:{},color:{}},emits:["update:modelValue"],setup(e,{emit:o}){const t=E(!0),a=e,s=o;S(()=>a.modelValue,()=>{a.timeOut!==-1&&u()});const u=()=>{setTimeout(()=>{s("update:modelValue",!1)},a.timeOut)};return(d,c)=>e.modelValue?(V(),v("div",A,[n(P(j),{show:t.value,"onUpdate:show":c[0]||(c[0]=m=>t.value=m),style:b(e.color)},{default:_(()=>[l("div",F,[l("div",G,[n(B,{icon:"vuesax-linear:info-circle",class:"ml-[20px] text-white",size:"25"}),l("div",null,[I(d.$slots,"default",{},()=>[z(C(e.text),1)])])])])]),_:3},8,["show","style"])])):O("",!0)}});export{J as _}; import{d as r,l as n,af as p,ag as f,ah as h,ai as w,aj as y,ak as g,a4 as x,al as i,a3 as k,ab as N,E as S,f as V,c as v,u as P,n as b,q as _,b as l,I as B,z as I,C as z,t as C,e as O,g as E}from"./index-gciDsUQi.js";const[T,U]=k("notify"),j=["lockScroll","position","show","teleport","zIndex"],q=w({},y,{type:i("danger"),color:String,message:x,position:i("top"),className:g,background:String,lockScroll:Boolean});var D=r({name:T,props:q,emits:["update:show"],setup(e,{emit:o,slots:t}){const a=s=>o("update:show",s);return()=>n(h,p({class:[U([e.type]),e.className],style:{color:e.color,background:e.background},overlay:!1,duration:.2,"onUpdate:show":a},f(e,j)),{default:()=>[t.default?t.default():e.message]})}});const $=N(D),A={key:0,class:"flex"},F={class:"w-full text-white my-2"},G={class:"w-full flex content-end"},J=r({__name:"NotifyVant",props:{modelValue:{type:Boolean},timeOut:{},text:{},color:{}},emits:["update:modelValue"],setup(e,{emit:o}){const t=E(!0),a=e,s=o;S(()=>a.modelValue,()=>{a.timeOut!==-1&&u()});const u=()=>{setTimeout(()=>{s("update:modelValue",!1)},a.timeOut)};return(d,c)=>e.modelValue?(V(),v("div",A,[n(P($),{show:t.value,"onUpdate:show":c[0]||(c[0]=m=>t.value=m),style:b(e.color)},{default:_(()=>[l("div",F,[l("div",G,[n(B,{icon:"vuesax-linear:info-circle",class:"ml-[20px] text-white",size:"25"}),l("div",null,[I(d.$slots,"default",{},()=>[z(C(e.text),1)])])])])]),_:3},8,["show","style"])])):O("",!0)}});export{J as _};
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{d as _,c as s,b as a,l as d,I as b,t as u,z as i,f as t,v as p,_ as k}from"./index-CmYEpHK9.js";const h={class:"bar"},f={class:"bar__inner"},m={key:1,class:"bar__spacer","aria-hidden":"true"},B={class:"bar__title"},g={class:"bar__trailing"},v=_({__name:"PageBar",props:{title:{},back:{type:Boolean,default:!0},backTo:{}},setup(e){const o=e,r=p();function l(){o.backTo?r.push({name:o.backTo}):r.back()}return(n,c)=>(t(),s("header",h,[a("div",f,[e.back?(t(),s("button",{key:0,type:"button",class:"bar__btn","aria-label":"بازگشت",onClick:l},[d(b,{icon:"vuesax-linear:arrow-right-1",size:"20"})])):(t(),s("span",m)),a("h1",B,u(e.title),1),a("div",g,[i(n.$slots,"trailing",{},()=>[c[0]||(c[0]=a("span",{class:"bar__spacer","aria-hidden":"true"},null,-1))],!0)])]),i(n.$slots,"below",{},void 0,!0)]))}}),I=k(v,[["__scopeId","data-v-d2d3dfeb"]]);export{I as P}; import{d as _,c as s,b as a,l as d,I as b,t as u,z as i,f as t,v as p,_ as k}from"./index-gciDsUQi.js";const h={class:"bar"},f={class:"bar__inner"},m={key:1,class:"bar__spacer","aria-hidden":"true"},B={class:"bar__title"},g={class:"bar__trailing"},v=_({__name:"PageBar",props:{title:{},back:{type:Boolean,default:!0},backTo:{}},setup(e){const o=e,r=p();function l(){o.backTo?r.push({name:o.backTo}):r.back()}return(n,c)=>(t(),s("header",h,[a("div",f,[e.back?(t(),s("button",{key:0,type:"button",class:"bar__btn","aria-label":"بازگشت",onClick:l},[d(b,{icon:"vuesax-linear:arrow-right-1",size:"20"})])):(t(),s("span",m)),a("h1",B,u(e.title),1),a("div",g,[i(n.$slots,"trailing",{},()=>[c[0]||(c[0]=a("span",{class:"bar__spacer","aria-hidden":"true"},null,-1))],!0)])]),i(n.$slots,"below",{},void 0,!0)]))}}),I=k(v,[["__scopeId","data-v-d2d3dfeb"]]);export{I as P};
-1
View File
@@ -1 +0,0 @@
.pc[data-v-dd58e1ab]{cursor:pointer;border-radius:32px;background:#f4f5f6;padding:16px 14px 15px;text-align:center;transition:transform .2s ease,background .2s ease}.pc[data-v-dd58e1ab]:active{transform:scale(.985)}.pc__art[data-v-dd58e1ab]{position:relative;aspect-ratio:1 / 1;display:flex;align-items:center;justify-content:center}.pc__art img[data-v-dd58e1ab]{max-width:82%;max-height:100%;-o-object-fit:contain;object-fit:contain;-o-object-position:center;object-position:center;display:block}.pc__artEmpty[data-v-dd58e1ab]{width:100%;height:100%;display:grid;place-items:center;color:#8a909c}.pc__fav[data-v-dd58e1ab]{position:absolute;top:0;left:0;width:30px;height:30px;display:grid;place-items:center;border:0;cursor:pointer;border-radius:50%;color:#8a909c;background:#ffffffbf;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);transition:color .18s ease,transform .18s ease}.pc__fav.is-on[data-v-dd58e1ab]{color:#7a1b33;background:#fff}.pc__fav[data-v-dd58e1ab]:active{transform:scale(.9)}.pc__flag[data-v-dd58e1ab]{position:absolute;top:0;right:0;padding:4px 9px;border-radius:999px;font-size:10px;font-weight:700;color:#fff;background:#14161a}.pc__flag--soft[data-v-dd58e1ab]{background:#a33c55}.pc__title[data-v-dd58e1ab]{margin:12px 0 0;font-size:12.5px;font-weight:600;color:#14161a;line-height:1.6;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pc__weight[data-v-dd58e1ab]{margin:5px 0 0;font-size:16px;font-weight:700;line-height:1.4;color:#22262e}.pc__weightUnit[data-v-dd58e1ab]{font-size:12px;font-weight:600;color:#5b616e}.pc__meta[data-v-dd58e1ab]{display:flex;align-items:center;justify-content:center;gap:8px;padding-top:5px;font-size:11.5px}.pc__wageOld[data-v-dd58e1ab]{color:#8a909c;text-decoration:line-through}.pc__wage[data-v-dd58e1ab]{color:#e31a1a;font-weight:700}
+1
View File
@@ -0,0 +1 @@
.pc[data-v-ed785ed2]{cursor:pointer;border-radius:32px;background:#f4f5f6;padding:16px 14px 15px;text-align:center;transition:transform .2s ease,background .2s ease}.pc[data-v-ed785ed2]:active{transform:scale(.985)}.pc__art[data-v-ed785ed2]{position:relative;aspect-ratio:1 / 1;display:flex;align-items:center;justify-content:center}.pc__art img[data-v-ed785ed2]{max-width:82%;max-height:100%;-o-object-fit:contain;object-fit:contain;-o-object-position:center;object-position:center;display:block}.pc__artEmpty[data-v-ed785ed2]{width:100%;height:100%;display:grid;place-items:center;color:#8a909c}.pc__fav[data-v-ed785ed2]{position:absolute;top:0;left:0;width:30px;height:30px;display:grid;place-items:center;border:0;cursor:pointer;border-radius:50%;color:#8a909c;background:#ffffffbf;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);transition:color .18s ease,transform .18s ease}.pc__fav.is-on[data-v-ed785ed2]{color:#7a1b33;background:#fff}.pc__fav[data-v-ed785ed2]:active{transform:scale(.9)}.pc__flag[data-v-ed785ed2]{position:absolute;top:0;right:0;padding:4px 9px;border-radius:999px;font-size:10px;font-weight:700;color:#fff;background:#14161a}.pc__flag--soft[data-v-ed785ed2]{background:#a33c55}.pc__title[data-v-ed785ed2]{margin:12px 0 0;font-size:12px;font-weight:500;color:#14161a;line-height:1.6;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pc__weight[data-v-ed785ed2]{margin:5px 0 0;font-size:16px;font-weight:700;line-height:1.4;color:#22262e}.pc__weightUnit[data-v-ed785ed2]{font-size:12px;font-weight:600;color:#5b616e}.pc__meta[data-v-ed785ed2]{display:flex;align-items:center;justify-content:center;gap:8px;padding-top:5px;font-size:11.5px}.pc__wageOld[data-v-ed785ed2]{color:#8a909c;text-decoration:line-through}.pc__wage[data-v-ed785ed2]{color:#e31a1a;font-weight:500;font-size:12px}
@@ -1 +1 @@
import{d as F,k as D,f as t,c as a,F as P,b as o,u as e,w as S,l as p,I as h,X as z,i as N,e as c,t as _,C as V,q as I,j as q,g as E,_ as O}from"./index-CmYEpHK9.js";import{a as U,S as $}from"./SingleProduct-CDKWZbYq.js";import{u as j}from"./cartStore-DSCChC_v.js";import{D as L}from"./index-DSgZu76n.js";const M={class:"pc__art"},T=["alt"],X={key:1,class:"pc__artEmpty","aria-hidden":"true"},A=["aria-label"],G={key:2,class:"pc__flag"},H={key:3,class:"pc__flag pc__flag--soft"},J={class:"pc__title"},K={class:"pc__weight"},Q={key:0,class:"pc__meta"},R={key:0,class:"pc__wageOld"},W={class:"pc__wage"},Y=F({__name:"ProductCard",props:{value:{},wageBefore:{}},setup(d){const w=d,r=U(),{image:v,id:i,title:f,min_gram:y,wage:m,is_exist:b,qty:k,isPreOrder:x}=j(w.value),l=E(!1);function C(){l.value=!0}function g(n){return n%1===0?String(n):n.toFixed(2)}return(n,s)=>{const B=D("lazy");return t(),a(P,null,[o("article",{class:"pc",onClick:C},[o("div",M,[e(v)?S((t(),a("img",{key:0,alt:e(f)},null,8,T)),[[B,e(v)]]):(t(),a("div",X,[p(h,{icon:"vuesax-linear:diamonds",size:"26"})])),o("button",{type:"button",class:N(["pc__fav",{"is-on":e(r).isFavorite(e(i))}]),"aria-label":e(r).isFavorite(e(i))?"حذف از علاقه‌مندی":"افزودن به علاقه‌مندی",onClick:s[0]||(s[0]=z(u=>e(r).toggle(e(i)),["stop"]))},[p(h,{icon:e(r).isFavorite(e(i))?"vuesax-linear:heart-fill":"vuesax-linear:heart",size:"16"},null,8,["icon"])],10,A),e(b)?e(x)&&e(k)===0?(t(),a("span",H,"پیش‌سفارش")):c("",!0):(t(),a("span",G,"ناموجود"))]),o("h3",J,_(e(f)),1),o("p",K,[V(_(e(y))+" ",1),s[3]||(s[3]=o("span",{class:"pc__weightUnit"},"گرم",-1))]),e(m)!=null?(t(),a("div",Q,[d.wageBefore!=null?(t(),a("span",R,"اجرت "+_(g(d.wageBefore))+"٪",1)):c("",!0),o("span",W,_(g(e(m)))+"٪ اجرت",1)])):c("",!0)]),p(e(L),{visible:l.value,"onUpdate:visible":s[2]||(s[2]=u=>l.value=u),placement:"bottom",closable:!1,class:"rtl",height:"100vh",bodyStyle:{padding:0,position:"relative"}},{default:I(()=>[l.value?(t(),q($,{id:e(i),key:e(i),onCloseDrawer:s[1]||(s[1]=u=>l.value=!1)},null,8,["id"])):c("",!0)]),_:1},8,["visible"])],64)}}}),ae=O(Y,[["__scopeId","data-v-dd58e1ab"]]);export{ae as P}; import{d as F,k as D,f as t,c as a,F as P,b as o,u as e,w as S,l as p,I as h,X as z,i as N,e as c,t as _,C as V,q as I,j as q,g as E,_ as O}from"./index-gciDsUQi.js";import{a as U,S as $}from"./SingleProduct-Bz_F3HVn.js";import{u as j}from"./cartStore-BqvRThaH.js";import{D as L}from"./index-B7DBPshJ.js";const M={class:"pc__art"},T=["alt"],X={key:1,class:"pc__artEmpty","aria-hidden":"true"},A=["aria-label"],G={key:2,class:"pc__flag"},H={key:3,class:"pc__flag pc__flag--soft"},J={class:"pc__title"},K={class:"pc__weight"},Q={key:0,class:"pc__meta"},R={key:0,class:"pc__wageOld"},W={class:"pc__wage"},Y=F({__name:"ProductCard",props:{value:{},wageBefore:{}},setup(d){const w=d,r=U(),{image:v,id:i,title:f,min_gram:y,wage:m,is_exist:k,qty:b,isPreOrder:x}=j(w.value),l=E(!1);function C(){l.value=!0}function g(n){return n%1===0?String(n):n.toFixed(2)}return(n,s)=>{const B=D("lazy");return t(),a(P,null,[o("article",{class:"pc",onClick:C},[o("div",M,[e(v)?S((t(),a("img",{key:0,alt:e(f)},null,8,T)),[[B,e(v)]]):(t(),a("div",X,[p(h,{icon:"vuesax-linear:diamonds",size:"26"})])),o("button",{type:"button",class:N(["pc__fav",{"is-on":e(r).isFavorite(e(i))}]),"aria-label":e(r).isFavorite(e(i))?"حذف از علاقه‌مندی":"افزودن به علاقه‌مندی",onClick:s[0]||(s[0]=z(u=>e(r).toggle(e(i)),["stop"]))},[p(h,{icon:e(r).isFavorite(e(i))?"vuesax-linear:heart-fill":"vuesax-linear:heart",size:"16"},null,8,["icon"])],10,A),e(k)?e(x)&&e(b)===0?(t(),a("span",H,"پیش‌سفارش")):c("",!0):(t(),a("span",G,"ناموجود"))]),o("h3",J,_(e(f)),1),o("p",K,[V(_(e(y))+" ",1),s[3]||(s[3]=o("span",{class:"pc__weightUnit"},"گرم",-1))]),e(m)!=null?(t(),a("div",Q,[d.wageBefore!=null?(t(),a("span",R,"اجرت "+_(g(d.wageBefore))+"٪",1)):c("",!0),o("span",W,_(g(e(m)))+"٪ اجرت",1)])):c("",!0)]),p(e(L),{visible:l.value,"onUpdate:visible":s[2]||(s[2]=u=>l.value=u),placement:"bottom",closable:!1,class:"rtl",height:"100vh",bodyStyle:{padding:0,position:"relative"}},{default:I(()=>[l.value?(t(),q($,{id:e(i),key:e(i),onCloseDrawer:s[1]||(s[1]=u=>l.value=!1)},null,8,["id"])):c("",!0)]),_:1},8,["visible"])],64)}}}),ae=O(Y,[["__scopeId","data-v-ed785ed2"]]);export{ae as P};
@@ -1 +1 @@
import{d as I,D as S,o as E,c as a,l,q as y,g as p,s as V,f as i,b as e,I as f,t,e as _,i as b,h as k,_ as h}from"./index-CmYEpHK9.js";import{P as C}from"./PageBar-D80e377v.js";import{S as N}from"./SectionHead-L1TLZGJP.js";import{a as T}from"./client-WIBW0tF5.js";const g={class:"prof"},B={class:"prof__identity"},D={class:"prof__avatar","aria-hidden":"true"},P={class:"prof__name"},x={key:0,class:"prof__mobile"},R={key:0,class:"prof__block"},w={class:"prof__specs"},F={class:"prof__spec"},z={class:"prof__spec"},M={class:"prof__spec"},O={class:"prof__spec"},U={key:1,class:"prof__empty"},A=I({__name:"ProfileView",setup(L){const c={UNVERIFIED:"احراز نشده",STEP_ONE_SUBMITTED:"مرحله اول ثبت شده",STEP_TWO_SUBMITTED:"مرحله دوم ثبت شده",VERIFIED:"تایید شده"},u=S(),s=p(null),n=p(!1),v=k(()=>{var r;return((r=s.value)==null?void 0:r.verificationStep)==="VERIFIED"});return E(async()=>{if(u.customerId){n.value=!0;try{s.value=await T.getCustomer(u.customerId)}finally{n.value=!1}}}),(r,o)=>{const m=V("container");return i(),a("div",g,[l(C,{title:"اطلاعات کاربری","back-to":"userPage::hasSubRoute"}),l(m,{class:"prof__body"},{default:y(()=>{var d;return[e("section",B,[e("div",D,[l(f,{icon:"vuesax-linear:user",size:"30"})]),e("h2",P,t(((d=s.value)==null?void 0:d.fullName)??"—"),1),s.value?(i(),a("p",x,t(s.value.mobile),1)):_("",!0),s.value?(i(),a("span",{key:1,class:b(["prof__seal",v.value?"is-ok":"is-pending"])},t(c[s.value.verificationStep]??s.value.verificationStep),3)):_("",!0)]),s.value?(i(),a("section",R,[l(N,{title:"مشخصات",subtitle:"اطلاعات ثبت‌شده حساب شما"}),e("dl",w,[e("div",F,[o[0]||(o[0]=e("dt",null,"نام و نام خانوادگی",-1)),e("dd",null,t(s.value.fullName),1)]),e("div",z,[o[1]||(o[1]=e("dt",null,"تلفن همراه",-1)),e("dd",null,t(s.value.mobile),1)]),e("div",M,[o[2]||(o[2]=e("dt",null,"کد ملی",-1)),e("dd",null,t(s.value.nationalCode),1)]),e("div",O,[o[3]||(o[3]=e("dt",null,"وضعیت احراز هویت",-1)),e("dd",null,t(c[s.value.verificationStep]??s.value.verificationStep),1)])])])):n.value?_("",!0):(i(),a("section",U,[l(f,{icon:"vuesax-linear:user",size:"26"}),o[4]||(o[4]=e("p",null,"اطلاعات حساب در دسترس نیست.",-1))]))]}),_:1})])}}}),G=h(A,[["__scopeId","data-v-7c544fc5"]]);export{G as default}; import{d as I,D as S,o as E,c as a,l,q as y,g as p,s as V,f as i,b as e,I as f,t,e as _,i as b,h as k,_ as h}from"./index-gciDsUQi.js";import{P as C}from"./PageBar-uS5upavZ.js";import{S as N}from"./SectionHead-BKOd3BbV.js";import{a as T}from"./client-WIBW0tF5.js";const g={class:"prof"},B={class:"prof__identity"},D={class:"prof__avatar","aria-hidden":"true"},P={class:"prof__name"},x={key:0,class:"prof__mobile"},R={key:0,class:"prof__block"},w={class:"prof__specs"},F={class:"prof__spec"},z={class:"prof__spec"},M={class:"prof__spec"},O={class:"prof__spec"},U={key:1,class:"prof__empty"},A=I({__name:"ProfileView",setup(L){const c={UNVERIFIED:"احراز نشده",STEP_ONE_SUBMITTED:"مرحله اول ثبت شده",STEP_TWO_SUBMITTED:"مرحله دوم ثبت شده",VERIFIED:"تایید شده"},u=S(),s=p(null),n=p(!1),v=k(()=>{var r;return((r=s.value)==null?void 0:r.verificationStep)==="VERIFIED"});return E(async()=>{if(u.customerId){n.value=!0;try{s.value=await T.getCustomer(u.customerId)}finally{n.value=!1}}}),(r,o)=>{const m=V("container");return i(),a("div",g,[l(C,{title:"اطلاعات کاربری","back-to":"userPage::hasSubRoute"}),l(m,{class:"prof__body"},{default:y(()=>{var d;return[e("section",B,[e("div",D,[l(f,{icon:"vuesax-linear:user",size:"30"})]),e("h2",P,t(((d=s.value)==null?void 0:d.fullName)??"—"),1),s.value?(i(),a("p",x,t(s.value.mobile),1)):_("",!0),s.value?(i(),a("span",{key:1,class:b(["prof__seal",v.value?"is-ok":"is-pending"])},t(c[s.value.verificationStep]??s.value.verificationStep),3)):_("",!0)]),s.value?(i(),a("section",R,[l(N,{title:"مشخصات",subtitle:"اطلاعات ثبت‌شده حساب شما"}),e("dl",w,[e("div",F,[o[0]||(o[0]=e("dt",null,"نام و نام خانوادگی",-1)),e("dd",null,t(s.value.fullName),1)]),e("div",z,[o[1]||(o[1]=e("dt",null,"تلفن همراه",-1)),e("dd",null,t(s.value.mobile),1)]),e("div",M,[o[2]||(o[2]=e("dt",null,"کد ملی",-1)),e("dd",null,t(s.value.nationalCode),1)]),e("div",O,[o[3]||(o[3]=e("dt",null,"وضعیت احراز هویت",-1)),e("dd",null,t(c[s.value.verificationStep]??s.value.verificationStep),1)])])])):n.value?_("",!0):(i(),a("section",U,[l(f,{icon:"vuesax-linear:user",size:"26"}),o[4]||(o[4]=e("p",null,"اطلاعات حساب در دسترس نیست.",-1))]))]}),_:1})])}}}),G=h(A,[["__scopeId","data-v-7c544fc5"]]);export{G as default};
@@ -1 +1 @@
import{p as n,j as d}from"./filters-CLL9nGZ2.js";import{d as l,c as s,i as u,b as a,C as m,t as i,u as c,e as _,h as v,f as r,_ as q}from"./index-CmYEpHK9.js";const f={key:0,class:"rate-strip__value"},y={class:"rate-strip__num"},k={key:1,class:"rate-strip__value rate-strip__value--muted"},x={key:2,class:"rate-strip__time"},B=l({__name:"RateStrip",props:{quote:{},compact:{type:Boolean}},setup(t){const o=t,p=v(()=>o.quote?o.quote.pricePerGramRial.split(".")[0]:"");return(C,e)=>(r(),s("div",{class:u(["rate-strip",{"is-empty":!t.quote,"rate-strip--compact":t.compact}])},[e[1]||(e[1]=a("div",{class:"rate-strip__label"},[a("span",{class:"rate-strip__dot","aria-hidden":"true"}),m(" نرخ طلای ۱۸ عیار ")],-1)),t.quote?(r(),s("div",f,[a("span",y,i(c(n)(p.value)),1),e[0]||(e[0]=a("span",{class:"rate-strip__unit"},"ریال / گرم",-1))])):(r(),s("div",k,"در انتظار اعلام نرخ")),t.quote?(r(),s("div",x,i(c(d)(t.quote.quotedAt)),1)):_("",!0)],2))}}),h=q(B,[["__scopeId","data-v-0b16738e"]]);export{h as R}; import{p as n,j as d}from"./filters-CLL9nGZ2.js";import{d as l,c as s,i as u,b as a,C as m,t as i,u as c,e as _,h as v,f as r,_ as q}from"./index-gciDsUQi.js";const f={key:0,class:"rate-strip__value"},y={class:"rate-strip__num"},k={key:1,class:"rate-strip__value rate-strip__value--muted"},x={key:2,class:"rate-strip__time"},B=l({__name:"RateStrip",props:{quote:{},compact:{type:Boolean}},setup(t){const o=t,p=v(()=>o.quote?o.quote.pricePerGramRial.split(".")[0]:"");return(C,e)=>(r(),s("div",{class:u(["rate-strip",{"is-empty":!t.quote,"rate-strip--compact":t.compact}])},[e[1]||(e[1]=a("div",{class:"rate-strip__label"},[a("span",{class:"rate-strip__dot","aria-hidden":"true"}),m(" نرخ طلای ۱۸ عیار ")],-1)),t.quote?(r(),s("div",f,[a("span",y,i(c(n)(p.value)),1),e[0]||(e[0]=a("span",{class:"rate-strip__unit"},"ریال / گرم",-1))])):(r(),s("div",k,"در انتظار اعلام نرخ")),t.quote?(r(),s("div",x,i(c(d)(t.quote.quotedAt)),1)):_("",!0)],2))}}),h=q(B,[["__scopeId","data-v-0b16738e"]]);export{h as R};
@@ -1 +1 @@
import{d as v,a0 as h,E as m,f as t,c as l,l as n,I as r,w as f,a1 as b,b as x,a2 as k,e as I,A as w,g as y,_ as C}from"./index-CmYEpHK9.js";const S={class:"si"},V=["placeholder"],B=v({__name:"SearchInput",props:{placeHolder:{}},emits:["search"],setup(i,{emit:d}){const c=d,a=w({clear:!1}),e=y(void 0),s=()=>{c("search",e.value)},u=()=>{e.value=void 0,a.clear=!1,c("search",void 0)},p=h(e,700);return m(p,()=>{e?a.clear=!0:a.clear=!1,s()}),(N,o)=>(t(),l("div",S,[n(r,{icon:"vuesax-linear:search-normal-1",class:"si__icon",size:"18",onClick:s}),f(x("input",{"onUpdate:modelValue":o[0]||(o[0]=_=>e.value=_),class:"si__input",placeholder:i.placeHolder,onKeyup:k(s,["enter"])},null,40,V),[[b,e.value]]),a.clear?(t(),l("button",{key:0,type:"button",class:"si__clear","aria-label":"پاک کردن",onClick:u},[n(r,{icon:"vuesax-linear:add",size:"16"})])):I("",!0)]))}}),D=C(B,[["__scopeId","data-v-8ebd3183"]]);export{D as S}; import{d as v,a0 as h,E as m,f as t,c as l,l as n,I as r,w as f,a1 as b,b as x,a2 as k,e as I,A as w,g as y,_ as C}from"./index-gciDsUQi.js";const S={class:"si"},V=["placeholder"],B=v({__name:"SearchInput",props:{placeHolder:{}},emits:["search"],setup(i,{emit:d}){const c=d,a=w({clear:!1}),e=y(void 0),s=()=>{c("search",e.value)},u=()=>{e.value=void 0,a.clear=!1,c("search",void 0)},p=h(e,700);return m(p,()=>{e?a.clear=!0:a.clear=!1,s()}),(N,o)=>(t(),l("div",S,[n(r,{icon:"vuesax-linear:search-normal-1",class:"si__icon",size:"18",onClick:s}),f(x("input",{"onUpdate:modelValue":o[0]||(o[0]=_=>e.value=_),class:"si__input",placeholder:i.placeHolder,onKeyup:k(s,["enter"])},null,40,V),[[b,e.value]]),a.clear?(t(),l("button",{key:0,type:"button",class:"si__clear","aria-label":"پاک کردن",onClick:u},[n(r,{icon:"vuesax-linear:add",size:"16"})])):I("",!0)]))}}),D=C(B,[["__scopeId","data-v-8ebd3183"]]);export{D as S};
@@ -1 +1 @@
.dd[data-v-9e57de56]{padding:15px;border:1px solid #ECEDEF;border-radius:16px;background:#fff}.dd.is-open[data-v-9e57de56]{border-color:#a33c55}.dd__head[data-v-9e57de56]{display:flex;align-items:center;justify-content:space-between;gap:12px;width:100%;border:0;padding:0;cursor:pointer;background:none;font-family:inherit;text-align:start}.dd__title[data-v-9e57de56]{margin:0;font-size:13px;font-weight:700;color:#14161a}.dd__chevron[data-v-9e57de56]{flex:0 0 auto;color:#8a909c;transition:transform .2s ease}.dd.is-open .dd__chevron[data-v-9e57de56]{transform:rotate(180deg);color:#7a1b33}.dd__body[data-v-9e57de56]{overflow:hidden;padding-top:15px}.drop-enter-active{animation:drop .2s cubic-bezier(.33,1,.68,1)}.drop-leave-to{animation:drop .2s cubic-bezier(.33,1,.68,1) reverse}@keyframes drop{0%{max-height:0}to{max-height:240px}}.filt[data-v-9651e9da]{background:#fff;min-height:100vh}.filt__body[data-v-9651e9da]{max-width:768px;margin:0 auto;padding:120px 16px 110px;display:flex;flex-direction:column;gap:12px}.filt__chips[data-v-9651e9da]{display:flex;flex-wrap:wrap;gap:8px}.filt__chip[data-v-9651e9da]{padding:8px 14px;border-radius:999px;cursor:pointer;font-family:inherit;font-size:12px;font-weight:600;color:#22262e;background:#fff;border:1px solid #ECEDEF;transition:all .2s ease}.filt__chip.is-on[data-v-9651e9da]{color:#fff;background:#14161a;border-color:#14161a}.filt__chip[data-v-9651e9da]:active{transform:scale(.97)}.filt__empty[data-v-9651e9da]{margin:0;font-size:12px;color:#8a909c}.filt__gram[data-v-9651e9da]{padding:4px 6px 0}.filt__gram[data-v-9651e9da] .ant-slider-track{background-color:#7a1b33}.filt__gram[data-v-9651e9da] .ant-slider-handle{border-color:#7a1b33}.filt__gram[data-v-9651e9da] .ant-slider-rail{background-color:#ecedef}.filt__gramFigures[data-v-9651e9da]{display:flex;align-items:center;justify-content:space-between;gap:10px;padding-top:18px}.filt__gramFigure[data-v-9651e9da]{font-size:12px;font-weight:700;color:#14161a}.filt__gramFigure i[data-v-9651e9da]{font-style:normal;font-size:11px;font-weight:600;color:#8a909c;padding-inline-end:4px}.filt__bar[data-v-9651e9da]{position:fixed;bottom:0;left:0;right:0;z-index:5;background:#ffffffeb;-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);border-top:1px solid #ECEDEF;padding-bottom:16px}.filt__barInner[data-v-9651e9da]{max-width:768px;margin:0 auto;padding:12px 16px;display:flex;align-items:center;gap:10px}.filt__clear[data-v-9651e9da],.filt__apply[data-v-9651e9da]{flex:1;padding:13px 18px;border:0;cursor:pointer;border-radius:14px;font-family:inherit;font-size:13px;font-weight:700;transition:transform .15s ease}.filt__clear[data-v-9651e9da]{color:#22262e;background:#f4f5f6;border:1px solid #ECEDEF}.filt__apply[data-v-9651e9da]{color:#fff;background:#14161a}.filt__clear[data-v-9651e9da]:active,.filt__apply[data-v-9651e9da]:active{transform:scale(.98)}.search[data-v-492690a8]{background:#fff;min-height:100vh}.search__barExtra[data-v-492690a8]{max-width:768px;margin:0 auto;padding:0 16px}.search__chips[data-v-492690a8]{display:flex;gap:8px;overflow-x:auto;margin:12px 0 10px -16px;scroll-snap-type:x proximity}.search__chips[data-v-492690a8]:before,.search__chips[data-v-492690a8]:after{content:"";flex:0 0 16px}.search__chip[data-v-492690a8]{scroll-snap-align:start;flex:0 0 auto;display:inline-flex;align-items:center;gap:6px;height:40px;padding:0 18px;border-radius:999px;cursor:pointer;font-family:inherit;font-size:12.5px;font-weight:600;white-space:nowrap;color:#22262e;background:#fff;border:1px solid #ECEDEF;transition:all .2s ease}.search__chip.is-on[data-v-492690a8]{color:#fff;background:#14161a;border-color:#14161a}.search__chip--filter.is-on[data-v-492690a8]{background:#7a1b33;border-color:#7a1b33}.search__chip[data-v-492690a8]:active{transform:scale(.97)}.search__body[data-v-492690a8]{padding-top:176px;padding-bottom:90px}.search__body[data-v-492690a8]>*:first-child{margin-top:16px}.search__collections[data-v-492690a8]{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:9px;margin-bottom:22px}.search__collection[data-v-492690a8]{display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:13px 10px;border-radius:14px;cursor:pointer;font-family:inherit;font-size:12.5px;font-weight:700;color:#22262e;background:#f4f5f6;border:1px solid #ECEDEF;transition:all .2s ease}.search__collection[data-v-492690a8]:active{transform:scale(.98)}.search__grid[data-v-492690a8]{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}.search__more[data-v-492690a8]{display:flex;justify-content:center;padding:18px 0 4px}.search__empty[data-v-492690a8]{margin-top:8px;padding:34px 20px;border:1px dashed #ECEDEF;border-radius:18px;display:flex;flex-direction:column;align-items:center;gap:10px;color:#8a909c}.search__empty p[data-v-492690a8]{margin:0;font-size:12.5px} .dd[data-v-9e57de56]{padding:15px;border:1px solid #ECEDEF;border-radius:16px;background:#fff}.dd.is-open[data-v-9e57de56]{border-color:#a33c55}.dd__head[data-v-9e57de56]{display:flex;align-items:center;justify-content:space-between;gap:12px;width:100%;border:0;padding:0;cursor:pointer;background:none;font-family:inherit;text-align:start}.dd__title[data-v-9e57de56]{margin:0;font-size:13px;font-weight:700;color:#14161a}.dd__chevron[data-v-9e57de56]{flex:0 0 auto;color:#8a909c;transition:transform .2s ease}.dd.is-open .dd__chevron[data-v-9e57de56]{transform:rotate(180deg);color:#7a1b33}.dd__body[data-v-9e57de56]{overflow:hidden;padding-top:15px}.drop-enter-active{animation:drop .2s cubic-bezier(.33,1,.68,1)}.drop-leave-to{animation:drop .2s cubic-bezier(.33,1,.68,1) reverse}@keyframes drop{0%{max-height:0}to{max-height:240px}}.filt[data-v-9651e9da]{background:#fff;min-height:100vh}.filt__body[data-v-9651e9da]{max-width:768px;margin:0 auto;padding:120px 16px 110px;display:flex;flex-direction:column;gap:12px}.filt__chips[data-v-9651e9da]{display:flex;flex-wrap:wrap;gap:8px}.filt__chip[data-v-9651e9da]{padding:8px 14px;border-radius:999px;cursor:pointer;font-family:inherit;font-size:12px;font-weight:600;color:#22262e;background:#fff;border:1px solid #ECEDEF;transition:all .2s ease}.filt__chip.is-on[data-v-9651e9da]{color:#fff;background:#14161a;border-color:#14161a}.filt__chip[data-v-9651e9da]:active{transform:scale(.97)}.filt__empty[data-v-9651e9da]{margin:0;font-size:12px;color:#8a909c}.filt__gram[data-v-9651e9da]{padding:4px 6px 0}.filt__gram[data-v-9651e9da] .ant-slider-track{background-color:#7a1b33}.filt__gram[data-v-9651e9da] .ant-slider-handle{border-color:#7a1b33}.filt__gram[data-v-9651e9da] .ant-slider-rail{background-color:#ecedef}.filt__gramFigures[data-v-9651e9da]{display:flex;align-items:center;justify-content:space-between;gap:10px;padding-top:18px}.filt__gramFigure[data-v-9651e9da]{font-size:12px;font-weight:700;color:#14161a}.filt__gramFigure i[data-v-9651e9da]{font-style:normal;font-size:11px;font-weight:600;color:#8a909c;padding-inline-end:4px}.filt__bar[data-v-9651e9da]{position:fixed;bottom:0;left:0;right:0;z-index:5;background:#ffffffeb;-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);border-top:1px solid #ECEDEF;padding-bottom:16px}.filt__barInner[data-v-9651e9da]{max-width:768px;margin:0 auto;padding:12px 16px;display:flex;align-items:center;gap:10px}.filt__clear[data-v-9651e9da],.filt__apply[data-v-9651e9da]{flex:1;padding:13px 18px;border:0;cursor:pointer;border-radius:14px;font-family:inherit;font-size:13px;font-weight:700;transition:transform .15s ease}.filt__clear[data-v-9651e9da]{color:#22262e;background:#f4f5f6;border:1px solid #ECEDEF}.filt__apply[data-v-9651e9da]{color:#fff;background:#14161a}.filt__clear[data-v-9651e9da]:active,.filt__apply[data-v-9651e9da]:active{transform:scale(.98)}.search[data-v-df0a35c2]{background:#fff;min-height:100vh}.search__barExtra[data-v-df0a35c2]{max-width:768px;margin:0 auto;padding:0 16px}.search__chips[data-v-df0a35c2]{display:flex;gap:8px;overflow-x:auto;margin:12px 0 10px -16px;scroll-snap-type:x proximity}.search__chips[data-v-df0a35c2]:before,.search__chips[data-v-df0a35c2]:after{content:"";flex:0 0 16px}.search__chip[data-v-df0a35c2]{scroll-snap-align:start;flex:0 0 auto;display:inline-flex;align-items:center;gap:6px;height:40px;padding:0 18px;border-radius:999px;cursor:pointer;font-family:inherit;font-size:12.5px;font-weight:600;white-space:nowrap;color:#22262e;background:#fff;border:1px solid #ECEDEF;transition:all .2s ease}.search__chip.is-on[data-v-df0a35c2]{color:#fff;background:#14161a;border-color:#14161a}.search__chip--filter.is-on[data-v-df0a35c2]{background:#7a1b33;border-color:#7a1b33}.search__chip[data-v-df0a35c2]:active{transform:scale(.97)}.search__body[data-v-df0a35c2]{padding-top:176px;padding-bottom:90px}.search__body[data-v-df0a35c2]>*:first-child{margin-top:16px}.search__collections[data-v-df0a35c2]{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:9px;margin-bottom:22px}.search__collection[data-v-df0a35c2]{display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:13px 10px;border-radius:14px;cursor:pointer;font-family:inherit;font-size:12.5px;font-weight:700;color:#22262e;background:#f4f5f6;border:1px solid #ECEDEF;transition:all .2s ease}.search__collection[data-v-df0a35c2]:active{transform:scale(.98)}.search__grid[data-v-df0a35c2]{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}.search__more[data-v-df0a35c2]{display:flex;justify-content:center;padding:18px 0 4px}.search__empty[data-v-df0a35c2]{margin-top:8px;padding:34px 20px;border:1px dashed #ECEDEF;border-radius:18px;display:flex;flex-direction:column;align-items:center;gap:10px;color:#8a909c}.search__empty p[data-v-df0a35c2]{margin:0;font-size:12.5px}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{d as r,f as t,c as s,b as o,t as a,e as n,C as _,l as d,I as u,_ as b}from"./index-CmYEpHK9.js";const m={class:"sec"},f={class:"sec__text"},x={class:"sec__title"},h={key:0,class:"sec__sub"},k=r({__name:"SectionHead",props:{title:{},subtitle:{},actionLabel:{}},emits:["action"],setup(e,{emit:i}){const l=i;return(p,c)=>(t(),s("header",m,[o("div",f,[o("h2",x,a(e.title),1),e.subtitle?(t(),s("p",h,a(e.subtitle),1)):n("",!0)]),e.actionLabel?(t(),s("button",{key:0,type:"button",class:"sec__action",onClick:c[0]||(c[0]=v=>l("action"))},[_(a(e.actionLabel)+" ",1),d(u,{icon:"vuesax-linear:arrow-left-2",size:"15"})])):n("",!0)]))}}),C=b(k,[["__scopeId","data-v-e5c65b7c"]]);export{C as S}; import{d as r,f as t,c as s,b as o,t as a,e as n,C as _,l as d,I as u,_ as b}from"./index-gciDsUQi.js";const m={class:"sec"},f={class:"sec__text"},x={class:"sec__title"},h={key:0,class:"sec__sub"},k=r({__name:"SectionHead",props:{title:{},subtitle:{},actionLabel:{}},emits:["action"],setup(e,{emit:i}){const l=i;return(p,c)=>(t(),s("header",m,[o("div",f,[o("h2",x,a(e.title),1),e.subtitle?(t(),s("p",h,a(e.subtitle),1)):n("",!0)]),e.actionLabel?(t(),s("button",{key:0,type:"button",class:"sec__action",onClick:c[0]||(c[0]=v=>l("action"))},[_(a(e.actionLabel)+" ",1),d(u,{icon:"vuesax-linear:arrow-left-2",size:"15"})])):n("",!0)]))}}),C=b(k,[["__scopeId","data-v-e5c65b7c"]]);export{C as S};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{d as k,D as A,o as C,c as i,l as r,q as f,j as D,u as m,e as E,g as u,f as l,F as R,b as s,r as w,I as T,t as o,i as p,s as N,_ as L}from"./index-CmYEpHK9.js";import{P}from"./PageBar-D80e377v.js";import{a as M}from"./Spinner.vue_vue_type_style_index_0_lang-CvgYqzhC.js";import{S as O}from"./SectionHead-L1TLZGJP.js";import{j as g,p as B}from"./filters-CLL9nGZ2.js";import{a as I}from"./client-WIBW0tF5.js";import{D as U}from"./index-DSgZu76n.js";import"./_commonjsHelpers-Cpj98o6Y.js";import"./classCallCheck-Ddn4Ym7v.js";const V={class:"tx"},$={class:"tx__list"},Y=["onClick"],j={class:"tx__main"},z={class:"tx__head"},F={class:"tx__kind"},G={key:0,class:"tx__note"},H={class:"tx__foot"},q={class:"tx__date"},J={key:1,class:"tx__empty"},K={class:"sheet"},Q={class:"sheet__specs"},W={class:"sheet__spec"},X={class:"sheet__spec"},Z={class:"sheet__spec"},ss={class:"sheet__spec"},es={class:"sheet__spec"},ts=k({__name:"TransactionsView",setup(os){const x={ORDER_STOCK:"سفارش",ORDER_PRE_ORDER:"سفارش ساخت",REPAYMENT_MELTED_GOLD:"طلای آب‌شده",REPAYMENT_TRANSFER:"حواله",REPAYMENT_CHEQUE:"چک",REPAYMENT_CASH:"پولی",WALLET_BONUS:"پاداش",ADJUSTMENT:"اصلاحیه"},h=A(),_=u(!1),d=u([]),v=u(!1),n=u(null),c=a=>a.direction==="CREDIT",y=a=>a.currency==="GOLD_GRAM"?`${a.amount} گرم`:`${B(a.amount)} ریال`,S=a=>{n.value=a,v.value=!0};return C(async()=>{if(h.customerId){_.value=!0;try{d.value=await I.getLedgerEntries(h.customerId)}finally{_.value=!1}}}),(a,e)=>{const b=N("container");return l(),i("div",V,[r(P,{title:"اسناد","back-to":"userPage::hasSubRoute"}),r(M,{spinning:_.value,tip:"درحال دریافت اطلاعات"},{default:f(()=>[r(b,{class:"tx__body"},{default:f(()=>[d.value.length?(l(),i(R,{key:0},[r(O,{title:"گردش حساب",subtitle:`${d.value.length} سند`},null,8,["subtitle"]),s("div",$,[(l(!0),i(R,null,w(d.value,t=>(l(),i("article",{key:t.id,class:"tx__row",onClick:ns=>S(t)},[s("span",{class:p(["tx__mark",c(t)?"is-in":"is-out"]),"aria-hidden":"true"},[r(T,{icon:c(t)?"vuesax-linear:arrow-down":"vuesax-linear:arrow-up",size:"16"},null,8,["icon"])],2),s("div",j,[s("div",z,[s("h3",F,o(x[t.sourceType]??t.sourceType),1),s("span",{class:p(["tx__amount",c(t)?"is-in":"is-out"])},o(y(t)),3)]),t.note?(l(),i("p",G,o(t.note),1)):E("",!0),s("div",H,[s("span",q,o(m(g)(t.createdAt)),1),s("span",{class:p(["tx__state",{"is-pending":!t.confirmed}])},o(t.confirmed?"تایید شده":"در انتظار تایید"),3)])])],8,Y))),128))])],64)):_.value?E("",!0):(l(),i("section",J,[r(T,{icon:"vuesax-linear:receipt-text",size:"26"}),e[1]||(e[1]=s("p",null,"سندی برای حساب شما ثبت نشده است.",-1))]))]),_:1})]),_:1},8,["spinning"]),n.value?(l(),D(m(U),{key:0,visible:v.value,"onUpdate:visible":e[0]||(e[0]=t=>v.value=t),placement:"bottom",closable:!1,height:"420px",class:"rtl",bodyStyle:{padding:0}},{default:f(()=>[s("div",K,[e[7]||(e[7]=s("h3",{class:"sheet__title"},"جزئیات سند",-1)),s("dl",Q,[s("div",W,[e[2]||(e[2]=s("dt",null,"نوع سند",-1)),s("dd",null,o(x[n.value.sourceType]??n.value.sourceType),1)]),s("div",X,[e[3]||(e[3]=s("dt",null,"مبلغ",-1)),s("dd",{class:p(c(n.value)?"is-in":"is-out")},o(y(n.value)),3)]),s("div",Z,[e[4]||(e[4]=s("dt",null,"وضعیت",-1)),s("dd",null,o(n.value.confirmed?"تایید شده":"در انتظار تایید"),1)]),s("div",ss,[e[5]||(e[5]=s("dt",null,"تاریخ",-1)),s("dd",null,o(m(g)(n.value.createdAt)),1)]),s("div",es,[e[6]||(e[6]=s("dt",null,"توضیحات",-1)),s("dd",null,o(n.value.note||"—"),1)])])])]),_:1},8,["visible"])):E("",!0)])}}}),vs=L(ts,[["__scopeId","data-v-83b037a9"]]);export{vs as default}; import{d as k,D as A,o as C,c as i,l as r,q as f,j as D,u as m,e as E,g as u,f as l,F as R,b as s,r as w,I as T,t as o,i as p,s as N,_ as L}from"./index-gciDsUQi.js";import{P}from"./PageBar-uS5upavZ.js";import{a as M}from"./Spinner.vue_vue_type_style_index_0_lang-CkjhJnbV.js";import{S as O}from"./SectionHead-BKOd3BbV.js";import{j as g,p as B}from"./filters-CLL9nGZ2.js";import{a as I}from"./client-WIBW0tF5.js";import{D as U}from"./index-B7DBPshJ.js";import"./_commonjsHelpers-Cpj98o6Y.js";import"./classCallCheck-C6rD-0r0.js";const V={class:"tx"},$={class:"tx__list"},Y=["onClick"],j={class:"tx__main"},z={class:"tx__head"},F={class:"tx__kind"},G={key:0,class:"tx__note"},H={class:"tx__foot"},q={class:"tx__date"},J={key:1,class:"tx__empty"},K={class:"sheet"},Q={class:"sheet__specs"},W={class:"sheet__spec"},X={class:"sheet__spec"},Z={class:"sheet__spec"},ss={class:"sheet__spec"},es={class:"sheet__spec"},ts=k({__name:"TransactionsView",setup(os){const x={ORDER_STOCK:"سفارش",ORDER_PRE_ORDER:"سفارش ساخت",REPAYMENT_MELTED_GOLD:"طلای آب‌شده",REPAYMENT_TRANSFER:"حواله",REPAYMENT_CHEQUE:"چک",REPAYMENT_CASH:"پولی",WALLET_BONUS:"پاداش",ADJUSTMENT:"اصلاحیه"},h=A(),_=u(!1),d=u([]),v=u(!1),n=u(null),c=a=>a.direction==="CREDIT",y=a=>a.currency==="GOLD_GRAM"?`${a.amount} گرم`:`${B(a.amount)} ریال`,S=a=>{n.value=a,v.value=!0};return C(async()=>{if(h.customerId){_.value=!0;try{d.value=await I.getLedgerEntries(h.customerId)}finally{_.value=!1}}}),(a,e)=>{const b=N("container");return l(),i("div",V,[r(P,{title:"اسناد","back-to":"userPage::hasSubRoute"}),r(M,{spinning:_.value,tip:"درحال دریافت اطلاعات"},{default:f(()=>[r(b,{class:"tx__body"},{default:f(()=>[d.value.length?(l(),i(R,{key:0},[r(O,{title:"گردش حساب",subtitle:`${d.value.length} سند`},null,8,["subtitle"]),s("div",$,[(l(!0),i(R,null,w(d.value,t=>(l(),i("article",{key:t.id,class:"tx__row",onClick:ns=>S(t)},[s("span",{class:p(["tx__mark",c(t)?"is-in":"is-out"]),"aria-hidden":"true"},[r(T,{icon:c(t)?"vuesax-linear:arrow-down":"vuesax-linear:arrow-up",size:"16"},null,8,["icon"])],2),s("div",j,[s("div",z,[s("h3",F,o(x[t.sourceType]??t.sourceType),1),s("span",{class:p(["tx__amount",c(t)?"is-in":"is-out"])},o(y(t)),3)]),t.note?(l(),i("p",G,o(t.note),1)):E("",!0),s("div",H,[s("span",q,o(m(g)(t.createdAt)),1),s("span",{class:p(["tx__state",{"is-pending":!t.confirmed}])},o(t.confirmed?"تایید شده":"در انتظار تایید"),3)])])],8,Y))),128))])],64)):_.value?E("",!0):(l(),i("section",J,[r(T,{icon:"vuesax-linear:receipt-text",size:"26"}),e[1]||(e[1]=s("p",null,"سندی برای حساب شما ثبت نشده است.",-1))]))]),_:1})]),_:1},8,["spinning"]),n.value?(l(),D(m(U),{key:0,visible:v.value,"onUpdate:visible":e[0]||(e[0]=t=>v.value=t),placement:"bottom",closable:!1,height:"420px",class:"rtl",bodyStyle:{padding:0}},{default:f(()=>[s("div",K,[e[7]||(e[7]=s("h3",{class:"sheet__title"},"جزئیات سند",-1)),s("dl",Q,[s("div",W,[e[2]||(e[2]=s("dt",null,"نوع سند",-1)),s("dd",null,o(x[n.value.sourceType]??n.value.sourceType),1)]),s("div",X,[e[3]||(e[3]=s("dt",null,"مبلغ",-1)),s("dd",{class:p(c(n.value)?"is-in":"is-out")},o(y(n.value)),3)]),s("div",Z,[e[4]||(e[4]=s("dt",null,"وضعیت",-1)),s("dd",null,o(n.value.confirmed?"تایید شده":"در انتظار تایید"),1)]),s("div",ss,[e[5]||(e[5]=s("dt",null,"تاریخ",-1)),s("dd",null,o(m(g)(n.value.createdAt)),1)]),s("div",es,[e[6]||(e[6]=s("dt",null,"توضیحات",-1)),s("dd",null,o(n.value.note||"—"),1)])])])]),_:1},8,["visible"])):E("",!0)])}}}),vs=L(ts,[["__scopeId","data-v-83b037a9"]]);export{vs as default};
@@ -1 +1 @@
import{g as d,u as F,h as i,m as S}from"./index-CmYEpHK9.js";import{a as P}from"./client-WIBW0tF5.js";function W(t){return!t||t.length===0?null:t.reduce((e,s)=>parseFloat(s.defaultWagePercent)<parseFloat(e.defaultWagePercent)?s:e)}function I(t,e){const s=d(t),r=i(()=>{var n,m;return(m=(n=s.value.images)==null?void 0:n[0])==null?void 0:m.url}),o=i(()=>s.value.id),u=i(()=>s.value.title),l=i(()=>s.value.variants),a=d(e??W(F(l))),v=i(()=>a.value?parseFloat(a.value.rashWeightGrams):0),g=i(()=>a.value?parseFloat(a.value.defaultWagePercent):null),x=d(null),h=i(()=>{var n;return((n=a.value)==null?void 0:n.qty)??0}),f=i(()=>{var n;return((n=a.value)==null?void 0:n.isPreOrder)??!1}),y=i(()=>h.value>0||f.value),_=i(()=>s.value.productCode??null),C=d(!1);return{product:s,image:r,id:o,title:u,productCode:_,variants:l,selectedVariant:a,min_gram:v,wage:g,hasDiscountWage:x,qty:h,isPreOrder:f,is_exist:y,has_warranty:C}}const p="appCart";function q(){try{const t=localStorage.getItem(p);return t?JSON.parse(t):[]}catch{return[]}}function c(t){localStorage.setItem(p,JSON.stringify(t))}const G=S("cart",{state:()=>({items:q()}),actions:{async init(){try{const t=await P.listProducts(),e=[];for(const s of this.items){const r=t.find(u=>u.id===s.pid),o=r==null?void 0:r.variants.find(u=>u.id===s.vid);r&&o&&e.push({...s,product:r})}this.items=e,c(this.items)}catch(t){console.log(t)}},addToCart(t){const e=this.find_index(t);e===-1?this.items.push(t):(this.items[e].product=t.product,this.items[e].qty=t.qty),c(this.items)},remove(t){const e=this.find_index(t);e>-1&&this.items.splice(e,1),c(this.items)},updateFeild(t,e,s,r){if(!this.isInCart(s))return!1;const o=e??this.find_index(s);return this.items[o][r]=t,c(this.items),!0},find_index(t){return this.items.findIndex(e=>e.pid===t.pid&&e.vid===t.vid)},isInCart(t){return this.find_index(t)>-1},variant(t){return t.product.variants.find(e=>e.id===t.vid)},total(){return this.items.reduce((t,e)=>{const s=this.variant(e);return t+(s?parseFloat(s.rashWeightGrams)*e.qty:0)},0).toFixed(2)},clear(){this.items=[],c(this.items)}}});export{G as a,I as u}; import{g as d,u as F,h as i,m as S}from"./index-gciDsUQi.js";import{a as P}from"./client-WIBW0tF5.js";function W(t){return!t||t.length===0?null:t.reduce((e,s)=>parseFloat(s.defaultWagePercent)<parseFloat(e.defaultWagePercent)?s:e)}function I(t,e){const s=d(t),r=i(()=>{var n,m;return(m=(n=s.value.images)==null?void 0:n[0])==null?void 0:m.url}),o=i(()=>s.value.id),u=i(()=>s.value.title),l=i(()=>s.value.variants),a=d(e??W(F(l))),v=i(()=>a.value?parseFloat(a.value.rashWeightGrams):0),g=i(()=>a.value?parseFloat(a.value.defaultWagePercent):null),x=d(null),h=i(()=>{var n;return((n=a.value)==null?void 0:n.qty)??0}),f=i(()=>{var n;return((n=a.value)==null?void 0:n.isPreOrder)??!1}),y=i(()=>h.value>0||f.value),_=i(()=>s.value.productCode??null),C=d(!1);return{product:s,image:r,id:o,title:u,productCode:_,variants:l,selectedVariant:a,min_gram:v,wage:g,hasDiscountWage:x,qty:h,isPreOrder:f,is_exist:y,has_warranty:C}}const p="appCart";function q(){try{const t=localStorage.getItem(p);return t?JSON.parse(t):[]}catch{return[]}}function c(t){localStorage.setItem(p,JSON.stringify(t))}const G=S("cart",{state:()=>({items:q()}),actions:{async init(){try{const t=await P.listProducts(),e=[];for(const s of this.items){const r=t.find(u=>u.id===s.pid),o=r==null?void 0:r.variants.find(u=>u.id===s.vid);r&&o&&e.push({...s,product:r})}this.items=e,c(this.items)}catch(t){console.log(t)}},addToCart(t){const e=this.find_index(t);e===-1?this.items.push(t):(this.items[e].product=t.product,this.items[e].qty=t.qty),c(this.items)},remove(t){const e=this.find_index(t);e>-1&&this.items.splice(e,1),c(this.items)},updateFeild(t,e,s,r){if(!this.isInCart(s))return!1;const o=e??this.find_index(s);return this.items[o][r]=t,c(this.items),!0},find_index(t){return this.items.findIndex(e=>e.pid===t.pid&&e.vid===t.vid)},isInCart(t){return this.find_index(t)>-1},variant(t){return t.product.variants.find(e=>e.id===t.vid)},total(){return this.items.reduce((t,e)=>{const s=this.variant(e);return t+(s?parseFloat(s.rashWeightGrams)*e.qty:0)},0).toFixed(2)},clear(){this.items=[],c(this.items)}}});export{G as a,I as u};
@@ -1 +1 @@
import{c as l}from"./Spinner.vue_vue_type_style_index_0_lang-CvgYqzhC.js";var f=function(n){return setTimeout(n,16)},c=function(n){return clearTimeout(n)};typeof window<"u"&&"requestAnimationFrame"in window&&(f=function(n){return window.requestAnimationFrame(n)},c=function(n){return window.cancelAnimationFrame(n)});var i=0,o=new Map;function u(e){o.delete(e)}function w(e){var n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:1;i+=1;var r=i;function a(t){if(t===0)u(r),e();else{var s=f(function(){a(t-1)});o.set(r,s)}}return a(n),r}w.cancel=function(e){var n=o.get(e);return u(n),c(n)};function m(e,n){for(var r=l({},e),a=0;a<n.length;a+=1){var t=n[a];delete r[t]}return r}function p(e,n,r){return Object.defineProperty(e,"prototype",{writable:!1}),e}function v(e,n){if(!(e instanceof n))throw new TypeError("Cannot call a class as a function")}export{p as _,v as a,m as o,w}; import{c as l}from"./Spinner.vue_vue_type_style_index_0_lang-CkjhJnbV.js";var f=function(n){return setTimeout(n,16)},c=function(n){return clearTimeout(n)};typeof window<"u"&&"requestAnimationFrame"in window&&(f=function(n){return window.requestAnimationFrame(n)},c=function(n){return window.cancelAnimationFrame(n)});var i=0,o=new Map;function u(e){o.delete(e)}function w(e){var n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:1;i+=1;var r=i;function a(t){if(t===0)u(r),e();else{var s=f(function(){a(t-1)});o.set(r,s)}}return a(n),r}w.cancel=function(e){var n=o.get(e);return u(n),c(n)};function m(e,n){for(var r=l({},e),a=0;a<n.length;a+=1){var t=n[a];delete r[t]}return r}function p(e,n,r){return Object.defineProperty(e,"prototype",{writable:!1}),e}function v(e,n){if(!(e instanceof n))throw new TypeError("Cannot call a class as a function")}export{p as _,v as a,m as o,w};
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{a3 as y,d as A,a4 as L,a5 as $,E as p,a6 as w,l as c,g as D,A as N,L as B,a7 as I,p as M,a8 as f,a9 as R,aa as U,ab as Y}from"./index-CmYEpHK9.js";const[C,r,F]=y("pull-refresh"),x=50,G=["pulling","loosing","success"],X={disabled:Boolean,modelValue:Boolean,headHeight:f(x),successText:String,pullingText:String,loosingText:String,loadingText:String,pullDistance:L,successDuration:f(500),animationDuration:f(300)};var j=A({name:C,props:X,emits:["change","refresh","update:modelValue"],setup(s,{emit:d,slots:o}){let u;const g=D(),T=D(),H=$(g),e=N({status:"normal",distance:0,duration:0}),i=U(),P=()=>{if(s.headHeight!==x)return{height:`${s.headHeight}px`}},h=()=>e.status!=="loading"&&e.status!=="success"&&!s.disabled,E=t=>{const a=+(s.pullDistance||s.headHeight);return t>a&&(t<a*2?t=a+(t-a)/2:t=a*1.5+(t-a*2)/4),Math.round(t)},n=(t,a)=>{const l=+(s.pullDistance||s.headHeight);e.distance=t,a?e.status="loading":t===0?e.status="normal":t<l?e.status="pulling":e.status="loosing",d("change",{status:e.status,distance:t})},m=()=>{const{status:t}=e;return t==="normal"?"":s[`${t}Text`]||F(t)},k=()=>{const{status:t,distance:a}=e;if(o[t])return o[t]({distance:a});const l=[];return G.includes(t)&&l.push(c("div",{class:r("text")},[m()])),t==="loading"&&l.push(c(B,{class:r("loading")},{default:m})),l},V=()=>{e.status="success",setTimeout(()=>{n(0)},+s.successDuration)},v=t=>{u=R(H.value)===0,u&&(e.duration=0,i.start(t))},b=t=>{h()&&v(t)},_=t=>{if(h()){u||v(t);const{deltaY:a}=i;i.move(t),u&&a.value>=0&&i.isVertical()&&(I(t),n(E(a.value)))}},S=()=>{u&&i.deltaY.value&&h()&&(e.duration=+s.animationDuration,e.status==="loosing"?(n(+s.headHeight,!0),d("update:modelValue",!0),M(()=>d("refresh"))):n(0))};return p(()=>s.modelValue,t=>{e.duration=+s.animationDuration,t?n(+s.headHeight,!0):o.success||s.successText?V():n(0,!1)}),w("touchmove",_,{target:T}),()=>{var t;const a={transitionDuration:`${e.duration}ms`,transform:e.distance?`translate3d(0,${e.distance}px, 0)`:""};return c("div",{ref:g,class:r()},[c("div",{ref:T,class:r("track"),style:a,onTouchstartPassive:b,onTouchend:S,onTouchcancel:S},[c("div",{class:r("head"),style:P()},[k()]),(t=o.default)==null?void 0:t.call(o)])])}}});const z=Y(j);export{z as P}; import{a3 as y,d as A,a4 as L,a5 as $,E as p,a6 as w,l as c,g as D,A as N,L as B,a7 as I,p as M,a8 as f,a9 as R,aa as U,ab as Y}from"./index-gciDsUQi.js";const[C,r,F]=y("pull-refresh"),x=50,G=["pulling","loosing","success"],X={disabled:Boolean,modelValue:Boolean,headHeight:f(x),successText:String,pullingText:String,loosingText:String,loadingText:String,pullDistance:L,successDuration:f(500),animationDuration:f(300)};var j=A({name:C,props:X,emits:["change","refresh","update:modelValue"],setup(s,{emit:d,slots:o}){let u;const g=D(),T=D(),H=$(g),e=N({status:"normal",distance:0,duration:0}),i=U(),P=()=>{if(s.headHeight!==x)return{height:`${s.headHeight}px`}},h=()=>e.status!=="loading"&&e.status!=="success"&&!s.disabled,E=t=>{const a=+(s.pullDistance||s.headHeight);return t>a&&(t<a*2?t=a+(t-a)/2:t=a*1.5+(t-a*2)/4),Math.round(t)},n=(t,a)=>{const l=+(s.pullDistance||s.headHeight);e.distance=t,a?e.status="loading":t===0?e.status="normal":t<l?e.status="pulling":e.status="loosing",d("change",{status:e.status,distance:t})},m=()=>{const{status:t}=e;return t==="normal"?"":s[`${t}Text`]||F(t)},k=()=>{const{status:t,distance:a}=e;if(o[t])return o[t]({distance:a});const l=[];return G.includes(t)&&l.push(c("div",{class:r("text")},[m()])),t==="loading"&&l.push(c(B,{class:r("loading")},{default:m})),l},V=()=>{e.status="success",setTimeout(()=>{n(0)},+s.successDuration)},v=t=>{u=R(H.value)===0,u&&(e.duration=0,i.start(t))},b=t=>{h()&&v(t)},_=t=>{if(h()){u||v(t);const{deltaY:a}=i;i.move(t),u&&a.value>=0&&i.isVertical()&&(I(t),n(E(a.value)))}},S=()=>{u&&i.deltaY.value&&h()&&(e.duration=+s.animationDuration,e.status==="loosing"?(n(+s.headHeight,!0),d("update:modelValue",!0),M(()=>d("refresh"))):n(0))};return p(()=>s.modelValue,t=>{e.duration=+s.animationDuration,t?n(+s.headHeight,!0):o.success||s.successText?V():n(0,!1)}),w("touchmove",_,{target:T}),()=>{var t;const a={transitionDuration:`${e.duration}ms`,transform:e.distance?`translate3d(0,${e.distance}px, 0)`:""};return c("div",{ref:g,class:r()},[c("div",{ref:T,class:r("track"),style:a,onTouchstartPassive:b,onTouchend:S,onTouchcancel:S},[c("div",{class:r("head"),style:P()},[k()]),(t=o.default)==null?void 0:t.call(o)])])}}});const z=Y(j);export{z as P};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -9,8 +9,8 @@
<meta name="background-color" content="#1C2D56" /> <meta name="background-color" content="#1C2D56" />
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport" /> <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport" />
<title>طلای منتزر</title> <title>طلای منتزر</title>
<script type="module" crossorigin src="/assets/index-CmYEpHK9.js"></script> <script type="module" crossorigin src="/assets/index-gciDsUQi.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-W391hScl.css"> <link rel="stylesheet" crossorigin href="/assets/index-D-jEuKqG.css">
<link rel="manifest" href="/manifest.webmanifest"></head> <link rel="manifest" href="/manifest.webmanifest"></head>
<body style="background-color: #1C2D56"> <body style="background-color: #1C2D56">
<div class="bg-white w-screen h-screen fixed"></div> <div class="bg-white w-screen h-screen fixed"></div>
+1 -1
View File
File diff suppressed because one or more lines are too long
+30 -10
View File
@@ -3,7 +3,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, ref } from 'vue' import { computed, nextTick, ref, watch } from 'vue'
import icons from './icons.json' import icons from './icons.json'
interface Props { interface Props {
@@ -14,17 +14,37 @@ interface Props {
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
color: null, color: null,
}) })
const tempIcon = ref<string>()
const refIcon = ref<HTMLSpanElement>() const refIcon = ref<HTMLSpanElement>()
tempIcon.value = (icons as Record<string, string>)[`${props.icon}`]
onMounted(() => { // FIXED: this used to be a plain ref, assigned once at setup —
if (props.size) { // tempIcon.value = icons[props.icon]
const svg = refIcon.value?.children[0] as SVGElement // — instead of a computed. Vue reuses the same component instance across
svg.setAttribute('width', props.size as string) // re-renders when an `Icon` sits in the same template position (which every
svg.setAttribute('height', props.size as string) // caller in this app does; none pass a `:key` keyed to the icon name), so
} // setup only runs once per mount and that assignment never ran again. Any
}) // caller that swaps `icon` on an already-mounted instance — the exact pattern
// the cart quantity steppers use, `:icon="qty === 1 ? 'trash' : 'minus'"` —
// had its SVG freeze on whichever icon happened to render first and silently
// stop tracking the prop after that: incrementing a cart line past 1 never
// swapped the frozen trash glyph back to minus, and the reverse (decrementing
// back down to 1) never swapped a frozen minus back to trash either. A
// computed re-evaluates on every `icon` change, so the markup — and, via the
// watcher below, the width/height attributes size() sets on it — now actually
// track the prop instead of only reading it once.
const tempIcon = computed(() => (icons as Record<string, string>)[`${props.icon}`])
function applySize() {
if (!props.size) return
const svg = refIcon.value?.children[0] as SVGElement | undefined
svg?.setAttribute('width', props.size as string)
svg?.setAttribute('height', props.size as string)
}
// v-html replaces refIcon's children synchronously with the DOM update, but
// that update itself is queued — nextTick() waits for it so applySize() finds
// the new <svg> already in the DOM rather than the previous one (or none, on
// the very first render).
watch(tempIcon, () => nextTick(applySize), { immediate: true })
</script> </script>
<style scoped> <style scoped>
@@ -29,6 +29,12 @@
</div> </div>
<div class="ci__row"> <div class="ci__row">
<!-- App-wide stepper order rule (also followed by SingleProduct.vue's
.pd__qty): plus on the visual right, minus (or trash at qty 1) on
the visual left. The page is RTL, so a plain flex row lays its
DOM-first child on the right — plus has to come FIRST in markup,
not last, to land there. Keep both steppers' DOM order in sync if
either one changes. -->
<div class="ci__qty"> <div class="ci__qty">
<button type="button" class="ci__qtyBtn" aria-label="افزودن" @click="increment"> <button type="button" class="ci__qtyBtn" aria-label="افزودن" @click="increment">
<Icon icon="vuesax-linear:add" size="16" /> <Icon icon="vuesax-linear:add" size="16" />
@@ -0,0 +1,287 @@
<!--
Swipeable image slider — a from-scratch rewrite of the gallery/preview
carousel that used to live inline in SingleProduct.vue.
WHY A REWRITE, NOT A PATCH: the old slider swiped correctly by feel but
visibly jumped mid-gesture, in both the inline gallery and the full-screen
preview. The root cause was structural, not a tunable number:
- The whole app sets `direction: rtl !important` on #app (see
src/App.vue). Under RTL, a flex row lays its children right-to-left —
slide 0 sits at the visual RIGHT edge, not the left.
- `transform: translate3d(X%, 0, 0)` is direction-agnostic: a positive X
always moves content toward the visual right, in RTL exactly as in LTR.
- The old track combined `translate3d(index * 100%, 0, 0)` with the page's
ambient RTL flex order. Those two systems don't agree on which way
"forward" is, so the settle position after a drag did not line up with
where the flex box had actually placed that slide — the visual result
was the image overshooting and snapping back, i.e. the reported "jump".
THE FIX: this component's slider track is an isolated `direction: ltr`
subtree (see .gs__track below). Once inside that subtree, slide order,
pointer-delta sign and `translateX` sign are all plain, ordinary LTR
arithmetic — index 0 first, positive X moves right, done — regardless of
what direction the rest of the page is in. The component's own controls
(dots, drag gesture) still read as natural "swipe right for previous
image" to an RTL user because the *gesture* is still interpreted in screen
space; only the slider's internal layout is pinned to LTR so its own maths
is self-consistent.
A few other correctness fixes bundled into the same rewrite, since they were
entangled with the same code:
- Pointer Events instead of Touch Events: one code path for touch, mouse
and pen, with proper pointer capture so a drag that leaves the element
(finger sliding off the edge) is still tracked to completion instead of
silently stopping — the old touch-only handlers dropped the gesture the
moment the finger crossed the element boundary.
- Position is settled against the track's OWN measured width
(getBoundingClientRect), not against `%` of an assumed-100%-per-slide
layout — immune to any box-sizing/padding mismatch between the measured
width and the CSS `flex: 0 0 100%` cell width.
- One component, two instances (the inline stage and the full-screen
preview both render a <GallerySlider>) instead of one set of drag state
manually re-entered for two different DOM subtrees via a `source` tag.
They stay in sync through `v-model:index`, a plain reactive prop/emit
binding — not shared mutable drag variables, which is what the "does a
drag started on A leak into B" bug class was ultimately rooted in.
-->
<template>
<div
ref="root"
class="gs"
:class="{ 'is-dragging': dragging }"
@pointerdown="onPointerDown"
@pointermove="onPointerMove"
@pointerup="onPointerUp"
@pointercancel="onPointerCancel"
@click="onClick"
>
<div class="gs__track" :style="trackStyle">
<div v-for="src in slides" :key="src" class="gs__cell">
<img :src="src" :alt="alt" class="gs__img" :class="fitClass" draggable="false" />
</div>
</div>
<div v-if="showCounter && slides.length > 1" class="gs__counter">{{ index + 1 }}/{{ slides.length }}</div>
<slot name="dots" :index="index" :go="go" />
</div>
</template>
<script setup lang="ts">
import { computed, onBeforeUnmount, ref } from 'vue'
const props = withDefaults(
defineProps<{
slides: string[]
alt: string
/** `cover` (the inline stage's default) or `contain` (the preview's). */
fit?: 'cover' | 'contain'
/** Show the built-in "n/N" pill, bottom-start of the slider.
*
* Off by default. FIXED: both call sites in SingleProduct.vue already
* draw their own overlay chrome in that same corner — the inline stage
* has .pd__expand there, and the preview has its own top-centre
* .pd__previewCounter — so this component's counter used to render on
* top of .pd__expand unconditionally, the two pieces of UI visibly
* overlapping into illegible stacked text/icons. Neither caller wants
* this counter today; a future bare usage that does can opt in. */
showCounter?: boolean
}>(),
{ fit: 'cover', showCounter: false },
)
const index = defineModel<number>('index', { default: 0 })
const emit = defineEmits<{
/** A plain tap — press and release without a real drag. Distinct from a
* native `click`, which a touch sequence also fires synthetically on
* release even after a drag; consumers that want "open on tap only"
* should use this instead of listening for `click` themselves. */
(e: 'tap'): void
}>()
const fitClass = computed(() => (props.fit === 'contain' ? 'gs__img--contain' : 'gs__img--cover'))
const root = ref<HTMLElement | null>(null)
// ── Drag state ────────────────────────────────────────────────
// All local to one instance — the inline stage and the full-screen preview
// each mount their own GallerySlider and so each get their own root, dragX,
// dragging, etc. Nothing here is shared across instances; only `index` is,
// via v-model, which is exactly the one thing that SHOULD stay in sync.
const dragging = ref(false)
const dragX = ref(0)
let pointerId: number | null = null
let startX = 0
let trackWidth = 0
// Past this many pixels of travel, the drag counts as a swipe rather than a
// tap — used both to decide whether to commit to the next/previous slide and
// to suppress the `tap` event so a swipe never also opens the preview.
const DRAG_COMMIT_RATIO = 0.18 // fraction of the track's own width
const TAP_MAX_PX = 6
function onPointerDown(e: PointerEvent) {
if (props.slides.length < 2 || pointerId !== null) return
const el = root.value
if (!el) return
pointerId = e.pointerId
startX = e.clientX
trackWidth = el.getBoundingClientRect().width || 1
dragX.value = 0
dragging.value = true
// Pointer capture keeps delivering move/up events to this element even once
// the pointer leaves it — the touch-events version of this component lost
// the gesture the instant a fast swipe crossed the element's edge.
el.setPointerCapture?.(e.pointerId)
}
function onPointerMove(e: PointerEvent) {
if (!dragging.value || e.pointerId !== pointerId) return
dragX.value = e.clientX - startX
}
function endDrag() {
if (!dragging.value) return
const dx = dragX.value
const ratio = dx / trackWidth
dragging.value = false
pointerId = null
dragX.value = 0
// Screen-space drag direction, translated to an index step. This is the one
// place RTL vs. LTR actually matters for *behaviour* (not layout): dragging
// a finger to the left (negative dx) is "next image" regardless of the
// track's own internal LTR layout, because that is what the gesture means
// to the person doing it, in either reading direction.
if (ratio <= -DRAG_COMMIT_RATIO && index.value < props.slides.length - 1) index.value++
else if (ratio >= DRAG_COMMIT_RATIO && index.value > 0) index.value--
if (Math.abs(dx) <= TAP_MAX_PX) emit('tap')
}
function onPointerUp(e: PointerEvent) {
if (e.pointerId !== pointerId) return
endDrag()
}
function onPointerCancel(e: PointerEvent) {
if (e.pointerId !== pointerId) return
// A cancelled gesture (e.g. the OS takes over for a system swipe) snaps back
// to the current slide rather than committing a possibly-partial drag.
dragging.value = false
pointerId = null
dragX.value = 0
}
// A touch sequence still fires a native `click` on release. `onPointerUp`
// already emits `tap` for a real tap and has already run by the time `click`
// fires, so this handler only needs to swallow the click's default double-
// firing — nothing further to do here, but the listener stays as documentation
// that `click` is deliberately NOT used for the tap-to-preview affordance
// (`tap` is) precisely because `click` also fires after a swipe.
function onClick() {
/* intentionally empty — see comment above */
}
function go(i: number) {
index.value = i
}
// ── Track position ───────────────────────────────────────────
// The whole point of isolating this in its own `direction: ltr` subtree (see
// .gs__track in <style>): inside it, "index 0 is first" and "positive X moves
// right" are both just true, with no page-level RTL to reconcile against.
const trackStyle = computed(() => {
const base = -index.value * 100
const dragPercent = trackWidth ? (dragX.value / trackWidth) * 100 : 0
return {
transform: `translate3d(${base + dragPercent}%, 0, 0)`,
transition: dragging.value ? 'none' : undefined,
}
})
onBeforeUnmount(() => {
if (pointerId !== null && root.value) {
try {
root.value.releasePointerCapture(pointerId)
} catch {
/* element may already be detached */
}
}
})
</script>
<style scoped lang="less">
.gs {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
touch-action: pan-y;
user-select: none;
-webkit-user-select: none;
}
// Isolated from the page's `direction: rtl` — see the file header. Everything
// under this node uses plain LTR layout and LTR-signed transforms.
.gs__track {
direction: ltr;
display: flex;
height: 100%;
width: 100%;
transition: transform 0.42s cubic-bezier(0.22, 0.61, 0.36, 1);
will-change: transform;
}
.gs.is-dragging .gs__track {
transition: none;
}
.gs__cell {
flex: 0 0 100%;
width: 100%;
height: 100%;
}
.gs__img {
width: 100%;
height: 100%;
display: block;
-webkit-user-drag: none;
user-select: none;
}
.gs__img--cover {
// The inline stage's own padding/background is supplied by the caller
// (.pd__stage); this component only sets the fit mode.
object-fit: contain;
padding: 46px 20px 20px;
}
.gs__img--contain {
object-fit: contain;
padding: 20px 22px;
}
.gs__counter {
position: absolute;
bottom: 14px;
// Placed with a logical property (so it sits at the trailing edge in the
// page's own RTL reading order) — this is UI chrome layered on top of the
// slider, not part of the isolated LTR track, so it should still follow the
// page's direction like everything else in the app.
inset-inline-start: 14px;
padding: 4px 9px;
border-radius: 999px;
font-size: 10.5px;
font-weight: 700;
color: #fff;
background: rgba(20, 22, 26, 0.45);
backdrop-filter: blur(4px);
direction: ltr;
pointer-events: none;
}
</style>
@@ -69,35 +69,40 @@
<!-- ── Gallery ───────────────────────────────────────── --> <!-- ── Gallery ───────────────────────────────────────── -->
<div class="pd__gallery"> <div class="pd__gallery">
<div <GallerySlider
v-if="hasImages" v-if="hasImages"
v-model:index="imageIndex"
:slides="slides"
:alt="title"
class="pd__stage" class="pd__stage"
@touchstart.passive="onTouchStart" @tap="openPreview"
@touchmove.passive="onTouchMove"
@touchend="onTouchEnd"
> >
<div class="pd__track" :class="{ 'is-dragging': dragging }" :style="{ transform: `translate3d(${trackOffset}, 0, 0)` }"> <template #dots="{ index, go }">
<div v-for="src in slides" :key="src" class="pd__slide"> <!-- The reference's dot rail: vertical, pinned to the trailing
<img :src="src" :alt="title" class="pd__art" /> edge. -->
<div v-if="slides.length > 1" class="pd__rail">
<button
v-for="(s, i) in slides"
:key="s"
type="button"
class="pd__railDot"
:class="{ 'is-on': i === index }"
:aria-label="`تصویر ${i + 1}`"
@click.stop="go(i)"
/>
</div> </div>
</div> </template>
</GallerySlider>
<!-- The reference's dot rail: vertical, pinned to the trailing edge. -->
<div v-if="slides.length > 1" class="pd__rail">
<button
v-for="(s, i) in slides"
:key="s"
type="button"
class="pd__railDot"
:class="{ 'is-on': i === imageIndex }"
:aria-label="`تصویر ${i + 1}`"
@click="imageIndex = i"
/>
</div>
</div>
<div v-else class="pd__stage pd__stage--empty"> <div v-else class="pd__stage pd__stage--empty">
<Icon icon="vuesax-linear:diamonds" size="27" /> <Icon icon="vuesax-linear:diamonds" size="27" />
</div> </div>
<!-- Tap-to-preview affordance, so the full-screen viewer is
discoverable without it being the whole stage's only behaviour
(the stage still swipes in place). -->
<span v-if="hasImages" class="pd__expand" aria-hidden="true">
<Icon icon="vuesax-linear:maximize-4" size="15" />
</span>
</div> </div>
<!-- ── Scrolling content ─────────────────────────────── --> <!-- ── Scrolling content ─────────────────────────────── -->
@@ -198,7 +203,18 @@
it deep-links to its category (a real destination); once it is, it it deep-links to its category (a real destination); once it is, it
becomes the quantity stepper, which has to live somewhere and this becomes the quantity stepper, which has to live somewhere and this
is the slot the layout already gives it. --> is the slot the layout already gives it. -->
<!-- Stepper order rule, the same one CartItem.vue follows: plus on
the visual right, minus (or trash at qty 1) on the visual left.
The page is RTL, so a plain flex row already lays its DOM-first
child on the right — plus has to be FIRST in markup, not last, to
land there. (FIXED: this used to be minus/trash, value, plus —
the reverse of the intended layout, which put plus on the visual
left instead of the right.) -->
<div v-if="inCart" class="pd__qty"> <div v-if="inCart" class="pd__qty">
<button type="button" class="pd__qtyBtn" aria-label="افزایش" @click="increment">
<Icon icon="vuesax-linear:add" size="15" />
</button>
<span class="pd__qtyValue">{{ cartQty }}</span>
<button <button
type="button" type="button"
class="pd__qtyBtn" class="pd__qtyBtn"
@@ -207,10 +223,6 @@
> >
<Icon :icon="cartQty === 1 ? 'vuesax-linear:trash' : 'vuesax-linear:minus'" size="15" /> <Icon :icon="cartQty === 1 ? 'vuesax-linear:trash' : 'vuesax-linear:minus'" size="15" />
</button> </button>
<span class="pd__qtyValue">{{ cartQty }}</span>
<button type="button" class="pd__qtyBtn" aria-label="افزایش" @click="increment">
<Icon icon="vuesax-linear:add" size="15" />
</button>
</div> </div>
<button v-else-if="categoryTitle" type="button" class="pd__pill" @click="goCategory"> <button v-else-if="categoryTitle" type="button" class="pd__pill" @click="goCategory">
<Icon icon="vuesax-linear:category" size="17" /> <Icon icon="vuesax-linear:category" size="17" />
@@ -234,6 +246,54 @@
</div> </div>
</article> </article>
<!-- ── Full-screen preview ───────────────────────────────── -->
<!-- A sibling of .pd, not nested inside it: .pd is the scroll container
(overflow-y: auto), and an element positioned fixed/absolute inside it
would still be clipped by that overflow in the Drawer mount, the same
reason the gallery is sticky rather than fixed. Sitting outside it
avoids the problem entirely — this is a real overlay, not something
that has to stay in the document's scroll flow. -->
<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">
<Icon icon="vuesax-linear:add" size="20" />
</button>
<div v-if="slides.length > 1" class="pd__previewCounter">{{ imageIndex + 1 }} / {{ slides.length }}</div>
<!-- Same slider, a second instance: `v-model:index` is what keeps it
in lockstep with the inline gallery above (both read/write the
one `imageIndex` ref), not shared drag state — each instance
tracks and settles its own gesture independently. -->
<GallerySlider
v-model:index="imageIndex"
:slides="slides"
:alt="title"
class="pd__previewStage"
fit="contain"
/>
<!-- Horizontal thumbnail strip, replacing the old dot row: each
frame is the actual photo, so jumping to a specific image is
one glance-and-tap instead of counting dots to find it. -->
<div v-if="slides.length > 1" class="pd__previewThumbs no-scrollbar">
<button
v-for="(s, i) in slides"
:key="s"
type="button"
class="pd__previewThumb"
:class="{ 'is-on': i === imageIndex }"
:aria-label="`تصویر ${i + 1}`"
@click="imageIndex = i"
>
<img :src="s" :alt="`${title} ${i + 1}`" draggable="false" />
</button>
</div>
</div>
</Transition>
</Teleport>
<NotifyVant :timeOut="3000" v-model="notify" :color="'background-color: #14161A'"> <NotifyVant :timeOut="3000" v-model="notify" :color="'background-color: #14161A'">
<div class="font-bold text-right mb-2">ثبت سفارش</div> <div class="font-bold text-right mb-2">ثبت سفارش</div>
<div>محصول با موفقیت به سبد خرید افزوده شد</div> <div>محصول با موفقیت به سبد خرید افزوده شد</div>
@@ -257,9 +317,10 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, onBeforeMount, ref, watch } from 'vue' import { computed, onBeforeMount, onBeforeUnmount, ref, watch } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import Icon from '../icon/Icon.vue' import Icon from '../icon/Icon.vue'
import GallerySlider from './GallerySlider.vue'
import Spinner from '../Spinner.vue' import Spinner from '../Spinner.vue'
import NotifyVant from '../NotifyVant.vue' import NotifyVant from '../NotifyVant.vue'
import DialogVant from '../DialogVant.vue' import DialogVant from '../DialogVant.vue'
@@ -361,48 +422,30 @@ watch(slides, (list) => {
if (imageIndex.value > list.length - 1) imageIndex.value = 0 if (imageIndex.value > list.length - 1) imageIndex.value = 0
}) })
// ── Gallery swipe ──────────────────────────────────────────── // ── Gallery ───────────────────────────────────────────────────
// The slides are stacked on one line and the track is translated to bring the // The swipe/drag mechanics live in GallerySlider.vue now — see that file's
// active one on screen. The page is RTL, so the line runs right-to-left and // header for why the old inline implementation was rewritten rather than
// slide N sits at +N × 100%: a drag towards the start of the line (negative dx // patched (it jumped mid-gesture because of an RTL/transform sign mismatch).
// in screen coordinates) advances to the next image. // The inline stage and the full-screen preview below each mount their own
// <GallerySlider>; `imageIndex` is the one thing they share, via v-model, so
// picking an image in either place moves both.
// //
// A plain transform slider rather than a carousel library — the app already // GallerySlider distinguishes a tap from a swipe itself and emits `tap` only
// pulls swiper in for the home rails, but this is a few lines of maths over // for the former, so opening the preview needs no drag-jitter bookkeeping here
// three or four images and does not need one. // the way the old inline handlers did.
const dragX = ref(0) const previewOpen = ref(false)
const dragging = ref(false)
let startX = 0
// While dragging, the finger's travel is carried as a raw pixel delta on top of function openPreview() {
// the settled percentage, so the image tracks the thumb instead of snapping. if (!hasImages.value) return
const trackOffset = computed(() => previewOpen.value = true
dragging.value ? `calc(${imageIndex.value * 100}% + ${dragX.value}px)` : `${imageIndex.value * 100}%`, // The preview is teleported to <body> and would otherwise let the page (or
) // the Drawer) scroll behind it.
document.body.style.overflow = 'hidden'
// Past this much travel the swipe commits to the next image; below it the
// track springs back to where it started.
const SWIPE_COMMIT_PX = 48
function onTouchStart(e: TouchEvent) {
if (slides.value.length < 2) return
startX = e.touches[0].clientX
dragX.value = 0
dragging.value = true
} }
function onTouchMove(e: TouchEvent) { function closePreview() {
if (!dragging.value) return previewOpen.value = false
dragX.value = e.touches[0].clientX - startX document.body.style.overflow = ''
}
function onTouchEnd() {
if (!dragging.value) return
const dx = dragX.value
dragging.value = false
dragX.value = 0
if (dx <= -SWIPE_COMMIT_PX && imageIndex.value < slides.value.length - 1) imageIndex.value++
else if (dx >= SWIPE_COMMIT_PX && imageIndex.value > 0) imageIndex.value--
} }
const wageLabel = computed(() => { const wageLabel = computed(() => {
@@ -467,6 +510,13 @@ const goCategory = () => {
onBeforeMount(async () => { onBeforeMount(async () => {
await productDetail.load() 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
// set would otherwise outlive it and leave the rest of the app unscrollable.
onBeforeUnmount(() => {
if (previewOpen.value) document.body.style.overflow = ''
})
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
@@ -656,47 +706,12 @@ onBeforeMount(async () => {
color: @inkFaint; color: @inkFaint;
} }
.pd__track { // The slider's own track/cell/image rules (.gs__track etc.) live in
display: flex; // GallerySlider.vue, which this stage now renders — see that file's header
height: 100%; // for why the swipe mechanics were rewritten there instead of staying inline
// Settles with the same long decelerate the home screen's reveal uses, so // here. .pd__stage only supplies the sizing shell (height, background) that
// the two read as one system. Suppressed mid-drag so the image sits under // the slider fills; .gs__img--cover on that component sets the same padding
// the finger rather than lagging behind it. // and `object-fit: contain` this rule used to.
transition: transform 0.5s cubic-bezier(0.32, 0.72, 0, 1);
will-change: transform;
}
.pd__track.is-dragging {
transition: none;
}
.pd__slide {
flex: 0 0 100%;
height: 100%;
// Each slide sits one full width further along the line; with the track
// translated by +index × 100% the active one lands on screen.
margin-inline-start: -100%;
}
.pd__slide:first-child {
margin-inline-start: 0;
}
.pd__art {
width: 100%;
height: 100%;
// `contain`, not `cover`: a piece of jewellery is the subject of its own
// photo, so cropping it to fill the frame cut the edges off. Every other
// card in the app (ProductCard, ProductPanel, CartItem) uses `contain` for
// the same reason — the whole piece has to be visible.
object-fit: contain;
// Breathing room so the piece does not touch the frame or sit under the
// floating controls at the top.
padding: 46px 20px 20px;
display: block;
// Otherwise a drag on the image starts the browser's own image drag and the
// touch sequence never reaches touchend.
-webkit-user-drag: none;
user-select: none;
}
// Vertical dot rail on the trailing edge, as in the reference. // Vertical dot rail on the trailing edge, as in the reference.
.pd__rail { .pd__rail {
@@ -727,6 +742,196 @@ onBeforeMount(async () => {
background: #fff; background: #fff;
} }
// Small tap-to-preview affordance, bottom-start corner of the stage — clear of
// the trailing dot rail and the leading controls above it.
.pd__expand {
position: absolute;
bottom: 12px;
inset-inline-start: 12px;
width: 30px;
height: 30px;
display: grid;
place-items: center;
border-radius: 10px;
color: #fff;
background: rgba(20, 22, 26, 0.35);
backdrop-filter: blur(6px);
pointer-events: none;
}
/* ── Full-screen preview ─────────────────────────────────── */
// Teleported to <body> — see the template comment on why this sits outside
// .pd rather than nested in it.
.pd__preview {
position: fixed;
inset: 0;
// FIXED: this was 1000, the exact z-index ant-design-vue's Drawer uses for
// its own wrapper (@zindex-modal in its theme, see node_modules/
// ant-design-vue/es/style/themes/default.less). Both are teleported to
// <body> as siblings, so a tie at the same z-index resolves by DOM order —
// fragile and, in the product-card mount (the drawer this component usually
// opens inside), it lost: the Drawer's own content, including .pd__bar at
// its OWN stacking context z-index of 20, painted on top of the preview
// instead of under it — the "چیزی آن پایین به‌هم ریخته" in the screenshot.
// Clearing the modal layer's z-index by a wide margin makes the preview
// unconditionally the topmost layer, regardless of which Teleport happened
// to mount last.
z-index: 2000;
display: flex;
flex-direction: column;
// A dim, blurred veil rather than a near-opaque slab: low-alpha black so
// whatever is behind it (the product photo mid-transition, the page) still
// reads through softly, and `backdrop-filter: blur` on that same layer is
// what actually does the softening — this is the frosted-glass look the
// header bar and the floating gallery controls elsewhere on this page
// already use (see .pd__header.is-solid, .pd__round), not a new material.
background: rgba(10, 10, 12, 0.55);
backdrop-filter: blur(28px) saturate(140%);
}
.pd__previewClose {
position: absolute;
top: calc(14px + env(safe-area-inset-top, 0px));
inset-inline-end: 14px;
z-index: 2;
width: 38px;
height: 38px;
display: grid;
place-items: center;
border: 0;
border-radius: 12px;
cursor: pointer;
color: #fff;
background: rgba(0, 0, 0, 0. );
backdrop-filter: blur(10px);
transform: rotate(45deg);
transition: transform 0.18s ease, background 0.18s ease;
}
.pd__previewClose:active {
transform: rotate(45deg) scale(0.92);
}
// FIXED: this box spanned the full width of the screen (inset-inline: 0) to
// centre its text, and at top:24px/height~18px it vertically overlapped the
// close button's box (top:14px, 38px tall — occupying 14–52px) by ~28px. Nothing
// visually showed it there since the text itself is centred and narrow, but the
// full-width transparent box still intercepted pointer events in that overlap
// band, sitting on top of the close button (same z-index, later in the DOM
// wins the tie) — a tap that looked like it landed on the button actually hit
// this invisible strip instead, which is why the button felt like its hitbox
// had drifted below where it was drawn. Text and decoration only, never a
// click target, so it should never have been able to steal one.
.pd__previewCounter {
position: absolute;
top: calc(24px + env(safe-area-inset-top, 0px));
inset-inline-start: 0;
inset-inline-end: 0;
z-index: 2;
text-align: center;
font-size: 12px;
font-weight: 700;
color: rgba(255, 255, 255, 0.75);
direction: ltr;
pointer-events: none;
}
// Sizing shell for the slider it wraps (GallerySlider needs a sized parent —
// `flex: 1` here, `height: 54vh` for .pd__stage above); the track/cell/image
// rules themselves live in GallerySlider.vue, with `fit="contain"` set in the
// template for this instance.
.pd__previewStage {
flex: 1;
min-height: 0;
}
// Horizontal thumbnail strip, replacing the old dot row (which told you HOW
// MANY images there were and which one you were on, but not what any of them
// actually showed — jumping to a specific photo meant counting dots). Real
// frames of the real photos instead: quicker to scan and to reach with a
// single tap, especially past four or five images where dots alone stop being
// legible at a glance.
.pd__previewThumbs {
flex-shrink: 0;
display: flex;
// Centred, not left- or right-anchored: with few thumbnails (the common
// case — most products have a handful of photos) a flex row's default
// start-alignment left the strip hugging one edge instead of sitting under
// the image it belongs to. `justify-content` only affects extra space when
// the row is short enough not to need it — once there are enough thumbnails
// to overflow, `overflow-x: auto` takes over and this has no effect, so
// scrolling to reach the rest still works exactly as before.
justify-content: center;
gap: 8px;
overflow-x: auto;
scroll-snap-type: x proximity;
padding: 10px 16px calc(18px + env(safe-area-inset-bottom, 0px));
}
.pd__previewThumb {
scroll-snap-align: center;
flex: 0 0 auto;
width: 52px;
height: 52px;
padding: 6px;
border: 0;
border-radius: 12px;
cursor: pointer;
overflow: hidden;
// FIXED: was a near-invisible tint (rgba(255,255,255,0.06)) — against the
// dark preview overlay, a piece shot on a transparent or white background
// (the catalogue photos this strip shows) had no surface to sit on and read
// as floating in the dark with no edges. Solid white gives every thumbnail
// the same real ground the piece was actually photographed against, the way
// ProductCard/ProductPanel's own panels do for the exact same reason.
background: #fff;
// A dim, unselected thumbnail is the resting state; the active one lifts to
// full brightness with a ring, so the strip reads as a filmstrip with one
// frame in focus rather than a flat, undifferentiated row.
opacity: 0.65;
outline: 2px solid transparent;
outline-offset: 2px;
transition: opacity 0.2s ease, outline-color 0.2s ease, transform 0.18s ease;
}
.pd__previewThumb:active {
transform: scale(0.94);
}
.pd__previewThumb.is-on {
opacity: 1;
// Still legible against the thumbnail's own new white background because
// `outline-offset: 2px` above pushes the ring OUTSIDE the box — the 2px gap
// between the white square and the white ring shows the dark preview
// overlay behind it, so the ring reads as floating over dark, not as a
// white-on-white line that would vanish.
outline-color: #fff;
}
.pd__previewThumb img {
width: 100%;
height: 100%;
// `contain`, not `cover`: with the white background above, the whole piece
// should sit fully visible on that ground rather than being cropped to fill
// the square — the same reasoning behind `contain` everywhere else in this
// app that shows a piece of jewellery (GallerySlider's own `cover`/`contain`
// fit is for the full-size stage, where the frame is large enough that
// `cover` doesn't lose meaningful detail; a 52px thumbnail has no such room
// to spare).
object-fit: contain;
display: block;
-webkit-user-drag: none;
user-select: none;
}
// Simple fade + settle for the preview opening/closing, independent of the
// gallery's own slide-swipe transition on .pd__track.
.pd-preview-enter-active,
.pd-preview-leave-active {
transition: opacity 0.24s ease;
}
.pd-preview-enter-from,
.pd-preview-leave-to {
opacity: 0;
}
/* ── Sheet ───────────────────────────────────────────────── */ /* ── Sheet ───────────────────────────────────────────────── */
// Rides over the stuck gallery. A higher stacking level than .pd__gallery is // Rides over the stuck gallery. A higher stacking level than .pd__gallery is
// what lets it cover the image instead of scrolling behind it, and the bottom // what lets it cover the image instead of scrolling behind it, and the bottom