iT邦幫忙

2024 iThome 鐵人賽

DAY 27
0

哈囉大家好!今天要帶大家一起來打造一個超酷的鸚鵡鮮食計算機!不管你是不是程式新手,跟著我們一步一步來,保證你也能輕鬆搞定!讓我們一起來看看怎麼用 Nuxt 3 和 Pinia 來完成這個有趣的專案吧!

1. 設定 API Store

首先,我們要先設定 API Store。這個 Store 就像是一個神奇的工具箱,裡面放著我們需要的所有工具(也就是 API 函式)。

// stores/api.ts

import { defineStore } from 'pinia'
import axios from 'axios'

const baseURL = 'https://two024it-test-app.onrender.com'

const api = axios.create({
  baseURL
})

export const useApiStore = defineStore('api-store', {
  actions: {
    // 取得食物列表
    async fetchFoodList() {
      const response = await api.get('/freshfoods/')
      return response
    },
    // * 新增鮮食計算
    async calculateFood(data: any) {
      const response = await api.post('/foods/calculatefood', data)
      return response
    }
  }
})

這裡我們定義了兩個「動作」(actions):

  1. fetchFoodList:用來獲取所有可用的鮮食列表。
  2. calculateFood:用來計算特定食物的營養價值。

2. 實作計算機頁面

接下來,我們要開始製作計算機的主頁面。這個頁面就像是一個互動式的表單,讓使用者可以輸入資料,然後獲得計算結果。

<!-- pages/calculator.vue -->
<script setup lang="ts">
import { useApiStore } from '~/stores/api'
import { showLoading, hideLoading } from '~/stores/eventBus'

const apiStore = useApiStore()
const foodList = ref([])

// ... 更多程式碼 ...

</script>

<template>
  <!-- 這裡放置 HTML 結構 -->
</template>

在這個檔案中,我們:

  1. 引入了剛才建立的 API Store。
  2. 設定了一些變數來儲存食物列表和使用者的輸入。
  3. 準備了一個模板來顯示我們的頁面內容。

3. 實作資料獲取功能

現在,我們要實作獲取食物列表的功能:

// 取得食物列表
async function fetchFoods() {
  try {
    showLoading()
    const res = await apiStore.fetchFoodList()
    const result = res.data

    console.log(result)

    if (result && result.status === 'success') {
      foodList.value = result.data
      console.log(foodList.value)
    }
  } catch (e) {
    console.log(e)
  } finally {
    hideLoading()
  }
}

這個函式做了幾件事:

  1. 顯示一個載入中的畫面(showLoading())。
  2. 使用我們的 API Store 來獲取食物列表。
  3. 如果成功了,就把獲得的資料存到 foodList 中。
  4. 如果失敗了,就在控制台顯示錯誤訊息。
  5. 不管成功還是失敗,最後都會隱藏載入中的畫面(hideLoading())。
    這就像是派一個小助手去超市採購,回來後把採購清單交給我們,讓我們知道有哪些食材可以選擇。

昨天有做過,但還是再重複一次,怕有同學漏掉 🙌

4. 實作計算功能

接下來是重頭戲,我們要實作計算功能:

// 新增鮮食計算
async function submitCalculatefood() {
  try {
    showLoading()
    const res = await apiStore.calculateFood({
      weight: weight.value,
      activity: activity.value,
      foodId: foodId.value
    })
    const result = res.data

    console.log(result)

    if (result && result.status === 'success') {
      calculatefoodInfo.value = result.data
      console.log('新增成功')
    }
  } catch (e) {
    console.log(e)
  } finally {
    hideLoading()
  }
}

這個函式做的事情有:

  1. 再次顯示載入中的畫面。
  2. 將使用者輸入的資料(鸚鵡體重、活動水平和選擇的食物)送到伺服器進行計算。
  3. 如果計算成功,就將結果存到 calculatefoodInfo 中。
  4. 如果失敗,同樣在控制台顯示錯誤訊息。
  5. 最後隱藏載入中的畫面。
    這就像是把鸚鵡的資料和選擇的食物交給一位營養師,讓他幫我們計算出最適合的飲食方案。

5. 製作使用者介面

最後,我們要把所有東西組合起來,製作一個好用又好看的使用者介面:

5-1. 標籤頁區域

<div class="flex-wrapper">
  <div class="flex overflow-x-auto">
    <div
      class="calculator-tab"
      :class="{ 'calculator-tab-active': mode === 0 }"
      @click="mode = 0"
    >
      鮮食隨機配
    </div>
    <div
      class="calculator-tab"
      :class="{ 'calculator-tab-active': mode === 1 }"
      @click="mode = 1"
    >
      指定鮮食算攝取量
    </div>
    <div
      class="calculator-tab"
      :class="{ 'calculator-tab-active': mode === 2 }"
      @click="mode = 2"
    >
      指定熱量算鮮食
    </div>
    <div
      class="calculator-tab"
      :class="{ 'calculator-tab-active': mode === 3 }"
      @click="mode = 3"
    >
      指定飼料算攝取量
    </div>
    <div
      class="calculator-tab"
      :class="{ 'calculator-tab-active': mode === 4 }"
      @click="mode = 4"
    >
      指定熱量算飼料
    </div>
  </div>
