哈囉大家好!今天我們要來玩點有趣的東西了 —— 我們要開始打造我們的網頁骨架啦!想像一下,我們今天要給我們的 Nuxt3 專案穿上漂亮的衣服,讓它不再只是一個空蕩蕩的框架。準備好了嗎?讓我們開始吧!
首先,讓我們來看看我們要做出來的頁面吧:
聯繫我們頁面:
食物計算機頁面:

是不是看起來很酷?接下來我們會一步一步把這些頁面實現出來 (但不是今天)!
首先,我們要整理一下 app.vue。這個檔案就像是我們網站的大骨架,所有的頁面都會包在這裡面。
<script setup>
import axios from 'axios'
// 建立一個 Axios 實例,就像是給我們的 API 請求一個專屬的小幫手
const api = axios.create({
  baseURL: 'https://two024it-test-app.onrender.com'// 設定基礎 URL
})
const list = ref([])
// 取得資料的函式
async function fetchData() {
  try {
    const response = await api.get('/freshfoods/')// 取得食物列表
    console.log(response.data)// 在控制台印出回應資料
    list.value = response.data
  } catch (error) {
    console.error('哎呀,出錯了:', error)// 錯誤處理
  }
}
onMounted(() => {
  fetchData()
})
</script>
<template>
  <div class="relative flex min-h-screen w-full flex-col">
<!-- 導航欄 -->
    <header class="z-20">
<!-- <Header /> -->
    </header>
<!-- 頁面內容 -->
    <main class="page-wrapper">
      <NuxtPage />
    </main>
  </div>
</template>
<style scoped>
.page-wrapper {
  @apply mx-auto flex w-full max-w-[1200px] grow px-3 pb-5 pt-[52px] lg:px-5 lg:pt-[64px];
  align-items: start;
}
</style>
這裡我們用了 <NuxtPage /> 組件,它就像是一個神奇的佔位符,Nuxt 會自動把我們的頁面內容放在這裡。
接下來,我們要在 pages 資料夾裡新增一個 index.vue 檔案。這就是我們的首頁啦!
<script setup></script>
<template>
  <div>index</div>
</template>
<style scoped></style>
當你執行專案並進入首頁時,你會看到 "index" 這個字。這是因為 <NuxtPage /> 幫我們把 pages/index.vue 的內容放到那裡了。是不是很神奇?
接著,我們要在 pages 資料夾裡新增所有需要的頁面。我會新增以下頁面:
index.vue(首頁,已存在)calculator.vue(食物計算機)food.vue(營養指南)bird.vue(百科全書)hospital.vue(醫護站)connect.vue(聯繫我們)每個檔案可以使用以下基本結構:
<template>
  <div>
    <h1>頁面標題</h1>
    <!-- 在這裡添加頁面內容 -->
  </div>
