feat(bigscreen): 任务展示页 + 暂时关闭语音对话页 + 导航/文档同步
- App.jsx 移除 /voice、新增 /tasks→TaskDisplay。 - pageNav PAGE_ORDER 移除 voice、加 tasks(方向键/页眉/MQTT navigate_rel 自动跟随)。 - useMqttControl:PAGE_ALIAS 加 tasks;移除 voice_*、改 task_refresh。 - PageHeader STATUS_BY_PATH 加 /tasks。 - 新增 TaskDisplay.jsx + task-display.css:轮播卡(分类/状态/标题/摘要/标签/预算/任务ID+QRCodeSVG 扫码接单)、useTasks 轮询 /park/api/tasks、接受 MQTT task_refresh。 - 新增 utils/taskData.js(useTasks, tenantFetch 带租户身份)。 - docs/mqtt-commands.md:navigate 列表/语音章节标注语音页关闭、tasks 页指令。 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
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行)、标签、预算、任务ID + 二维码(扫码即领)
|
||||
· 轮播:每页若干张,自动翻页;MQTT task_refresh 可触发刷新 */
|
||||
|
||||
const PAGE_SIZE = 3;
|
||||
const ROTATE_MS = 14_000;
|
||||
|
||||
export default function TaskDisplay() {
|
||||
const { tasks, loading, error } = useTasks();
|
||||
const [page, setPage] = useState(0);
|
||||
const [refreshTick, setRefreshTick] = useState(0);
|
||||
const tickRef = useRef(0);
|
||||
|
||||
// 接受 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 pageCount = Math.max(1, Math.ceil(tasks.length / PAGE_SIZE));
|
||||
useEffect(() => {
|
||||
if (pageCount <= 1) { setPage(0); return; }
|
||||
const t = setInterval(() => setPage((p) => (p + 1) % pageCount), ROTATE_MS);
|
||||
return () => clearInterval(t);
|
||||
}, [pageCount]);
|
||||
|
||||
const visible = useMemo(() => {
|
||||
if (tasks.length === 0) return [];
|
||||
const start = page * PAGE_SIZE;
|
||||
return Array.from({ length: PAGE_SIZE }, (_, i) => tasks[(start + i) % tasks.length]);
|
||||
}, [tasks, page]);
|
||||
|
||||
return (
|
||||
<div className="tasks-screen">
|
||||
<div className="tasks-head">
|
||||
<span className="tasks-head-dot" />
|
||||
<span className="tasks-head-title">系统任务中心</span>
|
||||
<span className="tasks-head-sub">扫码即可接单 · 园区企业发标 · 合伙人接单</span>
|
||||
<span className="tasks-head-count">{tasks.length} 个任务</span>
|
||||
</div>
|
||||
|
||||
{loading && tasks.length === 0 ? (
|
||||
<div className="tasks-loading">正在加载任务…</div>
|
||||
) : error && tasks.length === 0 ? (
|
||||
<div className="tasks-loading">{error}</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="tasks-grid">
|
||||
{visible.map((t) => (
|
||||
<TaskCard key={t.key} task={t} />
|
||||
))}
|
||||
</div>
|
||||
{pageCount > 1 && (
|
||||
<div className="tasks-dots">
|
||||
{Array.from({ length: pageCount }).map((_, i) => (
|
||||
<span key={i} className={i === page ? 'tasks-dot on' : 'tasks-dot'} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TaskCard({ task }) {
|
||||
const qrValue = task.scan_payload || task.task_code;
|
||||
return (
|
||||
<div className={task.claimed ? 'task-card claimed' : 'task-card'}>
|
||||
<div className="task-top">
|
||||
<span className="task-cat">{task.category}</span>
|
||||
{task.claimed ? (
|
||||
<span className="task-status taken">已接单</span>
|
||||
) : (
|
||||
<span className="task-status open">可接单</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="task-title">{task.title}</div>
|
||||
{task.summary ? <p className="task-summary">{task.summary}</p> : null}
|
||||
|
||||
<div className="task-tags">
|
||||
{task.tags.slice(0, 4).map((tg) => (
|
||||
<span key={tg} className="task-tag">{tg}</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="task-budget">预算 {task.budgetLabel}</div>
|
||||
|
||||
<div className="task-foot">
|
||||
<div className="task-meta">
|
||||
<span className="task-id-label">任务ID</span>
|
||||
<span className="task-id">{task.task_code || task.id}</span>
|
||||
</div>
|
||||
<div className="task-qr">
|
||||
<QRCodeSVG value={qrValue || 'TASK'} size={72} fgColor="#1d2c44" level="M" />
|
||||
<span className="task-qr-hint">扫码接单</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user