feat: Vue 3 Basis mit TailwindCSS, PrimeVUE, i18n und Dark/Light Mode

- Vue 3 + Vite Setup
- TailwindCSS für Styling
- PrimeVUE Komponenten
- vue-i18n (DE/EN)
- Dark/Light Mode Toggle
- Responsive Landingpage
- Docker-ready
This commit is contained in:
2026-02-10 22:11:57 +00:00
parent ec5864a4bd
commit de7ca59417
19 changed files with 371 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
node_modules
dist
.DS_Store
*.local

12
Dockerfile Normal file
View File

@@ -0,0 +1,12 @@
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

13
index.html Normal file
View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FluxKit</title>
<link rel="icon" href="/favicon.ico">
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

10
nginx.conf Normal file
View File

@@ -0,0 +1,10 @@
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}

27
package.json Normal file
View File

@@ -0,0 +1,27 @@
{
"name": "fluxkit",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.4.0",
"vue-router": "^4.2.0",
"vue-i18n": "^9.9.0",
"pinia": "^2.1.0",
"primevue": "^4.2.0",
"@primevue/themes": "^4.2.0",
"primeicons": "^7.0.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.0",
"vite": "^5.4.0",
"tailwindcss": "^3.4.0",
"postcss": "^8.4.0",
"autoprefixer": "^10.4.0"
}
}

6
postcss.config.js Normal file
View File

@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

51
src/App.vue Normal file
View File

@@ -0,0 +1,51 @@
<template>
<div class="min-h-screen bg-white dark:bg-gray-900 transition-colors">
<nav class="bg-purple-600 dark:bg-purple-800 text-white p-4">
<div class="container mx-auto flex justify-between items-center">
<router-link to="/" class="text-2xl font-bold">🚀 FluxKit</router-link>
<div class="flex items-center gap-4">
<router-link to="/" class="hover:underline">{{ $t('nav.home') }}</router-link>
<router-link to="/about" class="hover:underline">{{ $t('nav.about') }}</router-link>
<router-link to="/contact" class="hover:underline">{{ $t('nav.contact') }}</router-link>
<Select v-model="locale" :options="locales" optionLabel="name" optionValue="code"
class="w-24" @change="changeLocale" />
<Button :icon="themeStore.isDark ? 'pi pi-sun' : 'pi pi-moon'"
@click="themeStore.toggle()" rounded text />
</div>
</div>
</nav>
<main>
<router-view />
</main>
<footer class="bg-gray-100 dark:bg-gray-800 p-6 mt-12">
<div class="container mx-auto text-center text-gray-600 dark:text-gray-400">
© 2026 FluxKit. All rights reserved.
</div>
</footer>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useThemeStore } from './stores/theme'
import Button from 'primevue/button'
import Select from 'primevue/select'
const { locale } = useI18n()
const themeStore = useThemeStore()
const locales = [
{ name: 'DE', code: 'de' },
{ name: 'EN', code: 'en' }
]
const changeLocale = () => {
localStorage.setItem('locale', locale.value)
}
</script>

19
src/assets/main.css Normal file
View File

@@ -0,0 +1,19 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--color-bg: #ffffff;
--color-text: #1a1a1a;
}
.dark {
--color-bg: #1a1a1a;
--color-text: #ffffff;
}
body {
background-color: var(--color-bg);
color: var(--color-text);
transition: background-color 0.3s, color 0.3s;
}

10
src/i18n.js Normal file
View File

@@ -0,0 +1,10 @@
import { createI18n } from 'vue-i18n'
import de from './locales/de.json'
import en from './locales/en.json'
export default createI18n({
legacy: false,
locale: localStorage.getItem('locale') || 'de',
fallbackLocale: 'en',
messages: { de, en }
})

26
src/locales/de.json Normal file
View File

@@ -0,0 +1,26 @@
{
"nav": {
"home": "Startseite",
"about": "Über uns",
"contact": "Kontakt"
},
"hero": {
"title": "Willkommen bei FluxKit",
"subtitle": "Die moderne Lösung für Ihre digitalen Projekte",
"cta": "Jetzt starten"
},
"features": {
"title": "Unsere Features",
"fast": "Blitzschnell",
"fastDesc": "Optimierte Performance für beste Nutzererfahrung",
"secure": "Sicher",
"secureDesc": "Höchste Sicherheitsstandards für Ihre Daten",
"modern": "Modern",
"modernDesc": "Neueste Technologien für zukunftssichere Lösungen"
},
"theme": {
"light": "Hell",
"dark": "Dunkel"
},
"language": "Sprache"
}

26
src/locales/en.json Normal file
View File

@@ -0,0 +1,26 @@
{
"nav": {
"home": "Home",
"about": "About",
"contact": "Contact"
},
"hero": {
"title": "Welcome to FluxKit",
"subtitle": "The modern solution for your digital projects",
"cta": "Get Started"
},
"features": {
"title": "Our Features",
"fast": "Lightning Fast",
"fastDesc": "Optimized performance for the best user experience",
"secure": "Secure",
"secureDesc": "Highest security standards for your data",
"modern": "Modern",
"modernDesc": "Latest technologies for future-proof solutions"
},
"theme": {
"light": "Light",
"dark": "Dark"
},
"language": "Language"
}

