ضمیمه ب: توضیحات کامل مدلهای داده (فصل ۳)
این ضمیمه جزئیات عمیقتر درباره مدلهای داده (Ship و Booking) و نحوه استفاده آنها در APIها، storeها، و صفحات اپلیکیشن مرکز اجاره کشتیهای کروز را ارائه میدهد. این اطلاعات که در فصل سوم بهصورت خلاصه یا ناقص پوشش داده شده بودند، در اینجا بهطور جامع شرح داده میشوند تا درک کاملتری از ساختار دادهها و کاربرد آنها فراهم شود.
مدلهای داده
مدلهای داده در فایل types/index.ts تعریف شدهاند و برای اطمینان از ایمنی نوع (Type Safety) در سراسر اپلیکیشن با TypeScript استفاده میشوند. این مدلها پایهای برای تعاملات API، مدیریت وضعیت با Pinia، و نمایش دادهها در رابط کاربری هستند.
مدل Ship
- تعریف:
// types/index.ts
export interface Ship {
id: number;
name: string;
capacity: number;
pricePerDay: number;
amenities: string[];
image: string;
}
-
فیلدها:
id: شناسه یکتا برای هر کشتی (عدد).name: نام کشتی (رشته، مانند "Ocean Explorer").capacity: ظرفیت مسافران (عدد، مثلاً ۵۰۰).pricePerDay: قیمت اجاره روزانه (عدد، مثلاً ۱۰۰۰۰ دلار).amenities: آرایهای از امکانات کشتی (مانند["Pool", "Gym"]).image: مسیر تصویر کشتی (رشته، مانند/images/ocean.jpg).
-
کاربرد در API:
-
مسیر سرور (
server/api/ships.ts):
import type { Ship } from '~/types';
export default defineEventHandler((): Ship[] => {
return [
{
id: 1,
name: 'Ocean Explorer',
capacity: 500,
pricePerDay: 10000,
amenities: ['Pool', 'Gym'],
image: '/images/ocean.jpg'
},
{
id: 2,
name: 'Luxury Liner',
capacity: 1000,
pricePerDay: 20000,
amenities: ['Spa', 'Theater'],
image: '/images/luxury.jpg'
},
];
});
-
توضیحات: این API یک آرایه از اشیاء
Shipرا برمیگرداند که با مدل تعریفشده درtypes/index.tsمطابقت دارند. دادهها بهصورت موقت در حافظه ذخیره شدهاند، اما در فصل ۱۱ با Supabase یا Prisma جایگزین شدند. -
کاربرد در رابط کاربری:
- در
components/ShipCard.vue:
- در
<template>
<div class="card">
<NuxtImg :src="ship.image" alt="Ship" class="w-full h-48 object-cover" provider="static" />
<div class="p-4">
<h2 class="text-xl font-semibold">{{ ship.name }}</h2>
<p class="text-gray-600">Capacity: {{ ship.capacity }}</p>
<p class="text-gray-600">Price: ${{ ship.pricePerDay }}/day</p>
<NuxtLink :to="`/ships/${ship.id}`" class="text-blue-500 hover:underline">View Details</NuxtLink>
</div>
</div>
</template>
<script setup>
defineProps<{ ship: Ship }>();
</script>
-
توضیحات: کامپوننت
ShipCardاز مدلShipبرای نمایش اطلاعات کشتی استفاده میکند. پراپshipبا تایپShipتعریف شده تا ایمنی نوع تضمین شود.- در
pages/ships/[id].vue:
- در
<script setup>
import type { Ship } from '~/types';
const route = useRoute();
const { data: ship } = await useAsyncData('ship', () => $fetch(`/api/ships/${route.params.id}`));
</script>
<template>
<div v-if="ship" class="container mx-auto p-6">
<h1 class="text-3xl font-bold">{{ ship.name }}</h1>
<NuxtImg :src="ship.image" alt="Ship" class="w-full h-64 object-cover mb-4 rounded" />
<p>Capacity: {{ ship.capacity }}</p>
<p>Price: ${{ ship.pricePerDay }}/day</p>
<p>Amenities: {{ ship.amenities.join(', ') }}</p>
</div>
</template>
-
توضیحات: این صفحه از مدل
Shipبرای نمایش جزئیات یک کشتی خاص استفاده میکند. دادهها از API دریافت شده و با<NuxtImg>بهینهسازی میشوند. -
کاربرد در Store:
-
در
stores/ships.ts:
import { defineStore } from 'pinia';
import type { Ship } from '~/types';
export const useShipsStore = defineStore('ships', {
state: () => ({
ships: [] as Ship[],
search: '',
}),
getters: {
filteredShips: (state) => state.ships.filter(ship =>
ship.name.toLowerCase().includes(state.search.toLowerCase())
),
},
actions: {
async fetchShips() {
this.ships = await $fetch('/api/ships');
},
setSearch(query: string) {
this.search = query;
},
},
});
- توضیحات: store از مدل
Shipبرای ذخیره و فیلتر کردن لیست کشتیها استفاده میکند. متدfetchShipsدادهها را از/api/shipsدریافت میکند، وfilteredShipsبرای جستجوی پویا استفاده میشود.
مدل Booking
- تعریف:
// types/index.ts
export interface Booking {
id: number;
shipId: number;
startDate: Date;
endDate: Date;
totalPrice: number;
}
-
فیلدها:
id: شناسه یکتا برای رزرو (عدد).shipId: شناسه کشتی مرتبط (مرجع بهShip.id).startDate: تاریخ شروع رزرو (شیءDate).endDate: تاریخ پایان رزرو (شیءDate).totalPrice: هزینه کل رزرو (عدد، مثلاً ۳۰۰۰۰ دلار).
-
کاربرد در API:
-
مسیر سرور (
server/api/bookings.ts):
import type { Booking } from '~/types';
let bookings: Booking[] = [];
export default defineEventHandler({
async get() {
return bookings;
},
async post(event) {
const body = await readBody(event);
const newBooking: Booking = {
id: bookings.length + 1,
shipId: body.shipId,
startDate: new Date(body.startDate),
endDate: new Date(body.endDate),
totalPrice: body.totalPrice,
};
bookings.push(newBooking);
return newBooking;
},
});
-
توضیحات: این API امکان دریافت (
GET) و افزودن (POST) رزروها را فراهم میکند. دادههای ورودی با مدلBookingمطابقت دارند و بهصورت موقت در آرایهbookingsذخیره میشوند (در فصل ۱۱ با Supabase جایگزین شد). -
کاربرد در رابط کاربری:
- در
pages/bookings.vue:
- در
<script setup>
import { useBookingsStore } from '~/stores/bookings';
import { useFormatDate } from '~/composables/useFormatDate';
const bookingsStore = useBookingsStore();
onMounted(() => bookingsStore.fetchBookings());
</script>
<template>
<div class="container mx-auto p-6">
<h1 class="text-3xl font-bold mb-6">Your Bookings</h1>
<div v-for="booking in bookingsStore.bookings" :key="booking.id" class="card mb-4">
<p>Ship ID: {{ booking.shipId }}</p>
<p>Dates: {{ useFormatDate(booking.startDate) }} to {{ useFormatDate(booking.endDate) }}</p>
<p>Total Price: ${{ booking.totalPrice }}</p>
</div>
</div>
</template>
-
توضیحات: این صفحه لیست رزروها را از store دریافت کرده و با استفاده از
useFormatDateتاریخها را بهصورت خوانا نمایش میدهد. -
کاربرد در Store:
-
در
stores/bookings.ts:
import { defineStore } from 'pinia';
import type { Booking } from '~/types';
export const useBookingsStore = defineStore('bookings', {
state: () => ({
bookings: [] as Booking[],
}),
actions: {
async fetchBookings() {
this.bookings = await $fetch('/api/bookings');
},
async addBooking(booking: Omit<Booking, 'id'>) {
const newBooking = await $fetch('/api/bookings', {
method: 'POST',
body: booking,
});
this.bookings.push(newBooking);
},
},
});
- توضیحات: store از مدل
Bookingبرای مدیریت رزروها استفاده میکند. متدfetchBookingsلیست رزروها را از API دریافت میکند، وaddBookingرزرو جدید را به API ارسال کرده و به لیست اضافه میکند.
ادغام با پایگاه داده (فصل ۱۱)
در فصل سوم، دادهها بهصورت موقت در حافظه ذخیره شدند، اما در فصل ۱۱ با Supabase یا Prisma جایگزین شدند. در زیر، جزئیات این ادغام برای مدلهای Ship و Booking ارائه شده است:
-
Supabase:
-
جدول
ships:
CREATE TABLE ships (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
capacity INTEGER NOT NULL,
price_per_day INTEGER NOT NULL,
amenities TEXT[] NOT NULL,
image TEXT NOT NULL
);
- جدول
bookings:
CREATE TABLE bookings (
id SERIAL PRIMARY KEY,
ship_id INTEGER REFERENCES ships(id),
start_date TIMESTAMP NOT NULL,
end_date TIMESTAMP NOT NULL,
total_price INTEGER NOT NULL
);
- بهروزرسانی API:
// server/api/ships.ts
import { serverSupabaseClient } from '#supabase/server';
import type { Ship } from '~/types';
export default defineEventHandler(async (event): Promise<Ship[]> => {
const client = serverSupabaseClient(event);
const { data } = await client.from('ships').select('*');
return data || [];
});
// server/api/bookings.ts
import { serverSupabaseClient } from '#supabase/server';
import type { Booking } from '~/types';
export default defineEventHandler({
async get(event) {
const client = serverSupabaseClient(event);
const { data } = await client.from('bookings').select('*');
return data || [];
},
async post(event) {
const client = serverSupabaseClient(event);
const body = await readBody(event);
const newBooking: Booking = {
id: Date.now(), // در تولید از UUID یا SERIAL استفاده کنید
shipId: body.shipId,
startDate: new Date(body.startDate),
endDate: new Date(body.endDate),
totalPrice: body.totalPrice,
};
const { data } = await client.from('bookings').insert(newBooking).select();
return data[0];
},
});
-
Prisma:
-
Schema:
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Ship {
id Int @id @default(autoincrement())
name String
capacity Int
pricePerDay Int
amenities String[]
image String
bookings Booking[]
}
model Booking {
id Int @id @default(autoincrement())
shipId Int
startDate DateTime
endDate DateTime
totalPrice Int
ship Ship @relation(fields: [shipId], references: [id])
}
- بهروزرسانی API:
// server/api/ships.ts
import { PrismaClient } from '@prisma/client';
import type { Ship } from '~/types';
const prisma = new PrismaClient();
export default defineEventHandler(async (): Promise<Ship[]> => {
return prisma.ship.findMany();
});
چرا این بخش در ضمیمهها خلاصه بود؟
ضمیمههای اولیه فقط schemaهای پایگاه داده (Supabase و Prisma) را ارائه کردند و توضیحات عمیق درباره:
- تعریف دقیق مدلهای
ShipوBookingو فیلدهای آنها. - کاربرد این مدلها در APIها، storeها، و رابط کاربری.
- ادغام کامل با پایگاه داده در فصل ۱۱.
را پوشش ندادند. این ضمیمه تمام این جنبهها را با کدهای عملی و توضیحات جامع ارائه میدهد.
نکات عملی برای پیادهسازی
-
ایجاد فایل
types/index.ts: -
فایل را در ریشه پروژه ایجاد کرده و مدلهای
ShipوBookingرا کپی کنید. -
اطمینان حاصل کنید که تمام APIها و storeها از این تایپها استفاده میکنند.
-
تست مدلها:
-
با اجرای
npm run dev، بررسی کنید که APIها (/api/shipsو/api/bookings) دادههای سازگار با مدلها را برمیگردانند. -
از TypeScript برای شناسایی خطاهای تایپ در زمان توسعه استفاده کنید.
-
گسترش مدلها:
-
میتوانید فیلدهای اضافی به
Shipاضافه کنید (مانندdescriptionیاrating). -
برای
Booking، فیلدهایی مانندuserId(برای ارتباط با کاربر) یاstatus(برای وضعیت رزرو) اضافه کنید. -
ادغام با پایگاه داده:
-
برای Supabase، جداول را در داشبورد ایجاد کرده و RLS (Row Level Security) را فعال کنید.
- برای Prisma، از
npx prisma migrate devبرای اعمال schema استفاده کنید.
جمعبندی ضمیمه ب
این ضمیمه جزئیات کامل مدلهای داده Ship و Booking را ارائه داد، از جمله تعریف، کاربرد در APIها، storeها، و رابط کاربری، و همچنین ادغام با پایگاه دادههای Supabase و Prisma. این اطلاعات مکمل فصل سوم بوده و پایهای محکم برای مدیریت دادهها در اپلیکیشن فراهم میکند. برای اطلاعات بیشتر، به فصلهای ۶ و ۱۱ مراجعه کنید یا مخزن فرضی GitHub (https://github.com/khosronz/cruise-rental-app) را بررسی کنید.