今天,我們要來為系統新增兩個新功能:會員積分系統和課程預約功能,繼續擴充Gym Pro的系統功能完整性。
會員積分系統可以鼓勵會員更頻繁地使用健身房設施和參加課程。需要設計一個積分獲取和使用的機制。
首先,我們需要在會員模型中增加積分欄位。在 src/types/Member.ts
中:
export interface Member {
id: number;
name: string;
email: string;
joinDate: string;
membershipType: string;
points: number; // 新增積分欄位
}
我們還需要一個模型來記錄積分的獲取和使用歷史。在 src/types/PointHistory.ts
中:
export interface PointHistory {
id: number;
memberId: number;
amount: number;
type: 'earn' | 'use';
description: string;
date: string;
}
在 src/services/pointService.ts
中:
import axios from 'axios';
import { PointHistory } from '../types/PointHistory';
export const earnPoints = async (memberId: number, amount: number, description: string): Promise<PointHistory> => {
const response = await axios.post('/api/points/earn', { memberId, amount, description });
return response.data;
};
export const usePoints = async (memberId: number, amount: number, description: string): Promise<PointHistory> => {
const response = await axios.post('/api/points/use', { memberId, amount, description });
return response.data;
};
export const getPointHistory = async (memberId: number): Promise<PointHistory[]> => {
const response = await axios.get(`/api/points/history/${memberId}`);
return response.data;
};
在會員詳情頁面中增加積分信息和積分歷史。在 src/pages/MemberDetail.tsx
中:
import React from 'react';
import { useParams } from 'react-router-dom';
import { useQuery } from '@tanstack/react-query';
import { getMember } from '../services/memberService';
import { getPointHistory } from '../services/pointService';
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"
const MemberDetail: React.FC = () => {
const { id } = useParams<{ id: string }>();
const { data: member } = useQuery(['member', id], () => getMember(id!));
const { data: pointHistory } = useQuery(['pointHistory', id], () => getPointHistory(Number(id)));
if (!member) return <div>載入中...</div>;
return (
<div className="p-4">
<h1 className="text-2xl font-bold mb-4">{member.name} 的會員資料</h1>
<Card className="mb-4">
<CardHeader>
<CardTitle>會員資訊</CardTitle>
</CardHeader>
<CardContent>
<p>Email: {member.email}</p>
<p>加入日期: {member.joinDate}</p>
<p>會員類型: {member.membershipType}</p>
<p>積分: {member.points}</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>積分歷史</CardTitle>
</CardHeader>
<CardContent>
<ul>
{pointHistory?.map((history) => (
<li key={history.id} className="mb-2">
{history.date}: {history.type === 'earn' ? '獲得' : '使用'} {history.amount} 積分 - {history.description}
</li>
))}
</ul>
</CardContent>
</Card>
</div>
);
};
export default MemberDetail;
課程預約功能可以讓會員輕鬆地預約他們想參加的課程,同時也方便健身房管理課程容量。
在 src/types/Reservation.ts
中:
export interface Reservation {
id: number;
memberId: number;
classId: number;
date: string;
status: 'confirmed' | 'cancelled' | 'attended';
}
在 src/services/reservationService.ts
中:
import axios from 'axios';
import { Reservation } from '../types/Reservation';
export const makeReservation = async (memberId: number, classId: number, date: string): Promise<Reservation> => {
const response = await axios.post('/api/reservations', { memberId, classId, date });
return response.data;
};
export const cancelReservation = async (reservationId: number): Promise<void> => {
await axios.delete(`/api/reservations/${reservationId}`);
};
export const getMemberReservations = async (memberId: number): Promise<Reservation[]> => {
const response = await axios.get(`/api/reservations/member/${memberId}`);
return response.data;
};
export const getClassReservations = async (classId: number): Promise<Reservation[]> => {
const response = await axios.get(`/api/reservations/class/${classId}`);
return response.data;
};
在課程詳情頁面中增加預約功能。在 src/pages/ClassDetail.tsx
中:
import React from 'react';
import { useParams } from 'react-router-dom';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { getClass } from '../services/classService';
import { makeReservation, getClassReservations } from '../services/reservationService';
import { useAuth } from '../contexts/AuthContext';
import { Button } from "@/components/ui/button"
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"
const ClassDetail: React.FC = () => {
const { id } = useParams<{ id: string }>();
const { user } = useAuth();
const queryClient = useQueryClient();
const { data: classData } = useQuery(['class', id], () => getClass(id!));
const { data: reservations } = useQuery(['classReservations', id], () => getClassReservations(Number(id)));
const reserveMutation = useMutation(makeReservation, {
onSuccess: () => {
queryClient.invalidateQueries(['classReservations', id]);
},
});
if (!classData) return <div>載入中...</div>;
const handleReserve = () => {
if (user) {
reserveMutation.mutate({ memberId: user.id, classId: Number(id), date: classData.date });
}
};
return (
<div className="p-4">
<h1 className="text-2xl font-bold mb-4">{classData.name}</h1>
<Card className="mb-4">
<CardHeader>
<CardTitle>課程資訊</CardTitle>
</CardHeader>
<CardContent>
<p>教練: {classData.instructor}</p>
<p>日期: {classData.date}</p>
<p>時間: {classData.time}</p>
<p>時長: {classData.duration} 分鐘</p>
<p>已預約人數: {reservations?.length || 0}</p>
<Button onClick={handleReserve} disabled={reserveMutation.isLoading}>
預約課程
</Button>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>預約名單</CardTitle>
</CardHeader>
<CardContent>
<ul>
{reservations?.map((reservation) => (
<li key={reservation.id}>{reservation.memberId}</li>
))}
</ul>
</CardContent>
</Card>
</div>
);
};
export default ClassDetail;
今天我們為 Gym Pro 系統增加了兩個新功能: