ux: 花名册列表两行记录样式美化

- 姓名列:姓名加粗深色 + 证件号码淡灰小字,紧凑行间距
- 社保缴费列:个人/企业改为右对齐标签式,字号区分主次
- 操作列:两行按钮间增加分隔线,按钮间距收紧
- 全行 padding 从 py-3 调整为 py-2.5,行高更紧凑
- 行间分隔线变淡(gray-100 → gray-50),hover 效果加 group

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
selfrelease
2026-08-15 15:14:13 +08:00
parent 34b82bdbe7
commit 81e1cd0f72
+34 -32
View File
@@ -674,23 +674,25 @@ export default function Roster() {
{employees.map((e: any) => (
<tr
key={e.id}
className="border-b border-gray-100 last:border-0 cursor-pointer transition-colors hover:bg-primary/[0.03]"
className="border-b border-gray-50 last:border-0 cursor-pointer transition-colors hover:bg-primary/[0.03] group"
onClick={() => setSelectedId(e.id)}
>
<td className="px-4 py-3" onClick={(ev) => ev.stopPropagation()}>
<td className="px-4 py-2.5" onClick={(ev) => ev.stopPropagation()}>
<input type="checkbox" checked={selectedIds.has(e.id)} onChange={() => toggleSelect(e.id)} />
</td>
<td className="px-4 py-3 font-medium">
<div>{e.name}</div>
<div className="text-gray-400 text-xs font-mono cursor-pointer hover:text-primary transition-colors" title="点击复制完整证件号码" onClick={(ev) => {
ev.stopPropagation()
if (e.idCardNumber) {
copyToClipboard(e.idCardNumber, '已复制证件号码')
}
}}>{e.idCardMasked || '—'}</div>
<td className="px-4 py-2.5">
<div className="flex flex-col gap-0.5">
<span className="font-medium text-gray-900 text-sm leading-tight">{e.name}</span>
<span className="text-gray-400 text-[11px] font-mono cursor-pointer hover:text-primary transition-colors leading-tight" title="点击复制完整证件号码" onClick={(ev) => {
ev.stopPropagation()
if (e.idCardNumber) {
copyToClipboard(e.idCardNumber, '已复制证件号码')
}
}}>{e.idCardMasked || '—'}</span>
</div>
</td>
{colVisible('department') && <td className="px-4 py-3 text-gray-500">{e.department}</td>}
{colVisible('status') && <td className="px-4 py-3">
{colVisible('department') && <td className="px-4 py-2.5 text-gray-500">{e.department}</td>}
{colVisible('status') && <td className="px-4 py-2.5">
<span className={`px-2 py-0.5 rounded text-xs ${
e.status === 'ACTIVE' ? 'bg-green-50 text-safe'
: e.status === 'PRE_HIRE' ? 'bg-blue-50 text-blue-600'
@@ -708,8 +710,8 @@ export default function Roster() {
</span>
)}
</td>}
{colVisible('hireDate') && <td className="px-4 py-3 text-gray-500">{e.hireDate?.toString().slice(0, 10)}</td>}
<td className="hidden px-4 py-3 text-gray-500">
{colVisible('hireDate') && <td className="px-4 py-2.5 text-gray-500">{e.hireDate?.toString().slice(0, 10)}</td>}
<td className="hidden px-4 py-2.5 text-gray-500">
{e.hasTermination && e.latestTerminationDate && e.latestTerminationStatus !== 'CANCELLED' && e.latestTerminationStatus !== 'COMPLETED' ? (
<span className={e.status === 'RESIGNED' ? 'text-gray-500' : 'text-amber-600'}>
{e.latestTerminationDate.toString().slice(0, 10)}
@@ -719,10 +721,10 @@ export default function Roster() {
<span className="text-gray-300"></span>
)}
</td>
{colVisible('position') && <td className="px-4 py-3 text-gray-500">{e.position || '—'}</td>}
{colVisible('phone') && <td className="px-4 py-3 text-gray-500">{e.phone || '—'}</td>}
{colVisible('gender') && <td className="px-4 py-3 text-center text-gray-500">{e.gender || '—'}</td>}
{colVisible('contractType') && <td className="px-4 py-3">
{colVisible('position') && <td className="px-4 py-2.5 text-gray-500">{e.position || '—'}</td>}
{colVisible('phone') && <td className="px-4 py-2.5 text-gray-500">{e.phone || '—'}</td>}
{colVisible('gender') && <td className="px-4 py-2.5 text-center text-gray-500">{e.gender || '—'}</td>}
{colVisible('contractType') && <td className="px-4 py-2.5">
{(() => {
const typeConfig: Record<string, { label: string; style: string }> = {
FIXED: { label: '劳动合同-固定期', style: 'bg-blue-50 text-blue-700 border border-blue-200' },
@@ -739,7 +741,7 @@ export default function Roster() {
return <span className={`px-2 py-0.5 rounded text-xs ${cfg.style}`}>{cfg.label}</span>
})()}
</td>}
{colVisible('contractStatus') && <td className="px-4 py-3">
{colVisible('contractStatus') && <td className="px-4 py-2.5">
{(() => {
const tagStyles: Record<string, string> = {
expired: 'bg-red-50 text-danger',
@@ -764,7 +766,7 @@ export default function Roster() {
return <span className={`px-2 py-0.5 rounded text-xs ${style}`}>{text}</span>
})()}
</td>}
{colVisible('contractExpiry') && <td className="px-4 py-3 text-gray-500">
{colVisible('contractExpiry') && <td className="px-4 py-2.5 text-gray-500">
{(() => {
const endDate = e.latestContract?.endDate
if (!endDate) {
@@ -784,7 +786,7 @@ export default function Roster() {
return <span className="text-xs">{dateStr}</span>
})()}
</td>}
{colVisible('socialStatus') && <td className="px-4 py-3">
{colVisible('socialStatus') && <td className="px-4 py-2.5">
{(() => {
const status = e.socialInsuranceStatus
if (!status) return <span className="text-gray-300 text-xs"></span>
@@ -798,12 +800,12 @@ export default function Roster() {
return <span className={`px-2 py-0.5 rounded text-xs ${c.style}`}>{c.label}</span>
})()}
</td>}
{colVisible('socialInsBase') && <td className="px-4 py-3 text-right text-xs">
{colVisible('socialInsBase') && <td className="px-4 py-2.5 text-right text-xs">
{e.socialInsBase ? e.socialInsBase.toLocaleString() : <span className="text-gray-300"></span>}
</td>}
{colVisible('socialInsAmount') && <td className="px-4 py-3 text-right text-xs">
{colVisible('socialInsAmount') && <td className="px-4 py-2.5 text-right">
{(() => {
if (!e.socialInsCalc && !e.housingFundCalc) return <span className="text-gray-300"></span>
if (!e.socialInsCalc && !e.housingFundCalc) return <span className="text-gray-300 text-xs"></span>
const socialEmp = e.socialInsCalc?.socialEmp || 0
const socialOrg = e.socialInsCalc?.socialOrg || 0
const housingEmp = e.housingFundCalc?.housingEmp || 0
@@ -811,14 +813,14 @@ export default function Roster() {
const totalEmp = socialEmp + housingEmp
const totalOrg = socialOrg + housingOrg
return (
<div className="flex flex-col">
<span>: {totalEmp.toFixed(2)}</span>
<span className="text-gray-400">: {totalOrg.toFixed(2)}</span>
<div className="flex flex-col gap-0.5 items-end">
<span className="text-xs text-gray-700 leading-tight"> {totalEmp.toFixed(2)}</span>
<span className="text-[11px] text-gray-400 leading-tight"> {totalOrg.toFixed(2)}</span>
</div>
)
})()}
</td>}
{colVisible('records') && <td className="px-4 py-3 text-center">
{colVisible('records') && <td className="px-4 py-2.5 text-center">
<div className="flex items-center justify-center gap-1 flex-wrap">
{(() => {
const items = [
@@ -839,10 +841,10 @@ export default function Roster() {
})()}
</div>
</td>}
<td className="px-4 py-3 text-center">
<td className="px-3 py-2 text-center">
{e.status === 'ACTIVE' && (!e.hasTermination || e.latestTerminationStatus === 'CANCELLED' || e.latestTerminationStatus === 'COMPLETED') && (
<div className="flex flex-col items-center gap-1">
<div className="flex items-center justify-center gap-1">
<div className="flex flex-col items-center gap-1.5">
<div className="flex items-center justify-center gap-0.5">
<button
type="button"
title="调薪"
@@ -886,7 +888,7 @@ export default function Roster() {
<Building2 className="h-4 w-4" />
</button>
</div>
<div className="flex items-center justify-center gap-1">
<div className="flex items-center justify-center gap-0.5 border-t border-gray-100 pt-1.5">
<button
type="button"
title="开具证明"