#4. Covid 19 Tracker(Vue版)

今天任务的实作内容,主要是参考这支影片
影片中使用的程序码风格是Vue的Option API,在我的专案中则是换成Composition API。Demo Link

Vue Components 规划

https://ithelp.ithome.com.tw/upload/images/20210905/20130534pp2nSYpfEJ.png

以下来看每个组件的程序码

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版本。

心得

  1. 用过Vue 3的Composition API就不太想回去Option API了。
  2. 这次使用的API的资料更新速度比较慢,下次会改用disease.sh来玩玩看。

明日任务

  1. Q&A区块效果(点击Q会展开A)
  2. 图片模糊载入效果(Vue版)
  3. 卡片扩展效果(Vue版)

<<:  #5 Types of CSS Selector

>>:  5.移转 Aras PLM大小事-Agile 汇出 Document

15 - NVM - Node.js 版本管理工具

Node.js 版本间的差异使得有些专案需要使用特定的版本才能正常运作,因此各专案间需要来回的切换 ...

D28 - 「来互相伤害啊!」:粗乃玩摇杆!

先建立游戏组件,以便加入後续内容。 src\components\window-app-cat-vs...

D6 allauth 采坑日记 Extending & Substituting User model (1)

我本来只是想在注册页面(Signup)增加手机号码(不需简讯验证) 结果被Allauth跟djang...

愿Alex老师安息,一路好走!

Alex老师是为台湾CISSP资安教育训练开创新局的好老师! 愿Alex老师安息,一路好走! Al...

虹语岚访仲夏夜-22(专业的小四篇)

「你又在看什麽? 我已经气到不想跟你说话了。」 『我才气好吗?  别生气啦...我把现在这个看完好吗...