fix(ui): 修复阶段 2/3 错显扫描结果,新增开始/继续/取消按钮

- 阶段 1 进行时,阶段 2/3 改为'等待阶段 1 扫描完成'占位,不再错显'已抽检 0/0 正在处理'
- 新增独立'扫描控制'区:▶ 开始扫描 /  继续扫描 / ⏸ 取消 三个按钮
- 续扫进度文件存在时才启用'继续扫描',避免误用
- start_inspection 增加 clear_progress 参数:true=全新扫描,false=续扫
- runner 在 walker 返回后增加取消检查,避免扫描被取消后仍进入抽样/抽检
- '当前文件'在扫描控制区统一显示,便于查看实时进度
This commit is contained in:
2026-06-10 16:46:27 +08:00
parent 2f92826525
commit b634d3464e
2 changed files with 153 additions and 31 deletions

View File

@@ -164,6 +164,15 @@ pub async fn run(
// 扫完清空 ETA
if let Ok(mut e) = scan_eta.lock() { e.clear(); }
// 中断检查:若用户在扫描阶段点了"取消",直接退出,避免继续进入抽样/抽检
if cancel.load(Ordering::Relaxed) {
push_log("⏹ 已取消(扫描阶段)".into());
set_state(RunState::Cancelled);
set_step(String::new());
set_current(None);
return Ok(());
}
if candidates_count == 0 {
push_log("⚠ 没有可抽检的文件,请检查扫描范围/白名单".into());
set_state(RunState::Done);

View File

@@ -12,29 +12,12 @@ use crate::ui::material;
/// 主面板
pub fn draw(ui: &mut egui::Ui, app: &mut App) {
// 1. 主控制
material::group(ui, "主控制", |ui| {
ui.horizontal(|ui| {
let can_start = matches!(app.state, RunState::Idle | RunState::Done | RunState::Cancelled | RunState::Error(_));
let can_cancel = matches!(app.state, RunState::Scanning | RunState::Sampling | RunState::Inspecting | RunState::Reporting);
if ui.add_enabled(can_start, material::primary_button("▶ 开始检测")).clicked() {
start_inspection(app);
}
if ui.add_enabled(can_cancel, material::danger_button("⏹ 取消")).clicked() {
app.cancel_flag.store(true, Ordering::Relaxed);
app.state = RunState::Cancelled;
}
if ui.button("📂 打开报告目录").clicked() {
let _ = std::fs::create_dir_all(&app.config.report.output_dir);
let _ = open_in_explorer(&app.config.report.output_dir);
}
});
});
// 0. 扫描控制(开始/继续/取消)
draw_scan_controls(ui, app);
ui.add_space(6.0);
// 2. 状态卡片
// 1. 状态卡片
material::group(ui, "当前状态", |ui| {
let state_str = match &app.state {
RunState::Idle => "● 空闲".to_string(),
@@ -65,7 +48,7 @@ pub fn draw(ui: &mut egui::Ui, app: &mut App) {
ui.add_space(6.0);
// 3. 阶段 1扫描进度
// 2. 阶段 1扫描进度
material::group(ui, "阶段 1/3扫描全盘候选文件", |ui| {
let scanned = app.scan_scanned.load(Ordering::Relaxed);
let found = app.scan_found.load(Ordering::Relaxed);
@@ -122,8 +105,18 @@ pub fn draw(ui: &mut egui::Ui, app: &mut App) {
ui.add_space(6.0);
// 4. 阶段 2/3抽样结果 + 抽检进度
// 3. 阶段 2/3抽样结果 + 抽检进度
material::group(ui, "阶段 2/3抽样结果 / 阶段 3/3抽检", |ui| {
// 阶段 1 还在进行时,阶段 2/3 信息无意义,给出占位提示
if matches!(app.state, RunState::Idle | RunState::Scanning) {
ui.horizontal(|ui| {
ui.label(egui::RichText::new("⏳ 等待阶段 1 扫描完成……")
.strong().size(14.0)
.color(material::ON_SURFACE_DIM));
});
return;
}
let p = app.progress.load(Ordering::Relaxed);
let t = app.total.load(Ordering::Relaxed);
let frac = if t == 0 { 0.0 } else { p as f32 / t as f32 };
@@ -165,14 +158,16 @@ pub fn draw(ui: &mut egui::Ui, app: &mut App) {
}
});
// 当前正在处理
if let Ok(cur) = app.current_file.lock() {
if let Some(f) = cur.as_ref() {
ui.add_space(4.0);
ui.horizontal(|ui| {
ui.label("📄 正在处理:");
ui.label(egui::RichText::new(shorten_path(f, 100)).monospace().color(material::PRIMARY_DARK));
});
// 当前正在处理(仅在阶段 2/3 时显示;阶段 1 的当前文件由上面"阶段 1"组显示)
if matches!(app.state, RunState::Sampling | RunState::Inspecting) {
if let Ok(cur) = app.current_file.lock() {
if let Some(f) = cur.as_ref() {
ui.add_space(4.0);
ui.horizontal(|ui| {
ui.label("📄 正在处理:");
ui.label(egui::RichText::new(shorten_path(f, 100)).monospace().color(material::PRIMARY_DARK));
});
}
}
}
});
@@ -251,8 +246,126 @@ fn classify_log(line: &str) -> (egui::Color32, &'static str) {
}
}
/// 扫描控制区:开始扫描 / 继续扫描 / 取消
fn draw_scan_controls(ui: &mut egui::Ui, app: &mut App) {
material::group(ui, "扫描控制", |ui| {
// 状态判断
let running = matches!(
app.state,
RunState::Scanning | RunState::Sampling | RunState::Inspecting | RunState::Reporting
);
let can_start = !running;
let can_cancel = running;
// 续扫进度文件是否存在
let pf = crate::scan::progress_store::progress_file();
let has_progress = pf.exists();
ui.horizontal(|ui| {
// ▶ 开始扫描:全新扫描(先清空旧续扫进度)
if ui
.add_enabled(can_start, material::primary_button("▶ 开始扫描"))
.on_hover_text("从头开始扫描:先清空旧续扫进度,再启动新的扫描流程")
.clicked()
{
start_inspection(app, true);
}
// ⏩ 继续扫描:使用已有续扫进度跳过已扫过的文件
if ui
.add_enabled(can_start && has_progress, material::primary_button("⏩ 继续扫描"))
.on_hover_text("使用已有的续扫进度,跳过上次已扫过的文件")
.clicked()
{
start_inspection(app, false);
}
// ⏸ 取消:中断当前正在运行的扫描/抽检
if ui
.add_enabled(can_cancel, material::danger_button("⏸ 取消"))
.on_hover_text("中断当前正在进行的扫描/抽检(可点'继续扫描'从断点恢复)")
.clicked()
{
app.cancel_flag.store(true, Ordering::Relaxed);
// 不立刻覆盖 state让后台任务在合适的检查点写 Cancelled避免 UI 与后端不一致
}
ui.separator();
if ui.button("📂 打开报告目录").clicked() {
let _ = std::fs::create_dir_all(&app.config.report.output_dir);
let _ = open_in_explorer(&app.config.report.output_dir);
}
});
// 续扫进度提示
if has_progress {
ui.add_space(4.0);
ui.horizontal(|ui| {
ui.label(
egui::RichText::new("✔ 检测到续扫进度文件")
.color(material::SUCCESS)
.size(12.0),
);
ui.label(
egui::RichText::new(format!("{}", pf.display()))
.color(material::ON_SURFACE_DIM)
.size(11.0),
);
if ui.small_button("🗑 清空进度").clicked() {
let _ = std::fs::remove_file(&pf);
}
});
} else {
ui.add_space(2.0);
ui.label(
egui::RichText::new("✖ 暂无续扫进度;点击'开始扫描'会从零开始")
.color(material::ON_SURFACE_DIM)
.size(11.0),
);
}
// 当前正在扫描/处理的文件(统一显示入口)
if running {
if let Ok(cur) = app.current_file.lock() {
if let Some(f) = cur.as_ref() {
ui.add_space(4.0);
ui.horizontal(|ui| {
ui.label(
egui::RichText::new("📄 当前文件:")
.strong()
.color(material::PRIMARY_DARK)
.size(13.0),
);
ui.label(
egui::RichText::new(shorten_path(f, 100))
.monospace()
.color(material::ON_SURFACE)
.size(12.0),
);
});
}
}
}
});
}
/// 启动后台抽检任务
fn start_inspection(app: &mut App) {
///
/// - `clear_progress`true 表示全新扫描先清空续扫进度文件false 表示续扫(保留续扫进度)
fn start_inspection(app: &mut App, clear_progress: bool) {
// 全新扫描:先删掉续扫进度文件,确保不会跳过任何文件
if clear_progress {
let pf = crate::scan::progress_store::progress_file();
if pf.exists() {
if let Err(e) = std::fs::remove_file(&pf) {
tracing::warn!("清空续扫进度失败:{}", e);
} else {
tracing::info!("已清空续扫进度:{}", pf.display());
}
}
}
app.state = RunState::Scanning;
app.progress.store(0, Ordering::Relaxed);
app.total.store(0, Ordering::Relaxed);