</div>

這裡有五個標籤,每個都使用了動態類綁定和點擊事件:

  • :class="{ 'calculator-tab-active': mode === X }": 這是一個條件判斷。當 mode 的值等於對應的數字時,標籤會添加 calculator-tab-active 類,用於顯示當前選中的標籤。
  • @click="mode = X": 點擊事件,用於切換模式。

5-2. 內容區域

<div class="cus-border">
  <!-- 簡介 -->
  <div class="cus-intro">
    不知道該給鸚鵡吃什麼嗎?<br />
    輸入鸚鵡體重跟活動水平,我幫你推薦營養均衡的鮮食!
  </div>
  <hr class="cus-line-row" />
  
  <!-- 0 鮮食隨機配 -->
  <section v-if="mode === 0" class="">
    <div class="cus-block-padding">鮮食隨機配</div>
  </section>

  <!-- 1 指定鮮食算攝取量 -->
  <section v-else-if="mode === 1" class="text-sm sm:text-base">
    <!-- 表單內容 -->
  </section>
  
  <!-- 2 指定熱量算鮮食 -->
  <section v-else-if="mode === 2" class="">
    <div class="cus-block-padding">指定熱量算鮮食</div>
  </section>

  <!-- 3 指定飼料算攝取量 -->
  <section v-else-if="mode === 3" class="">
    <div class="cus-block-padding">指定飼料算攝取量</div>
  </section>

  <!-- 4 指定熱量算飼料 -->
  <section v-else-if="mode === 4" class="">
    <div class="cus-block-padding">指定熱量算飼料</div>
  </section>

</div>

這段程式碼實現了一個動態切換內容的鮮食計算機介面:

  1. 外層 <div> 使用 cus-border 類來設置整體樣式。
  2. 頂部有一個簡介區域,使用 cus-intro 類來設置樣式。
  3. 使用 v-ifv-else-if 指令來根據 mode 變數的值動態顯示不同的內容區塊。這實現了單頁面多功能的效果。
  4. 共有五種模式(0-4),每種模式對應不同的計算功能:
    • 鮮食隨機配
    • 指定鮮食算攝取量
    • 指定熱量算鮮食
    • 指定飼料算攝取量
    • 指定熱量算飼料
  5. 每個功能區塊都包裝在 <section> 標籤中,使用 cus-block-padding 類來設置內邊距。
  6. 「指定鮮食算攝取量」區塊使用額外的 text-sm sm:text-base 類,可能用於響應式文字大小調整。
    這種結構允許在單一頁面上靈活切換不同的功能,提高了使用者體驗和介面的整潔度。通過改變 mode 的值,可以輕鬆控制顯示哪個功能區塊。

5-3. 表單區域

<div class="cus-block-padding">
  <h2 class="cus-page-title">輸入基本資料</h2>

  <div class="cus-col-3">
    <div class="cus-col-1">
      <label for="weight" class="cus-label"
        >1. 體重 (g) <span class="text-red2">*</span></label
      >
      <input
        type="number"
        class="cus-input"
        id="weight"
        v-model="weight"
        placeholder="請輸入鸚鵡體重 (g)"
      />
      <span class="cus-input-note">基礎代謝率(BMR) = 175 * (體重 / 1000) ^ 0.75</span>
    </div>

    <div class="cus-col-1">
      <label for="activity" class="cus-label"
        >2. 活動水平 <span class="text-red2">*</span></label
      >
      <div class="cus-radio-row">
        <label class="cus-label-radio" for="low">
          <input
            type="radio"
            name="activity"
            class=""
            id="low"
            v-model="activity"
            value="low"
          />
          <span></span>
          低 - 平常不太活動
        </label>

        <label class="cus-label-radio" for="medium">
          <input
            type="radio"
            name="activity"
            class=""
            id="medium"
            v-model="activity"
            value="medium"
          />
          <span></span>
          中 - 適度活動
        </label>

        <label for="high" class="cus-label-radio">
          <input
            type="radio"
            name="activity"
            class=""
            id="high"
            v-model="activity"
            value="high"
          />
          <span></span>高 - 經常活動
        </label>
      </div>
      <span class="cus-input-note">活動水平用來調整 BMR,可以更符合需求</span>
    </div>

    <div class="cus-col-1" v-if="foodList.length">
      <label for="foodId" class="cus-label"
        >3. 選擇想計算的食物 <span class="text-red2">*</span></label
      >
      <select type="number" class="cus-input" id="foodId" v-model="foodId">
        <option value="" disabled selected>請選擇食物</option>
        <option v-for="food in foodList" :key="food._id" :value="food._id">
          {{ food.name }}
        </option>
      </select>
      <span class="cus-input-note">目前僅提供計算資料庫內的食物</span>
    </div>
  </div>

  <button
    class="cus-btn-primary mt-5"
    :disabled="!weight || !foodId"
    @click="submitCalculatefood"
  >
    開始計算
  </button>
</div>

