Files
park-desktop/src/utils/taskData.js
T
Pine 75f50cebba feat(tasks): 大屏任务卡展示增强 —— 接单方式/截止/交付时限/人数/独占/发包方
- taskData.js normalizeTask 加 mode/modeLabel/deadline/deliveryDays/headcount/exclusive/publisherName。
- TaskDisplay.jsx 卡片增 接单方式+预算、截止/交付时限/独占|人数 徽标、发包方。
- task-display.css 加 task-meta 行样式。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-25 19:30:34 +08:00

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 };
}