哈囉大家好!今天我們要來玩點有趣的東西了 —— 我們要開始打造我們的網頁骨架啦!想像一下,我們今天要給我們的 Nuxt3 專案穿上漂亮的衣服,讓它不再只是一個空蕩蕩的框架。準備好了嗎?讓我們開始吧!
房東不給養鸚鵡 - 那就用 Nuxt + Express + MongoDB 30 天寫一個全端鸚鵡專案。
挑戰人生第一次鐵人賽,就來個佛心分享 side project,從專案發想、規劃、設計、資料庫、開發到部署,技術使用 Nuxt 3、Tailwind CSS、Pinia、Axios、Express、MongoDB,製作一個前後端分離的鸚鵡專案,功能主要介紹食物計算機和聯絡我們,希望可以讓更多人瞭解專案的完整流程 ✨
IT 全文章連結先來看看我們的目標
首先,讓我們來看看我們要做出來的頁面吧:
聯繫我們頁面:
食物計算機頁面:
是不是看起來很酷?接下來我們會一步一步把這些頁面實現出來 (但不是今天)!
開始動手做吧!
1. 整理 app.vue
首先,我們要整理一下 app.vue
。這個檔案就像是我們網站的大骨架,所有的頁面都會包在這裡面。
1 | <script setup> |
這裡我們用了 <NuxtPage />
組件,它就像是一個神奇的佔位符,Nuxt 會自動把我們的頁面內容放在這裡。
2. 建立首頁
接下來,我們要在 pages
資料夾裡新增一個 index.vue
檔案。這就是我們的首頁啦!
1 | <script setup></script> |
當你執行專案並進入首頁時,你會看到 “index” 這個字。這是因為 <NuxtPage />
幫我們把 pages/index.vue
的內容放到那裡了。是不是很神奇?
3. 新增其他頁面
接著,我們要在 pages
資料夾裡新增所有需要的頁面。我會新增以下頁面:
index.vue
(首頁,已存在)calculator.vue
(食物計算機)food.vue
(營養指南)bird.vue
(百科全書)hospital.vue
(醫護站)connect.vue
(聯繫我們)
每個檔案可以使用以下基本結構:
1 | <template> |
4. 設定網站基礎配色
為了讓我們的網站看起來更漂亮,我們要設定一些基礎的顏色。打開 tailwind.config.ts
,加入以下程式碼:
1 | extend: { |
這樣我們就可以用像 text-blue3
這樣的 class 來使用我們自定義的顏色了!超方便的吧?
5. 安裝和使用 Nuxt Icon
安裝圖示套件:
開啟終端機,在專案根目錄執行以下指令安裝 Nuxt Icon 模組:
1
npm install -D @nuxt/icon
設定 Nuxt:
打開
nuxt.config.ts
檔案。在
modules
陣列中添加nuxt-icon
:1
2
3
4export default defineNuxtConfig({
devtools: { enabled: true },
modules: ['@nuxtjs/tailwindcss', '@nuxt-icon']
})
使用圖示:
前往 Icones 網站尋找需要的圖示。
複製您選擇的圖示名稱。
在頁面中使用以下格式插入圖示:
1
<Icon name="ph:hand-tap" size="20"></Icon>
name
屬性中填入您剛剛複製的圖示名稱。size
屬性用來控制圖示大小。
注意:這邊如果貼在
Header.vue
測試會看不到哦,因為我們app.vue
的Header
元件是註解的狀態 ><實用提示:
推薦安裝 VS Code 擴充套件:Iconify IntelliSense
這個擴充套件可以將程式碼中的圖示名稱轉換為可視化的圖示,方便預覽和選擇。
完成以上步驟後,您就可以在專案中使用漂亮的圖示了!
6. 建立元件 components
資料夾
在專案根目錄新增一個
components
資料夾。
這個資料夾將用來存放所有的 Vue 元件。在
components
資料夾內新增一個Header.vue
檔案。
這個檔案將包含我們的導航欄元件程式碼。
7. 建立資源 assets
資料夾
- 在專案根目錄新增一個
assets
資料夾。
這個資料夾用於存放靜態資源,如圖片、樣式表等。 - 在
assets
資料夾內新增一個images
資料夾。
這個資料夾將專門用來存放圖片檔案。 - 將 LOGO 圖片放入
assets/images
資料夾中。
8. 建立 建立全局樣式文件
在這個步驟中,我們將創建一個全局的 CSS 文件,用於定義整個應用程序的基本樣式和一些通用的 CSS 類。
在專案根目錄下創建
assets/css/main.css
文件。將以下內容複製到
main.css
文件中:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51@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 文件的各個部分:
- 字體導入:
- 使用 Google Fonts 導入 Noto Sans TC 字體,這是一種適合中文顯示的sans-serif字體。
- CSS 變數定義:
- 在
:root
選擇器中定義了一系列顏色變數,包括不同深淺的藍色、紅色和背景色。 - 使用 CSS 變數可以讓我們在整個應用中方便地管理和修改顏色。
- 在
- 全局樣式:
- 為
body
元素設置了字體、字間距和背景色。
- 為
- 表單元素樣式重置:
- 移除了
input
、textarea
和select
元素在獲得焦點時的默認輪廓。
- 移除了
- 通用圖片樣式:
.pic-auto
類提供了一種便捷的方式來設置圖片的寬高和適應方式。
- 頁面最大寬度限制:
.page-max-w
類用於限制內容的最大寬度,通常與 padding 一起使用來創建響應式佈局。
- Hover 效果:
.hover-auto
類為元素添加了過渡效果,可用於創建平滑的懸停動畫。
- 字體導入:
要在專案中使用這個 CSS 文件,需要在
nuxt.config.ts
中進行配置:1
2
3
4export default defineNuxtConfig({
css: ['~/assets/css/main.css'],
// 其他配置...
})這樣,
main.css
中定義的樣式就會被應用到整個專案中。您可以在元件中直接使用這些 CSS 類和變數,例如:1
2
3
4
5
6
7
8
9
10
11<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>通過這種方式,我們可以確保整個應用程序有一致的樣式,同時也方便了樣式的管理和修改。
9. 查看設計稿
在開始編寫程式碼之前,請仔細查看導航欄的設計稿:
- 左側是 LOGO
- 右側是導航選項
這個設計將指導我們如何構建Header.vue
元件的結構和樣式。
完成這些步驟後,我們就準備好開始製作導航欄元件了。接下來,我們將開始編寫Header.vue
的內容,實現設計稿中的導航欄功能。
導航欄詳細製作步驟
1. 建立基本結構
首先,我們要在 components
資料夾裡新增一個 Header.vue
檔案。這個檔案會包含我們導航欄的所有程式碼。
1 | <script setup lang="ts"> |
這是 Vue 單文件組件的基本結構。<script setup>
區塊用於 JavaScript 邏輯,<template>
用於 HTML 結構,<style scoped>
用於 CSS 樣式。
2. 製作 Logo
我們先來放置網站的 Logo:
1 | <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
資料夾中。
3. 製作選單按鈕
接下來,我們來做選單按鈕:
1 | <script setup lang="ts"> |
讓我解釋一下這段程式碼:
const route = useRoute()
讓我們可以知道現在在哪個頁面。menuOpen
是一個變數,用來記錄選單是開啟還是關閉的。toggleMenu
函式用來切換選單的開關狀態。- 在
template
中,我們用v-show="!menuOpen"
來控制按鈕的顯示。 @click="toggleMenu"
表示當按鈕被點擊時,會呼叫toggleMenu
函式。- 我們用一系列的
v-if
和v-else-if
來根據不同的頁面路徑顯示不同的文字。 Icon
元件是用來顯示一個小圖示。
4. 製作選單內容
現在來製作選單的主要內容:
1 | <template> |
這段程式碼做了什麼:
- 我們用
v-show="menuOpen"
來控制整個選單的顯示。 - 每個選單項目都是一個
nuxt-link
,點擊後會跳轉到對應的頁面。 exact-active-class
用來設定當前頁面的選單項目的樣式。
5. 自動關閉選單
最後,我們來加入一個功能,讓選單在頁面切換時自動關閉:
1 | <script setup lang="ts"> |
這段程式碼的作用是:
- 使用
watch
來監視路由的變化。 - 當路由改變(也就是頁面切換)時,如果選單是開啟的,就自動關閉它。
額外補充:Transition 動畫效果
為了讓我們的導航欄更加吸引人,我們可以加入一些轉場效果。在 Vue 中,我們可以使用 <Transition>
元件來實現這個功能。這個功能不只是讓網頁看起來更酷炫,還能提升使用者體驗,讓介面變化更加流暢自然。
Vue 的 Transition 元件
首先,讓我們來看看 Vue 官方文檔中關於 Transition 的說明:Vue Transition 官方文檔
Vue 提供了一個 <Transition>
元件,它可以讓我們輕鬆地為任何元素或元件添加進入/離開的動畫效果。這個元件非常強大,可以處理以下幾種情況:
- 條件渲染 (使用 v-if)
- 條件展示 (使用 v-show)
- 動態元件
- 元件根節點
基本用法
在我們的導航欄例中,我是這樣使用 Transition 的:
1 | <template> |
這裡的 name="fade"
是很重要的,它定義了我們的動畫效果的名稱。這個名稱會被用來生成 CSS 類別名稱,Vue 會在適當的時機添加或移除這些類別。
CSS 過渡類別
當一個元素被 <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 樣式定義
讓我們來看看我們為導航欄定義的 CSS 樣式:
1 | <style scoped> |
這裡發生了什麼?
.fade-enter-active
和.fade-leave-active
:- 這兩個類別定義了動畫的持續時間和變化方式。
transition: all 0.4s ease-in-out;
表示所有屬性的變化都會在 0.4 秒內完成,使用 ease-in-out 的變化曲線。- ease-in-out 曲線讓動畫在開始和結束時較慢,中間較快,創造出更自然的效果。
.fade-enter-from
和.fade-leave-to
:- 這兩個類別定義了動畫的起始狀態和結束狀態。
transform: translatey(-264px);
表示元素會從上方 264px 的位置移動到原位(或從原位移動到上方 264px 的位置)。opacity: 0;
表示元素會從完全透明變為可見(或從可見變為完全透明)。
動畫效果解析
結合起來,這些 CSS 樣式創造了這樣的效果:
- 當選單出現時(進入動畫):
- 初始狀態:選單在上方 264px 處且完全透明。
- 動畫過程:選單在 0.4 秒內逐漸下移到原位,同時逐漸變得不透明。
- 最終狀態:選單在原位且完全不透明。
- 當選單消失時(離開動畫):
- 初始狀態:選單在原位且完全不透明。
- 動畫過程:選單在 0.4 秒內逐漸上移 264px,同時逐漸變得透明。
- 最終狀態:選單在上方 264px 處且完全透明。
整個過程持續 0.4 秒,使用 ease-in-out 的變化曲線,這會讓動畫看起來更加平滑自然。
今日成果展示
我們得到了一個可以切換頁面又有動畫的 navbar (點縮圖可以打開 Youtube 影片喔)
今日範例完整程式碼
components/Header.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
<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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<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
1
2
3
4
5
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
devtools: { enabled: true },
modules: ['@nuxtjs/tailwindcss', '@nuxt-icon']
})
tailwind.config.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/** @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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
@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 的世界中探險吧!加油!
希望這篇文章有幫助到你,謝謝你的觀看,若有想看的系列也歡迎告訴我 😉