Développement du cœur de métier
Ensuite, nous allons construire la logique principale de gestion des tâches dans App.vue.
Toute la logique est la suivante :
- Flux de données: Saisir le contenu dans le champ de saisie ->
v-modelupdatenewTaskTitle。 - Déclencheur d'action: Appuyer sur Entrée -> exécuter
addTask->tasksAjouter un élément au tableau. - Mise à jour de la vue:
tasksmodifications -> déclenchentfilteredTasksrecalcul automatique -> Vue effectue un nouveau rendulila liste. - Flux d'état: Cliquez sur le bouton de filtrage -> modifier
currentFilterla valeur -> déclencher à nouveaufilteredTasksle calcul -> la page affiche différentes catégories de tâches.
Mise en page de base et données réactives
Écrivez dans src/App.vue :
Exemple
<script setup>
import { ref, computed } from 'vue';
// 1. Modèle de données des tâches
const tasks = ref([
{ id: '1', title: 'Découvrez les nouvelles fonctionnalités de Tailwind v4', isCompleted: false },
{ id: '2', title: 'Maîtrisez la Composition API', isCompleted: true }
]);
const newTaskTitle = ref('');
const currentFilter = ref('all');
// 2. Méthodes principales
const addTask = () => {
const title = newTaskTitle.value.trim();
if (!title) return;
tasks.value.unshift({
id: crypto.randomUUID(),
title,
isCompleted: false
});
newTaskTitle.value = '';
};
const removeTask = (id) => {
tasks.value = tasks.value.filter(t => t.id !== id);
};
const toggleTask = (id) => {
const task = tasks.value.find(t => t.id === id);
if (task) task.isCompleted = !task.isCompleted;
};
// 3. Propriétés calculées pour le filtrage
const filteredTasks = computed(() => {
if (currentFilter.value === 'active') return tasks.value.filter(t => !t.isCompleted);
if (currentFilter.value === 'completed') return tasks.value.filter(t => t.isCompleted);
return tasks.value;
});
</script>
<template>
<div class="min-h-screen py-12 px-4">
<div class="max-w-md mx-auto bg-white rounded-3xl shadow-xl shadow-slate-200 border border-slate-100 overflow-hidden">
<header class="bg-linear-to-br from-blue-600 to-indigo-700 p-8 text-white">
<h1 class="text-3xl font-black tracking-tight">TaskHub</h1>
<p class="text-blue-100/80 text-sm">Vue 3 + Tailwind v4 en pratique</p>
</header>
<main class="p-6">
<div class="flex gap-2 mb-8">
<input
v-model="newTaskTitle"
@keyup.enter="addTask"
placeholder="Que faut-il faire aujourd'hui ?"
class="flex-1 bg-slate-50 border-none rounded-2xl px-4 py-3 focus:ring-2 focus:ring-brand/50 outline-none transition-all"
/>
<button @click="addTask" class="bg-brand text-white px-6 rounded-2xl font-bold hover:scale-105 active:scale-95 transition-all">
+
</button>
</div>
<div class="flex p-1 bg-slate-100 rounded-xl mb-6">
<button
v-for="f in ['all', 'active', 'completed']"
:key="f"
@click="currentFilter = f"
:classe="[
'flex-1 py-1.5 text-xs font-bold rounded-lg transition-all capitalize',
currentFilter === f ? 'bg-white text-brand shadow-sm' : 'text-slate-500'
]"
>
{{ f }}
</button>
</div>
<ul class="space-y-3">
<TransitionGroup name="list">
<li
v-for="task in filteredTasks"
:key="task.id"
class="group flex items-center justify-between p-4 bg-slate-50 rounded-2xl border border-transparent hover:border-slate-200 hover:bg-white transition-all"
>
<div class="flex items-center gap-3">
<input
type="checkbox"
:checked="task.isCompleted"
@change="toggleTask(task.id)"
class="w-5 h-5 accent-brand cursor-pointer"
/>
<span :class="['text-slate-700 font-medium', task.isCompleted ? 'line-through text-slate-400 opacity-50' : '']">
{{ task.title }}
</span>
</div>
<button @click="removeTask(task.id)" class="opacity-0 group-hover:opacity-100 text-slate-300 hover:text-red-500 transition-all p-1">
✕
</button>
</li>
</TransitionGroup>
</ul>
<div v-if="filteredTasks.length === 0" class="text-center py-12 text-slate-400 text-sm">
Aucune tâche associée pour le moment...
</div>
</main>
</div>
</div>
</template>
<style scoped>
/* Animation de transition de liste */
.list-enter-active, .list-leave-active { transition: all 0.4s cubic-bezier(0.18, 0.89, 0.32, 1.28); }
.list-enter-from, .list-leave-to { opacity: 0; transform: translateX(30px); }
</style>
import { ref, computed } from 'vue';
// 1. Modèle de données des tâches
const tasks = ref([
{ id: '1', title: 'Découvrez les nouvelles fonctionnalités de Tailwind v4', isCompleted: false },
{ id: '2', title: 'Maîtrisez la Composition API', isCompleted: true }
]);
const newTaskTitle = ref('');
const currentFilter = ref('all');
// 2. Méthodes principales
const addTask = () => {
const title = newTaskTitle.value.trim();
if (!title) return;
tasks.value.unshift({
id: crypto.randomUUID(),
title,
isCompleted: false
});
newTaskTitle.value = '';
};
const removeTask = (id) => {
tasks.value = tasks.value.filter(t => t.id !== id);
};
const toggleTask = (id) => {
const task = tasks.value.find(t => t.id === id);
if (task) task.isCompleted = !task.isCompleted;
};
// 3. Propriétés calculées pour le filtrage
const filteredTasks = computed(() => {
if (currentFilter.value === 'active') return tasks.value.filter(t => !t.isCompleted);
if (currentFilter.value === 'completed') return tasks.value.filter(t => t.isCompleted);
return tasks.value;
});
</script>
<template>
<div class="min-h-screen py-12 px-4">
<div class="max-w-md mx-auto bg-white rounded-3xl shadow-xl shadow-slate-200 border border-slate-100 overflow-hidden">
<header class="bg-linear-to-br from-blue-600 to-indigo-700 p-8 text-white">
<h1 class="text-3xl font-black tracking-tight">TaskHub</h1>
<p class="text-blue-100/80 text-sm">Vue 3 + Tailwind v4 en pratique</p>
</header>
<main class="p-6">
<div class="flex gap-2 mb-8">
<input
v-model="newTaskTitle"
@keyup.enter="addTask"
placeholder="Que faut-il faire aujourd'hui ?"
class="flex-1 bg-slate-50 border-none rounded-2xl px-4 py-3 focus:ring-2 focus:ring-brand/50 outline-none transition-all"
/>
<button @click="addTask" class="bg-brand text-white px-6 rounded-2xl font-bold hover:scale-105 active:scale-95 transition-all">
+
</button>
</div>
<div class="flex p-1 bg-slate-100 rounded-xl mb-6">
<button
v-for="f in ['all', 'active', 'completed']"
:key="f"
@click="currentFilter = f"
:classe="[
'flex-1 py-1.5 text-xs font-bold rounded-lg transition-all capitalize',
currentFilter === f ? 'bg-white text-brand shadow-sm' : 'text-slate-500'
]"
>
{{ f }}
</button>
</div>
<ul class="space-y-3">
<TransitionGroup name="list">
<li
v-for="task in filteredTasks"
:key="task.id"
class="group flex items-center justify-between p-4 bg-slate-50 rounded-2xl border border-transparent hover:border-slate-200 hover:bg-white transition-all"
>
<div class="flex items-center gap-3">
<input
type="checkbox"
:checked="task.isCompleted"
@change="toggleTask(task.id)"
class="w-5 h-5 accent-brand cursor-pointer"
/>
<span :class="['text-slate-700 font-medium', task.isCompleted ? 'line-through text-slate-400 opacity-50' : '']">
{{ task.title }}
</span>
</div>
<button @click="removeTask(task.id)" class="opacity-0 group-hover:opacity-100 text-slate-300 hover:text-red-500 transition-all p-1">
✕
</button>
</li>
</TransitionGroup>
</ul>
<div v-if="filteredTasks.length === 0" class="text-center py-12 text-slate-400 text-sm">
Aucune tâche associée pour le moment...
</div>
</main>
</div>
</div>
</template>
<style scoped>
/* Animation de transition de liste */
.list-enter-active, .list-leave-active { transition: all 0.4s cubic-bezier(0.18, 0.89, 0.32, 1.28); }
.list-enter-from, .list-leave-to { opacity: 0; transform: translateX(30px); }
</style>
Après l'exécution, l'interface est la suivante :

