Files
insar-management-system-v2/docs/archive/FIX_FRONTEND_LOCKING_2026-03-05.md
T

7.0 KiB
Raw Blame History

前端锁定修复总结(2026-03-05)

修复概述

按照系统设计理念(管理员操作时全局锁定),修复了两个前端锁定问题:

  1. 数据分发任务锁定P0 高优先级)
  2. 扫描任务锁定P1 中优先级)

修复 1:数据分发任务锁定

问题

DataCopierPanel 是独立的任务管理系统,不与全局锁定机制集成。

修改文件

  1. frontend/src/App.jsx
  2. frontend/src/DataCopierPanel.jsx

修改内容

App.jsx (1334 行)

// 修改前
<DataCopierPanel
    apiEndpoint={apiClient.defaults.baseURL}
    readOnly={isReadOnlyUser}
/>

// 修改后
<DataCopierPanel
    apiEndpoint={apiClient.defaults.baseURL}
    readOnly={isReadOnlyUser}
    onJobQueued={(taskId) => handleTaskStart(taskId, '数据分发任务已入队,正在处理...')}
/>

DataCopierPanel.jsx (14 行)

// 修改前
const DataCopierPanel = ({ apiEndpoint, readOnly = false }) => {

// 修改后
const DataCopierPanel = ({ apiEndpoint, readOnly = false, onJobQueued }) => {

DataCopierPanel.jsx (119-131 行)

// 修改前
try {
    const response = await axios.post(endpoint, {
        batch_id: selectedBatchId,
        dest_dir: destDir,
        copy_statuses: copyStatuses,
    }, { withCredentials: true });
    setTaskId(response.data.task_id);
} catch (error) {

// 修改后
try {
    const response = await axios.post(endpoint, {
        batch_id: selectedBatchId,
        dest_dir: destDir,
        copy_statuses: copyStatuses,
    }, { withCredentials: true });
    const taskId = response.data.task_id;
    setTaskId(taskId);

    // 触发全局锁定
    if (onJobQueued) {
        onJobQueued(taskId);
    }
} catch (error) {

效果

  • 数据分发任务启动时,前端立即锁定
  • 任务完成后,前端自动解锁
  • 与其他任务(IDL 自动化、数据监控)行为一致

修复 2:扫描任务锁定

问题

扫描任务执行太快(< 3 秒),前端轮询间隔(3 秒)来不及捕获 RUNNING 状态。

修改文件

frontend/src/DataMonitorPanel.jsx

修改内容

DataMonitorPanel.jsx (168-198 行)

// 修改前
const handleRunNow = async (target) => {
    // ... 验证逻辑 ...
    setLoading(true);
    setMessage(`正在触发${targetMap[target] || '全部'}手动扫描...`);
    try {
        const url = target ? `${apiEndpoint}/monitor/run-now?target=${target}` : `${apiEndpoint}/monitor/run-now`;
        const res = await fetch(url, {
            method: 'POST',
            credentials: 'include'
        });
        const data = await res.json();
        if (res.ok) {
            setMessage(data.message);
            if (onTaskStart) onTaskStart(data.task_id, `已触发${targetMap[target] || '全部'}手动扫描...`);
        }
        // ...
    }
};

// 修改后
const handleRunNow = async (target) => {
    // ... 验证逻辑 ...
    setLoading(true);
    setMessage(`正在触发${targetMap[target] || '全部'}手动扫描...`);

    // 立即触发全局锁定(在 API 调用之前)
    if (onTaskStart) {
        onTaskStart(null, `正在触发${targetMap[target] || '全部'}手动扫描...`);
    }

    try {
        const url = target ? `${apiEndpoint}/monitor/run-now?target=${target}` : `${apiEndpoint}/monitor/run-now`;
        const res = await fetch(url, {
            method: 'POST',
            credentials: 'include'
        });
        const data = await res.json();
        if (res.ok) {
            setMessage(data.message);
            // 更新任务 ID
            if (onTaskStart) onTaskStart(data.task_id, `已触发${targetMap[target] || '全部'}手动扫描...`);
        }
        // ...
    }
};

关键改进

  1. 在 API 调用之前立即锁定onTaskStart(null, message)
  2. API 成功后更新任务 IDonTaskStart(taskId, message)
  3. 失败时自动解锁:全局轮询检测到没有活跃任务时自动解锁

效果

  • 用户点击"扫描"按钮后,前端立即锁定
  • 即使任务执行很快(< 3 秒),用户也能看到锁定状态
  • 任务完成后,前端自动解锁

测试验证

测试 1:数据分发任务锁定

  1. 登录管理员账号
  2. 进入"数据分发" Tab
  3. 选择批次和目标目录
  4. 点击"开始复制"
  5. 预期:前端立即锁定,显示"数据分发任务已入队,正在处理..."
  6. 等待任务完成
  7. 预期:前端自动解锁

测试 2:扫描任务锁定

  1. 登录管理员账号
  2. 进入"数据管理" Tab
  3. 点击"扫描雷达数据"或"扫描 D-InSAR 结果"
  4. 预期:前端立即锁定,显示"正在触发...手动扫描..."
  5. 等待任务完成(可能很快)
  6. 预期:前端自动解锁

测试 3:多任务冲突

  1. 启动数据分发任务
  2. 预期:前端锁定,无法启动其他任务
  3. 尝试点击其他操作按钮
  4. 预期:按钮被禁用或操作被阻止

技术细节

handleTaskStart 函数行为

// hooks/useDinsarOperations.js:123-129
const handleTaskStart = (taskId, message) => {
    if (taskId) {
        setPendingTaskIds(prev => [...prev, taskId]);
    }
    setIsGlobalLocked(true);  // 总是锁定
    if (message) addLog('info', message);
};

关键点

  • taskId 可以为 null(立即锁定,稍后更新 ID
  • setIsGlobalLocked(true) 总是执行(无论 taskId 是否为 null
  • 这允许我们在 API 调用之前就锁定前端

解锁机制

前端通过 useGlobalTaskControl hook 自动解锁:

// hooks/useGlobalTaskControl.js:56-64
if (hasRunningTasks !== isGlobalLockedRef.current) {
    setIsGlobalLocked(hasRunningTasks);
    if (!hasRunningTasks) {
        addLog('success', '后台任务已完成,正在同步最新数据...');
        setTimeout(() => {
            initializeAppDataRef.current?.({ refreshRadarSearch: true });
        }, 500);
    }
}

工作原理

  1. 每 3 秒轮询 /tasks/active
  2. 检测到没有活跃任务时,自动解锁
  3. 不需要手动调用解锁

修改总结

修改文件

  1. frontend/src/App.jsx - 1 处修改(添加 onJobQueued 回调)
  2. frontend/src/DataCopierPanel.jsx - 2 处修改(接收回调 + 调用回调)
  3. frontend/src/DataMonitorPanel.jsx - 1 处修改(提前锁定)

代码行数

  • 新增:约 10 行
  • 修改:约 5 行
  • 删除:0 行

风险评估

  • 🟢 低风险:改动小,逻辑清晰
  • 🟢 向后兼容:不影响现有功能
  • 🟢 易于回滚:修改集中,容易撤销

相关文档

  • ANALYSIS_FRONTEND_LOCKING_2026-03-05.md - 问题分析
  • SECURITY_FIX_PROGRESS.md - 修复进度跟踪
  • HOTFIX_PROJ_CONFLICT_2026-03-05.md - PROJ 冲突修复

下一步

  1. 代码修改完成
  2. 前端构建测试
  3. 功能测试验证
  4. 用户验收测试

修复完成时间2026-03-05 修复人Claude Opus 4.6 状态 代码修改完成,等待测试验证