這個表單區域包含了幾個重要的部分:

  1. 體重輸入:使用 v-model="weight" 綁定數據。
  2. 活動水平選擇:使用單選按鈕和 v-model="activity" 綁定數據。
  3. 食物選擇:使用 v-if="foodList.length" 判斷是否有食物列表,然後使用 v-for 循環顯示選項。
  4. 提交按鈕:使用 :disabled="!weight || !foodId" 判斷是否禁用按鈕。只有當體重和食物ID都有值時,按鈕才能點擊。

5-4. 結果顯示區域

      	<!-- 手機版結果顯示 -->
        <div v-if="calculatefoodInfo" class="lg:hidden">
          <hr class="cus-line-row" />
          <div class="cus-block-padding">
            <h2 class="cus-page-title">計算結果</h2>

            <!-- ? 每日基本營養需求 -->
            <h3 class="cus-title-border-left">每日基本營養需求</h3>
            <div class="cus-table mb-5">
              <div class="cus-table-row grid grid-cols-2">
                <div class="cus-table-th">熱量 (kcal/day)</div>
                <div class="cus-table-td">{{ calculatefoodInfo.dailyCalories }}</div>
              </div>
              <div class="cus-table-row grid grid-cols-2">
                <div class="cus-table-th">蛋白質 (g/day)</div>
                <div class="cus-table-td">{{ calculatefoodInfo.dailyProteinNeed }}</div>
              </div>
              <div class="cus-table-row grid grid-cols-2">
                <div class="cus-table-th">脂肪 (g/day)</div>
                <div class="cus-table-td">{{ calculatefoodInfo.dailyFatNeed }}</div>
              </div>
              <div class="grid grid-cols-2">
                <div class="cus-table-th">碳水化合物 (g/day)</div>
                <div class="cus-table-td">{{ calculatefoodInfo.dailyCarbsNeed }}</div>
              </div>
            </div>

            <!-- ? 食物營養資訊 -->
            <h3 class="cus-title-border-left">食物營養資訊</h3>
            <div class="cus-table mb-5">
              <div class="cus-table-row grid grid-cols-2">
                <div class="cus-table-th">食物名稱</div>
                <div class="cus-table-td">{{ calculatefoodInfo.food?.name }}</div>
              </div>
              <div class="cus-table-row grid grid-cols-2">
                <div class="cus-table-th">熱量 (kcal/100g)</div>
                <div class="cus-table-td">{{ calculatefoodInfo.food?.calories }}</div>
              </div>
              <div class="cus-table-row grid grid-cols-2">
                <div class="cus-table-th">蛋白質 (g/100g)</div>
                <div class="cus-table-td">{{ calculatefoodInfo.food?.protein }}</div>
              </div>
              <div class="cus-table-row grid grid-cols-2">
                <div class="cus-table-th">脂肪 (g/100g)</div>
                <div class="cus-table-td">{{ calculatefoodInfo.food?.fat }}</div>
              </div>
              <div class="cus-table-row grid grid-cols-2">
                <div class="cus-table-th">碳水化合物 (g/100g)</div>
                <div class="cus-table-td">{{ calculatefoodInfo.food?.carbs }}</div>
              </div>
              <div class="cus-table-row grid grid-cols-2">
                <div class="cus-table-th">每日最大攝取量 (g/day)</div>
                <div class="cus-table-td">{{ calculatefoodInfo.food?.maxIntake }}</div>
              </div>
              <div class="cus-table-row grid grid-cols-2">
                <div class="cus-table-th">食物備註</div>
                <div class="cus-table-td">{{ calculatefoodInfo.food?.note }}</div>
              </div>
              <div class="grid grid-cols-2">
                <div class="cus-table-th">好處</div>
                <div class="cus-table-td">{{ calculatefoodInfo.food?.nutrition }}</div>
              </div>
            </div>

            <!-- ? 食物建議攝取量 & 營養資訊 -->
            <h3 class="cus-title-border-left">食物建議攝取量 & 營養資訊</h3>
            <div class="cus-table mb-5">
              <div class="cus-table-row grid grid-cols-2">
                <div class="cus-table-th">建議攝取量 (g/day)</div>
                <div class="cus-table-td">{{ calculatefoodInfo.foodIntake }}</div>
              </div>

              <div class="cus-table-row grid grid-cols-2">
                <div class="cus-table-th">蛋白質量 (g/day)</div>
                <div class="cus-table-td">{{ calculatefoodInfo.nutrientsProvided?.protein }}</div>
              </div>

              <div class="cus-table-row grid grid-cols-2">
                <div class="cus-table-th">脂肪量 (g/day)</div>
                <div class="cus-table-td">{{ calculatefoodInfo.nutrientsProvided?.fat }}</div>
              </div>

              <div class="grid grid-cols-2">
                <div class="cus-table-th">碳水化合物量 (g/day)</div>
                <div class="cus-table-td">{{ calculatefoodInfo.nutrientsProvided?.carbs }}</div>
              </div>

              <div class="cus-table-row grid grid-cols-2">
                <div class="cus-table-th">提供總熱量 (kcal/day)</div>
                <div class="cus-table-td">{{ calculatefoodInfo.foodProvidedCalories }}</div>
              </div>
            </div>

            <!-- ? 每日所需營養缺失量 -->
            <h3 class="cus-title-border-left-err" v-if="calculatefoodInfo.caloriesDifference > 0">
              每日所需營養缺失量: {{ calculatefoodInfo.caloriesDifference }} (kcal/day)
            </h3>
            <h3 v-else class="cus-title-border-left">補充小知識</h3>
            <div class="text-blue4">
              每日營養最佳分配比例為:<br />
              蛋白質 2:脂肪 2:碳水化合物 6,<br />
              單一種鮮食難以滿足鸚鵡每日所需營養,<br />
              建議可以使用鮮食隨機配!
            </div>
          </div>
        </div>

				<!-- 電腦版結果顯示 -->
        <div v-if="calculatefoodInfo" class="hidden lg:block">
          <hr class="cus-line-row" />
          <div class="cus-block-padding">
            <h2 class="cus-page-title">計算結果</h2>

            <!-- ? 每日基本營養需求 -->
            <h3 class="cus-title-border-left">每日基本營養需求</h3>
            <div class="cus-table mb-5">
              <div class="cus-table-row grid grid-cols-4">
                <div class="cus-table-th">熱量 (kcal/day)</div>
                <div class="cus-table-th">蛋白質 (g/day)</div>
                <div class="cus-table-th">脂肪 (g/day)</div>
                <div class="cus-table-th">碳水化合物 (g/day)</div>
              </div>
              <div class="grid grid-cols-4">
                <div class="cus-table-td">{{ calculatefoodInfo.dailyCalories }}</div>
                <div class="cus-table-td">{{ calculatefoodInfo.dailyProteinNeed }}</div>
                <div class="cus-table-td">{{ calculatefoodInfo.dailyFatNeed }}</div>
                <div class="cus-table-td">{{ calculatefoodInfo.dailyCarbsNeed }}</div>
              </div>
            </div>

            <!-- ? 食物營養資訊 -->
            <h3 class="cus-title-border-left">食物營養資訊</h3>
            <div class="cus-table mb-5">
              <div class="cus-table-row grid grid-cols-5">
                <div class="cus-table-th">食物名稱</div>
                <div class="cus-table-th">熱量 (kcal/100g)</div>
                <div class="cus-table-th">蛋白質 (g/100g)</div>
                <div class="cus-table-th">脂肪 (g/100g)</div>
                <div class="cus-table-th">碳水化合物 (g/100g)</div>
              </div>

              <div class="grid grid-cols-5">
                <div class="cus-table-td">{{ calculatefoodInfo.food?.name }}</div>
                <div class="cus-table-td">{{ calculatefoodInfo.food?.calories }}</div>
                <div class="cus-table-td">{{ calculatefoodInfo.food?.protein }}</div>
                <div class="cus-table-td">{{ calculatefoodInfo.food?.fat }}</div>
                <div class="cus-table-td">{{ calculatefoodInfo.food?.carbs }}</div>
              </div>
            </div>

            <div class="cus-table mb-5">
              <div class="cus-table-row grid grid-cols-5">
                <div class="cus-table-th">每日最大攝取量 (g/day)</div>
                <div class="cus-table-th">食物備註</div>
                <div class="cus-table-th col-span-3">好處</div>
              </div>
              <div class="grid grid-cols-5">
                <div class="cus-table-td">{{ calculatefoodInfo.food?.maxIntake }}</div>
                <div class="cus-table-td">{{ calculatefoodInfo.food?.note }}</div>
                <div class="cus-table-td col-span-3">{{ calculatefoodInfo.food?.nutrition }}</div>
              </div>
            </div>

            <!-- ? 食物建議攝取量 & 營養資訊 -->
            <h3 class="cus-title-border-left">食物建議攝取量 & 營養資訊</h3>
            <div class="cus-table mb-5">
              <div class="cus-table-row grid grid-cols-5">
                <div class="cus-table-th">建議攝取量 (g/day)</div>
                <div class="cus-table-th">蛋白質量 (g/day)</div>
                <div class="cus-table-th">脂肪量 (g/day)</div>
                <div class="cus-table-th">碳水化合物量 (g/day)</div>
                <div class="cus-table-th">提供總熱量 (kcal/day)</div>
              </div>

              <div class="grid grid-cols-5">
                <div class="cus-table-td">{{ calculatefoodInfo.foodIntake }}</div>
                <div class="cus-table-td">{{ calculatefoodInfo.nutrientsProvided?.protein }}</div>
                <div class="cus-table-td">{{ calculatefoodInfo.nutrientsProvided?.fat }}</div>
                <div class="cus-table-td">{{ calculatefoodInfo.nutrientsProvided?.carbs }}</div>
                <div class="cus-table-td">{{ calculatefoodInfo.foodProvidedCalories }}</div>
              </div>
            </div>

            <!-- ? 每日所需營養缺失量 -->
            <h3 class="cus-title-border-left-err" v-if="calculatefoodInfo.caloriesDifference > 0">
              每日所需營養缺失量: {{ calculatefoodInfo.caloriesDifference }} (kcal/day)
            </h3>
            <h3 v-else class="cus-title-border-left">補充小知識</h3>
            <div class="text-blue4">
              每日營養最佳分配比例為:<br />
              蛋白質 2:脂肪 2:碳水化合物 6,<br />
              單一種鮮食難以滿足鸚鵡每日所需營養,<br />
              建議可以使用鮮食隨機配!
            </div>
          </div>
        </div>

