import React, { useEffect, useMemo, useRef, useState } from "react"; import { MessageCircleMore, RefreshCw, X } from "lucide-react"; const REASONS = [ "内容不清晰", "数量不足", "问题不匹配", "回答不符合要求", "商品不一致", "其他", ]; const STATUSES = ["全部", "待审核"]; const formatTime = (value) => value ? new Date(value).toLocaleString("zh-CN", { hour12: false }) : "--"; const productName = (record) => [ ...new Set( record.questions.filter((q) => q.selected).map((q) => q.product_title), ), ].join(" / ") || "待识别商品"; const StatusBadge = ({ status }) => ( {status} ); function RefundPrompt({ onConfirm, onCancel }) { return (
✓

审核已通过

是否前往任务看板的“待返款”模块进行返款?

); } function ReviewDetail({ record, close, decide, busy }) { const [reason, setReason] = useState(REASONS[0]); const [customReason, setCustomReason] = useState(""); const selected = record.questions.filter((q) => q.selected); const pending = record.status === "待审核"; return (
event.target === event.currentTarget && close()} >

回答审核 · 用户任务列表

任务ID:{record.submission_no}

{record.user_id} {record.submission_no} 商品:{productName(record)} 提交时间:{formatTime(record.updated_at)}

① 问答记录(发布问题 ↔ 用户回答)

{selected.map((question, index) => (
Q{index + 1}:{question.question}

A: {question.answer}

))}

② 用户上传的任务截图

{record.completion_screenshots.map((image, index) => (
{`任务截图${index
任务截图 {index + 1}
))}

③ 返款二维码截图

{record.payment_screenshots?.length ? ( record.payment_screenshots.map((image) => (
收款码
审核通过后用于后续人工处理
)) ) : (
未单独上传收款码
)}
{record.latest_review && !pending && (
{record.latest_review.approved ? "审核已通过" : "审核未通过"} {!record.latest_review.approved && (

原因:{record.latest_review.reason_code} {record.latest_review.reason_text ? `;${record.latest_review.reason_text}` : ""}

)}
)}
{pending ? (
审核不通过原因: setCustomReason(event.target.value)} placeholder={ reason === "其他" ? "请输入自定义原因(必填)" : "补充说明(选填)" } />
) : ( )}
); } function AskReviewDetail({ record, close, onChanged, api, onApproved }) { const [reason, setReason] = useState("截图不清晰"); const [reasonText, setReasonText] = useState(""); const [busy, setBusy] = useState(false); const selected = record.questions.filter((item) => item.selected); async function decide(approved) { setBusy(true); try { await api( `/api/admin/ask-completion-reviews/${record.batch_id || record.assignment_id}/decision`, { method: "POST", body: JSON.stringify({ approved, reason_code: approved ? "" : reason, reason_text: reasonText.trim(), }), }, ); close(); await onChanged(); window.dispatchEvent(new Event("review-count-changed")); if (approved) onApproved(); } catch (error) { window.alert(error.message); } finally { setBusy(false); } } return (
event.target === event.currentTarget && close()} >

提问审核 · 用户任务详情

任务ID:{record.task_id}

{record.user_id} {record.task_id} 商品:{record.product_name} 提交时间:{formatTime(record.updated_at)}

① 用户认领并完成的提问

{selected.map((item, index) => (
Q{index + 1}:{item.question}
))}

② 用户上传的提问截图

{record.question_screenshots.map((image, index) => (
{`提问截图${index
提问截图 {index + 1}
))}

③ 用户上传的收款码

{record.payment_screenshots.map((image) => (
收款码
收款码截图
))}
{record.latest_review && record.status !== "待审核" && (
{record.latest_review.approved ? "审核已通过" : "审核未通过"} {!record.latest_review.approved && (

原因:{record.latest_review.reason_code} {record.latest_review.reason_text ? `;${record.latest_review.reason_text}` : ""}

)}
)}
{record.status === "待审核" ? (
审核不通过原因: setReasonText(event.target.value)} placeholder="补充说明(选填)" />
) : ( )}
); } function AskReviewQueue({ api, onApproved }) { const [records, setRecords] = useState([]); const [detail, setDetail] = useState(null); const [loading, setLoading] = useState(true); async function openDetail(record) { try { const result = await api( `/api/admin/ask-completion-reviews/${record.batch_id || record.assignment_id}`, ); setDetail(result.data); } catch (error) { window.alert(error.message); } } async function load() { setLoading(true); try { const result = await api("/api/admin/ask-completion-reviews"); setRecords(result.data); } catch (error) { window.alert(error.message); } finally { setLoading(false); } } useEffect(() => { load(); }, []); return ( <>
用户 / 任务 商品 提交时间 提问数 审核状态 操作
{loading ? (
正在加载提问审核任务…
) : records.length === 0 ? (
暂无提问审核任务
) : ( records.map((record) => (
{record.user_id} {record.task_id} {record.phone.replace(/(\d{3})\d{4}(\d{4})/, "$1****$2")} {record.product_name} {formatTime(record.updated_at)} {record.questions.filter((item) => item.selected).length} {" "} 条提问
)) )}
{detail && ( setDetail(null)} onChanged={load} api={api} onApproved={onApproved} /> )} ); } export default function ReviewCenterPage({ api }) { const [queue, setQueue] = useState("ask"); const [statusFilter, setStatusFilter] = useState("待审核"); const [records, setRecords] = useState([]); const [detail, setDetail] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); const [busy, setBusy] = useState(false); const [detailLoading, setDetailLoading] = useState(false); const [refundPrompt, setRefundPrompt] = useState(false); const loadVersion = useRef(0); async function openAnswerDetail(record) { setDetailLoading(true); try { const result = await api( `/api/admin/answer-completion-reviews/${record.id}`, ); setDetail(result.data); } catch (err) { window.alert(err.message); } finally { setDetailLoading(false); } } async function load() { const version = ++loadVersion.current; setLoading(true); try { const result = await api("/api/admin/answer-completion-reviews"); if (version !== loadVersion.current) return; setRecords(result.data); setError(""); } catch (err) { if (version === loadVersion.current) setError(err.message); } finally { if (version === loadVersion.current) setLoading(false); } } useEffect(() => { load(); }, []); useEffect(() => { if (queue !== "answer") return undefined; const timer = window.setInterval(() => load(), 5000); return () => window.clearInterval(timer); }, [queue]); const allRows = useMemo( () => records.filter((record) => record.status === "待审核"), [records], ); const rows = useMemo( () => statusFilter === "全部" ? allRows : allRows.filter((record) => record.status === statusFilter), [allRows, statusFilter], ); const count = (status) => status === "全部" ? allRows.length : allRows.filter((record) => record.status === status).length; async function decide(record, approved, reasonCode = "", reasonText = "") { setBusy(true); try { await api(`/api/admin/answer-completion-reviews/${record.id}/decision`, { method: "POST", body: JSON.stringify({ approved, reason_code: reasonCode, reason_text: reasonText, }), }); setDetail(null); await load(); window.dispatchEvent(new Event("review-count-changed")); setStatusFilter("待审核"); if (approved) setRefundPrompt(true); } catch (err) { window.alert(err.message); } finally { setBusy(false); } } return ( <>

审核中心

提问审核与回答审核,对照用户提交截图进行任务终审。

{queue === "answer" ? "回答审核说明" : "提问审核说明"}

{queue === "answer" ? "同一用户在同一任务下的多组问答按「用户ID + 任务ID」归并为一条;点击查看任务列表,逐组对照问答和用户完成截图。" : "审核提问用户提交的问题、截图及任务完成情况。提问任务链路接入后会显示在这里。"}

{error &&
{error}
} {queue === "ask" ? ( setRefundPrompt(true)} /> ) : ( <>
审核状态: {STATUSES.map((status) => ( ))}
用户 / 任务 商品 提交时间 问答数 审核状态 操作
{loading ? (
正在加载审核任务…
) : rows.length === 0 ? (
当前状态暂无回答审核任务
) : ( rows.map((record) => (
{record.user_id} {record.submission_no} {record.phone.replace(/(\d{3})\d{4}(\d{4})/, "$1****$2")} {productName(record)} {formatTime(record.updated_at)} {record.questions.filter((q) => q.selected).length} {" "} 组问答
)) )}
)} {detail && ( setDetail(null)} decide={decide} busy={busy} /> )} {refundPrompt && ( setRefundPrompt(false)} onConfirm={() => { setRefundPrompt(false); window.dispatchEvent(new CustomEvent("open-task-board", { detail: { status: "待返款" } })); }} /> )} ); }