ضمیمه ج: جزئیات کامل پیادهسازی کامپوننتهای اضافی (فصل ۵)
این ضمیمه جزئیات کامل درباره کامپوننتهای اضافی که در فصل پنجم بهصورت خلاصه یا غایب بودند را ارائه میدهد. تمرکز بر پیادهسازی کامپوننتهای AppHeader.vue (هدر) و BookingForm.vue (فرم رزرو جداگانه) و همچنین نحوه استایلدهی آنها با Tailwind CSS است. این کامپوننتها به اپلیکیشن مرکز اجاره کشتیهای کروز قابلیتهای ماژولار و کاربرپسند اضافه میکنند و از اصول طراحی پاسخگو و مدرن پیروی میکنند.
کامپوننت هدر (AppHeader.vue)
هدر اپلیکیشن برای ناوبری و ارائه یک تجربه کاربری یکپارچه طراحی شده است. در فصل پنجم، هدر بهصورت پیشفرض در layouts/default.vue پیادهسازی شده بود، اما تبدیل آن به یک کامپوننت جداگانه امکان استفاده مجدد و نگهداری بهتر را فراهم میکند.
کد کامل:
<!-- components/AppHeader.vue -->
<template>
<header class="bg-blue-800 text-white p-4 sticky top-0 z-10">
<nav class="container mx-auto flex justify-between items-center">
<!-- لوگو و لینک به صفحه اصلی -->
<NuxtLink to="/" class="text-2xl font-bold tracking-tight">
Cruise Rental
</NuxtLink>
<!-- لینکهای ناوبری -->
<div class="space-x-4 flex items-center">
<NuxtLink to="/ships" class="hover:underline hover:text-blue-200 transition duration-200">
Ships
</NuxtLink>
<NuxtLink to="/bookings" class="hover:underline hover:text-blue-200 transition duration-200">
Bookings
</NuxtLink>
<!-- دکمه ورود/خروج -->
<template v-if="authData">
<span class="text-sm">{{ authData.user?.name }}</span>
<button @click="signOut" class="btn btn-sm bg-red-500 hover:bg-red-600">
Sign Out
</button>
</template>
<NuxtLink v-else to="/login" class="btn btn-sm">
Sign In
</NuxtLink>
</div>
</nav>
</header>
</template>
<script setup>
import { useAuth, signOut } from '#auth';
const { data: authData } = useAuth();
</script>
توضیحات:
-
ساختار:
-
<header>: از کلاسهای Tailwind مانندbg-blue-800,text-white, وsticky top-0برای ایجاد یک هدر ثابت با پسزمینه آبی تیره استفاده میکند. <nav>: شامل لوگو (لینک به/) و لینکهای ناوبری به صفحات/shipsو/bookings.- ورود/خروج: با استفاده از
useAuthاز@sidebase/nuxt-auth، وضعیت احراز هویت بررسی میشود. اگر کاربر وارد شده باشد، نام کاربر و دکمه خروج نمایش داده میشود؛ در غیر این صورت، لینک ورود ظاهر میشود. -
استایلدهی:
-
کلاسهای Tailwind مانند
hover:underline,transition duration-200, وbtn(تعریفشده درassets/css/main.css) برای جلوههای بصری و پاسخگویی. container mx-auto: محتوا را در مرکز صفحه با حاشیههای مناسب نگه میدارد.flex justify-between items-center: برای چیدمان افقی و تراز عمودی لینکها.-
کاربرد:
-
این کامپوننت در
layouts/default.vueاستفاده میشود تا در تمام صفحات نمایش داده شود:
<!-- layouts/default.vue -->
<template>
<div class="min-h-screen bg-gray-50">
<AppHeader />
<main class="container mx-auto p-6">
<slot />
</main>
</div>
</template>
-
مزایا:
-
ماژولاریتی: جداسازی هدر بهعنوان یک کامپوننت مستقل، نگهداری و گسترش را آسان میکند.
- پاسخگویی: طراحی با Tailwind برای دستگاههای مختلف پاسخگو است.
- ادغام با احراز هویت: نمایش پویا بر اساس وضعیت ورود کاربر.
کامپوننت فرم رزرو (BookingForm.vue)
فرم رزرو که در pages/ships/[id].vue استفاده شده بود، میتواند به یک کامپوننت جداگانه تبدیل شود تا قابلیت استفاده مجدد و کد تمیزتر فراهم شود. این کامپوننت از vee-validate برای اعتبارسنجی فرم استفاده میکند.
کد کامل:
<!-- components/BookingForm.vue -->
<template>
<form @submit.prevent="submit" class="space-y-4 max-w-md mx-auto">
<div>
<label class="block text-sm font-medium text-gray-700">Start Date</label>
<input
v-model="form.startDate"
type="date"
class="border p-2 rounded w-full focus:ring focus:ring-blue-200"
/>
<span v-if="errors.startDate" class="error">{{ errors.startDate }}</span>
</div>
<div>
<label class="block text-sm font-medium text-gray-700">End Date</label>
<input
v-model="form.endDate"
type="date"
class="border p-2 rounded w-full focus:ring focus:ring-blue-200"
/>
<span v-if="errors.endDate" class="error">{{ errors.endDate }}</span>
</div>
<button type="submit" class="btn w-full">Book Now</button>
</form>
</template>
<script setup>
import { useForm } from 'vee-validate';
import * as yup from 'yup';
import type { Ship } from '~/types';
defineProps<{ ship: Ship }>();
defineEmits<{
(e: 'submit', values: { startDate: string; endDate: string }): void;
}>();
const schema = yup.object({
startDate: yup
.date()
.required('Start date is required')
.min(new Date(), 'Start date must be today or later'),
endDate: yup
.date()
.required('End date is required')
.min(yup.ref('startDate'), 'End date must be after start date'),
});
const { handleSubmit, errors } = useForm({ validationSchema: schema });
const form = reactive({ startDate: '', endDate: '' });
const submit = handleSubmit((values) => {
emit('submit', values);
});
</script>
استفاده در pages/ships/[id].vue:
<!-- pages/ships/[id].vue -->
<script setup>
import { useBookingsStore } from '~/stores/bookings';
import type { Ship } from '~/types';
import { loadStripe } from '@stripe/stripe-js';
const route = useRoute();
const bookingsStore = useBookingsStore();
const config = useRuntimeConfig();
const { data: ship } = await useAsyncData('ship', () => $fetch(`/api/ships/${route.params.id}`));
const handleBooking = async (values: { startDate: string; endDate: string }) => {
if (!ship.value) return;
const days = (new Date(values.endDate) - new Date(values.startDate)) / (1000 * 60 * 60 * 24);
const totalPrice = days * ship.value.pricePerDay;
const booking = {
shipId: ship.value.id,
startDate: new Date(values.startDate),
endDate: new Date(values.endDate),
totalPrice,
};
const { id } = await $fetch('/api/stripe-checkout', {
method: 'POST',
body: { booking },
});
const stripe = await loadStripe(config.public.stripePublishableKey);
await stripe.redirectToCheckout({ sessionId: id });
await bookingsStore.addBooking(booking);
alert('Booking successful!');
};
</script>
<template>
<div v-if="ship" class="container mx-auto p-6">
<h1 class="text-3xl font-bold mb-6">{{ ship.name }}</h1>
<NuxtImg :src="ship.image" alt="Ship" class="w-full h-64 object-cover mb-4 rounded" />
<BookingForm :ship="ship" @submit="handleBooking" />
</div>
</template>
توضیحات:
-
ساختار:
-
<form>: شامل دو فیلد ورودی برای تاریخ شروع و پایان، با اعتبارسنجی توسطvee-validateو Yup. - فیلدها:
startDate: تاریخ شروع رزرو، باید امروز یا آینده باشد.endDate: تاریخ پایان رزرو، باید پس ازstartDateباشد.
- دکمه: دکمه "Book Now" با کلاس
btnبرای ارسال فرم. -
استایلدهی:
-
کلاسهای Tailwind مانند
space-y-4,max-w-md, وmx-autoبرای چیدمان و پاسخگویی. focus:ring focus:ring-blue-200: افکت فوکوس برای ورودیها.error: کلاس سفارشی (تعریفشده درassets/css/main.css) برای نمایش خطاها.-
اعتبارسنجی:
-
از Yup برای تعریف قوانین اعتبارسنجی استفاده شده است:
required: فیلدها اجباری هستند.min(new Date()): تاریخ شروع باید امروز یا آینده باشد.min(yup.ref('startDate')): تاریخ پایان باید پس از تاریخ شروع باشد.
- خطاها با کلاس
errorنمایش داده میشوند. -
کاربرد:
-
این کامپوننت در
ships/[id].vueاستفاده میشود تا فرم رزرو را بهصورت جداگانه مدیریت کند. - رویداد
submitدادههای فرم را به صفحه والد (ships/[id].vue) ارسال میکند تا برای محاسبه قیمت و ارسال به Stripe استفاده شود. -
مزایا:
-
ماژولاریتی: جداسازی فرم رزرو کد را تمیزتر و قابل نگهداریتر میکند.
- اعتبارسنجی قوی: استفاده از
vee-validateخطاهای کاربر را بهصورت بلادرنگ نمایش میدهد. - پاسخگویی: طراحی با Tailwind برای دستگاههای مختلف مناسب است.
استایلدهی اضافی با Tailwind CSS
برای اطمینان از یکپارچگی بصری در کامپوننتها، استایلهای سفارشی در assets/css/main.css تعریف شدهاند:
@tailwind base;
@tailwind components;
@tailwind utilities;
.card {
@apply bg-white shadow-md rounded-lg overflow-hidden;
}
.btn {
@apply bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 transition duration-200;
}
.btn-sm {
@apply px-3 py-1 text-sm;
}
.error {
@apply text-red-500 text-sm mt-1;
}
-
کلاسهای استفادهشده:
-
.card: برای کامپوننتهایی مانندShipCardو کارتهای رزرو درbookings.vue. .btnو.btn-sm: برای دکمهها درAppHeaderوBookingForm..error: برای نمایش خطاهای اعتبارسنجی درBookingForm.-
پاسخگویی:
-
در
AppHeader.vue:
@media (max-width: 640px) {
.space-x-4 {
@apply flex-col space-y-2 space-x-0;
}
}
- این کد باعث میشود لینکهای ناوبری در دستگاههای کوچک بهصورت عمودی نمایش داده شوند.
- در
BookingForm.vue:max-w-md mx-autoفرم را در دستگاههای بزرگ متمرکز و محدود به عرض متوسط میکند.
چرا این بخش در ضمیمهها خلاصه بود؟
ضمیمههای اولیه فقط به کامپوننت ShipCard.vue اشاره کردند و کامپوننتهای اضافی مانند AppHeader.vue و BookingForm.vue را پوشش ندادند. همچنین، جزئیات استایلدهی و ادغام این کامپوننتها با احراز هویت و فرمهای پویا بهصورت کامل توضیح داده نشده بود.
نکات عملی برای پیادهسازی
-
ایجاد کامپوننتها:
-
فایلهای
AppHeader.vueوBookingForm.vueرا در پوشهcomponents/ایجاد کنید. -
اطمینان حاصل کنید که
assets/css/main.cssشامل کلاسهای سفارشی است. -
ادغام با احراز هویت:
-
در
AppHeader.vue، از@sidebase/nuxt-authبرای بررسی وضعیت ورود استفاده کنید. -
مسیر
/loginرا برای هدایت کاربران غیرمجاز تنظیم کنید. -
تست پاسخگویی:
-
با ابزار DevTools مرورگر، نمایش کامپوننتها را در اندازههای مختلف صفحه (موبایل، تبلت، دسکتاپ) بررسی کنید.
-
از
npm run devبرای تست بلادرنگ استفاده کنید. -
گسترش:
-
در
AppHeader.vue، میتوانید منوی همبرگری برای موبایل اضافه کنید:
<button class="md:hidden" @click="toggleMenu">☰</button>
<div v-if="menuOpen" class="md:hidden flex flex-col space-y-2 mt-2">
<NuxtLink to="/ships">Ships</NuxtLink>
<NuxtLink to="/bookings">Bookings</NuxtLink>
</div>
<script setup>
const menuOpen = ref(false);
const toggleMenu = () => { menuOpen.value = !menuOpen.value; };
</script>
- در
BookingForm.vue، فیلدهای اضافی مانند تعداد مسافران اضافه کنید:
const schema = yup.object({
startDate: yup.date().required().min(new Date()),
endDate: yup.date().required().min(yup.ref('startDate')),
passengers: yup.number().required('Number of passengers is required').min(1),
});
جمعبندی ضمیمه ج
این ضمیمه جزئیات کامل پیادهسازی کامپوننتهای AppHeader.vue و BookingForm.vue را ارائه داد، شامل کد، استایلدهی با Tailwind CSS، و ادغام با احراز هویت و اعتبارسنجی فرم. این کامپوننتها اپلیکیشن را ماژولارتر و کاربرپسندتر میکنند. برای اطلاعات بیشتر، به فصلهای ۵ و ۱۱ مراجعه کنید یا مخزن فرضی GitHub (https://github.com/khosronz/cruise-rental-app) را بررسی کنید.