import React, { useState, useEffect, useRef } from 'react'; import { Form, Input, InputNumber, Button, Card, Divider, Space, Typography, message, Tag, Row, Col, Alert, DatePicker, Result, Spin, } from 'antd'; import { SaveOutlined, SendOutlined, LoadingOutlined, CheckCircleOutlined } from '@ant-design/icons'; import { useAuth } from '../../context/AuthContext'; import { performanceApi, PerformanceSubmitDTO } from '../../api/performance'; import { ALL_PERFORMANCE_ITEMS } from './performanceItems'; import dayjs, { Dayjs } from 'dayjs'; const { Title, Text } = Typography; const { TextArea } = Input; interface FormValues { assessmentMonth: Dayjs; workSummary: string; leave: number; late: number; absent: number; lackCard: number; attendanceRemark?: string; items: Array<{ userContent: string; selfScore: number; evidence?: string; }>; } const PerformanceForm: React.FC = () => { const { userInfo } = useAuth(); const [form] = Form.useForm(); const [submitting, setSubmitting] = useState(false); const [saving, setSaving] = useState(false); const [submitted, setSubmitted] = useState(false); const [aiWaiting, setAiWaiting] = useState(false); const [aiDone, setAiDone] = useState(false); const pollTimerRef = useRef | null>(null); const isMobile = typeof window !== 'undefined' && window.innerWidth < 768; // 清理轮询定时器 useEffect(() => { return () => { if (pollTimerRef.current) clearInterval(pollTimerRef.current); }; }, []); // 轮询 AI 结果 const pollAIResult = (perfId: number) => { setAiWaiting(true); let attempts = 0; const maxAttempts = 40; // 最多轮询 40 次(约 2 分钟) pollTimerRef.current = setInterval(async () => { attempts++; try { const result = await performanceApi.checkAIResult(perfId); if (result.done) { clearInterval(pollTimerRef.current!); setAiWaiting(false); setAiDone(true); } else if (attempts >= maxAttempts) { clearInterval(pollTimerRef.current!); setAiWaiting(false); setAiDone(true); // 超时也跳转,让用户去历史记录查看 } } catch { // 忽略轮询错误,继续重试 } }, 3000); // 每 3 秒轮询一次 }; // 获取本月作为默认值 const getPreviousMonth = (): Dayjs => { return dayjs(); }; const buildDTO = (values: FormValues, status: 'draft' | 'submitted'): PerformanceSubmitDTO => ({ month: values.assessmentMonth.format('YYYY-MM'), status, workSummary: values.workSummary || '', attendance: { leave: values.leave ?? 0, late: values.late ?? 0, absent: values.absent ?? 0, lackCard: values.lackCard ?? 0, remark: values.attendanceRemark, }, performanceItems: ALL_PERFORMANCE_ITEMS.map((item, idx) => ({ itemName: item.itemName, weight: item.weight, userContent: values.items?.[idx]?.userContent || '', selfScore: values.items?.[idx]?.selfScore ?? 0, evidence: values.items?.[idx]?.evidence, })), }); const handleSave = async () => { const values = form.getFieldsValue(); setSaving(true); try { await performanceApi.submit(buildDTO(values, 'draft')); message.success('草稿已暂存'); } catch (err: any) { message.error(err?.response?.data?.message || '暂存失败,请重试'); } finally { setSaving(false); } }; const handleSubmit = async (values: FormValues) => { setSubmitting(true); try { const result = await performanceApi.submit(buildDTO(values, 'submitted')); setSubmitted(true); form.resetFields(); // 提交成功后开始轮询 AI 结果 if (result?.perfId) { pollAIResult(result.perfId); } } catch (err: any) { message.error(err?.response?.data?.message || '提交失败,请重试'); } finally { setSubmitting(false); } }; if (submitted) { // AI 分析完成 if (aiDone) { return ( } title="AI 分析完成!" subTitle="绩效已提交,AI 评分已生成,可前往历史记录查看详情。" style={{ margin: 24 }} extra={ } /> ); } // AI 分析中 if (aiWaiting) { return (
} />
AI 正在分析中,请稍等! 系统正在对您的绩效数据进行 AI 智能评分,通常需要 30 秒至 1 分钟...
); } // 提交成功但还未开始轮询(过渡状态) return ( setSubmitted(false)}>重新填报 } /> ); } return (
绩效填报 {/* 基础信息 */} {isMobile ? (
姓名:{userInfo?.name}
部门:{userInfo?.department}
岗位:{userInfo?.position}
) : ( 姓名:{userInfo?.name} 部门:{userInfo?.department} 岗位:{userInfo?.position} )}
({ selfScore: 0 })), }} > {/* 考核月份选择 */} { // 不能选择未来的月份 return current && current > dayjs().endOf('month'); }} /> {/* 考核指标 */} {/* 业务素质 */} 业务素质考评 <Tag color="blue">占总分 70%</Tag> {ALL_PERFORMANCE_ITEMS.filter(i => i.category === 'business').map((item, idx) => ( {item.itemName} 权重 {item.weight} 分 } > {item.description}