This commit is contained in:
mohadese namavar
2024-06-16 00:22:14 +04:30
commit ec84dfd222
322 changed files with 77942 additions and 0 deletions
+89
View File
@@ -0,0 +1,89 @@
<template>
<div class="buy">
<p>
<span>{{ $t('priceForYou') }}</span>
<small v-if="boxSeller">
{{ $t('everyBox') }}
</small>
</p>
<div>
<span v-if="specialPrice">
{{ numberFormat(specialPrice) }}
<small>{{ $t('priceUnit') }}</small>
</span>
<span>
{{ numberFormat(price) }}
<small v-if="!specialPrice">
{{ $t('priceUnit') }}
</small>
</span>
</div>
<CartQuantity v-if="cart.find(id)" v-model="quantity" :range="orderRange" />
<Button v-else :label="$t('addToCart')" :loading="cart.loading" @click="quantity = orderRange.min" />
</div>
</template>
<script setup>
const { id, orderRange, boxSeller, specialPrice, price } = inject('data')
const cart = useCartStore()
const quantity = computed({
get: () => cart.find(id)?.quantity || 0,
set: (v) => cart.update(id, v)
})
const numberFormat = (number) =>
new Intl.NumberFormat('fa-IR', { maximumSignificantDigits: 3 }).format(number)
</script>
<style lang="scss">
.product .buy {
@apply flex flex-wrap justify-between items-center px-6 pt-1 pb-2.5 lg:gap-6 shadow-[0px_-3px_5px_#00000005];
@apply fixed inset-x-0 bottom-0 h-[6.2rem] bg-white shadow border-t border-[#F1F2F4] lg:h-auto;
@apply z-10 lg:z-0 lg:static lg:bg-transparent lg:shadow-none lg:border-t-0 lg:p-0 lg:gap-y-6 lg:mt-auto;
p {
@apply w-full lg:w-max text-[#7F7F7F] font-medium font-vazir pr-1.5 lg:p-0;
span {
@apply text-xs lg:text-xl leading-[2.1rem];
}
small {
@apply text-[0.7em] lg:text-base leading-[1.9rem];
}
}
&>div:nth-of-type(1) {
@apply flex flex-col justify-end gap-2 max-lg:order-last;
span {
@apply text-left text-[#333] text-sm font-bold lg:text-[1.6em] font-vazir;
}
small {
@apply text-xs font-medium lg:hidden;
}
&:has(span:nth-child(2)) {
span:first-child {
@apply max-lg:-mt-8;
}
span:last-child {
@apply line-through text-xs lg:text-base text-gray-600;
}
}
}
&>button {
@apply w-[10.8rem] h-[2.6rem] lg:w-[25.1rem] lg:h-14 #{!important};
.p-button-label {
@apply text-xs lg:text-xl font-bold font-iran-sans;
}
}
}
</style>
+169
View File
@@ -0,0 +1,169 @@
<template>
<router-link :to="`/product/${uid}`">
<article :class="['product-card', `product-card-${type}`]">
<img :src="coverPath" :alt="title" />
<div>
<h2 v-if="type != 'minimal'">
{{ category[category.length - 1]?.name }}
</h2>
<h1>{{ title }}</h1>
<ul v-if="type != 'minimal' && stars">
<li v-for="i in 5" :key="i">
<img :src="`/icons/star.svg`" :class="starsClass(i)" />
</li>
</ul>
<template v-if="type != 'minimal'">
<Button :icon="`isax isax-heart${like ? '5' : ''}`" severity="secondary" text
@click.prevent="favorite.update(id)" />
</template>
<div v-if="type != 'minimal'">
<div>
<span>
<b>{{ numberFormat(price) }}</b>
<small>{{ $t('priceUnit') }}</small>
</span>
<s v-if="type == 'action' && specialPrice">
{{ numberFormat(specialPrice) }}
</s>
</div>
<ProductQuantity v-if="type == 'action'" v-model="quantity" :range="orderRange" />
</div>
</div>
</article>
</router-link>
</template>
<script setup>
const props = defineProps({
data: { type: Object, default: {} },
type: { type: String, default: 'main' }
})
const { _id: id, uid, coverPath, category = [], title, price, specialPrice, stars, orderRange } = props.data
const favorite = useFavoritesStore()
const like = computed(() => favorite.find(id))
const cart = useCartStore()
const quantity = computed({
get: () => cart.find(id)?.quantity || 0,
set: (v) => cart.update(id, v)
})
const starsClass = (i) => (i > stars ? 'opacity-25' : '')
const numberFormat = (number) =>
new Intl.NumberFormat('fa-IR', { maximumSignificantDigits: 3 }).format(number)
</script>
<style lang="scss">
.product-card {
box-shadow: 0px 2px 10px 0px #87C29A;
@apply bg-white rounded-[0.6rem] border flex flex-col relative;
&>img {
@apply w-[3.8rem] h-[3.8rem] lg:w-[10.3rem] lg:h-[10.3rem] self-center object-contain object-bottom;
}
&>div {
@apply flex flex-col grow;
h2 {
@apply text-[#7F7F7F] text-[0.6em] leading-5 lg:text-sm font-medium font-vazir;
}
h1 {
@apply text-[#333] text-[0.7em] leading-5 lg:text-base font-bold font-vazir mt-1 line-clamp-1 lg:line-clamp-2;
}
ul {
@apply hidden lg:flex gap-1 mt-3 ;
img {
@apply w-4 h-4;
}
}
&>div {
@apply flex justify-between items-center gap-4 mt-auto;
&>div:nth-of-type(1) {
@apply flex flex-col items-end font-vazir;
span {
@apply text-[#333];
small {
@apply text-[0.6em] lg:text-[0.8em] font-medium mr-1;
}
b {
@apply text-sm lg:text-lg;
}
}
s {
@apply mt-1 text-[#7F7F7F] text-[0.7em] lg:text-[0.9em] font-medium;
}
}
}
&>button {
@apply absolute rounded-full w-12 h-12 #{!important};
.p-button-icon {
@apply text-base lg:text-2xl text-[#333];
}
}
}
}
.product-card-main {
@apply w-[9.3rem] h-[11.4rem] p-3 lg:w-[18.25rem] lg:h-[25.4rem] lg:p-6 lg:pb-8;
&>div {
h2 {
@apply mt-6;
}
&>button {
@apply absolute -bottom-1 -left-1 lg:bottom-[1.4rem] lg:left-3.5;
}
}
}
.product-card-action {
@apply w-[12.6rem] h-[16.5rem] lg:w-[18.3rem] lg:h-[29rem] p-3 lg:p-5;
&>img {
@apply max-lg:mt-1.5 max-lg:w-[5.7rem] max-lg:h-[5.7rem];
}
&>div {
h2 {
@apply mt-9;
}
&>div {
@apply flex-row-reverse;
}
&>button {
@apply absolute top-1 left-1 lg:top-2 lg:left-2;
}
}
}
.product-card-minimal {
@apply w-[10.1rem] h-[11.4rem] px-2.5 gap-[1.9rem] items-center;
@apply lg:w-[16.5rem] lg:h-[16.5rem] py-6 lg:p-4 lg:gap-6;
&>div {
h1 {
@apply text-center text-[0.7em] lg:text-base leading-4;
}
}
img {
@apply w-[4.7rem] h-20 lg:w-[8.4rem] lg:h-[8.9rem];
}
}
</style>
+49
View File
@@ -0,0 +1,49 @@
<template>
<section class="description">
<h2>{{ $t('description') }}</h2>
<div>
<div v-html="description" />
</div>
</section>
</template>
<script setup>
const { description } = inject('data')
</script>
<style lang="scss">
.product .description {
@apply w-full flex items-start gap-[13.8rem] px-6;
& > h2 {
@apply hidden lg:flex gap-3 items-center text-primary-600 text-xl font-bold font-vazir;
&::before {
@apply content-['_'] h-6 w-[0.3rem] rounded-full bg-primary-600;
}
}
& > div {
@apply w-full max-h-[17.1rem] lg:w-[57.8rem] lg:max-h-[36.1rem] overflow-y-auto lg:pt-6;
&::-webkit-scrollbar-track {
@apply bg-transparent;
}
&::-webkit-scrollbar {
@apply w-1;
}
&::-webkit-scrollbar-thumb {
@apply bg-[#E5E5E5];
}
div * {
@apply font-vazir;
all: initial;
// @apply text-[#595959] text-xs lg:text-xl font-normal font-vazir lg:leading-9 pl-4 lg:pl-8;
}
}
}
</style>
+78
View File
@@ -0,0 +1,78 @@
<template>
<div class="gallery">
<div>
<ul>
<li v-for="(act, key) in actions" :key="key">
<Button v-bind="act.props" @click="act.click()" />
</li>
</ul>
<img :src="coverImage" />
</div>
<ul>
<li v-for="(image, i) in images" :key="i">
<i class="isax isax-gallery-slash" />
<Image :src="image" preview />
</li>
</ul>
</div>
</template>
<script setup>
const { actions } = inject('init')
const { coverImage, images } = inject('data')
</script>
<style lang="scss">
.product .gallery {
@apply w-full h-full max-h-[38.4rem] flex flex-col gap-6 ;
&>div {
@apply flex p-6 w-full grow max-h-[29.9rem] border border-[#E5E5E5] overflow-hidden;
&>ul {
@apply flex flex-col gap-0 -m-3 relative;
li {
@apply ltr;
i {
@apply absolute inset-0;
}
button {
@apply w-12 h-12 #{!important};
span {
@apply text-2xl text-[#333333];
}
}
}
}
&>img {
@apply flex w-full h-full max-w-[24.1rem] max-h-[24.1rem] object-contain;
@apply shrink mt-5 mx-auto 2xl:translate-x-3 px-8 object-top;
}
}
&>ul {
@apply flex gap-3 flex-wrap h-28 overflow-hidden;
li {
@apply w-28 h-28 flex items-center justify-center relative bg-[#F2F2F2] cursor-pointer;
span {
@apply w-full h-full absolute;
img {
@apply w-full h-full object-cover;
}
}
i {
@apply text-2xl absolute;
}
}
}
}
</style>
+59
View File
@@ -0,0 +1,59 @@
<template>
<div class="product-card-quantity">
<p v-if="count < 0">{{ $t('unavailable') }}</p>
<template v-else-if="count == 0">
<Button :label="$t('buy')" icon="isax isax-bag-2" iconPos="right" text @click.prevent="count++" />
</template>
<template v-else>
<Button text icon="isax isax-add" @click.prevent="count++" :disabled="count >= range.max" />
<p>{{ count }}</p>
<Button text icon="isax isax-minus" @click.prevent="count--" :disabled="count <= range.min" />
</template>
</div>
</template>
<script setup>
defineProps({
range: { type: Object, default: { min: 1, max: 1000 } }
})
const count = defineModel()
</script>
<style lang="scss">
.product-card-quantity {
@apply flex items-center justify-between w-[4.8rem] h-[1.9rem] lg:w-[5.8rem] lg:h-10 rounded-[1.8rem] border border-primary-600 overflow-hidden;
p {
@apply flex w-max items-center text-primary-600 text-[0.7em] lg:text-[1.1em] font-medium font-vazir mx-auto;
}
&:not(:has(button)) {
@apply border-[#7F7F7F];
p {
@apply text-[#7F7F7F];
}
}
button {
@apply rounded-full font-normal font-iran-sans text-[0.7em] lg:text-[1.1em] #{!important};
.isax {
@apply text-xl;
}
}
button:not(:has(.isax-bag-2)) {
@apply w-7 h-7 first:mr-1 last:ml-1 #{!important};
}
button:has(.isax-bag-2) {
@apply w-full h-full #{!important};
}
button[disabled]{
@apply opacity-20;
}
}
</style>
+204
View File
@@ -0,0 +1,204 @@
<template>
<div class="seller">
<div>
<h2>{{ $t('seller') }}</h2>
<template v-if="device == 'mobile'">
<Button :label="$t('otherSellersCount', { count: 3 })" text @click="handleClick" />
</template>
</div>
<ul>
<li v-for="(item, i) in items.slice(0, 3)" :key="i">
<i :class="['isax', `isax-${item.icon}`]" />
<span>{{ item.title }}</span>
</li>
<li v-show="shopID?.rate >= 0">
<small>{{ shopID.rate * 10 }}%</small>
<span>{{ $t('satisfaction') }}</span>
<span>{{ $t('performance') }} {{ shopID?.performance }}</span>
</li>
<li v-show="boxSeller">
<i class="isax isax-d-square"></i>
<span>{{ $t('boxShopping') }}</span>
<span>{{ $t('minBuyBox', { minBuy }) }}</span>
</li>
<li v-show="boxSeller">
<div><i /></div>
<span>{{ $t('itemInBox', { itemInBox }) }}</span>
</li>
<li>
<div>
<template v-for="({ primary }, i) in items.slice(3)" :key="i">
<i :class="primary ? 'bg-primary-600' : 'bg-neutral-200'" />
</template>
</div>
<ul>
<li v-for="(item, i) in items.slice(3)" :key="i">
<i :class="`isax isax-${item.icon}`" />
<span>{{ item.title }}</span>
</li>
</ul>
</li>
</ul>
<ProductBuy />
</div>
</template>
<script setup>
const { dialog, device, t } = inject('service')
const { shopID, warranty, boxSeller, localSend, minBuy, itemInBox } = inject('data')
const items = reactive([
{ title: shopID.shopName, icon: 'shop' },
{ title: warranty, icon: 'shield-tick' },
{ title: t('availableSellerWarehouse'), icon: 'box-tick' },
{ title: t('sendFromLocal'), icon: 'box', primary: localSend },
{ title: t('sendFromSeller'), icon: 'profile-circle', primary: !localSend }
])
const handleClick = () => {
dialog.show(resolveComponent('ProductOtherSellers'))
}
</script>
<style lang="scss">
.product .seller {
@apply w-full lg:w-[28.1rem] lg:h-[42rem] bg-[#FAFAFA] lg:rounded-[0.6rem] lg:border border-neutral-200;
@apply flex flex-col gap-6 lg:gap-7 px-6 py-5 lg:py-6 max-lg:order-4;
&>div:not([class]) {
@apply flex justify-between items-center;
h2 {
@apply text-[#191919] text-base lg:text-2xl font-bold font-vazir;
}
button {
@apply rtl p-0 h-max #{!important};
.p-button-label {
@apply text-[0.7em] text-primary-600;
}
}
}
&>ul {
@apply flex flex-col gap-[1.1rem] lg:gap-4;
&>li {
@apply flex items-center;
&:nth-of-type(-n + 3) {
@apply gap-3 lg:gap-4;
i {
@apply text-[1.4em] lg:text-2xl;
}
span {
@apply text-[#333] text-sm lg:text-xl font-medium font-vazir;
}
}
&:nth-of-type(2) {
@apply order-5 py-[1.2rem] lg:py-4 border-y border-[#E5E5E5];
}
&:nth-of-type(3) {
@apply order-6;
}
&:nth-of-type(4) {
@apply gap-1 lg:pr-10 font-vazir order-2 pb-[1.1rem] lg:pb-4 border-b border-[#E5E5E5];
small {
@apply text-[#019907] text-xs lg:text-base;
}
span {
@apply text-[#999999] text-xs lg:text-base;
&:first-of-type {
@apply pl-2.5 relative;
&::after {
@apply content-['_'] w-px bg-[#D9D9D9] h-4 absolute left-0.5 top-1/2 -translate-y-1/2;
}
}
}
}
&:nth-of-type(5) {
@apply gap-4 font-vazir order-3;
i {
@apply text-[#019907] text-[1.4em] lg:text-2xl;
}
span {
@apply text-[#019907] text-xs lg:text-lg font-medium;
&:first-of-type {
@apply pl-2.5 -ml-2 relative;
&::after {
@apply content-['_'] w-px bg-[#D9D9D9] h-4 absolute left-0 top-1/2 -translate-y-1/2;
}
}
}
}
&:nth-of-type(6) {
@apply gap-4 font-vazir order-4;
div {
@apply w-2 lg:w-2.5 h-5 lg:h-6 mx-[0.4rem] -mt-2.5 lg:-mt-3.5 relative flex justify-center;
i {
@apply w-2 h-2 lg:w-2.5 lg:h-2.5 rounded-full mt-auto bg-[#019907] z-0;
}
&::before {
@apply content-['_'] absolute w-0.5 inset-y-0 bg-neutral-200;
}
}
span {
@apply text-[#019907] text-xs lg:text-lg font-medium;
}
}
&:nth-of-type(7) {
@apply gap-6 lg:gap-4 order-7 pb-4 lg:border-b border-[#E5E5E5];
div {
@apply w-2.5 h-[4.3rem] lg:mx-[0.4rem] pt-4 lg:pt-3.5 -mt-2 lg:-mt-3.5 flex flex-col items-center justify-between relative;
i {
@apply w-2.5 h-2.5 rounded-full z-0;
}
&::before {
@apply content-['_'] absolute w-0.5 inset-y-0 bg-neutral-200;
}
}
ul {
@apply flex flex-col mt-2 gap-6 lg:gap-5 font-vazir;
li {
@apply flex gap-2 items-center h-4 lg:h-[1.6rem];
i {
@apply text-xl text-primary-600;
}
span {
@apply text-[#999] text-[0.7em] lg:text-base font-normal;
}
}
}
}
}
}
}
</style>
+52
View File
@@ -0,0 +1,52 @@
<template>
<section v-if="similars.length > 0" class="similars">
<h2>{{ $t('productSimilars') }}</h2>
<div>
<template v-if="device == 'desktop'">
<Button v-for="(btn, i) in arrows" :key="i" v-bind="btn" />
</template>
<Swiper v-bind="swiper.similars">
<SwiperSlide v-for="(item, i) in similars" :key="i">
<ProductCard :data="item" type="action" />
</SwiperSlide>
</Swiper>
</div>
</section>
</template>
<script setup>
const { swiper, arrows } = inject('init')
const { device } = inject('service')
const { similars } = inject('data')
</script>
<style lang="scss">
.product .similars {
@apply w-full flex flex-col items-center overflow-hidden;
&>h2 {
@apply text-[#0B1F11] text-lg lg:text-3xl font-bold font-vazir;
}
&>div {
@apply max-w-full w-full flex gap-10 h-max mt-6 lg:my-16 relative;
.swiper {
@apply w-full container h-max py-4 px-6;
.swiper-wrapper {
@apply w-max h-max;
.swiper-slide {
@apply w-max ml-[0.9rem] last:ml-0;
}
}
}
&>button {
@apply w-10 h-10 self-center absolute text-xl p-0 bg-[#B2B2B2] border-none #{!important};
@apply first-of-type:-right-16 last-of-type:-left-16 max-lg:hidden;
}
}
}
</style>
+49
View File
@@ -0,0 +1,49 @@
<template>
<section class="specification">
<h2>{{ $t('specifications') }}</h2>
<ul>
<li v-for="(item, key) in specs" :key="key">
<label>{{ key }}</label>
<span>{{ item }}</span>
</li>
</ul>
</section>
</template>
<script setup>
const { specs } = inject('data')
</script>
<style lang="scss">
.product .specification {
@apply w-full flex items-start gap-[13.6rem] px-6;
h2 {
@apply hidden lg:flex gap-3 items-center text-primary-600 text-xl font-bold font-vazir;
&::before {
@apply content-['_'] h-6 w-[0.3rem] rounded-full bg-primary-600;
}
}
& > ul {
@apply w-full lg:w-max flex flex-col;
li {
@apply flex justify-between lg:justify-start gap-12 border-b-2 py-[1.1rem] lg:py-6;
label {
@apply lg:w-[20.2rem] flex items-center gap-3 text-[#7F7F7F] text-xs lg:text-lg font-medium font-vazir;
&::before {
@apply content-['_'] w-2 h-2 bg-[#7F7F7F] rounded-full;
}
}
span {
@apply text-[#333333] text-xs lg:text-lg font-vazir;
}
}
}
}
</style>
+93
View File
@@ -0,0 +1,93 @@
<template>
<div class="summary">
<h1>{{ title }}</h1>
<h2>
<span>{{ titleEN }}</span>
<hr />
</h2>
<div>
<i class="isax isax-star-15" />
<span>{{ stars }}</span>
<a href="#comments">
{{ comments.length }} {{ $t('viewpoint') }}
</a>
</div>
<h3>{{ $t('keyFeatures') }}</h3>
<ul>
<li v-for="(value, key) in shortDesc" :key="key">
<label>{{ key }}:</label>
<span>{{ value }}</span>
</li>
</ul>
</div>
</template>
<script setup>
const { title, titleEN, stars, comments, shortDesc } = inject('data')
</script>
<style lang="scss">
.product>.wrapper>.summary {
@apply w-full lg:w-auto lg:min-w-[17.5rem] grow px-6 lg:px-0 lg:max-w-[47.9rem] lg:h-[29.9rem] flex flex-col mt-9 lg:mt-0 lg:mr-6 max-lg:order-2;
h1 {
@apply text-[#191919] font-bold font-vazir text-base lg:text-2xl;
}
h2 {
@apply flex items-center gap-3 mt-3 lg:mt-4 text-sm lg:text-base;
span {
@apply text-[#B2B2B2] font-vazir;
}
hr {
@apply hidden lg:block h-px lg:h-0.5 grow bg-[#E5E5E5];
}
}
&>div {
@apply flex items-center gap-1 font-vazir mt-1.5 lg:mt-2.5;
i {
@apply text-xl text-[#FFB628];
}
span {
@apply flex items-center gap-4 ml-4 text-[#191919] text-base lg:text-xl;
&::after {
@apply content-['_'] w-1.5 h-1.5 bg-neutral-200 rounded-full;
}
}
a {
@apply text-primary-500 text-base lg:text-xl;
}
}
h3 {
@apply text-zinc-900 font-bold font-vazir mt-12 text-base lg:text-2xl;
}
ul {
@apply flex flex-col gap-[1.1rem] lg:gap-7 mt-6 lg:mt-8;
li {
@apply flex items-center gap-3 font-vazir;
label {
@apply flex items-center gap-3 text-[#7F7F7F] text-xs lg:text-lg font-medium;
&::before {
@apply content-['_'] w-2 h-2 bg-[#7F7F7F] rounded-full;
}
}
span {
@apply text-[#333] text-xs lg:text-lg;
}
}
}
}
</style>
+76
View File
@@ -0,0 +1,76 @@
<template>
<section class="wrapper">
<Swiper v-if="device == 'mobile'" v-bind="swiper.gallery">
<SwiperSlide v-for="(image, i) in product.images" :key="i">
<img :src="image" />
</SwiperSlide>
</Swiper>
<div>
<Button v-bind="btns.report.props" @click="handleClick()" />
<ProductGallery v-if="device == 'desktop'" />
</div>
<ProductSummary />
<ProductSeller />
</section>
</template>
<script setup>
const { swiper, btns } = inject('init')
const { device, dialog } = inject('service')
const product = inject('data')
const handleClick = () => {
dialog.show(resolveComponent('ProductDialogReport'), product)
}
</script>
<style lang="scss">
.product>.wrapper {
@apply w-full flex max-lg:flex-col gap-3 lg:gap-[1.6rem];
.swiper {
@apply w-screen h-max pb-4 max-lg:order-1;
.swiper-wrapper {
@apply w-max z-0;
.swiper-slide {
@apply h-max w-screen;
img {
@apply w-[11.8rem] h-[12.5rem] lg:w-[15.6rem] lg:h-[15.6rem] mx-auto;
}
}
}
.swiper-pagination {
@apply pl-5 left-3 translate-x-0 lg:hidden;
.swiper-pagination-bullet {
@apply bg-[#D9D9D9] opacity-100;
}
* {
@apply mx-0.5;
}
}
}
&>div:not([class]) {
@apply flex max-lg:flex-col max-lg:order-3 gap-y-3 max-w-[38rem] grow;
&>button {
@apply w-max h-10 lg:h-14 shrink-0 max-lg:flex-row-reverse self-end #{!important};
@apply lg:-ml-[14.2rem] lg:-mb-2;
.p-button-icon {
@apply text-[1.1em] lg:text-2xl max-lg:ml-0 max-lg:mr-2 #{!important};
}
.p-button-label {
@apply text-[#7F7F7F] font-vazir font-medium text-[0.7em] lg:text-base;
}
}
}
}
</style>
+47
View File
@@ -0,0 +1,47 @@
<template>
<div class="product-comment-item">
<p>{{ content }}</p>
<div>
<span>
{{
new Date(createdAt).toLocaleDateString('fa', {
month: 'long', year: 'numeric', day: 'numeric'
})
}}
</span>
<span>{{ userID?.firstName }} {{ userID?.lastName }}</span>
</div>
</div>
</template>
<script setup>
const props = defineProps(['data'])
const { createdAt, userID, content } = props.data
</script>
<style lang="scss">
.product-comment-card {
@apply w-[16.9rem] h-[13.1rem] rounded-xl border p-[1.1rem] flex flex-col justify-between;
p {
@apply w-full text-[#7F7F7F] font-vazir text-xs leading-5;
}
&>div {
@apply flex gap-1.5 items-center;
span:nth-child(1) {
@apply text-[#B2B2B2] font-vazir text-[0.7em];
}
span:nth-child(2) {
@apply relative flex items-center gap-1.5;
&::before {
@apply content-['_'] w-1.5 h-1.5 bg-neutral-200 rounded-full;
}
}
}
}
</style>
+66
View File
@@ -0,0 +1,66 @@
<template>
<div class="product-comment-item">
<div>
<span>{{ point }}</span>
<span>{{ role || $t('buyer') }}</span>
<span>
{{
new Date(createdAt).toLocaleDateString('fa', {
month: 'long', year: 'numeric', day: 'numeric'
})
}}
</span>
<span>{{ userID?.firstName }} {{ userID?.lastName }}</span>
</div>
<p>{{ content }}</p>
</div>
</template>
<script setup>
const props = defineProps(['data'])
const { point, role, createdAt, userID, content } = props.data
</script>
<style lang="scss">
.product-comment-item {
@apply w-full max-w-[47.9rem] xl:h-[8.3rem] flex flex-col border-b border-[#E5E5E5];
@apply pb-4 lg:pb-8;
@apply gap-4 lg:gap-6;
div {
@apply flex gap-4 items-center;
span:nth-child(-n + 2) {
@apply flex items-center justify-center pt-0.5 rounded font-iran-sans;
@apply text-sm md:text-base xl:text-[1.1em];
@apply h-7 lg:h-[2.2rem];
@apply px-3 lg:px-4;
}
span:nth-child(1) {
@apply bg-[#019907] text-white;
}
span:nth-child(2) {
@apply bg-[#F2F2F2] text-[#999999] -mr-2;
}
span:nth-child(n + 3) {
@apply text-[#B2B2B2] font-vazir;
@apply text-[0.7em] lg:text-lg;
}
span:nth-child(4) {
@apply relative flex items-center gap-1.5 lg:gap-4;
&::before {
@apply content-['_'] w-1.5 h-1.5 bg-neutral-200 rounded-full;
}
}
}
p {
@apply w-full text-[#7F7F7F] font-vazir text-lg leading-[2.3rem];
}
}
</style>
+58
View File
@@ -0,0 +1,58 @@
<template>
<div class="sort">
<h3>
<i class="isax isax-document-filter"></i>
<span>{{ $t('sortBy') }}:</span>
</h3>
<ul>
<li v-for="(btn, key) in btns.sort" :key="key">
<Button v-bind="btn.props" @click="sort(key)" :active="sorted == key" />
</li>
</ul>
</div>
</template>
<script setup>
const { btns } = inject('init')
let { comments } = inject('data')
const sorted = ref('createdAt')
const sort = (key) => {
sorted.value = key
comments = comments.sort((a, b) => {
return key == 'createdAt' ?
new Date(b.createdAt) - new Date(a.createdAt) :
b.point - a.point
})
}
</script>
<style lang="scss">
.product .comments .sort {
@apply flex gap-4;
h3 {
@apply flex items-center gap-2 text-[#333333] ml-9;
i {
@apply text-2xl;
}
span {
@apply text-lg font-medium font-iran-sans;
}
}
ul {
@apply flex;
button {
@apply h-10 py-1.5 font-iran-sans font-medium text-lg text-[#B2B2B2] #{!important};
&[active=true] {
@apply text-primary-600 #{!important};
}
}
}
}
</style>
+56
View File
@@ -0,0 +1,56 @@
<template>
<div class="summary">
<h2>{{ $t('buyersComments') }}</h2>
<div>
<i class="isax isax-star-15"></i>
<bdi>5 / {{ product.stars }}</bdi>
</div>
<p>{{ $t('buyersCommentsDesc') }}</p>
<Button v-bind="btns.submit.props" @click="handleClick" />
</div>
</template>
<script setup>
const { btns } = inject('init')
const product = inject('data')
const { dialog } = inject('service')
const handleClick = () => {
dialog.show(resolveComponent('ProductCommentDialogForm'), product)
}
</script>
<style lang="scss">
.product .comments>.summary {
@apply flex w-[18.3rem] flex-col gap-4;
h2 {
@apply flex gap-3 items-center text-primary-600 text-xl font-bold font-vazir;
&::before {
@apply content-['_'] h-6 w-[0.3rem] rounded-full bg-primary-600;
}
}
div {
@apply flex gap-2 items-center mt-6;
i {
@apply text-xl text-[#FFB628];
}
bdi {
@apply text-[#191919] text-xl font-normal font-vazir;
}
}
p {
@apply text-[#999999] font-vazir;
}
button {
@apply h-[3.4rem] text-xl font-medium font-iran-sans #{!important};
}
}
</style>
+42
View File
@@ -0,0 +1,42 @@
<template>
<section class="tab-comments">
<Button :label="$t('commentsCount', { count: 4 })" text @click="handleClick" />
<Swiper>
<SwiperSlide v-for="(item, i) in items" :key="i">
<ProductCommentCard :data="item" />
</SwiperSlide>
</Swiper>
</section>
</template>
<script setup>
</script>
<style lang="scss">
.product .tab-comments {
@apply flex flex-col gap-1 items-start;
&>button {
@apply mr-3 #{!important};
.p-button-label {
@apply text-[#47B556];
}
}
.swiper {
@apply w-full h-[13.1rem] px-6;
.swiper-wrapper {
@apply w-max z-0;
.swiper-slide {
@apply h-max w-max ml-2.5 last:ml-0;
}
}
}
}
</style>
+43
View File
@@ -0,0 +1,43 @@
<template>
<section class="comments">
<ProductCommentSummary />
<div>
<ProductCommentSort />
<div>
<ul>
<li v-for="(comment, i) in comments" :key="i">
<ProductCommentItem :data="comment" />
</li>
</ul>
</div>
</div>
</section>
</template>
<script setup>
const { comments } = inject('data')
</script>
<style lang="scss">
.product .comments {
@apply w-full flex gap-[11.4rem] lg:px-6;
&>div:not([class]) {
@apply flex flex-col gap-3 lg:gap-8 overflow-hidden grow;
&>button {
@apply lg:hidden w-10 h-5 p-0 mr-6;
.p-button-label {
@apply text-[0.7em] rtl text-primary-600;
}
}
&>div:nth-of-type(2) {
&>ul {
@apply flex lg:flex-col gap-9;
}
}
}
}
</style>
+234
View File
@@ -0,0 +1,234 @@
<template>
<div class="product-comment-form">
<div>
<div>
<h2>{{ $t('yourComment') }}</h2>
<Button v-bind="closeBtn.props" @click="dialog.close()" />
</div>
<p>{{ $t('aboutTheProdcut', { title }) }}</p>
</div>
<div>
<ul>
<li v-for="(item, key) in fields" :key="key">
<component :is="item.is" ref="chips" :id="key" v-bind="item.props" v-model="form[key]" />
<label :for="key">{{ item.label }}</label>
<template v-if="['positive', 'negative'].includes(key)">
<Button icon="isax isax-add" severity="secondary" text @click="addChip(i)" />
</template>
<small v-if="errors[key]">{{ errors[key] }}</small>
</li>
<li>
<label> {{ $t('yourScore') }} : </label>
<ul>
<li v-for="i in 5" :key="i" @click="form.point = i">
<img src="/icons/star.svg" :class="starsClass(i)" />
</li>
</ul>
<small v-if="errors.point">{{ errors.point }}</small>
</li>
<li>
<label>{{ $t('description') }} :</label>
<Textarea v-model="form.content" :placeholder="$t('writeDesc')" />
<small v-if="errors.content">{{ errors.content }}</small>
</li>
</ul>
</div>
<Button :label="$t('submitComment')" :loading="loading" @click="submit()" />
</div>
</template>
<script setup>
import init from '../../../../initialize/productCommentForm.js'
const { closeBtn, fields } = init()
const { toast, t } = inject('service')
const dialog = inject('dialogRef')
const { title, id } = dialog.value.data
const chips = ref()
const form = reactive({})
const errors = ref({});
const loading = ref(false);
const submit = async () => {
if (Object.keys(errors.value).length < 1) {
loading.value = true
const { status, error } = await useFetch(`/api/comments/${id}`, {
headers: { Authorization: useCookie('token') }, method: 'post', body: form
})
if (status.value == 'success') {
toast.add({
life: 3000, severity: 'success', summary: t('success'), detail: t('yourCommentSummited')
})
dialog.value.close()
} else {
if (error.value.statusCode == 422)
{errors.value = error.value.data}
else
toast.add({
life: 3000, severity: 'error', summary: t('error'), detail: error.value.message
})
}
loading.value = false
}
}
const addChip = (i) => {
chips.value[i].addItem(new Event('keydown'), chips.value[i].$data.inputValue, true)
}
const starsClass = (i) => (i > (form?.point || 0) ? 'opacity-25' : '')
watch(computed(() => form), (value, old) => {
Object.keys(errors.value).forEach(key => {
if (value[key] != old[key]) delete errors.value[key]
});
})
</script>
<style lang="scss">
.product-comment-form {
@apply w-full lg:w-[40.1rem] lg:h-[48.6rem] bg-white rounded-t-[0.6rem] lg:rounded-[0.6rem] flex flex-col relative;
&::before {
@apply content-['_'] absolute inset-x-0 bottom-[4.5rem] h-3 shadow-[0px_-2px_5px_#0000000a] lg:shadow-none z-0;
}
&>div:nth-of-type(1) {
@apply flex flex-col gap-1.5 lg:gap-2 p-6 lg:px-10 lg:py-8 lg:border-b-2 border-[#E5E5E5];
&>div {
@apply flex justify-between items-center;
h2 {
@apply text-zinc-800 text-sm lg:text-xl font-bold font-vazir;
}
button {
@apply -mx-4 -my-3;
.p-button-icon {
@apply text-[1.4em] lg:text-3xl text-[#191919];
}
}
}
p {
@apply text-[#7F7F7F] text-[0.7em] lg:text-lg font-vazir;
}
}
&>div:nth-of-type(2) {
@apply lg:h-[29.9rem] grow p-6 lg:py-8 lg:px-10 lg:overflow-y-auto;
&>ul {
@apply w-full flex flex-col items-center gap-[1.9rem] lg:gap-6 lg:py-1.5 lg:pr-14 lg:pl-16;
&>li:nth-of-type(-n + 3) {
@apply w-[21.4rem] lg:w-[27.1rem] lg:min-h-[3.5rem] relative mt-1.5 lg:mt-3 flex items-center [&_button]:first:hidden;
button {
@apply text-2xl absolute left-0 top-0 text-[#7F7F7F];
}
label {
@apply w-max absolute right-3 top-4 lg:top-3.5 px-1 bg-white transition-['top'];
@apply text-neutral-400 text-sm lg:text-base font-medium font-vazir cursor-text;
@apply pointer-events-none;
}
&>input {
@apply h-[3.4rem] pl-10 rtl w-full rounded-[0.6rem] border shadow-none;
@apply text-zinc-800 font-vazir;
}
.p-chips {
@apply w-full max-w-full rounded-[0.6rem];
ul {
@apply pl-8 w-full h-max grow flex-row-reverse;
input {
@apply w-full rtl text-zinc-800 font-vazir;
}
.p-chips-token {
@apply ltr my-1;
}
}
}
&:has(input:focus),
&:has(.p-chips-token-label),
&:has(input:not(:placeholder-shown)) {
label {
@apply -top-2 h-4;
@apply text-zinc-800 text-xs font-normal font-vazir;
}
}
small{
@apply absolute -bottom-6 left-0 ;
}
}
&>li:nth-of-type(4) {
@apply w-[21.4rem] lg:w-[27.1rem] flex items-center gap-4 lg:mt-2;
label {
@apply text-[#4C4C4C] text-sm lg:text-xl font-medium font-vazir;
}
ul {
@apply flex gap-1.5;
img {
@apply w-[1.1rem] h-[1.1rem] lg:w-6 lg:h-6 cursor-pointer;
}
}
}
&>li:nth-of-type(5) {
@apply flex flex-col gap-4 relative;
label {
@apply text-[#4C4C4C] text-sm lg:text-xl font-medium font-vazir;
}
textarea {
@apply w-[21.4rem] h-[12.1rem] lg:w-[27.1rem] lg:h-[12.1rem] p-4 rounded-[0.6rem] border border-[#E5E5E5];
&::placeholder {
@apply text-[#999999] text-xs lg:text-lg font-normal font-vazir;
}
}
}
&>li::after {
content: attr(error);
@apply text-red-500 text-sm absolute -bottom-6 left-0;
}
small {
@apply text-red-500 font-vazir mr-auto;
}
li:has(small) * {
@apply border-red-500 #{!important};
}
}
}
&>button {
@apply w-[21.4rem] lg:w-[35.1rem] h-[2.9rem] my-6 self-center lg:h-14 lg:m-10 #{!important};
.p-button-label {
@apply font-bold font-vazir leading-tight text-sm lg:text-base;
}
}
}
</style>
@@ -0,0 +1,68 @@
<template>
<div class="product-comments">
<div>
<Button icon="isax isax-arrow-right-1" link @click="$emit('close')" />
<h2>{{ $t("commentsCount", { count: 5 }) }}</h2>
<Button icon="isax isax-document-filter" link @click="handleClick" />
</div>
<div>
<ul>
<li v-for="(item, i) in items" :key="i">
<ProductCommentItem :data="item" />
</li>
</ul>
</div>
<Button :label="$t('submitComment')" icon="isax isax-message" rounded @click="handleClick" />
</div>
</template>
<script setup>
const items = reactive([{}, {}, {}, {}, {}, {}, {}, {}, {}]);
</script>
<style lang="scss">
.product-comments {
@apply w-full bg-white rounded-t-[0.6rem] flex flex-col gap-10 relative px-6 pt-6 overflow-hidden;
&>div:nth-of-type(1) {
@apply w-full flex items-center gap-3;
&>h2 {
@apply text-[#333333] text-sm font-medium font-vazir ml-auto;
}
&>button {
@apply -mx-4 -my-3;
.p-button-icon {
@apply text-[#333333] text-[1.4em];
}
}
}
&>div:nth-of-type(2) {
@apply h-full overflow-y-auto;
&::-webkit-scrollbar {
@apply hidden;
}
&>ul {
@apply h-max flex flex-col gap-6;
}
}
&>button {
@apply absolute left-6 bottom-5 w-[8.3rem] h-12 px-[1.4rem] #{!important};
.p-button-label {
@apply text-xs font-vazir;
}
.p-button-icon {
@apply text-xl;
}
}
}
</style>
@@ -0,0 +1,62 @@
<template>
<div class="product-comments-sort">
<div>
<Button icon="pi pi-times" link @click="$emit('close')" />
<h2>{{ $t("sortBy") }}</h2>
</div>
<ul>
<li v-for="(item, key) in items" :key="key">
<h3 @click="events.select(key)" v-ripple>
{{ item.title }}
</h3>
<i v-if="item.selected" class="pi pi-check"></i>
</li>
</ul>
</div>
</template>
<script setup>
const { t } = useI18n();
const items = reactive({
newest: { title: t("newest"), selected: true },
mostUseful: { title: t("mostUseful") },
});
const events = reactive({
select: (key) => {
items.newest.selected = false;
items.mostUseful.selected = false;
items[key].selected = true;
},
});
</script>
<style lang="scss">
.product-comments-sort {
@apply w-full flex flex-col gap-9 p-6 bg-white rounded-t-[0.6rem];
&>div {
@apply flex items-center gap-3;
&>button {
@apply -mx-4 -my-3 text-[#333333] #{!important};
}
&>h2 {
@apply text-[#333333] text-sm font-medium font-vazir;
}
}
&>ul {
@apply w-full flex flex-col gap-4;
&>li {
@apply flex items-center justify-between pb-4 first:border-b text-[#333333];
h3 {
@apply text-xs font-medium font-vazir;
}
}
}
}
</style>
+105
View File
@@ -0,0 +1,105 @@
<template>
<div class="product-chart">
<div>
<h2>{{ $t('productPriceChangesChart') }}</h2>
<Button v-bind="btns.close.props" @click="dialog.close()" />
</div>
<div>
<p>{{ $t('priceFilterFrom') }}</p>
<AppCalendar v-model="date.from" class="isax isax-note5" />
<p>{{ $t('until') }}</p>
<AppCalendar v-model="date.to" class="isax isax-note5" />
</div>
<Chart :data="chartData" :options="chartOptions" type="line" />
</div>
</template>
<script setup>
import Chart from 'primevue/chart';
const { meta } = useRoute()
const { btns, chart } = meta.init
const dialog = inject('dialogRef')
const { pastPrices } = dialog.value.data
const date = reactive({})
const chartData = ref()
const chartOptions = ref()
onBeforeMount(() => {
try {
date.from = new Date(pastPrices[0].date).toLocaleDateString('fa')
date.to = new Date(pastPrices[pastPrices.length - 1].date).toLocaleDateString('fa')
} catch (error) { }
})
onMounted(() => {
chartData.value = chart.setChartData(pastPrices, date)
chartOptions.value = chart.setChartOptions()
})
watch(
date,
() => chartData.value = chart.setChartData(pastPrices, date),
{ deep: true }
)
</script>
<style lang="scss">
.product-chart {
@apply w-full lg:w-[64rem] lg:h-[43.1rem] bg-white rounded-t-[0.6rem] lg:rounded-[0.6rem] flex flex-col lg:pb-[4.9rem];
&>div:nth-of-type(1) {
@apply flex justify-between items-center gap-3 p-6 lg:p-8 pb-6 lg:border-b-2 border-[#E5E5E5];
h2 {
@apply text-zinc-800 text-sm lg:text-xl font-bold font-vazir;
}
button {
@apply -mx-4 -my-3;
.p-button-icon {
@apply text-[1.4em] lg:text-3xl text-[#191919];
}
}
}
&>div:nth-of-type(2) {
@apply flex flex-wrap items-center gap-[1.1rem] p-6 lg:gap-6 lg:px-8 lg:py-10;
&>p {
@apply text-[#333333] first:w-full lg:first:w-max text-sm lg:text-lg font-medium font-vazir;
}
.p-calendar {
@apply flex items-center gap-3 p-3 font-['iconsax'] #{!important};
@apply w-32 h-11 lg:w-[10.4rem] lg:h-12 rounded-lg border-2 border-[#E5E5E5];
&::before {
@apply text-xl lg:text-2xl order-1;
}
&::after {
@apply content-['_'] w-[0.1rem] h-4 bg-[#E5E5E5] order-2;
}
input {
@apply border-none text-left bg-transparent shadow-none w-[6rem] order-3 p-0;
@apply text-zinc-800 text-xs lg:text-lg font-normal font-vazir;
}
}
}
.p-chart {
@apply w-[22.9rem] h-[21.2rem] lg:w-[59.3rem] lg:h-[24.3rem];
}
}
body:has(.product-chart) {
.p-datepicker {
@apply lg:-translate-x-28;
}
}
</style>
+42
View File
@@ -0,0 +1,42 @@
<template>
<div class="product-menu">
<ul>
<li v-for="({ icon, label, click }, i) in meta.init.menu" :key="i" >
<div @click="click" v-ripple>
<i :class="icon" />
<span>{{ label }}</span>
</div>
</li>
</ul>
</div>
</template>
<script setup>
const { meta } = useRoute()
</script>
<style lang="scss">
.product-menu {
@apply w-full flex flex-col px-6 py-[1.1rem] rounded-t-[0.6rem] bg-white rtl;
ul {
@apply w-full flex flex-col gap-4;
li {
@apply border-[#E5E5E5] border-b last:border-none;
div {
@apply flex items-center gap-4 pb-4 text-[#333333] relative overflow-hidden;
i {
@apply text-xl;
}
h2 {
@apply text-xs font-medium font-vazir;
}
}
}
}
}
</style>
+104
View File
@@ -0,0 +1,104 @@
<template>
<div class="product-reminder">
<div>
<div>
<h2>{{ $t('notices') }}</h2>
<Button v-bind="btns.close.props" @click="dialog.close()" />
</div>
<p>{{ $t('howToInformAboutAmazingPrice') }}</p>
</div>
<ul>
<li v-for="key in checkboxes.reminder" :key="key">
<Checkbox v-model="form[key]" binary :input-id="key" />
<label :for="key"> {{ $t(key + 'Notice') }} </label>
</li>
</ul>
<Button :label="$t('submit')" :loading="loading" @click="submit()" />
</div>
</template>
<script setup>
const { meta } = useRoute()
const { btns, checkboxes } = meta.init
const { toast, t } = inject('service')
const dialog = inject('dialogRef')
const { reminder, _id: id } = dialog.value.data
const form = reactive(reminder[0] || {})
const store = useProductsStore()
const loading = ref(false)
const submit = async () => {
loading.value = true
const { status, data } = await store.addReminder(id, form)
loading.value = false
if (status.value == 'success') {
dialog.value.data.reminder = data.value.reminder
toast.add({
life: 3000, severity: 'success', summary: t('success'), detail: t('submitedSuccessfully')
})
dialog.value.close()
}
}
</script>
<style lang="scss">
.product-reminder {
@apply w-full lg:w-[40.3rem] lg:h-[25.2rem] bg-white rounded-t-[0.6rem] lg:rounded-[0.6rem] flex flex-col gap-8 lg:gap-10 p-6 lg:p-10 lg:pt-8 relative;
&::after {
@apply max-lg:content-['_'] absolute inset-x-0 bottom-[4.5rem] h-3 shadow-[0px_-2px_5px_#0000000a];
}
&>div:nth-of-type(1) {
@apply flex flex-col gap-1.5 lg:gap-2;
&>div {
@apply flex justify-between items-center;
h2 {
@apply text-zinc-800 text-sm lg:text-xl font-bold font-vazir;
}
button {
@apply -mx-4 -my-3;
.p-button-icon {
@apply text-[1.4em] lg:text-3xl text-[#191919];
}
}
}
p {
@apply text-[#7F7F7F] text-[0.7em] lg:text-lg font-normal font-vazir;
}
}
ul {
@apply flex flex-col gap-[1.1rem] lg:gap-6 max-lg:mt-1;
li {
@apply flex items-center gap-4 cursor-pointer w-max select-none;
.p-checkbox,
.p-checkbox-box {
@apply w-4 lg:w-6 h-4 lg:h-6;
}
label {
@apply text-[#4C4C4C] text-xs lg:text-xl font-medium font-vazir cursor-pointer;
}
}
}
&>button {
@apply h-[2.9rem] lg:h-14 #{!important};
.p-button-label {
@apply font-bold font-iran-sans leading-tight text-sm lg:text-base;
}
}
}
</style>
+124
View File
@@ -0,0 +1,124 @@
<template>
<div class="product-report">
<div>
<div>
<h2>{{ $t("reportIncorrectSpecs") }}</h2>
<Button v-bind="btns.close.props" @click="dialog.close()" />
</div>
<p>{{ title }}</p>
</div>
<ul>
<li v-for="key in checkboxes.report" :key="key">
<Checkbox v-model="form.items[key]" binary :input-id="key" />
<label :for="key">{{ $t(key + "Product") }}</label>
</li>
<li>
<label>{{ $t("description") }}</label>
<Textarea v-model="form.content" :placeholder="$t('writeDesc')" />
</li>
</ul>
<Button :label="$t('send')" :loading="loading" @click="send()" />
</div>
</template>
<script setup>
const { meta } = useRoute()
const { btns, checkboxes } = meta.init
const { toast, t } = inject('service')
const dialog = inject('dialogRef')
const { title, id, report } = dialog.value.data
const form = reactive(report ?? { items: {}, content: '' });
const store = useProductsStore();
const loading = ref(false)
const send = async () => {
if (Object.keys(form.items).length > 0 && form.content.length > 0) {
loading.value = true
const { status, data } = await store.addReport(id, form);
loading.value = false
if (status.value == 'success') {
toast.add({
life: 3000, severity: 'success', summary: t('success'), detail: t('submitedSuccessfully')
})
dialog.value.close()
}
}
}
</script>
<style lang="scss">
.product-report {
@apply w-full lg:w-[40.1rem] lg:h-[48.6rem] bg-white rounded-t-[0.6rem] lg:rounded-[0.6rem] flex flex-col relative;
&::after {
@apply max-lg:content-['_'] absolute inset-x-0 bottom-[4.5rem] h-3 shadow-[0px_-2px_5px_#0000000a];
}
&>div:nth-of-type(1) {
@apply flex flex-col gap-1.5 lg:gap-2 border-b lg:border-b-2 border-[#E5E5E5] p-6 lg:px-10 lg:pt-8 pb-6;
&>div {
@apply flex justify-between items-center;
h2 {
@apply text-zinc-800 text-sm lg:text-xl font-bold font-vazir;
}
button {
@apply -mx-4 -my-3;
.p-button-icon {
@apply text-[1.4em] lg:text-3xl text-[#191919];
}
}
}
p {
@apply text-[#7F7F7F] text-[0.7em] lg:text-lg font-normal font-vazir;
}
}
ul {
@apply flex flex-col gap-[1.1rem] px-6 pt-9 lg:gap-5 lg:pt-6 lg:px-10;
li {
@apply flex items-center gap-4 cursor-pointer w-max select-none;
.p-checkbox,
.p-checkbox-box {
@apply w-4 h-4 lg:w-6 lg:h-6;
}
label {
@apply text-[#4C4C4C] text-xs lg:text-xl font-medium font-vazir cursor-pointer;
}
}
li:last-child {
@apply w-full lg:w-[35.1rem] flex flex-col items-start gap-4 mt-3 lg:mt-4;
label {
@apply text-[#7F7F7F] text-xs lg:text-xl font-medium font-vazir max-lg:mr-2.5;
}
textarea {
@apply w-full h-[9.6rem] lg:w-[35.1rem] lg:h-[9.6rem] p-4 rounded-[0.6rem] border border-[#E5E5E5];
&::placeholder {
@apply text-[#999999] text-[0.7em] lg:text-lg font-normal font-vazir;
}
}
}
}
&>button {
@apply h-[2.9rem] m-6 mt-8 lg:h-14 lg:m-10 lg:mt-9 #{!important};
.p-button-label {
@apply font-bold font-iran-sans text-sm lg:text-base;
}
}
}
</style>
+102
View File
@@ -0,0 +1,102 @@
<template>
<div class="product-share">
<div>
<div>
<h2>{{ $t('share') }}</h2>
<Button v-bind="btns.close.props" @click="dialog.close()" />
</div>
<p>{{ $t('shareProductWhitFriends') }}</p>
</div>
<div>
<template v-for="(btn, key) in btns.shares" :key="key">
<Button v-bind="btn.props" @click="events[key]()" />
</template>
</div>
</div>
</template>
<script setup>
const { meta } = useRoute()
const { btns } = meta.init
const { toast, t } = inject('service')
const dialog = inject('dialogRef')
const events = reactive({
copyLink: () => {
navigator.clipboard.writeText(location.href).then(function () {
toast.add({ life: 3000, summary: t('success'), detail: t('copied'), severity: 'success' })
})
dialog.value.close()
},
telegram: () => {
window.open('tg://msg_url?url=' + location.href, '_blank')
dialog.value.close()
},
whatsapp: () => {
window.open('whatsapp://send?text=' + location.href, '_blank')
dialog.value.close()
}
})
</script>
<style lang="scss">
.product-share {
@apply w-full lg:w-[40.1rem] lg:h-[19.9rem] bg-white rounded-t-[0.6rem] lg:rounded-[0.6rem] flex flex-col justify-between;
&>div:nth-of-type(1) {
@apply flex flex-col gap-1.5 lg:gap-2 p-6 lg:px-10 lg:pb-2 lg:pt-8;
&>div {
@apply flex justify-between items-center;
h2 {
@apply text-zinc-800 text-sm lg:text-xl font-bold font-vazir;
}
button {
@apply -mx-4 -my-3;
.p-button-icon {
@apply text-[1.4em] lg:text-3xl text-[#B2B2B2];
}
}
}
p {
@apply text-[#7F7F7F] text-[0.7em] lg:text-lg font-normal font-vazir;
}
}
&>div:nth-of-type(2) {
@apply flex flex-wrap gap-x-3 gap-y-[1.1rem] lg:gap-y-6 px-6 pb-5 pt-3 lg:p-10;
button {
@apply grow justify-center h-12 lg:h-14;
.p-button-icon {
@apply text-xl lg:text-[1.4em] ml-3;
}
.p-button-label {
@apply text-xs lg:text-xl font-medium font-iran-sans grow-0;
}
}
button:nth-of-type(1) {
@apply w-full border-2 border-[#B2B2B2] text-[#333333];
.p-button-icon {
@apply text-xl lg:text-2xl;
}
}
button:nth-of-type(2) {
@apply bg-[#43E97B] hover:bg-[#467ca7] border-[#43E97B];
}
button:nth-of-type(3) {
@apply bg-[#34AD39] hover:bg-[#30a034] border-[#34AD39];
}
}
}
</style>
+133
View File
@@ -0,0 +1,133 @@
<template>
<div class="other-seller-item">
<div>
<div>
<i class="isax isax-shop"></i>
<h3>{{ title }}</h3>
</div>
<p>
<small>{{ percent }}%</small>
<span>{{ $t("satisfaction") }}</span>
<span>{{ $t("performance") }} {{ $t(performance) }}</span>
</p>
</div>
<div>
<p v-for="(item, i) in items" :key="i">
<i :class="item.icon"></i>
<span>{{ item.label }}</span>
</p>
</div>
<div>
<Button :label="$t('show')" size="small" />
<span>
<b>{{ price }}</b>
<small>{{ $t("priceUnit") }}</small>
</span>
</div>
</div>
</template>
<script setup>
const props = defineProps({
title: { type: String, default: "فروشگاه اسپیس" },
percent: { type: Number, default: 50 },
performance: { type: String, default: "veryGood" },
shippingDesc: {
type: String,
default: "ارسال از فروشنده پس از تایید نهایی 1-2 روز دیگر",
},
warranty: { type: String, default: "گارانتی 12 ماهه اسپیس" },
price: { type: String, default: "580,000" },
});
const items = reactive([
{ icon: "isax isax-box", label: props.shippingDesc },
{ icon: "isax isax-shield-tick", label: props.warranty },
]);
</script>
<style lang="scss">
.other-seller-item {
@apply w-full flex flex-col gap-4 justify-center relative;
@apply lg:flex-row xl:gap-8 lg:justify-between lg:items-center lg:bg-neutral-50 lg:rounded-[10px];
@apply lg:h-28 lg:px-4 xl:px-6;
& > div:nth-of-type(1) {
@apply flex flex-col gap-3 pb-4 border-b lg:border-b-0 xl:pr-4 lg:pb-0;
& > div {
@apply flex gap-3 lg:gap-4 items-center;
i {
@apply text-xl lg:text-2xl;
}
h3 {
@apply text-zinc-800 text-xs lg:text-xl font-medium font-vazir;
}
}
p {
@apply flex flex-wrap whitespace-nowrap items-center gap-1 xl:mr-10 font-vazir;
small {
@apply text-[#019907] text-[11.20px] lg:text-base;
}
span {
@apply text-right text-[#999999] text-[11.20px] lg:text-base;
&:first-of-type {
@apply pl-3 lg:pl-4 relative;
&::after {
@apply content-['_'] w-px bg-[#D9D9D9] h-4 absolute left-0.5 lg:left-1 top-1/2 -translate-y-1/2;
}
}
}
}
}
& > div:nth-of-type(2) {
@apply flex flex-col 2xl:flex-row gap-4 2xl:gap-40;
& > p {
@apply flex items-center gap-2 pb-4 border-b lg:border-b-0 lg:pb-0;
i {
@apply text-xl;
}
span {
@apply text-[#999] font-medium font-vazir text-[11.20px] lg:text-base;
}
}
}
& > div:nth-of-type(3) {
@apply h-max flex justify-between items-center pb-[30px] border-b lg:border-b-0;
@apply lg:flex-row-reverse lg:gap-4 xl:gap-6 2xl:gap-8 lg:pb-0;
& > span {
@apply font-vazir text-[#4C4C4C];
b {
@apply text-xs lg:text-[25px] font-medium;
}
small {
@apply text-xs lg:text-xl mr-1;
}
}
button {
@apply w-[147px] h-[41px] lg:w-[153px] lg:h-11 #{!important};
span {
@apply font-iran-sans text-sm lg:text-lg font-medium;
}
}
}
}
</style>
+30
View File
@@ -0,0 +1,30 @@
<template>
<section class="product-other-sellers">
<h2>{{ $t("otherSellers") }}</h2>
<ul>
<li v-for="(item, i) in items" :key="i">
<OtherSellerItem />
</li>
</ul>
</section>
</template>
<script setup>
defineProps({
items: { type: Array, default: [{}, {}, {}] },
});
</script>
<style lang="scss">
.product-other-sellers {
@apply w-full flex flex-col gap-16 px-6;
& > h2 {
@apply text-[#0B1F11] text-3xl font-bold font-vazir;
}
& > ul {
@apply w-full flex flex-col gap-6;
}
}
</style>