import React, { useEffect, useMemo, useRef, useState } from 'react'; import { QRCodeSVG } from 'qrcode.react'; import { useTasks } from '../utils/taskData'; import '../styles/task-display.css'; /* 任务展示页:轮播卡片 + 二维码扫码接单。 · 数据:useTasks() 轮询 /park/api/tasks(系统任务中心上墙任务) · 布局:一屏固定 2 行;宽度 ≥1600 → 3 列(2×3=6 张),否则 → 2 列(2×2=4 张) · 卡片:分类/状态徽标、标题、摘要(2行)、标签、预算、任务ID + 二维码(扫码即领) · 轮播:每页 列数×行数 张,自动翻页;MQTT task_refresh 可触发刷新 */ const ROTATE_MS = 14_000; const ROWS = 2; // 一屏固定 2 行 /* 按宽度计算列数:宽≥1600→3 列,否则 2 列。导出便于测试。 */ export function columnsFor(width) { const w = width || (typeof window !== 'undefined' ? window.innerWidth : 1280); return w >= 1600 ? 3 : 2; } export default function TaskDisplay() { const { tasks, loading, error } = useTasks(); const [page, setPage] = useState(0); const [refreshTick, setRefreshTick] = useState(0); const [cols, setCols] = useState(() => columnsFor()); const tickRef = useRef(0); // 视口宽度 → 列数(2 或 3 列),随屏幕大小自适应(行数固定 2) useEffect(() => { const update = () => setCols(columnsFor()); update(); window.addEventListener('resize', update); return () => window.removeEventListener('resize', update); }, []); // 接受 MQTT task_refresh → 触发重新轮询 useEffect(() => { const onRefresh = () => { tickRef.current += 1; setRefreshTick(tickRef.current); }; window.addEventListener('dpm:task-control', onRefresh); return () => window.removeEventListener('dpm:task-control', onRefresh); }, []); const pageSize = cols * ROWS; // 2×2=4 或 2×3=6 const pageCount = Math.max(1, Math.ceil(tasks.length / pageSize)); const [countdown, setCountdown] = useState(Math.round(ROTATE_MS / 1000)); useEffect(() => { if (pageCount <= 1) { setPage(0); return; } const t = setInterval(() => setPage((p) => (p + 1) % pageCount), ROTATE_MS); return () => clearInterval(t); }, [pageCount]); // 轮播倒计时:每秒递减,翻页时重置(页数或页变更即复位) useEffect(() => { setCountdown(Math.round(ROTATE_MS / 1000)); }, [page, pageCount]); useEffect(() => { const t = setInterval(() => setCountdown((c) => (c > 0 ? c - 1 : 0)), 1000); return () => clearInterval(t); }, []); const visible = useMemo(() => { if (tasks.length === 0) return []; const start = page * pageSize; // 不足一页时按行补位(循环取),保证 2 行填满 const items = tasks.slice(start, start + pageSize); return items.length ? items : Array.from({ length: pageSize }, (_, i) => tasks[i % tasks.length]); }, [tasks, page, pageSize]); return (
系统任务中心 扫码即可接单 · 园区企业发标 · 合伙人接单 {/* 右上角信息面板:说明 / 总数 / 轮播倒计时 / 页码 */}
总数{tasks.length}
翻页{pageCount > 1 ? `${countdown}s` : '—'}
页码{pageCount > 1 ? `${page + 1}/${pageCount}` : '1/1'}
接单方式:抢单 / 竞标 / 指派 / 推荐
{loading && tasks.length === 0 ? (
正在加载任务…
) : error && tasks.length === 0 ? (
{error}
) : ( <>
{visible.map((t) => ( ))}
{pageCount > 1 && (
{Array.from({ length: pageCount }).map((_, i) => ( ))}
)} )}
); } function TaskCard({ task }) { const qrValue = task.scan_payload || task.task_code; return (
{task.category} {task.claimed ? ( 已接单 ) : ( 可接单 )}
{task.title}
{task.summary ?

{task.summary}

: null}
{task.tags.slice(0, 4).map((tg) => ( {tg} ))}
预算 {task.budgetLabel} · {task.modeLabel}
{task.deadline && 接单截止 {task.deadline}} {task.deliveryDays > 0 && 交付时限 {task.deliveryDays} 天} {task.exclusive ? 独占接单 : task.headcount > 0 ? 限 {task.headcount} 人 : null}
任务ID · {task.publisherName ? `${task.publisherName} 发包` : '园区发包'} {task.task_code || task.id}
扫码接单
); }