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
+156
View File
@@ -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>
+62
View File
@@ -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>
+46
View File
@@ -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>
+134
View File
@@ -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>
+101
View File
@@ -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>
+69
View File
@@ -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>
+68
View File
@@ -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>
+91
View File
@@ -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>
+212
View File
@@ -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>
+120
View File
@@ -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>
+201
View File
@@ -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>
+122
View File
@@ -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>
+171
View File
@@ -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>
+82
View File
@@ -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') : '&nbsp;' }}</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>