分离左侧快速操作面板

This commit is contained in:
xianyi
2025-12-16 10:12:28 +08:00
parent 5b8e74c072
commit bf864de805
4 changed files with 162 additions and 81 deletions

View File

@@ -0,0 +1,64 @@
import type { ExamClient } from '../../data/mockData';
import { EXAM_CLIENTS } from '../../data/mockData';
import { InfoCard } from '../ui';
interface MealRegistrationModalProps {
onClose: () => void;
totalExamCount: number;
mealCount: number;
notMealCount: number;
mealDoneIds: string[];
onMealDone: (id: string) => void;
}
export const MealRegistrationModal = ({
onClose,
totalExamCount,
mealCount,
notMealCount,
mealDoneIds,
onMealDone,
}: MealRegistrationModalProps) => {
return (
<div className='fixed inset-0 z-40 flex items-center justify-center bg-black/30'>
<div className='w-[560px] 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'></div>
<button className='text-xs text-gray-500' onClick={onClose}>
</button>
</div>
<div className='px-4 py-4 bg-gray-50/60'>
<div className='space-y-3'>
<div className='grid grid-cols-3 gap-3 text-xs'>
<InfoCard label='今日体检人数' value={totalExamCount} />
<InfoCard label='已用餐人数' value={mealCount} />
<InfoCard label='未用餐人数' value={notMealCount} />
</div>
<div className='text-xs text-gray-600 mt-2 mb-1'></div>
<div className='max-h-60 overflow-auto border rounded-2xl bg-white p-2 text-xs'>
{EXAM_CLIENTS.map((c: ExamClient) => {
const checked = mealDoneIds.includes(c.id);
return (
<label
key={c.id}
className='flex items-center justify-between px-3 py-1.5 rounded-2xl hover:bg-gray-50 cursor-pointer'
>
<span>
{c.name} <span className='text-gray-400 text-[11px]'>({c.id})</span>
</span>
<span className='flex items-center gap-2'>
<span className='text-gray-400'>{c.status}</span>
<input type='checkbox' checked={checked} onChange={() => onMealDone(c.id)} />
</span>
</label>
);
})}
</div>
</div>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,35 @@
interface NoteModalProps {
noteText: string;
onNoteChange: (v: string) => void;
onClose: () => void;
}
export const NoteModal = ({ noteText, onNoteChange, onClose }: NoteModalProps) => {
return (
<div className='fixed inset-0 z-40 flex items-center justify-center bg-black/30'>
<div className='w-[560px] 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'></div>
<button className='text-xs text-gray-500' onClick={onClose}>
</button>
</div>
<div className='px-4 py-4 bg-gray-50/60'>
<div className='space-y-3 text-xs text-gray-700'>
<div></div>
<textarea
className='w-full rounded-2xl border px-3 py-2 text-xs outline-none focus:ring-2 focus:ring-gray-200 min-h-[96px]'
placeholder='例如:客户有既往疾病史、沟通偏好、特殊关怀需求等,可在此记录。'
value={noteText}
onChange={(e) => onNoteChange(e.target.value)}
/>
<div className='text-right text-[11px] text-gray-500'>
</div>
</div>
</div>
</div>
</div>
);
};

View File

@@ -1,6 +1,8 @@
import type { ExamClient, QuickActionType } from '../../data/mockData'; import type { QuickActionType } from '../../data/mockData';
import { EXAM_CLIENTS } from '../../data/mockData'; import { MealRegistrationModal } from './MealRegistrationModal';
import { InfoCard } from '../ui'; import { NoteModal } from './NoteModal';
import { VipAuthModal } from './VipAuthModal';
interface QuickActionModalProps { interface QuickActionModalProps {
action: QuickActionType; action: QuickActionType;
noteText: string; noteText: string;
@@ -24,90 +26,32 @@ export const QuickActionModal = ({
mealDoneIds, mealDoneIds,
onMealDone, onMealDone,
}: QuickActionModalProps) => { }: QuickActionModalProps) => {
const titleMap: Record<Exclude<QuickActionType, 'none'>, string> = {
meal: '用餐登记',
vip: '太平 VIP 认证说明',
note: '备注窗',
};
if (action === 'none') { if (action === 'none') {
return null; return null;
} }
if (action === 'meal') {
return ( return (
<div className='fixed inset-0 z-40 flex items-center justify-center bg-black/30'> <MealRegistrationModal
<div className='w-[560px] max-w-[95vw] bg-white rounded-3xl shadow-xl overflow-hidden text-sm'> onClose={onClose}
<div className='px-4 py-3 border-b flex items-center justify-between'> totalExamCount={totalExamCount}
<div className='font-semibold'>{titleMap[action]}</div> mealCount={mealCount}
<button className='text-xs text-gray-500' onClick={onClose}> notMealCount={notMealCount}
mealDoneIds={mealDoneIds}
</button> onMealDone={onMealDone}
</div>
<div className='px-4 py-4 bg-gray-50/60'>
{action === 'meal' && (
<div className='space-y-3'>
<div className='grid grid-cols-3 gap-3 text-xs'>
<InfoCard label='今日体检人数' value={totalExamCount} />
<InfoCard label='已用餐人数' value={mealCount} />
<InfoCard label='未用餐人数' value={notMealCount} />
</div>
<div className='text-xs text-gray-600 mt-2 mb-1'></div>
<div className='max-h-60 overflow-auto border rounded-2xl bg-white p-2 text-xs'>
{EXAM_CLIENTS.map((c: ExamClient) => {
const checked = mealDoneIds.includes(c.id);
return (
<label
key={c.id}
className='flex items-center justify-between px-3 py-1.5 rounded-2xl hover:bg-gray-50 cursor-pointer'
>
<span>
{c.name} <span className='text-gray-400 text-[11px]'>({c.id})</span>
</span>
<span className='flex items-center gap-2'>
<span className='text-gray-400'>{c.status}</span>
<input type='checkbox' checked={checked} onChange={() => onMealDone(c.id)} />
</span>
</label>
);
})}
</div>
</div>
)}
{action === 'vip' && (
<div className='flex gap-4 items-center'>
<div className='flex-1 text-xs text-gray-700 space-y-2'>
<p> VIP </p>
<ul className='list-disc ml-5 space-y-1'>
<li> APP </li>
<li> VIP </li>
<li></li>
</ul>
</div>
<div className='w-40 h-40 rounded-3xl bg-white border flex items-center justify-center text-xs text-gray-500'>
<img src="https://datacenter-open.oss-cn-hangzhou.aliyuncs.com/his/taiping-vip.jpg" alt='太平 VIP 认证二维码' className='w-full h-full object-cover' />
</div>
</div>
)}
{action === 'note' && (
<div className='space-y-3 text-xs text-gray-700'>
<div></div>
<textarea
className='w-full rounded-2xl border px-3 py-2 text-xs outline-none focus:ring-2 focus:ring-gray-200 min-h-[96px]'
placeholder='例如:客户有既往疾病史、沟通偏好、特殊关怀需求等,可在此记录。'
value={noteText}
onChange={(e) => onNoteChange(e.target.value)}
/> />
<div className='text-right text-[11px] text-gray-500'>
</div>
</div>
)}
</div>
</div>
</div>
); );
}
if (action === 'note') {
return <NoteModal noteText={noteText} onNoteChange={onNoteChange} onClose={onClose} />;
}
if (action === 'vip') {
return <VipAuthModal onClose={onClose} />;
}
return null;
}; };

View File

@@ -0,0 +1,38 @@
interface VipAuthModalProps {
onClose: () => void;
}
export const VipAuthModal = ({ onClose }: VipAuthModalProps) => {
return (
<div className='fixed inset-0 z-40 flex items-center justify-center bg-black/30'>
<div className='w-[560px] 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'> VIP </div>
<button className='text-xs text-gray-500' onClick={onClose}>
</button>
</div>
<div className='px-4 py-4 bg-gray-50/60'>
<div className='flex gap-4 items-center'>
<div className='flex-1 text-xs text-gray-700 space-y-2'>
<p> VIP </p>
<ul className='list-disc ml-5 space-y-1'>
<li> APP </li>
<li> VIP </li>
<li></li>
</ul>
</div>
<div className='w-40 h-40 rounded-3xl bg-white border flex items-center justify-center text-xs text-gray-500'>
<img
src='https://datacenter-open.oss-cn-hangzhou.aliyuncs.com/his/taiping-vip.jpg'
alt='太平 VIP 认证二维码'
className='w-full h-full object-cover'
/>
</div>
</div>
</div>
</div>
</div>
);
};