36 lines
1.4 KiB
TypeScript
36 lines
1.4 KiB
TypeScript
// 不自动运行(依赖 react,需要在 React 项目中使用)
|
|
// 用 useReducer 管理任务状态,用 useEffect 订阅服务端事件,并在卸载 / 任务变化时清理。
|
|
import { useEffect, useReducer } from "react";
|
|
import { initialState, taskReducer, type TaskEvent } from "./taskReducer.ts";
|
|
|
|
export function TaskPanel({ taskId, subscribe }: {
|
|
taskId: string;
|
|
subscribe: (id: string, onEvent: (e: TaskEvent) => void) => () => void; // 返回取消订阅函数
|
|
}) {
|
|
const [state, dispatch] = useReducer(taskReducer, initialState);
|
|
|
|
useEffect(() => {
|
|
dispatch({ type: "submitted", id: taskId });
|
|
const unsubscribe = subscribe(taskId, dispatch); // dispatch 引用稳定,不需要放进依赖数组
|
|
return unsubscribe; // 清理:taskId 变化或组件卸载时取消订阅,避免旧任务的事件串进来
|
|
}, [taskId, subscribe]);
|
|
|
|
return (
|
|
<section aria-live="polite">
|
|
<p>状态:{state.status}</p>
|
|
<ol>
|
|
{state.steps.map((s) => (
|
|
<li key={s.name}>{s.done ? "✓" : "…"} {s.name}</li>
|
|
))}
|
|
</ol>
|
|
{state.status === "failed" && (
|
|
<p role="alert">
|
|
{state.error} <button onClick={() => dispatch({ type: "retry" })}>重试</button>
|
|
</p>
|
|
)}
|
|
{state.result && <a href={state.result.url}>{state.result.title}</a>}
|
|
{state.status === "running" && <button onClick={() => dispatch({ type: "cancel" })}>取消</button>}
|
|
</section>
|
|
);
|
|
}
|