feat: 绩效工资改为手动获取+按考核系数着色
后端: - 建立批次时绩效工资填0,不自动带出 - 获取绩效工资接口返回 details(grade/coefficient/performanceSalary) - 无考核记录时系数为1.0(绩效工资=基数全额) 前端: - 存储获取绩效工资返回的 details 到 perfDetails state - 绩效工资列按系数着色:A级(1.2)绿色/C级(0.8)橙色/D级(0.6)红色 - B级(1.0)和无考核正常色,0值灰色 - hover显示考核等级和系数
This commit is contained in:
@@ -489,6 +489,7 @@ function BatchDetail({ batchId, onBack }: { batchId: string; onBack: () => void
|
||||
const [taxDetailFor, setTaxDetailFor] = useState<{ employeeId: string; name: string } | null>(null)
|
||||
const [taxDetailData, setTaxDetailData] = useState<any>(null)
|
||||
const [taxDetailLoading, setTaxDetailLoading] = useState(false)
|
||||
const [perfDetails, setPerfDetails] = useState<Map<string, { grade: string | null; coefficient: number }>>(new Map())
|
||||
|
||||
const { data: batch, isLoading } = useQuery<any>({
|
||||
queryKey: ['batch-detail', batchId],
|
||||
@@ -599,6 +600,13 @@ function BatchDetail({ batchId, onBack }: { batchId: string; onBack: () => void
|
||||
onSuccess: (res: any) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['batch-detail'] })
|
||||
queryClient.invalidateQueries({ queryKey: ['batches'] })
|
||||
if (res.data?.details) {
|
||||
const map = new Map<string, { grade: string | null; coefficient: number }>()
|
||||
for (const d of res.data.details) {
|
||||
map.set(d.employeeId, { grade: d.grade, coefficient: d.coefficient })
|
||||
}
|
||||
setPerfDetails(map)
|
||||
}
|
||||
if (res.data?.filled > 0) {
|
||||
toast.success(res.data.message)
|
||||
} else {
|
||||
@@ -1229,7 +1237,56 @@ function BatchDetail({ batchId, onBack }: { batchId: string; onBack: () => void
|
||||
})()}
|
||||
</td>
|
||||
{renderCell(entry, 'baseSalary')}
|
||||
{renderCell(entry, 'performanceSalary')}
|
||||
{(() => {
|
||||
const perfVal = entry.performanceSalary || 0
|
||||
const perfInfo = perfDetails.get(entry.employeeId)
|
||||
const coef = perfInfo?.coefficient
|
||||
const grade = perfInfo?.grade
|
||||
// 颜色:A(1.2)=绿色, C(0.8)=橙色, D(0.6)=红色, B(1.0)/无=默认
|
||||
let colorClass = ''
|
||||
let title = ''
|
||||
if (coef != null && coef !== 1.0) {
|
||||
if (coef > 1.0) { colorClass = 'text-green-600'; title = `考核等级 ${grade},系数 ${coef}` }
|
||||
else if (coef >= 0.8) { colorClass = 'text-orange-600'; title = `考核等级 ${grade},系数 ${coef}` }
|
||||
else { colorClass = 'text-red-600'; title = `考核等级 ${grade},系数 ${coef}` }
|
||||
} else if (coef === 1.0 && grade) {
|
||||
title = `考核等级 ${grade},系数 1.0`
|
||||
}
|
||||
const isEditing = editCell?.employeeId === entry.employeeId && editCell?.field === 'performanceSalary'
|
||||
const canEdit = !isArchived && editableFields.includes('performanceSalary')
|
||||
if (isEditing) {
|
||||
return (
|
||||
<td className="py-1 px-1 text-right" key="performanceSalary">
|
||||
<input
|
||||
type="text"
|
||||
inputMode="decimal"
|
||||
autoFocus
|
||||
className="w-24 text-right border-b-2 border-primary bg-transparent px-1 py-0.5 text-xs focus:outline-none [appearance:none]"
|
||||
value={editValue}
|
||||
onChange={(e) => setEditValue(e.target.value)}
|
||||
onBlur={handleBlur}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
</td>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<td
|
||||
key="performanceSalary"
|
||||
className={`py-2 px-2 text-right ${canEdit ? 'cursor-text' : ''}`}
|
||||
onClick={() => canEdit && startEdit(entry.employeeId, 'performanceSalary', perfVal)}
|
||||
title={title}
|
||||
>
|
||||
{canEdit ? (
|
||||
<span className={`border-b border-dashed border-gray-300 hover:border-primary ${colorClass} ${perfVal === 0 ? 'text-gray-300' : ''}`}>
|
||||
{fmt(perfVal)}
|
||||
</span>
|
||||
) : (
|
||||
<span className={`${colorClass} ${perfVal === 0 ? 'text-gray-300' : ''}`}>{fmt(perfVal)}</span>
|
||||
)}
|
||||
</td>
|
||||
)
|
||||
})()}
|
||||
{renderCell(entry, 'overtimePay')}
|
||||
{renderCell(entry, 'allowance')}
|
||||
{renderCell(entry, 'bonus')}
|
||||
|
||||
Reference in New Issue
Block a user