iT邦幫忙

2025 iThome 鐵人賽

DAY 19
1
Modern Web

設計 x 開發:從 Figma 到 Vue,打造 LINE 互動形象網站!系列 第 19

19 從專案初始化到畫面完成!(上):用 Nuxt 完成第一版網站功能

  • 分享至 

  • xImage
  •  

前言

在前一篇我們完成 Nuxt 專案初始化,以及介紹到目錄結構,這篇將依照設計稿,一步步完成整體畫面製作。本篇會專注在網站註冊頁面 UI 元件的開發,例如 條款提示視窗、插圖視覺區塊、LINE 按鈕設計等,建立好畫面基礎,為後續加上 LIFF 互動功能做好準備💪!

建立註冊主畫面 UI

  1. 在 Nuxt 專案中,pages的放置位置與檔案命名會直接對應形成網址路徑(例如 /user/register),因此SFC檔名建議以小寫+中橫線(kebab-case) 來命名,當在 Linux 系統訪問網站時,就不會有大小寫混淆,訪問不到位址的問題。
    接著專案內新增 pages 資料夾,以及在建立好的資料夾新增 signup.vue 檔案:
pages/
├── signup.vue

這邊可能有人會很好奇,既然官方已經有定義好專案目錄結構了,為什麼 Nuxt 在一開始,不幫我們自動建立好所有目錄?

Nuxt 採取「約定大於配置」的設計架構,設計目標是盡可能保持輕量,它不強迫開發者在一開始就建立所有目錄,而是讓開發者自行去選擇,建立「實際需要使用」的部分,而不是一開始就在專案初始化時就自動產出一堆空資料夾。例如 假設你只想建立一個沒有多個頁面的單頁應用程式(SPA),就不需要建立 pages/ 資料夾,而只需使用 app.vue

  1. 接著安裝我們前面提到的 Nuxt UI,這是由 NuxtLabs 官方提供的一套高品質、原生整合的 UI 元件庫,預設使用的是 Tailwind CSS。
npm install @nuxt/ui

安裝完套件後,記得在 nuxt.config.ts 裡設定好 模組與 CSS 引入

export default defineNuxtConfig({
  modules: ['@nuxt/ui'],
  css: ['~/assets/css/main.css']
})

接下來,新增 assets/css 資料夾與 main.css 檔案,並在 main.css 中引入 Nuxt UI 的樣式:

......
@import "@nuxt/ui";

完成後再重新執行 npm run dev

  1. 為什麼首頁會出現 404?
    可能會發現訪問http://localhost:3000 ,頁面會呈現404 - Page not found: / 🤔,這是因為目前根路徑 / 還沒有對應的頁面檔案。前面有提到 Nuxt 採用 基於檔案的路由(file-based routing),會根據 pages/ 目錄下的檔案,自動生成對應的路由。我們目前只有新增 pages/signup.vue,所以訪問要輸入:
http://localhost:3000/signup

19 從專案初始化到畫面完成!(上):用 Nuxt 完成第一版網站功能 - 圖示1
(回到開始畫面)

  1. 打開 app.vue,註解或移除掉預設內容的兩個元件,加上 <NuxtPage />
    <NuxtPage /> 是 Nuxt 提供的自動路由頁面插槽元件,會根據 pages/ 目錄自動載入對應頁面。
<!-- App.vue -->
<template>
  <NuxtPage />
</template>

額外補充:如果有要套用 layout,請在外層加上 NuxtLayout

<!-- App.vue -->
<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>
</template>
  1. 先準備接下來會用到的圖片素材,在 assets 內新增 images 資料夾,並放入 gradient-bg.pngline-logo.svg 圖檔。
assets/
├── images/
│       └── gradient-bg.png
│       └── line-logo.svg

接著打開 signup.vue,我們來製作一個「雙欄式的登入/註冊畫面」:
外層 <main> 是整體容器,並透過 md:flex-row 實作出響應式的雙欄排版,當畫面寬度到達中型裝置(md)以上時,會由直式排版轉為左右雙欄。