這段程式碼主要是結果顯示部分,分為手機版和電腦版兩種佈局。主要功能包括:

  1. 條件渲染:使用 v-if="calculatefoodInfo" 確保只在有計算結果時顯示內容。
  2. 響應式設計:透過 Tailwind CSS 的類別 lg:hiddenhidden lg:block 實現在不同螢幕尺寸下切換顯示版本。
  3. 資料呈現:將從 API 取回的計算結果(calculatefoodInfo)呈現在畫面上,包括每日基本營養需求、食物營養資訊、建議攝取量等。
  4. 動態內容:根據計算結果動態顯示營養缺失量或補充知識。
  5. 格式化顯示:使用表格形式有條理地呈現複雜的營養資訊,增強可讀性。

今日範例完整程式碼

pages/calculator.vue

<!-- pages / calculator.vue -->
<script setup lang="ts">
import { useApiStore } from '~/stores/api'
import { showLoading, hideLoading } from '~/stores/eventBus'

const apiStore = useApiStore()
const foodList = ref([])

const mode = ref(1)
/**
 * 選擇模式
 * 0 鮮食隨機配
 * 1 指定鮮食算攝取量
 * 2 指定熱量算鮮食
 * 3 指定飼料算攝取量
 * 4 指定熱量算飼料
 */