29
src/main.js Normal file
View File

@@ -0,0 +1,29 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import PrimeVue from 'primevue/config'
import Aura from '@primevue/themes/aura'
import ToastService from 'primevue/toastservice'
import App from './App.vue'
import router from './router'
import i18n from './i18n'
import 'primeicons/primeicons.css'
import './assets/main.css'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.use(i18n)
app.use(PrimeVue, {
theme: {
preset: Aura,
options: {
darkModeSelector: '.dark'
}
}
})
app.use(ToastService)
app.mount('#app')

13
src/router/index.js Normal file
View File

@@ -0,0 +1,13 @@
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', name: 'home', component: HomeView },
{ path: '/about', name: 'about', component: () => import('../views/AboutView.vue') },
{ path: '/contact', name: 'contact', component: () => import('../views/ContactView.vue') }
]
})
export default router

17
src/stores/theme.js Normal file
View File

@@ -0,0 +1,17 @@
import { defineStore } from 'pinia'
import { ref, watch } from 'vue'
export const useThemeStore = defineStore('theme', () => {
const isDark = ref(localStorage.getItem('theme') === 'dark')
const toggle = () => {
isDark.value = !isDark.value
}
watch(isDark, (val) => {
localStorage.setItem('theme', val ? 'dark' : 'light')
document.documentElement.classList.toggle('dark', val)
}, { immediate: true })
return { isDark, toggle }
})

17
src/views/AboutView.vue Normal file
View File

@@ -0,0 +1,17 @@
<template>
<div class="container mx-auto px-4 py-12">
<h1 class="text-4xl font-bold mb-6 text-gray-800 dark:text-white">{{ $t('nav.about') }}</h1>
<Card>
<template #content>
<p class="text-gray-600 dark:text-gray-400">
FluxKit ist eine moderne Vue 3 Landingpage mit TailwindCSS, PrimeVue,
Internationalisierung und Dark/Light Mode Support.
</p>
</template>
</Card>
</div>
</template>
<script setup>
import Card from 'primevue/card'
</script>

16
src/views/ContactView.vue Normal file
View File

@@ -0,0 +1,16 @@
<template>
<div class="container mx-auto px-4 py-12">
<h1 class="text-4xl font-bold mb-6 text-gray-800 dark:text-white">{{ $t('nav.contact') }}</h1>
<Card>
<template #content>
<p class="text-gray-600 dark:text-gray-400 mb-4">Kontaktieren Sie uns!</p>
<Button label="E-Mail senden" icon="pi pi-envelope" />
</template>
</Card>
</div>
</template>
<script setup>
import Card from 'primevue/card'
import Button from 'primevue/button'
</script>

52
src/views/HomeView.vue Normal file
View File

@@ -0,0 +1,52 @@
<template>
<div>
<!-- Hero Section -->
<section class="bg-gradient-to-r from-purple-600 to-indigo-600 text-white py-20">
<div class="container mx-auto text-center px-4">
<h1 class="text-5xl font-bold mb-4">{{ $t('hero.title') }}</h1>
<p class="text-xl mb-8 opacity-90">{{ $t('hero.subtitle') }}</p>
<Button :label="$t('hero.cta')" icon="pi pi-arrow-right" iconPos="right" size="large" />
</div>
</section>
<!-- Features Section -->
<section class="py-16 bg-white dark:bg-gray-900">
<div class="container mx-auto px-4">
<h2 class="text-3xl font-bold text-center mb-12 text-gray-800 dark:text-white">
{{ $t('features.title') }}
</h2>
<div class="grid md:grid-cols-3 gap-8">
<Card class="text-center">
<template #content>
<i class="pi pi-bolt text-4xl text-purple-600 mb-4"></i>
<h3 class="text-xl font-semibold mb-2">{{ $t('features.fast') }}</h3>
<p class="text-gray-600 dark:text-gray-400">{{ $t('features.fastDesc') }}</p>
</template>
</Card>
<Card class="text-center">
<template #content>
<i class="pi pi-shield text-4xl text-purple-600 mb-4"></i>
<h3 class="text-xl font-semibold mb-2">{{ $t('features.secure') }}</h3>
<p class="text-gray-600 dark:text-gray-400">{{ $t('features.secureDesc') }}</p>
</template>
</Card>
<Card class="text-center">
<template #content>
<i class="pi pi-sparkles text-4xl text-purple-600 mb-4"></i>
<h3 class="text-xl font-semibold mb-2">{{ $t('features.modern') }}</h3>
<p class="text-gray-600 dark:text-gray-400">{{ $t('features.modernDesc') }}</p>
</template>
</Card>
</div>
</div>
</section>
</div>
</template>
<script setup>
import Button from 'primevue/button'
import Card from 'primevue/card'
</script>

12
tailwind.config.js Normal file
View File

@@ -0,0 +1,12 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{vue,js,ts,jsx,tsx}",
],
darkMode: 'class',
theme: {
extend: {},
},
plugins: [],
}

11
vite.config.js Normal file
View File

@@ -0,0 +1,11 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': '/src'
}
}
})