فصل سوم: مدل داده و مدیریت وضعیت با Pinia
در این فصل، به بررسی مدل داده اپلیکیشن مرکز اجاره کشتیهای کروز و نحوه استفاده از Pinia، کتابخانه مدیریت وضعیت مدرن برای Vue 3، میپردازیم. Pinia به ما کمک میکند تا دادههای مشترک مانند اطلاعات کشتیها و رزروها را بهصورت متمرکز مدیریت کنیم. همچنین، از TypeScript برای تعریف تایپهای دقیق استفاده خواهیم کرد تا کد ایمنتر و قابل نگهداریتر باشد. این فصل شامل تعریف مدلهای داده، پیادهسازی storeهای Pinia برای کشتیها و رزروها، و توضیح کامل اجزای هر store است.
مدل داده (Data Model)
مدل داده، ساختار اصلی دادههای اپلیکیشن را تعریف میکند. در این پروژه، دو مدل داده اصلی داریم: Ship (کشتی) و Booking (رزرو). این مدلها بهعنوان پایهای برای مدیریت اطلاعات در اپلیکیشن استفاده میشوند.
۱. مدل Ship (کشتی)
این مدل، اطلاعات مربوط به هر کشتی کروز را ذخیره میکند.
{
id: number, // شناسه یکتا برای هر کشتی
name: string, // نام کشتی (مثلاً "Ocean Explorer")
capacity: number, // ظرفیت کشتی (تعداد مسافران)
pricePerDay: number, // قیمت اجاره روزانه (به دلار)
amenities: string[], // امکانات کشتی (مثلاً ["استخر", "باشگاه"])
image: string // آدرس تصویر کشتی
}
توضیحات:
- id: یک عدد یکتا برای شناسایی هر کشتی.
- name: نام کشتی که در صفحات لیست و جزئیات نمایش داده میشود.
- capacity: تعداد مسافرانی که کشتی میتواند حمل کند.
- pricePerDay: هزینه اجاره کشتی بهازای هر روز.
- amenities: لیستی از امکانات موجود در کشتی (مانند استخر، سالن تئاتر، یا اسپا).
- image: آدرس تصویر کشتی برای نمایش در رابط کاربری.
۲. مدل Booking (رزرو)
این مدل، اطلاعات مربوط به رزروهای کاربران را ذخیره میکند.
{
id: number, // شناسه یکتا برای هر رزرو
shipId: number, // شناسه کشتی رزروشده
startDate: Date, // تاریخ شروع رزرو
endDate: Date, // تاریخ پایان رزرو
totalPrice: number // قیمت کل رزرو
}
توضیحات:
- id: یک عدد یکتا برای شناسایی هر رزرو.
- shipId: شناسه کشتیای که کاربر رزرو کرده است، که به مدل Ship متصل میشود.
- startDate: تاریخ شروع رزرو (بهصورت شیء Date).
- endDate: تاریخ پایان رزرو.
- totalPrice: هزینه کل رزرو، که معمولاً از ضرب تعداد روزهای رزرو در
pricePerDayکشتی محاسبه میشود.
Store کشتیها (stores/ships.ts)
این store وظیفه مدیریت دادههای مربوط به کشتیها، فیلترها و صفحهبندی را بر عهده دارد.
کد:
// stores/ships.ts
import { defineStore } from 'pinia';
import type { Ship } from '~/types';
export const useShipsStore = defineStore('ships', {
state: () => ({
ships: [] as Ship[], // لیست کشتیها
filters: { search: '', maxPrice: Infinity }, // فیلترهای جستجو
pagination: { page: 1, perPage: 6 }, // تنظیمات صفحهبندی
}),
getters: {
filteredShips(state): Ship[] {
return state.ships
.filter(ship => ship.name.toLowerCase().includes(state.filters.search.toLowerCase()))
.filter(ship => ship.pricePerDay <= state.filters.maxPrice)
.slice(
(state.pagination.page - 1) * state.pagination.perPage,
state.pagination.page * state.pagination.perPage
);
},
getShipById: (state) => (id: number) => state.ships.find(ship => ship.id === id),
totalPages: (state) => Math.ceil(state.ships.length / state.pagination.perPage),
},
actions: {
async fetchShips() {
const { data } = await useFetch('/api/ships', {
baseURL: useRuntimeConfig().public.apiBase,
});
this.ships = data.value || [];
},
setSearch(search: string) {
this.filters.search = search;
this.pagination.page = 1;
},
setMaxPrice(maxPrice: number) {
this.filters.maxPrice = maxPrice;
this.pagination.page = 1;
},
setPage(page: number) {
this.pagination.page = page;
},
},
});
توضیحات اجزا:
-
تعریف Store:
-
از
defineStoreبرای ایجاد یک store به نامshipsاستفاده شده است. -
type { Ship }از فایل تایپها (~/types) وارد شده تا تایپینگ دقیق برای دادههای کشتیها فراهم شود. -
وضعیت (State):
-
ships: آرایهای از اشیاء Ship که لیست تمام کشتیها را ذخیره میکند.
- filters: شامل دو فیلتر:
search: رشتهای برای جستجوی کشتیها بر اساس نام.maxPrice: حداکثر قیمت روزانه برای فیلتر کردن کشتیها (بهصورت پیشفرض Infinity برای نمایش همه).
-
pagination: تنظیمات صفحهبندی:
page: شماره صفحه فعلی.perPage: تعداد کشتیهای نمایشدادهشده در هر صفحه (در اینجا ۶).
-
Getters:
-
filteredShips: لیستی از کشتیها را پس از اعمال فیلترهای جستجو و قیمت و همچنین صفحهبندی برمیگرداند.
- ابتدا کشتیها را بر اساس تطابق نام با
searchفیلتر میکند (بدون حساسیت به حروف کوچک/بزرگ). - سپس کشتیهایی که قیمت روزانه آنها کمتر یا برابر با
maxPriceاست را نگه میدارد. - در نهایت، با استفاده از
slice، فقط کشتیهای مربوط به صفحه فعلی را نمایش میدهد.
- ابتدا کشتیها را بر اساس تطابق نام با
- getShipById: تابعی که با دریافت
id، کشتی مربوطه را از لیست پیدا میکند. -
totalPages: تعداد کل صفحات را با تقسیم تعداد کشتیها بر تعداد آیتمهای هر صفحه محاسبه میکند.
-
Actions:
-
fetchShips: بهصورت غیرهمزمان دادههای کشتیها را از مسیر
/api/shipsدریافت میکند.- از
useFetch(تابع داخلی Nuxt) استفاده میکند وbaseURLرا ازruntimeConfigمیخواند. - دادههای دریافتی را در
shipsذخیره میکند یا اگر دادهای وجود نداشت، آرایه خالی تنظیم میکند.
- از
- setSearch: فیلتر جستجو را بهروزرسانی میکند و شماره صفحه را به ۱ برمیگرداند تا نتایج از ابتدا نمایش داده شوند.
- setMaxPrice: فیلتر قیمت را تنظیم میکند و صفحه را ریست میکند.
- setPage: شماره صفحه فعلی را بهروزرسانی میکند.
کاربرد: این store امکان مدیریت لیست کشتیها، فیلتر کردن بر اساس نام و قیمت، و صفحهبندی را فراهم میکند. برای مثال، در صفحه لیست کشتیها، از filteredShips برای نمایش کشتیهای فیلترشده استفاده میشود.
Store رزروها (stores/bookings.ts)
این store برای مدیریت رزروهای کاربران ایجاد شده است.
کد:
// stores/bookings.ts
import { defineStore } from 'pinia';
import type { Booking } from '~/types';
export const useBookingsStore = defineStore('bookings', {
state: () => ({
bookings: [] as Booking[], // لیست رزروها
}),
actions: {
async addBooking(booking: Omit<Booking, 'id'>) {
const response = await $fetch('/api/bookings', {
method: 'POST',
body: booking,
});
this.bookings.push(response);
},
async fetchBookings() {
const { data } = await useFetch('/api/bookings');
this.bookings = data.value || [];
},
},
});
توضیحات اجزا:
-
تعریف Store:
-
store با نام
bookingsتعریف شده و از تایپBookingاستفاده میکند. -
وضعیت (State):
-
bookings: آرایهای از اشیاء Booking که تمام رزروهای کاربر را ذخیره میکند.
-
Actions:
-
addBooking: یک رزرو جدید را به سرور ارسال کرده و پاسخ را به لیست رزروها اضافه میکند.
- از
$fetch(تابع داخلی Nuxt) برای ارسال درخواست POST به/api/bookingsاستفاده میکند. - ورودی
bookingشامل تمام فیلدهای مدل Booking بهجزidاست (زیراidتوسط سرور تولید میشود). - پاسخ سرور (رزرو جدید با
id) به آرایهbookingsاضافه میشود.
- از
- fetchBookings: لیست رزروها را از مسیر
/api/bookingsدریافت کرده و درbookingsذخیره میکند.- از
useFetchبرای درخواست GET استفاده میکند و اگر دادهای وجود نداشت، آرایه خالی تنظیم میکند.
- از
کاربرد: این store برای افزودن رزروهای جدید (مثلاً هنگام تکمیل فرم رزرو) و نمایش تاریخچه رزروها در داشبورد کاربر استفاده میشود.
تعریف تایپها (types/index.ts)
برای اطمینان از ایمنی نوع در پروژه، مدلهای داده را با TypeScript تعریف میکنیم.
کد:
// types/index.ts
export interface Ship {
id: number;
name: string;
capacity: number;
pricePerDay: number;
amenities: string[];
image: string;
}
export interface Booking {
id: number;
shipId: number;
startDate: Date;
endDate: Date;
totalPrice: number;
}
توضیحات:
-
Ship Interface:
-
تعریف دقیق فیلدهای مدل Ship با تایپهای مشخص (مثلاً
numberبرایidوstring[]برایamenities). - این تایپ در store کشتیها و کامپوننتها برای اطمینان از صحت دادهها استفاده میشود.
-
Booking Interface:
-
تعریف فیلدهای مدل Booking، با تایپهای مناسب (مثلاً
DateبرایstartDateوendDate). - استفاده از
shipIdبرای ارتباط رزرو با یک کشتی خاص.
کاربرد: این فایل تایپهای مشترک را در سراسر پروژه فراهم میکند تا از خطاهای تایپ در زمان توسعه جلوگیری شود.
جمعبندی فصل سوم
در این فصل، مدلهای داده اپلیکیشن (Ship و Booking) را تعریف کردیم و با استفاده از Pinia، دو store برای مدیریت کشتیها و رزروها ایجاد کردیم. store کشتیها قابلیت فیلتر، صفحهبندی و دریافت دادهها را فراهم میکند، در حالی که store رزروها امکان افزودن و دریافت رزروها را مدیریت میکند. استفاده از TypeScript ایمنی و مقیاسپذیری کد را افزایش میدهد. در فصلهای بعدی، از این storeها در صفحات و کامپوننتها برای نمایش و مدیریت دادهها استفاده خواهیم کرد.