const weight = ref('') // 體重
const activity = ref('low') // low, medium, high
const foodId = ref('') // 食物 ID

const calculatefoodInfo: any = ref('') // 指定鮮食算攝取量

onMounted(() => {
  fetchFoods()
})

// 取得食物列表
async function fetchFoods() {
  try {
    showLoading()
    const res = await apiStore.fetchFoodList()
    const result = res.data

    console.log(result)

    if (result && result.status === 'success') {
      foodList.value = result.data
      console.log(foodList.value)
    }
  } catch (e) {
    console.log(e)
  } finally {
    hideLoading()
  }
}

// 新增鮮食計算
async function submitCalculatefood() {
  try {
    showLoading()
    const res = await apiStore.calculateFood({
      weight: weight.value,
      activity: activity.value,
      foodId: foodId.value
    })
    const result = res.data

    console.log(result)

    if (result && result.status === 'success') {
      calculatefoodInfo.value = result.data
      console.log('新增成功')
    }
  } catch (e) {
    console.log(e)
  } finally {
    hideLoading()
  }
}
</script>

<template>
  <div class="w-full">
    <!-- * tab -->
    <div class="flex-wrapper">
      <div class="flex overflow-x-auto">
        <div
          class="calculator-tab"
          :class="{ 'calculator-tab-active': mode === 0 }"
          @click="mode = 0"
        >
          鮮食隨機配
        </div>
        <div
          class="calculator-tab"
          :class="{ 'calculator-tab-active': mode === 1 }"
          @click="mode = 1"
        >
          指定鮮食算攝取量
        </div>
        <div
          class="calculator-tab"
          :class="{ 'calculator-tab-active': mode === 2 }"
          @click="mode = 2"
        >
          指定熱量算鮮食
        </div>
        <div
          class="calculator-tab"
          :class="{ 'calculator-tab-active': mode === 3 }"
          @click="mode = 3"
        >
          指定飼料算攝取量
        </div>
        <div
          class="calculator-tab"
          :class="{ 'calculator-tab-active': mode === 4 }"
          @click="mode = 4"
        >
          指定熱量算飼料
        </div>
      </div>
    </div>

    <!-- * content -->
    <div class="cus-border">
      <!-- * introduction -->
      <div class="cus-intro">
        不知道該給鸚鵡吃什麼嗎?<br />
        輸入鸚鵡體重跟活動水平,我幫你推薦營養均衡的鮮食!
      </div>
      <hr class="cus-line-row" />

      <!-- * page > mode -->
      <!-- ? 0 鮮食隨機配 -->
      <section v-if="mode === 0" class="">
        <div class="cus-block-padding">鮮食隨機配</div>
      </section>

      <!-- ? 1 指定鮮食算攝取量 -->
      <section v-else-if="mode === 1">
        <!-- >> 1 指定鮮食算攝取量 info -->
        <div class="cus-block-padding">
          <h2 class="cus-page-title">輸入基本資料</h2>

          <div class="cus-col-3">
            <div class="cus-col-1">
              <label for="weight" class="cus-label"
                >1. 體重 (g) <span class="text-red2">*</span></label
              >
              <input
                type="number"
                class="cus-input"
                id="weight"
                v-model="weight"
                placeholder="請輸入鸚鵡體重 (g)"
              />
              <span class="cus-input-note">基礎代謝率(BMR) = 175 * (體重 / 1000) ^ 0.75</span>
            </div>

            <div class="cus-col-1">
              <label for="activity" class="cus-label"
                >2. 活動水平 <span class="text-red2">*</span></label
              >
              <div class="cus-radio-row">
                <label class="cus-label-radio" for="low">
                  <input
                    type="radio"
                    name="activity"
                    class=""
                    id="low"
                    v-model="activity"
                    value="low"
                  />
                  <span></span>
                  低 - 平常不太活動
                </label>

                <label class="cus-label-radio" for="medium">
                  <input
                    type="radio"
                    name="activity"
                    class=""
                    id="medium"
                    v-model="activity"
                    value="medium"
                  />
                  <span></span>
                  中 - 適度活動
                </label>

                <label for="high" class="cus-label-radio">
                  <input
                    type="radio"
                    name="activity"
                    class=""
                    id="high"
                    v-model="activity"
                    value="high"
                  />
                  <span></span>高 - 經常活動
                </label>
              </div>
              <span class="cus-input-note">活動水平用來調整 BMR,可以更符合需求</span>
            </div>

            <div class="cus-col-1" v-if="foodList.length">
              <label for="foodId" class="cus-label"
                >3. 選擇想計算的食物 <span class="text-red2">*</span></label
              >
              <select type="number" class="cus-input" id="foodId" v-model="foodId">
                <option value="" disabled selected>請選擇食物</option>
                <option v-for="food in foodList" :key="food._id" :value="food._id">
                  {{ food.name }}
                </option>
              </select>
              <span class="cus-input-note">目前僅提供計算資料庫內的食物</span>
            </div>
          </div>

          <button
            class="cus-btn-primary mt-5"
            :disabled="!weight || !foodId"
            @click="submitCalculatefood"
          >
            開始計算
          </button>
        </div>

        <!-- >> 1 result > md -->
        <div v-if="calculatefoodInfo" class="lg:hidden">
          <hr class="cus-line-row" />
          <div class="cus-block-padding">
            <h2 class="cus-page-title">計算結果</h2>

            <!-- ? 每日基本營養需求 -->
            <h3 class="cus-title-border-left">每日基本營養需求</h3>
            <div class="cus-table mb-5">
              <div class="cus-table-row grid grid-cols-2">
                <div class="cus-table-th">熱量 (kcal/day)</div>
                <div class="cus-table-td">{{ calculatefoodInfo.dailyCalories }}</div>
              </div>
              <div class="cus-table-row grid grid-cols-2">
                <div class="cus-table-th">蛋白質 (g/day)</div>
                <div class="cus-table-td">{{ calculatefoodInfo.dailyProteinNeed }}</div>
              </div>
              <div class="cus-table-row grid grid-cols-2">
                <div class="cus-table-th">脂肪 (g/day)</div>
                <div class="cus-table-td">{{ calculatefoodInfo.dailyFatNeed }}</div>
              </div>
              <div class="grid grid-cols-2">
                <div class="cus-table-th">碳水化合物 (g/day)</div>
                <div class="cus-table-td">{{ calculatefoodInfo.dailyCarbsNeed }}</div>
              </div>
            </div>

            <!-- ? 食物營養資訊 -->
            <h3 class="cus-title-border-left">食物營養資訊</h3>
            <div class="cus-table mb-5">
              <div class="cus-table-row grid grid-cols-2">
                <div class="cus-table-th">食物名稱</div>
                <div class="cus-table-td">{{ calculatefoodInfo.food?.name }}</div>
              </div>
              <div class="cus-table-row grid grid-cols-2">
                <div class="cus-table-th">熱量 (kcal/100g)</div>
                <div class="cus-table-td">{{ calculatefoodInfo.food?.calories }}</div>
              </div>
              <div class="cus-table-row grid grid-cols-2">
                <div class="cus-table-th">蛋白質 (g/100g)</div>
                <div class="cus-table-td">{{ calculatefoodInfo.food?.protein }}</div>
              </div>
              <div class="cus-table-row grid grid-cols-2">
                <div class="cus-table-th">脂肪 (g/100g)</div>
                <div class="cus-table-td">{{ calculatefoodInfo.food?.fat }}</div>
              </div>
              <div class="cus-table-row grid grid-cols-2">
                <div class="cus-table-th">碳水化合物 (g/100g)</div>
                <div class="cus-table-td">{{ calculatefoodInfo.food?.carbs }}</div>
              </div>
              <div class="cus-table-row grid grid-cols-2">
                <div class="cus-table-th">每日最大攝取量 (g/day)</div>
                <div class="cus-table-td">{{ calculatefoodInfo.food?.maxIntake }}</div>
              </div>
              <div class="cus-table-row grid grid-cols-2">
                <div class="cus-table-th">食物備註</div>
                <div class="cus-table-td">{{ calculatefoodInfo.food?.note }}</div>
              </div>
              <div class="grid grid-cols-2">
                <div class="cus-table-th">好處</div>
                <div class="cus-table-td">{{ calculatefoodInfo.food?.nutrition }}</div>
              </div>
            </div>

            <!-- ? 食物建議攝取量 & 營養資訊 -->
            <h3 class="cus-title-border-left">食物建議攝取量 & 營養資訊</h3>
            <div class="cus-table mb-5">
              <div class="cus-table-row grid grid-cols-2">
                <div class="cus-table-th">建議攝取量 (g/day)</div>
                <div class="cus-table-td">{{ calculatefoodInfo.foodIntake }}</div>
              </div>

              <div class="cus-table-row grid grid-cols-2">
                <div class="cus-table-th">蛋白質量 (g/day)</div>
                <div class="cus-table-td">{{ calculatefoodInfo.nutrientsProvided?.protein }}</div>
              </div>

              <div class="cus-table-row grid grid-cols-2">
                <div class="cus-table-th">脂肪量 (g/day)</div>
                <div class="cus-table-td">{{ calculatefoodInfo.nutrientsProvided?.fat }}</div>
              </div>

              <div class="grid grid-cols-2">
                <div class="cus-table-th">碳水化合物量 (g/day)</div>
                <div class="cus-table-td">{{ calculatefoodInfo.nutrientsProvided?.carbs }}</div>
              </div>

              <div class="cus-table-row grid grid-cols-2">
                <div class="cus-table-th">提供總熱量 (kcal/day)</div>
                <div class="cus-table-td">{{ calculatefoodInfo.foodProvidedCalories }}</div>
              </div>
            </div>

            <!-- ? 每日所需營養缺失量 -->
            <h3 class="cus-title-border-left-err" v-if="calculatefoodInfo.caloriesDifference > 0">
              每日所需營養缺失量: {{ calculatefoodInfo.caloriesDifference }} (kcal/day)
            </h3>
            <h3 v-else class="cus-title-border-left">補充小知識</h3>
            <div class="text-blue4">
              每日營養最佳分配比例為:<br />
              蛋白質 2:脂肪 2:碳水化合物 6,<br />
              單一種鮮食難以滿足鸚鵡每日所需營養,<br />
              建議可以使用鮮食隨機配!
            </div>
          </div>
          <!-- <pre>
            {{ calculatefoodInfo }}
          </pre> -->
        </div>

        <!-- >> 1 result > pc -->
        <div v-if="calculatefoodInfo" class="hidden lg:block">
          <hr class="cus-line-row" />
          <div class="cus-block-padding">
            <h2 class="cus-page-title">計算結果</h2>

            <!-- ? 每日基本營養需求 -->
            <h3 class="cus-title-border-left">每日基本營養需求</h3>
            <div class="cus-table mb-5">
              <div class="cus-table-row grid grid-cols-4">
                <div class="cus-table-th">熱量 (kcal/day)</div>
                <div class="cus-table-th">蛋白質 (g/day)</div>
                <div class="cus-table-th">脂肪 (g/day)</div>
                <div class="cus-table-th">碳水化合物 (g/day)</div>
              </div>
              <div class="grid grid-cols-4">
                <div class="cus-table-td">{{ calculatefoodInfo.dailyCalories }}</div>
                <div class="cus-table-td">{{ calculatefoodInfo.dailyProteinNeed }}</div>
                <div class="cus-table-td">{{ calculatefoodInfo.dailyFatNeed }}</div>
                <div class="cus-table-td">{{ calculatefoodInfo.dailyCarbsNeed }}</div>
              </div>
            </div>

            <!-- ? 食物營養資訊 -->
            <h3 class="cus-title-border-left">食物營養資訊</h3>
            <div class="cus-table mb-5">
              <div class="cus-table-row grid grid-cols-5">
                <div class="cus-table-th">食物名稱</div>
                <div class="cus-table-th">熱量 (kcal/100g)</div>
                <div class="cus-table-th">蛋白質 (g/100g)</div>
                <div class="cus-table-th">脂肪 (g/100g)</div>
                <div class="cus-table-th">碳水化合物 (g/100g)</div>
              </div>

              <div class="grid grid-cols-5">
                <div class="cus-table-td">{{ calculatefoodInfo.food?.name }}</div>
                <div class="cus-table-td">{{ calculatefoodInfo.food?.calories }}</div>
                <div class="cus-table-td">{{ calculatefoodInfo.food?.protein }}</div>
                <div class="cus-table-td">{{ calculatefoodInfo.food?.fat }}</div>
                <div class="cus-table-td">{{ calculatefoodInfo.food?.carbs }}</div>
              </div>
            </div>

            <div class="cus-table mb-5">
              <div class="cus-table-row grid grid-cols-5">
                <div class="cus-table-th">每日最大攝取量 (g/day)</div>
                <div class="cus-table-th">食物備註</div>
                <div class="cus-table-th col-span-3">好處</div>
              </div>
              <div class="grid grid-cols-5">
                <div class="cus-table-td">{{ calculatefoodInfo.food?.maxIntake }}</div>
                <div class="cus-table-td">{{ calculatefoodInfo.food?.note }}</div>
                <div class="cus-table-td col-span-3">{{ calculatefoodInfo.food?.nutrition }}</div>
              </div>
            </div>

            <!-- ? 食物建議攝取量 & 營養資訊 -->
            <h3 class="cus-title-border-left">食物建議攝取量 & 營養資訊</h3>
            <div class="cus-table mb-5">
              <div class="cus-table-row grid grid-cols-5">
                <div class="cus-table-th">建議攝取量 (g/day)</div>
                <div class="cus-table-th">蛋白質量 (g/day)</div>
                <div class="cus-table-th">脂肪量 (g/day)</div>
                <div class="cus-table-th">碳水化合物量 (g/day)</div>
                <div class="cus-table-th">提供總熱量 (kcal/day)</div>
              </div>

              <div class="grid grid-cols-5">
                <div class="cus-table-td">{{ calculatefoodInfo.foodIntake }}</div>
                <div class="cus-table-td">{{ calculatefoodInfo.nutrientsProvided?.protein }}</div>
                <div class="cus-table-td">{{ calculatefoodInfo.nutrientsProvided?.fat }}</div>
                <div class="cus-table-td">{{ calculatefoodInfo.nutrientsProvided?.carbs }}</div>
                <div class="cus-table-td">{{ calculatefoodInfo.foodProvidedCalories }}</div>
              </div>
            </div>

            <!-- ? 每日所需營養缺失量 -->
            <h3 class="cus-title-border-left-err" v-if="calculatefoodInfo.caloriesDifference > 0">
              每日所需營養缺失量: {{ calculatefoodInfo.caloriesDifference }} (kcal/day)
            </h3>
            <h3 v-else class="cus-title-border-left">補充小知識</h3>
            <div class="text-blue4">
              每日營養最佳分配比例為:<br />
              蛋白質 2:脂肪 2:碳水化合物 6,<br />
              單一種鮮食難以滿足鸚鵡每日所需營養,<br />
              建議可以使用鮮食隨機配!
            </div>
          </div>
          <!-- <pre>
            {{ calculatefoodInfo }}
          </pre> -->
        </div>
      </section>

      <!-- ? 2 指定熱量算鮮食 -->
      <section v-else-if="mode === 2" class="">
        <div class="cus-block-padding">指定熱量算鮮食</div>
      </section>

      <!-- ? 3 指定飼料算攝取量 -->
      <section v-else-if="mode === 3" class="">
        <div class="cus-block-padding">指定飼料算攝取量</div>
      </section>

      <!-- ? 4 指定熱量算飼料 -->
      <section v-else-if="mode === 4" class="">
        <div class="cus-block-padding">指定熱量算飼料</div>
      </section>

      <!-- * bottom introduction -->
      <hr class="cus-line-row" />
      <div class="cus-intro">
        推薦食物及計算方式皆由 AI 協助蒐集和整理,僅供參考。<br />
        若有特殊飲食需求建議尋求專業幫助!
      </div>
    </div>
  </div>