</template>
<script setup>
// 這裡可以添加需要的 JavaScript 邏輯
</script>
<style scoped>
/* 這裡可以添加頁面專屬的樣式 */
</style>
為了讓我們的網站看起來更漂亮,我們要設定一些基礎的顏色。打開 tailwind.config.ts,加入以下程式碼:
extend: {
  colors: {
    blue1: '#e9f1fe',
    blue2: '#c4d7ed',
    blue3: '#abc8e2',
    blue4: '#375d81',
    blue5: '#183152',
    red1: '#ffaeaa',
    red2: '#ed6f69',
    bg: '#fff6ea'
  }
}
這樣我們就可以用像 text-blue3 這樣的 class 來使用我們自定義的顏色了!超方便的吧?
安裝圖示套件:
開啟終端機,在專案根目錄執行以下指令安裝 Nuxt Icon 模組:
npm install -D @nuxt/icon
設定 Nuxt:
打開 nuxt.config.ts 檔案。
在 modules 陣列中添加 nuxt-icon:
export default defineNuxtConfig({
  devtools: { enabled: true },
  modules: ['@nuxtjs/tailwindcss', '@nuxt-icon']
})
使用圖示:
前往 Icones 網站尋找需要的圖示。
複製您選擇的圖示名稱。
在頁面中使用以下格式插入圖示:
<Icon name="ph:hand-tap" size="20"></Icon>
name 屬性中填入您剛剛複製的圖示名稱。
size 屬性用來控制圖示大小。
注意:這邊如果貼在
Header.vue測試會看不到哦,因為我們app.vue的Header元件是註解的狀態 ><
實用提示:
推薦安裝 VS Code 擴充套件:Iconify IntelliSense
這個擴充套件可以將程式碼中的圖示名稱轉換為可視化的圖示,方便預覽和選擇。
完成以上步驟後,您就可以在專案中使用漂亮的圖示了!
components 資料夾在專案根目錄新增一個 components 資料夾。
這個資料夾將用來存放所有的 Vue 元件。
在 components 資料夾內新增一個 Header.vue 檔案。
這個檔案將包含我們的導航欄元件程式碼。
assets 資料夾assets 資料夾。assets 資料夾內新增一個 images 資料夾。assets/images 資料夾中。 
在這個步驟中,我們將創建一個全局的 CSS 文件,用於定義整個應用程序的基本樣式和一些通用的 CSS 類。
在專案根目錄下創建 assets/css/main.css 文件。
將以下內容複製到 main.css 文件中:
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@100..900&display=swap');
:root {
  --color-blue1: #e9f1fe;
  --color-blue2: #c4d7ed;
  --color-blue3: #abc8e2;
  --color-blue4: #375d81;
  --color-blue5: #183152;
  --color-red1: #ffaeaa;
  --color-red2: #ed6f69;
  --color-bg: #fff6ea;
}
body {
  font-family: 'Noto Sans TC', sans-serif;
  letter-spacing: 1.25px;
  background: var(--color-bg);
}
/* 消樣式 */
input:focus-visible {
  outline: none;
}
textarea:focus-visible {
  outline: none;
}
select:focus-visible {
  outline: none;
}
/* 圖片通用 */
.pic-auto {
  width: 100%;
  height: 100%;
  object-fit: cover;
  vertical-align: middle;
}
/* 限制最大寬度 !> 1200px, 搭配 px-5 使用 */
.page-max-w {
  @apply mx-auto w-full max-w-[1200px];
}
/* hover 效果 */
.hover-auto {
  @apply transform duration-200;
}
讓我們來解析這個 CSS 文件的各個部分:
:root 選擇器中定義了一系列顏色變數,包括不同深淺的藍色、紅色和背景色。body 元素設置了字體、字間距和背景色。input、textarea 和 select 元素在獲得焦點時的默認輪廓。.pic-auto 類提供了一種便捷的方式來設置圖片的寬高和適應方式。.page-max-w 類用於限制內容的最大寬度,通常與 padding 一起使用來創建響應式佈局。.hover-auto 類為元素添加了過渡效果,可用於創建平滑的懸停動畫。要在專案中使用這個 CSS 文件,需要在 nuxt.config.ts 中進行配置:
export default defineNuxtConfig({
  css: ['~/assets/css/main.css'],
// 其他配置...
})
這樣,main.css 中定義的樣式就會被應用到整個專案中。您可以在元件中直接使用這些 CSS 類和變數,例如:
<template>
  <div class="page-max-w">
    <img src="..." alt="..." class="pic-auto hover-auto" />
  </div>
</template>
<style scoped>
.custom-element {
  color: var(--color-blue4);
}
</style>
通過這種方式,我們可以確保整個應用程序有一致的樣式,同時也方便了樣式的管理和修改。
在開始編寫程式碼之前,請仔細查看導航欄的設計稿:


Header.vue 元件的結構和樣式。Header.vue 的內容,實現設計稿中的導航欄功能。首先,我們要在 components 資料夾裡新增一個 Header.vue 檔案。這個檔案會包含我們導航欄的所有程式碼。
<script setup lang="ts">
// 這裡放 JavaScript 程式碼
</script>
<template>
<!-- 這裡放 HTML 結構 -->
</template>
<style scoped>
/* 這裡放 CSS 樣式 */
</style>
這是 Vue 單文件組件的基本結構。<script setup> 區塊用於 JavaScript 邏輯,<template> 用於 HTML 結構,<style scoped> 用於 CSS 樣式。
我們先來放置網站的 Logo:
<template>
  <nuxt-link to="/" class="logo fixed left-5 top-2 w-[32px] lg:w-[40px]">
    <img src="~/assets/images/logo.svg" alt="logo" class="pic-auto" />
  </nuxt-link>
