init git
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user