import { useState } from 'react' import { usePageSize } from '../hooks/usePageSize' import { useQuery } from '@tanstack/react-query' import { ScrollText } from 'lucide-react' import { auditApi } from '../lib/api-services' import Card from '../components/ui/Card' import Button from '../components/ui/Button' import EmptyState from '../components/ui/EmptyState' import Pagination from '../components/ui/Pagination' import PageGuide from '../components/ui/PageGuide' const ACTION_LABELS: Record = { CREATE: '创建', UPDATE: '更新', DELETE: '删除', CORRECT: '更正', CREATE_EMPLOYEE: '创建员工', UPDATE_EMPLOYEE: '更新员工', DELETE_EMPLOYEE: '删除员工', IMPORT_EMPLOYEES: '导入员工', BATCH_RENEW: '批量续签', ADD_CONTRACT: '添加合同', CREATE_CONTRACT: '创建合同', SIGN_CONTRACT: '签署合同', TERMINATE: '解聘', CREATE_TERMINATION: '创建解聘记录', APPROVE_TERMINATION: '审批解聘', REJECT_TERMINATION: '驳回解聘', EXECUTE_TERMINATION: '执行解聘', CANCEL_TERMINATION: '撤销解聘', SUBMIT_TERMINATION: '提交解聘审批', CREATE_DRAFT: '创建草稿', REHIRE: '重新入职', PAYROLL_GENERATE: '生成工资条', PAYROLL_ARCHIVE: '归档发薪批次', PAYROLL_PUBLISH: '发布工资条', EXPORT_PAYROLL: '导出薪税', AI_CHAT: 'AI 对话', AI_REVIEW: 'AI 审查', UPDATE_SETTINGS: '更新设置', POLICY_ADVANCE: '制度流程推进', POLICY_PUBLISH: '制度发布', EVIDENCE_CREATE: '创建证据链', ATTENDANCE_CONFIRM: '考勤确认', } const ENTITY_LABELS: Record = { EMPLOYEE: '员工', CONTRACT: '合同', TERMINATION: '解聘', TERMINATION_RECORD: '解聘记录', PAYROLL: '薪税', PAYSLIP: '工资条', SETTINGS: '设置', POLICY: '制度', POLICY_DOCUMENT: '制度文件', EVIDENCE: '证据链', AI: 'AI', ATTENDANCE: '考勤', DISCIPLINARY: '违纪', EmployeeSocialInsRecord: '社保记录', EmployeeHousingFundRecord: '公积金记录', SocialMonthlyProcess: '社保月度流程', RetirementPolicy: '退休政策', NotificationSetting: '通知设置', } /** 审计日志 detail 字段中文标签映射 */ const DETAIL_LABELS: Record = { employeeName: '员工', department: '部门', name: '姓名', reason: '原因', type: '类型', terminationDate: '离职日期', compensation: '补偿金', contractType: '合同类型', startDate: '开始日期', endDate: '结束日期', employeeId: '员工ID', count: '数量', years: '续签年限', hireDate: '入职日期', oldSalary: '原薪资', newSalary: '新薪资', oldDepartment: '原部门', newDepartment: '新部门', comment: '备注', resignationReason: '离职原因', batch: '批量', violationType: '违纪类型', severity: '严重程度', action: '处理措施', topic: '培训主题', trainer: '培训师', duration: '时长', period: '考核周期', score: '得分', grade: '等级', reviewer: '考评人', } /** 将 detail 对象格式化为可读字符串 */ function formatDetail(detail: any): string { if (!detail || typeof detail !== 'object') return '' const entries = Object.entries(detail).filter(([, v]) => v !== '' && v !== null && v !== undefined) if (entries.length === 0) return '' return entries.map(([k, v]) => { const label = DETAIL_LABELS[k] || k let val = String(v) if (k === 'compensation' && val && val !== '0') val = `¥${val}` if (k === 'startDate' || k === 'endDate' || k === 'terminationDate' || k === 'hireDate') { val = String(v).slice(0, 10) } return `${label}: ${val}` }).join(' | ') } /** * 系统操作日志页面 */ export default function AuditLog() { const pageSize = usePageSize() const [page, setPage] = useState(1) const [action, setAction] = useState('') const [entity, setEntity] = useState('') const [dateFrom, setDateFrom] = useState('') const [dateTo, setDateTo] = useState('') const { data, isLoading } = useQuery({ queryKey: ['audit-logs', page, pageSize, action, entity, dateFrom, dateTo], queryFn: async () => { const p: any = { page, pageSize } if (action) p.action = action if (entity) p.entity = entity if (dateFrom) p.dateFrom = dateFrom if (dateTo) p.dateTo = dateTo return await auditApi.list(p) }, }) const { data: stats } = useQuery({ queryKey: ['audit-stats'], queryFn: async () => { return await auditApi.stats() }, }) return (
审计日志,记录系统中所有关键操作,包括员工档案变更、合同签订、薪酬调整、权限变更等。支持按操作类型、时间范围、操作人筛选查看,用于合规追溯。

系统操作日志

记录所有关键操作,保留6个月以上

{/* 统计概览 */} {stats && stats.length > 0 && (
操作类型分布
{stats.slice(0, 10).map((s: any) => (
{ACTION_LABELS[s.action] || s.action} {s._count.action}
))}
)} {/* 筛选栏 */}
{ setDateFrom(e.target.value); setPage(1) }} className="block mt-1 px-2 py-1.5 text-sm border rounded-lg focus:outline-none focus:ring-1 focus:ring-primary" />
{ setDateTo(e.target.value); setPage(1) }} className="block mt-1 px-2 py-1.5 text-sm border rounded-lg focus:outline-none focus:ring-1 focus:ring-primary" />
{(action || entity || dateFrom || dateTo) && ( )}
{/* 日志列表 */} {isLoading ? (
加载中...
) : !data?.items || data.items.length === 0 ? ( ) : ( <>
{data.items.map((log: any) => (
{ACTION_LABELS[log.action] || log.action} {ENTITY_LABELS[log.entity] || log.entity}
{log.detail && (() => { const formatted = formatDetail(log.detail); return formatted ? (
{formatted}
) : null; })()}
{new Date(log.createdAt).toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })}
{log.ip &&
{log.ip}
}
))}
setPage(1)} /> )}
) }