- Notifications
You must be signed in to change notification settings - Fork0
Curso-VUE/3-vue-shop
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
- Crear proyecto con Vue UI
- Instalación y configuración de los plugins
- Iniciar un módulo con Vuex para manejar los productos de la aplicación
- Acciones del móduloproducts haciendo petición HTTP conasync yawait
- Definir la lógica del listado de productos
- Primera versión del sitado de productos paginados
- Componente que representa un producto
- Componente con slots para definir un layout
- Módulo del carrito de compras
- Añadir productos al carrito desde el listado de productos
- Componente carrito
- Persistir Vuex en LocalStorage
- Persistir Vuex en IndexedDB, ideal para PWAs
Creamos el nuevo proyecto a través devue ui
Vamos a utilizar los siguientes plugins:
Instalamos ambos plugins mediante el comandonpm i bootstrap-vue vue-paginate
Para cargar los plugins en la aplicación es buena práctica crear un directorioplugins ensrc donde incluiremos un archivo .js por cada plugin que vayamos a utilizar.
- bootstrap-vue.js
importVuefrom'vue'import{BootstrapVue}from'bootstrap-vue'import'bootstrap/dist/css/bootstrap.css'import'bootstrap-vue/dist/bootstrap-vue.css'Vue.use(BootstrapVue)
- vue-paginate
importVuefrom'vue'importVuePaginatefrom'vue-paginate'Vue.use(VuePaginate)
Crearemos también en la carpetaplugins unindex.js donde requeriremos los dos ficheros que acabamos de crear.
require('./bootstrap-vue');require('./vue-paginate');
Finalmente enmain.js requerimos elindex.js deplugins después de las importaciones.
importVuefrom'vue'importAppfrom'./App.vue'importstorefrom'./store'require('./plugins');...
Para generar el dataset utilizamoshttps://www.mockaroo.com/ que es un generador de datos aleatorios.
Vamos a generar un dataset con elementos que tengan los parámetros siguientes:
- id
- name
- picture
- price
- stock
Guardamos su contenido en un nuevo archivopublic/fixtures/products.json.
En este proyecto vamos a trabajar con elstore mediante módulos, así que dentro desrc creamos una carpetamodules y tantas carpetas como módulos necesitemos. En este caso, por ahora únicamente necesitaremos una carpetaproducts.
Dentro de la carpetaproducts creamos un archivo para las actions, otro para los mutations, otro para el state y finalmente un index que importe los anteriores.
- state.js
exportdefault{products:[]}
- mutations.js
exportfunctionsetProducts(state,products){state.products=products;}
actions.js (por ahora vacío)
index.js
importstatefrom'./state';import*asmutationsfrom'./mutations';import*asactionsfrom'./actions';constnamespaced=true;exportdefault{ namespaced, state, mutations, actions}
Finalmente importamos el módulo en elstore.
importVuefrom'vue'importVuexfrom'vuex'Vue.use(Vuex)importproductsfrom'../modules/products';exportdefaultnewVuex.Store({modules:{ products}})
Vamos a utilizar las acciones para realizar peticiones asíncronas y actualizar elstore.
Configuramos la acción en/src/modules/products/actions.js:
exportasyncfunctionfetchProducts({ commit}){constdata=awaitfetch('/fixtures/products.json');constproducts=awaitdata.json();commit('products/setProducts',products,{root:true});}
En el commit hay que especificar la ruta del mutation que vamos a utilizar, el nuevo valor y { root: true} para indicar que el estado se encuentra en la raiz.
Eliminamos el componenteHelloWorld.vue así como su importación, declaración y renderizado deapp.vue. Eliminamos también los estilos, de forma que nos quede el componente limpio.
En generamos un nuevo componenteProductList. En él configuramos el mapState y el mapActions para acceder al estado y a las acciones del móduloproducts.
Configuramos el hookmounted para obtener los productos una vez el componente se haya cargado.
En el template, visualizamos la cantidad de productos que se han cargado para comprobar que funciona correctamente.
<template><div>{{products.length}}</div></template><script>import{mapActions,mapMutations,mapState}from'vuex';exportdefault{mounted(){this.fetchProducts()},computed:{ ...mapState('products',['products'])},methods:{ ...mapActions('products',['fetchProducts'])}}</script>
Configuramos la paginación en los datos deProductList:
data(){return{paginate:['products'],perPage:3}},
Creamos una primera aproximación del template de componenteProductList.
Primero establecemos que se visualice un alert cuando no haya datos de productos:
<template><divv-if="products.length"></div><b-alertv-elseshowvariant="info">No hay productos para mostar</b-alert></template>
Para la lista de productos paginados utilizamos la etiquetapaginate en eldiv:
<paginatename="products":list="products":per="perPage"><pv-for="product in paginated('products')":key="product.id">{{product.name}}</p></paginate>
Al mismo nivel que paginate renderizamos los links de las siguientes páginas mediante la etiquetapaginate-links:
<paginate-linksfor="products":limit="10":classes="{ 'ul': 'pagination', 'li': 'page-item', 'li > a': 'page-link' }"></paginate-links>
El límite indica la cantidad máxima de paginate-links que se muestran. Con la configuración de clases podemos añadir una clase a los elementos que decidamos.
Creamos el template del nuevo componenteProductItem:
<template><b-card:title="product.name":img-src="product.picture":img-alt="product.name"img-topstyle="max-width: 30rem;"class="mb-2"><b-buttonblockvariant="warning"@click="$emit('addToCart', product)"> Añadir al carrito</b-button></b-card></template>
Definimos en el script las props que nos llegarán del componente padre (en este caso product):
<script>exportdefault{props:{product:{type:Object,required:true,}}}</script>
EnProductList renderizamos el nuevo componente sustityyendo lo que teníamos dentro de las etiquetaspaginate:
<b-card-groupcolumns><product-itemv-for="product in paginated('products')":key="product.id":product="product"@addTOCart="addProductToCart"></product-item></b-card-group>
Creamos un método para manejar el evento que se genera la hacer click en el botón del componente hijo:
methods:{ ...mapActions('products',['fetchProducts']),addProductToCart(product){console.log(product)}}
Vamos a utilizar slots para definir un layout en donde mostrar los productos y el carrito.
Generamos un nuevo componente ShopLayout en el que renderizaremos a 9 columnas la lista de productos y a 3 columnas el carrito:
<template><b-container><b-row><b-colcols="9"><slotname="product-list"></slot></b-col><b-colcols="3"><slotname="cart"></slot></b-col><b-row></b-container></template>
Utilizamos el nuevo componente enapp.vue:
- En el template:
<template><divid="app"><shop-layout><templateslot="product-list"><product-list></product-list></template><templateslot="cart"> Carrito</template></shop-layout></div></template>
- En el script:
<script>importProductListfrom"./components/ProductList.vue";importShopLayoutfrom"./components/ShopLayout.vue";exportdefault{name:"App",components:{ ShopLayout, ProductList},};</script>
Creamos una nuevo módulocart que tendrástate,mutations ygetters.
- index.js: Al igual que en el móduloproducts exportamos los elementos del módulo:
importstatefrom'./state';import*asmutationsfrom'./mutations';import*asgettersfrom'./getters';constnamespaced=true;exportdefault{ namespaced, state, mutations, getters}
- state.js: El estado del módulo contentrá el carrito de la compra
exportdefault{cart:[]}
- mutations.js: Exsportamos una funciones para añadir un producto a la lista y otra para eliminar un producto.
import{find,filter}from'lodash';exportfunctionaddProduct(state,product){constproductInCart=find(state.cart,{id:product.id});if(!productInCart){constcopy=Object.assign({},product);copy.qty=1;state.cart.push(copy);}else{productInCart.qty+=1;}}exportfunctionremoveProductFromCart(state,product){state.cart=filter(state.cart,({id})=>id!==product.id);}
- getters.js: Losgetters permiten obtener los datos del state filtrados o calculados.
exportfunctiontotalCost(state){returnstate.cart.reduce((acc,product)=>{returnacc+parseFloat(product.price)*product.qty},0);}
Vamos a definir el métodoaddProductToCart del componenteProductList mapeando la mutación del módulocart:
methods:{ ...mapActions('products',['fetchProducts']), ...mapMutations('cart',['addProduct']),addProductToCart(product){this.addProduct(product);}}
Generamos el componentecart y en el script definimos los siguientes elementos:
- data: array con los nombres de los campos de cada producto que utilizaremos en una tabla de bootstrap.
- computed:state ygetters decart
- methods:mutations decart.
<script>import{mapGetters,mapMutations,mapState}from'vuex';exportdefault{data(){return{fields:['name','qty','price','actions']}},computed:{ ...mapState('cart',['cart']), ...mapGetters('cart',['totalCost'])},methods:{ ...mapMutations('cart',['removeProductFromCart'])}}</script>
El template quedaría de la siguiente forma:
<template><divv-if="cart.length"><b-tablestripedhover:items="cart":fields="fields"><templatev-slot:cell(actions)="cell"><b-buttonsize="sm"variant="danger"@click.stop="removeProductFromCart(cell.item)"> Eliminar</b-button></template></b-table><b-alertshowvariant="success"class="text-center"> Coste total: {{ totalCost }}€</b-alert></div><b-alertv-elseshowvariant="info"> No hay productos en el carrito</b-alert></template>
Importamos el componentecart al componente principal,app:
...<templateslot="cart"><cart></cart></template>...
...importCartfrom"./components/Cart.vue"; components:{ShopLayout,ProductList,Cart},};...
Vamos a instalar la dependenciavuex-persist en el proyecto.
Realizamos la modificaciones necesarias en elstore:
importVuefrom'vue'importVuexfrom'vuex'importVuexPersistencefrom'vuex-persist'importproductsfrom'../modules/products';importcartfrom'../modules/cart';Vue.use(Vuex)constvuexLocal=newVuexPersistence({storage:window.localStorage})exportdefaultnewVuex.Store({modules:{ products, cart},plugins:[vuexLocal.plugin]})
De esta forma se guardarán en LocalStorage el estado de todos los módulos de nuestra aplicación. Si decidimosalmacenar el estado de sólo algunos módulos, debemos indicarlo en la configuración del plugin:
constvuexLocal=newVuexPersistence({storage:window.localStorage,modules:['cart']})
Vamos a utilizar el pluginlocalForage
Únicamente debemos modificar la configuración del storage en VuexPersistance...
importlocalForagefrom'localforage'constvuexLocal=newVuexPersistence({storage:localForage,asyncStorage:true,modules:['cart']})
About
Resources
Uh oh!
There was an error while loading.Please reload this page.