Lego tutorial

Az alábbiakban a Lego készletek című feladat megoldását fogom részletezni. A kék infoboxokban a feladat eredeti szövegei találhatóak, alattuk pedig a kifejtés, hogy hogyan kell megoldani. Ezt követően minden nagyobb egység alatt, a zöld lenyitható dobozokban látható a teljes fájl kódja.

Útvonalak

Készítse el a következő útvonalakat:
Útvonal neveKomponensUrlParaméterek
homeindex.vue/
setsets/[id].vue/sets/:idid: A készlet azonosítója
create-setsets/create.vue/sets/create
@pages/index.vue
<template>
   <BaseLayout>
     <h1 class="text-6xl my-10">Hello! 👋</h1>
   </BaseLayout>
</template>
<script>
  import BaseLayout from "@layouts/BaseLayout.vue"

  export default {
    components: {
      BaseLayout
    }
  }
</script>
<route lang="yaml">
  name: home
</route>
@pages/sets/[id
<template>
  <BaseLayout>
  </BaseLayout>
</template>
<script>
  import BaseLayout from "@layouts/BaseLayout.vue"

  export default {
    components: {
      BaseLayout
    }
  }
</script>
<route lang="yaml">
  name: set
</route>
@pages/sets/create.vue
<template>
  <BaseLayout>
  </BaseLayout>
</template>
<script>
  import BaseLayout from "@layouts/BaseLayout.vue"

  export default {
    components: {
      BaseLayout
    }
  }
</script>
<route lang="yaml">
  name: create-set
</route>

Tárolók

ThemeStore

Bővítse ki a stores/ThemeStore.js fájlban található tárolót egy themes állapottal. Készítse el az alábbi függvényeket:
state() {
  return {
    themes: []
  }
},
getThemes(): Kérje le az összes témát a Backendben elkészült végpontról és tárolja el az előbbi állapotban.
import { http } from "@utils/http"
actions: {
  async getThemes() {
    const response = await http.get(`themes`)
    this.themes = response.data.data
  },
},
getTheme(id): Azonosító alapján kérjen le egy témát és adja vissza.
async getTheme(id) {
  const response = await http.get(`themes/${id}`)
  return response.data.data
},

A @stores/ThemeStore.mjs teljes tartalma...

SetStore

Készítsen tárolót SetStore néven, amely tartalmaz egy sets állapotot. Készítse el az alábbi függvényeket

Hozzuk létre az src/stores mappában a SetStore.mjs fájlt, és készítsük el a tárolót az alábbi módon. Figyeljünk oda rá, hogy a state() függvény esetén, mindig térjünk vissza magával az állapottal, különben nem fog helyesen működni a tároló!

import { defineStore } from "pinia"

export const useSetStore = defineStore('set-store',{
  state(){
        return{
            sets:[]
        }
    },
})
getSets(): Kérje le az összes készletet a Backendben elkészült végpontról és tárolja el az előbbi állapotban
import { http } from '@utils/http'
actions:{
  async getSets(){
    const response = await http.get(`sets`)
    this.sets = response.data.data
  },
}
getSet(id): Azonosító alapján kérjen le egy készletet és adja vissza.
async getSet(id){
  const response = await http.get(`sets/${id}`)
  return response.data.data
},
createSet(data): Töltse fel a Backend megfelelő végpontjára a paraméterként kapott adatot.
async createSet(data){
  const response = await http.post(`sets`,data)
  this.sets.push(response.data.data)
}

A @stores/SetStore.mjs teljes tartalma...

Komponensek

BaseTable

Készítsen csíkozott táblázatot amber-100 és orange-100 színekkel, amely a következő oszlopokat tartalmazza rendre:Név, Korosztály, Elemszám, Értékelés, Ár

Hozzuk létre először is magát a komponents a components mappában BaseTable.vue néven és adjuk meg az alap tag-eket hozzá.

  <template>
  </template>
  <script>
  </script>

Ezt követően a <template>-n belül elkezdhetjük a táblázat megadását.

<table class="w-full">
  <thead>
    <tr>
      <th>Név</th>
      <th>Korosztály</th>
      <th>Elemszám</th>
      <th>Értékelés</th>
      <th>Ár</th>
    </tr>
  </thead>
  <tbody>
    <tr class="odd:bg-amber-100 even:bg-orange-100">
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
    </tr>
  </tbody>
</table>
A SetStore tároló alapján generálja a sorokat a táblázat.

Importáljuk be a szükséges dolgokat első körben a <script>-en belül.

import { useSetStore } from '@stores/SetStore'
import { mapState } from 'pinia'

Ezt követően kössük össze a sets állapotot a komponensünkkel, amelyet egy számított állapoton keresztül hajtunk végre.

export default {
    computed: {
        ...mapState(useSetStore, ['sets'])
    }
}

Mostmár meg tudjuk oldani, hogy a táblázat sorai ez alapján generálódjanak. Egy v-for segítségével végig megyünk a sets tömbön és legeneráljuk belőle a sorokat, amiben ki is töltjük a megfelelő adatokat.

Figyeljünk arra, hogy a :key="set.id" fontos, ne hagyjuk le, különben később problémát okoz a sorok kezelésében!

<tr v-for="set of sets" :key="set.id" class="odd:bg-amber-100 even:bg-orange-100">
  <td>{{ set.name }}</td>
  <td>{{ set.age_range }}</td>
  <td>{{ set.piece_count }}</td>
  <td>{{ set.rating }}</td>
  <td>{{ set.price }} Ft</td>
</tr>
A értékelés legyen egy span elem, amelynek paddingje 1 egységnyi, háttere yellow-500. Az név pedig legyen egy olyan hivatkozás, amely a készlet azonosítója alapján átvisz annak az oldalára!

Alakítsuk át először az értékelést, ami így fog kinézni nekünk ezután:

<td>
  <span class="p-1 bg-yellow-500">{{ set.rating }}</span>
</td>

És most nézzük a hivatkozást. Itt a <RouterLink> kmoponens segítségével fogjuk ezt megadni, ahol megadjuk az oldal nevét (set), ahova megyünk, illetve az átadni kívánt paramétereket (id).

<td>
  <RouterLink :to="{ name: 'set', params: { id: set.id } }">{{ set.name }}</RouterLink>
</td>
A cellák kapjanak 2 egységnyi belső eltartást és legyenek a mezők középre igazítottak!

Figyeljünk, hogy a cellák kifejezés az vonatkozik a fejlécre is!

<thead>
  <tr>
    <th class="p-2 text-center">Név</th>
    <th class="p-2 text-center">Korosztály</th>
    <th class="p-2 text-center">Elemszám</th>
    <th class="p-2 text-center">Értékelés</th>
    <th class="p-2 text-center">Ár</th>
  </tr>
</thead>
<tbody>
  <tr v-for="set of sets" :key="set.id" class="odd:bg-amber-100 even:bg-orange-100">
    <td class="p-2 text-center">
      <RouterLink :to="{ name: 'set', params: { id: set.id } }">{{ set.name }}</RouterLink>
    </td>
    <td class="p-2 text-center">{{ set.age_range }}</td>
    <td class="p-2 text-center">{{ set.piece_count }}</td>
    <td class="p-2 text-center"><span class="p-1 bg-yellow-500">{{ set.rating }}</span></td>
    <td class="p-2 text-center">{{ set.price }} Ft</td>
  </tr>
</tbody>

A @components/BaseTable.mjs teljes tartalma...

BaseCard

Készítsen kártyát, amely közepesen le van kerekítve és 1 pixeles körvonalat kap amber-600-as színnel.

Induljunk ki szintén egy üres komponensből, amit ugyanúgy a components mappában hozunk lére BaseCard.vue néven

  <template>
    <div class="rounded-md border border-amber-600"></div>
  </template>
  <script>
  </script>
Tartalmazzon a kártya egy harmadik színtű címsort, amely középre van igazítva, háttere amber-600, szöveg színe pedig fehér, valamint függőlegesen 2 egységnyi paddinget kapjon. Ezen kívül a teteje legyen szintén közepesen lekerekítve.
  <h3 class="bg-amber-600 text-white text-center rounded-t-md py-2"></h3>
Legyen egy bekezdés is a kártyán, amelynek belső eltartása 4 egységnyi.
<p class="p-4"></p>

::alert{type="info"}
A kártya vegyen át a szülőtől egy `title` és egy `content` tulajdonságot, amelyek szöveges típusúak. Ezeket helyezze el a kártyán a megfelelő helyre.
::

```html
<script>
export default {
    props: {
        title:  String,
        content:  String,
    }
}
</script>
<h3 class="bg-amber-600 text-white text-center rounded-t-md py-2">{{ title }}</h3>
<p class="p-4">{{ content }}</p>

A @components/BaseCard.vue teljes tartalma...

Gyökérkomponens

Állítsa be, hogy az oldal betöltésére, hogy az előbb két tároló kérje le az adatokat.

Importáljuk be a szükséges dolgokat első körben a <script>-en belül

import { mapActions } from 'pinia';
import { useSetStore } from '@stores/SetStore.mjs';
import { useThemeStore } from '@stores/ThemeStore.mjs';

Ezután kössük be a két lekérő függvényt a tárolókból a mapActions segítségével az export default {}-on belül

 methods:{
    ...mapActions(useSetStore, ['getSets']),
    ...mapActions(useThemeStore, ['getThemes'])
  },

Végül bővítski ki a mounted() függvénnyel, és hívjuk meg a két bekötött függvényt.

mounted(){
  this.getSets()
  this.getThemes()
}

A @/App.vue teljes tartalma...

Oldalak

Főoldal

Csak és kizárólag a főcímet, valamint BaseTable-t tartalmazza a BaseLayouton belül! A Főcím szövege legyen: “LEGO készletek”
<template>
  <BaseLayout>
    <h1 class="text-6xl my-10">LEGO készletek</h1>
    <BaseTable />
  </BaseLayout>
</template>

<script>
import BaseLayout from '@layouts/BaseLayout.vue'
import BaseTable from '@components/BaseTable.vue';

export default {
  components: {
    BaseLayout,
    BaseTable
  },
}
</script>
<route lang="yaml">
  name: home
</route>

Részletek oldal

Vegyen fel belső állapotot, amelyben eltárolja a készletet, amelyet az oldal betöltésére és az útvonal figyelése során is a SetStore segítségével kér le! Ezen kívül egy töltést jelző állapotot, amelyet az adatok betöltődését követően állít!

Készítsük el a belső állapotokat első körben.

<script>
export default {
  data(){
    set: null,
    loading: true
  }
}
</script>

Ezután importáljuk be a szükséges dolgokat

import { mapActions } from 'pinia'
import { useSetStore } from '@stores/SetStore.mjs'

Majd kössük be a megfelelő függvényünket a SetStore-ból

methods: {
        ...mapActions(useSetStore, ['getSet'])
    },

Végül oldjuk meg a lekérdezést az oldal betöltésére.

async mounted() {
        this.set = await this.getSet(this.$route.params.id)
        this.loading = false
    },
Készítsen számított állapotot, amely visszaadja, az árat a pénznemmel ellátva (pl: “94990 Ft”). Amennyiben az ár 0, úgy a következő szöveg jelenjen meg “Jelenleg nincs forgalomban”
 computed: {
        price(){
            if(this.set.price === 0){
                return "Jelenleg nincs forgalomban"
            }
            return `${this.set.price} Ft`
        }
    },
Amíg az oldal töltés alatt van, tartalmazza a BaseSpinner komponenst, amely középre van igazítva, felső margója 25 egység legyen.
import BaseSpinner from '@components/layout/BaseSpinner.vue'
components:{
        BaseCard,
        BaseLayout,
        BaseSpinner
    }
<template>
    <BaseLayout>
      <BaseSpinner v-if="loading" class="block mx-auto mt-25" />
    </BaseLayout>
</template>
Valamint tartalmazzon egy rácsrendszert is, amely 4 oszloppal és 4 egységnyi hézaggal rendelkezik. Az első oszlop minden nézeten töltse ki a teljes sort. A további oszlopok kis nézeten teljes sort töltse ki, közepes nézettől felfelé azonban a két-két oszlop egymás mellett foglaljon helyet, míg nagy kijelzőn egy sorba 4 oszlop jelenjen meg.
<div v-else class="grid gap-4 grid-cols-4">
  <div class="col-span-4"></div>
  <div class="col-span-4 md:col-span-2 lg:col-span-1"></div>
  <div class="col-span-4 md:col-span-2 lg:col-span-1"></div>
  <div class="col-span-4 md:col-span-2 lg:col-span-1"></div>
  <div class="col-span-4 md:col-span-2 lg:col-span-1"></div>
</div>
Az első oszlop egy főcímet tartalmazzon az téma nevével és a készlet nevével (Pl.: “Technic: Oracle Red Bull Racing RB20 F1 Car").
<div class="col-span-4">
  <h1 class="text-6xl my-10">{{ set.theme.name }}: {{ set.name }}</h1>
</div>
Ezt követően négy oszlop legyen, amelyek egy-egy BaseCard-ot tartalmazzon.
  1. Korosztály
  2. Elemszám
  3. Értékelés
  4. Ár
Az ár esetén a számított állapotot adja át!
<div v-else class="grid gap-4 grid-cols-4">
  <div class="col-span-4">
      <h1 class="text-6xl my-10">{{ set.theme.name }}: {{ set.name }}</h1>
  </div>
  <div class="col-span-4 md:col-span-2 lg:col-span-1">
    <BaseCard title="Korosztály" :content="set.age_range" />
  </div>
  <div class="col-span-4 md:col-span-2 lg:col-span-1">
    <BaseCard title="Elemszám" :content="set.piece_count" />
    </div>
  <div class="col-span-4 md:col-span-2 lg:col-span-1">
    <BaseCard title="Értékelés" :content="set.rating" />
  </div>
  <div class="col-span-4 md:col-span-2 lg:col-span-1">
    <BaseCard title="Ár" :content="price" />
  </div>
</div>

A @pages/sets/[id].vue teljes tartalma...

Készlet létrehozása oldal

Készíts űrlapot a következők alapján! Az űrlapon tiltsa le a beépített műveleteket és adja meg a típusát. A feladat során az űrlap formázását a keretrendszer elvégzi!
<FormKit type="form" :actions="false">
            
</FormKit>
Állítsa be a mezők típusát és validációját úgy, hogy az megfeleljen a Backendes validációknak (Request-ek)!
<FormKit type="form" :actions="false">
    <FormKit type="text" label="Készlet neve" name="name"/>
    <FormKit type="text" label="Korosztály" name="age_range"/>
    <FormKit type="number" label="Elemszám" name="piece_count"/>
    <FormKit type="number" label="Értékelés" name="rating"/>
    <FormKit type="number" label="Ár" name="price"/>
    <FormKit type="select" label="Téma" name="theme_id"/>
</FormKit>

A App\Http\Requests\StoreSetRequest tartalma...

Adja meg a legördülő lista számára, hogy az opciókat a ThemeStore-ban található themeOptions-ből töltse be.
import { mapState } from 'pinia'
import { useThemeStore } from '@stores/ThemeStore.mjs'
    computed:{
        ...mapState(useThemeStore, ['themeOptions'])
    },
<FormKit type="select" label="Téma" name="theme_id" :options="themeOptions"/>
Készítsen függvényt, amely lekezeli az űrlap elküldését, végrehajtja a SetStore segítségével az adatok továbbítását, ezt követően átirányít a főoldalra oldalra.
<FormKit type="submit" value="Új készlet rögzítése"/>
<FormKit type="form" :actions="false" @submit="submitForm">
import { mapState, mapActions } from 'pinia'
import { useSetStore } from '@stores/SetStore.mjs'
methods:{
        ...mapActions(useSetStore, ['createSet']),
}
async submitForm(data){
            await this.createSet(data)
            this.$router.push({name: 'home'})
        }

A @pages/sets/create.vue teljes tartalma...