iT邦幫忙

2025 iThome 鐵人賽

DAY 26
0
Vue.js

在 Vue 過氣前要學的三十件事系列 第 26

在 Vue 過氣前要學的第二十六件事 - Avengers Assemble!

  • 分享至 

  • xImage
  •  

前言

廢話少說開始寫扣吧!

請確保你以下所有套件都有安裝在專案中,
如果還沒安裝建議到各篇文章中看如何安裝。

在 Vue 過氣前要學的第十九件事 - Vue Router
在 Vue 過氣前要學的第二十一件事 - Tailwind v4
在 Vue 過氣前要學的第二十四件事- Pinia
在 Vue 過氣前要學的第二十五件事- Vee-validate & Yup

這邊的 Input 將全都使用前篇文中實作出來的驗證 input
且為了不太過擴散,此篇實作中不包含 <select><textarea>

此篇文章程式碼偏多,建議邊做邊看避免漏掉某些細節

規劃


在開發任何功能前要做好規劃,有助於了解是否會有突發狀況;

避免要驗收了才突然發現事情遠不只你想的那麼簡單,
也能讓你像是在解決代辦清單一樣一步一步完成龐大的項目。

雖然今天的功能並不龐大,但 Practice makes prefect
只要從小東西開始練習,之後遇到的複雜的問題時你也能拆解問題

首先我們先看一下基礎樣式:
https://ithelp.ithome.com.tw/upload/images/20250925/20172784xlrVzFw1aa.png
兩個輸入表單頁+完成頁

我們的順序會是 :

  1. 創建三個空白頁面
  2. 建立 Routes
  3. 頁面切版
  4. 各頁面 useForm + Yup Schema
  5. 使用 Pinia 新建 Store 及初始資料
  6. 存入 Store 總驗證
  7. 測試功能是否正常

開發

  1. 創建三個空白頁面
<!-- RegisterEmail.vue -->
<template>註冊帳號</template>
<!-- RegisterInfo -->
<template>基本資料</template>
<!-- RegisterSuccess -->
<template>恭喜你發財</template>
  1. 建立 routes

//router/index.js
import { createRouter, createWebHistory } from "vue-router";
import RegisterEmail from "../views/RegisterEmail.vue";
import RegisterInfo from "../views/RegisterInfo.vue";
import RegisterSuccess from "../views/RegisterSuccess.vue";

const routes = [
  { path: "/", redirect: "/register" },
  {
    path: "/register",
    name: "Register",
    children: [
      {
        path: "",
        name: "RegisterEmail",
        component: RegisterEmail,
      },
      {
        path: "info",
        name: "RegisterInfo",
        component: RegisterInfo,
      },
      {
        path: "success",
        name: "RegisterSuccess",
        component: RegisterSuccess,
      },
    ],
  },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});
export default router;
  1. 頁面切版

<!-- RegisterEmail.vue -->
<script setup>
import { useRouter } from "vue-router";
import VInput from "../components/VInput.vue";

const router = useRouter();

function handleSubmit() {
  router.push({ name: "RegisterInfo" });
}
</script>

<template>
  <div class="min-h-screen flex items-center justify-center p-5">
    <div class="p-10 bg-white rounded-3xl shadow-2xl w-full max-w-lg">
      <h1 class="mb-8 text-center text-3xl">註冊帳號</h1>
      <form @submit.prevent="handleSubmit">
        <VInput name="email" type="email" label="電子郵件" />
        <VInput name="password" type="password" label="密碼" />
        <VInput name="confirmPassword" type="password" label="確認密碼" />
        <!-- 這邊不使用 <router-link> 是因為後面還要把資料存進 store -->
        <button
          type="submit"
          class="w-full mt-5 py-3.5 border rounded-lg cursor-pointer"
        >
          下一步
        </button>
      </form>
    </div>
  </div>
</template>
<!-- RegisterInfo.vue -->
<script setup>
import { useRouter } from "vue-router";
import VInput from "../components/VInput.vue";

const router = useRouter();

function handleSubmit() {
  router.push({ name: "RegisterInfo" });
}
</script>

<template>
  <div class="min-h-screen flex items-center justify-center p-5">
    <div class="p-10 bg-white rounded-3xl shadow-2xl w-full max-w-lg">
      <h1 class="mb-8 text-center text-3xl">註冊帳號</h1>
      <form @submit.prevent="handleSubmit">
        <VInput name="firstName" type="text" label="姓名" />
        <VInput name="phone" type="tel" label="手機號碼" />
        <VInput name="address" type="text" label="地址" />
        <!-- 這邊不使用 <router-link> 原因同上 -->
        <button
          type="submit"
          class="w-full mt-5 py-3.5 border rounded-lg cursor-pointer"
        >
          下一步
        </button>
      </form>
    </div>
  </div>
</template>
<!-- RegisterSuccess.vue -->
<template>
  <div class="min-h-screen flex items-center justify-center p-5">
    <h1 class="text-4xl">恭喜你發財</h1>
  </div>
</template>

https://ithelp.ithome.com.tw/upload/images/20250925/20172784XJQ1ADTuZK.png
▲ 基礎切版不帶驗證功能

  1. 各頁面 useForm() + Yup Schema

<!-- RegisterEmail.vue -->
<script setup>
import { useRouter } from "vue-router";
+ import { useForm } from "vee-validate";
+ import * as yup from "yup";
import VInput from "../components/VInput.vue";

const router = useRouter();

+ // 這邊可以這邊可以改成你自己的 schema
+ const schema = yup.object({
+  username: yup.string().required("帳號為必填項目"),
+  email: yup
+    .string()
+    .required("電子郵件為必填項目")
+    .email("請輸入有效的電子郵件格式"),
+  password: yup
+    .string()
+    .required("密碼為必填項目")
+    .min(8, "密碼至少需要8個字元")
+    .matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, "密碼必須包含大小寫字母和數字"),
+  confirmPassword: yup
+    .string()
+    .required("確認密碼為必填項目")
+    .oneOf([yup.ref("password")], "密碼確認不符"),
+ });

+ useForm({ validationSchema: schema });

function handleSubmit() {
  router.push({ name: "RegisterInfo" });
}
</script>
<!-- RegisterInfo.vue -->
<script setup>
import { useRouter } from "vue-router";
+ import { useForm } from "vee-validate";
+ import * as yup from "yup";
import VInput from "../components/VInput.vue";

const router = useRouter();

+ // 這邊可以這邊可以改成你自己的 schema
+ const schema = yup.object({
+  firstName: yup.string().required("姓名為必填項目"),
+  phone: yup
+    .string()
+    .required("手機號碼為必填項目")
+    .matches(/^09\d{8}$/, "請輸入有效的手機號碼格式"),
+  birthDate: yup.date().required("出生日期為必填項目"),
+  gender: yup.string().required("性別為必填項目"),
+  address: yup.string().required("地址為必填項目"),
+ });

+ useForm({ validationSchema: schema });

function handleSubmit() {
  router.replace({ name: "RegisterSuccess" });
}
</script>
  1. 使用 Pinia 新建 Store 及初始資料

//store/register.js
import { defineStore } from "pinia";

export const useRegisterStore = defineStore("register", {
  // 初始資料
  state: () => ({
    form: {
      username: "",
      email: "",
      password: "",
      confirmPassword: "",
      firstName: "",
      phone: "",
      birthDate: "",
      gender: "",
      address: "",
    },
  }),

  actions: {
    // 獲取資料
    loadData() {
      return {
        form: this.form,
      };
    },
    // 將資料存進 store
    assignData(data) {
      Object.assign(this.form, data);
    },
  },
});
  1. 將各頁面資料存進 Store 並在最後總驗證

https://ithelp.ithome.com.tw/upload/images/20250925/20172784nxhjyFrwhq.png

<!-- RegisterEmail.vue -->
<script setup>
import { useRouter } from "vue-router";
import { useForm } from "vee-validate";
import * as yup from "yup";
+ import { useRegisterStore } from "../stores/register";
import VInput from "../components/VInput.vue";

const router = useRouter();
+ const registerStore = useRegisterStore();

const schema = yup.object({
 // 略...
});

- useForm({ validationSchema: schema });
+ const { values, setValues } = useForm({ validationSchema: schema });

function handleSubmit() {
+ registerStore.assignData(values);
  router.push({ name: "RegisterInfo" });
}

+ onMounted(() => {
+   const savedData = registerStore.fetchData();

+   if (savedData.form.email) {
+    setValues({
+      email: savedData.form.email,
+      password: savedData.form.password,
+      confirmPassword: savedData.form.confirmPassword,
+    });
+  }
+});
</script>
<!-- RegisterInfo.vue -->
<script setup>
import { useRouter } from "vue-router";
import { useForm } from "vee-validate";
import * as yup from "yup";
+ import { useRegisterStore } from "../stores/register";
import VInput from "../components/VInput.vue";
import * as yup from "yup";

const router = useRouter();
const registerStore = useRegisterStore();

const schema = yup.object({
 // 略...
});

+ const allSchema = yup.object({
+ // 這邊為了方便示範,手動把前一頁和這一頁的 schema合併起來而已
+ });

+ useForm({ validationSchema: schema });

- function handleSubmit() {
+ async function handleSubmit() {
+  try {
+    registerStore.assignData(values);
+    await allSchema.validate(registerStore.form);
+    這邊手動對 Store 中的資料作總驗證, 這是 yup 自帶的 validate()
     router.replace({ name: "RegisterSuccess" });
+  } catch (e) {
+    alert(`驗證失敗: ${e.message}`);
+    return;
+  }  
+};

+ onMounted(() => {
+   const savedData = registerStore.fetchData();
+
+   if (savedData.form.firstName) {
+     setValues({
+       firstName: savedData.form.firstName,
+       phone: savedData.form.phone,
+       address: savedData.form.address,
+     });
+   }
+ });
</script>

7 . 測試輸入以及送出會不會正常驗證


https://ithelp.ithome.com.tw/upload/images/20250925/20172784i5L9a9RAdo.png

  1. 如果一切順利的話就完成完成!

https://ithelp.ithome.com.tw/upload/images/20250925/20172784dyJpgLinSE.png
發票又中 500 好爽

結語

今天呢!

我們一路從 Tailwind 切版,Vue Router 建立路由,
Vee-validate 驗證資料格式,Pinia 狀態管理,最後總驗證全部資料。

致此我們全部基礎練習和套件使用篇章到達尾聲
明天將進入最後一個篇章,進階使用及觀念補充。

一些小練習

  1. 試著複刻我們上面的流程吧!

上一篇
在 Vue 過氣前要學的第二十五件事 - 我真的不是機器人 / 跨頁表單驗證 ( 中 ) / Vee-validate & Yup
系列文
在 Vue 過氣前要學的三十件事26
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言