统一导出历史文件操作

This commit is contained in:
张家训
2026-07-31 19:39:00 +08:00
parent 9c82c8f638
commit c029b367f6
8 changed files with 336 additions and 170 deletions
@@ -10,7 +10,6 @@ import com.example.workutils.biz.apidoc.parser.JavaSourceParser;
import com.example.workutils.system.config.WorkUtilsProperties;
import com.example.workutils.system.support.FileOperationHelper;
import com.example.workutils.system.support.HistoryPersistence;
import com.example.workutils.system.support.SystemOpenHelper;
import com.example.workutils.system.ui.StyledButton;
import com.example.workutils.system.ui.Theme;
import com.example.workutils.system.support.AppExecutors;
@@ -21,6 +20,9 @@ import com.example.workutils.system.ui.async.OperationStatusPane;
import com.example.workutils.system.ui.history.ExportHistoryPane;
import com.example.workutils.system.ui.history.HistoryFileCommands;
import com.example.workutils.system.ui.history.HistoryItem;
import com.example.workutils.system.ui.history.ResultFileActions;
import com.example.workutils.system.ui.history.SystemFileActionGateway;
import com.example.workutils.system.ui.notification.FxNotificationCenter;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
@@ -58,6 +60,7 @@ public class ApiDocWindow {
private final ApiBaseClassManager apiBaseClassManager;
private final RunApiDocExportUseCase exportUseCase;
private final AsyncOperationController<RunApiDocExportUseCase.Result> exportOperation;
private final HistoryFileCommands historyFileCommands;
// 模式选择
private ToggleGroup modeGroup;
@@ -107,6 +110,11 @@ public class ApiDocWindow {
historyItems.setAll(historyRows.stream()
.map(ApiDocWindow::toHistoryItem).toList()));
this.stage = new Stage();
this.historyFileCommands = new ResultFileActions(
new SystemFileActionGateway(),
new FxNotificationCenter(stage),
message -> log("[INFO] " + message))
.commands();
buildUI();
loadProjects();
loadHistory();
@@ -285,7 +293,7 @@ public class ApiDocWindow {
ExportHistoryPane historyPane = new ExportHistoryPane(
"导出格式", "文件数", historyItems,
historyFileCommands(), fsBody());
historyFileCommands, fsBody());
historyPane.setPrefHeight(210);
historyWrapper.getChildren().add(historyPane);
tabHistory.setContent(historyWrapper);
@@ -760,13 +768,13 @@ public class ApiDocWindow {
+ "-fx-background-radius: " + Theme.RADIUS_SM + "; -fx-padding: 2 6;"));
openBtn.setOnAction(e -> {
openFile(currentFileName);
historyFileCommands.openFile(currentFileName);
});
dirBtn.setOnAction(e -> {
openFolder(currentFileName);
historyFileCommands.openFolder(currentFileName);
});
copyBtn.setOnAction(e -> {
copyFilePath(currentFileName);
historyFileCommands.copyPath(currentFileName);
});
delBtn.setOnAction(e -> {
if (currentRow != null) {
@@ -793,63 +801,10 @@ public class ApiDocWindow {
}
}
private void copyFilePath(String path) {
if (path == null) return;
javafx.scene.input.Clipboard.getSystemClipboard()
.setContent(new javafx.scene.input.ClipboardContent() {{
putString(path);
}});
log("[INFO] 已复制路径: " + path);
}
};
return cell;
}
private HistoryFileCommands historyFileCommands() {
return new HistoryFileCommands(
this::openFile,
this::openWithNotepad,
this::openFolder,
path -> {
javafx.scene.input.ClipboardContent content =
new javafx.scene.input.ClipboardContent();
content.putString(path);
javafx.scene.input.Clipboard.getSystemClipboard().setContent(content);
log("[INFO] 已复制路径: " + path);
});
}
// ==================== 文件打开工具方法 ====================
private void openFile(String path) {
if (path == null) return;
try {
File f = new File(path);
if (!f.exists()) return;
SystemOpenHelper.openFile(f.toPath());
} catch (Exception ignored) {
}
}
private void openWithNotepad(String path) {
if (path == null) return;
try {
SystemOpenHelper.editFile(path);
} catch (Exception e) {
openFile(path);
}
}
private void openFolder(String path) {
if (path == null) return;
try {
File f = new File(path).getParentFile();
if (f == null || !f.exists()) return;
SystemOpenHelper.revealInFileManager(path);
} catch (Exception ignored) {
}
}
// ==================== 数据行 ====================
static HistoryItem toHistoryItem(HistoryRow row) {
@@ -6,7 +6,6 @@ import com.example.workutils.biz.mybatisexport.service.MyBatisExportService;
import com.example.workutils.system.config.WorkUtilsProperties;
import com.example.workutils.system.support.AppExecutors;
import com.example.workutils.system.support.HistoryPersistence;
import com.example.workutils.system.support.SystemOpenHelper;
import com.example.workutils.system.ui.StyledButton;
import com.example.workutils.system.ui.Theme;
import com.example.workutils.system.ui.async.AsyncOperationController;
@@ -15,6 +14,9 @@ import com.example.workutils.system.ui.async.OperationStatusPane;
import com.example.workutils.system.ui.history.ExportHistoryPane;
import com.example.workutils.system.ui.history.HistoryFileCommands;
import com.example.workutils.system.ui.history.HistoryItem;
import com.example.workutils.system.ui.history.ResultFileActions;
import com.example.workutils.system.ui.history.SystemFileActionGateway;
import com.example.workutils.system.ui.notification.FxNotificationCenter;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
@@ -26,7 +28,6 @@ import javafx.scene.layout.*;
import javafx.scene.Parent;
import javafx.stage.Stage;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
@@ -46,6 +47,7 @@ public class MyBatisExportWindow {
private final WorkUtilsProperties properties;
private final RunSqlExportUseCase runExportUseCase;
private final AsyncOperationController<MyBatisExportService.ExportResult> exportOperation;
private final HistoryFileCommands historyFileCommands;
// 过滤
private ComboBox<String> filterModeCmb;
@@ -81,6 +83,11 @@ public class MyBatisExportWindow {
historyItems.setAll(historyRows.stream()
.map(MyBatisExportWindow::toHistoryItem).toList()));
this.stage = new Stage();
this.historyFileCommands = new ResultFileActions(
new SystemFileActionGateway(),
new FxNotificationCenter(stage),
message -> logArea.appendText("[INFO] " + message + "\n"))
.commands();
buildUI();
loadHistory();
}
@@ -174,25 +181,12 @@ public class MyBatisExportWindow {
tableTitle.setTextFill(Theme.TEXT_PRIMARY);
ExportHistoryPane historyPane = new ExportHistoryPane(
"导出数", historyItems, historyFileCommands(), fsBody());
"导出数", historyItems, historyFileCommands, fsBody());
VBox.setVgrow(historyPane, Priority.ALWAYS);
root.getChildren().addAll(filterRow, progressRow, logTitle, logArea, tableTitle, historyPane);
stage.setScene(new Scene(root));
}
private HistoryFileCommands historyFileCommands() {
return new HistoryFileCommands(
this::openFile,
this::openWithNotepad,
this::openFolder,
path -> {
javafx.scene.input.ClipboardContent content =
new javafx.scene.input.ClipboardContent();
content.putString(path);
javafx.scene.input.Clipboard.getSystemClipboard().setContent(content);
});
}
// ==================== 历史持久化 ====================
/**
@@ -275,37 +269,6 @@ public class MyBatisExportWindow {
saveHistory();
}
// ==================== 文件打开工具方法 ====================
private void openFile(String path) {
if (path == null) return;
try {
File f = new File(path);
if (!f.exists()) return;
SystemOpenHelper.openFile(f.toPath());
} catch (Exception ignored) {
}
}
private void openWithNotepad(String path) {
if (path == null) return;
try {
SystemOpenHelper.editFile(path);
} catch (Exception e) {
openFile(path);
}
}
private void openFolder(String path) {
if (path == null) return;
try {
File f = new File(path).getParentFile();
if (f == null || !f.exists()) return;
SystemOpenHelper.revealInFileManager(path);
} catch (Exception ignored) {
}
}
// ==================== 数据行 ====================
static HistoryItem toHistoryItem(HistoryRow row) {
@@ -8,7 +8,6 @@ import com.example.workutils.system.support.AppExecutors;
import com.example.workutils.system.support.FileOperationHelper;
import com.example.workutils.system.support.HistoryPersistence;
import com.example.workutils.system.support.OutputHelper;
import com.example.workutils.system.support.SystemOpenHelper;
import com.example.workutils.system.ui.StyledButton;
import com.example.workutils.system.ui.Theme;
import com.example.workutils.system.ui.async.AsyncOperationController;
@@ -17,6 +16,9 @@ import com.example.workutils.system.ui.async.OperationStatusPane;
import com.example.workutils.system.ui.history.ExportHistoryPane;
import com.example.workutils.system.ui.history.HistoryFileCommands;
import com.example.workutils.system.ui.history.HistoryItem;
import com.example.workutils.system.ui.history.ResultFileActions;
import com.example.workutils.system.ui.history.SystemFileActionGateway;
import com.example.workutils.system.ui.notification.FxNotificationCenter;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
@@ -28,7 +30,6 @@ import javafx.scene.layout.*;
import javafx.scene.Parent;
import javafx.stage.Stage;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
@@ -49,6 +50,7 @@ public class ScannerWindow {
private final OutputHelper outputHelper;
private final RunScanUseCase runScanUseCase;
private final AsyncOperationController<RunScanUseCase.Result> scanOperation;
private final HistoryFileCommands historyFileCommands;
// UI
private CheckBox regexCb;
@@ -79,6 +81,11 @@ public class ScannerWindow {
historyItems.setAll(historyRows.stream()
.map(ScannerWindow::toHistoryItem).toList()));
this.stage = new Stage();
this.historyFileCommands = new ResultFileActions(
new SystemFileActionGateway(),
new FxNotificationCenter(stage),
message -> logArea.appendText("[INFO] " + message + "\n"))
.commands();
buildUI();
loadHistory();
}
@@ -158,7 +165,7 @@ public class ScannerWindow {
tableTitle.setTextFill(Theme.TEXT_PRIMARY);
ExportHistoryPane historyPane = new ExportHistoryPane(
"匹配数", historyItems, historyFileCommands(), fsBody());
"匹配数", historyItems, historyFileCommands, fsBody());
VBox.setVgrow(historyPane, Priority.ALWAYS);
// === 日志区 ===
@@ -172,20 +179,6 @@ public class ScannerWindow {
stage.setScene(new Scene(root));
}
private HistoryFileCommands historyFileCommands() {
return new HistoryFileCommands(
this::openFile,
this::openWithNotepad,
this::openFolder,
path -> {
javafx.scene.input.ClipboardContent content =
new javafx.scene.input.ClipboardContent();
content.putString(path);
javafx.scene.input.Clipboard.getSystemClipboard().setContent(content);
logArea.appendText("[INFO] 已复制路径: " + path + "\n");
});
}
// ==================== 历史持久化 ====================
/**
@@ -253,54 +246,6 @@ public class ScannerWindow {
logArea.appendText("已导出: " + result.fileName() + "\n");
}
// ==================== 文件打开工具方法 ====================
private void openFile(String path) {
if (path == null) {
logArea.appendText("[WARN] 路径为空\n");
return;
}
try {
File f = new File(path);
if (!f.exists()) {
logArea.appendText("[WARN] 文件不存在: " + path + "\n");
return;
}
// Delegate platform-specific open behavior to SystemOpenHelper.
SystemOpenHelper.openFile(f.toPath());
logArea.appendText("[OK] 已打开: " + path + "\n");
} catch (Exception e) {
logArea.appendText("[ERROR] 打开失败: " + (e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName()) + "\n");
}
}
private void openWithNotepad(String path) {
try {
SystemOpenHelper.editFile(path);
logArea.appendText("[OK] 用系统编辑器打开: " + path + "\n");
} catch (Exception e) {
openFile(path); // fallback
}
}
private void openFolder(String path) {
if (path == null) {
logArea.appendText("[WARN] 路径为空\n");
return;
}
try {
File f = new File(path).getParentFile();
if (f == null || !f.exists()) {
logArea.appendText("[WARN] 目录不存在: " + path + "\n");
return;
}
SystemOpenHelper.revealInFileManager(path);
logArea.appendText("[OK] 已打开目录: " + f.getAbsolutePath() + "\n");
} catch (Exception e) {
logArea.appendText("[ERROR] 打开目录失败: " + (e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName()) + "\n");
}
}
// ==================== 数据行 ====================
static HistoryItem toHistoryItem(HistoryRow row) {
@@ -0,0 +1,15 @@
package com.example.workutils.system.ui.history;
import java.io.IOException;
import java.nio.file.Path;
public interface FileActionGateway {
void open(Path path) throws IOException;
void edit(Path path) throws IOException;
void reveal(Path path) throws IOException;
void copyPath(String path);
}
@@ -0,0 +1,90 @@
package com.example.workutils.system.ui.history;
import com.example.workutils.system.ui.notification.NotificationCenter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.Objects;
import java.util.function.Consumer;
public final class ResultFileActions {
private final FileActionGateway gateway;
private final NotificationCenter notifications;
private final Consumer<String> successFeedback;
public ResultFileActions(
FileActionGateway gateway,
NotificationCenter notifications,
Consumer<String> successFeedback) {
this.gateway = Objects.requireNonNull(gateway, "gateway");
this.notifications = Objects.requireNonNull(notifications, "notifications");
this.successFeedback = Objects.requireNonNull(successFeedback, "successFeedback");
}
public HistoryFileCommands commands() {
return new HistoryFileCommands(
path -> performExisting(path, "打开文件", gateway::open),
path -> performExisting(path, "编辑文件", gateway::edit),
path -> performExisting(path, "打开目录", gateway::reveal),
this::copyPath);
}
private void performExisting(String value, String action, PathAction operation) {
Path path = parsePath(value);
if (path == null) {
return;
}
if (!Files.isRegularFile(path)) {
notifications.warning("文件操作失败", "文件不存在");
return;
}
try {
operation.accept(path);
} catch (IOException | RuntimeException error) {
notifyFailure(action, error);
}
}
private void copyPath(String value) {
Path path = parsePath(value);
if (path == null) {
return;
}
String normalized = path.toString();
try {
gateway.copyPath(normalized);
successFeedback.accept(Files.exists(path)
? "已复制路径: " + normalized
: "已复制路径(文件已不存在): " + normalized);
} catch (RuntimeException error) {
notifyFailure("复制路径", error);
}
}
private Path parsePath(String value) {
if (value == null || value.isBlank()) {
notifications.warning("文件操作失败", "文件路径不能为空");
return null;
}
try {
return Path.of(value.trim()).toAbsolutePath().normalize();
} catch (InvalidPathException error) {
notifyFailure("解析路径", error);
return null;
}
}
private void notifyFailure(String action, Exception error) {
String detail = error.getMessage();
notifications.error("文件操作失败", action + "失败"
+ (detail == null || detail.isBlank() ? "" : ": " + detail));
}
@FunctionalInterface
private interface PathAction {
void accept(Path path) throws IOException;
}
}
@@ -0,0 +1,33 @@
package com.example.workutils.system.ui.history;
import com.example.workutils.system.support.SystemOpenHelper;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import java.io.IOException;
import java.nio.file.Path;
public final class SystemFileActionGateway implements FileActionGateway {
@Override
public void open(Path path) throws IOException {
SystemOpenHelper.openFile(path);
}
@Override
public void edit(Path path) throws IOException {
SystemOpenHelper.editFile(path);
}
@Override
public void reveal(Path path) throws IOException {
SystemOpenHelper.revealInFileManager(path);
}
@Override
public void copyPath(String path) {
ClipboardContent content = new ClipboardContent();
content.putString(path);
Clipboard.getSystemClipboard().setContent(content);
}
}