fix: HR系统优化批次1 - P0/P1问题修复
P0-3: 修复合同状态判断逻辑,有合同记录但signDate为null时不再误判未签 P0-5: 修复参保城市默认北京问题,导入和预览均改为null P0-11: 添加全局ErrorBoundary防止白屏,三处布局均包裹 P1-2: 合同附件改为可选,允许先保存再补充上传 P1-7.2: 排班弹窗增加员工搜索(姓名/部门) P1-8.2: 加班费导入支持Excel(xlsx/xls)格式,兼容中英文列名 P1-9: 社保/公积金基数月度办理支持逐人修改,后端返回recordId
This commit is contained in:
@@ -2,6 +2,7 @@ import { useState, useRef } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { useConfirm } from '../hooks/useConfirm'
|
||||
import * as XLSX from 'xlsx'
|
||||
import { Calculator, AlertCircle, Info, Check, Upload, Layers, Settings as SettingsIcon, Archive, Plus, Trash2, AlertTriangle, Download, FileText, X, ChevronLeft, Wallet, LayoutTemplate, Clock, Receipt, Users, TrendingDown, TrendingUp, BadgeCheck } from 'lucide-react'
|
||||
import api from '../lib/api'
|
||||
import { useAuthStore } from '../store/authStore'
|
||||
@@ -1527,34 +1528,70 @@ function OvertimeCalculator() {
|
||||
const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0]
|
||||
if (!file) return
|
||||
const reader = new FileReader()
|
||||
reader.onload = (event) => {
|
||||
const text = event.target?.result as string
|
||||
const lines = text.split('\n').filter(l => l.trim())
|
||||
const empList = employees?.items || []
|
||||
const fileName = file.name.toLowerCase()
|
||||
|
||||
const parseRows = (rows: any[]): void => {
|
||||
const items: any[] = []
|
||||
const empList = employees?.items || []
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
const cols = lines[i].split(',').map(c => c.trim())
|
||||
const empName = cols[0]
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
const row = rows[i]
|
||||
// 兼容中文列名和英文列名
|
||||
const empName = String(row['姓名'] ?? row['name'] ?? row['姓名*'] ?? '').trim()
|
||||
if (!empName) continue
|
||||
const emp = empList.find(e => e.name === empName)
|
||||
if (!emp) continue
|
||||
items.push({
|
||||
employeeId: emp.id,
|
||||
employeeName: emp.name,
|
||||
department: emp.department,
|
||||
month: cols[4] || month,
|
||||
weekdayHours: Number(cols[1]) || 0,
|
||||
weekendHours: Number(cols[2]) || 0,
|
||||
holidayHours: Number(cols[3]) || 0,
|
||||
month: String(row['月份'] ?? row['month'] ?? '').trim() || month,
|
||||
weekdayHours: Number(row['工作日加班时长'] ?? row['weekdayHours'] ?? row['工作日'] ?? 0) || 0,
|
||||
weekendHours: Number(row['休息日加班时长'] ?? row['weekendHours'] ?? row['休息日'] ?? 0) || 0,
|
||||
holidayHours: Number(row['法定节假日加班时长'] ?? row['holidayHours'] ?? row['法定节假日'] ?? 0) || 0,
|
||||
})
|
||||
}
|
||||
if (items.length > 0) {
|
||||
setPreviewData(items)
|
||||
} else {
|
||||
toast.error('未匹配到员工,请确保CSV第一列为员工姓名')
|
||||
toast.error('未匹配到员工,请确保文件包含"姓名"列')
|
||||
}
|
||||
}
|
||||
reader.readAsText(file)
|
||||
|
||||
if (fileName.endsWith('.xlsx') || fileName.endsWith('.xls')) {
|
||||
// Excel 格式解析
|
||||
const reader = new FileReader()
|
||||
reader.onload = (event) => {
|
||||
try {
|
||||
const data = new Uint8Array(event.target?.result as ArrayBuffer)
|
||||
const wb = XLSX.read(data, { type: 'array' })
|
||||
const ws = wb.Sheets[wb.SheetNames[0]]
|
||||
const rows = XLSX.utils.sheet_to_json(ws)
|
||||
parseRows(rows)
|
||||
} catch {
|
||||
toast.error('Excel 文件解析失败')
|
||||
}
|
||||
}
|
||||
reader.readAsArrayBuffer(file)
|
||||
} else {
|
||||
// CSV 格式解析(保持兼容)
|
||||
const reader = new FileReader()
|
||||
reader.onload = (event) => {
|
||||
const text = event.target?.result as string
|
||||
const lines = text.split('\n').filter(l => l.trim())
|
||||
if (lines.length < 2) { toast.error('CSV 文件内容为空'); return }
|
||||
// 解析表头
|
||||
const headers = lines[0].split(',').map(c => c.trim())
|
||||
const rows: any[] = []
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
const cols = lines[i].split(',').map(c => c.trim())
|
||||
const row: any = {}
|
||||
headers.forEach((h, idx) => { row[h] = cols[idx] ?? '' })
|
||||
rows.push(row)
|
||||
}
|
||||
parseRows(rows)
|
||||
}
|
||||
reader.readAsText(file)
|
||||
}
|
||||
}
|
||||
|
||||
const confirmImport = () => {
|
||||
|
||||
Reference in New Issue
Block a user