Vista previa de UI generada
Buenos días,
María 👋
4
semana
2.4k
kcal
87%
racha
Hoy
Push Day A
Iniciar
Cardio 5K
Hecho
Archivos generados en
src/views/HomePage.vue
views/WorkoutDetail.vue
views/ProfilePage.vue
components/WorkoutCard.vue
components/StatBadge.vue
components/TabBar.vue
router/index.js
Código generado — views/HomePage.vue
script
template
style
// views/HomePage.vue — generado por ui-to-vue-converter <script setup> import { ref, computed, onMounted } from 'vue' import { showToast } from 'vant' import WorkoutCard from '@/components/WorkoutCard.vue' import StatBadge from '@/components/StatBadge.vue' import TabBar from '@/components/TabBar.vue' import { useRouter } from 'vue-router' const router = useRouter() // --- estado del dashboard --- const user = ref({ name: 'María', avatar: '/assets/avatar-placeholder.png' }) const weeklyStats = ref({ sessionsThisWeek: 4, caloriesBurned: 2400, streakPct: 87 }) const todayWorkouts = ref([]) const loading = ref(false) // --- lógica --- const greeting = computed(() => { const h = new Date().getHours() if (h < 12) return 'Buenos días' if (h < 18) return 'Buenas tardes' return 'Buenas noches' }) onMounted(async () => { loading.value = true try { // TODO: reemplazar con llamada real a la API const res = await fetch('/api/workouts/today') todayWorkouts.value = await res.json() } catch { showToast('Error cargando los entrenos de hoy') } finally { loading.value = false } }) function startWorkout(id) { router.push({ name: 'WorkoutDetail', params: { id } }) } </script>
<template> <div class="home-page"> <!-- Barra superior --> <van-nav-bar :title="'FitTrack'" :right-text="'🔔'" fixed /> <!-- Saludo + gradiente --> <div class="greeting-header"> <p class="greeting-sub">{{ greeting }},</p> <h1 class="greeting-name">{{ user.name }} 👋</h1> </div> <!-- Stats semanales (componente compartido) --> <div class="stats-row"> <StatBadge v-for="(val, key) in weeklyStats" :key="key" :value="val" :label="key" /> </div> <!-- Lista de hoy --> <van-cell-group inset title="Hoy"> <van-loading v-if="loading" class="loading-center" /> <van-empty v-else-if="!todayWorkouts.length" description="Sin entrenos para hoy" /> <WorkoutCard v-else v-for="w in todayWorkouts" :key="w.id" :workout="w" @start="startWorkout(w.id)" /> </van-cell-group> <TabBar active-tab="home" /> </div> </template>
<style scoped> .home-page { min-height: 100vh; background: #f8fafc; padding-bottom: 64px; /* espacio para tabbar */ } .greeting-header { background: linear-gradient(135deg, #6c63ff, #a78bfa); color: #fff; padding: 56px 20px 20px; /* 56px por nav-bar fixed */ } .greeting-sub { font-size: 14px; opacity: 0.85; } .greeting-name { font-size: 22px; font-weight: 700; margin-top: 4px; } .stats-row { display: flex; gap: 8px; padding: 12px 16px; background: #fff; } .loading-center { display: flex; justify-content: center; padding: 24px; } </style>
Componente compartido — WorkoutCard.vue
WorkoutCard.vue
<script setup> // Extraído como shared porque aparece en HomePage y WorkoutDetail const props = defineProps({ workout: { type: Object, required: true } }) const emit = defineEmits(['start']) const statusColor = { done: '#22c55e', pending: '#6c63ff', skipped: '#ef4444' } </script> <template> <van-cell :title="workout.name" :label="workout.meta" is-link @click="emit('start')" > <template #icon> <div class="workout-icon">{{ workout.emoji }}</div> </template> <template #right-icon> <van-tag :color="statusColor[workout.status]" round > {{ workout.status === 'done' ? 'Hecho' : 'Iniciar' }} </van-tag> </template> </van-cell> </template>
Router — router/index.js
router/index.js
import { createRouter, createWebHistory } from 'vue-router' const routes = [ { path: '/', name: 'HomePage', component: () => import('@/views/HomePage.vue') }, { path: '/workout/:id', name: 'WorkoutDetail', component: () => import('@/views/WorkoutDetail.vue'), props: true }, { path: '/profile', name: 'ProfilePage', component: () => import('@/views/ProfilePage.vue') } ] export default createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes })
✅ Output Review Checklist
Páginas generadas bajo
views/Regiones repetidas extraídas en
components/ (WorkoutCard aparece en 2 vistas)Router compatible con Vue Router 4 (createWebHistory + lazy imports)
Componentes usan Vant 4 de forma consistente (van-cell, van-tag, van-nav-bar…)
Unidades CSS en px/vw, alineadas con mobile @3x baseline
Revisar copy de placeholder antes de commit (greeting, labels de stats)
Reemplazar
fetch('/api/workouts/today') con llamada real a la APIPasar por ESLint + Prettier + vue-tsc antes del primer commit