/** * 工作台外壳 —— 自原型 `(proto)/page.tsx` 平移(README《前端 UI · 原型 1:1 平移》)。 * * 左轨 = 品牌 → **工作台五要素**(任务 / 审批 / 消息 / 纪要 / 汇报)→ 轨底身份; * 任务四区 = 左栏列表区 / 中上标题区 / 中下工作区 / 右栏对话区。 * * **前端零过程状态**:数据全部来自服务端投影,刷新即从投影重建(§9.2)。 */ import { useState } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import type { Session, TaskCard, TaskDetail } from '@contracts'; import { listApprovalsForMeRoute, listMessagesRoute, listTasksRoute, logoutRoute, sessionRoute, taskDetailRoute, } from '@contracts'; import { TaskChat, TaskList, ViewRegion } from '@features/desk-task'; import { ApprovalDesk } from '@features/desk-approval'; import { MessageDesk } from '@features/desk-message'; import { MinuteDesk } from '@features/desk-minute'; import { ReportDesk } from '@features/desk-report'; import { Ico } from '@web-shared/Ico.tsx'; import { call } from '@web-shared/typed-fetch.ts'; import { useChatWidth } from '@web-shared/useChatWidth.ts'; import { SURFACE_META, canSeeSurface } from '@web-shared/gov/surfaces.ts'; import { queryKeys } from './query.ts'; type ModuleKey = 'tasks' | 'approvals' | 'messages' | 'minutes' | 'report'; const NAV_MODULES: ReadonlyArray<{ key: ModuleKey; label: string; icon: string }> = [ { key: 'tasks', label: '任务', icon: 'list-checks' }, { key: 'approvals', label: '审批', icon: 'file-check' }, { key: 'messages', label: '消息', icon: 'messages-square' }, { key: 'minutes', label: '纪要', icon: 'notebook-pen' }, { key: 'report', label: '汇报', icon: 'bar-chart' }, ]; export function AppShell() { const queryClient = useQueryClient(); const [module, setModule] = useState('tasks'); const [taskId, setTaskId] = useState(''); const [collapsed, setCollapsed] = useState(false); const split = useChatWidth(); const session = useQuery({ queryKey: queryKeys.session, queryFn: () => call(sessionRoute), }); const tasks = useQuery({ queryKey: queryKeys.tasks, queryFn: () => call<{ tasks: TaskCard[] }>(listTasksRoute), }); // 角标常驻:责任面要一眼看见「什么在等我」,不能等切过去才知道 const messages = useQuery({ queryKey: queryKeys.messages, queryFn: () => call<{ messages: Array<{ id: string; read: boolean }> }>(listMessagesRoute), }); const approvals = useQuery({ queryKey: ['approvals'], queryFn: () => call<{ approvals: Array<{ state: string }> }>(listApprovalsForMeRoute), }); const list = tasks.data?.tasks ?? []; const active = list.filter((t) => t.terminal === undefined); const current = taskId !== '' ? taskId : (active[0]?.id ?? list[0]?.id ?? ''); const detail = useQuery({ queryKey: queryKeys.task(current), queryFn: () => call(taskDetailRoute, { params: { taskId: current } }), enabled: current !== '', }); /** 登出后**整体清空缓存**:上一个人的任务、审批、消息不能留在下一个人眼前 */ const logout = useMutation({ mutationFn: () => call<{ ok: boolean }>(logoutRoute), onSuccess: () => { queryClient.clear(); // 整页导航:SSE 连接、组件状态、路由位置一并归零 —— 登出不留残影 window.location.assign('/'); }, }); const me = session.data?.staff.name ?? '—'; return (
{module === 'tasks' ? (
{collapsed && ( )} setCollapsed(true)} /> {detail.data !== undefined ? ( ) : (
{tasks.isLoading ? '载入中…' : '还没有落到你工位的任务'}
)} {detail.data !== undefined && ( )} {detail.data !== undefined && (
)}
) : module === 'approvals' ? ( ) : module === 'messages' ? ( ) : module === 'minutes' ? ( ) : ( )}
); }