根據設計稿,左側是插圖區塊,背景設計採用 bg-gradient-to-r from-[#6b4d6b] to-[#2a3e68] 建立線性漸層背景,從紫色漸變到藍色。使用 flex items-center justify-center 將圖片置中。圖片使用 import 將素材載入,交由 Nuxt 編譯處理,讓變數 gradientBg 綁定圖片來源。

右側為註冊功能區塊,設定內距 p-8,同樣以 md:w-1/2 響應式設計呈現。製作綠色背景的 LINE 註冊按鈕,與品牌視覺風格一致,LINE 圖示透過 import 導入 lineLogo,放在按鈕內。點擊後會觸發 onLineSignupClick 方法(該方法在 <script setup> 裡定義)。

  <template>
	<main class="min-h-screen flex flex-col md:flex-row">
        <div class="w-full md:w-1/2 bg-gradient-to-r from-[#6b4d6b] to-[#2a3e68] p-8 flex justify-center">
            <div class="max-w-2xl mt-8">
                <div class="rounded-md overflow-hidden">
                    <img
                    :src="gradientBg"
                    alt="充滿童趣風格的海底插圖,描繪章魚、鯊魚、熱帶魚與海星在深海中悠游。"
                    class="w-lg h-auto"
                    />
                </div>
            </div>
        </div>
        <div class="w-full md:w-1/2 bg-white p-8 flex justify-center font-normal">
            <div class="max-w-md w-full mt-30">
                <h3 class="text-3xl font-extrabold text-[#525252] text-center mb-3">立即註冊</h3>
                <p class="text-center mb-4 text-[#525252] font-medium">歡迎!透過 <span class=" text-[#00c300]">LINE</span> 註冊,開始使用服務吧。</p>

                <button @click="onLineSignupClick" class="lineLogo h-12 w-full bg-[#00c300] cursor-pointer font-bold text-white py-3 px-4 rounded-md flex items-center justify-center mb-4">
                    <img
                    alt="LINE註冊登入圖示"
                    :src="lineLogo"
                    width=24
                    height=24
                    class="mr-2"
                    />
                    LINE 註冊
                </button>

                <div class="bg-[#e7eaef] h-12 p-4 rounded-md text-center mb-2 flex items-center justify-center text-center">
                    <span class="text-[#525252] font-medium">已有帳號了?</span>
                    <a href="#" class="text-[#2a68c8] ml-2 hover:underline font-bold">
                    在此登入
                    </a>
                </div>

                <div class="text-start text-[#aeada9] font-medium">
                    <a href="#" class="hover:underline">
                    隱私權政策
                    </a>
                    <span class="mx-1">|</span>
                    <a href="#" class="hover:underline">
                    服務條款聲明
                    </a>
                </div>
            </div>
        </div>
    </main>
</template>
<script setup>
    import gradientBg from '@/assets/images/gradient-bg.png'
    import lineLogo from '@/assets/images/line-logo.svg'

    let onLineSignupClick = async()=> {
      // 等等下方會分享到內容
    }
</script>
  1. 再來我們來製作導覽列,由於導覽列會在前台多個頁面中共用,建議將它封裝成元件,所以可以將 Header.vue 導覽列元件,放入 components 資料夾中。
    新增 componentspublic資料夾,再新增Header.vue,Nuxt 會自動註冊 components/ 目錄中的元件。建議使用大駝峰命名(PascalCase),以避免與原生 HTML 標籤名稱衝突
components/
├── public/
│       └── Header.vue

打開 Header.vue 撰寫導覽列內容:

<template>
    <header class="w-full bg-white py-3 px-6 flex justify-between items-center">
        <div class="flex items-center justify-center space-x-6 font-medium">
            <a href="/" class="flex items-center">
                <img
                    src="/public/logo.png"
                    alt="Sea Museum"
                    width=176
                    height=65
                    class="mr-2"
                />
            </a>
			<nav class="hidden md:flex items-center space-x-8 font-medium">
				<a href="#" class="text-[#525252] font-normal tracking-normal font-sans text-center">
					展區介紹
				</a>
				<a href="#" class="text-[#525252] font-normal tracking-normal font-sans text-center">
					海洋保育專區
				</a>
				<a href="#" class="text-[#525252] font-normal tracking-normal font-sans text-center">
					參觀資訊
				</a>
				<a href="#" class="text-[#525252] font-normal tracking-normal font-sans text-center">
					最新消息
				</a>
			</nav>
        </div>
        <div class="flex items-center">
            <svg class="text-[#525252]" xmlns="http://www.w3.org/2000/svg" width="1.5rem" height="1.5em" viewBox="0 0 24 24"><!-- Icon from Material Design Icons by Pictogrammers - https://github.com/Templarian/MaterialDesign/blob/master/LICENSE --><path fill="currentColor" d="M12 4a4 4 0 0 1 4 4a4 4 0 0 1-4 4a4 4 0 0 1-4-4a4 4 0 0 1 4-4m0 10c4.42 0 8 1.79 8 4v2H4v-2c0-2.21 3.58-4 8-4" /></svg>
        </div>
    </header>
</template>

(上方程式中 Icon來源

  1. 最後,回到 signup.vue,在頁面中加入我們剛剛建立好的 <PublicHeader /> 元件。
  <template>
    <PublicHeader />
    <main class="min-h-screen flex flex-col md:flex-row">
    ......

目前為止,註冊 UI 成果如下:
https://ithelp.ithome.com.tw/upload/images/20250826/201725783yuqdJ69lh.png

建立其他元件素材 UI

  1. 接著製作「LINE註冊」流程會使用到的隱私權政策與服務條款視窗。這邊我們使用 SweetAlert2 套件,非常適合快速開發出好用又美觀的彈窗套件,請先開啟終端機安裝:
npm install sweetalert2

新增 plugins/ 資料夾,並建立一個 plugin 檔案 sweetalert.js,這樣 SweetAlert2 就可以在任何 Vue 元件中使用。

import Swal from 'sweetalert2'

export default defineNuxtPlugin(() => {
  return {
    provide: {
      swal: Swal
    }
  }
})
  1. 在 Nuxt 3 中,plugin 會透過 provide 將外部功能注入到 NuxtApp。因此在元件內,我們需要使用 useNuxtApp() 來取得 $swal,並在註冊頁的 onLineSignupClick 方法中加入 SweetAlert 的彈窗內容:
  • title:彈窗標題
  • icon:標題上方的圖示,還有 info, success, warning, error 其他值可以使用。
  • width:可調整彈窗寬度
  • html:可自訂 HTML 製作彈窗內容
<script setup>
	....
	const { $swal } = useNuxtApp()
    const onLineSignupClick = async()=> {
	     await $swal.fire({
			title: "隱私權政策與服務條款聲明",
			icon: "info",
            width: "90%",
            html: `<div style="text-align: left; max-height: 60vh; overflow-y: auto; padding-right: 8px;">
                <h3 class="text-3xl font-bold text-[#3FC3EE] mb-3">隱私權政策</h3>
                <h4 class="text-xl font-bold">一、適用範圍</h4>
                <p>本政策適用於您在使用本網站所提供的各項服務時,所涉及的個人資料收集、處理與利用。</p>
                <br/>
                <h4 class="text-xl font-bold">二、個人資料蒐集目的與類型</h4>
                <p>本網站將蒐集下列資訊以提供會員服務、訂單處理、客服聯繫與行銷通知:</p>
                <ul>
                <li>1. 姓名、聯絡電話、電子郵件等聯絡資料</li>
                <li>2. 登入資訊、IP 位址、瀏覽紀錄、裝置資訊等使用行為紀錄</li>
                </ul>
                <br/>
                <h4 class="text-xl font-bold">三、資料使用方式</h4>
                <p>蒐集之個人資料僅用於提供服務與行銷推播,並依法律規定處理與保護。</p>
                <br/>
                <h4 class="text-xl font-bold">四、資料保存與安全</h4>
                <ul>
                <li>1. 您的個人資料將依據業務需求與法令規定保存,期間屆滿後將刪除或匿名化處理。</li>
                <li>2. 本網站採取合適的資安措施,以防止個資被未授權存取、洩漏或篡改。</li>
                </ul>
                <br/>
                <h4 class="text-xl font-bold">五、個人資料權利</h4>
                <p>依據《個人資料保護法》,您對所提供的個人資料擁有以下權利:</p>
                <ul>
                <li>1. 查詢或請求閱覽。</li>
                <li>2. 請求製給複製本。</li>
                <li>3. 請求補充或更正。</li>
                <li>4. 請求停止蒐集、處理或利用。</li>
                <li>5. 請求刪除。</li>
                </ul>
                <br/>
                <h4 class="text-xl font-bold">六、Cookie 技術使用</h4>
                <p>為提升使用體驗,網站可能使用 Cookie 技術,可透過瀏覽器設定限制。</p>
                <br/>
                <h4 class="text-xl font-bold">七、政策修訂</h4>
                <p>本政策得隨時修改,變更內容將公告於本網站,不另行個別通知,請定期查閱。</p>
                <hr class="text-gray-300 border-2 my-4">
                <h3 class="text-3xl font-bold text-[#3FC3EE] mb-3">服務條款</h3>
                <h4 class="text-xl font-bold">一、接受條款</h4>
                <p>歡迎您使用本網站(以下簡稱「本網站」)所提供的服務。當您註冊為會員、或使用本網站任何服務,即表示您已閱讀、了解並同意遵守本服務條款。若您不同意,請勿使用本網站服務。</p>
                <br/>
                <h4 class="text-xl font-bold">二、會員帳號與密碼</h4>
                <p>使用本網站前,您須提供正確個人資訊以完成註冊,若因帳號使用不當導致損害,概由會員自行負責。</p>
                <br/>
                <h4 class="text-xl font-bold">三、使用規範</h4>
                <p>1. 您不得利用本網站從事任何非法、侵權或違反公共秩序善良風俗之行為。</p>
                <p>2. 本網站有權對違反條款之帳號採取限制、終止服務等處置,恕不另行通知。</p>
                <br/>
                <h4 class="text-xl font-bold">四、服務內容變更</h4>
                <p>本網站保留隨時修改、暫停或終止全部或部分服務內容之權利,並不負事先通知之義務。</p>
                <br/>
                <h4 class="text-xl font-bold">五、免責聲明</h4>
                <ul>
                <li>1. 對於因系統維護、天災、駭客攻擊等不可抗力因素所致的服務中斷或資料遺失,本網站不負任何賠償責任。</li>
                <li>2. 本網站對於使用者上傳或張貼的內容,無須負事先審查義務,若發現違法情事將依法處理。</li>
                </ul>
                <br/>
                <h4 class="text-xl font-bold">六、智慧財產權</h4>
                <p>本網站所有內容(含文字、圖像、設計、程式碼等)均為本網站或其合法權利人所有,未經授權不得任意使用、重製或散布。</p>
                <br/>
                <h4 class="text-xl font-bold">七、準據法與管轄權</h4>
                <p>本服務條款之解釋與適用,以中華民國法律為準據法,並以本網站營運所在地之法院為第一審管轄法院。</p>
            </div>`,
            showCloseButton: true,
            showCancelButton: true,
            focusConfirm: false,
            confirmButtonText: `同意`,
            cancelButtonText: `不同意`,
		})
    }
</script>
  1. 為了讓註冊與登入流程更完整,我們接下來會製作一個簡易的「會員首頁」,用來模擬使用者成功登入後導向的畫面。
    請在 pages/ 資料夾下,新增 member/ 子目錄,並建立 index.vue 作為會員首頁。
pages/
├── member/
│       └── index.vue

畫面內容如下,有些地方像是名稱、LINE ID 的部份,我們可以先填入假資料,模擬畫面實際有值的樣子:

<template>
    <PublicHeader />
    <div class="min-h-screen bg-[#fafafa]">
        <div class="relative h-48 bg-gradient-to-r from-[#644D5E] to-[#154065] overflow-hidden">
            <div class="absolute inset-0 opacity-30">
                <div class="animation absolute top-4 left-8 w-3 h-3 bg-white/40 rounded-full"></div>
                <div class="animation absolute top-12 right-16 w-2 h-2 bg-white/30 rounded-full"></div>
                <div class="animation absolute bottom-8 left-1/4 w-4 h-4 bg-white/20 rounded-full"></div>
                <div class="animation absolute top-8 left-1/3 w-2 h-2 bg-white/40 rounded-full"></div>
                <div class="animation absolute bottom-12 right-1/3 w-3 h-3 bg-white/30 rounded-full"></div>
            </div>
        </div>
        <div class="relative -mt-16 px-6">
            <div class="flex flex-col items-center mb-12">
                <div class="relative m-4">
                    <div class="flex items-center justify-center h-24 w-24 rounded-full border-4 border-white shadow-lg overflow-hidden bg-[#f6f8f9]">
                        <svg xmlns="http://www.w3.org/2000/svg" class="text-[#E2E8F0]"  width="4rem" height="4em" viewBox="0 0 24 24">
                            <path d="M8.5 13.498l2.5 3.006l3.5-4.506l4.5 6H5m16 1v-14a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2z" fill="currentColor" />
                        </svg>
                    </div>
                    <button class="absolute -bottom-1 -right-1 w-8 h-8 bg-[#707070] rounded-full flex items-center justify-center cursor-pointer shadow-md hover:bg-[#525252]">
                        <svg width="16" height="16" class="text-[#FFFFFF]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                            <path d="M5 12h14"/>
                            <path d="M12 5v14"/>
                        </svg>
                    </button>
                </div>
				<h1 class="text-2xl font-medium text-[#525252] mb-2">嗨 ooo 你好!</h1>
				<p class="text-[#98A0AE] text-sm">LINE ID Uxxxxxxxxxxxxxxxxx</p>
            </div>
            <div class="max-w-md mx-auto space-y-8">
                <div class="space-y-2">
                    <label class="text-[#98A0AE] text-base font-medium">姓名</label>
                    <div class="border-b border-[#CCCECF] pb-2">
                    <input
                        type="text"
                        class="w-full border-0 bg-transparent p-0 text-[#525252] placeholder:text-[#98A0AE] focus:outline-none"
                    />
                    </div>
                </div>
                <div class="space-y-2">
                    <label class="text-[#525252] text-base font-medium">信箱</label>
                    <div class="border-b border-[#CCCECF] pb-2">
                    <input
                        type="email"
                        class="w-full border-0 bg-transparent p-0 text-[#525252] placeholder:text-[#98A0AE] focus:outline-none"
                    />
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>
<style scoped>
    input:focus {
        outline: none;
    }
    .animation {
        animation: float 3s ease-in-out infinite;
    }
    @keyframes float {
        0%, 100% {
            transform: translateY(0px);
        }
        50% {
            transform: translateY(-10px);
        }
    }
</style>

目前會員首頁 UI 的成果如下:
19 從專案初始化到畫面完成!(上):用 Nuxt 完成第一版網站功能 - 圖示3

結語

在這個篇章中,我們已經完成註冊流程所需的 UI 製作,包含雙欄式排版、插圖視覺區塊、LINE 註冊按鈕、條款彈窗,以及登入後的會員首頁畫面。透過 Nuxt UI 我們可以快速構建符合品牌風格的元件與佈局,而 SweetAlert 的加入讓互動細節更加完整、生動。

完成這些基礎畫面,我們也能順利往串接 LIFF(LINE Front-end Framework)功能前進囉。在下一篇我們將進一步整合 LIFF 技術,實作 LINE 第三方登入機制,並完成整體註冊流程,讓使用者可以一鍵登入、順利導向會員首頁,敬請期待✨!

參考資料與延伸閱讀

Nuxt UI 參考來源
Tailwindcss 參考來源
icones 來源
sweetalert2 來源


上一篇
18 建立 Nuxt 3 開發環境 + 專案初始化與目錄結構說明
下一篇
20 從專案畫面到 LINE 註冊完成!(下):用 Nuxt 完成第一版網站功能
系列文
設計 x 開發:從 Figma 到 Vue,打造 LINE 互動形象網站!27
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言