/* 任务展示页数据:轮询 /park/api/tasks(系统任务中心上墙任务),带租户身份。 参考 parkData.js 的 useParkSim 轮询 + 归一化防崩模式。 */ import { useEffect, useRef, useState } from 'react'; import { tenantFetch } from './tenant'; const POLL_MS = 8_000; function normalizeTask(raw, idx) { const t = raw || {}; return { id: t.id || '', task_code: t.task_code || '', scan_payload: t.scan_payload || t.task_code || '', title: t.title || '未命名任务', category: t.category || '综合', summary: t.summary || '', tags: Array.isArray(t.tags) ? t.tags : [], budgetMin: Number(t.budget_min) || 0, budgetMax: Number(t.budget_max) || 0, budgetLabel: fmtBudget(t.budget_min, t.budget_max), mode: t.mode || 'grab', modeLabel: MODE_LABEL[t.mode] || '抢单', deadline: t.deadline || '', deliveryDays: Number(t.delivery_days) || 0, headcount: Number(t.headcount) || 0, exclusive: !!t.exclusive, publisherName: t.publisher_name || '', status: t.status || 'published', claimed: t.status === 'claimed' || t.status === 'doing' || !!t.claimed_by, claimedBy: t.claimed_by || '', claimedAt: t.claimed_at || '', key: t.id || `task-${idx}`, }; } const MODE_LABEL = { grab: '抢单', bid: '竞标', assign: '指派', recommend: '推荐', }; function fmtBudget(min, max) { const n = Number(min) || 0, x = Number(max) || 0; if (!n && !x) return '面议'; if (!x) return `¥${n}`; if (n === x) return `¥${n}`; return `¥${n}~¥${x}`; } export function useTasks() { const [tasks, setTasks] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); const aliveRef = useRef(true); useEffect(() => { aliveRef.current = true; setLoading(true); const load = async () => { try { const res = await tenantFetch('/api/tasks'); if (!res.ok) throw new Error(`任务加载失败(${res.status})`); const body = await res.json(); const items = Array.isArray(body?.items) ? body.items : []; if (aliveRef.current) { setTasks(items.map(normalizeTask)); setError(''); } } catch (e) { if (aliveRef.current) setError(e.message || '任务加载失败'); } finally { if (aliveRef.current) setLoading(false); } }; load(); const timer = setInterval(load, POLL_MS); return () => { aliveRef.current = false; clearInterval(timer); }; }, []); return { tasks, loading, error }; }