// ===== page-admin.jsx =====
const { useState: useSA, useMemo: useMA, useEffect: useEA, useRef: useRA } = React;
const ADMIN_STATUSES = {
pending: { key: 'pending', label: '待支付', dotClass: 'bg-ink-400' },
uploaded: { key: 'uploaded', label: '已上传待确认', dotClass: 'bg-warn-500' },
processing: { key: 'processing', label: 'LLM 处理中', dotClass: 'bg-brand-500' },
done: { key: 'done', label: '已完成', dotClass: 'bg-ok-500' },
refunded: { key: 'refunded', label: '已退款', dotClass: 'bg-bad-500' },
expired: { key: 'expired', label: '已过期', dotClass: 'bg-ink-300' },
llm_failed: { key: 'llm_failed', label: 'LLM 失败', dotClass: 'bg-bad-600' },
};
// ⚠️ 后台凭证内嵌在前端(按需求降低使用门槛)。
// 一旦域名被公开广泛传播,请改回密码登录(注释掉下面 useEA 块即可)。
const ADMIN_AUTO_USERNAME = 'admin';
const ADMIN_AUTO_PASSWORD = 'JtrZvqHnvRprUtn5gLjg5nH_so4';
function AdminPage({ navigate }) {
const [loggedIn, setLoggedIn] = useSA(window.API.isAdminLoggedIn());
useEA(() => {
if (!loggedIn) {
window.API.saveAdmin(ADMIN_AUTO_USERNAME, ADMIN_AUTO_PASSWORD);
setLoggedIn(true);
}
}, []);
if (!loggedIn) {
// 首次渲染瞬间的占位(自动登录会立刻完成)
return (
);
}
return { window.API.clearAdmin(); setLoggedIn(false); window.location.hash = '#/'; }} navigate={navigate} />;
}
function AdminLogin({ onSuccess, navigate }) {
const [username, setU] = useSA('admin');
const [password, setP] = useSA('');
const [submitting, setS] = useSA(false);
const [error, setE] = useSA(null);
const submit = async (e) => {
e.preventDefault();
if (!password.trim() || submitting) return;
setS(true);
setE(null);
window.API.saveAdmin(username.trim(), password);
try {
await window.API.adminStats(); // 用 stats 端点验证凭证
onSuccess();
} catch (err) {
window.API.clearAdmin();
setE(err?.message || '登录失败');
} finally {
setS(false);
}
};
return (
);
}
function AdminDashboard({ onLogout, navigate }) {
const [orders, setOrders] = useSA([]);
const [total, setTotal] = useSA(0);
const [stats, setStats] = useSA(null);
const [loading, setLoading] = useSA(true);
const [q, setQ] = useSA('');
const [statusFilter, setStatus] = useSA('all');
const [busyId, setBusy] = useSA(null);
const [errMsg, setErrMsg] = useSA(null);
const refreshTimer = useRA(null);
const load = async () => {
try {
const params = {};
if (statusFilter !== 'all') params.status = statusFilter;
if (q.trim()) {
// 数字纯尾数搜索
if (/^\d{1,2}$/.test(q.trim())) {
params.amount_suffix = parseInt(q.trim(), 10);
} else {
params.q = q.trim();
}
}
const [ord, st] = await Promise.all([
window.API.adminListOrders(params),
window.API.adminStats(),
]);
setOrders(ord.items || []);
setTotal(ord.total || 0);
setStats(st);
setErrMsg(null);
} catch (err) {
console.error('admin load failed', err);
setErrMsg(err?.message || '加载失败');
if (err?.status === 401) {
onLogout();
}
} finally {
setLoading(false);
}
};
useEA(() => {
load();
refreshTimer.current = setInterval(load, 8000);
return () => clearInterval(refreshTimer.current);
}, [statusFilter, q]);
const counts = useMA(() => {
return Object.keys(ADMIN_STATUSES).reduce((acc, k) => {
acc[k] = orders.filter(o => o.status === k).length;
return acc;
}, { all: orders.length });
}, [orders]);
const confirm = async (id) => {
setBusy(id);
try {
await window.API.adminConfirm(id, '');
await load();
} catch (err) {
alert(`确认失败:${err?.message || err}`);
} finally {
setBusy(null);
}
};
const refund = async (id) => {
if (!confirm_native('确定要标记此订单为已退款?')) return;
setBusy(id);
try {
await window.API.adminRefund(id, '管理员退款');
await load();
} catch (err) {
alert(`退款失败:${err?.message || err}`);
} finally {
setBusy(null);
}
};
const reprocess = async (id) => {
setBusy(id);
try {
await window.API.adminReprocess(id);
await load();
} catch (err) {
alert(`重跑失败:${err?.message || err}`);
} finally {
setBusy(null);
}
};
return (
{/* 实时看板 */}
{stats && (
)}
订单管理
手动确认支付到账 · 触发 LLM 检测 · 监控订单状态。{loading ? ' · 加载中...' : ` · 共 ${total} 单`}
{errMsg && (
{errMsg}
)}
setStatus('all')} />
{Object.values(ADMIN_STATUSES).map(s => (
setStatus(s.key)} />
))}
| 订单号 |
上传时间 |
文件名 |
字数 |
金额 |
状态 |
操作 |
{orders.map(o => {
const isBusy = busyId === o.id;
return (
| {o.id} |
{new Date(o.created_at).toLocaleString('zh-CN')} |
{o.file_name} |
{o.word_count.toLocaleString()} |
¥ {o.amount.toFixed(2)} |
|
{(o.status === 'pending' || o.status === 'uploaded') && (
)}
{(o.status === 'llm_failed' || o.status === 'processing') && (
)}
{(o.status === 'done' || o.status === 'processing') && o.report_id && (
)}
{o.status !== 'refunded' && o.status !== 'expired' && (
)}
|
);
})}
{orders.length === 0 && !loading && (
| 暂无订单 |
)}
{loading && orders.length === 0 && (
| 加载中... |
)}
工作流: 用户上传论文 → 系统生成订单(待支付)→ 用户扫码支付 → 你手机收到支付宝通知 →
在此搜索"金额尾数(如 42)"快速定位 → 点击「确认收款」→ 系统自动进入「LLM 处理中」并在 30 秒后切到「已完成」,用户的等待页会自动跳转到报告。
数据每 8 秒自动刷新。
);
}
function StatTile({ label, value, accent = 'ink' }) {
const accents = {
ink: 'text-navy-900',
ok: 'text-ok-600',
warn: 'text-warn-600',
brand: 'text-brand-600',
};
return (
);
}
function Th({ children, className = '' }) {
return {children} | ;
}
function Td({ children, className = '' }) {
return {children} | ;
}
function FilterChip({ label, count, dot, active, onClick }) {
return (
);
}
// 浏览器原生 confirm,避免和上面 confirm() 函数重名
function confirm_native(msg) {
return window.confirm(msg);
}
window.AdminPage = AdminPage;