init git
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<DataTable :value="muchSell" class="seller-best-selling" :responsiveLayout="rl">
|
||||
<template #header>
|
||||
<h2>{{ $t('bestsellingPorducts') }}</h2>
|
||||
<Dropdown v-model="period" :options="options" optionValue="value" optionLabel="label">
|
||||
<template #dropdownicon>
|
||||
<i class="isax isax-arrow-bottom" />
|
||||
</template>
|
||||
</Dropdown>
|
||||
</template>
|
||||
<Column v-for="(col, i) in columns[device]" :key="i" v-bind="col"
|
||||
class="!text-center [&:first-child_button]:hidden">
|
||||
<template #body="{ data, field }">
|
||||
<img v-if="device == 'desktop' && i === 1" :src="field(data)" alt="">
|
||||
<template v-else>
|
||||
<span>{{ field(data) }}</span>
|
||||
<Button v-if="device == 'mobile'" :label="$t('moreDetails')" icon="isax isax-arrow-left-2" link @click="show(data)"/>
|
||||
</template>
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<template #empty>
|
||||
<p>{{ $t('notFound') }}</p>
|
||||
</template>
|
||||
</DataTable>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import PanelSellerDialogDetailsBestSelling from './dialog/details/BestSelling.vue'
|
||||
|
||||
const { device, dialog, t } = inject('service')
|
||||
const rl = computed(() => device.value == 'mobile' ? 'stack' : '')
|
||||
|
||||
const { muchSell = [] } = inject('data')
|
||||
|
||||
const columns = reactive({
|
||||
desktop: [
|
||||
{ header: t('row'), field: (data) => muchSell.indexOf(data) + 1 },
|
||||
{ header: t('image'), field: (data) => data.product.coverImage },
|
||||
{ header: t('productTitle'), field: (data) => data.product.title },
|
||||
{ header: t('salesPrice'), field: (data) => numberFormat(data.total) + ' ' + t('priceUnit') },
|
||||
{ header: t('inventory'), field: (data) => data.quantity },
|
||||
],
|
||||
mobile: [
|
||||
{ field: (data) => data.product.title },
|
||||
{ field: (data) => numberFormat(data.total) + ' ' + t('priceUnit') },
|
||||
]
|
||||
})
|
||||
|
||||
const options = reactive([
|
||||
{ label: t('thisWeek'), value: 'weekly' }
|
||||
])
|
||||
const period = ref('weekly')
|
||||
|
||||
const show = (data) => {
|
||||
dialog.show(PanelSellerDialogDetailsBestSelling, data)
|
||||
}
|
||||
|
||||
const numberFormat = (number) =>
|
||||
new Intl.NumberFormat('fa-IR', { maximumSignificantDigits: 3 }).format(number)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.seller-best-selling {
|
||||
@apply rtl border-2 border-[#E5E5E5] rounded-[0.6rem] overflow-hidden w-full;
|
||||
|
||||
.p-datatable-header {
|
||||
@apply flex items-center justify-between bg-white pt-3 pl-3 pr-[1.1rem] pb-[1.1rem] lg:py-2.5 lg:px-5;
|
||||
|
||||
h2 {
|
||||
@apply text-[#333333] text-sm lg:text-lg font-medium font-vazir;
|
||||
}
|
||||
|
||||
.p-dropdown {
|
||||
@apply w-[5.6rem] h-[2.4rem] lg:w-[6.5rem] lg:h-[2.6rem] bg-[#F2F2F2] rounded-[0.6rem] text-[#5D5D5D] border-none;
|
||||
|
||||
.p-dropdown-trigger {
|
||||
@apply w-max translate-x-2;
|
||||
|
||||
i {
|
||||
@apply text-base lg:text-lg;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
span {
|
||||
@apply p-3 h-max w-max pl-0 font-vazir text-xs lg:text-sm #{!important};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.p-datatable-wrapper {
|
||||
@apply max-lg:overflow-hidden #{!important};
|
||||
|
||||
.p-datatable-table {
|
||||
@apply border-separate border-spacing-y-[0.8rem] px-[1.1rem] py-3;
|
||||
|
||||
.p-datatable-thead {
|
||||
@apply w-full h-[3.1rem] lg:px-5 max-lg:hidden;
|
||||
|
||||
th {
|
||||
@apply text-[#5D5D5D] text-base font-normal font-vazir bg-[#F2F2F2];
|
||||
@apply first:rounded-r-[0.6rem] last:rounded-l-[0.6rem];
|
||||
}
|
||||
}
|
||||
|
||||
.p-datatable-tbody {
|
||||
@apply lg:translate-y-1.5;
|
||||
|
||||
tr {
|
||||
@apply w-full rounded-[0.6rem] bg-transparent shadow-[0px_0px_6px_#0000001f];
|
||||
|
||||
td {
|
||||
@apply max-lg:w-full max-lg:px-3 text-right lg:text-center;
|
||||
@apply lg:first:rounded-r-[0.6rem] lg:last:rounded-l-[0.6rem] py-0 lg:h-[4.5rem];
|
||||
|
||||
&>span {
|
||||
@apply max-lg:py-3 min-h-[3.8rem] flex items-center text-xs lg:text-base text-[#333333] font-vazir;
|
||||
}
|
||||
|
||||
img {
|
||||
@apply w-[3.75rem] h-[3.75rem];
|
||||
}
|
||||
|
||||
@media(max-width: 1024px) {
|
||||
&>button {
|
||||
@apply -m-3 mr-auto #{!important};
|
||||
|
||||
.p-button-label {
|
||||
@apply text-xs font-medium font-vazir whitespace-nowrap #{!important};
|
||||
}
|
||||
|
||||
.p-button-icon {
|
||||
@apply text-sm mr-1;
|
||||
}
|
||||
}
|
||||
|
||||
&:first-child span {
|
||||
@apply w-full border-b py-3;
|
||||
}
|
||||
|
||||
.p-column-title {
|
||||
@apply hidden #{!important};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.p-datatable-emptymessage p {
|
||||
@apply lg:h-[3.8rem] flex justify-center items-center
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div class="seller-management">
|
||||
<h2>{{ $t(title + 'Mangement') }}</h2>
|
||||
<ul>
|
||||
<li v-for="(item, i) in items[title]" :key="i">
|
||||
<small>{{ item.label }}</small>
|
||||
<span>{{ item.value }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps(['title'])
|
||||
|
||||
const {
|
||||
itemHighStock, zeroStock, returnedProducts, itemLowStock,
|
||||
oneWeekAgo, oneDayAgo, pendingProducts = [], receivedProducts,
|
||||
} = inject('data')
|
||||
|
||||
const items = reactive({
|
||||
warehouse: [
|
||||
{ label: 'کالاهای موجود', value: itemHighStock },
|
||||
{ label: 'کالاهای بدون موجودی', value: zeroStock },
|
||||
{ label: 'کالاهای مرجوعی یک هفته گذشته', value: returnedProducts },
|
||||
{ label: 'کالاهای در حال اتمام موجودی', value: itemLowStock },
|
||||
],
|
||||
orders: [
|
||||
{ label: 'سفارشات این هفته', value: oneWeekAgo },
|
||||
{ label: 'سفارش های امروز', value: oneDayAgo },
|
||||
{ label: 'سفارش های پذیرش نشده', value: pendingProducts.length },
|
||||
{ label: 'سفارش های ارسال شده', value: receivedProducts },
|
||||
]
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.seller-management {
|
||||
@apply w-full md:w-[21.4rem] lg:w-[28.1rem] flex flex-col pt-[1.1rem] gap-[1.1rem] lg:pt-5 lg:gap-5 rounded-[0.6rem] border-2 border-neutral-200;
|
||||
|
||||
h2 {
|
||||
@apply text-[#333333] text-sm lg:text-lg font-medium font-vazir pr-[1.1rem] lg:pr-5;
|
||||
}
|
||||
|
||||
ul {
|
||||
@apply w-full flex flex-col gap-6 lg:gap-[1.9rem] p-[1.1rem] lg:pt-6 border-t border-[#E5E5E5];
|
||||
|
||||
li {
|
||||
@apply flex justify-between items-center text-[#333333] font-medium font-vazir;
|
||||
|
||||
small {
|
||||
@apply text-xs lg:text-base;
|
||||
}
|
||||
|
||||
span {
|
||||
@apply text-sm lg:text-xl;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<ul class="seller-menu">
|
||||
<li v-for="(item, i) in items" :key="i">
|
||||
<router-link :to="item.link" v-ripple>
|
||||
<i :class="item.icon" />
|
||||
<span>{{ item.label }}</span>
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
const items = reactive([
|
||||
{ link: '/panel/seller/store', label: 'فروشگاه', icon: 'isax isax-shop' },
|
||||
{ link: '/panel/seller/products', label: 'همه کالاها', icon: 'isax isax-bag' },
|
||||
{ link: '/panel/seller/new-product', label: 'معرفی کالا', icon: 'isax isax-bag-cross' },
|
||||
{ link: '/panel/seller/orders', label: 'سفارشات', icon: 'isax isax-bag-tick-2' },
|
||||
])
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.seller-menu {
|
||||
@apply flex flex-col gap-[0.3rem] pt-4 border-t border-[#E5E5E5];
|
||||
|
||||
a {
|
||||
@apply w-72 h-[3.2rem] py-1.5 pr-[1.1rem] flex items-center gap-[0.9rem] text-[#333333] overflow-hidden relative;
|
||||
|
||||
&.router-link-active {
|
||||
@apply bg-[#47B556]/20 text-[#47B556];
|
||||
|
||||
&:after {
|
||||
@apply content-[''] absolute right-0 h-[2.4rem] w-[0.2rem] rounded-tl-sm rounded-bl-sm bg-[#47B556];
|
||||
}
|
||||
}
|
||||
|
||||
i {
|
||||
@apply text-2xl;
|
||||
}
|
||||
|
||||
span {
|
||||
@apply text-base font-medium font-vazir
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,134 @@
|
||||
<template>
|
||||
<div class="seller-order">
|
||||
<div>
|
||||
<div>
|
||||
<img :src="coverImage" alt="">
|
||||
<h2>{{ title }}</h2>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ $t('number') }} : {{ data.quantity }}</span>
|
||||
<span>{{ $t('amount') }} : {{ numberFormat(data.price) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Tag v-bind="tag" rounded />
|
||||
<div v-if="status == 'Pending'">
|
||||
<Button icon="isax isax-tick-square" link severity="secondary" :loading="accepting" @click="accept()" />
|
||||
<Button icon="isax isax-close-square" link severity="secondary" :loading="canceling"
|
||||
@click="cancel()" />
|
||||
</div>
|
||||
<small v-else>{{ new Date(sendDate).toLocaleDateString('fa-IR') }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps(['data'])
|
||||
let { product, status, _id: id, sendDate } = props.data
|
||||
const { coverImage, title } = product
|
||||
const { t } = inject('service')
|
||||
const tags = reactive({
|
||||
pending: { severity: 'warn', value: t('pending') },
|
||||
accepted: { severity: 'info', value: t('accepted') },
|
||||
received: { severity: 'success', value: t('received') },
|
||||
returned: { severity: 'error', value: t('returned') },
|
||||
canceled: { severity: 'error', value: t('canceled') },
|
||||
})
|
||||
|
||||
const tag = ref(tags[status.toLowerCase()])
|
||||
|
||||
const accepting = ref(false)
|
||||
const canceling = ref(false)
|
||||
|
||||
const accept = async () => {
|
||||
accepting.value = true
|
||||
const result = await useFetch('/api/shop/product/accept/' + id, {
|
||||
headers: {
|
||||
Authorization: useCookie('token')
|
||||
}
|
||||
})
|
||||
accepting.value = false
|
||||
|
||||
if (result.status.value == 'success') {
|
||||
status = 'accepted'
|
||||
tag.value = tags[status]
|
||||
sendDate = Date.now()
|
||||
}
|
||||
}
|
||||
|
||||
const cancel = async () => {
|
||||
canceling.value = true
|
||||
const result = await useFetch('/api/shop/product/cancel/' + id, {
|
||||
headers: {
|
||||
Authorization: useCookie('token')
|
||||
}
|
||||
})
|
||||
canceling.value = false
|
||||
|
||||
if (result.status.value == 'success') {
|
||||
status = 'canceled'
|
||||
tag.value = tags[status]
|
||||
sendDate = Date.now()
|
||||
}
|
||||
}
|
||||
|
||||
const numberFormat = (number) =>
|
||||
new Intl.NumberFormat('fa-IR', { maximumSignificantDigits: 3 }).format(number)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.seller-order {
|
||||
@apply w-full h-[12.1rem] rounded-[0.6rem] border border-[#E5E5E5] px-[1.1rem] lg:px-[1.9rem] flex flex-col;
|
||||
|
||||
&>div:first-child {
|
||||
@apply w-full flex max-lg:flex-col max-lg:gap-[1.9rem] justify-between items-center py-[1.1rem] lg:py-5 border-b border-[#E5E5E5];
|
||||
|
||||
&>div:first-child {
|
||||
@apply flex items-center gap-1.5 lg:gap-5;
|
||||
|
||||
img {
|
||||
@apply w-[3.8rem] h-[3.8rem] lg:w-20 lg:h-20 bg-gray-200 shrink-0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@apply text-[#333333] text-sm lg:text-base font-medium font-vazir;
|
||||
}
|
||||
}
|
||||
|
||||
&>div:last-child {
|
||||
@apply max-lg:w-full lg:h-full flex lg:flex-col justify-between items-end;
|
||||
|
||||
span {
|
||||
@apply text-[#333333] text-xs lg:text-base font-normal font-vazir;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
&>div:last-child {
|
||||
@apply w-full h-full flex justify-between items-center;
|
||||
|
||||
.p-tag {
|
||||
@apply h-[1.8rem];
|
||||
}
|
||||
|
||||
&>div {
|
||||
@apply flex gap-1.5 lg:gap-3;
|
||||
|
||||
.p-button {
|
||||
@apply p-0 w-max h-max #{!important};
|
||||
|
||||
.p-button-icon {
|
||||
@apply text-xl lg:text-2xl text-[#333333];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&>small {
|
||||
@apply text-[#333333] text-sm lg:text-base font-vazir;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<div class="seller-chart">
|
||||
<Dropdown v-model="period" :options="options" optionValue="value" optionLabel="label">
|
||||
<template #dropdownicon>
|
||||
<i class="isax isax-arrow-bottom" />
|
||||
</template>
|
||||
</Dropdown>
|
||||
|
||||
<Chart type="pie" :data="chartData" :options="chartOptions" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Chart from 'primevue/chart';
|
||||
|
||||
const chartData = ref();
|
||||
const chartOptions = ref();
|
||||
|
||||
const setChartData = () => {
|
||||
const documentStyle = getComputedStyle(document.body);
|
||||
|
||||
return {
|
||||
labels: ['سود', 'درآمد', 'فروش'],
|
||||
datasets: [
|
||||
{
|
||||
data: [21451000, 20123000, 10000000],
|
||||
backgroundColor: ['#FF6384', '#FF9F40', '#4BC0C0'],
|
||||
hoverBackgroundColor: ['#FF6384cc', '#FF9F40cc', '#4BC0C0cc']
|
||||
}
|
||||
]
|
||||
};
|
||||
};
|
||||
|
||||
const setChartOptions = () => {
|
||||
return {
|
||||
plugins: {
|
||||
legend: {
|
||||
labels: {
|
||||
usePointStyle: true,
|
||||
color: '#333333',
|
||||
pointStyle: 'rect',
|
||||
pointStyleWidth: 20,
|
||||
font: {
|
||||
family: 'vazir',
|
||||
size: 14
|
||||
}
|
||||
|
||||
},
|
||||
position: 'bottom',
|
||||
rtl: true
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: '#050B20',
|
||||
bodyFont: 'vazir',
|
||||
bodyColor: 'white',
|
||||
rtl: true,
|
||||
displayColors: false,
|
||||
callbacks: {
|
||||
title: () => null
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const { t } = inject('service')
|
||||
const options = reactive([
|
||||
{ label: t('thisWeek'), value: 'weekly' }
|
||||
])
|
||||
const period = ref('weekly')
|
||||
|
||||
onMounted(() => {
|
||||
chartData.value = setChartData();
|
||||
chartOptions.value = setChartOptions();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.seller-chart {
|
||||
@apply w-[21.4rem] h-[25.4rem] lg:w-[18.3rem] lg:h-[22.8rem] rounded-[0.6rem] border-2 border-[#E5E5E5] p-3;
|
||||
@apply flex flex-col gap-[1.1rem] max-xl:hidden lg:hidden xl:flex;
|
||||
|
||||
.p-dropdown {
|
||||
@apply w-[5.6rem] h-[2.4rem] lg:w-[6.5rem] lg:h-[2.6rem] bg-[#F2F2F2] rounded-[0.6rem] text-[#5D5D5D] border-none;
|
||||
|
||||
.p-dropdown-trigger {
|
||||
@apply w-max translate-x-2;
|
||||
|
||||
i {
|
||||
@apply text-base lg:text-lg;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
span {
|
||||
@apply p-3 h-max w-max pl-0 font-vazir text-xs lg:text-sm #{!important};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<div class="seller-profile">
|
||||
<span v-if="shop.rate > -1">
|
||||
<small>{{ shop.rate }}</small>
|
||||
<i class="isax isax-star-15" />
|
||||
</span>
|
||||
<div>
|
||||
<picture>
|
||||
<img :src="shop?.userID?.profilePhoto">
|
||||
</picture>
|
||||
<div>
|
||||
<h2>{{ shop.shopName }}</h2>
|
||||
<span>
|
||||
{{ $t('registerDate') }} : {{ new Date(shop.createdAt).toLocaleDateString('fa-IR') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<PanelSellerMenu v-if="device == 'desktop'" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const { device } = inject('service')
|
||||
const { shop } = inject('data')
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.seller-profile {
|
||||
@apply w-full md:w-[21.4rem] min-h-[6.4rem] lg:w-[18.3rem] lg:h-[30rem] rounded-[0.6rem] border-2 border-neutral-200 relative;
|
||||
|
||||
&>span {
|
||||
@apply absolute left-4 top-4 lg:left-3 lg:top-3 flex items-center gap-2;
|
||||
|
||||
i {
|
||||
@apply text-lg text-[#FFB628] -mt-0.5;
|
||||
}
|
||||
|
||||
small {
|
||||
@apply text-zinc-800 text-sm font-medium font-poppins;
|
||||
}
|
||||
}
|
||||
|
||||
&>div {
|
||||
@apply flex lg:flex-col gap-[1.1rem] lg:gap-3 items-center max-lg:h-full max-lg:pr-4 lg:pt-7 lg:pb-4;
|
||||
|
||||
picture {
|
||||
@apply flex w-[4.1rem] h-[4.1rem] lg:w-[5.6rem] lg:h-[5.6rem] rounded-full border border-[#47B556];
|
||||
|
||||
img {
|
||||
@apply w-[3.6rem] h-[3.6rem] lg:w-[4.9rem] lg:h-[4.9rem] bg-zinc-300 rounded-full m-auto;
|
||||
}
|
||||
}
|
||||
|
||||
&>div {
|
||||
@apply flex flex-col lg:items-center gap-5;
|
||||
|
||||
h2 {
|
||||
@apply text-zinc-800 text-xs lg:text-base font-medium font-vazir;
|
||||
}
|
||||
|
||||
span {
|
||||
@apply text-zinc-500 text-[0.6em] lg:text-xs font-medium font-vazir;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div class="seller-sales">
|
||||
<h2>{{ $t(title + 'Sales') }}</h2>
|
||||
<ul>
|
||||
<li v-for="(item, i) in items[title]" :key="i">
|
||||
<p>
|
||||
{{ numberFormat(item.value) }} <span>{{ suffix[title] }}</span>
|
||||
</p>
|
||||
<span>{{ item.label }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps(['title'])
|
||||
|
||||
const {
|
||||
salesCurrentWeek = 0, salesAgoWeek = 0, salseAgoMonth = 0,
|
||||
oneDayAgo = 0, oneWeekAgo = 0, oneMonthAgo = 0
|
||||
} = inject('data')
|
||||
|
||||
const items = reactive({
|
||||
status: [
|
||||
{ label: 'فروش هفته جاری', value: salesCurrentWeek },
|
||||
{ label: 'فروش هفته گذشته', value: salesAgoWeek },
|
||||
{ label: 'فروش ماه گذشته', value: salseAgoMonth },
|
||||
],
|
||||
number: [
|
||||
{ label: 'تعداد کالای فروش رفته هفته جاری', value: oneDayAgo },
|
||||
{ label: 'تعداد کالای فروش رفته هفته گذشته', value: oneWeekAgo },
|
||||
{ label: 'تعداد کالای فروش رفته ماه گذشته', value: oneMonthAgo },
|
||||
]
|
||||
})
|
||||
|
||||
const suffix = reactive({ status: 'ریال', number: 'عدد' })
|
||||
|
||||
const numberFormat = (number) =>
|
||||
new Intl.NumberFormat('fa-IR', { maximumSignificantDigits: 3 }).format(number)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.seller-sales {
|
||||
@apply w-full md:w-[21.4rem] lg:w-[28.1rem] flex flex-col pt-[1.1rem] gap-[1.1rem] lg:pt-5 lg:gap-5 rounded-[0.6rem] border-2 border-neutral-200;
|
||||
|
||||
h2 {
|
||||
@apply text-[#333333] text-sm lg:text-lg font-medium font-vazir pr-[1.1rem] lg:pr-5;
|
||||
}
|
||||
|
||||
ul {
|
||||
@apply w-full flex flex-col gap-6 lg:gap-[1.9rem] p-[1.1rem] lg:pt-6 border-t border-[#E5E5E5];
|
||||
|
||||
li {
|
||||
@apply flex flex-col text-[#333333] [&>p]:first:text-[#47B556] font-medium font-vazir;
|
||||
|
||||
p {
|
||||
@apply text-lg lg:text-2xl;
|
||||
}
|
||||
|
||||
|
||||
span {
|
||||
@apply text-xs lg:text-base;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<div class="seller-score">
|
||||
<h2>{{ $t('yourePerformanceScore') }}</h2>
|
||||
<ul>
|
||||
<li v-for="(item, i) in items" :key="i">
|
||||
<small>{{ item.title }}</small>
|
||||
<span>{{ item.percent }}%</span>
|
||||
<svg viewBox="0 0 250 250" :style="{'--percent': item.percent }">
|
||||
<circle v-for="j in 2" :key="j" />
|
||||
</svg>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const { shop } = inject('data')
|
||||
const items = reactive([
|
||||
{ title: 'رضایت مشتریان', percent: shop.rate * 10 },
|
||||
{ title: 'تاخیر در ارسال', percent: shop.warn * 10 },
|
||||
{ title: 'بازگشت کالا', percent: shop.returnedScore },
|
||||
{ title: 'لغو سفارش', percent: shop.canceledScore },
|
||||
])
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.seller-score {
|
||||
@apply w-full md:w-[21.4rem] h-[10.1rem] lg:w-[18.3rem] lg:h-[21.9rem] flex flex-col pt-[1.1rem] gap-[1.1rem] lg:pt-5 lg:gap-5 rounded-[0.6rem] border-2 border-neutral-200;
|
||||
|
||||
h2 {
|
||||
@apply text-[#333333] text-sm lg:text-lg font-medium font-vazir pr-[1.1rem] lg:pr-5;
|
||||
}
|
||||
|
||||
ul {
|
||||
@apply w-full flex flex-wrap gap-2.5 lg:gap-x-[1.9rem] lg:gap-y-5 pt-[1.1rem] lg:pt-5 justify-center border-t border-[#E5E5E5];
|
||||
|
||||
li {
|
||||
@apply w-[4.3rem] h-[4.3rem] lg:w-[6.9rem] lg:h-[6.9rem] relative flex flex-col items-center justify-center gap-2 rounded-full;
|
||||
|
||||
small {
|
||||
@apply text-[#333333] text-[0.4em] lg:text-[0.6em] font-bold font-vazir mt-1;
|
||||
}
|
||||
|
||||
span {
|
||||
@apply text-[#333333] text-xs lg:text-base font-medium font-vazir;
|
||||
}
|
||||
|
||||
svg {
|
||||
@apply absolute inset-0;
|
||||
|
||||
--stroke-width: 5px;
|
||||
|
||||
@screen lg {
|
||||
--stroke-width: 12px;
|
||||
}
|
||||
|
||||
--radius: calc((250px - var(--stroke-width)) / 2);
|
||||
|
||||
circle {
|
||||
cx: 125px;
|
||||
cy: 125px;
|
||||
r: var(--radius);
|
||||
stroke-width: var(--stroke-width);
|
||||
fill: none;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
circle:first-child {
|
||||
stroke: #D9D9D9;
|
||||
}
|
||||
|
||||
circle:last-child {
|
||||
transition: stroke-dasharray 0.3s linear 0s;
|
||||
stroke: #47B556;
|
||||
transform-origin: 125px 125px;
|
||||
--circumference: calc(var(--radius) * 3.14 * 2);
|
||||
--dash: calc((var(--percent) * var(--circumference)) / 100);
|
||||
stroke-dasharray: var(--dash) calc(var(--circumference) - var(--dash));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,212 @@
|
||||
<template>
|
||||
<DataTable :expandedRows="[]" :value="pendingProducts" class="seller-upcoming" :responsiveLayout="rl">
|
||||
<template #header>
|
||||
<h2>{{ $t('upcomingShipments') }}</h2>
|
||||
<Dropdown v-model="period" :options="options" optionValue="value" optionLabel="label">
|
||||
<template #dropdownicon>
|
||||
<i class="isax isax-arrow-bottom" />
|
||||
</template>
|
||||
</Dropdown>
|
||||
</template>
|
||||
<Column v-for="(col, i) in columns[device]" :key="i" v-bind="col" class="[&:first-child_button]:hidden">
|
||||
<template #body="{ data, field }">
|
||||
<span>{{ field(data) }}</span>
|
||||
<Button v-if="device == 'mobile'" :label="$t('moreDetails')" icon="isax isax-arrow-left-2"
|
||||
link @click="show(data)" />
|
||||
</template>
|
||||
</Column>
|
||||
<Column v-if="device == 'desktop'" expander />
|
||||
|
||||
<template #expansion="{ data }">
|
||||
<ul>
|
||||
<li>
|
||||
<span v-if="data.quantity > 1">
|
||||
{{ data.quantity }}x
|
||||
</span>
|
||||
<img :src="data.product.coverImage">
|
||||
</li>
|
||||
</ul>
|
||||
<Button :label="$t('showFactor')" icon="isax isax-receipt-item" iconPos="right" link />
|
||||
</template>
|
||||
|
||||
<template #empty>
|
||||
<p>{{ $t('notFound') }}</p>
|
||||
</template>
|
||||
</DataTable>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import PanelSellerDialogDetailsOrder from './dialog/details/Order.vue'
|
||||
|
||||
const { t, device, dialog } = inject('service')
|
||||
const rl = computed(() => device.value == 'mobile' ? 'stack' : '')
|
||||
|
||||
const { pendingProducts = [] } = inject('data')
|
||||
|
||||
const columns = reactive({
|
||||
desktop: [
|
||||
{ header: t('row'), field: (item) => pendingProducts.indexOf(item) + 1 },
|
||||
{ header: t('purchaseDate'), field: (item) => new Date(item.orderDate).toLocaleDateString() },
|
||||
{ header: t('postageDate'), field: (item) => new Date(item.sendDate).toLocaleDateString() },
|
||||
{ header: t('price'), field: (item) => new Intl.NumberFormat('fa-IR', { maximumSignificantDigits: 3 }).format(item.price) },
|
||||
],
|
||||
mobile: [
|
||||
{ field: (item) => item.product.title },
|
||||
{ field: (item) => new Date(item.sendDate).toLocaleDateString() },
|
||||
]
|
||||
})
|
||||
|
||||
const options = reactive([
|
||||
{ label: t('thisWeek'), value: 'weekly' }
|
||||
])
|
||||
const period = ref('weekly')
|
||||
|
||||
const show = (data) => {
|
||||
dialog.show(PanelSellerDialogDetailsOrder, data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.seller-upcoming {
|
||||
@apply rtl border-2 border-[#E5E5E5] rounded-[0.6rem] overflow-hidden w-full;
|
||||
|
||||
.p-datatable-header {
|
||||
@apply flex items-center justify-between bg-white pt-3 pl-3 pr-[1.1rem] pb-[1.1rem] lg:py-2.5 lg:px-5;
|
||||
|
||||
h2 {
|
||||
@apply text-[#333333] text-sm lg:text-lg font-medium font-vazir;
|
||||
}
|
||||
|
||||
.p-dropdown {
|
||||
@apply w-[5.6rem] h-[2.4rem] lg:w-[6.5rem] lg:h-[2.6rem] bg-[#F2F2F2] rounded-[0.6rem] text-[#5D5D5D] border-none;
|
||||
|
||||
.p-dropdown-trigger {
|
||||
@apply w-max translate-x-2;
|
||||
|
||||
i {
|
||||
@apply text-base lg:text-lg;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
span {
|
||||
@apply p-3 h-max w-max pl-0 font-vazir text-xs lg:text-sm #{!important};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.p-datatable-wrapper {
|
||||
@apply max-lg:overflow-hidden #{!important};
|
||||
|
||||
.p-datatable-table {
|
||||
@apply border-separate border-spacing-y-[0.8rem] px-[1.1rem] py-3;
|
||||
|
||||
.p-datatable-thead {
|
||||
@apply w-full h-[3.1rem] max-lg:hidden;
|
||||
|
||||
th {
|
||||
@apply text-[#5D5D5D] text-base font-normal font-vazir bg-[#F2F2F2];
|
||||
@apply first:rounded-r-[0.6rem] last:rounded-l-[0.6rem];
|
||||
}
|
||||
}
|
||||
|
||||
.p-datatable-tbody {
|
||||
@apply lg:translate-y-1.5;
|
||||
|
||||
tr {
|
||||
@apply max-lg:h-[7.5rem] bg-transparent relative #{!important};
|
||||
|
||||
&:not(.p-datatable-row-expansion)::after {
|
||||
@apply content-['_'] absolute inset-x-0 z-0 max-lg:inset-y-0 lg:h-[3.8rem] rounded-[0.6rem];
|
||||
@apply shadow-[0px_0px_6px_#0000001f];
|
||||
}
|
||||
|
||||
|
||||
td {
|
||||
@apply max-lg:w-full max-lg:px-3 text-right last:text-left #{!important};
|
||||
@apply lg:first:rounded-r-[0.6rem] lg:last:rounded-l-[0.6rem] py-0 align-top lg:border-none;
|
||||
|
||||
&>span {
|
||||
@apply max-lg:py-3 min-h-[3.8rem] flex items-center text-xs lg:text-base text-[#333333] font-vazir;
|
||||
}
|
||||
|
||||
.p-row-toggler {
|
||||
@apply mt-3.5 z-10;
|
||||
}
|
||||
|
||||
@media(max-width: 1024px) {
|
||||
&>button {
|
||||
@apply z-[1] -m-3 mr-auto #{!important};
|
||||
|
||||
.p-button-label {
|
||||
@apply text-xs font-medium font-vazir;
|
||||
}
|
||||
|
||||
.p-button-icon {
|
||||
@apply text-sm mr-1;
|
||||
}
|
||||
}
|
||||
|
||||
&:first-child span {
|
||||
@apply max-lg:w-full max-lg:border-b;
|
||||
}
|
||||
|
||||
.p-column-title {
|
||||
@apply hidden #{!important};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
&.p-datatable-row-expansion {
|
||||
@keyframes fade {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
animation: fade 0.2s ease 0.1s forwards;
|
||||
@apply opacity-0 -translate-y-3 shadow-none;
|
||||
|
||||
ul {
|
||||
@apply flex items-center gap-[1.1rem] rtl h-[9.9rem] border-y border-[#E5E5E5];
|
||||
|
||||
li {
|
||||
@apply flex flex-col items-center justify-end h-[7.3rem] gap-3;
|
||||
|
||||
span {
|
||||
@apply text-[#333333] text-base font-normal font-poppins
|
||||
}
|
||||
|
||||
img {
|
||||
@apply w-20 h-20;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
@apply float-left;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&:has(+.p-datatable-row-expansion)::after {
|
||||
@apply h-[17.5rem] transition-[height];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.p-datatable-emptymessage p {
|
||||
@apply lg:h-[3.8rem] flex justify-center items-center
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<div class="upload-national-card" @click="showImages">
|
||||
<img v-if="source" :src="source" />
|
||||
<i v-else class="isax isax-document-upload"></i>
|
||||
<input ref="input" type="file" hidden @input="chooseImage">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const file = defineModel()
|
||||
const input = ref()
|
||||
const source = ref()
|
||||
const filename = ref()
|
||||
|
||||
const showImages = () => input.value.click();
|
||||
|
||||
const chooseImage = (e) => {
|
||||
const binaryData = [];
|
||||
binaryData.push(e.target.files[0]);
|
||||
source.value = URL.createObjectURL(new Blob(binaryData, { type: "image" }))
|
||||
file.value = e.target.files[0];
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.upload-national-card {
|
||||
@apply cursor-pointer min-h-14 h-max relative w-[21.8rem] lg:w-[27.1rem] border border-[#CCCCCC] rounded-[0.6rem] p-4 rtl;
|
||||
|
||||
i {
|
||||
@apply text-xl float-left;
|
||||
}
|
||||
|
||||
|
||||
img {
|
||||
@apply w-full h-[11.6rem] lg:h-[14.4rem] object-cover object-center rounded-[0.6rem];
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<div class="seller-add-spec">
|
||||
<div>
|
||||
<div>
|
||||
<h2>{{ $t("addSpec") }}</h2>
|
||||
<Button icon="isax isax-close-circle" severity="secondary" rounded link @click="dialog.close()" />
|
||||
</div>
|
||||
<p>{{ dialog.data.title }}</p>
|
||||
</div>
|
||||
<ul>
|
||||
<li>
|
||||
<InputText v-model="form.key" placeholder id="key" />
|
||||
<label for="key">{{ $t('title') }}</label>
|
||||
</li>
|
||||
<li>
|
||||
<Textarea v-model="form.value" placeholder id="value" />
|
||||
<label for="value">{{ $t("description") }}</label>
|
||||
</li>
|
||||
</ul>
|
||||
<Button :label="$t('submitSpec')" @click="add()" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const dialog = inject('dialogRef')
|
||||
const form = reactive({})
|
||||
|
||||
const add = () => {
|
||||
dialog.value.data.specs[form.key] = form.value
|
||||
dialog.value.close();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.seller-add-spec {
|
||||
@apply w-full lg:w-[40.1rem] lg:h-[38.6rem] bg-white rounded-t-[0.6rem] lg:rounded-[0.6rem] flex flex-col relative;
|
||||
|
||||
&::after {
|
||||
@apply content-['_'] absolute inset-x-0 bottom-[4.5rem] h-3 shadow-[0px_-2px_5px_#0000000a] lg:shadow-none;
|
||||
}
|
||||
|
||||
&>div {
|
||||
@apply flex flex-col gap-1.5 lg:h-[7.8rem] lg:gap-3 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-[#333333] 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 h-full items-center 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 relative;
|
||||
@apply w-[21.4rem] lg:w-[27.1rem];
|
||||
|
||||
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, textarea {
|
||||
@apply h-[3.4rem] pl-10 rtl w-full rounded-[0.6rem] border shadow-none;
|
||||
@apply text-[#4C4C4C] font-vazir grow;
|
||||
}
|
||||
|
||||
&:has(input:focus),
|
||||
&:has(textarea:focus),
|
||||
&:has(input:not(:placeholder-shown)),
|
||||
&:has(textarea:not(:placeholder-shown)) {
|
||||
label {
|
||||
@apply -top-2 h-4;
|
||||
@apply text-[#4C4C4C] text-xs font-normal font-vazir;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
li:last-child {
|
||||
@apply w-full lg:w-[27.1rem] h-full 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:h-full 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 shrink-0 #{!important};
|
||||
|
||||
.p-button-label {
|
||||
@apply font-bold font-iran-sans text-sm lg:text-base;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<div class="seller-product-added">
|
||||
<div>
|
||||
<i class="isax isax-tick-square"></i>
|
||||
<h2>{{ $t('productAdded') }}</h2>
|
||||
</div>
|
||||
<Button :label="$t('confirm')" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.eller-product-added {
|
||||
@apply flex flex-col justify-between pt-[1.1rem] max-lg:h-[11.8rem] lg:w-[28.3rem] lg:h-64 lg:p-10;
|
||||
|
||||
div {
|
||||
@apply flex max-lg:flex-col items-center gap-4 lg:mt-4;
|
||||
|
||||
h2 {
|
||||
@apply text-[#333333] text-xl font-bold font-vazir leading-[2.8rem];
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
@apply w-full h-12 lg:h-14;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<div class="seller-details-best-selling">
|
||||
<div>
|
||||
<Button icon="isax isax-close-circle" severity="secondary" rounded link @click="dialog.close()" />
|
||||
<h2>{{ title }}</h2>
|
||||
<img :src="coverImage">
|
||||
</div>
|
||||
<ul>
|
||||
<li v-for="(value, key ) in items" :key="key">
|
||||
<label>{{ $t(key) }}</label>
|
||||
<span>{{ value }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const dialog = inject('dialogRef')
|
||||
const { product, quantity, total } = dialog.value.data
|
||||
const { title, coverImagem, stockQuantity} = product
|
||||
|
||||
const numberFormat = (number) =>
|
||||
new Intl.NumberFormat('fa-IR', { maximumSignificantDigits: 3 }).format(number)
|
||||
|
||||
const items = reactive({
|
||||
salesNumber: quantity,
|
||||
salesPrice: numberFormat(total),
|
||||
inventory: stockQuantity,
|
||||
status: "دارای رقیب",
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.seller-details-best-selling {
|
||||
@apply flex flex-col bg-white rounded-t-[0.6rem] gap-[1.9rem] px-6 py-[1.1rem] rtl;
|
||||
|
||||
&>div {
|
||||
@apply flex gap-1.5;
|
||||
|
||||
h2 {
|
||||
@apply w-56 text-[#333333] text-sm font-medium font-vazir pt-0.5;
|
||||
}
|
||||
|
||||
button {
|
||||
@apply -mx-4 -my-3;
|
||||
|
||||
.p-button-icon {
|
||||
@apply text-lg text-[#B2B2B2] #{!important};
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
@apply w-[3.8rem] h-[3.8rem] mr-auto;
|
||||
}
|
||||
}
|
||||
|
||||
&>ul {
|
||||
@apply flex flex-col gap-[1.1rem] mt-1.5;
|
||||
|
||||
li {
|
||||
@apply flex justify-between;
|
||||
|
||||
label {
|
||||
@apply text-[#5D5D5D] text-xs font-vazir
|
||||
}
|
||||
|
||||
span {
|
||||
@apply text-[#333333] text-xs font-medium font-vazir;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<div class="seller-details-order">
|
||||
<div>
|
||||
<Button icon="isax isax-close-circle" severity="secondary" rounded link @click="dialog.close()" />
|
||||
<h2>{{ $t('orderDetails') }}</h2>
|
||||
<Button icon="isax isax-receipt-item" iconPos="right" link :label="$t('showFactor')" @click="show()" />
|
||||
</div>
|
||||
<ul>
|
||||
<li v-for="(value, key ) in items" :key="key">
|
||||
<label>{{ $t(key) }}</label>
|
||||
<span>{{ value }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li v-for="(product, k) in [product]" :key="k">
|
||||
<span v-if="product.quantity > 1">
|
||||
{{ product.quantity }}x
|
||||
</span>
|
||||
<img :src="product.coverImage">
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const dialog = inject('dialogRef')
|
||||
const { total, product} = dialog.value.data
|
||||
|
||||
const items = reactive({
|
||||
address: ".........................................................................",
|
||||
receiver: "علی مصلحی",
|
||||
purchaseDate: "1402/05/13",
|
||||
postageDate: "1402/05/20",
|
||||
price: "252,000",
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.seller-details-order {
|
||||
@apply flex flex-col bg-white rounded-t-[0.6rem] gap-[1.1rem] px-6 pt-[1.1rem] rtl;
|
||||
|
||||
&>div {
|
||||
@apply flex items-center gap-1.5;
|
||||
|
||||
h2 {
|
||||
@apply text-[#333333] text-sm font-medium font-vazir;
|
||||
}
|
||||
|
||||
&>button {
|
||||
@apply -mx-4 -my-3 last:mr-auto;
|
||||
|
||||
&:first-child {
|
||||
@apply text-lg text-[#B2B2B2] #{!important};
|
||||
}
|
||||
|
||||
&:last-child{
|
||||
@apply text-[#47B556] font-vazir;
|
||||
|
||||
.p-button-label{
|
||||
@apply text-xs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&>ul:first-of-type {
|
||||
@apply flex flex-col gap-[1.1rem] mt-1.5;
|
||||
|
||||
li {
|
||||
@apply flex justify-between;
|
||||
|
||||
label {
|
||||
@apply text-[#5D5D5D] text-xs font-vazir
|
||||
}
|
||||
|
||||
span {
|
||||
@apply text-[#333333] text-xs font-medium font-vazir;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&>ul:last-of-type {
|
||||
@apply flex items-center gap-[1.1rem] pb-[1.1rem] border-t border-[#E5E5E5];
|
||||
|
||||
li {
|
||||
@apply flex flex-col h-28 items-center justify-end gap-2.5;
|
||||
|
||||
span {
|
||||
@apply text-[#333333] text-xs font-normal font-poppins
|
||||
}
|
||||
|
||||
img {
|
||||
@apply w-[3.8rem] h-[3.8rem];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div class="seller-details-product">
|
||||
<div>
|
||||
<h2>{{ title }}</h2>
|
||||
<Button icon="isax isax-close-circle" severity="secondary" rounded link @click="dialog.close()" />
|
||||
</div>
|
||||
<ul>
|
||||
<li v-for="(value, key ) in items" :key="key">
|
||||
<label>{{ $t(key) }}</label>
|
||||
<span>{{ value }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const { title = 'هارددیسک اکسترنال ای دیتا مدل HM900 ظرفیت 4 ترابایت' } = reactive({})
|
||||
const items = reactive({
|
||||
salesPrice: "242,523",
|
||||
inventory: "1",
|
||||
status: "دارای رقیب",
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.seller-details-product {
|
||||
@apply flex flex-col gap-[1.9rem] px-6 py-[1.1rem] rtl;
|
||||
|
||||
&>div {
|
||||
@apply flex items-center justify-between gap-1.5;
|
||||
|
||||
h2 {
|
||||
@apply w-56 text-[#333333] text-sm font-medium font-vazir;
|
||||
}
|
||||
|
||||
button {
|
||||
@apply -mx-4 -my-3;
|
||||
|
||||
.p-button-icon {
|
||||
@apply text-lg text-[#B2B2B2] #{!important};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&>ul {
|
||||
@apply felx flex-col gap-[1.1rem] mt-1.5;
|
||||
|
||||
li {
|
||||
@apply flex justify-between;
|
||||
|
||||
label {
|
||||
@apply text-[#5D5D5D] text-xs font-vazir
|
||||
}
|
||||
|
||||
span {
|
||||
@apply text-[#333333] text-xs font-medium font-vazir;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,201 @@
|
||||
<template>
|
||||
<section class="seller-first-step">
|
||||
<ul>
|
||||
<li v-for="({ is, props, label }, key) in fields.first" :key="key">
|
||||
<Editor v-if="key == 'description'" v-bind="props" v-model="form[key]"/>
|
||||
<component v-else :is="is" v-bind="props" v-model="form[key]" />
|
||||
<label :for="key"> {{ label }} </label>
|
||||
<small v-if="errors[key]">{{ errors[key] }}</small>
|
||||
</li>
|
||||
</ul>
|
||||
<div>
|
||||
<Button v-bind="btns.next.props" @click="next()" />
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Editor from 'primevue/editor';
|
||||
|
||||
const { t } = inject('service')
|
||||
const { fields, btns } = inject('init')
|
||||
const form = inject('form')
|
||||
const step = inject('step')
|
||||
const { brands } = inject('data')
|
||||
const errors = ref({})
|
||||
|
||||
fields.first.brandID.props.options = brands
|
||||
const categories = useCategoriesStore()
|
||||
|
||||
fields.first.subCategory.props.options = computed(() => {
|
||||
let items = []
|
||||
categories.items.forEach((item) => {
|
||||
if (item.sub) {
|
||||
item.sub.forEach((item2) => {
|
||||
if (item2._id == form.category) {
|
||||
items = item2.sub ?? [item2]
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
return items;
|
||||
})
|
||||
|
||||
fields.first.minBuy.props.disabled = computed(() => !form.boxSeller)
|
||||
fields.first.maxBuy.props.disabled = computed(() => !form.boxSeller)
|
||||
fields.first.itemInBox.props.disabled = computed(() => !form.boxSeller)
|
||||
|
||||
if (form.boxSeller) {
|
||||
const { min = 1, max = 20 } = form?.orderRange
|
||||
form.minBuy = min
|
||||
form.maxBuy = max
|
||||
}
|
||||
|
||||
watch(
|
||||
computed(() => Object.assign({}, form)),
|
||||
(value, old) => {
|
||||
Object.keys(errors.value).forEach((key) => {
|
||||
if (value[key] != old[key])
|
||||
delete errors.value[key]
|
||||
})
|
||||
},
|
||||
{ deep: true })
|
||||
|
||||
const next = () => {
|
||||
errors.value = {}
|
||||
Object.keys(fields.first).forEach((key) => {
|
||||
if (!['localSend', 'boxSeller', 'warranty'].includes(key) && !fields.first[key].props.disabled) {
|
||||
if ([undefined, null, ''].includes(form[key])) {
|
||||
errors.value[key] = t('required')
|
||||
}
|
||||
}
|
||||
})
|
||||
if (Object.keys(errors.value).length < 1)
|
||||
step.value++;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.seller-first-step {
|
||||
@apply w-full flex flex-col pb-20 gap-8 lg:gap-[3.8rem];
|
||||
|
||||
&>ul {
|
||||
@apply grid grid-cols-2 lg:grid-cols-4 gap-x-3 gap-y-6 lg:gap-x-6 lg:gap-y-10 w-full max-w-full;
|
||||
|
||||
li {
|
||||
@apply w-full h-14 relative max-xl:col-span-2;
|
||||
|
||||
label {
|
||||
@apply w-max absolute right-3 top-4 px-1 bg-white transition-['top'] select-none;
|
||||
@apply text-neutral-400 text-sm lg:text-base font-medium font-vazir cursor-text pointer-events-none;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
@apply rtl w-full rounded-[0.6rem] border shadow-none border-[#CCCCCC] text-zinc-800 font-vazir;
|
||||
}
|
||||
|
||||
&:has(input:focus),
|
||||
&:has(textarea:focus),
|
||||
&:has(.p-inputwrapper-filled),
|
||||
&:has(input:not(:placeholder-shown)),
|
||||
&:has(textarea:not(:placeholder-shown)) {
|
||||
label {
|
||||
@apply -top-2 h-4;
|
||||
@apply text-zinc-800 text-xs font-normal;
|
||||
}
|
||||
}
|
||||
|
||||
.p-dropdown,
|
||||
.p-inputnumber {
|
||||
@apply w-full;
|
||||
|
||||
input {
|
||||
@apply ltr text-left;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.p-dropdown-label {
|
||||
@apply font-vazir pt-4;
|
||||
}
|
||||
|
||||
&:nth-of-type(2) {
|
||||
label {
|
||||
@apply left-3 right-auto;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-of-type(1),
|
||||
&:nth-of-type(2) {
|
||||
@apply col-span-2;
|
||||
}
|
||||
|
||||
&:nth-of-type(11) {
|
||||
@apply col-span-2 lg:col-span-3 row-span-3 h-full max-lg:order-last;
|
||||
|
||||
textarea {
|
||||
@apply grow h-full;
|
||||
}
|
||||
}
|
||||
|
||||
&:has(.p-checkbox) {
|
||||
@apply border border-[#CCCCCC] rounded-[0.6rem] flex flex-row-reverse justify-between items-center px-4;
|
||||
|
||||
label {
|
||||
@apply static text-sm lg:text-base text-neutral-400 h-max pointer-events-auto cursor-pointer #{!important};
|
||||
}
|
||||
|
||||
.p-checkbox {
|
||||
@apply w-[1.1rem] lg:w-5 shrink-0;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-of-type(9),
|
||||
&:nth-of-type(10),
|
||||
&:nth-of-type(12),
|
||||
&:nth-of-type(13) {
|
||||
@apply max-lg:col-span-1;
|
||||
}
|
||||
|
||||
small {
|
||||
@apply text-red-500 font-vazir;
|
||||
}
|
||||
|
||||
&:has(small) * {
|
||||
@apply border-red-500 #{!important};
|
||||
}
|
||||
|
||||
&:has(.p-overlay-open)::after {
|
||||
@apply content-['_'] inset-0 fixed bg-black/20 z-[1];
|
||||
}
|
||||
|
||||
&:has(.p-disabled),
|
||||
&:has([disabled]) {
|
||||
@apply max-lg:hidden lg:invisible;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.p-editor-content{
|
||||
@apply rtl text-right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&>div {
|
||||
@apply inset-x-0 bottom-0 flex gap-3 w-full lg:w-max self-end;
|
||||
@apply max-lg:fixed max-lg:border-t max-lg:shadow-[0_-2px_5px_#0000000a];
|
||||
@apply h-[4.8rem] px-6 pt-3 lg:p-0 lg:h-max bg-white;
|
||||
|
||||
&>button {
|
||||
@apply w-full h-[2.9rem] lg:w-[11.4rem] lg:h-14 #{!important};
|
||||
|
||||
.p-button-label {
|
||||
@apply text-sm lg:text-xl font-medium font-vazir;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,122 @@
|
||||
<template>
|
||||
<section class="seller-second-step">
|
||||
<ul>
|
||||
<li v-for="(_, key) in form.specs" :key="key">
|
||||
<IconField iconPosition="left">
|
||||
<InputText :id="key" placeholder v-model="form.specs[key]" />
|
||||
<InputIcon class="isax isax-close-circle" @click="delete form.specs[key]" />
|
||||
</IconField>
|
||||
<label :for="key"> {{ key }} </label>
|
||||
<small v-if="errors[key]">{{ errors[key] }}</small>
|
||||
</li>
|
||||
<li @click="handleClick()" v-ripple>
|
||||
<span>{{ $t('addSpec') }}</span>
|
||||
<i class="isax isax-box-add" />
|
||||
</li>
|
||||
</ul>
|
||||
<div>
|
||||
<Button v-bind="btns.prev.props" @click="step--" />
|
||||
<Button v-bind="btns.next.props" :disabled="disabled" @click="next()" />
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const { dialog, t } = inject('service')
|
||||
const { btns } = inject('init')
|
||||
|
||||
const form = inject('form')
|
||||
const step = inject('step')
|
||||
const disabled = computed(() => form.specs.length > 0)
|
||||
const errors = ref({})
|
||||
|
||||
const next = () => {
|
||||
errors.value = {}
|
||||
Object.entries(form.specs).forEach(([key, value]) => {
|
||||
if ([undefined, null, ''].includes(value))
|
||||
errors.value[key] = t('required')
|
||||
})
|
||||
|
||||
if (Object.keys(errors.value).length < 1)
|
||||
step.value++;
|
||||
}
|
||||
|
||||
const handleClick = () => {
|
||||
dialog.show(resolveComponent('PanelSellerDialogAddSpec'), form)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.seller-second-step {
|
||||
@apply w-full flex flex-col gap-8 lg:gap-[3.8rem];
|
||||
|
||||
ul {
|
||||
@apply grid lg:grid-cols-3 gap-x-3 gap-y-6 lg:gap-x-6 lg:gap-y-10 w-full max-w-full;
|
||||
|
||||
li {
|
||||
@apply w-full h-14 relative;
|
||||
|
||||
label {
|
||||
@apply w-max absolute right-3 max-lg:translate-y-0.5 top-4 px-1 bg-white transition-['top'] select-none;
|
||||
@apply text-neutral-400 text-sm lg:text-base font-medium font-vazir cursor-text pointer-events-none;
|
||||
}
|
||||
|
||||
input {
|
||||
@apply w-full rounded-[0.6rem] border shadow-none border-[#CCCCCC] text-zinc-800 font-vazir rtl;
|
||||
}
|
||||
|
||||
.p-input-icon {
|
||||
@apply text-lg lg:text-xl h-max -translate-y-[20%] cursor-pointer;
|
||||
}
|
||||
|
||||
&:has(input:focus),
|
||||
&:has(input:not(:placeholder-shown)) {
|
||||
label {
|
||||
@apply -top-2 h-4;
|
||||
@apply text-zinc-800 text-xs font-normal;
|
||||
}
|
||||
}
|
||||
|
||||
.p-inputnumber {
|
||||
@apply w-full;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
@apply cursor-pointer border border-[#CCCCCC] rounded-[0.6rem] px-4 pointer-events-auto;
|
||||
@apply flex justify-between items-center overflow-hidden relative;
|
||||
|
||||
span {
|
||||
@apply text-neutral-400 text-sm lg:text-base font-medium font-vazir;
|
||||
}
|
||||
|
||||
i {
|
||||
@apply text-[#47B556] text-2xl;
|
||||
}
|
||||
}
|
||||
|
||||
small {
|
||||
@apply text-red-500 font-vazir;
|
||||
}
|
||||
|
||||
&:has(small) * {
|
||||
@apply border-red-500 #{!important};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&>div {
|
||||
@apply inset-x-0 bottom-0 flex gap-3 w-full lg:w-max self-end;
|
||||
@apply max-lg:fixed max-lg:border-t max-lg:shadow-[0_-2px_5px_#0000000a];
|
||||
@apply h-[4.8rem] px-6 pt-3 lg:p-0 lg:h-max bg-white;
|
||||
|
||||
&>button {
|
||||
@apply w-full h-[2.9rem] lg:w-[11.4rem] lg:h-14 #{!important};
|
||||
|
||||
.p-button-label {
|
||||
@apply text-sm lg:text-xl font-medium font-vazir;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<section class="seller-thirdy-step">
|
||||
<ul>
|
||||
<li>
|
||||
<UploadImage v-model="form.coverImage" cover @update:modelValue="delete errors.coverImage" />
|
||||
<small v-if="errors.coverImage">{{ errors.coverImage }}</small>
|
||||
</li>
|
||||
<li v-for="(_, i) in form.images" :key="i">
|
||||
<UploadImage v-model="form.images[i]" @update:modelValue="update" />
|
||||
<small v-if="errors.images && i < 1">{{ errors.images }}</small>
|
||||
</li>
|
||||
</ul>
|
||||
<div>
|
||||
<Button v-bind="btns.prev.props" @click="step--" />
|
||||
<Button v-bind="btns.submit.props" :loading="loading" @click="submit()" />
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import UploadImage from '../َUploadImage.vue'
|
||||
|
||||
const { btns } = inject('init')
|
||||
const { toast, t } = inject('service')
|
||||
const router = useRouter()
|
||||
const form = inject('form')
|
||||
const step = inject('step')
|
||||
const loading = ref(false)
|
||||
const errors = ref({})
|
||||
|
||||
const update = () => {
|
||||
form.images = form.images.filter((image) => image != null)
|
||||
form.images.push(null)
|
||||
|
||||
delete errors.value.images;
|
||||
}
|
||||
|
||||
const submit = async () => {
|
||||
errors.value = {}
|
||||
const images = form.images.filter((image) => image != null)
|
||||
|
||||
if (!form.coverImage)
|
||||
errors.value.coverImage = t('required')
|
||||
|
||||
if (images.length < 1)
|
||||
errors.value.images = t('required')
|
||||
|
||||
|
||||
if (Object.keys(errors.value).length < 1) {
|
||||
loading.value = true
|
||||
const body = Object.assign({}, form)
|
||||
body.images = body.images.filter((image) => image)
|
||||
const { minBuy: min, maxBuy: max } = body
|
||||
body.orderRange = { min, max }
|
||||
delete body.minBuy
|
||||
delete body.maxBuy
|
||||
|
||||
//Upload File
|
||||
if (typeof body.coverImage === 'object') {
|
||||
const formData = new FormData()
|
||||
formData.append('images', form.coverImage)
|
||||
|
||||
const { status, data, error } = await useFetch('/api/images/upload', {
|
||||
headers: { Authorization: useCookie("token") }, params: { type: 1 }, body: formData, method: 'post'
|
||||
})
|
||||
|
||||
if (status.value == 'success')
|
||||
body.coverImage = data.value.urls[0]
|
||||
else {
|
||||
loading.value = false
|
||||
return toast.add({
|
||||
life: 2000, severity: 'error', summary: t('error'), detail: error.value.msg
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//upload images
|
||||
const files = body.images.filter((image) => typeof image === 'object')
|
||||
if (files.length > 0) {
|
||||
const formData = new FormData()
|
||||
files.forEach((image, i) => {
|
||||
formData.append('images', image)
|
||||
});
|
||||
|
||||
const { status, data, error } = await useFetch('/api/images/upload', {
|
||||
headers: { Authorization: useCookie("token") }, params: { type: 1 }, body: formData, method: 'post'
|
||||
})
|
||||
|
||||
if (status.value == 'success') {
|
||||
body.images = body.images.map((image) => {
|
||||
if (typeof image === 'object')
|
||||
return data.value.urls.shift();
|
||||
return image;
|
||||
})
|
||||
|
||||
} else {
|
||||
loading.value = false
|
||||
return toast.add({
|
||||
life: 2000, severity: 'error', summary: t('error'), detail: error.value.msg
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let endPoint = '/api/products'
|
||||
let method = 'post'
|
||||
|
||||
if (body.id) {
|
||||
endPoint += '/' + body.id
|
||||
method = 'put'
|
||||
}
|
||||
|
||||
const { status, data, error } = await useFetch(endPoint, {
|
||||
headers: { Authorization: useCookie("token") }, method, body
|
||||
})
|
||||
|
||||
if (status.value == 'success')
|
||||
router.push('/panel/seller/products')
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (form.id) update()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.seller-thirdy-step {
|
||||
@apply w-full flex flex-col gap-8 pb-20 lg:gap-[3.8rem];
|
||||
|
||||
&>ul {
|
||||
@apply flex flex-wrap gap-6;
|
||||
|
||||
li {
|
||||
small {
|
||||
@apply text-red-500 font-vazir;
|
||||
}
|
||||
|
||||
&:has(small) * {
|
||||
@apply border-red-500 #{!important};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&>div {
|
||||
@apply inset-x-0 bottom-0 flex gap-3 w-full lg:w-[27.1rem] self-end;
|
||||
@apply max-lg:fixed max-lg:border-t max-lg:shadow-[0_-2px_5px_#0000000a];
|
||||
@apply h-[4.8rem] px-6 pt-3 lg:p-0 lg:h-max bg-white;
|
||||
|
||||
&>button {
|
||||
@apply w-full h-[2.9rem] lg:h-14 #{!important};
|
||||
|
||||
.p-button-label {
|
||||
@apply text-sm lg:text-xl font-medium font-vazir;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<div class="seller-upload-image">
|
||||
<div v-if="!source" @drop.prevent="chooseImage('dataTransfer', $event)" @dragover.prevent>
|
||||
<i class="isax isax-gallery-export"></i>
|
||||
<p>{{ 'cover' in $attrs ? $t('coverImage') : ' ' }}</p>
|
||||
<h2>{{ $t('forUploadImages') }} </h2>
|
||||
<span @click="showImages()">{{ $t('clickHere') }}</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
<img :src="source" />
|
||||
<Button :label="$t('placement')" severity="secondary" text raised @click="showImages()" />
|
||||
<Button :label="$t('delete')" severity="secondary" text raised @click="deleteImage()" />
|
||||
</div>
|
||||
<input ref="input" type="file" hidden @input="chooseImage('target', $event)">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const image = defineModel()
|
||||
const input = ref()
|
||||
const source = ref()
|
||||
|
||||
const showImages = () => input.value.click();
|
||||
const deleteImage = () => {
|
||||
image.value = null
|
||||
source.value = null
|
||||
input.value.value = null
|
||||
}
|
||||
const chooseImage = (name, e) => {
|
||||
const binaryData = [];
|
||||
binaryData.push(e[name].files[0]);
|
||||
source.value = URL.createObjectURL(new Blob(binaryData, { type: "image" }))
|
||||
image.value = e[name].files[0];
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if(image.value) source.value = image.value
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.seller-upload-image {
|
||||
@apply w-[21.4rem] h-[21.4rem] lg:w-[24.8rem] lg:h-[24.8rem] bg-white;
|
||||
|
||||
&>div:not(:has(img)) {
|
||||
@apply h-full flex flex-col justify-center items-center border border-[#47B556] border-dashed rounded-[0.6rem];
|
||||
|
||||
|
||||
i {
|
||||
@apply text-[3.1em] lg:text-[3.8em] text-[#47B556];
|
||||
}
|
||||
|
||||
p {
|
||||
@apply text-[#7F7F7F] text-[0.6em] lg:text-sm font-medium font-vazir mt-6;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@apply text-[#333333] text-sm lg:text-lg font-medium font-vazir mt-1.5;
|
||||
}
|
||||
|
||||
span {
|
||||
@apply text-[#47B556] text-sm lg:text-lg font-medium font-vazir cursor-pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&>div:has(img) {
|
||||
@apply h-full flex flex-col gap-3 justify-center items-center relative;
|
||||
|
||||
img {
|
||||
@apply h-full w-full absolute object-cover rounded-[0.6rem];
|
||||
}
|
||||
|
||||
button {
|
||||
@apply w-[7.5rem] h-[3.1rem] bg-white z-[1];
|
||||
}
|
||||
|
||||
&::after{
|
||||
@apply bg-[#47B556]/20 absolute content-['_'] inset-0 rounded-[0.6rem];
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user