Appearance
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/nuxtIn 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/nuxtIn 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 →
