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

266 lines
7.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 前端锁定修复总结(2026-03-05
## 修复概述
按照系统设计理念(管理员操作时全局锁定),修复了两个前端锁定问题:
1.**数据分发任务锁定**P0 高优先级)
2.**扫描任务锁定**P1 中优先级)
---
## 修复 1:数据分发任务锁定
### 问题
`DataCopierPanel` 是独立的任务管理系统,不与全局锁定机制集成。
### 修改文件
1. `frontend/src/App.jsx`
2. `frontend/src/DataCopierPanel.jsx`
### 修改内容
**App.jsx (1334 行)**
```javascript
// 修改前
<DataCopierPanel
apiEndpoint={apiClient.defaults.baseURL}
readOnly={isReadOnlyUser}
/>
// 修改后
<DataCopierPanel
apiEndpoint={apiClient.defaults.baseURL}
readOnly={isReadOnlyUser}
onJobQueued={(taskId) => handleTaskStart(taskId, '数据分发任务已入队,正在处理...')}
/>
```
**DataCopierPanel.jsx (14 行)**
```javascript
// 修改前
const DataCopierPanel = ({ apiEndpoint, readOnly = false }) => {
// 修改后
const DataCopierPanel = ({ apiEndpoint, readOnly = false, onJobQueued }) => {
```
**DataCopierPanel.jsx (119-131 行)**
```javascript
// 修改前
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 行)**
```javascript
// 修改前
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 成功后更新任务 ID**`onTaskStart(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 函数行为
```javascript
// 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 自动解锁:
```javascript
// 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
**状态**:✅ 代码修改完成,等待测试验证