更新版本:

1. 修复对话框图标显示
2. 文件名生成
This commit is contained in:
叁玖领域 2025-11-02 16:03:55 +08:00
parent 0f54983b30
commit 98796bf123
11 changed files with 241 additions and 18 deletions

View File

@ -1,3 +1,3 @@
project_group =top.r3944realms.docchecktoolrefacored
project_name = doc-check-tool
project_version = 1.0.0.3
project_version = 1.0.0.5

View File

@ -12,7 +12,7 @@ public class JavaFxApplication extends Application {
@Override
public void init() throws Exception {
super.init();
System.setVersion("1.0.0.4");
System.setVersion("1.0.0.5");
}
@Override

View File

@ -3,8 +3,10 @@ package top.r3944realms.docchecktoolrefactored;
import javafx.stage.DirectoryChooser;
import javafx.stage.FileChooser;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import top.r3944realms.docchecktoolrefactored.core.Setting;
import top.r3944realms.docchecktoolrefactored.ui.module.ProjectInfoPaneController;
import top.r3944realms.docchecktoolrefactored.util.LoggerMarker;
import java.io.*;
@ -29,6 +31,8 @@ public enum System {
private static final long DEFAULT_SINGLE_TIMEOUT = 30;
private static final long DEFAULT_TOTAL_TIMEOUT = 12600;
private static final boolean DEFAULT_ENABLE_STEP = false;
@Getter
private static final ProjectInfoPaneController.ProjectInfo projectInfo = new ProjectInfoPaneController.ProjectInfo();
public static void init() {
loadSettings();

View File

@ -3,19 +3,29 @@ package top.r3944realms.docchecktoolrefactored.ui;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.control.Button;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.KeyEvent;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.docchecktoolrefactored.System;
import top.r3944realms.docchecktoolrefactored.core.Setting;
import top.r3944realms.docchecktoolrefactored.ui.utils.DialogUtil;
import top.r3944realms.docchecktoolrefactored.util.LoggerMarker;
import java.awt.*;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;
@ -161,14 +171,89 @@ public class MainStageController {
@FXML
void onOpenHelpDoc(ActionEvent actionEvent) {
DialogUtil.showDetailedInformationDialog("帮助文档", "操作手册","见.exe程序同文件夹下的“操作手册。docx”文件。");
try {
// 获取帮助文档文件路径如果不存在则从资源中释放
File helpFile = getOrExtractHelpDocument();
if (helpFile.exists()) {
// 打开文档
openDocument(helpFile);
} else {
DialogUtil.showErrorDialog("打开失败", "帮助文档不存在",
"无法找到帮助文档,请联系技术支持。");
}
} catch (Exception e) {
DialogUtil.showDetailedErrorDialog("打开失败", "无法打开帮助文档",
"错误信息:" + e.getMessage());
}
}
/**
* 获取或从资源中提取帮助文档
*/
private File getOrExtractHelpDocument() throws IOException {
// 获取程序运行目录
String appDir = java.lang.System.getProperty("user.dir");
File helpFile = new File(appDir, HELP_DOC_FILE_NAME);
// 如果文件不存在则从资源中复制出来
if (!helpFile.exists()) {
try (InputStream resourceStream = getClass().getResourceAsStream(HELP_DOC_RESOURCE_PATH)) {
if (resourceStream == null) {
throw new FileNotFoundException("资源中未找到帮助文档: " + HELP_DOC_RESOURCE_PATH);
}
// 复制资源到程序目录
Files.copy(resourceStream, helpFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
}
return helpFile;
}
// 帮助文档在资源中的路径
private static final String HELP_DOC_RESOURCE_PATH = "/docs/UserHelpDocument.docx";
// 释放到外部的文件名
private static final String HELP_DOC_FILE_NAME = "操作手册.docx";
/**
* 使用系统默认程序打开文档
*/
private void openDocument(File file) throws IOException {
if (Desktop.isDesktopSupported()) {
Desktop desktop = Desktop.getDesktop();
if (desktop.isSupported(Desktop.Action.OPEN)) {
desktop.open(file);
} else {
throw new UnsupportedOperationException("系统不支持打开文件操作");
}
} else {
// 对于不支持Desktop的环境如某些Linux系统
String os = java.lang.System.getProperty("os.name").toLowerCase();
ProcessBuilder pb = getProcessBuilder(file, os);
pb.start();
}
}
private static @NotNull ProcessBuilder getProcessBuilder(File file, String os) {
ProcessBuilder pb;
if (os.contains("win")) {
pb = new ProcessBuilder("cmd.exe", "/c", file.getAbsolutePath());
} else if (os.contains("mac")) {
pb = new ProcessBuilder("open", file.getAbsolutePath());
} else if (os.contains("nix") || os.contains("nux")) {
pb = new ProcessBuilder("xdg-open", file.getAbsolutePath());
} else {
throw new UnsupportedOperationException("不支持的操作系统");
}
return pb;
}
@FXML void onAbout(ActionEvent actionEvent) {
DialogUtil.showDetailedInformationDialog("版本", "版本信息","1.0.0-beta");
DialogUtil.showDetailedInformationDialog("关于软件", "数字化验收工具","软件版本:" + System.version() + "\n");
}
public void updateStepButtonsVisibility() {
Setting setting = System.getSetting();

View File

@ -135,7 +135,7 @@ public class PathCheckPaneController implements Initializable {
FileChooser fileChooser = System.getFileChooser();
fileChooser.setTitle("选择保存逻辑地址文件的位置");
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("CSV Files", "*.csv"));
fileChooser.setInitialFileName(selectedMode.toString() + "逻辑地址文件.csv");
fileChooser.setInitialFileName(System.getProjectInfo().makeProjectInfoIntoFileNamePrefix() + "-" + selectedMode.toString() + "-逻辑地址文件 " + ".csv");
File outputFile = fileChooser.showSaveDialog(generateLogicalAddress2B.getScene().getWindow());
if (outputFile == null) {
@ -241,7 +241,7 @@ public class PathCheckPaneController implements Initializable {
FileChooser fileChooser = System.getFileChooser();
fileChooser.setTitle("选择保存物理地址文件的位置");
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("CSV Files", "*.csv"));
fileChooser.setInitialFileName(selectedMode.toString() + "物理地址文件.csv");
fileChooser.setInitialFileName(System.getProjectInfo().makeProjectInfoIntoFileNamePrefix() + "-" + selectedMode.toString() + "-物理地址文件.csv");
// 使用当前窗口作为父窗口显示文件选择对话框
File outputFile = fileChooser.showSaveDialog(selectJPGFolder2B.getScene().getWindow());

View File

@ -2,13 +2,21 @@ package top.r3944realms.docchecktoolrefactored.ui.module;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TextField;
import lombok.Getter;
import lombok.Setter;
import top.r3944realms.docchecktoolrefactored.System;
import top.r3944realms.docchecktoolrefactored.ui.utils.DialogUtil;
import top.r3944realms.docchecktoolrefactored.util.FileUtil;
import java.net.URL;
import java.util.ResourceBundle;
/**
* The type Project info pane controller.
*/
public class ProjectInfoPaneController {
public class ProjectInfoPaneController implements Initializable {
@FXML private TextField projectNameTF;
@FXML private TextField AcceptanceTimeTF;
@FXML private TextField totalCatalogNumberTF;
@ -23,6 +31,46 @@ public class ProjectInfoPaneController {
totalCatalogNumberTF.clear();
AcceptanceTimeTF.clear();
}
}
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
addAutoSaveListener(projectNameTF, "projectName");
addAutoSaveListener(AcceptanceTimeTF, "acceptanceTime");
addAutoSaveListener(totalCatalogNumberTF, "totalCatalogNumber");
addAutoSaveListener(fileCategoriesTF, "fileCategory");
addAutoSaveListener(fileYearTF, "fileYear");
}
private void addAutoSaveListener(TextField field, String fieldName) {
field.textProperty().addListener((observable, oldValue, newValue) -> {
switch (fieldName) {
case "projectName" -> System.getProjectInfo().setProjectName(newValue);
case "acceptanceTime" -> System.getProjectInfo().setAcceptanceTime(newValue);
case "totalCatalogNumber" -> System.getProjectInfo().setTotalCatalogNumber(newValue);
case "fileCategory" -> System.getProjectInfo().setFileCategory(newValue);
case "fileYear" -> System.getProjectInfo().setFileYear(newValue);
}
});
}
@Setter @Getter
public static class ProjectInfo {
private String projectName;
private String acceptanceTime;
private String totalCatalogNumber;
private String fileCategory;
private String fileYear;
@Override
public String toString() {
return "ProjectInfo{" +
"projectName='" + projectName + '\'' +
", acceptanceTime='" + acceptanceTime + '\'' +
", totalCatalogNumber='" + totalCatalogNumber + '\'' +
", fileCategory='" + fileCategory + '\'' +
", fileYear='" + fileYear + '\'' +
'}';
}
public String makeProjectInfoIntoFileNamePrefix() {
return FileUtil.ensureValidFileName(projectName);
}
}
}

View File

@ -207,7 +207,7 @@ public class StorageCarrierPaneController {
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("CSV Files", "*.csv"));
// 设置默认文件名
fileChooser.setInitialFileName("哈希值列表文件.csv");
fileChooser.setInitialFileName(System.getProjectInfo().makeProjectInfoIntoFileNamePrefix() + "-哈希值列表文件.csv");
// 使用当前窗口作为父窗口显示文件选择对话框
File outputFile = fileChooser.showSaveDialog(selectLoadDigitalOutcomes7B.getScene().getWindow());

View File

@ -68,7 +68,10 @@ public class DuplicateDocumentDetectionTask extends Task<String>{
if (total > 0) {
// 控制更新频率
String msg = switch (phase) {
case GROUP_BY_SIZE -> String.format("正在按文件大小分组: %d/%d", current, total);
case GROUP_BY_SIZE -> {
totalFiles.getAndIncrement();
yield String.format("正在按文件大小分组: %d/%d", current, total);
}
case CALCULATE_HASH -> String.format("正在计算哈希值: %d/%d", current, total);
};
updateMessage(msg);

View File

@ -7,6 +7,7 @@ import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;
import javafx.stage.Window;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.docchecktoolrefactored.Main;
import java.util.Optional;
@ -23,7 +24,7 @@ public class DialogUtil {
).toExternalForm();
private static final Image DIALOG_ICON = new Image(
Objects.requireNonNull(Main.class.getResourceAsStream("/img/logo256x.ico"))
Objects.requireNonNull(Main.class.getResourceAsStream("/img/icon.jpg"))
);
/**
* Show exit confirmation boolean.
@ -321,10 +322,12 @@ public class DialogUtil {
dialogPane.getStyleClass().add("dialog-pane");
}
// 封装设置Alert图标方法
private static void setAlertIcon(Alert alert) {
// 间接获取Alert对应的Stage
Stage alertStage = (Stage) alert.getDialogPane().getScene().getWindow();
// 添加图标可添加多个尺寸系统自动适配
alertStage.getIcons().add(DIALOG_ICON);
private static void setAlertIcon(@NotNull Alert alert) {
Platform.runLater(() -> {
Stage alertStage = (Stage) alert.getDialogPane().getScene().getWindow();
if (alertStage != null) {
alertStage.getIcons().add(DIALOG_ICON);
}
});
}
}

View File

@ -95,4 +95,84 @@ public class FileUtil {
return file;
}
/**
* 检查文件名是否合法跨平台
* @param fileName 文件名不含路径
* @return true 合法false 非法
*/
public static boolean isValidFileName(String fileName) {
if (fileName == null || fileName.isBlank()) return false;
// Windows 非法字符
String invalidChars = "<>:\"/\\\\|?*";
for (char c : invalidChars.toCharArray()) {
if (fileName.indexOf(c) >= 0) return false;
}
// Windows 保留名称
List<String> reservedNames = Arrays.asList(
"CON", "PRN", "AUX", "NUL",
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"
);
String upperName = fileName.toUpperCase(Locale.ROOT);
String nameWithoutExt = upperName.contains(".") ? upperName.substring(0, upperName.indexOf('.')) : upperName;
if (reservedNames.contains(nameWithoutExt)) return false;
// 不能以空格或句点结尾
if (fileName.endsWith(" ") || fileName.endsWith(".")) return false;
return true;
}
/**
* 自动修复非法文件名
* - 替换非法字符为下划线
* - 去除末尾空格和句点
* - 避免使用系统保留名
* @param fileName 原始文件名
* @return 修复后的安全文件名
*/
public static String sanitizeFileName(String fileName) {
if (fileName == null || fileName.isBlank()) {
return "untitled";
}
// 替换非法字符为下划线
String sanitized = fileName.replaceAll("[<>:\"/\\\\|?*]", "_");
// 去掉结尾空格和句点
sanitized = sanitized.replaceAll("[ \\.]+$", "");
// Windows 保留名修复
List<String> reservedNames = Arrays.asList(
"CON", "PRN", "AUX", "NUL",
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"
);
String upperName = sanitized.toUpperCase(Locale.ROOT);
String nameWithoutExt = upperName.contains(".") ? upperName.substring(0, upperName.indexOf('.')) : upperName;
if (reservedNames.contains(nameWithoutExt)) {
sanitized = "_" + sanitized; // 添加前缀防止冲突
}
// 如果修复后为空则命名为 untitled
if (sanitized.isBlank()) sanitized = "untitled";
return sanitized;
}
/**
* 校验或修复文件名
* 若合法则直接返回
* 若非法则自动修复并打印日志
*/
public static String ensureValidFileName(String fileName) {
if (isValidFileName(fileName)) {
return fileName;
}
String sanitized = sanitizeFileName(fileName);
log.warn(LoggerMarker.TRACE_MARKER, "非法文件名已修复: {} → {}", fileName, sanitized);
return sanitized;
}
}

Binary file not shown.