今天我們要升級昨天的code,
src/components/FuelGauge.vue
(FuelGauge.vue
燃料表:負責“拉桿 UI” + 元件版 v-model)
<input type="range">
滑桿。modelValue
當外部給的值,拉動時發出 update:modelValue
。<template>
<div class="gauge">
<label>燃料:</label>
<input type="range" min="0" max="100" :value="modelValue"
@input="$emit('update:modelValue', +$event.target.value)" />
<span>{{ modelValue }}%</span>
</div>
</template>
<script setup>
defineProps({ modelValue: { type: Number, required: true } })
defineEmits(['update:modelValue'])
</script>
<style scoped>
.gauge { display:flex; gap:8px; align-items:center; }
input[type="range"] { width: 160px; }
</style>
ShipCard.vue
支援 slot(艦艇卡片:負責單卡展示 + 事件回報 + 插槽擴充)
<template>
<main class="mc">
<h1>🛰️ Mission Control(Day 13)</h1>
<div class="toolbar">
<label><input type="checkbox" v-model="onlyReady" />只顯示「就緒」</label>
<span class="stats">艦隊:{{ ships.length }};就緒:{{ readyCount }};出擊:{{ launchedTotal }}</span>
</div>
<ul class="fleet">
<li v-for="s in filteredShips" :key="s.id">
<ShipCard
:ship="s"
:isSelected="selectedId === s.id"
@select="onSelect(s.id)"
@toggle-ready="onToggleReady(s.id)"
@launch="onLaunch(s.id)"
@update:fuel="val => onFuelChange(s.id, val)"
>
<!-- named slot:客製標題(scoped:拿到 ship) -->
<template #title="{ ship }">
<h3>🪐 {{ ship.name }} <small style="opacity:.7">({{ ship.type }})</small></h3>
</template>
<!-- named slot:客製動作列,加上「補給」 -->
<template #actions="{ ship, emit }">
<button @click="emit('toggle-ready')">{{ ship.ready ? '設為維修' : '設為就緒' }}</button>
<button class="primary" @click="emit('launch')">發射</button>
<button @click="refuel(ship.id)">補給 +25%</button>
</template>
</ShipCard>
</li>
</ul>
<section v-if="current" class="detail">
<h2>🔎 已選取:{{ current.name }}</h2>
<p>類型:{{ current.type }} 燃料:{{ current.fuel }}% 狀態:{{ current.ready ? '就緒' : '維修中' }}</p>
<p>出擊次數:{{ current.launches }}</p>
</section>
</main>
</template>
<script setup>
import { ref, computed } from 'vue'
import ShipCard from './components/ShipCard.vue'
const ships = ref([
{ id: 1, name: 'Orion', type: 'scout', fuel: 80, ready: true, launches: 1 },
{ id: 2, name: 'Nova', type: 'frigate', fuel: 55, ready: false, launches: 0 },
{ id: 3, name: 'Aquila', type: 'carrier', fuel: 92, ready: true, launches: 3 },
{ id: 4, name: 'Draco', type: 'destroyer', fuel: 35, ready: false, launches: 0 }
])
const selectedId = ref(null)
const onlyReady = ref(false)
const filteredShips = computed(() => onlyReady.value ? ships.value.filter(s => s.ready) : ships.value)
const current = computed(() => ships.value.find(s => s.id === selectedId.value) || null)
const readyCount = computed(() => ships.value.filter(s => s.ready).length)
const launchedTotal = computed(() => ships.value.reduce((sum, s) => sum + s.launches, 0))
const onSelect = (id) => { selectedId.value = selectedId.value === id ? null : id }
const onToggleReady = (id) => { const t = ships.value.find(s => s.id === id); if (t) t.ready = !t.ready }
const onLaunch = (id) => {
const t = ships.value.find(s => s.id === id)
if (!t) return
if (!t.ready) return alert('尚未就緒,無法出擊。')
if (t.fuel < 20) return alert('燃料過低,請補給。')
t.launches++; t.fuel = Math.max(0, t.fuel - 20)
alert(`🚀 ${t.name} 已出擊!`)
}
const onFuelChange = (id, val) => {
const t = ships.value.find(s => s.id === id)
if (t) t.fuel = val
}
const refuel = (id) => {
const t = ships.value.find(s => s.id === id)
if (t) t.fuel = Math.min(100, t.fuel + 25)
}
</script>
<style scoped>
/* 可沿用 Day12 的樣式 */
.mc { max-width: 920px; margin: 40px auto; font: 16px/1.6 ui-sans-serif, system-ui; }
.toolbar { display: flex; gap: 16px; align-items: center; margin-bottom: 12px; }
.stats { color: #64748b; }
.fleet { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px; padding: 0; list-style: none; }
.detail { margin-top: 16px; padding: 12px; border: 1px solid #1f2a44; border-radius: 12px; background: #0b1020; color: #dbeafe; }
</style>
App.vue
:吃 slot、接住 update:fuel
ships
。ship
傳下去;接住子層 emit 的事件(select
、toggle-ready
、launch
、update:fuel
)後真的更新資料。#title
、#actions
插槽客製卡片的標題與按鈕(例如「補給 +25%」)。<template>
<main class="mc">
<h1>🛰️ Mission Control(Day 13)</h1>
<div class="toolbar">
<label><input type="checkbox" v-model="onlyReady" />只顯示「就緒」</label>
<span class="stats">艦隊:{{ ships.length }};就緒:{{ readyCount }};出擊:{{ launchedTotal }}</span>
</div>
<ul class="fleet">
<li v-for="s in filteredShips" :key="s.id">
<ShipCard
:ship="s"
:isSelected="selectedId === s.id"
@select="onSelect(s.id)"
@toggle-ready="onToggleReady(s.id)"
@launch="onLaunch(s.id)"
@update:fuel="val => onFuelChange(s.id, val)"
>
<!-- named slot:客製標題(scoped:拿到 ship) -->
<template #title="{ ship }">
<h3>🪐 {{ ship.name }} <small style="opacity:.7">({{ ship.type }})</small></h3>
</template>
<!-- named slot:客製動作列,加上「補給」 -->
<template #actions="{ ship, emit }">
<button @click="emit('toggle-ready')">{{ ship.ready ? '設為維修' : '設為就緒' }}</button>
<button class="primary" @click="emit('launch')">發射</button>
<button @click="refuel(ship.id)">補給 +25%</button>
</template>
</ShipCard>
</li>
</ul>
<section v-if="current" class="detail">
<h2>🔎 已選取:{{ current.name }}</h2>
<p>類型:{{ current.type }} 燃料:{{ current.fuel }}% 狀態:{{ current.ready ? '就緒' : '維修中' }}</p>
<p>出擊次數:{{ current.launches }}</p>
</section>
</main>
</template>
<script setup>
import { ref, computed } from 'vue'
import ShipCard from './components/ShipCard.vue'
const ships = ref([
{ id: 1, name: 'Orion', type: 'scout', fuel: 80, ready: true, launches: 1 },
{ id: 2, name: 'Nova', type: 'frigate', fuel: 55, ready: false, launches: 0 },
{ id: 3, name: 'Aquila', type: 'carrier', fuel: 92, ready: true, launches: 3 },
{ id: 4, name: 'Draco', type: 'destroyer', fuel: 35, ready: false, launches: 0 }
])
const selectedId = ref(null)
const onlyReady = ref(false)
const filteredShips = computed(() => onlyReady.value ? ships.value.filter(s => s.ready) : ships.value)
const current = computed(() => ships.value.find(s => s.id === selectedId.value) || null)
const readyCount = computed(() => ships.value.filter(s => s.ready).length)
const launchedTotal = computed(() => ships.value.reduce((sum, s) => sum + s.launches, 0))
const onSelect = (id) => { selectedId.value = selectedId.value === id ? null : id }
const onToggleReady = (id) => { const t = ships.value.find(s => s.id === id); if (t) t.ready = !t.ready }
const onLaunch = (id) => {
const t = ships.value.find(s => s.id === id)
if (!t) return
if (!t.ready) return alert('尚未就緒,無法出擊。')
if (t.fuel < 20) return alert('燃料過低,請補給。')
t.launches++; t.fuel = Math.max(0, t.fuel - 20)
alert(`🚀 ${t.name} 已出擊!`)
}
const onFuelChange = (id, val) => {
const t = ships.value.find(s => s.id === id)
if (t) t.fuel = val
}
const refuel = (id) => {
const t = ships.value.find(s => s.id === id)
if (t) t.fuel = Math.min(100, t.fuel + 25)
}
</script>
<style scoped>
/* 可沿用 Day12 的樣式 */
.mc { max-width: 920px; margin: 40px auto; font: 16px/1.6 ui-sans-serif, system-ui; }
.toolbar { display: flex; gap: 16px; align-items: center; margin-bottom: 12px; }
.stats { color: #64748b; }
.fleet { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px; padding: 0; list-style: none; }
.detail { margin-top: 16px; padding: 12px; border: 1px solid #1f2a44; border-radius: 12px; background: #0b1020; color: #dbeafe; }
</style>
結果:
如此我們就獲得一個可以控制火箭的控制台了,當燃料不足發射時,我們可以手動加入燃料,讓火箭持續運作或休息,在我們鐵人賽的後面5天,我們也會用到這個部份來完成我們的小小專題!
明天我們將繼續更了解Vue.js的其他功能~!