在前幾天的修煉中,我們已經實作出了幾個我們需要的基礎元件(Component),每一個都蘊含著「單一職責」的概念。
今天,我們的目標就是將先前打造的元件們集結起來,實作出一個功能完整且具備現代設計感的登入頁面——LoginView.vue
。我們將會看到,這些獨立的元件如何像拼圖一樣,一塊塊地組合起來,最終構成一個能與使用者互動的完整畫面。
準備好了嗎?讓我們開始組合屬於我們的第一個表單吧!
我們需要先建立 src/view/LoginView.vue
。這是一個「視圖(View)」層級的元件,它的職責是整合多個基礎元件,構成一個完整的頁面。
/src
├── App.vue
├── main.js
├── assets/
├── components/ # 我們的基礎元件們
│ ├── BaseButton.vue
│ ├── BaseSelect.vue
│ ├── ErrorMessage.vue
│ ├── GoogleIcon.vue
│ └── LoadingSpinner.vue
├── router/
│ └── index.js
├── stores/
└── view/ # 頁面級元件
├── LoginView.vue <-- 今日主角
LoginView.vue
元件分析與組合LoginView.vue
是我們銷售系統的入口。它由一個背景、一個置中的登入表單構成。
讓我們一步步拆解它的結構。
<template>
的結構首先,我們看到 <template>
中定義了整個畫面的骨架。
<template>
<div class="login-container">
<form @submit.prevent="handleLogin" class="login-form">
<!-- Logo -->
<div>
<img class="login-logo" src="@/assets/Images/mrl_LOGO_340x40px_1.png" alt="居家先生" />
</div>
<h1 class="login-title">銷售系統</h1>
<!-- 門市選擇 -->
<div class="input-group">
<BaseSelect label="門市選擇" v-model="storeSelect" :options="storeOptions" />
<ErrorMessage v-if="storeError">{{ storeError }}</ErrorMessage>
</div>
<!-- 登入按鈕 -->
<BaseButton type="button" variant="white" :class="['btn-login']" @click="googleLogin" :disabled="isLoading">
<LoadingSpinner v-if="isLoading" size="medium" />
<GoogleIcon v-else :size="28" />
{{ isLoading ? '登入中...' : '使用 Google 登入' }}
</BaseButton>
<!-- 頁腳 -->
<p class="login-footer">
本系統僅供授權業務人員使用<br />
如有問題請聯繫系統管理員
</p>
</form>
</div>
</template>
這裡體現了元件化開發的核心思想:宣告式 UI。我們不是用程式碼去命令「畫一個選擇器、再畫一個按鈕」,而是像組合積木一樣,直接宣告「這裡需要一個 BaseSelect
、那裡需要一個 BaseButton
」。
BaseSelect
:我們用它來做「門市選擇」。透過 v-model="storeSelect"
將元件的內部狀態與 LoginView
的 storeSelect
變數雙向綁定。options
則由 :options="storeOptions"
傳入,定義了下拉選單的所有選項。
ErrorMessage
:在 BaseSelect
之後,它透過 v-if="storeError"
進行條件渲染。只有當 storeError
有值(即發生錯誤)時,這個元件才會被渲染出來,向使用者顯示錯誤訊息。
BaseButton
:它不僅是一個按鈕,更是一個動態容器。
v-if="isLoading"
判斷式,來決定要顯示 LoadingSpinner
還是 GoogleIcon
。{{ isLoading ? '登入中...' : '使用 Google 登入' }}
這個三元運算子實現了動態變化。:disabled="isLoading"
屬性綁定,讓按鈕在載入中時自動變為不可點擊狀態,防止使用者重複觸發。一個小小的按鈕,就整合了狀態判斷、條件渲染和屬性綁定,展現了 Vue 的響應式能力。
<script setup>
的邏輯如果說 <template>
是骨架,那 <script setup>
就是驅動這副骨架活動的大腦和神經。
import { ref, watch } from 'vue';
import { useRouter } from 'vue-router';
import BaseButton from '@/components/BaseButton.vue';
import BaseSelect from '@/components/BaseSelect.vue';
import LoadingSpinner from '@/components/LoadingSpinner.vue';
import GoogleIcon from '@/components/GoogleIcon.vue';
import ErrorMessage from '@/components/ErrorMessage.vue';
// 1. 引入所有需要的元件和 Vue API
const router = useRouter();
// 2. 定義元件狀態 (State)
const storeOptions = [
{ value: '', text: '請選擇您的門市...', disabled: true },
// ...其他門市
];
const storeSelect = ref('');
const storeError = ref('');
const isLoading = ref(false);
// 3. 定義互動邏輯 (Actions)
function validateStore() {
// ...驗證邏輯
}
async function googleLogin() {
if (!validateStore()) return;
isLoading.value = true;
// ...Google 登入的非同步處理
}
// 4. 狀態監控
watch(storeSelect, () => {
if (storeError.value) {
storeError.value = '';
}
});
這裡的邏輯非常清晰:
import
所有需要用到的基礎元件。ref
創建了幾個響應式變數,它們是整個元件的「狀態」。
storeSelect
:儲存使用者選擇的門市。storeError
:儲存驗證失敗時的錯誤訊息。isLoading
:控制是否顯示載入動畫。<template>
中對應的畫面。googleLogin
函式定義了點擊登入按鈕後的所有行為,包括驗證、設定載入狀態、呼叫 Google API 等。這是一個處理使用者互動的核心函式。watch
監聽:我們設定了一個 watch
來監控 storeSelect
的變化。一旦使用者開始選擇門市,之前顯示的錯誤訊息 storeError
就會被立刻清空。<style scoped>
的外觀最後,<style scoped>
賦予了這個登入頁面現代化的外觀。
.login-container {
background: url('@/assets/Images/darlicat_family.jpg') center/cover no-repeat;
}
.login-container::before {
/* ... */
background-color: rgba(0, 0, 0, 0.2);
}
.login-form {
background: rgba(255, 255, 255);
backdrop-filter: blur(20px);
/* ... */
animation: slideIn 0.8s ease-out;
}
這裡有幾個亮點:
scoped
屬性確保了這些樣式只會作用於 LoginView.vue
元件內部,不會「污染」到其他元件。@keyframes
定義了 slideIn
和 fadeInUp
等動畫,讓表單和其中的元素以動態的方式進場,而不是生硬地出現。今天,我們成功地將之前打造的各個基礎元件組合在一起,完成了一個功能齊全、外觀精美的登入頁面。我們學到了:
props
(如 :options
)和 v-model
實現父子元件的數據流動。ref
創建響應式狀態,並透過這些狀態的變化來動態控制畫面的顯示內容(如 v-if
和三元運算子)。watch
和 :disabled
等細節處理,讓表單互動更流暢、更人性化。這就是元件化開發的魅力所在。在 View 中,將每一個元件一塊一塊的組合成我們想要的樣子。
經過今天的修煉,我們已經掌握了「柒之型 - Form組合」。接下來,我們要處理 Google SSO 的登入功能!
明日,Day 15:[Authの呼吸・壹之型] SSO整合 - Google OAuth登入實作。心を燃やせ 🔥!