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:
Pine
2026-08-25 18:42:32 +08:00
parent 1566477b97
commit d61b59acf5
13 changed files with 441 additions and 41 deletions
+20 -15
View File
@@ -1,8 +1,8 @@
import { useEffect } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { onMqttMessage, connectMqtt, publishMqtt, getClientId, useMqttStatus } from './mqtt';
import { MQTT_TOPIC_HEARTBEAT, MQTT_TOPIC_COMMAND, getApiBase as API_BASE } from '../config';
import { getTenantId } from './tenant';
import { MQTT_TOPIC_HEARTBEAT, MQTT_TOPIC_COMMAND, MQTT_TOPIC_BIND, getApiBase as API_BASE } from '../config';
import { getTenantId, clearBinding } from './tenant';
import { Sfx } from './sounds';
import { PAGE_ORDER } from './pageNav';
import { aiStatus } from './statusBus';
@@ -31,6 +31,7 @@ const PAGE_ALIAS = {
home: '/', '数据大屏': '/', '首页': '/',
twin: '/twin', '数字孪生': '/twin', '3d': '/twin',
ai: '/ai', 'ai助手': '/ai',
tasks: '/tasks', '任务展示': '/tasks', task: '/tasks',
screen: '/screen', '媒体轮播': '/screen', media: '/screen',
wall: '/wall', '企业展示墙': '/wall', '企业墙': '/wall', '展示墙': '/wall',
};
@@ -61,6 +62,19 @@ export function useMqttControl() {
}).catch(() => {});
}, [mqttStatus]);
// 后端主动解绑:监听绑定频道,收到 unbound 事件 → 清理本地绑定并退出到待绑定页
// (大屏已绑定后仍订阅 bind/<clientId>,见 mqtt.js subscribe;unbind 后 main.jsx 见
// isDeviceBound()=false 渲染 BindScreen)
useEffect(() => {
connectMqtt();
const off = onMqttMessage((topic, data) => {
if (topic === `${MQTT_TOPIC_BIND}/${getClientId()}` && data && data.event === 'unbound') {
clearBinding();
}
});
return off;
}, []);
// 大屏在线心跳(后端据此统计在线大屏数)
useEffect(() => {
connectMqtt();
@@ -170,19 +184,10 @@ export function useMqttControl() {
}));
break;
// ── 语音对话页操作 ──
case 'voice_start':
// admin 主动问候测试:携带 text;若不在 /voice 页先跳转,文本暂存 sessionStorage
if (cmd.params?.text) sessionStorage.setItem('dpm_voice_greet', cmd.params.text);
if (location.pathname !== '/voice') navigate('/voice');
window.dispatchEvent(new CustomEvent('dpm:voice-control', {
detail: { action: 'start', text: cmd.params?.text || '' },
}));
break;
case 'voice_stop':
case 'voice_refresh':
window.dispatchEvent(new CustomEvent('dpm:voice-control', {
detail: { action: cmd.action.replace('voice_', '') },
// ── 任务展示页操作(语音页已暂时关闭,voice_* 指令不再处理) ──
case 'task_refresh':
window.dispatchEvent(new CustomEvent('dpm:task-control', {
detail: { action: 'refresh', ...(cmd.params || {}) },
}));
break;