ui: 优化特殊状态台账页面UI
- 统计卡片:添加图标、圆角xl、左图标右数据布局 - 列表卡片:类型图标背景色、hover显示操作按钮、分隔线分区 - 日期字段:改用Field组件双列网格布局,关键日期accent高亮 - 提醒区域:圆点指示器+过期动画pulse+今天到期文案 - 员工信息:独立分区带底部分隔线
This commit is contained in:
@@ -84,10 +84,17 @@ const STATUS_COLORS: Record<string, string> = {
|
||||
RESOLVED: 'bg-gray-100 text-gray-500',
|
||||
}
|
||||
|
||||
const ALERT_STYLES: Record<string, { border: string; badge: string; text: string }> = {
|
||||
red: { border: 'border-l-4 border-l-red-500', badge: 'bg-red-100 text-red-700', text: 'text-red-600' },
|
||||
yellow: { border: 'border-l-4 border-l-amber-500', badge: 'bg-amber-100 text-amber-700', text: 'text-amber-600' },
|
||||
green: { border: 'border-l-4 border-l-emerald-500', badge: 'bg-emerald-100 text-emerald-700', text: 'text-emerald-600' },
|
||||
const ALERT_STYLES: Record<string, { border: string; badge: string; text: string; bg: string; dot: string }> = {
|
||||
red: { border: 'border-l-4 border-l-red-500', badge: 'bg-red-100 text-red-700', text: 'text-red-600', bg: 'bg-red-50', dot: 'bg-red-500' },
|
||||
yellow: { border: 'border-l-4 border-l-amber-500', badge: 'bg-amber-100 text-amber-700', text: 'text-amber-600', bg: 'bg-amber-50', dot: 'bg-amber-500' },
|
||||
green: { border: 'border-l-4 border-l-emerald-500', badge: 'bg-emerald-100 text-emerald-700', text: 'text-emerald-600', bg: 'bg-emerald-50', dot: 'bg-emerald-500' },
|
||||
}
|
||||
|
||||
const TYPE_ICON_BG: Record<string, string> = {
|
||||
PREGNANCY: 'bg-pink-100 text-pink-600',
|
||||
WORK_INJURY: 'bg-red-100 text-red-600',
|
||||
MEDICAL_PERIOD: 'bg-orange-100 text-orange-600',
|
||||
OTHER: 'bg-gray-100 text-gray-600',
|
||||
}
|
||||
|
||||
function formatDate(d: string | null): string {
|
||||
@@ -260,32 +267,59 @@ export default function SpecialStatus() {
|
||||
|
||||
{/* 统计卡片 */}
|
||||
{stats && (
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-3">
|
||||
<div className="bg-white border border-gray-200 rounded-lg p-3">
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-3">
|
||||
<div className="bg-white border border-gray-200 rounded-xl p-3.5 flex items-center gap-3">
|
||||
<div className="w-9 h-9 rounded-lg bg-gray-100 flex items-center justify-center shrink-0">
|
||||
<AlertTriangle className="w-4 h-4 text-gray-500" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="text-xs text-gray-500">总记录</div>
|
||||
<div className="text-xl font-bold text-gray-900">{stats.total}</div>
|
||||
<div className="text-xl font-bold text-gray-900 leading-tight">{stats.total}</div>
|
||||
</div>
|
||||
<div className="bg-white border border-gray-200 rounded-lg p-3">
|
||||
</div>
|
||||
<div className="bg-white border border-gray-200 rounded-xl p-3.5 flex items-center gap-3">
|
||||
<div className="w-9 h-9 rounded-lg bg-emerald-50 flex items-center justify-center shrink-0">
|
||||
<Activity className="w-4 h-4 text-emerald-600" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="text-xs text-gray-500">进行中</div>
|
||||
<div className="text-xl font-bold text-emerald-600">{stats.active}</div>
|
||||
<div className="text-xl font-bold text-emerald-600 leading-tight">{stats.active}</div>
|
||||
</div>
|
||||
<div className="bg-white border border-gray-200 rounded-lg p-3">
|
||||
</div>
|
||||
<div className="bg-white border border-gray-200 rounded-xl p-3.5 flex items-center gap-3">
|
||||
<div className="w-9 h-9 rounded-lg bg-amber-50 flex items-center justify-center shrink-0">
|
||||
<Clock className="w-4 h-4 text-amber-600" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="text-xs text-gray-500">待处理</div>
|
||||
<div className="text-xl font-bold text-amber-600">{stats.pending}</div>
|
||||
<div className="text-xl font-bold text-amber-600 leading-tight">{stats.pending}</div>
|
||||
</div>
|
||||
<div className="bg-white border border-red-200 rounded-lg p-3">
|
||||
</div>
|
||||
<div className="bg-white border border-red-200 rounded-xl p-3.5 flex items-center gap-3">
|
||||
<div className="w-9 h-9 rounded-lg bg-red-50 flex items-center justify-center shrink-0">
|
||||
<AlertTriangle className="w-4 h-4 text-red-600" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="text-xs text-gray-500">紧急提醒</div>
|
||||
<div className="text-xl font-bold text-red-600">{stats.redAlert}</div>
|
||||
<div className="text-xl font-bold text-red-600 leading-tight">{stats.redAlert}</div>
|
||||
</div>
|
||||
<div className="bg-white border border-amber-200 rounded-lg p-3">
|
||||
</div>
|
||||
<div className="bg-white border border-amber-200 rounded-xl p-3.5 flex items-center gap-3">
|
||||
<div className="w-9 h-9 rounded-lg bg-amber-50 flex items-center justify-center shrink-0">
|
||||
<Clock className="w-4 h-4 text-amber-500" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="text-xs text-gray-500">即将到期</div>
|
||||
<div className="text-xl font-bold text-amber-600">{stats.yellowAlert}</div>
|
||||
<div className="text-xl font-bold text-amber-600 leading-tight">{stats.yellowAlert}</div>
|
||||
</div>
|
||||
<div className="bg-white border border-gray-200 rounded-lg p-3">
|
||||
<div className="text-xs text-gray-500 mb-1">类型分布</div>
|
||||
<div className="text-xs text-gray-700">
|
||||
</div>
|
||||
<div className="bg-white border border-gray-200 rounded-xl p-3.5">
|
||||
<div className="text-xs text-gray-500 mb-1.5">类型分布</div>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{stats.byType.map((t) => (
|
||||
<span key={t.type} className="mr-2">{t.label}: <b>{t.count}</b></span>
|
||||
<span key={t.type} className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-xs bg-gray-50 text-gray-600">
|
||||
{t.label}<b className="text-gray-900">{t.count}</b>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -338,85 +372,94 @@ export default function SpecialStatus() {
|
||||
const Icon = TYPE_ICONS[item.type] || AlertTriangle
|
||||
const alert = ALERT_STYLES[item.alertLevel] || ALERT_STYLES.green
|
||||
const reminderDays = daysUntil(item.reminderDate)
|
||||
const isOverdue = reminderDays !== null && reminderDays < 0
|
||||
return (
|
||||
<div key={item.id} className={`bg-white border ${alert.border} border-gray-200 rounded-lg p-4`}>
|
||||
{/* 头部 */}
|
||||
<div key={item.id} className={`bg-white border ${alert.border} border-gray-200 rounded-xl p-4 hover:shadow-md transition-shadow group`}>
|
||||
{/* 头部:类型图标 + 状态 + 操作 */}
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`px-2 py-0.5 rounded text-xs font-medium border ${TYPE_COLORS[item.type] || TYPE_COLORS.OTHER}`}>
|
||||
<Icon className="w-3 h-3 inline mr-1" />
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className={`w-8 h-8 rounded-lg flex items-center justify-center shrink-0 ${TYPE_ICON_BG[item.type] || TYPE_ICON_BG.OTHER}`}>
|
||||
<Icon className="w-4 h-4" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium border w-fit ${TYPE_COLORS[item.type] || TYPE_COLORS.OTHER}`}>
|
||||
{TYPE_LABELS[item.type] || item.type}
|
||||
</span>
|
||||
<span className={`px-2 py-0.5 rounded text-xs font-medium ${STATUS_COLORS[item.status] || STATUS_COLORS.RESOLVED}`}>
|
||||
<span className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium w-fit ${STATUS_COLORS[item.status] || STATUS_COLORS.RESOLVED}`}>
|
||||
{STATUS_LABELS[item.status] || item.status}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
<Link to={`/roster?employeeId=${item.employee.id}`} className="p-1 rounded text-gray-400 hover:text-primary hover:bg-gray-100" title="查看员工详情">
|
||||
</div>
|
||||
<div className="flex gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<Link to={`/roster?employeeId=${item.employee.id}`} className="p-1.5 rounded-md text-gray-400 hover:text-primary hover:bg-gray-100" title="查看员工详情">
|
||||
<Eye className="w-3.5 h-3.5" />
|
||||
</Link>
|
||||
<button onClick={() => handleOpenEdit(item)} className="p-1 rounded text-gray-400 hover:text-primary hover:bg-gray-100" title="编辑">
|
||||
<button onClick={() => handleOpenEdit(item)} className="p-1.5 rounded-md text-gray-400 hover:text-primary hover:bg-gray-100" title="编辑">
|
||||
<Edit2 className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<button onClick={() => setDeleteTarget(item)} className="p-1 rounded text-gray-400 hover:text-red-500 hover:bg-gray-100" title="删除">
|
||||
<button onClick={() => setDeleteTarget(item)} className="p-1.5 rounded-md text-gray-400 hover:text-red-500 hover:bg-gray-100" title="删除">
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 员工信息 */}
|
||||
<div className="mb-3">
|
||||
<div className="mb-3 pb-3 border-b border-gray-100">
|
||||
<div className="flex items-center gap-2">
|
||||
<Link to={`/roster?employeeId=${item.employee.id}`} className="font-medium text-gray-900 hover:text-primary hover:underline">{item.employee.name}</Link>
|
||||
{item.employee.idCardNumber && <span className="text-xs text-gray-400 font-mono ml-1">{item.employee.idCardNumber}</span>}
|
||||
<span className="text-sm text-gray-500 ml-2">{item.employee.department}</span>
|
||||
{item.employee.idCardNumber && <span className="text-xs text-gray-400 font-mono">{item.employee.idCardNumber}</span>}
|
||||
{item.employee.status === 'RESIGNED' && (
|
||||
<span className="ml-2 text-xs text-gray-400">已离职</span>
|
||||
<span className="text-xs text-gray-400 px-1.5 py-0.5 rounded bg-gray-100">已离职</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 mt-0.5">{item.employee.department}</div>
|
||||
</div>
|
||||
|
||||
{/* 关键日期 */}
|
||||
<div className="space-y-1.5 text-sm">
|
||||
<div className="space-y-2">
|
||||
{item.type === 'PREGNANCY' && (
|
||||
<>
|
||||
{item.expectedDueDate && <Row label="预产期" value={formatDate(item.expectedDueDate)} />}
|
||||
{item.maternityLeaveStart && <Row label="产假开始" value={formatDate(item.maternityLeaveStart)} />}
|
||||
{item.maternityLeaveEnd && <Row label="产假结束" value={formatDate(item.maternityLeaveEnd)} />}
|
||||
{item.nursingEndDate && <Row label="哺乳期截止" value={formatDate(item.nursingEndDate)} />}
|
||||
</>
|
||||
<div className="grid grid-cols-2 gap-x-3 gap-y-2">
|
||||
{item.expectedDueDate && <Field label="预产期" value={formatDate(item.expectedDueDate)} accent="pink" />}
|
||||
{item.maternityLeaveStart && <Field label="产假开始" value={formatDate(item.maternityLeaveStart)} />}
|
||||
{item.maternityLeaveEnd && <Field label="产假结束" value={formatDate(item.maternityLeaveEnd)} />}
|
||||
{item.nursingEndDate && <Field label="哺乳期截止" value={formatDate(item.nursingEndDate)} />}
|
||||
</div>
|
||||
)}
|
||||
{item.type === 'WORK_INJURY' && (
|
||||
<>
|
||||
{item.injuryDate && <Row label="受伤日期" value={formatDate(item.injuryDate)} />}
|
||||
{item.injuryDescription && <Row label="伤情描述" value={item.injuryDescription} />}
|
||||
<div className="space-y-2">
|
||||
{item.injuryDate && <Field label="受伤日期" value={formatDate(item.injuryDate)} accent="red" />}
|
||||
{item.injuryDescription && <Field label="伤情描述" value={item.injuryDescription} fullWidth />}
|
||||
{item.certificationDate ? (
|
||||
<Row label="认定日期" value={formatDate(item.certificationDate)} />
|
||||
<Field label="认定日期" value={formatDate(item.certificationDate)} />
|
||||
) : (
|
||||
<Row label="认定日期" value="待认定" valueClass="text-amber-600" />
|
||||
<Field label="认定日期" value="待认定" valueClass="text-amber-600" />
|
||||
)}
|
||||
{item.assessmentDate && <Row label="鉴定日期" value={formatDate(item.assessmentDate)} />}
|
||||
{item.disabilityLevel && <Row label="伤残等级" value={`${item.disabilityLevel}级`} />}
|
||||
</>
|
||||
<div className="grid grid-cols-2 gap-x-3 gap-y-2">
|
||||
{item.assessmentDate && <Field label="鉴定日期" value={formatDate(item.assessmentDate)} />}
|
||||
{item.disabilityLevel && <Field label="伤残等级" value={`${item.disabilityLevel}级`} />}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{item.type === 'MEDICAL_PERIOD' && (
|
||||
<>
|
||||
{item.startDate && <Row label="开始日期" value={formatDate(item.startDate)} />}
|
||||
{item.medicalMonths && <Row label="医疗期" value={`${item.medicalMonths}个月`} />}
|
||||
{item.medicalPeriodEnd && <Row label="截止日期" value={formatDate(item.medicalPeriodEnd)} />}
|
||||
</>
|
||||
<div className="grid grid-cols-2 gap-x-3 gap-y-2">
|
||||
{item.startDate && <Field label="开始日期" value={formatDate(item.startDate)} accent="orange" />}
|
||||
{item.medicalMonths && <Field label="医疗期" value={`${item.medicalMonths}个月`} />}
|
||||
{item.medicalPeriodEnd && <Field label="截止日期" value={formatDate(item.medicalPeriodEnd)} />}
|
||||
</div>
|
||||
)}
|
||||
{item.type === 'OTHER' && (
|
||||
<>
|
||||
{item.startDate && <Row label="开始日期" value={formatDate(item.startDate)} />}
|
||||
{item.endDate && <Row label="预计结束" value={formatDate(item.endDate)} />}
|
||||
</>
|
||||
<div className="grid grid-cols-2 gap-x-3 gap-y-2">
|
||||
{item.startDate && <Field label="开始日期" value={formatDate(item.startDate)} />}
|
||||
{item.endDate && <Field label="预计结束" value={formatDate(item.endDate)} />}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 提醒 */}
|
||||
{item.status === 'ACTIVE' && reminderDays !== null && (
|
||||
<div className={`mt-3 pt-3 border-t border-gray-100 flex items-center gap-1.5 text-xs ${alert.text}`}>
|
||||
<Clock className="w-3.5 h-3.5" />
|
||||
{reminderDays < 0 ? `已过期 ${Math.abs(reminderDays)} 天` : `距提醒日还有 ${reminderDays} 天`}
|
||||
<div className={`mt-3 pt-3 border-t border-gray-100 flex items-center gap-2 text-xs ${alert.text}`}>
|
||||
<div className={`w-2 h-2 rounded-full ${alert.dot} ${isOverdue ? 'animate-pulse' : ''}`} />
|
||||
{isOverdue ? `已过期 ${Math.abs(reminderDays)} 天` : reminderDays === 0 ? '今天到期' : `距提醒日还有 ${reminderDays} 天`}
|
||||
</div>
|
||||
)}
|
||||
{item.description && (
|
||||
@@ -634,12 +677,13 @@ export default function SpecialStatus() {
|
||||
)
|
||||
}
|
||||
|
||||
/** 信息行组件 */
|
||||
function Row({ label, value, valueClass }: { label: string; value: string; valueClass?: string }) {
|
||||
/** 信息字段组件 */
|
||||
function Field({ label, value, valueClass, accent, fullWidth }: { label: string; value: string; valueClass?: string; accent?: 'pink' | 'red' | 'orange'; fullWidth?: boolean }) {
|
||||
const accentColor = accent === 'pink' ? 'text-pink-600' : accent === 'red' ? 'text-red-600' : accent === 'orange' ? 'text-orange-600' : 'text-gray-400'
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-500 text-xs">{label}</span>
|
||||
<span className={`text-gray-900 text-xs font-medium ${valueClass || ''}`}>{value}</span>
|
||||
<div className={fullWidth ? 'col-span-2' : ''}>
|
||||
<div className="text-[11px] text-gray-400 mb-0.5">{label}</div>
|
||||
<div className={`text-xs font-medium ${valueClass || 'text-gray-900'}`}>{value}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user