Skip to content

Kapitel 7: Layouts & Komponentenentwicklung

In diesem Kapitel lernen Sie, wie Sie Layouts erstellen, wiederverwendbare Komponenten entwickeln und UI-Bibliotheken integrieren.


7.1 Layout-System

Layouts definieren die globale Struktur Ihrer Anwendung (Header, Footer, Sidebar).

Standard-Layout erstellen (layouts/default.vue):

vue
<template>
  <div class="default-layout">
    <!-- Header -->
    <AppHeader />

    <!-- Hauptinhalt -->
    <main class="main-content">
      <slot />  <!-- Hier wird die Seite eingefügt -->
    </main>

    <!-- Footer -->
    <AppFooter />
  </div>
</template>

<style scoped>
.default-layout {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.main-content {
  flex: 1;
  padding: 20px;
  max-width: 1200px;
  margin: 0 auto;
  width: 100%;
}
</style>

Layout einer Seite zuweisen:

pages/about.vue:

vue
<script setup>
definePageMeta({
  layout: 'default'  // Verwendet layouts/default.vue
})
</script>

<template>
  <div>
    <h1>Über uns</h1>
    <p>Dies ist die Über-uns-Seite.</p>
  </div>
</template>

Benutzerdefiniertes Layout erstellen:

layouts/admin.vue (für Admin-Bereich):

vue
<template>
  <div class="admin-layout">
    <AdminSidebar />
    <div class="admin-content">
      <slot />
    </div>
  </div>
</template>

<style scoped>
.admin-layout {
  display: flex;
  min-height: 100vh;
}

.admin-content {
  flex: 1;
  padding: 20px;
}
</style>

Verwendung:

vue
<!-- pages/admin/dashboard.vue -->
<script setup>
definePageMeta({
  layout: 'admin'
})
</script>

7.2 Benutzerdefinierte Komponenten

Komponente mit Props erstellen:

components/ProductCard.vue:

vue
<script setup>
// Props definieren
const props = defineProps({
  title: {
    type: String,
    required: true
  },
  price: {
    type: Number,
    default: 0
  },
  image: {
    type: String,
    default: '/placeholder.jpg'
  }
})

// Emit-Event definieren
const emit = defineEmits(['add-to-cart'])
</script>

<template>
  <div class="product-card">
    <img :src="image" :alt="title" class="product-image" />
    <h3 class="product-title">{{ title }}</h3>
    <p class="product-price">{{ price.toFixed(2) }} €</p>
    <button @click="emit('add-to-cart')" class="add-to-cart-btn">
      In den Warenkorb
    </button>
  </div>
</template>

<style scoped>
.product-card {
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 15px;
  text-align: center;
}

.product-image {
  width: 100%;
  height: 200px;
  object-fit: cover;
  border-radius: 4px;
}

.product-title {
  font-size: 1.2em;
  margin: 10px 0;
}

.product-price {
  color: #00dc82;
  font-weight: bold;
  font-size: 1.1em;
}

.add-to-cart-btn {
  background: #00dc82;
  color: white;
  border: none;
  padding: 10px 20px;
  border-radius: 4px;
  cursor: pointer;
}
</style>

Komponente verwenden:

vue
<!-- pages/products/index.vue -->
<script setup>
const products = ref([
  { id: 1, title: 'Product 1', price: 29.99, image: '/product1.jpg' },
  { id: 2, title: 'Product 2', price: 49.99, image: '/product2.jpg' }
])

const addToCart = (productId) => {
  console.log('Produkt hinzugefügt:', productId)
}
</script>

<template>
  <div class="products-page">
    <h1>Unsere Produkte</h1>
    <div class="products-grid">
      <ProductCard
        v-for="product in products"
        :key="product.id"
        :title="product.title"
        :price="product.price"
        :image="product.image"
        @add-to-cart="addToCart(product.id)"
      />
    </div>
  </div>
</template>

<style scoped>
.products-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 20px;
  margin-top: 20px;
}
</style>

7.3 Slots verwenden

Slots ermöglichen es, Inhalte in Komponenten einzufügen.

Standard-Slot:

components/Card.vue:

vue
<template>
  <div class="card">
    <header v-if="$slots.header" class="card-header">
      <slot name="header" />
    </header>
    <div class="card-body">
      <slot />  <!-- Standard-Slot -->
    </div>
    <footer v-if="$slots.footer" class="card-footer">
      <slot name="footer" />
    </footer>
  </div>
</template>

<style scoped>
.card {
  border: 1px solid #ddd;
  border-radius: 8px;
  overflow: hidden;
}

.card-header {
  background: #f5f5f5;
  padding: 15px;
  font-weight: bold;
}

