init
This commit is contained in:
61
src/components/booking/BookingModal.tsx
Normal file
61
src/components/booking/BookingModal.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import { BOOKING_DOCTORS } from '../../data/mockData';
|
||||
import { Button, Input } from '../ui';
|
||||
|
||||
interface BookingModalProps {
|
||||
doctor: (typeof BOOKING_DOCTORS)[number];
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const BookingModal = ({ doctor, onClose }: BookingModalProps) => (
|
||||
<div className='fixed inset-0 z-40 flex items-center justify-center bg-black/30'>
|
||||
<div className='w-[520px] max-w-[95vw] bg-white rounded-3xl shadow-xl overflow-hidden text-sm'>
|
||||
<div className='px-4 py-3 border-b flex items-center justify-between'>
|
||||
<div className='font-semibold'>预约申请 · {doctor.name}</div>
|
||||
<button className='text-xs text-gray-500' onClick={onClose}>
|
||||
关闭
|
||||
</button>
|
||||
</div>
|
||||
<div className='px-4 py-4 bg-gray-50/60 space-y-3 text-xs text-gray-700'>
|
||||
<div className='grid grid-cols-2 gap-3'>
|
||||
<div>
|
||||
付费方式
|
||||
<select className='mt-1 w-full rounded-2xl border px-3 py-1.5 bg-white outline-none text-xs'>
|
||||
<option>自费</option>
|
||||
<option>单位结算</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
产品名称
|
||||
<Input placeholder='如:专家门诊咨询' className='mt-1' />
|
||||
</div>
|
||||
<div>
|
||||
是否定制
|
||||
<select className='mt-1 w-full rounded-2xl border px-3 py-1.5 bg-white outline-none text-xs'>
|
||||
<option>否</option>
|
||||
<option>是</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
期望预约时间
|
||||
<Input placeholder='例如:2025-11-20 上午' className='mt-1' />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
备注
|
||||
<textarea
|
||||
className='w-full mt-1 rounded-2xl border px-3 py-2 text-xs outline-none focus:ring-2 focus:ring-gray-200 min-h-[72px]'
|
||||
placeholder='可填写病情简要、既往史、特殊需求等信息'
|
||||
/>
|
||||
</div>
|
||||
<div className='flex items-center justify-between text-[11px] text-gray-500'>
|
||||
<span>
|
||||
医生:{doctor.name}({doctor.dept})
|
||||
</span>
|
||||
<Button>提交预约申请</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
104
src/components/booking/BookingSection.tsx
Normal file
104
src/components/booking/BookingSection.tsx
Normal file
@@ -0,0 +1,104 @@
|
||||
import { BOOKING_DOCTORS } from '../../data/mockData';
|
||||
import { Card, CardContent, CardHeader } from '../ui';
|
||||
import { BookingModal } from './BookingModal';
|
||||
import { cls } from '../../utils/cls';
|
||||
|
||||
interface BookingSectionProps {
|
||||
selectedDay: number;
|
||||
onSelectDay: (day: number) => void;
|
||||
bookingDoctor: (typeof BOOKING_DOCTORS)[number] | null;
|
||||
onSelectDoctor: (doctor: (typeof BOOKING_DOCTORS)[number]) => void;
|
||||
onCloseModal: () => void;
|
||||
}
|
||||
|
||||
export const BookingSection = ({
|
||||
selectedDay,
|
||||
onSelectDay,
|
||||
bookingDoctor,
|
||||
onSelectDoctor,
|
||||
onCloseModal,
|
||||
}: BookingSectionProps) => (
|
||||
<div className='grid grid-cols-12 gap-6'>
|
||||
<div className='col-span-4 space-y-4'>
|
||||
<Card>
|
||||
<CardHeader>预约日历</CardHeader>
|
||||
<CardContent>
|
||||
<div className='grid grid-cols-6 gap-2 text-sm'>
|
||||
{Array.from({ length: 30 }, (_, i) => i + 1).map((day) => (
|
||||
<button
|
||||
key={day}
|
||||
onClick={() => onSelectDay(day)}
|
||||
className={cls(
|
||||
'h-9 rounded-2xl border flex items-center justify-center',
|
||||
selectedDay === day ? 'bg-gray-900 text-white border-gray-900' : 'bg-white hover:bg-gray-50',
|
||||
)}
|
||||
>
|
||||
{day}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardContent>
|
||||
<div className='flex items-center justify-between text-sm'>
|
||||
<span>当日预约数</span>
|
||||
<span className='text-lg font-semibold'>{BOOKING_DOCTORS.length}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div className='col-span-8 space-y-4'>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<span>预约医生 · {selectedDay} 日</span>
|
||||
<div className='flex items-center gap-2 text-xs'>
|
||||
<span className='text-gray-500'>按科室筛选</span>
|
||||
<select className='border rounded-2xl px-3 py-1 bg-white outline-none text-xs'>
|
||||
<option>全部科室</option>
|
||||
<option>内科</option>
|
||||
<option>外科</option>
|
||||
</select>
|
||||
</div>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
|
||||
<div className='grid grid-cols-2 gap-4'>
|
||||
{BOOKING_DOCTORS.map((doctor) => {
|
||||
const ratio = doctor.total ? (doctor.total - doctor.remain) / doctor.total : 0;
|
||||
const percent = Math.round(Math.max(0, Math.min(1, ratio)) * 100);
|
||||
return (
|
||||
<button key={doctor.id} onClick={() => onSelectDoctor(doctor)} className='text-left w-full'>
|
||||
<Card className='bg-gray-50/40'>
|
||||
<CardContent>
|
||||
<div className='flex items-start justify-between mb-3'>
|
||||
<div>
|
||||
<div className='font-semibold mb-1'>{doctor.name}</div>
|
||||
<div className='text-xs text-gray-500'>{doctor.dept}</div>
|
||||
</div>
|
||||
<span className='px-3 py-1 rounded-2xl border text-xs text-gray-600 bg-white'>{doctor.period}</span>
|
||||
</div>
|
||||
<div className='text-xs text-gray-600 space-y-1 mb-2'>
|
||||
<div>当日号源:{doctor.total} 个</div>
|
||||
<div>
|
||||
剩余预约号 <span className='font-semibold text-gray-900'>{doctor.remain}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className='h-2 rounded-full bg-gray-200 overflow-hidden'>
|
||||
<div className='h-full bg-gray-900' style={{ width: `${percent}%` }} />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{bookingDoctor && <BookingModal doctor={bookingDoctor} onClose={onCloseModal} />}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user