廢話少說開始寫扣吧!
請確保你以下所有套件都有安裝在專案中,
如果還沒安裝建議到各篇文章中看如何安裝。
在 Vue 過氣前要學的第十九件事 - Vue Router
在 Vue 過氣前要學的第二十一件事 - Tailwind v4
在 Vue 過氣前要學的第二十四件事- Pinia
在 Vue 過氣前要學的第二十五件事- Vee-validate & Yup
這邊的 Input 將全都使用前篇文中實作出來的驗證 input
且為了不太過擴散,此篇實作中不包含 <select>
、<textarea>
此篇文章程式碼偏多,建議邊做邊看避免漏掉某些細節
在開發任何功能前要做好規劃,有助於了解是否會有突發狀況;
避免要驗收了才突然發現事情遠不只你想的那麼簡單,
也能讓你像是在解決代辦清單一樣一步一步完成龐大的項目。
雖然今天的功能並不龐大,但 Practice makes prefect,
只要從小東西開始練習,之後遇到的複雜的問題時你也能拆解問題。
首先我們先看一下基礎樣式:
▲兩個輸入表單頁+完成頁
我們的順序會是 :
<!-- RegisterEmail.vue -->
<template>註冊帳號</template>
<!-- RegisterInfo -->
<template>基本資料</template>
<!-- RegisterSuccess -->
<template>恭喜你發財</template>
//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;
<!-- 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>
▲ 基礎切版不帶驗證功能
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>
//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);
},
},
});
<!-- 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 . 測試輸入以及送出會不會正常驗證
▲ 發票又中 500 好爽
今天呢!
我們一路從 Tailwind 切版,Vue Router 建立路由,
Vee-validate 驗證資料格式,Pinia 狀態管理,最後總驗證全部資料。
致此我們全部基礎練習和套件使用篇章到達尾聲,
明天將進入最後一個篇章,進階使用及觀念補充。