/** * eQuery 管理页面 * * 展示 AI 自动生成的电子质疑清单,支持状态过滤、回复、关闭操作。 */ import React, { useState, useEffect, useCallback } from 'react'; import { Card, Table, Tag, Button, Select, Space, Typography, Badge, Modal, Input, message, Statistic, Row, Col, Tooltip, Empty, } from 'antd'; import { AlertOutlined, CheckCircleOutlined, ClockCircleOutlined, SyncOutlined, CloseCircleOutlined, SendOutlined, EyeOutlined, } from '@ant-design/icons'; import type { ColumnsType } from 'antd/es/table'; import * as iitProjectApi from '../api/iitProjectApi'; import type { Equery, EqueryStats } from '../api/iitProjectApi'; import { useIitProject } from '../context/IitProjectContext'; const { Text, Paragraph } = Typography; const { TextArea } = Input; const STATUS_CONFIG: Record = { pending: { color: 'warning', label: '待处理', icon: }, responded: { color: 'processing', label: '已回复', icon: }, reviewing: { color: 'purple', label: 'AI 复核中', icon: }, closed: { color: 'success', label: '已关闭', icon: }, reopened: { color: 'error', label: '已重开', icon: }, }; const SEVERITY_CONFIG: Record = { error: { color: 'error', label: '严重' }, warning: { color: 'warning', label: '警告' }, info: { color: 'default', label: '信息' }, }; const EQueryPage: React.FC = () => { const { projectId } = useIitProject(); const [equeries, setEqueries] = useState([]); const [total, setTotal] = useState(0); const [stats, setStats] = useState(null); const [loading, setLoading] = useState(false); const [statusFilter, setStatusFilter] = useState(undefined); const [page, setPage] = useState(1); // Respond modal const [respondModal, setRespondModal] = useState(false); const [respondTarget, setRespondTarget] = useState(null); const [responseText, setResponseText] = useState(''); const [responding, setResponding] = useState(false); // Detail modal const [detailModal, setDetailModal] = useState(false); const [detailTarget, setDetailTarget] = useState(null); const fetchData = useCallback(async () => { if (!projectId) return; setLoading(true); try { const [listResult, statsResult] = await Promise.all([ iitProjectApi.listEqueries(projectId, { status: statusFilter, page, pageSize: 20 }), iitProjectApi.getEqueryStats(projectId), ]); setEqueries(listResult.items); setTotal(listResult.total); setStats(statsResult); } catch { message.error('加载 eQuery 数据失败'); } finally { setLoading(false); } }, [projectId, statusFilter, page]); useEffect(() => { fetchData(); }, [fetchData]); const handleRespond = async () => { if (!respondTarget || !responseText.trim()) { message.warning('请输入回复内容'); return; } setResponding(true); try { await iitProjectApi.respondEquery(projectId, respondTarget.id, { responseText }); message.success('回复成功'); setRespondModal(false); setResponseText(''); fetchData(); } catch { message.error('回复失败'); } finally { setResponding(false); } }; const handleClose = async (equery: Equery) => { try { await iitProjectApi.closeEquery(projectId, equery.id, { closedBy: 'manual' }); message.success('已关闭'); fetchData(); } catch (err: any) { message.error(err.message || '关闭失败'); } }; const columns: ColumnsType = [ { title: '受试者', dataIndex: 'recordId', key: 'recordId', width: 100, render: (id: string) => {id}, }, { title: '状态', dataIndex: 'status', key: 'status', width: 110, render: (status: string) => { const cfg = STATUS_CONFIG[status] || { color: 'default', label: status, icon: null }; return {cfg.label}; }, }, { title: '严重程度', dataIndex: 'severity', key: 'severity', width: 90, render: (s: string) => { const cfg = SEVERITY_CONFIG[s] || { color: 'default', label: s }; return {cfg.label}; }, }, { title: '质疑内容', dataIndex: 'queryText', key: 'queryText', ellipsis: true, render: (text: string) => ( {text} ), }, { title: '字段', dataIndex: 'fieldName', key: 'fieldName', width: 130, render: (f: string) => f ? {f} : '—', }, { title: '创建时间', dataIndex: 'createdAt', key: 'createdAt', width: 160, render: (d: string) => new Date(d).toLocaleString('zh-CN'), }, { title: '操作', key: 'action', width: 160, render: (_: unknown, record: Equery) => ( )} {record.status !== 'closed' && ( )} ), }, ]; return (
{/* Stats */} {stats && ( } /> )} {/* Filter */}