</template>

<style scoped>
/* calculator-tab */
.calculator-tab {
  @apply w-[140px] min-w-[140px] py-[10px] text-center text-[14px] text-blue4 opacity-80;
  border-left: 1px solid transparent;
  border-top: 1px solid transparent;
  border-right: 1px solid transparent;
  border-radius: 6px 6px 0 0;
}

.calculator-tab-active {
  @apply font-bold opacity-100;
  border-left: 1px solid var(--color-blue4);
  border-top: 1px solid var(--color-blue4);
  border-right: 1px solid var(--color-blue4);
}

.overflow-x-auto {
  /* 隱藏滾動條的通用方法 */
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none; /* Internet Explorer 10+ */

  /* 對於 Webkit (Chrome, Safari, etc.) */
  &::-webkit-scrollbar {
    display: none;
  }
}

/* toggle */
.cus-toggle-tab {
  @apply flex gap-2 rounded-md border border-blue4 p-1 text-[14px];
}

.cus-toggle-tab-item {
  @apply rounded px-3 py-[6px] text-blue4;
}

.cus-toggle-tab-item-active {
  @apply bg-blue4 text-blue1;
}
</style>

stores/api.ts

// stores/api.ts

import { defineStore } from 'pinia'
import axios from 'axios'

const baseURL = 'https://two024it-test-app.onrender.com'