</template>
這段程式碼做了什麼呢?
nuxt-link 是 Nuxt 提供的特殊連結元件,這裡我們用它來連到首頁 "/"。class 裡的內容是使用 Tailwind CSS 來設定樣式,例如 fixed 表示固定位置,left-5 和 top-2 設定位置,w-[32px] 設定寬度。img 標籤用來顯示 Logo 圖片:
src="~/assets/images/logo.svg" 指定了 Logo 圖片的路徑。~ 是 Nuxt.js 中的特殊符號,代表專案的根目錄。這表示圖片位於專案的 assets/images 資料夾中。接下來,我們來做選單按鈕:
<script setup lang="ts">
const route = useRoute()
const menuOpen = ref(false)
const toggleMenu = () => {
  menuOpen.value = !menuOpen.value
}
</script>
<template>
  <div class="relative">
    <button v-show="!menuOpen" @click="toggleMenu" class="nav-btn">
      <Icon name="ph:hand-tap" size="20"></Icon>
      <div v-if="route.path === '/'">首頁</div>
      <div v-else-if="route.path === '/calculator'">食物計算機</div>
      <div v-else-if="route.path === '/food'">營養指南</div>
      <div v-else-if="route.path === '/bird'">百科全書</div>
      <div v-else-if="route.path === '/hospital'">醫護站</div>
      <div v-else="route.path === '/connect'">聯繫我們</div>
    </button>
  </div>
</template>
<style scoped>
.nav-btn {
  @apply bg-bg text-blue4 border-blue4 fixed right-0 top-0 z-20 flex w-[140px] items-center justify-center gap-[6px] rounded-bl-[6px] border-b border-l bg-opacity-20 p-[10px] font-bold backdrop-blur-sm;
}
</style>
讓我解釋一下這段程式碼:
const route = useRoute() 讓我們可以知道現在在哪個頁面。menuOpen 是一個變數,用來記錄選單是開啟還是關閉的。toggleMenu 函式用來切換選單的開關狀態。template 中,我們用 v-show="!menuOpen" 來控制按鈕的顯示。@click="toggleMenu" 表示當按鈕被點擊時,會呼叫 toggleMenu 函式。v-if 和 v-else-if 來根據不同的頁面路徑顯示不同的文字。Icon 元件是用來顯示一個小圖示。現在來製作選單的主要內容:
<template>
  <div class="relative">
    <!-- 前面的按鈕程式碼 -->
    <div v-show="menuOpen" class="nav-menu">
      <button class="nav-menu-item" @click="toggleMenu">CLOSE</button>
      <nuxt-link class="nav-menu-item" exact-active-class="bg-blue4 bg-opacity-15" to="/">首頁</nuxt-link>
      <nuxt-link class="nav-menu-item" exact-active-class="bg-blue4 bg-opacity-15" to="/calculator">食物計算機</nuxt-link>
      <nuxt-link class="nav-menu-item" exact-active-class="bg-blue4 bg-opacity-15" to="/food">營養指南</nuxt-link>
      <nuxt-link class="nav-menu-item" exact-active-class="bg-blue4 bg-opacity-15" to="/bird">百科全書</nuxt-link>
      <nuxt-link class="nav-menu-item" exact-active-class="bg-blue4 bg-opacity-15" to="/hospital">醫護站</nuxt-link>
      <nuxt-link class="nav-menu-item" exact-active-class="bg-blue4 bg-opacity-15" to="/connect">聯繫我們</nuxt-link>
    </div>
  </div>
</template>
<style scoped>
.nav-menu {
  @apply text-blue4 border-blue4 fixed right-0 top-0 z-20 flex w-[140px] flex-col items-center justify-center rounded-bl-[6px] border-b border-l bg-opacity-20 font-bold backdrop-blur-sm;
}
.nav-menu-item {
  @apply flex w-full items-center justify-center p-[10px];
}
</style>
這段程式碼做了什麼:
v-show="menuOpen" 來控制整個選單的顯示。nuxt-link,點擊後會跳轉到對應的頁面。exact-active-class 用來設定當前頁面的選單項目的樣式。最後,我們來加入一個功能,讓選單在頁面切換時自動關閉:
<script setup lang="ts">
// ... 前面的程式碼
watch(
  () => route.path,
  (currentPath, previousPath) => {
    if (menuOpen.value) {
      toggleMenu()
    }
  }
)
</script>
這段程式碼的作用是:
watch 來監視路由的變化。為了讓我們的導航欄更加吸引人,我們可以加入一些轉場效果。在 Vue 中,我們可以使用 <Transition> 元件來實現這個功能。這個功能不只是讓網頁看起來更酷炫,還能提升使用者體驗,讓介面變化更加流暢自然。
首先,讓我們來看看 Vue 官方文檔中關於 Transition 的說明:Vue Transition 官方文檔
Vue 提供了一個 <Transition> 元件,它可以讓我們輕鬆地為任何元素或元件添加進入/離開的動畫效果。這個元件非常強大,可以處理以下幾種情況:
在我們的導航欄例中,我是這樣使用 Transition 的:
<template>
  <div class="relative">
    <Transition name="fade">
<!-- 選單按鈕 -->
    </Transition>
    <Transition name="fade">
<!-- 選單內容 -->
    </Transition>
  </div>
