Merge branch 'master' of https://github.com/CXT-maker/Version2
This commit is contained in:
commit
a447e791e0
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
@ -251,7 +251,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());
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
BIN
src/main/resources/docs/UserHelpDocument.docx
Normal file
BIN
src/main/resources/docs/UserHelpDocument.docx
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user