wip editar producto page
This commit is contained in:
346
components/ProductForm.vue
Normal file
346
components/ProductForm.vue
Normal file
@@ -0,0 +1,346 @@
|
||||
<template>
|
||||
<form class="form" @submit.prevent="sendProduct">
|
||||
{{ form }}
|
||||
<div class="cont-col">
|
||||
<BFormCheckbox
|
||||
id="customSwitch1"
|
||||
v-model="form.active"
|
||||
class="label"
|
||||
:value="form.active"
|
||||
>Activo
|
||||
<span class="help-text"
|
||||
>Desactiva el producto si quieres paralizar temporalmente su
|
||||
venta</span
|
||||
>
|
||||
</BFormCheckbox>
|
||||
</div>
|
||||
<br />
|
||||
<FormHeader title="general" />
|
||||
<p class="help-text">
|
||||
Estos son los datos básicos de un producto. Procura completar el mayor
|
||||
número de campos posible. Los campos señalados con asterisco (*) son
|
||||
obligatorios, los demás son opcionales. Si el producto o servicio no tiene
|
||||
precio asignado, aparecerá "Consultar precio".
|
||||
</p>
|
||||
<fieldset class="fieldset">
|
||||
<div class="cont">
|
||||
<FormInput
|
||||
v-model="form.name"
|
||||
:value="form.name ? form.name : ''"
|
||||
type="text"
|
||||
label-text="Nombre"
|
||||
required
|
||||
@input="form.name = $event"
|
||||
/>
|
||||
<FormInput
|
||||
v-model="form.price"
|
||||
min="0"
|
||||
step="0.01"
|
||||
type="number"
|
||||
label-text="Precio"
|
||||
placeholder="precio + iva"
|
||||
/>
|
||||
</div>
|
||||
<div class="cont">
|
||||
<FormInput
|
||||
v-model="form.url"
|
||||
type="text"
|
||||
label-text="Url"
|
||||
placeholder="enlace directo al producto en tu tienda o web"
|
||||
/>
|
||||
<small class="error" v-if="form.url && !isValidUrl(form.url)"
|
||||
>La url no es válida</small
|
||||
>
|
||||
<br />
|
||||
<FormInput
|
||||
v-model="form.sku"
|
||||
type="text"
|
||||
label-text="SKU o identificador"
|
||||
/>
|
||||
</div>
|
||||
<div class="cont-col">
|
||||
<label for="imagen">Imagen</label>
|
||||
<client-only>
|
||||
<FormInputImage :imageUrl="form.image" @change="handleImage" />
|
||||
</client-only>
|
||||
</div>
|
||||
<!-- <div class="cont-col">
|
||||
<input
|
||||
id="imagen"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
class="imagenInput"
|
||||
placeholder="Elige una imagen"
|
||||
@change="handleImage"
|
||||
/>
|
||||
</div> -->
|
||||
<div class="cont-col">
|
||||
<label for="">Descripción del producto</label>
|
||||
<textarea v-model="form.description" class="textarea" type="text" />
|
||||
</div>
|
||||
<div class="cont">
|
||||
<FormInput v-model="form.stock" type="number" label-text="Stock" />
|
||||
<FormInput
|
||||
v-model="form.discount"
|
||||
step="0.01"
|
||||
type="number"
|
||||
label-text="Descuento"
|
||||
/>
|
||||
</div>
|
||||
<div class="cont-col">
|
||||
<FormInput v-model="form.identifiers" label-text="Identificador único" />
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="fieldset">
|
||||
<FormHeader title="categorías" />
|
||||
<p class="help-text">
|
||||
Estos datos ayudan a que tu producto sea encontrado por los clientes.
|
||||
Procura completar el mayor número de campos posibles. Los campos
|
||||
señalados con asterisco (*) son obligatorios, los demás son opcionales.
|
||||
</p>
|
||||
|
||||
<div class="cont-col">
|
||||
<label for="category">Categoría*</label>
|
||||
<b-form-input
|
||||
v-model="form.category"
|
||||
autocomplete="off"
|
||||
list="my-list-id"
|
||||
id="category"
|
||||
></b-form-input>
|
||||
|
||||
<datalist id="my-list-id">
|
||||
<option v-for="(choice, index) in categories" :key="index">
|
||||
{{ choice }}
|
||||
</option>
|
||||
</datalist>
|
||||
<!-- <b-form-select required v-model="form.category" name="" id="category">
|
||||
<option disabled value="">Categoría</option>
|
||||
<option
|
||||
v-for="(category, key) in categories"
|
||||
:key="key"
|
||||
:value="category"
|
||||
>
|
||||
{{ category }}
|
||||
</option>
|
||||
</b-form-select> -->
|
||||
</div>
|
||||
<div class="cont-col">
|
||||
<label for="tags-basic">Palabras clave</label>
|
||||
<b-form-tags
|
||||
v-model="form.tags"
|
||||
placeholder="Añade palabras clave (moda, complementos...)"
|
||||
input-id="tags-basic"
|
||||
></b-form-tags>
|
||||
</div>
|
||||
<div class="cont-col">
|
||||
<label for="tags-basic">Atributos</label>
|
||||
<b-form-tags
|
||||
v-model="form.attributes"
|
||||
placeholder="Añade características del producto (talla, color...)"
|
||||
input-id="tags-basic"
|
||||
></b-form-tags>
|
||||
</div>
|
||||
</fieldset>
|
||||
<FormHeader title="envío" />
|
||||
<fieldset class="fieldset">
|
||||
<div class="cont-col">
|
||||
<label for="">Condiciones de envío</label>
|
||||
<p class="help-text">
|
||||
Aquí podrás indicar las condiciones de envío específicas para este
|
||||
producto. Si no lo rellenas se mostrarán las opciones por defecto que
|
||||
puedes editar en tu formulario de edición de la Cooperativa en el
|
||||
apartado condiciones de envío.
|
||||
</p>
|
||||
<textarea v-model="form.shipping_terms" class="textarea" type="text" />
|
||||
</div>
|
||||
<div class="cont-col">
|
||||
<FormInput
|
||||
v-model="form.shipping_cost"
|
||||
step="0.01"
|
||||
type="number"
|
||||
label-text="Gastos de envío"
|
||||
placeholder="ej. 4,50"
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="submit-btn" align="center">
|
||||
<SubmitButton text="guardar" imageUrl="" />
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormInputImage from './FormInputImage.vue'
|
||||
import dataProcessing from '~/utils/dataProcessing'
|
||||
export default {
|
||||
components: { FormInputImage },
|
||||
props: ['productForm'],
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
sku: '',
|
||||
name: '',
|
||||
description: '',
|
||||
image: null,
|
||||
url: '',
|
||||
price: '',
|
||||
shipping_cost: '',
|
||||
shipping_terms: '',
|
||||
source: 'MANUAL',
|
||||
active: true,
|
||||
discount: '',
|
||||
stock: null,
|
||||
category: '',
|
||||
tags: [],
|
||||
attributes: [],
|
||||
identifiers: '',
|
||||
},
|
||||
// tagsArray: ['tag1', 'tag2', 'tag3', 'tag33'],
|
||||
categories: [
|
||||
'Alimentación, bebida y tabaco',
|
||||
'Arte y ocio',
|
||||
'Bebés y niños pequeños',
|
||||
'Bricolaje',
|
||||
'Cámaras y ópticas',
|
||||
'Casa y jardín',
|
||||
'Economía e industria',
|
||||
'Electrónica',
|
||||
'Elementos religiosos y ceremoniales',
|
||||
'Equipamiento deportivo',
|
||||
'Juegos y juguetes',
|
||||
'Maletas y bolsos de viaje',
|
||||
'Material de oficina',
|
||||
'Mobiliario',
|
||||
'Multimedia',
|
||||
'Productos para adultos',
|
||||
'Productos para mascotas y animales',
|
||||
'Ropa y accesorios',
|
||||
'Salud y belleza',
|
||||
'Software',
|
||||
'Vehículos y recambios',
|
||||
],
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.productForm) {
|
||||
Object.keys(this.form).forEach((key) => {
|
||||
this.form[key] = this.productForm[key]
|
||||
})
|
||||
}
|
||||
this.getAllCategories()
|
||||
},
|
||||
|
||||
methods: {
|
||||
isValidUrl: dataProcessing.isValidUrl,
|
||||
async getAllCategories() {
|
||||
const { data } = await this.$api.get(`/products/all_categories/`)
|
||||
this.categories = data
|
||||
},
|
||||
parseForm() {
|
||||
const formData = new FormData()
|
||||
|
||||
Object.keys(this.form).forEach((key) => {
|
||||
if ((key === 'tags') | (key === 'attributes')) {
|
||||
formData.append(key, JSON.stringify(this.form[key]))
|
||||
} else if (this.form[key] || this.form[key] === '')
|
||||
formData.append(key, this.form[key])
|
||||
})
|
||||
if (typeof formData.get('image') === 'string') {
|
||||
formData.delete('image')
|
||||
}
|
||||
|
||||
return formData
|
||||
},
|
||||
|
||||
async handleImage(e) {
|
||||
this.form.image = e
|
||||
},
|
||||
|
||||
sendProduct() {
|
||||
const formData = this.parseForm()
|
||||
this.$emit('send', formData)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.form {
|
||||
@include desktop {
|
||||
width: 40%;
|
||||
}
|
||||
@include tablet {
|
||||
width: 60%;
|
||||
}
|
||||
@include mobile {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.imagenInput {
|
||||
font-size: $s;
|
||||
}
|
||||
|
||||
label, .label {
|
||||
text-align: left;
|
||||
color: $color-navy;
|
||||
font-weight: $bold;
|
||||
font-size: $xs;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
width: 100%;
|
||||
background-color: $color-grey-inputs;
|
||||
border: 1px solid $color-grey-inputs-border;
|
||||
border-radius: 4px;
|
||||
padding: 10px 5px;
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
.fieldset {
|
||||
margin-top: 40px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
.cont {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@include mobile {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.cont-col {
|
||||
margin: 15px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
h3 {
|
||||
color: $color-navy;
|
||||
font-size: $m;
|
||||
margin: 10px 0 15px 0;
|
||||
}
|
||||
}
|
||||
.submit-btn {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.help-text {
|
||||
text-align: left;
|
||||
font-size: $xs;
|
||||
text-align: justify;
|
||||
font-weight: $regular;
|
||||
color: $color-greylayout;
|
||||
font-family: $font-secondary;
|
||||
background-color: $color-light;
|
||||
margin-bottom: 20px;
|
||||
margin-top: -10px;
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
color: $color-greytext;
|
||||
}
|
||||
}
|
||||
.error {
|
||||
color: $color-error;
|
||||
}
|
||||
</style>
|
||||
@@ -1,15 +1,154 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Editar producto /editar/productos/:id</h1>
|
||||
</div>
|
||||
<BContainer class="container">
|
||||
<BRow align-h="center">
|
||||
<BCol class="edit-product">
|
||||
<h1 class="title">Editar producto</h1>
|
||||
<ProductForm :product-form="form" @send="submitProduct" />
|
||||
<div v-if="form.source !== 'MANUAL'" class="data">
|
||||
<dl class="creation-data">
|
||||
<dt>Fecha de creación:</dt>
|
||||
<dd>{{ formatDatetime(form.created) }}</dd>
|
||||
|
||||
<dt>Fecha de actualización:</dt>
|
||||
<dd>{{ formatDatetime(form.updated) }}</dd>
|
||||
</dl>
|
||||
<dl class="import-data">
|
||||
<dt>Fecha de importación:</dt>
|
||||
<dd>{{ formatDatetime(form.sync_date) }}</dd>
|
||||
|
||||
<dt>Importado desde:</dt>
|
||||
<dd>{{ form.source }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</BCol>
|
||||
</BRow>
|
||||
</BContainer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
import dataProcessing from '~/utils/dataProcessing'
|
||||
export default {
|
||||
setup() {
|
||||
definePageMeta({
|
||||
layout: 'editar',
|
||||
middleware: 'auth',
|
||||
auth: { authority: 'COOP_MANAGER' },
|
||||
})
|
||||
const auth = useAuthStore();
|
||||
return {
|
||||
auth
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
data() {
|
||||
return {
|
||||
form: {},
|
||||
}
|
||||
},
|
||||
|
||||
async created() {
|
||||
try {
|
||||
const config = useRuntimeConfig()
|
||||
const form = await $fetch(`my_products/${this.$route.params.id}/`, {
|
||||
baseURL: config.public.baseURL,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.auth.access}`,
|
||||
},
|
||||
})
|
||||
if (form.source !== 'MANUAL') {
|
||||
try {
|
||||
const result = await $fetch(`history/${form.history}/`, {
|
||||
baseURL: config.public.baseURL,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.auth.access}`,
|
||||
},
|
||||
})
|
||||
form.sync_date = result.data.sync_date
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
form.sync_date = null
|
||||
}
|
||||
}
|
||||
return { form }
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
formatDatetime: dataProcessing.formatDatetime,
|
||||
|
||||
async submitProduct(value) {
|
||||
try {
|
||||
await this.$api.put(`/my_products/${this.$route.params.id}/`, value, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
})
|
||||
this.$bvToast.toast(`Producto actualizado correctamente`, {
|
||||
title: 'latienda.coop',
|
||||
autoHideDelay: 5000,
|
||||
appendToast: true,
|
||||
})
|
||||
} catch {
|
||||
this.$bvToast.toast(`Ha habido un error`, {
|
||||
title: 'latienda.coop',
|
||||
autoHideDelay: 5000,
|
||||
appendToast: true,
|
||||
variant: 'danger',
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.container {
|
||||
margin-top: 40px;
|
||||
margin-bottom: 80px;
|
||||
}
|
||||
.edit-product {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.title {
|
||||
color: $color-navy;
|
||||
font-size: $xxl;
|
||||
margin-bottom: 60px;
|
||||
width: 60%;
|
||||
}
|
||||
.help-text {
|
||||
border: 1px solid $color-greylayout;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
text-align: justify;
|
||||
hyphens: auto;
|
||||
font-size: $xs;
|
||||
font-weight: $regular;
|
||||
color: $color-greylayout;
|
||||
font-family: $font-secondary;
|
||||
background-color: $color-light;
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
color: $color-greytext;
|
||||
}
|
||||
}
|
||||
|
||||
.data {
|
||||
margin: 0 100px;
|
||||
margin-top: 80px;
|
||||
font-size: $xs;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: $color-greytext;
|
||||
}
|
||||
.creation-data,
|
||||
.import-data {
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user