</template>
這裡的 name="fade" 是很重要的,它定義了我們的動畫效果的名稱。這個名稱會被用來生成 CSS 類別名稱,Vue 會在適當的時機添加或移除這些類別。
當一個元素被 <Transition> 包裹時,Vue 會在元素進入或離開時自動添加/移除一些 CSS 類別。這些類別的名稱是基於我們設定的 name 屬性。
在我們的例子中,因為我們使用了 name="fade",所以 Vue 會使用以下的類別:
fade-enter-from:進入動畫的起始狀態fade-enter-active:進入動畫的生效狀態fade-enter-to:進入動畫的結束狀態fade-leave-from:離開動畫的起始狀態fade-leave-active:離開動畫的生效狀態fade-leave-to:離開動畫的結束狀態讓我們來看看我們為導航欄定義的 CSS 樣式:
<style scoped>
.fade-enter-active,
.fade-leave-active {
  transition: all 0.4s ease-in-out;
}
.fade-enter-from,
.fade-leave-to {
  transform: translatey(-264px);
  opacity: 0;
}
</style>
這裡發生了什麼?
.fade-enter-active 和 .fade-leave-active:
transition: all 0.4s ease-in-out; 表示所有屬性的變化都會在 0.4 秒內完成,使用 ease-in-out 的變化曲線。.fade-enter-from 和 .fade-leave-to:
transform: translatey(-264px); 表示元素會從上方 264px 的位置移動到原位(或從原位移動到上方 264px 的位置)。opacity: 0; 表示元素會從完全透明變為可見(或從可見變為完全透明)。結合起來,這些 CSS 樣式創造了這樣的效果:
整個過程持續 0.4 秒,使用 ease-in-out 的變化曲線,這會讓動畫看起來更加平滑自然。
components/Header.vue
<script setup lang="ts">
const route = useRoute()
// 選單變數
const menuOpen = ref(false)
// 選單開關
const toggleMenu = () => {
  menuOpen.value = !menuOpen.value
}
// 監聽路由的 path 變化, 換頁時自動關閉選單
watch(
  () => route.path,
  (currentPath, previousPath) => {
    if (menuOpen.value) {
      toggleMenu()
    }
  }
)
</script>
<template>
  <nuxt-link to="/" class="logo fixed left-5 top-2 w-[32px] lg:w-[40px]">
    <img src="~/assets/images/logo.svg" alt="logo" class="pic-auto" />
  </nuxt-link>
  <div class="relative">
    <!-- * 選單按鈕 -->
    <Transition name="fade">
      <button v-show="!menuOpen" @click="toggleMenu" class="nav-btn">
        <Icon name="ph:hand-tap" size="20"></Icon>
        <div v-if="route.path === '/'">首頁</div>
        <div v-else-if="route.path === '/calculator'">食物計算機</div>
        <div v-else-if="route.path === '/food'">營養指南</div>
        <div v-else-if="route.path === '/bird'">百科全書</div>
        <div v-else-if="route.path === '/hospital'">醫護站</div>
        <div v-else="route.path === '/connect'">聯繫我們</div>
      </button>
    </Transition>
    <!-- * 選單 -->
    <Transition name="fade">
      <div v-show="menuOpen" class="nav-menu">
        <button class="nav-menu-item" @click="toggleMenu">CLOSE</button>
        <nuxt-link class="nav-menu-item" exact-active-class="bg-blue4 bg-opacity-15" to="/"
          >首頁</nuxt-link
        >
        <nuxt-link
          class="nav-menu-item"
          exact-active-class="bg-blue4 bg-opacity-15"
          to="/calculator"
          >食物計算機</nuxt-link
        >
        <nuxt-link class="nav-menu-item" exact-active-class="bg-blue4 bg-opacity-15" to="/food"
          >營養指南</nuxt-link
        >
        <nuxt-link class="nav-menu-item" exact-active-class="bg-blue4 bg-opacity-15" to="/bird"
          >百科全書</nuxt-link
        >
        <nuxt-link class="nav-menu-item" exact-active-class="bg-blue4 bg-opacity-15" to="/hospital"
          >醫護站</nuxt-link
        >
        <nuxt-link class="nav-menu-item" exact-active-class="bg-blue4 bg-opacity-15" to="/connect"
          >聯繫我們</nuxt-link
        >
      </div>
    </Transition>
  </div>