Points clés à retenir
1. API de composition
Le code ci-dessus illustre l'organisation de la logique centrale de Vue 3 :
ref(Bases de la réactivité):tasks、newTaskTitlesont des références réactives. Dans<script>le contexte, pour les modifier, il faut utiliser.value(tel quetasks.value = ...), mais dans<template>le contexte, vous écrivez directement le nom de la variable. Vue suit automatiquement les changements de ces valeurs et met à jour l'interface utilisateur.computed(Propriétés calculées):filteredTasksest une application très classique. Elle esttasksetcurrentFilterdérivée de.- Avantages: Elle est réactive et possèdeune capacité de mise en cache. Si
tasksn'a pas changé, lorsque la page est rendue plusieurs fois, elle ne réexécutera pas la logique de filtrage.
2. Rendu de listes et valeurs de clé
<li v-for="task in filteredTasks" :key="task.id">
:keyL'importance de: Dans Vue, les boucles de liste doivent fournirkey. Ici, nous utilisonscrypto.randomUUID()ID unique généré. Cela garantit que lorsque l'ordre de la liste change ou qu'un élément est supprimé, Vue peut identifier précisément le nœud DOM spécifique, évitant les erreurs de rendu (en particulier pour les listes avec animations ou états de saisie).
3. Gestion des événements et modificateurs
@keyup.enter: C'est unmodificateur de touche. Il faitaddTaskla fonction ne se déclenche que lorsque l'utilisateur appuie sur la touche Entrée, ce qui améliore considérablement l'expérience de saisie.- Passage de paramètres d'événement:
removeTask(task.id)montre comment transmettre les données de l'élément de boucle actuel à une fonction dans le template.
4. Liaison bidirectionnelle (v-model)
v-model="newTaskTitle"met en œuvre la synchronisation entre le champ de saisie et la variable.- Principe: C'est en fait
:valueet@inputsucre syntaxique pour.
5. Système d'animation (TransitionGroup)
- Ceci est un composant Vue intégré spécifiquement utilisé pour gérer les listes (
v-for) ajouts et suppressions. - Il bascule automatiquement les classes CSS lorsque les éléments entrent ou sortent (comme
.list-enter-from). Combiné avec le code ci-dessousstyle, il produit l'effet de glissement fluide des éléments de liste à l'entrée et à la sortie.
Explication des fonctionnalités de style de Tailwind v4
Comparé à la v3, Tailwind v4 met davantage l'accent surles capacités CSS nativesetla simplicité.。
1. Toute nouvelle syntaxe de dégradé
bg-linear-to-br: Dans la v4, les dégradés ne nécessitent plusbg-gradient-to-brcette syntaxe verbeuse. À la place, utilisez directementlinear-to-方位qui est plus proche du CSS natiflinear-gradient。
2. Gestion de l'opacité
text-blue-100/80:/80représente 80% d'opacité. La gestion de l'opacité des couleurs dans v4 est très intuitive, sans besoin de prédéfinir des configurations de couleur complexes.
3. Références aux variables de thème
bg-brand: Nous, dans le CSS@themedéfinir--color-brand. Dans v4, tant qu'une variable CSS est définie, Tailwind génère automatiquement les classes utilitaires correspondantes (telles quebg-brand,text-brand,border-brand), sans avoir à bricoler le fichier de configuration JS.
4. Puissantes pseudo-classes interactives
groupet `group-hover:opacity-100`:- Dans
lidéfini surgroup。 - Défini sur le bouton de suppression
group-hover:opacity-100。 - Effet: Permet d'afficher le bouton de suppression uniquement lorsque la souris survole cette ligne. Ce type deliaison d'état parent-enfantne nécessite pas d'écrire une seule ligne de code JS.
active:scale-95: Utilisez les pseudo-classes CSS pour obtenir rapidement un retour de mise à l'échelle du bouton au clic.
5. Bordures modernes et ombres d'anneau
focus:ring-2 focus:ring-brand/50: Lorsque le champ de saisie a le focus, utilisezringpropriété pour créer une bordure lumineuse externe,brand/50garantit que la lueur a un aspect translucide plutôt qu'une couleur unie rigide.