فصل سوم: مدل داده و مدیریت وضعیت با 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;
    },
  },
});

توضیحات اجزا:

  1. تعریف Store:

  2. از defineStore برای ایجاد یک store به نام ships استفاده شده است.

  3. type { Ship } از فایل تایپ‌ها (~/types) وارد شده تا تایپینگ دقیق برای داده‌های کشتی‌ها فراهم شود.

  4. وضعیت (State):

  5. ships: آرایه‌ای از اشیاء Ship که لیست تمام کشتی‌ها را ذخیره می‌کند.

  6. filters: شامل دو فیلتر:
    • search: رشته‌ای برای جستجوی کشتی‌ها بر اساس نام.
    • maxPrice: حداکثر قیمت روزانه برای فیلتر کردن کشتی‌ها (به‌صورت پیش‌فرض Infinity برای نمایش همه).
  7. pagination: تنظیمات صفحه‌بندی:

    • page: شماره صفحه فعلی.
    • perPage: تعداد کشتی‌های نمایش‌داده‌شده در هر صفحه (در اینجا ۶).
  8. Getters:

  9. filteredShips: لیستی از کشتی‌ها را پس از اعمال فیلترهای جستجو و قیمت و همچنین صفحه‌بندی برمی‌گرداند.

    • ابتدا کشتی‌ها را بر اساس تطابق نام با search فیلتر می‌کند (بدون حساسیت به حروف کوچک/بزرگ).
    • سپس کشتی‌هایی که قیمت روزانه آن‌ها کمتر یا برابر با maxPrice است را نگه می‌دارد.
    • در نهایت، با استفاده از slice، فقط کشتی‌های مربوط به صفحه فعلی را نمایش می‌دهد.
  10. getShipById: تابعی که با دریافت id، کشتی مربوطه را از لیست پیدا می‌کند.
  11. totalPages: تعداد کل صفحات را با تقسیم تعداد کشتی‌ها بر تعداد آیتم‌های هر صفحه محاسبه می‌کند.

  12. Actions:

  13. fetchShips: به‌صورت غیرهمزمان داده‌های کشتی‌ها را از مسیر /api/ships دریافت می‌کند.

    • از useFetch (تابع داخلی Nuxt) استفاده می‌کند و baseURL را از runtimeConfig می‌خواند.
    • داده‌های دریافتی را در ships ذخیره می‌کند یا اگر داده‌ای وجود نداشت، آرایه خالی تنظیم می‌کند.
  14. setSearch: فیلتر جستجو را به‌روزرسانی می‌کند و شماره صفحه را به ۱ برمی‌گرداند تا نتایج از ابتدا نمایش داده شوند.
  15. setMaxPrice: فیلتر قیمت را تنظیم می‌کند و صفحه را ریست می‌کند.
  16. 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 || [];
    },
  },
});

توضیحات اجزا:

  1. تعریف Store:

  2. store با نام bookings تعریف شده و از تایپ Booking استفاده می‌کند.

  3. وضعیت (State):

  4. bookings: آرایه‌ای از اشیاء Booking که تمام رزروهای کاربر را ذخیره می‌کند.

  5. Actions:

  6. addBooking: یک رزرو جدید را به سرور ارسال کرده و پاسخ را به لیست رزروها اضافه می‌کند.

    • از $fetch (تابع داخلی Nuxt) برای ارسال درخواست POST به /api/bookings استفاده می‌کند.
    • ورودی booking شامل تمام فیلدهای مدل Booking به‌جز id است (زیرا id توسط سرور تولید می‌شود).
    • پاسخ سرور (رزرو جدید با id) به آرایه bookings اضافه می‌شود.
  7. 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ها در صفحات و کامپوننت‌ها برای نمایش و مدیریت داده‌ها استفاده خواهیم کرد.