</template>
<style scoped>
.nav-btn {
  @apply bg-bg text-blue4 border-blue4 fixed right-0 top-0 z-20 flex w-[140px] items-center justify-center gap-[6px] rounded-bl-[6px] border-b border-l bg-opacity-20 p-[10px] font-bold backdrop-blur-sm;
}
.nav-menu {
  @apply text-blue4 border-blue4 fixed right-0 top-0 z-20 flex w-[140px] flex-col items-center justify-center rounded-bl-[6px] border-b border-l bg-opacity-20 font-bold backdrop-blur-sm;
}
.nav-menu-item {
  @apply flex w-full items-center justify-center p-[10px];
}
.fade-enter-active,
.fade-leave-active {
  transition: all 0.4s ease-in-out;
}
.fade-enter-from,
.fade-leave-to {
  transform: translatey(-264px);
  opacity: 0;
}
</style>
app.vue
<script setup>
import axios from 'axios'
// 創建一個自定義的 Axios 實例
const api = axios.create({
  baseURL: 'https://two024it-test-app.onrender.com' // 設置基礎 URL
  // timeout: 5000 // 設置超時時間為 5 秒
})
const list = ref([])
// 測試取資料
async function fetchData() {
  try {
    const response = await api.get('/freshfoods/') // 獲取食物列表
    console.log(response.data) // 處理響應數據
    list.value = response.data
  } catch (error) {
    console.error('發生錯誤:', error) // 錯誤處理
  }
}
onMounted(() => {
  fetchData()
})
</script>
<template>
  <div class="app relative flex min-h-screen w-full flex-col">
    <!-- * navbar -->
    <header class="z-20">
      <Header />
    </header>
    <!-- * pages -->
    <main class="page-wrapper">
      <NuxtPage />
    </main>
  </div>
</template>
<style scoped>
.app {
  font-family: 'Noto Sans TC', sans-serif;
}
.page-wrapper {
  @apply mx-auto flex w-full max-w-[1200px] grow px-3 pb-5 pt-[52px] lg:px-5 lg:pt-[64px];
  align-items: start;
}
</style>
nuxt.config.ts
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
  devtools: { enabled: true },
  modules: ['@nuxtjs/tailwindcss', '@nuxt-icon']
})
tailwind.config.ts
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './components/**/*.{vue,js,ts}', // 掃描所有 Vue 組件
    './layouts/**/*.vue', // 掃描所有布局文件
    './pages/**/*.vue', // 掃描所有頁面文件
    './composables/**/*.{js,ts}', // 掃描所有可組合式函數
    './plugins/**/*.{js,ts}', // 掃描所有插件
    './utils/**/*.{js,ts}', // 掃描所有工具函數
    './{App,app}.{js,ts,vue}', // 掃描主應用文件
    './{Error,error}.{js,ts,vue}', // 掃描錯誤處理文件
    './app.config.{js,ts}' // 掃描應用配置文件
  ],
  theme: {
    extend: {
      colors: {
        blue1: '#e9f1fe',
        blue2: '#c4d7ed',
        blue3: '#abc8e2',
        blue4: '#375d81',
        blue5: '#183152',
        red1: '#ffaeaa',
        red2: '#ed6f69',
        bg: '#fff6ea'
      }
    }
  },
  plugins: [] // 可以添加 Tailwind 插件
}
assets/css/main.css
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@100..900&display=swap');
:root {
  --color-blue1: #e9f1fe;
  --color-blue2: #c4d7ed;
  --color-blue3: #abc8e2;
  --color-blue4: #375d81;
  --color-blue5: #183152;
  --color-red1: #ffaeaa;
  --color-red2: #ed6f69;
  --color-bg: #fff6ea;
}
body {
  font-family: 'Noto Sans TC', sans-serif;
  letter-spacing: 1.25px;
  background: var(--color-bg);
}
/* 消樣式 */
input:focus-visible {
  outline: none;
}
textarea:focus-visible {
  outline: none;
}
select:focus-visible {
  outline: none;
}
/* 圖片通用 */
.pic-auto {
  width: 100%;
  height: 100%;
  object-fit: cover;
  vertical-align: middle;
}
/* 限制最大寬度 !> 1200px, 搭配 px-5 使用 */
.page-max-w {
  @apply mx-auto w-full max-w-[1200px];
}
/* hover 效果 */
.hover-auto {
  @apply transform duration-200;
}
好啦!這就是我們超酷導航欄的全部內容了。是不是覺得很有趣?雖然看起來有點複雜,但只要一步一步來,你也可以做出這麼厲害的導航欄喔!,加油!
大家有沒有想要更詳細補充的部分呢?歡迎在下方留言分享喔!讓我們一起在 Nuxt3 的世界中探險吧!加油!
(對了,如果你覺得今天的內容對你有幫助,別忘了給個讚支持一下喔!這會是我繼續努力的動力呢~)