今天任務的實作內容,主要是參考這支影片
影片中使用的程式碼風格是Vue的Option API,在我的專案中則是換成Composition API。Demo Link
以下來看每個組件的程式碼
Covid19Tracker.vue
// src/views/Covid19Tracker.vue
<template>
<home-btn/>
<div class="mt-20 bg-gray-200 h-full w-[80vw] shadow-md rounded-md">
<!-- Header -->
<header class="w-full text-center bg-green-800 text-white p-4 mb-10 rounded-t-md">
<div class="relative flex flex-row justify-center text-3xl md:text-5xl font-bold mb-3">
<Icon icon="fa-solid:viruses" class="mr-2 pb-1" height="36"/>
<p>Covid-19 Tracker</p>
</div>
<p>API By <a class="text-blue-300" href="https://covid19api.com" target="_blank">covid10api.com</a></p>
</header>
<!-- Main -->
<main class="p-10 pt-1" v-if="!loading">
<DataTitle :dataDate="dataDate" :text="title" />
<DataBoxes :stats="status" />
// 將get-country屬性綁定函式getCountryData,然後將該屬性送給子組件
<CountrySelect :countries="countries" @get-country="getCountryData" />
<button
v-if="status.Country"
class="bg-blue-700 text-white rounded p-3 mt-10 focus:outline-none hover:bg-blue-500"
@click="clearCountryData"
>
Clear Country
</button>
</main>
<main v-else class="flex flex-col align-center justify-center text-center pb-10">
<div class="text-gray-600 text-3xl m-10">
Fetching Data...
</div>
</main>
</div>
</template>
<script>
import { Icon } from '@iconify/vue'
import HomeBtn from '../components/HomeBtn.vue'
import CountrySelect from '../components/covid19-tracker/CountrySelect.vue'
import DataBoxes from '../components/covid19-tracker/DataBoxes.vue'
import DataTitle from '../components/covid19-tracker/DataTitle.vue'
import { ref } from 'vue'
export default {
name: 'Covid 19 Tracker',
components: {
Icon,
HomeBtn,
DataTitle,
DataBoxes,
CountrySelect
},
setup () {
// 透過 ref()來包裝原始型別、物件、陣列
const loading = ref(true)
const title = ref('Global')
const dataDate = ref('')
const status = ref({})
const countries = ref([])
// 透過api取得資料
const fetchCovidData = async () => {
const res = await fetch('https://api.covid19api.com/summary')
return await res.json()
}
// 取得特定國家的疫情資料
const getCountryData = country => {
status.value = country
title.value = country.Country
}
// 重新透過api取得資料,然後將value改為Global
const clearCountryData = async () => {
loading.value = true
const data = await fetchCovidData()
title.value = 'Global'
status.value = data.Global
loading.value = false
}
const baseSetup = async () => {
const data = await fetchCovidData()
dataDate.value = data.Date
status.value = data.Global
countries.value = data.Countries
loading.value = false
}
// 呼叫函式baseSetup來調用fetchCovidData
baseSetup()
// 回傳定義好的資料,渲染到template上的子組件
return {
loading,
title,
dataDate,
status,
countries,
getCountryData,
clearCountryData
}
}
}
</script>
DataTitle.vue
// src/components/covid19-tracker/DataTitle.vue
<template>
<div class="text-center">
<h2 class="text-3xl font-bold">{{ text }}</h2>
<div class="text-2xl mt-4 mb-10">
{{ timestamp }}
</div>
</div>
</template>
<script>
import dayjs from 'dayjs'
import { computed } from 'vue'
export default {
name: 'DataTitle',
props: ['text', 'dataDate'],
setup (props) {
const { dataDate } = { props }
return {
// 透過day.js顯示日期
timestamp: computed(() => dayjs(dataDate).format('MMMM D YYYY, h:mm:ss a'))
}
}
}
</script>
<style>
</style>
DataBoxes.vue
// src/components/covid19-tracker/DataBoxes.vue
<template>
<div class="grid md:grid-cols-2 gap-4">
<!-- box1 -->
<div class="shadow-md bg-blue-100 p-10 text-center rounded">
<h3 class="text-3xl text-blue-900 font-bold mb-4">Cases</h3>
<div class="text-2xl mb-4">
<span class="font-bold">New:</span>
{{ numberWithCommas(stats.NewConfirmed) }}
</div>
<div class="text-2xl mb-4">
<span class="font-bold">Total:</span>
{{ numberWithCommas(stats.TotalConfirmed) }}
</div>
</div>
<!-- box2 -->
<div class="shadow-md bg-blue-200 p-10 text-center rounded">
<h3 class="text-3xl text-blue-900 font-bold mb-4">Deaths</h3>
<div class="text-2xl mb-4">
<span class="font-bold">New:</span>
{{ numberWithCommas(stats.NewDeaths) }}
</div>
<div class="text-2xl mb-4">
<span class="font-bold">Total:</span>
{{ numberWithCommas(stats.TotalDeaths) }}
</div>
</div>
</div>
</template>
<script>
export default {
name: 'DataBoxes',
// 從父組件取得stats資料
props: ['stats'],
setup () {
return {
numberWithCommas (x) {
return x.toString()
// 用正規表達式將數字標記千位、百萬位...
.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
}
}
}
}
</script>
CountrySelect.vue
// src/components/covid19-tracker/CountrySelect.vue
<template>
<select @change="onChange"
v-model="selected"
class="form-select mt-10 block w-full border p-3 rounded outline-none cursor-pointer"
>
<!-- selected最初的value是0,因此最初會先顯示Select Country -->
<option value="0">Select Country</option>
<!-- 用v-for迴圈將countries陣列渲染出來 -->
<option v-for="country in countries" :value="country.ID" :key="country.ID">
{{ country.Country}}
</option>
</select>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'CountrySelect',
props: ['countries'],
setup (props, { emit }) {
const selected = ref(0)
return {
selected,
onChange () {
// 透過array.find比對出ID與selected.value相符的資料
const country = props.countries.find((item) => item.ID === selected.value)
// 使用從父組件送來的屬性get-country,該屬性會呼叫父組件的函式getCountryData,然後將country作為參數引用之。
emit('get-country', country)
}
}
}
}
</script>
同樣是這個Covid 19 Tracker,在我的一篇筆記有紀錄Vue的程式碼風格,依序練習自Option API、Composition API到Script Setup版本。