const api = axios.create({
  baseURL
})

export const useApiStore = defineStore('api-store', {
  actions: {
    // 取得食物列表
    async fetchFoodList() {
      const response = await api.get('/freshfoods/')
      return response
    },
    // 新增鮮食計算
    async calculateFood(data: any) {
      const response = await api.post('/foods/calculatefood', data)
      return response
    }
  }
})

結語

哇!我們真的做到了!我們一起完成了一個超酷的鸚鵡鮮食計算機。從設定 API、獲取資料、進行計算,到最後呈現結果,我們一步一步地完成了每個環節。
記住,程式設計就像是在搭積木,我們今天學到的每個步驟都是一塊重要的積木。慢慢來,多加練習,你也可以成為一個厲害的程式設計師!
最後,別忘了實際動手試試看喔!實踐是最好的學習方式。加油,你一定可以的!
大家有沒有想要更詳細補充的部分呢?歡迎在下方留言分享喔!讓我們一起在 Nuxt3 的世界中探險吧!加油!
(對了,如果你覺得今天的內容對你有幫助,別忘了給個讚支持一下喔!這會是我繼續努力的動力呢~)


上一篇
[ DAY26 ] Nuxt 3 進階技巧:Pinia、API 整合、Loading 元件
下一篇
[ DAY28 ] Nuxt 3 專案實戰:打造互動式聯絡表單
系列文
房東不給養鸚鵡 - 那就用 Nuxt + Express + MongoDB 30 天寫一個全端鸚鵡專案30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言