Vue.js course Β· Module 6: Components

Project: Mars Dashboard

3 min read
In this lesson2

Final project of the 3D Printer Bay! Build a complete NOVA LAB station dashboard with multiple components, lifecycles, and composables.

Module Summary

In this module you learned the fundamentals of building Vue.js applications from components - just as NOVA LAB station consists of modular habitat parts, your application consists of components:

  • Components - creating self-contained, reusable UI elements with their own template (<template>), logic (<script setup>) and styles (<style>). Each component is a separate .vue file with clearly defined responsibility
  • Component import - local registration (import in a given component) and global registration (available throughout the app via app.component()). In Composition API with <script setup>, imported components are automatically available in the template
  • Composition - building complex interfaces from simple, smaller components. The parent-child pattern, passing data down and emitting events up the component tree
  • Lifecycle hooks - onMounted (after DOM insertion), onUpdated (after update), onUnmounted (after removal), onBeforeMount, onBeforeUpdate - allow executing code at key moments in a component's life
  • Scoped styles - style isolation in components via the scoped attribute on the <style> tag. Styles are automatically limited to the given component and don't affect the rest of the application
  • Composables - reusable functions (e.g., useTimer, useFetch) that encapsulate reactive state and logic. Allow sharing logic between components without duplicating code

Project Requirements

Build a Mars Dashboard - the central control panel of NOVA LAB station, consisting of multiple cooperating components:

1. StatusBar Component

Display the overall station status: station name, current date and time (updated every second via a composable), energy level (progress bar), external temperature, and habitat pressure. Use onMounted for data initialization and onUnmounted for timer cleanup.

2. ModuleList Component

A list of habitat modules (e.g., Oxygen Generator, Water Recycler, Solar Panels, Communication Array) with the ability to toggle each module's status (ACTIVE/STANDBY/OFFLINE). Each module should be rendered as a separate ModuleCard component with appropriate status colors.

3. useTimer Composable

Create a useTimer composable that counts time since application start (mission uptime). Use ref for state, onMounted to start setInterval, and onUnmounted to clear the interval (clearInterval). The composable should return formatted time (HH:MM:SS).

4. Scoped styles

Each component should have its own isolated styles with the scoped attribute. Use a dark space theme (dark backgrounds, neon accent colors: green for ACTIVE, yellow for STANDBY, red for OFFLINE). Ensure responsiveness - the dashboard should work at different screen sizes.

Expand the starter below!

Code for this lesson: App.vue
1<script setup>
2import { ref, onMounted, onUnmounted } from 'vue'
3
4// Mars Dashboard - Final project of module 6
5// Demonstration of components, lifecycle hooks, composables
6
7// Composable: useTimer
8function useTimer() {
9  const elapsed = ref(0)
10  let interval = null
11
12  onMounted(() => {
13    interval = setInterval(() => { elapsed.value++ }, 1000)
14  })
15
16  onUnmounted(() => {
17    if (interval) clearInterval(interval)
18  })
19
20  const formatted = ref('')
21  setInterval(() => {
22    const h = Math.floor(elapsed.value / 3600)
23    const m = Math.floor((elapsed.value % 3600) / 60)
24    const s = elapsed.value % 60
25    formatted.value = [h, m, s].map(v => String(v).padStart(2, '0')).join(':')
26  }, 1000)
27
28  return { elapsed, formatted }
29}
30
31const { formatted: uptime } = useTimer()
32
33const stationName = ref('NOVA LAB Mars Base')
34const energyLevel = ref(87)
35
36const modules = ref([
37  { id: 1, name: 'Laboratory', status: true },
38  { id: 2, name: 'Reactor', status: true },
39  { id: 3, name: 'Communication', status: false },
40  { id: 4, name: 'Greenhouse', status: true },
41  { id: 5, name: 'Workshop', status: true }
42])
43
44function toggleModule(id) {
45  const mod = modules.value.find(m => m.id === id)
46  if (mod) mod.status = !mod.status
47}
48
49// TODO: Extract StatusBar and ModuleList into separate components
50// TODO: Add scoped styles to each component
51// TODO: Extend the useTimer composable with start/stop
52</script>
53
54<template>
55  <div class="mars-dashboard">
56    <!-- StatusBar -->
57    <header class="status-bar">
58      <h1>{{ stationName }}</h1>
59      <div class="info">
60        <span>Uptime: {{ uptime }}</span>
61        <span>Energy: {{ energyLevel }}%</span>
62      </div>
63    </header>
64
65    <!-- ModuleList -->
66    <section class="module-list">
67      <h2>Habitat Modules</h2>
68      <div
69        v-for="mod in modules"
70        :key="mod.id"
71        class="module-item"
72        :class="{ online: mod.status, offline: !mod.status }"
73        @click="toggleModule(mod.id)"
74      >
75        <span class="dot"></span>
76        <span>{{ mod.name }}</span>
77        <span class="tag">{{ mod.status ? 'ONLINE' : 'OFFLINE' }}</span>
78      </div>
79    </section>
80  </div>
81</template>
82
83<style scoped>
84.mars-dashboard { background: #0a0a2e; color: #e0e0ff; padding: 2rem; min-height: 100vh; font-family: monospace; }
85.status-bar { border-bottom: 1px solid #333; padding-bottom: 1rem; margin-bottom: 1rem; }
86.status-bar h1 { color: #ff6b35; margin: 0; }
87.info { display: flex; gap: 2rem; color: #888; margin-top: 0.5rem; }
88.module-list h2 { color: #00d4ff; }
89.module-item { display: flex; align-items: center; gap: 1rem; padding: 0.8rem; margin: 0.5rem 0; border-radius: 4px; cursor: pointer; background: #1a1a4e; }
90.module-item.online { border-left: 3px solid #00ff88; }
91.module-item.offline { border-left: 3px solid #ff4444; opacity: 0.7; }
92.dot { width: 8px; height: 8px; border-radius: 50%; }
93.online .dot { background: #00ff88; }
94.offline .dot { background: #ff4444; }
95.tag { margin-left: auto; font-size: 0.8rem; }
96</style>

Spotted a mistake in this lesson?

Hands-on tasks in the game

  • Code editor

    Build a MarsDashboard with multiple components, composables, lifecycle hooks, and scoped styles.

Useful articles