.card-body {
  padding: 15px;
}

.card-footer {
  background: #f5f5f5;
  padding: 10px 15px;
}
</style>

Slot verwenden:

vue
<template>
  <Card>
    <template #header>
      <h2>Kartentitel</h2>
    </template>

    <p>Dies ist der Hauptinhalt der Karte.</p>
    <p>Mehrere Absätze sind möglich.</p>

    <template #footer>
      <button>OK</button>
      <button>Abbrechen</button>
    </template>
  </Card>
</template>

Scoped Slots (Daten an Slot übergeben):

components/List.vue:

vue
<script setup>
const items = ref(['Item 1', 'Item 2', 'Item 3'])
</script>

<template>
  <ul>
    <li v-for="(item, index) in items" :key="index">
      <slot :item="item" :index="index" />
    </li>
  </ul>
</template>

Verwendung:

vue
<template>
  <List v-slot="{ item, index }">
    <span>{{ index + 1 }}. {{ item }}</span>
  </List>
</template>

7.4 Drittanbieter-UI-Bibliotheken integrieren

Option 1: Element Plus (Vue 3 UI-Bibliothek)

Installieren:

bash
pnpm add element-plus @element-plus/nuxt

In nuxt.config.ts konfigurieren:

typescript
export default defineNuxtConfig({
  modules: [
    '@element-plus/nuxt'
  ],
  elementPlus: {
    locales: ['de']  // Deutsch
  }
})

Verwendung:

vue
<template>
  <div>
    <el-button type="primary">Primary Button</el-button>
    <el-input v-model="input" placeholder="Bitte eingeben" />
    <el-table :data="tableData">
      <el-table-column prop="date" label="Datum" />
      <el-table-column prop="name" label="Name" />
    </el-table>
  </div>
</template>

Option 2: Naive UI (Vue 3 UI-Bibliothek)

Installieren:

bash
pnpm add naive-ui @naive-ui/nuxt

In nuxt.config.ts:

typescript
export default defineNuxtConfig({
  modules: [
    '@naive-ui/nuxt'
  ]
})

Verwendung:

vue
<template>
  <n-space>
    <n-button type="primary">Primary</n-button>
    <n-input v-model:value="value" placeholder="Eingabe" />
    <n-date-picker />
  </n-space>
</template>

7.5 Allgemeine Komponenten kapseln

Für wiederkehrende UI-Elemente sollten Sie eigene Komponenten kapseln.

Beispiel: components/BaseButton.vue

vue
<script setup>
const props = defineProps({
  variant: {
    type: String,
    default: 'primary',
    validator: (val) => ['primary', 'secondary', 'danger'].includes(val)
  },
  size: {
    type: String,
    default: 'medium',
    validator: (val) => ['small', 'medium', 'large'].includes(val)
  },
  disabled: {
    type: Boolean,
    default: false
  }
})

const emit = defineEmits(['click'])
</script>

<template>
  <button
    :class="['base-button', `base-button--${variant}`, `base-button--${size}`]"
    :disabled="disabled"
    @click="emit('click')"
  >
    <slot>Button</slot>
  </button>
</template>

<style scoped>
.base-button {
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-weight: 500;
  transition: all 0.3s;
}

.base-button--primary {
  background: #00dc82;
  color: white;
}

.base-button--secondary {
  background: #f5f5f5;
  color: #333;
}

.base-button--danger {
  background: #ff4d4f;
  color: white;
}

.base-button--small {
  padding: 5px 10px;
  font-size: 0.8em;
}

.base-button--medium {
  padding: 10px 20px;
  font-size: 1em;
}

.base-button--large {
  padding: 15px 30px;
  font-size: 1.2em;
}

.base-button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
</style>

Verwendung:

vue
<template>
  <BaseButton variant="primary" size="large" @click="handleClick">
    Klick mich!
  </BaseButton>
</template>

7.6 Zusammenfassung

In diesem Kapitel haben Sie gelernt:

  • ✅ Layouts zu erstellen und zu verwenden (layouts/-Verzeichnis)
  • ✅ Benutzerdefinierte Komponenten mit Props & Emits zu erstellen
  • ✅ Slots zu verwenden (Standard, Benannt, Scoped)
  • ✅ Drittanbieter-UI-Bibliotheken (Element Plus, Naive UI) zu integrieren
  • ✅ Allgemeine Komponenten zu kapseln (BaseButton)

Nächste Schritte: Im nächsten Kapitel lernen wir Composables & kompositionelle Logikwiederverwendung – wie man wiederverwendbare Logik erstellt.


📚 Weiterführende Ressourcen


Nächstes Kapitel: Kapitel 8: Composables & kompositionelle Logik →

Frei für alle Anfänger