From 2191efab2fc368a7608808a45e99d98ede4f342a Mon Sep 17 00:00:00 2001 From: Huarch Date: Thu, 24 Sep 2026 12:57:16 +0800 Subject: [PATCH] fix(workbench): hold refresh feedback --- .../components/scheduled-condition-feed.tsx | 25 +++++++++++++++---- .../browser/operational-task-approval.e2e.ts | 8 ++++++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/features/workbench/components/scheduled-condition-feed.tsx b/src/features/workbench/components/scheduled-condition-feed.tsx index 5fd65c0..ea3416b 100644 --- a/src/features/workbench/components/scheduled-condition-feed.tsx +++ b/src/features/workbench/components/scheduled-condition-feed.tsx @@ -10,7 +10,7 @@ import { XCircle } from "lucide-react"; import { AnimatePresence, motion } from "motion/react"; -import { useCallback, useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { MAP_TOOL_PANEL_SURFACE_CLASS_NAME, showMapNotice } from "@/features/map/core"; import { isGeneratedScheduledConditionSessionId } from "../data/scheduled-conditions"; import type { @@ -62,6 +62,7 @@ const CONDITION_CONTENT_SURFACE_CLASS_NAME = "surface-reading rounded-xl border border-slate-200/70"; const CONDITION_CONTROL_SURFACE_CLASS_NAME = "surface-control rounded-xl border border-slate-200/70"; +const MINIMUM_REFRESH_FEEDBACK_MS = 3_000; export type ScheduledConditionFeedPresentation = | "desktop-dock" @@ -156,6 +157,7 @@ export function ScheduledConditionFeed({ const [scheduleSubmitting, setScheduleSubmitting] = useState(false); const [scheduleError, setScheduleError] = useState(null); const [refreshing, setRefreshing] = useState(false); + const refreshingRef = useRef(false); const mobileSheet = presentation === "mobile-sheet"; const desktopDock = presentation === "desktop-dock"; const compactPresentation = mobileSheet || desktopDock; @@ -335,19 +337,32 @@ export function ScheduledConditionFeed({ } async function handleRefresh() { - if (!onRefresh || refreshing) return; + if (!onRefresh || refreshingRef.current) return; + refreshingRef.current = true; setRefreshing(true); + const startedAt = Date.now(); + let refreshError: unknown = null; try { await onRefresh(); } catch (error) { + refreshError = error; + } finally { + const remainingFeedbackMs = MINIMUM_REFRESH_FEEDBACK_MS - (Date.now() - startedAt); + if (remainingFeedbackMs > 0) { + await new Promise((resolve) => window.setTimeout(resolve, remainingFeedbackMs)); + } + refreshingRef.current = false; + setRefreshing(false); + } + if (refreshError) { showMapNotice({ id: "scheduled-condition-refresh", tone: "error", title: "工况任务刷新失败", - message: error instanceof Error ? error.message : "暂时无法重新获取工况任务。" + message: refreshError instanceof Error + ? refreshError.message + : "暂时无法重新获取工况任务。" }); - } finally { - setRefreshing(false); } } diff --git a/tests/browser/operational-task-approval.e2e.ts b/tests/browser/operational-task-approval.e2e.ts index 2da407c..f9e773a 100644 --- a/tests/browser/operational-task-approval.e2e.ts +++ b/tests/browser/operational-task-approval.e2e.ts @@ -271,8 +271,16 @@ test("审批动作展示中文参数和证据,viewer 不可审批,冲突后 await expect(conditionPanel.getByRole("button", { name: /预约任务/ })).toContainText("后端"); const conditionRefreshButton = conditionPanel.getByRole("button", { name: "刷新工况任务" }); await expect(conditionRefreshButton).toBeVisible(); + const refreshStartedAt = Date.now(); await conditionRefreshButton.click(); await expect.poll(() => snapshotRequests).toBe(2); + const refreshingButton = conditionPanel.getByRole("button", { name: "正在刷新工况任务" }); + await expect(refreshingButton).toBeDisabled(); + await refreshingButton.evaluate((button: HTMLButtonElement) => button.click()); + await page.waitForTimeout(500); + expect(snapshotRequests).toBe(2); + await expect(conditionRefreshButton).toBeEnabled({ timeout: 4_000 }); + expect(Date.now() - refreshStartedAt).toBeGreaterThanOrEqual(2_900); await conditionPanel.getByRole("button", { name: "配置定时流程" }).click(); await conditionPanel.getByLabel("定时流程名称").fill("夜间阀门巡检"); await conditionPanel.getByLabel("任务说明").fill("每十五分钟检查预约维修涉及的边界阀门。");