75f50cebba
- taskData.js normalizeTask 加 mode/modeLabel/deadline/deliveryDays/headcount/exclusive/publisherName。 - TaskDisplay.jsx 卡片增 接单方式+预算、截止/交付时限/独占|人数 徽标、发包方。 - task-display.css 加 task-meta 行样式。 Co-Authored-By: Claude <noreply@anthropic.com>
80 lines
2.5 KiB
JavaScript
80 lines
2.5 KiB
JavaScript
/* 任务展示页数据:轮询 /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 };
|
|
}
|