// ===== page-order.jsx ===== const { useState: useSO, useEffect: useEO, useRef: useRO } = React; // 后端 status → 前端 phase const STATUS_TO_PHASE = { pending: 'pending', uploaded: 'uploaded', processing: 'processing', done: 'done', refunded: 'refunded', expired: 'expired', llm_failed: 'failed', }; function OrderPage({ navigate, params }) { const orderId = params?.id || ''; const urlToken = params?.token || ''; // URL 自带 token 时立即落盘到 localStorage,避免后续丢失(关 tab/换设备等) if (orderId && urlToken) { window.API.saveToken(orderId, urlToken); } const [order, setOrder] = useSO(null); const [loading, setLoading] = useSO(true); const [error, setError] = useSO(null); const [secondsLeft, setSecondsLeft] = useSO(0); const [procProgress, setProcProgress] = useSO(0); const phase = order ? (STATUS_TO_PHASE[order.status] || 'pending') : 'pending'; const pollingRef = useRO(null); // 初次加载 + polling const fetchOrder = async () => { try { const data = await window.API.getOrder(orderId); setOrder(data); setError(null); } catch (err) { console.error('fetch order failed', err); setError(err); } finally { setLoading(false); } }; useEO(() => { if (!orderId) { setError({ message: '订单号缺失' }); setLoading(false); return; } fetchOrder(); // 每 5s polling 一次 pollingRef.current = setInterval(fetchOrder, 5000); return () => clearInterval(pollingRef.current); }, [orderId]); // 倒计时:基于 expires_at useEO(() => { if (!order || phase !== 'pending') return; const tick = () => { const expires = new Date(order.expires_at + (order.expires_at.endsWith('Z') ? '' : 'Z')).getTime(); const remaining = Math.max(0, Math.floor((expires - Date.now()) / 1000)); setSecondsLeft(remaining); }; tick(); const id = setInterval(tick, 1000); return () => clearInterval(id); }, [order, phase]); // processing 阶段:合成进度条(仅视觉) useEO(() => { if (phase !== 'processing') { setProcProgress(0); return; } let p = procProgress; const id = setInterval(() => { p = Math.min(95, p + 2 + Math.random() * 3); setProcProgress(p); }, 400); return () => clearInterval(id); }, [phase]); // 完成时自动跳报告页 useEO(() => { if (phase !== 'done' || !order) return; setProcProgress(100); const t = setTimeout(() => { clearInterval(pollingRef.current); // 把 token 一并带进报告页 URL(来源:URL query 或本地存储) const t2 = urlToken || window.API.getToken(order.order_id); navigate('report', { id: order.order_id, token: t2 }); }, 1200); return () => clearTimeout(t); }, [phase, order, navigate, urlToken]); if (loading) { return (
加载订单中...
); } if (error || !order) { return (

订单获取失败

{error?.message || '订单可能已过期或不存在'}

); } const amount = order.amount.toFixed(2); const orderIdDisplay = `#${order.order_id}`; const fmtTime = (s) => `${String(Math.floor(s/60)).padStart(2,'0')}:${String(s%60).padStart(2,'0')}`; return (
navigate('home')} className="hover:text-navy-900 cursor-pointer">首页 navigate('upload')} className="hover:text-navy-900 cursor-pointer">上传论文 订单支付
订单信息
AIGC 智能分析
{orderIdDisplay}
AIGC 智能分析 · 5 维度统计模型 {order.word_count.toLocaleString()} 字 {order.plan === 'premium' ? '完整版' : '标准版'} {order.file_name} ≤ 30 秒
应付金额
¥ {amount}
请按精确金额支付,便于系统自动对账。金额错误将无法自动识别订单。
订单状态
{/* 保存访问凭证 */} {/* 失败/退款/过期 状态 */} {(phase === 'failed' || phase === 'refunded' || phase === 'expired') && (
{phase === 'failed' && '分析失败'} {phase === 'refunded' && '订单已退款'} {phase === 'expired' && '订单已过期'}
{phase === 'failed' && '检测引擎临时故障,已通知运维人工重试。如需立刻处理请联系 support@mojian.ai'} {phase === 'refunded' && '订单金额已退回原支付账户,3 个工作日内到账。'} {phase === 'expired' && '订单 30 分钟未支付,已自动关闭。可重新上传论文。'}
)}
🔥 本周已为 位同学完成检测 · 你不是一个人在熬夜
资金与隐私
  • · 我们仅记录付款金额与订单号匹配,不获取你的支付宝账户信息
  • · 30 分钟未支付订单自动关闭,论文文件 24 小时内同步删除
  • · 异常订单可通过 support@mojian.ai 申请人工核对
); } function AccessCredsCard({ orderId }) { const [copied, setCopied] = useSO(''); const token = window.API.getToken(orderId); const url = `${window.location.origin}/#/order/${orderId}`; if (!token) return null; const copy = (text, key) => { navigator.clipboard.writeText(text).then(() => { setCopied(key); setTimeout(() => setCopied(''), 1800); }).catch(() => {}); }; return (
⚠ 保存你的访问凭证

如果你换设备或清缓存,需要凭这组凭证(订单号 + 访问令牌)才能找回报告。建议立刻复制下来。

订单号
{orderId}
访问令牌
{token}
完整访问链接
{url}

找回入口: window.location.hash = '#/lookup'}>底部"找回订单"

); } function KV({ label, children }) { return (
{label}
{children}
); } function StatusTimeline({ phase, secondsLeft, fmtTime, procProgress }) { const steps = [ { key: 'pending', label: '等待支付', desc: '请在右侧完成支付宝扫码付款' }, { key: 'uploaded', label: '已支付,等待确认', desc: '系统正在比对支付金额与订单' }, { key: 'processing', label: '报告生成中', desc: 'LLM 多维度分析中,请稍候' }, { key: 'done', label: '完成', desc: '报告已生成,即将跳转' }, ]; const order = ['pending', 'uploaded', 'processing', 'done']; const currentIdx = order.indexOf(phase); return (
    {steps.map((s, i) => { const done = i < currentIdx; const active = i === currentIdx; return (
  1. {done ? : active ? : }
    {s.label}
    {active && s.key === 'pending' && (
    剩余 {fmtTime(secondsLeft)}
    )} {active && s.key === 'processing' && (
    {Math.round(procProgress)}%
    )} {done && 完成}
    {s.desc}
    {active && s.key === 'processing' && (
    )}
  2. ); })}
); } function FakeQR({ size = 220 }) { const N = 25; const cells = []; let seed = 0x4d6f6a; const rand = () => { seed = (seed * 9301 + 49297) % 233280; return seed / 233280; }; for (let r = 0; r < N; r++) for (let c = 0; c < N; c++) { const inFinder = (r < 7 && c < 7) || (r < 7 && c >= N - 7) || (r >= N - 7 && c < 7); if (inFinder) { const lr = r < 7 ? r : r - (N - 7); const lc = c < 7 ? c : c - (N - 7); const onRing = lr === 0 || lr === 6 || lc === 0 || lc === 6; const onCore = lr >= 2 && lr <= 4 && lc >= 2 && lc <= 4; cells.push(onRing || onCore); } else if (r === 6 || c === 6) { cells.push((r + c) % 2 === 0); } else { cells.push(rand() > 0.52); } } return ( {cells.map((on, i) => on ? ( ) : null)} ); } // 真实支付宝码:若 /qrcode.png 存在则显示,否则降级为 FakeQR。 function PaymentQR({ size = 220 }) { const [hasReal, setHasReal] = useSO(true); if (!hasReal) return ; return ( 支付宝收款码 setHasReal(false)} className="block rounded-md" style={{ width: size, height: size, objectFit: 'contain' }} /> ); } function PaymentPanel({ amount, suffix, phase, secondsLeft, fmtTime }) { const paid = phase !== 'pending' && phase !== 'expired'; const suffixStr = String(suffix).padStart(2, '0'); return (
支付宝扫码支付
打开支付宝 · 扫一扫
{paid ? '已收到支付' : '等待付款'}
{paid && (
支付成功
)}
请扫码支付
¥ {amount}
⚠ 金额必须精确为 ¥{amount},请勿修改默认金额
{phase === 'pending' && (
金额尾数 .{suffixStr} 是你的专属订单标识
{fmtTime(secondsLeft)}
超时后释放给他人 · 请勿关闭页面
)}
); } function CornerMarks() { return ( <>
); } window.OrderPage = OrderPage;