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) => ( {employees.map((e: any) => (
<tr <tr
key={e.id} 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)} 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)} /> <input type="checkbox" checked={selectedIds.has(e.id)} onChange={() => toggleSelect(e.id)} />
</td> </td>
<td className="px-4 py-3 font-medium"> <td className="px-4 py-2.5">
<div>{e.name}</div> <div className="flex flex-col gap-0.5">
<div className="text-gray-400 text-xs font-mono cursor-pointer hover:text-primary transition-colors" title="点击复制完整证件号码" onClick={(ev) => { <span className="font-medium text-gray-900 text-sm leading-tight">{e.name}</span>
ev.stopPropagation() <span className="text-gray-400 text-[11px] font-mono cursor-pointer hover:text-primary transition-colors leading-tight" title="点击复制完整证件号码" onClick={(ev) => {
if (e.idCardNumber) { ev.stopPropagation()
copyToClipboard(e.idCardNumber, '已复制证件号码') if (e.idCardNumber) {
} copyToClipboard(e.idCardNumber, '已复制证件号码')
}}>{e.idCardMasked || '—'}</div> }
}}>{e.idCardMasked || '—'}</span>
</div>
</td> </td>
{colVisible('department') && <td className="px-4 py-3 text-gray-500">{e.department}</td>} {colVisible('department') && <td className="px-4 py-2.5 text-gray-500">{e.department}</td>}
{colVisible('status') && <td className="px-4 py-3"> {colVisible('status') && <td className="px-4 py-2.5">
<span className={`px-2 py-0.5 rounded text-xs ${ <span className={`px-2 py-0.5 rounded text-xs ${
e.status === 'ACTIVE' ? 'bg-green-50 text-safe' e.status === 'ACTIVE' ? 'bg-green-50 text-safe'
: e.status === 'PRE_HIRE' ? 'bg-blue-50 text-blue-600' : e.status === 'PRE_HIRE' ? 'bg-blue-50 text-blue-600'
@@ -708,8 +710,8 @@ export default function Roster() {
</span> </span>
)} )}
</td>} </td>}
{colVisible('hireDate') && <td className="px-4 py-3 text-gray-500">{e.hireDate?.toString().slice(0, 10)}</td>} {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-3 text-gray-500"> <td className="hidden px-4 py-2.5 text-gray-500">
{e.hasTermination && e.latestTerminationDate && e.latestTerminationStatus !== 'CANCELLED' && e.latestTerminationStatus !== 'COMPLETED' ? ( {e.hasTermination && e.latestTerminationDate && e.latestTerminationStatus !== 'CANCELLED' && e.latestTerminationStatus !== 'COMPLETED' ? (
<span className={e.status === 'RESIGNED' ? 'text-gray-500' : 'text-amber-600'}> <span className={e.status === 'RESIGNED' ? 'text-gray-500' : 'text-amber-600'}>
{e.latestTerminationDate.toString().slice(0, 10)} {e.latestTerminationDate.toString().slice(0, 10)}
@@ -719,10 +721,10 @@ export default function Roster() {
<span className="text-gray-300"></span> <span className="text-gray-300"></span>
)} )}
</td> </td>
{colVisible('position') && <td className="px-4 py-3 text-gray-500">{e.position || '—'}</td>} {colVisible('position') && <td className="px-4 py-2.5 text-gray-500">{e.position || '—'}</td>}
{colVisible('phone') && <td className="px-4 py-3 text-gray-500">{e.phone || '—'}</td>} {colVisible('phone') && <td className="px-4 py-2.5 text-gray-500">{e.phone || '—'}</td>}
{colVisible('gender') && <td className="px-4 py-3 text-center text-gray-500">{e.gender || '—'}</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-3"> {colVisible('contractType') && <td className="px-4 py-2.5">
{(() => { {(() => {
const typeConfig: Record<string, { label: string; style: string }> = { const typeConfig: Record<string, { label: string; style: string }> = {
FIXED: { label: '劳动合同-固定期', style: 'bg-blue-50 text-blue-700 border border-blue-200' }, 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> return <span className={`px-2 py-0.5 rounded text-xs ${cfg.style}`}>{cfg.label}</span>
})()} })()}
</td>} </td>}
{colVisible('contractStatus') && <td className="px-4 py-3"> {colVisible('contractStatus') && <td className="px-4 py-2.5">
{(() => { {(() => {
const tagStyles: Record<string, string> = { const tagStyles: Record<string, string> = {
expired: 'bg-red-50 text-danger', 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> return <span className={`px-2 py-0.5 rounded text-xs ${style}`}>{text}</span>
})()} })()}
</td>} </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 const endDate = e.latestContract?.endDate
if (!endDate) { if (!endDate) {
@@ -784,7 +786,7 @@ export default function Roster() {
return <span className="text-xs">{dateStr}</span> return <span className="text-xs">{dateStr}</span>
})()} })()}
</td>} </td>}
{colVisible('socialStatus') && <td className="px-4 py-3"> {colVisible('socialStatus') && <td className="px-4 py-2.5">
{(() => { {(() => {
const status = e.socialInsuranceStatus const status = e.socialInsuranceStatus
if (!status) return <span className="text-gray-300 text-xs"></span> 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> return <span className={`px-2 py-0.5 rounded text-xs ${c.style}`}>{c.label}</span>
})()} })()}
</td>} </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>} {e.socialInsBase ? e.socialInsBase.toLocaleString() : <span className="text-gray-300"></span>}
</td>} </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 socialEmp = e.socialInsCalc?.socialEmp || 0
const socialOrg = e.socialInsCalc?.socialOrg || 0 const socialOrg = e.socialInsCalc?.socialOrg || 0
const housingEmp = e.housingFundCalc?.housingEmp || 0 const housingEmp = e.housingFundCalc?.housingEmp || 0
@@ -811,14 +813,14 @@ export default function Roster() {
const totalEmp = socialEmp + housingEmp const totalEmp = socialEmp + housingEmp
const totalOrg = socialOrg + housingOrg const totalOrg = socialOrg + housingOrg
return ( return (
<div className="flex flex-col"> <div className="flex flex-col gap-0.5 items-end">
<span>: {totalEmp.toFixed(2)}</span> <span className="text-xs text-gray-700 leading-tight"> {totalEmp.toFixed(2)}</span>
<span className="text-gray-400">: {totalOrg.toFixed(2)}</span> <span className="text-[11px] text-gray-400 leading-tight"> {totalOrg.toFixed(2)}</span>
</div> </div>
) )
})()} })()}
</td>} </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"> <div className="flex items-center justify-center gap-1 flex-wrap">
{(() => { {(() => {
const items = [ const items = [
@@ -839,10 +841,10 @@ export default function Roster() {
})()} })()}
</div> </div>
</td>} </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') && ( {e.status === 'ACTIVE' && (!e.hasTermination || e.latestTerminationStatus === 'CANCELLED' || e.latestTerminationStatus === 'COMPLETED') && (
<div className="flex flex-col items-center gap-1"> <div className="flex flex-col items-center gap-1.5">
<div className="flex items-center justify-center gap-1"> <div className="flex items-center justify-center gap-0.5">
<button <button
type="button" type="button"
title="调薪" title="调薪"
@@ -886,7 +888,7 @@ export default function Roster() {
<Building2 className="h-4 w-4" /> <Building2 className="h-4 w-4" />
</button> </button>
</div> </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 <button
type="button" type="button"
title="开具证明" title="开具证明"