Compare commits

...

10 Commits

Author SHA1 Message Date
96f7acb2a9 更新版本(1.0->1.1):
1. 步骤任务超时终止配置
2. 步骤辅助关闭后主界面自动调整尺寸
3. 扫描超时会显示最后扫描的文件
4. track日志实时记录扫描文件
5. 一些不影响主功能的,细微调整(如FXML UI文件)
2026-01-09 11:16:06 +08:00
CXT-maker
d1608666db V1.0版本。1.修改打包方式 2.步骤2生成地址文件重复报错且报错不精准问题3.完善好的“用户使用说明书.pdf" 2025-11-24 14:36:28 +08:00
CXT-maker
df39df55d7 第一步添加总数分类
第二步添加“生成物理地址文件”的检测重复文件名
2025-11-17 20:29:11 +08:00
CXT-maker
a447e791e0 Merge branch 'master' of https://github.com/CXT-maker/Version2 2025-11-03 20:23:22 +08:00
98796bf123 更新版本:
1. 修复对话框图标显示
2. 文件名生成
2025-11-02 16:03:55 +08:00
CXT-maker
67a35cc661 对第二步修改重复文件名报错的尝试 2025-10-29 16:01:20 +08:00
0f54983b30 优化UI 2025-10-09 13:01:38 +08:00
81b420bacb Merge remote-tracking branch 'origin/master'
# Conflicts:
#	src/main/java/top/r3944realms/docchecktoolrefactored/ui/module/DuplicateDocumentPaneController.java
#	src/main/java/top/r3944realms/docchecktoolrefactored/ui/module/PathCheckPaneController.java
#	src/main/resources/fxml/module/step-1-pane.fxml
#	src/main/resources/fxml/module/step-7-pane.fxml
2025-09-28 21:01:55 +08:00
b7030ede3c feat:1.调整了显示界面的UI 2.去掉了反馈区域的同步 2025-09-28 20:59:05 +08:00
CXT-maker
0c5a75a17c 这个是发给他们的测试版。 2025-08-27 17:54:47 +08:00
51 changed files with 2558 additions and 308 deletions

View File

@ -4,7 +4,9 @@
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="LOCAL" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="$PROJECT_DIR$/../../projEnv/gradle/gradle-8.6" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />

View File

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ASMIdeaPluginConfiguration">
<asm skipDebug="false" skipFrames="false" skipCode="false" expandFrames="false" />

View File

@ -8,6 +8,7 @@ buildscript {
}
plugins {
id 'com.github.johnrengelman.shadow' version '8.1.1'
id 'java'
id 'io.franzbecker.gradle-lombok' version '3.0.0'
id 'application'
@ -65,7 +66,7 @@ dependencies {
implementation 'ch.qos.logback:logback-classic:1.5.18'
implementation 'ch.qos.logback:logback-core:1.5.18'
implementation 'ch.qos.logback:logback-classic:1.5.6'
implementation 'commons-cli:commons-cli:1.9.0'
implementation 'com.alibaba:easyexcel:4.0.3'
implementation 'org.apache.pdfbox:pdfbox:3.0.5'
@ -73,6 +74,16 @@ dependencies {
implementation 'org.apache.poi:poi-ooxml:5.4.1'
implementation 'com.intellij:annotations:12.0'
// ofdrw
implementation 'org.ofdrw:ofdrw-core:2.3.7'
implementation 'org.ofdrw:ofdrw-pkg:2.3.7'
implementation 'org.ofdrw:ofdrw-reader:2.3.7'
//
implementation 'dom4j:dom4j:1.6.1'
implementation 'org.bouncycastle:bcpkix-jdk18on:1.77' //
implementation 'commons-io:commons-io:2.11.0'
compileOnly 'org.projectlombok:lombok:1.18.38'
annotationProcessor 'org.projectlombok:lombok:1.18.38'
@ -83,10 +94,35 @@ dependencies {
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
}
// ... application ...
application {
// mainModule
mainClass = 'top.r3944realms.docchecktoolrefactored.Main'
}
// Shadow-JAR .SF.RSA Fat-JAR JVM
shadowJar {
// Jar
archiveBaseName.set(project_name)
archiveVersion.set(project_version)
// java -jar
manifest {
attributes 'Main-Class': application.mainClass.get()
}
// SPI
mergeServiceFiles()
// "Invalid signature file digest"
exclude 'META-INF/*.SF'
exclude 'META-INF/*.RSA'
exclude 'META-INF/*.DSA'
}
// build shadowJar ./gradlew build Fat-JAR
build.dependsOn shadowJar
javafx {
version = javafxVersion
@ -131,26 +167,7 @@ tasks.register('runCli', JavaExec) {
}
}
// JAR的任务
tasks.register('buildFatJar', Jar) {
group = 'build'
description = 'Builds a standalone JAR with all dependencies'
manifest {
attributes(
'Main-Class': 'top.r3944realms.docchecktoolrefactored.Main'
)
}
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
with jar // jar
archiveBaseName.set(project_name)
archiveVersion.set(project_version)
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
// =================== 便 ===================
@ -158,14 +175,24 @@ tasks.register('buildPortable', Exec) {
group = 'distribution'
description = 'Build portable EXE (no installer)'
dependsOn buildFatJar
// shadowJar buildFatJar
dependsOn shadowJar
doFirst {
mkdir "$buildDir/distributions"
// shadow JAR 便
def shadowJarFile = shadowJar.archiveFile.get().asFile
println "Using shadow JAR for packaging: ${shadowJarFile.name}"
println "Location: ${shadowJarFile.parent}"
}
commandLine 'jpackage',
'--name', 'DocCheckTool',
'--input', "$buildDir/libs",
'--main-jar', "${project_name}-${project_version}.jar",
'--name', project_name, // 使 project_name
'--input', shadowJar.archiveFile.get().asFile.parent, // shadow JAR
'--main-jar', shadowJar.archiveFileName.get(), // shadow JAR
'--main-class', application.mainClass.get(),
'--type', 'app-image', // 便
'--type', 'app-image',
'--app-version', project_version,
'--vendor', 'r3944realms',
'--dest', "$buildDir/distributions",
@ -174,8 +201,9 @@ tasks.register('buildPortable', Exec) {
'--java-options', '-Xms256m',
'--verbose',
'--icon', file('src/main/resources/img/logo256x.ico').absolutePath
}
doFirst {
mkdir "$buildDir/distributions"
}
}

View File

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

View File

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

View File

@ -5,6 +5,7 @@ import javafx.stage.FileChooser;
import lombok.Getter;
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.*;
@ -26,9 +27,12 @@ public enum System {
private static final Properties properties = new Properties();
private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
// 默认值
private static final boolean DEFAULT_ENABLE_TASK_TIMEOUT = false;
private static final long DEFAULT_SINGLE_TIMEOUT = 30;
private static final long DEFAULT_TOTAL_TIMEOUT = 300;
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();
@ -112,6 +116,7 @@ public enum System {
/** 将Setting对象转换为Properties */
private static void settingToProperties(Setting setting, Properties props) {
props.setProperty("scanTimeOutS", String.valueOf(setting.getScanTimeout()));
props.setProperty("enableTaskTimeout", String.valueOf(setting.isEnableTaskTimeout()));
props.setProperty("taskTimeOutS", String.valueOf(setting.getTaskTimeout()));
props.setProperty("enableStep", String.valueOf(setting.isEnableStep()));
}
@ -119,7 +124,12 @@ public enum System {
/** 将Properties转换为Setting对象 */
private static Setting propertiesToSetting(Properties props) {
Setting s = new Setting();
try {
s.setEnableTaskTimeout((Boolean.parseBoolean(props.getProperty("enableTaskTimeout", String.valueOf(DEFAULT_ENABLE_TASK_TIMEOUT)))));
} catch (Exception e) {
s.setScanTimeout(DEFAULT_SINGLE_TIMEOUT);
log.error(LoggerMarker.DEBUG_MARKER, "enableTaskTimeout格式错误使用默认值{}", DEFAULT_ENABLE_TASK_TIMEOUT);
}
try {
s.setScanTimeout(Long.parseLong(props.getProperty("scanTimeOutS", String.valueOf(DEFAULT_SINGLE_TIMEOUT))));
} catch (NumberFormatException e) {
@ -146,6 +156,7 @@ public enum System {
/** 获取默认Setting */
private static Setting defaultSetting() {
Setting s = new Setting();
s.setEnableStep(DEFAULT_ENABLE_TASK_TIMEOUT);
s.setScanTimeout(DEFAULT_SINGLE_TIMEOUT);
s.setTaskTimeout(DEFAULT_TOTAL_TIMEOUT);
return s;

View File

@ -31,6 +31,17 @@ public class DuplicateFinder {
private final FileHashCalculator hashCalculator;
private final boolean enableProgress;
private final ExecutorService executorService;
private final AtomicInteger targetFiles = new AtomicInteger(0);
private final AtomicInteger otherFiles = new AtomicInteger(0);
// Add getter methods
public int getTargetFilesCount() {
return targetFiles.get();
}
public int getOtherFilesCount() {
return otherFiles.get();
}
// 进度回调接口
public interface ProgressCallback {
default void onPhaseStarted(Phase phase) {}
@ -71,6 +82,8 @@ public class DuplicateFinder {
public List<DuplicateGroup> findDuplicates(Path rootDir) throws IOException {
// 清理错误列表
errors.clear();
targetFiles.set(0);
otherFiles.set(0);
// -----------------------------
// 第一阶段按文件大小分组
// -----------------------------
@ -154,6 +167,25 @@ public class DuplicateFinder {
meta.setPath(file);
meta.setSize(Files.size(file));
sizeGroups.computeIfAbsent(meta.getSize(), k -> new ArrayList<>()).add(meta);
// Classify and count files
String fileName = file.getFileName().toString().toLowerCase();
String extension = "";
int lastDotIndex = fileName.lastIndexOf('.');
if (lastDotIndex > 0) {
extension = fileName.substring(lastDotIndex + 1);
}
// Check if it's a target file
if ("pdf".equals(extension) || "ofd".equals(extension) ||
"jpg".equals(extension) || "jpeg".equals(extension) ||
"png".equals(extension) || "bmp".equals(extension) ||
"gif".equals(extension) || "tiff".equals(extension) ||
"jp2".equals(extension)) {
targetFiles.incrementAndGet();
} else {
otherFiles.incrementAndGet();
}
} catch (IOException e) {
log.error(LoggerMarker.TRACE_MARKER, "Failed to get file's size: {}", file);
}
@ -201,9 +233,10 @@ public class DuplicateFinder {
hashCalculator.calculateHash(file.getPath());
file.setHash(hash);
synchronized (hashGroups) {
//hashGroups已经定义为ConcurrentHashMap可以利用它本身的线程安全方法来减少同步块
//synchronized (hashGroups) {
hashGroups.computeIfAbsent(hash, k -> new ArrayList<>()).add(file);
}
//}
int current = processedFiles.incrementAndGet();
if (enableProgress && (current % PROGRESS_REPORT_INTERVAL == 0 || current == totalFilesToProcess)) {

View File

@ -82,7 +82,7 @@ public class HashFileGenerator {
public void onError(Path path, Exception e) {
log.error(LoggerMarker.TRACE_MARKER, "扫描错误: {} - {}", path, e.getMessage());
}
});
}, System.getSetting().getScanTimeout());
// 使用带超时的等待并响应中断
try {
@ -136,7 +136,7 @@ public class HashFileGenerator {
return;
}
String hash = hashCalculator.calculatePartialHash(file);
String hash = hashCalculator.calculateHash(file);
String[] result = {file.getFileName().toString(), hash};
synchronized (results) {

View File

@ -8,7 +8,10 @@ import top.r3944realms.docchecktoolrefactored.util.LoggerMarker;
import java.io.File;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Slf4j
public class LogicalAddressFileGenerator implements AddressFileGenerator {
private ProgressCallback callback;
@ -61,36 +64,67 @@ public class LogicalAddressFileGenerator implements AddressFileGenerator {
}
safeOnPhaseProgress(Phase.GENERATE_LOGICAL, 3, 3); // 3/3
safeOnPhaseCompleted(Phase.GENERATE_LOGICAL);
safeOnPhaseCompleted(Phase.GENERATE_LOGICAL);
} catch (RuntimeException e) {
// 直接重新抛出自定义的重复档号异常不让它被日志吞掉
if (e.getMessage() != null && e.getMessage().startsWith("存在重复档号:")) {
throw e;
}
log.info(LoggerMarker.RELEASE_MARKER, "生成逻辑地址文件时出错: {}", e.getMessage());
throw e;
} catch (Exception e) {
log.info(LoggerMarker.RELEASE_MARKER, "生成逻辑地址文件时出错: {}", e.getMessage());
throw new RuntimeException(e);
}
}
/**
* 生成页面级逻辑地址文件
*/
private void generatePageLevelFile(PrintWriter writer, List<Record> records, ProgressCallback callback) {
// 写入CSV头部
writer.println("逻辑文件名,逻辑地址");
int totalRecords = records.stream().mapToInt(r -> r.page).sum();
int current = 0;
// 处理每条记录
Set<String> seenCodes = new HashSet<>();
Set<String> duplicateCodes = new HashSet<>();
// 收集所有重复的档号
for (Record record : records) {
String archiveCode = record.archiveCode;
int page = record.page;
if (!seenCodes.add(archiveCode)) {
duplicateCodes.add(archiveCode);
}
}
// 为每页生成一行数据
for (int i = 1; i <= page; i++) {
// 生成逻辑文件名
String logicalFileName = String.format("%s-%04d", archiveCode, i);
// 如果存在重复档号记录日志并抛出异常
if (!duplicateCodes.isEmpty()) {
StringBuilder errorMsg = new StringBuilder("存在重复档号:\n");
for (String code : duplicateCodes) {
errorMsg.append(code).append("\n");
}
log.info(LoggerMarker.RELEASE_MARKER, "{}", errorMsg.toString());
// 生成逻辑地址
String logicalAddress = generatePageLevelLogicalAddress(archiveCode, i);
throw new RuntimeException(errorMsg.toString());
}else {
// 写入CSV头部
writer.println("逻辑文件名,逻辑地址");
int totalRecords = records.stream().mapToInt(r -> r.page).sum();
int current = 0;
// 写入CSV行
writer.printf("%s,%s%n", logicalFileName, logicalAddress);
current++;
safeOnPhaseProgress(Phase.GENERATE_LOGICAL, current, totalRecords);
// 处理每条记录
for (Record record : records) {
String archiveCode = record.archiveCode;
int page = record.page;
// 为每页生成一行数据
for (int i = 1; i <= page; i++) {
// 生成逻辑文件名
String logicalFileName = String.format("%s-%04d", archiveCode, i);
// 生成逻辑地址
String logicalAddress = generatePageLevelLogicalAddress(archiveCode, i);
// 写入CSV行
writer.printf("%s,%s%n", logicalFileName, logicalAddress);
current++;
safeOnPhaseProgress(Phase.GENERATE_LOGICAL, current, totalRecords);
}
}
}
}
@ -99,22 +133,43 @@ public class LogicalAddressFileGenerator implements AddressFileGenerator {
* 生成文件级逻辑地址文件
*/
private void generateFileLevelFile(PrintWriter writer, List<Record> records, ProgressCallback callback) {
// 写入CSV头部包含页数列
writer.println("逻辑文件名,逻辑地址,页数");
int totalRecords = records.stream().mapToInt(r -> r.page).sum();
int current = 0;
// 处理每条记录
Set<String> seenCodes = new HashSet<>();
Set<String> duplicateCodes = new HashSet<>();
// 收集所有重复的档号
for (Record record : records) {
String archiveCode = record.archiveCode;
int page = record.page;
if (!seenCodes.add(archiveCode)) {
duplicateCodes.add(archiveCode);
}
}
// 生成逻辑地址不包含页数
String logicalAddress = generateFileLevelLogicalAddress(archiveCode);
// 如果存在重复档号记录日志并抛出异常
if (!duplicateCodes.isEmpty()) {
StringBuilder errorMsg = new StringBuilder("存在重复档号:\n");
for (String code : duplicateCodes) {
errorMsg.append(code).append("\n");
}
log.info(LoggerMarker.RELEASE_MARKER, "{}", errorMsg.toString());
// 写入CSV行包含页数
writer.printf("%s,%s,%d%n", /* 逻辑文件名(就是档号)*/ archiveCode, logicalAddress, page);
safeOnPhaseProgress(Phase.GENERATE_LOGICAL, current, totalRecords);
throw new RuntimeException(errorMsg.toString());
} else {
// 写入CSV头部包含页数列
writer.println("逻辑文件名,逻辑地址,页数");
int totalRecords = records.stream().mapToInt(r -> r.page).sum();
int current = 0;
// 处理每条记录
for (Record record : records) {
String archiveCode = record.archiveCode;
int page = record.page;
// 生成逻辑地址不包含页数
String logicalAddress = generateFileLevelLogicalAddress(archiveCode);
// 写入CSV行包含页数
writer.printf("%s,%s,%d%n", /* 逻辑文件名(就是档号)*/ archiveCode, logicalAddress, page);
safeOnPhaseProgress(Phase.GENERATE_LOGICAL, current, totalRecords);
}
}
}

View File

@ -3,11 +3,14 @@ package top.r3944realms.docchecktoolrefactored.core;
import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.ofdrw.reader.OFDReader;
import top.r3944realms.docchecktoolrefactored.util.LoggerMarker;
import java.io.File;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.Set;
@Slf4j
public class PhysicalAddressFileGenerator implements AddressFileGenerator {
@ -55,10 +58,8 @@ public class PhysicalAddressFileGenerator implements AddressFileGenerator {
try (PrintWriter writer = new PrintWriter(outputFile, StandardCharsets.UTF_8)) {
if (folderType == PAGE_TYPE) {
writer.println("物理文件名,物理地址");
processPageLevelFolder(rootFolder, writer, outputFile.getAbsolutePath(), callback, counter, totalFiles);
} else if (folderType == FILE_TYPE) {
writer.println("物理文件名,物理地址,页数");
processFileLevelFolder(rootFolder, writer, outputFile.getAbsolutePath(), callback, counter, totalFiles);
} else {
throw new IllegalArgumentException("不支持的文件夹类型: " + folderType);
@ -66,7 +67,17 @@ public class PhysicalAddressFileGenerator implements AddressFileGenerator {
}
safeOnPhaseCompleted(Phase.GENERATE_PHYSICAL);
} catch (Exception e) {
} catch (RuntimeException e) {
// 直接重新抛出自定义的重复文件名异常不让它被日志吞掉
if (e.getMessage() != null && e.getMessage().startsWith("存在重复文件名:")) {
throw e;
}
safeOnPhaseCompleted(Phase.GENERATE_PHYSICAL);
safeOnPhaseProgress(Phase.GENERATE_PHYSICAL, 0, 0);
log.error("生成物理地址文件失败: {}", e.getMessage(), e);
throw e; // 重新抛出异常确保能被上层捕获
}
catch (Exception e) {
safeOnPhaseCompleted(Phase.GENERATE_PHYSICAL);
safeOnPhaseProgress(Phase.GENERATE_PHYSICAL, 0, 0);
log.error("生成物理地址文件失败: {}", e.getMessage(), e);
@ -90,6 +101,7 @@ public class PhysicalAddressFileGenerator implements AddressFileGenerator {
}
return count;
}
/**
* 处理页面级文件夹及其内部文件
*
@ -98,6 +110,61 @@ public class PhysicalAddressFileGenerator implements AddressFileGenerator {
* @param outputFilePath 输出文件的绝对路径
*/
private void processPageLevelFolder(File folder, PrintWriter writer, String outputFilePath, ProgressCallback callback, int[] counter, int total) {
Set<String> seenFileNames = new HashSet<>();
Set<String> duplicateFileNames = new HashSet<>();
// 收集所有重复的文件名
collectPageLevelFileNames(folder, outputFilePath, seenFileNames, duplicateFileNames);
// 如果存在重复文件名记录日志并抛出异常
if (!duplicateFileNames.isEmpty()) {
StringBuilder errorMsg = new StringBuilder("存在重复文件名:\n");
for (String name : duplicateFileNames) {
errorMsg.append(name).append("\n");
}
log.info(LoggerMarker.RELEASE_MARKER, "{}", errorMsg.toString());
throw new RuntimeException(errorMsg.toString());
} else {
// 写入CSV头部
writer.println("物理文件名,物理地址");
// 处理文件与原来的方法类似但排除了重复检查
processPageLevelFolderInternal(folder, writer, outputFilePath, callback, counter, total);
}
}
/**
* 递归收集页面级文件夹中的所有文件名以检测重复
*/
private void collectPageLevelFileNames(File folder, String outputFilePath,
Set<String> seenFileNames, Set<String> duplicateFileNames) {
File[] filesAndFolders = folder.listFiles(file -> !file.isHidden());
if (filesAndFolders != null) {
for (File file : filesAndFolders) {
// 跳过输出文件本身
if (file.getAbsolutePath().equals(outputFilePath)) continue;
if (file.isFile() && isImageFile(file.getName())) {
// 移除文件扩展名
String fileName = file.getName().substring(0, file.getName().lastIndexOf('.'));
// 检查是否重复
if (!seenFileNames.add(fileName)) {
duplicateFileNames.add(fileName);
}
} else if (file.isDirectory()) {
// 递归处理子文件夹
collectPageLevelFileNames(file, outputFilePath, seenFileNames, duplicateFileNames);
}
}
}
}
/**
* 实际处理页面级文件夹的内部实现无重复检查
*/
private void processPageLevelFolderInternal(File folder, PrintWriter writer, String outputFilePath,
ProgressCallback callback, int[] counter, int total) {
// 获取该文件夹下的所有非隐藏文件和文件夹
File[] filesAndFolders = folder.listFiles(file -> !file.isHidden());
@ -108,7 +175,6 @@ public class PhysicalAddressFileGenerator implements AddressFileGenerator {
// 只处理图片文件跳过其他类型的文件
if (file.isFile() && isImageFile(file.getName())) {
// 移除文件扩展名
String fileNameWithoutExt = file.getName().substring(0, file.getName().lastIndexOf('.'));
@ -125,12 +191,22 @@ public class PhysicalAddressFileGenerator implements AddressFileGenerator {
safeOnPhaseProgress(Phase.GENERATE_PHYSICAL, counter[0], total);
} else if (file.isDirectory()) {
// 递归处理子文件夹
processPageLevelFolder(file, writer, outputFilePath, callback, counter, total);
processPageLevelFolderInternal(file, writer, outputFilePath, callback, counter, total);
}
}
}
}
/**
* 处理文件级文件夹处理PDF文件
*
@ -139,6 +215,60 @@ public class PhysicalAddressFileGenerator implements AddressFileGenerator {
* @param outputFilePath 输出文件的绝对路径
*/
private void processFileLevelFolder(File folder, PrintWriter writer, String outputFilePath, ProgressCallback callback, int[] counter, int total) {
Set<String> seenFileNames = new HashSet<>();
Set<String> duplicateFileNames = new HashSet<>();
// 收集所有重复的文件名
collectFileLevelFileNames(folder, outputFilePath, seenFileNames, duplicateFileNames);
// 如果存在重复文件名记录日志并抛出异常
if (!duplicateFileNames.isEmpty()) {
StringBuilder errorMsg = new StringBuilder("存在重复文件名:\n");
for (String name : duplicateFileNames) {
errorMsg.append(name).append("\n");
}
log.info(LoggerMarker.RELEASE_MARKER, "{}", errorMsg.toString());
throw new RuntimeException(errorMsg.toString());
} else {
// 写入CSV头部
writer.println("物理文件名,物理地址,页数");
// 处理文件与原来的方法类似但排除了重复检查
processFileLevelFolderInternal(folder, writer, outputFilePath, callback, counter, total);
}
}
/**
* 递归收集文件级文件夹中的所有文件名以检测重复
*/
private void collectFileLevelFileNames(File folder, String outputFilePath,
Set<String> seenFileNames, Set<String> duplicateFileNames) {
File[] filesAndFolders = folder.listFiles(file -> !file.isHidden());
if (filesAndFolders != null) {
for (File file : filesAndFolders) {
// 跳过输出文件本身
if (file.getAbsolutePath().equals(outputFilePath)) continue;
if (file.isFile() && isPdfFile(file.getName())) {
String fileName = file.getName().substring(0, file.getName().lastIndexOf('.'));
// 检查是否重复
if (!seenFileNames.add(fileName)) {
duplicateFileNames.add(fileName);
}
} else if (file.isDirectory()) {
// 递归处理子文件夹
collectFileLevelFileNames(file, outputFilePath, seenFileNames, duplicateFileNames);
}
}
}
}
/**
* 实际处理文件级文件夹的内部实现无重复检查
*/
private void processFileLevelFolderInternal(File folder, PrintWriter writer, String outputFilePath,
ProgressCallback callback, int[] counter, int total) {
// 获取该文件夹下的所有非隐藏文件和文件夹
File[] filesAndFolders = folder.listFiles(file -> !file.isHidden());
@ -149,47 +279,57 @@ public class PhysicalAddressFileGenerator implements AddressFileGenerator {
continue;
}
if (file.isFile()&& isPdfFile(file.getName())) {
// 移除文件扩展名
String fileNameWithoutExt = file.getName().substring(0, file.getName().lastIndexOf('.'));
if (file.isFile() && isPdfFile(file.getName())) {
// 移除文件扩展名
String fileNameWithoutExt = file.getName().substring(0, file.getName().lastIndexOf('.'));
// 生成物理地址路径使用与页面级相同的逻辑
String physicalAddress = generatePhysicalAddress(file.getAbsolutePath(), fileNameWithoutExt);
// 生成物理地址路径使用与页面级相同的逻辑
String physicalAddress = generatePhysicalAddress(file.getAbsolutePath(), fileNameWithoutExt);
// 获取PDF页数
int pageCount = getPdfPageCount(file);
// 获取PDF页数
int pageCount = getPdfPageCount(file);
// 写入CSV行
writer.printf("%s,%s,%d%n", fileNameWithoutExt, physicalAddress, pageCount);
// 写入CSV行
writer.printf("%s,%s,%d%n", fileNameWithoutExt, physicalAddress, pageCount);
counter[0]++;
safeOnPhaseProgress(Phase.GENERATE_PHYSICAL, counter[0], total);
counter[0]++;
safeOnPhaseProgress(Phase.GENERATE_PHYSICAL, counter[0], total);
} else if (file.isDirectory()) {
// 递归处理子文件夹
processFileLevelFolder(file, writer, outputFilePath, callback, counter, total);
processFileLevelFolderInternal(file, writer, outputFilePath, callback, counter, total);
}
}
}
}
/**
* 获取PDF文件的页数
*
* @param pdfFile PDF文件
* @return 页数
*/
private int getPdfPageCount(File pdfFile) {
// 使用Apache PDFBox库获取PDF页数
try (PDDocument document = Loader.loadPDF(pdfFile)){
int pageCount = document.getNumberOfPages();
document.close();
return pageCount;
} catch (Exception e) {
log.warn(LoggerMarker.RELEASE_MARKER, "无法获取PDF文件页数: {}", pdfFile.getAbsolutePath(), e);
return 0;
/**
* 获取PDF或OFD文件的页数
*
* @param file 文件
* @return 页数
*/
private int getPdfPageCount(File file) {
try {
if (file.getName().toLowerCase().endsWith(".pdf")) {
// 使用Apache PDFBox库获取PDF页数
try (PDDocument document = Loader.loadPDF(file)) {
int pageCount = document.getNumberOfPages();
return pageCount;
}
} else if (file.getName().toLowerCase().endsWith(".ofd")) {
// 使用OFDRW库获取OFD页数
try (org.ofdrw.reader.OFDReader reader = new org.ofdrw.reader.OFDReader(file.toPath())) {
// 获取OFD文档的页面数量
int pageCount = reader.getNumberOfPages();
return pageCount;
}
}
} catch (Exception e) {
log.warn(LoggerMarker.RELEASE_MARKER, "无法获取文件页数: {}", file.getAbsolutePath(), e);
return 0;
}
return 0;
}
/**
* 判断是否为PDF文件
@ -198,7 +338,7 @@ public class PhysicalAddressFileGenerator implements AddressFileGenerator {
* @return 是否为PDF文件
*/
private boolean isPdfFile(String fileName) {
return fileName.toLowerCase().endsWith(".pdf");
return fileName.toLowerCase().endsWith(".pdf")|| fileName.toLowerCase().endsWith(".ofd");
}
/**
@ -211,7 +351,8 @@ public class PhysicalAddressFileGenerator implements AddressFileGenerator {
String lowerFileName = fileName.toLowerCase();
return lowerFileName.endsWith(".jpg") || lowerFileName.endsWith(".jpeg") ||
lowerFileName.endsWith(".png") || lowerFileName.endsWith(".bmp") ||
lowerFileName.endsWith(".gif") || lowerFileName.endsWith(".tiff");
lowerFileName.endsWith(".gif") || lowerFileName.endsWith(".tiff")||
lowerFileName.endsWith(".jp2"); // 支持 JPEG 2000 格式
}
/**

View File

@ -9,6 +9,7 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Setting {
private long scanTimeout = 30;
private boolean enableTaskTimeout = false;
private long taskTimeout = 60 * 5;
private boolean enableStep = false;
}

View File

@ -5,6 +5,7 @@ import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import top.r3944realms.docchecktoolrefactored.core.LogicalAddressFileGenerator;
import top.r3944realms.docchecktoolrefactored.ui.utils.DialogUtil;
import top.r3944realms.docchecktoolrefactored.util.FileUtil;
import top.r3944realms.docchecktoolrefactored.util.LoggerMarker;
@ -34,11 +35,11 @@ public class ExcelFileReader implements CatalogFileReader {
Sheet sheet = workbook.getSheetAt(0);
log.debug(LoggerMarker.DEBUG_MARKER, "读取工作表: {}", sheet.getSheetName());
// 获取标题行
// 获取标题行
Row headerRow = sheet.getRow(0);
if (headerRow == null) {
log.error(LoggerMarker.RELEASE_MARKER, "Excel文件缺少标题行");
throw new IllegalArgumentException("Excel文件缺少标题行");
log.error(LoggerMarker.RELEASE_MARKER, "Excel文件缺少标题行");
throw new IllegalArgumentException("Excel文件缺少标题行");
}
// 查找"档号""页数"列的索引
int archiveCodeIndex = -1;
@ -79,7 +80,7 @@ public class ExcelFileReader implements CatalogFileReader {
)
);
}
// 从第二行开始读取数据跳过标题行
// 从第二行开始读取数据跳过标题行只取数据部分内容
int totalRows = sheet.getLastRowNum();
int validRecords = 0;
int skippedRecords = 0;
@ -107,9 +108,12 @@ public class ExcelFileReader implements CatalogFileReader {
// 验证数据有效性
if (archiveCode.isEmpty()) {
throw new IllegalArgumentException("" + ( i + 1 ) + "行档号为空,停止处理");
}
if (page <= 0) {
throw new IllegalArgumentException("" + ( i + 1 ) + "行页数无效: " + page + ",停止处理");
}

View File

@ -9,16 +9,25 @@ import java.nio.file.AccessDeniedException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
@Slf4j
public class RobustParallelScanner implements FileScanner {
private final ForkJoinPool forkJoinPool;
private volatile boolean cancelled = false;
// 用于记录当前正在扫描的路径
private final AtomicReference<Path> currentScanningPath = new AtomicReference<>();
private final AtomicReference<String> lastProcessedFile = new AtomicReference<>();
private final AtomicInteger totalFilesScanned = new AtomicInteger(0);
private final AtomicInteger totalDirectoriesScanned = new AtomicInteger(0);
private volatile LocalDateTime scanStartTime;
private final int maxDepth;
public RobustParallelScanner(int maxDepth) {
this(Runtime.getRuntime().availableProcessors(), maxDepth);
@ -67,45 +76,117 @@ public class RobustParallelScanner implements FileScanner {
scanInternal(rootPath, listener, totalFiles, 30);
}
private void scanInternal(Path rootPath, FileScanListener listener, AtomicLong totalFiles, long timeout) {
resetScanState();
scanStartTime = LocalDateTime.now();
currentScanningPath.set(rootPath);
try {
validateDirectory(rootPath);
forkJoinPool.submit(() -> {
ForkJoinTask<?> submit = forkJoinPool.submit(() -> {
try {
AtomicInteger processedFiles = new AtomicInteger(0);
scanDirectory(rootPath, listener, processedFiles, totalFiles, 0);
if (!cancelled) {
listener.onScanComplete();
logScanStatistics();
}
} catch (Exception e) {
listener.onError(rootPath, e);
}
}).get(timeout, TimeUnit.SECONDS);
} catch (TimeoutException e) {
log.error(LoggerMarker.TRACE_MARKER, "Scan timeout: {}", rootPath, e);
forkJoinPool.shutdownNow();
listener.onError(rootPath, new TimeoutException("扫描超时30秒"));
});
handleTimeout(submit, rootPath, listener, timeout);
} catch (Exception e) {
listener.onError(rootPath, e);
}
}
private void handleTimeout(Future<?> scanFuture, Path rootPath,
FileScanListener listener, long timeout) throws TimeoutException {
try {
scanFuture.get(timeout, TimeUnit.SECONDS);
} catch (TimeoutException e) {
// 获取详细的超时信息
String timeoutInfo = buildTimeoutInfo(rootPath);
log.error(LoggerMarker.RELEASE_MARKER, "扫描超时 - {}", timeoutInfo, e);
// 取消扫描任务
cancelled = true;
scanFuture.cancel(true);
forkJoinPool.shutdownNow();
log.info("扫描超时 - {}", timeoutInfo);
// 创建详细的超时异常
throw new TimeoutException(
String.format("扫描超时: 已执行 %d 秒\n%s", timeout, timeoutInfo)
);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
listener.onError(rootPath, new InterruptedException("扫描被中断"));
} catch (ExecutionException e) {
listener.onError(rootPath, e);
}
}
private void resetScanState() {
cancelled = false;
currentScanningPath.set(null);
lastProcessedFile.set(null);
totalFilesScanned.set(0);
totalDirectoriesScanned.set(0);
}
private String buildTimeoutInfo(Path rootPath) {
return "扫描开始时间: " + formatDateTime(scanStartTime) + "\n" +
"扫描根目录: " + rootPath.toAbsolutePath() + "\n" +
"当前扫描路径: " + currentScanningPath.get() + "\n" +
"最后处理的文件: " + lastProcessedFile.get() + "\n" +
"已扫描文件数: " + totalFilesScanned.get() + "\n" +
"已扫描目录数: " + totalDirectoriesScanned.get() + "\n"
;
}
private void logScanStatistics() {
LocalDateTime endTime = LocalDateTime.now();
log.info(LoggerMarker.DEBUG_MARKER, """
扫描统计信息:
开始时间: {}
结束时间: {}
总扫描时间: {}
扫描文件数: {}
扫描目录数: {}
最后扫描路径: {}
""",
formatDateTime(scanStartTime),
formatDateTime(endTime),
java.time.Duration.between(scanStartTime, endTime).getSeconds(),
totalFilesScanned.get(),
totalDirectoriesScanned.get(),
currentScanningPath.get()
);
}
private void scanDirectory(Path dir, FileScanListener listener,
AtomicInteger processedFiles, AtomicLong totalFiles, int currentDepth) {
if (cancelled || currentDepth > maxDepth) return;
currentScanningPath.set(dir);
totalDirectoriesScanned.incrementAndGet();
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
for (Path path : stream) {
if (cancelled) break;
if (Files.isDirectory(path)) {
// 记录目录扫描开始
log.trace(LoggerMarker.TRACE_MARKER, "扫描目录: {}, 深度: {}", path, currentDepth + 1);
scanDirectory(path, listener, processedFiles, totalFiles, currentDepth + 1);
} else if (Files.isRegularFile(path)) {
lastProcessedFile.set(path.toAbsolutePath().toString());
processFile(path, listener, processedFiles, totalFiles);
}
}
} catch (IOException e) {
log.warn(LoggerMarker.TRACE_MARKER, "无法访问目录: {}", dir, e);
listener.onError(dir, e);
}
}
@ -115,14 +196,20 @@ public class RobustParallelScanner implements FileScanner {
try {
listener.onFileFound(file);
totalFilesScanned.incrementAndGet();
// 进度更新处理
if (listener instanceof ProgressAwareListener progressListener && totalFiles != null) {
if (totalFilesScanned.get() % 1000 == 0) {
log.debug(LoggerMarker.TRACE_MARKER, "已扫描 {} 个文件,当前文件: {}",
totalFilesScanned.get(), file);
}
if (listener instanceof ProgressAwareListener progressListener) {
int processed = processedFiles.incrementAndGet();
long total = totalFiles.get();
progressListener.onProgressUpdate(processed, (int)total);
}
} catch (Exception e) {
log.warn(LoggerMarker.TRACE_MARKER, "处理文件失败: {}", file, e);
listener.onError(file, e);
}
}
@ -139,6 +226,16 @@ public class RobustParallelScanner implements FileScanner {
path.toString().contains("$")) {
throw new IOException("系统目录禁止访问: " + path);
}
long freeSpace = Files.getFileStore(path).getUsableSpace();
if (freeSpace < 1024 * 1024 * 10) { // 小于10MB
log.warn(LoggerMarker.TRACE_MARKER, "磁盘空间不足: {},可用空间: {} MB",
path, freeSpace / (1024 * 1024));
}
}
private String formatDateTime(LocalDateTime dateTime) {
if (dateTime == null) return "N/A";
return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
@Override

View File

@ -3,19 +3,37 @@ 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 javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
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;
/**
* The type Main stage controller.
*/
@ -38,6 +56,7 @@ public class MainStageController {
@FXML private MenuItem exitMI;
@FXML private MenuItem logoutMI;
@FXML private MenuItem settingMI;
@FXML private HBox stepAssistant;
private List<Tab> tabs;
private int currentIndex = 0;
@ -153,17 +172,113 @@ public class MainStageController {
SceneManager.openSettingView();
}
@FXML void onOpenHelpDoc(ActionEvent actionEvent) {
DialogUtil.showDetailedInformationDialog("未实现", "敬请期待","待完善文档后内置");
@FXML
void onOpenHelpDoc(ActionEvent actionEvent) {
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.pdf";
// 释放到外部的文件名
private static final String HELP_DOC_FILE_NAME = "用户使用说明书.pdf";
/**
* 使用系统默认程序打开文档
*/
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("版本", "版本信息","这里写些信息");
DialogUtil.showDetailedInformationDialog("关于软件", "数字化验收工具","软件版本:" + System.version() + "\n");
}
public void updateStepButtonsVisibility() {
Setting setting = System.getSetting();
boolean visible = setting.isEnableStep(); // 由enableStep控制
boolean visible = setting.isEnableStep();
// 设置容器可见性和管理状态
stepAssistant.setVisible(visible);
stepAssistant.setManaged(visible);
// 同时设置按钮的可见性可选因为容器已控制
nextB.setVisible(visible);
prevB.setVisible(visible);
// 如果隐藏按钮区域调整布局
if (!visible) {
// 确保TabPane占据剩余空间
VBox.setVgrow(tabPane, Priority.ALWAYS);
} else {
// 恢复默认的垂直增长策略
VBox.setVgrow(tabPane, Priority.ALWAYS);
VBox.setVgrow(stepAssistant, Priority.NEVER);
}
}
}

View File

@ -69,6 +69,9 @@ public class SceneManager {
mainController = loader.getController(); // 保存控制器引用
Scene newScene = new Scene(root, 1200, 900);
String dialogCssPath = Objects.requireNonNull(Main.class.getResource("/css/inner/dialog.css")).toExternalForm();
newScene.getStylesheets().addAll(dialogCssPath);
applyFadeTransition(root);
primaryStage.setScene(newScene);
@ -96,10 +99,14 @@ public class SceneManager {
Stage settingStage = new Stage();
settingStage.getIcons().add(logo);
settingStage.setTitle("数字化验收工具 - 设置");
Scene scene = new Scene(root, 300, 206);
Scene scene = new Scene(root, 362, 332);
settingStage.setScene(scene); // 默认大小可调
settingStage.initOwner(primaryStage); // 设置父窗口
settingStage.initModality(Modality.WINDOW_MODAL);
String dialogCssPath = Objects.requireNonNull(Main.class.getResource("/css/inner/dialog.css")).toExternalForm();
scene.getStylesheets().addAll(dialogCssPath);
settingStage.setResizable(false);
settingStage.centerOnScreen();
@ -139,6 +146,8 @@ public class SceneManager {
Parent root = FXMLLoader.load(Objects.requireNonNull(Main.class.getResource(fxmlPath)));
Scene newScene = new Scene(root, width, height);
String dialogCssPath = Objects.requireNonNull(Main.class.getResource("/css/inner/dialog.css")).toExternalForm();
newScene.getStylesheets().addAll(dialogCssPath);
// 淡入效果
applyFadeTransition(root);

View File

@ -18,7 +18,7 @@ import java.util.ResourceBundle;
public class SettingDialogController implements Initializable {
@FXML private CheckBox enableStepCB;
@FXML private CheckBox enableStepCB, enableTaskTimeoutCB;
@FXML private Button resetB, saveB, cancelB;
@FXML private Spinner<Long> scanTimeOutS, taskTimeOutS;
@ -35,11 +35,19 @@ public class SettingDialogController implements Initializable {
setting = System.getSetting();
// 初始化 Spinner
enableTaskTimeoutCB.setSelected(setting.isEnableTaskTimeout());
scanTimeOutS.setValueFactory(new LongSpinnerValueFactory(1, 3600, setting.getScanTimeout()));
scanTimeOutS.setEditable(true);
if (setting.isEnableTaskTimeout()) {
taskTimeOutS.setEditable(true);
taskTimeOutS.setDisable(false);
} else {
taskTimeOutS.setDisable(true);
}
taskTimeOutS.setValueFactory(new LongSpinnerValueFactory(1, 3600 * 24, setting.getTaskTimeout()));
taskTimeOutS.setEditable(true);
// 添加焦点离开时校验
addSpinnerValidation(scanTimeOutS, SINGLE_MIN, SINGLE_MAX);
addSpinnerValidation(taskTimeOutS, TOTAL_MIN, TOTAL_MAX);
@ -53,6 +61,7 @@ public class SettingDialogController implements Initializable {
setting.setScanTimeout(scanTimeOutS.getValue());
setting.setTaskTimeout(taskTimeOutS.getValue());
setting.setEnableStep(enableStepCB.isSelected());
setting.setEnableTaskTimeout(enableTaskTimeoutCB.isSelected());
// 保存到配置文件
System.saveSettingsNow();
// 通知主界面刷新按钮状态
@ -69,6 +78,7 @@ public class SettingDialogController implements Initializable {
scanTimeOutS.getValueFactory().setValue(30L); // 默认单次超时
taskTimeOutS.getValueFactory().setValue(300L); // 默认总超时
enableStepCB.setSelected(false);
enableTaskTimeoutCB.setSelected(false);
}
/** 取消修改 */
@ -83,14 +93,19 @@ public class SettingDialogController implements Initializable {
}
@FXML
void onCheckTwo(MouseDragEvent mouseDragEvent) {
void onCheckThree(MouseDragEvent mouseDragEvent) {
validateSpinnerValue(taskTimeOutS, 60, 3600 * 24);
}
@FXML
void onSettingThree(ActionEvent actionEvent) {
void onSettingTwo(ActionEvent actionEvent) {
taskTimeOutS.setDisable(!enableTaskTimeoutCB.isSelected());
}
@FXML
void onSettingFour(ActionEvent actionEvent) {
}
/** 给 Spinner 添加离开焦点校验 */
private void addSpinnerValidation(Spinner<Long> spinner, long min, long max) {
spinner.getEditor().focusedProperty().addListener((obs, oldVal, newVal) -> {

View File

@ -1,5 +1,6 @@
package top.r3944realms.docchecktoolrefactored.ui.module;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
@ -10,6 +11,7 @@ import javafx.stage.DirectoryChooser;
import javafx.stage.Stage;
import lombok.extern.slf4j.Slf4j;
import top.r3944realms.docchecktoolrefactored.System;
import top.r3944realms.docchecktoolrefactored.core.ScanningException;
import top.r3944realms.docchecktoolrefactored.ui.SceneManager;
import top.r3944realms.docchecktoolrefactored.ui.task.DuplicateDocumentDetectionTask;
import top.r3944realms.docchecktoolrefactored.ui.utils.DialogUtil;
@ -17,6 +19,7 @@ import top.r3944realms.docchecktoolrefactored.ui.utils.ProgressBar;
import top.r3944realms.docchecktoolrefactored.util.LoggerMarker;
import java.io.File;
import java.util.List;
/**
* The type Duplicate document pane controller.
@ -31,6 +34,7 @@ public class DuplicateDocumentPaneController {
@FXML private Button cancel1B;
private final ProgressBar progressBar = new ProgressBar();
private DuplicateDocumentDetectionTask currentTask; // 保存任务引用
/**
* On select folder.
*
@ -54,6 +58,7 @@ public class DuplicateDocumentPaneController {
@FXML void onStart(ActionEvent actionEvent) {
// 禁用开始按钮避免重复点击
start1B.setDisable(true);
removeResultStyle();
log.info(LoggerMarker.DEBUG_MARKER, "用户点击了开始查重按钮");
String folderPath = loadFolder1TF.getText();
if (folderPath == null || folderPath.trim().isEmpty()) {
@ -63,6 +68,10 @@ public class DuplicateDocumentPaneController {
start1B.setDisable(false);
return;
}
// 调用提取的样式方法移除结果文本域错误态
removeResultStyle();
// 调用提取的样式方法添加开始按钮加载态
addStartButtonLoadingStyle();
cancel1B.setDisable(false);
// 显示进度条窗口
progressBar.showProgress(SceneManager.getPrimaryStage(), "重复文件检测", "正在初始化扫描...");
@ -82,10 +91,10 @@ public class DuplicateDocumentPaneController {
task.progressProperty().addListener(progressChangeListener);
// 绑定任务的消息到结果文本区域
ChangeListener<String> messageChangeListener = (observable, oldValue, newValue) -> {
result1TA.setText(newValue);
};
task.messageProperty().addListener(messageChangeListener);
// ChangeListener<String> messageChangeListener = (observable, oldValue, newValue) -> {
// result1TA.setText(newValue);
// };
// task.messageProperty().addListener(messageChangeListener);
// 绑定取消按钮 -> task.cancel()
progressBar.setOnCancel(() -> {
@ -101,6 +110,9 @@ public class DuplicateDocumentPaneController {
result1TA.setText(task.getValue());
start1B.setDisable(false);
cancel1B.setDisable(true);
// 调用提取的样式方法移除开始按钮加载态
removeStartButtonLoadingStyle();
addResultSuccessfulStyle();
log.info(LoggerMarker.RELEASE_MARKER, "查重任务完成,结果如下:{}", task.getValue());
});
@ -109,10 +121,22 @@ public class DuplicateDocumentPaneController {
progressBar.closeProgress();
Throwable exception = task.getException();
currentTask.progressProperty().removeListener(progressChangeListener);
currentTask.messageProperty().removeListener(messageChangeListener);
result1TA.setText("检测过程中发生错误: " + exception.getMessage());
DialogUtil.showDetailedErrorDialog("错误", "检测过程中发生错误", exception.getMessage());
// currentTask.messageProperty().removeListener(messageChangeListener);
// 调用提取的样式方法添加结果文本域错误态
addResultErrorStyle();
if (exception instanceof ScanningException e1) {
List<String> list = e1.exceptions.stream().map(Throwable::getMessage).toList();
String message = String.join("\n", list);
DialogUtil.showDetailedErrorDialog("错误", "检测过程中发生错误", message);
result1TA.setText("检测过程中发生错误: " + message);
} else {
DialogUtil.showDetailedErrorDialog("错误", "检测过程中发生错误", exception.getMessage());
result1TA.setText("检测过程中发生错误: " + exception.getMessage());
}
start1B.setDisable(false);
// 调用提取的样式方法移除开始按钮加载态
removeStartButtonLoadingStyle();
cancel1B.setDisable(true);
log.error(LoggerMarker.RELEASE_MARKER, "查重任务失败", exception);
});
@ -121,9 +145,11 @@ public class DuplicateDocumentPaneController {
task.setOnCancelled(e -> {
progressBar.closeProgress();
currentTask.progressProperty().removeListener(progressChangeListener);
currentTask.messageProperty().removeListener(messageChangeListener);
// currentTask.messageProperty().removeListener(messageChangeListener);
result1TA.appendText("\n检测已取消");
start1B.setDisable(false);
// 调用提取的样式方法移除开始按钮加载态
removeStartButtonLoadingStyle();
cancel1B.setDisable(true);
log.info(LoggerMarker.RELEASE_MARKER, "查重任务已被取消");
});
@ -140,4 +166,51 @@ public class DuplicateDocumentPaneController {
log.warn(LoggerMarker.DEBUG_MARKER, "没有正在运行的任务可取消");
}
}
}
// ======================== 提取的样式变更方法 ========================
/**
* 为结果文本域添加错误态样式
*/
private void addResultErrorStyle() {
Platform.runLater(() -> {
result1TA.getStyleClass().add("result-error");
});
}
/**
* 为结果文本域添加成功态样式
*/
private void addResultSuccessfulStyle() {
Platform.runLater(() -> {
result1TA.getStyleClass().add("result-error");
});
}
/**
* 为结果文本域移除样式
*/
private void removeResultStyle() {
Platform.runLater(() -> {
result1TA.getStyleClass().remove("result-error");
result1TA.getStyleClass().remove("result-success");
});
}
/**
* 为开始按钮添加加载态样式
*/
private void addStartButtonLoadingStyle() {
Platform.runLater(() -> {
start1B.getStyleClass().add("loading");
});
}
/**
* 为开始按钮移除加载态样式
*/
private void removeStartButtonLoadingStyle() {
Platform.runLater(() -> {
start1B.getStyleClass().remove("loading");
});
}
// ====================================================================
}

View File

@ -1,5 +1,6 @@
package top.r3944realms.docchecktoolrefactored.ui.module;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
@ -119,6 +120,7 @@ public class PathCheckPaneController implements Initializable {
*/
@FXML void onGenerateLA(ActionEvent actionEvent) {
generateLogicalAddress2B.setDisable(true);
removeResultStyle();
log.info(LoggerMarker.DEBUG_MARKER, "用户点击了生成逻辑地址文件按钮");
String filePath = loadCatalog2TF.getText();
if (filePath.isEmpty()) {
@ -133,14 +135,14 @@ 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) {
result2TA.setText("未选择保存位置");
log.warn(LoggerMarker.DEBUG_MARKER, "用户未选择任何文件");
DialogUtil.showWarningDialog("警告", "操作有误", "未选择保存位置");
generateLogicalAddress2B.setDisable(true);
generateLogicalAddress2B.setDisable(false);
return;
}
System.setLastModifiedFile(outputFile);
@ -177,8 +179,9 @@ public class PathCheckPaneController implements Initializable {
// 当任务完成时显示完整结果
task.setOnSucceeded(e -> {
progressBar.closeProgress();
result2TA.setText(task.getValue());
result2TA.setText("生成逻辑路径 csv 文件任务完成输出csv文件路径"+task.getValue());
generateLogicalAddress2B.setDisable(false);
addResultSuccessfulStyle();
log.info(LoggerMarker.RELEASE_MARKER, "生成逻辑路径 csv 文件任务完成输出csv文件路径{}", task.getValue());
});
@ -186,9 +189,19 @@ public class PathCheckPaneController implements Initializable {
task.setOnFailed(e -> {
progressBar.closeProgress();
Throwable exception = task.getException();
currentTask.progressProperty().removeListener(progressChangeListener);
currentTask.messageProperty().removeListener(messageChangeListener);
result2TA.setText("检测过程中发生错误: " + exception.getMessage());
// 移除监听器时使用 task 而不是 currentTask
task.progressProperty().removeListener(progressChangeListener);
task.messageProperty().removeListener(messageChangeListener);
// 检查是否是重复档号的特殊信息
if (exception.getMessage() != null && exception.getMessage().startsWith("存在重复档号:")) {
result2TA.setText(exception.getMessage());
} else {
result2TA.setText("检测过程中发生错误: " + exception.getMessage());
}
addResultErrorStyle();
DialogUtil.showDetailedErrorDialog("错误", "检测过程中发生错误: ", exception.getMessage());
generateLogicalAddress2B.setDisable(false);
log.error(LoggerMarker.RELEASE_MARKER, "生成逻辑路径 csv 文件任务失败", exception);
@ -200,6 +213,7 @@ public class PathCheckPaneController implements Initializable {
currentTask.progressProperty().removeListener(progressChangeListener);
currentTask.messageProperty().removeListener(messageChangeListener);
result2TA.appendText("\n检测已取消");
removeResultStyle();
generateLogicalAddress2B.setDisable(false);
log.info(LoggerMarker.RELEASE_MARKER, "生成逻辑路径 csv 文件任务已被取消");
});
@ -214,7 +228,9 @@ public class PathCheckPaneController implements Initializable {
* @param actionEvent the action event
*/
@FXML void onGeneratePA(ActionEvent actionEvent) {
generatePhysicalAddress2B.setDisable(true);
removeResultStyle();
String folderPath = loadJPGFolder2TF.getText();
if (folderPath.isEmpty()) {
result2TA.setText("请先选择文件夹。");
@ -235,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());
@ -280,9 +296,10 @@ public class PathCheckPaneController implements Initializable {
// 当任务完成时显示完整结果
task.setOnSucceeded(e -> {
progressBar.closeProgress();
result2TA.setText(task.getValue());
result2TA.setText("生成物理路径 csv 文件任务完成输出csv文件路径"+task.getValue());
generatePhysicalAddress2B.setDisable(false);
log.info(LoggerMarker.RELEASE_MARKER, "生成物理路径 csv 文件任务完成输出csv文件路径{}", task.getValue());
addResultSuccessfulStyle();
});
// 处理任务失败情况
@ -292,6 +309,7 @@ public class PathCheckPaneController implements Initializable {
currentTask.progressProperty().removeListener(progressChangeListener);
currentTask.messageProperty().removeListener(messageChangeListener);
result2TA.setText("检测过程中发生错误: " + exception.getMessage());
addResultErrorStyle();
DialogUtil.showDetailedErrorDialog("错误", "检测过程中发生错误: ", exception.getMessage());
generatePhysicalAddress2B.setDisable(false);
log.error(LoggerMarker.RELEASE_MARKER, "生成物理路径 csv 文件任务失败", exception);
@ -303,6 +321,7 @@ public class PathCheckPaneController implements Initializable {
currentTask.progressProperty().removeListener(progressChangeListener);
currentTask.messageProperty().removeListener(messageChangeListener);
result2TA.appendText("\n检测已取消");
removeResultStyle();
generatePhysicalAddress2B.setDisable(false);
log.info(LoggerMarker.RELEASE_MARKER, "生成物理路径 csv 文件任务已被取消");
});
@ -328,7 +347,8 @@ public class PathCheckPaneController implements Initializable {
return;
}
start2B.setDisable(true);
removeStartButtonLoadingStyle();
start2B.setText("比对中...");
// 显示进度条窗口
cancelableProgressBar.showProgress(SceneManager.getPrimaryStage(), "文件查漏检查", "正在初始化...");
// 创建后台任务
@ -346,10 +366,10 @@ public class PathCheckPaneController implements Initializable {
task.progressProperty().addListener(progressChangeListener);
// 绑定任务的消息到结果文本区域
ChangeListener<String> messageChangeListener = (observable, oldValue, newValue) -> {
result2TA.setText(newValue);
};
task.messageProperty().addListener(messageChangeListener);
// ChangeListener<String> messageChangeListener = (observable, oldValue, newValue) -> {
// result2TA.setText(newValue);
// };
// task.messageProperty().addListener(messageChangeListener);
// 绑定取消按钮 -> task.cancel()
cancelableProgressBar.setOnCancel(() -> {
@ -361,6 +381,8 @@ public class PathCheckPaneController implements Initializable {
task.setOnSucceeded(event -> {
cancelableProgressBar.closeProgress();
start2B.setDisable(false);
addStartButtonLoadingStyle();
start2B.setText("开始对比");
result2TA.setText(AddressFileComparator.ComparisonResult.generateComparisonResults(task.getValue(), loadFolderType2CB.getValue().compareMode));
result2TA.setText(AddressFileComparator.ComparisonResult.generateComparisonResults(task.getValue(), loadFolderType2CB.getValue().compareMode));
//内部比较器已有此处忽略日志打印
@ -371,6 +393,8 @@ public class PathCheckPaneController implements Initializable {
Throwable exception = task.getException();
result2TA.setText("文件比对失败: " + exception.getMessage());
start2B.setDisable(false);
removeStartButtonLoadingStyle();
start2B.setText("开始对比");
DialogUtil.showDetailedErrorDialog("错误", "文件比对失败:", exception.getMessage());
log.error(LoggerMarker.RELEASE_MARKER, "查漏任务失败", exception);
});
@ -379,8 +403,10 @@ public class PathCheckPaneController implements Initializable {
cancelableProgressBar.closeProgress();
result2TA.appendText("\n检测已取消");
start2B.setDisable(false);
removeStartButtonLoadingStyle();
start2B.setText("开始对比");
currentTask.progressProperty().removeListener(progressChangeListener);
currentTask.messageProperty().removeListener(messageChangeListener);
// currentTask.messageProperty().removeListener(messageChangeListener);
log.info(LoggerMarker.RELEASE_MARKER, "查漏任务取消");
});
@ -447,6 +473,51 @@ public class PathCheckPaneController implements Initializable {
this.compareMode = compareMode;
}
}
// ======================== 提取的样式变更方法 ========================
/**
* 为结果文本域添加错误态样式
*/
private void addResultErrorStyle() {
Platform.runLater(() -> {
result2TA.getStyleClass().add("result-error");
});
}
/**
* 为结果文本域添加成功态样式
*/
private void addResultSuccessfulStyle() {
Platform.runLater(() -> {
result2TA.getStyleClass().add("result-error");
});
}
/**
* 为结果文本域移除样式
*/
private void removeResultStyle() {
Platform.runLater(() -> {
result2TA.getStyleClass().remove("result-error");
result2TA.getStyleClass().remove("result-success");
});
}
/**
* 为开始按钮添加加载态样式
*/
private void addStartButtonLoadingStyle() {
Platform.runLater(() -> {
start2B.getStyleClass().add("loading");
});
}
/**
* 为开始按钮移除加载态样式
*/
private void removeStartButtonLoadingStyle() {
Platform.runLater(() -> {
start2B.getStyleClass().remove("loading");
});
}
// ====================================================================
}

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

@ -1,5 +1,6 @@
package top.r3944realms.docchecktoolrefactored.ui.module;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
@ -50,6 +51,10 @@ public class StorageCarrierPaneController {
@FXML
private Button clearSelectedFoldersButton;
// 保存按钮原始文本用于状态恢复
private final String calculateHashOriginalText = "计算哈希值";
private final String generateHashOriginalText = "生成哈希值表文件";
private final top.r3944realms.docchecktoolrefactored.ui.utils.ProgressBar progressBar = new top.r3944realms.docchecktoolrefactored.ui.utils.ProgressBar(false);
@FXML
void onSelectLD(ActionEvent event) {
@ -81,7 +86,11 @@ public class StorageCarrierPaneController {
loadDigitalOutcomes.setText(currentText + File.pathSeparator + folderPath);
}
}
log.info(LoggerMarker.DEBUG_MARKER, "用户选择了文件夹: {}", selectedFolder.getAbsolutePath());
// 添加成功样式
applySuccessStyle(loadDigitalOutcomes);
} else {
log.info(LoggerMarker.DEBUG_MARKER, "用户取消了文件夹选择");
}
@ -93,6 +102,9 @@ public class StorageCarrierPaneController {
loadDigitalOutcomes.setText("");
result7TA.setText("已清除所有已选择的文件夹");
log.info(LoggerMarker.DEBUG_MARKER, "已清除所有已选择的文件夹");
// 移除样式
resetStyle(loadDigitalOutcomes);
}
@ -109,6 +121,9 @@ public class StorageCarrierPaneController {
System.setLastModifiedFile(selectedFile);
loadCompressedFile.setText(selectedFile.getAbsolutePath());
log.info(LoggerMarker.DEBUG_MARKER, "用户选择了RAR文件: {}", selectedFile.getAbsolutePath());
// 添加成功样式
applySuccessStyle(loadCompressedFile);
} else {
log.info(LoggerMarker.DEBUG_MARKER, "用户取消了RAR文件选择");
}
@ -124,6 +139,7 @@ public class StorageCarrierPaneController {
result7TA.setText("请先选择一个 .rar 文件");
DialogUtil.showWarningDialog("警告", "操作有误", "请先选择一个 .rar 文件");
generateHashFile7B.setDisable(false);
// 重置样式
return;
}
@ -136,16 +152,22 @@ public class StorageCarrierPaneController {
return;
}
try {
// 添加加载样式
applyLoadingStyle(generateHashFile7B, "处理中...");
log.info(LoggerMarker.DEBUG_MARKER, "开始计算RAR文件MD5哈希值: {}", filePath);
MD5HashCalculator hashCalculator = new MD5HashCalculator();
String hashResult = hashCalculator.calculateHash(file.toPath());
result7TA.setText("计算结果:\n" + hashResult);
applySuccessStyle(result7TA);
generateHashFile7B.setDisable(false);
log.info(LoggerMarker.DEBUG_MARKER, "文件哈希值计算完成: {}", hashResult);
log.info(LoggerMarker.RELEASE_MARKER, "文件哈希值计算完成.\t{}文件的哈希值: {}", filePath,hashResult);
} catch (IOException e) {
log.error(LoggerMarker.DEBUG_MARKER, "计算文件哈希值时出错: {}", filePath, e);
DialogUtil.showDetailedErrorDialog("错误", "生成哈希文件时出错:", e.getMessage());
generateHashFile7B.setDisable(false);
// 重置样式
resetButtonStyle(generateHashFile7B, generateHashOriginalText);
result7TA.setText("计算哈希值时出错: " + e.getMessage());
}
}
@ -179,13 +201,13 @@ public class StorageCarrierPaneController {
return;
}
}
applyLoadingStyle(caculateHash7B, "处理中...");
FileChooser fileChooser = System.getFileChooser();
fileChooser.setTitle("选择保存哈希列表文件的位置");
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());
@ -219,17 +241,20 @@ public class StorageCarrierPaneController {
};
task.progressProperty().addListener(progressChangeListener);
// 绑定任务的消息到结果文本区域实时显示进度
ChangeListener<String> messageChangeListener = (observable, oldValue, newValue) -> {
result7TA.setText(newValue);
};
task.messageProperty().addListener(messageChangeListener);
// ChangeListener<String> messageChangeListener = (observable, oldValue, newValue) -> {
// result7TA.setText(newValue);
// };
// task.messageProperty().addListener(messageChangeListener);
// 任务成功完成
task.setOnSucceeded(e -> {
progressBar.closeProgress();
log.info(LoggerMarker.DEBUG_MARKER, "哈希文件生成任务成功完成");
caculateHash7B.setDisable(false);
applySuccessStyle(result7TA);
resetButtonStyle(caculateHash7B, calculateHashOriginalText);
result7TA.setText(task.getValue());
log.info(LoggerMarker.RELEASE_MARKER, task.getValue());
});
// 任务失败处理
@ -237,10 +262,12 @@ public class StorageCarrierPaneController {
progressBar.closeProgress();
Throwable exception = task.getException();
task.progressProperty().removeListener(progressChangeListener);
task.messageProperty().removeListener(messageChangeListener);
// task.messageProperty().removeListener(messageChangeListener);
caculateHash7B.setDisable(false);
String errorMsg = "生成哈希文件时出错: " + (exception != null ? exception.getMessage() : "未知错误");
applyErrorStyle(result7TA);
DialogUtil.showDetailedErrorDialog("错误", "生成哈希文件时出错", errorMsg);
resetButtonStyle(caculateHash7B, calculateHashOriginalText);
log.error(LoggerMarker.RELEASE_MARKER, "哈希文件生成任务失败", exception);
result7TA.setText(errorMsg);
});
@ -249,8 +276,10 @@ public class StorageCarrierPaneController {
task.setOnCancelled(e -> {
progressBar.closeProgress();
task.progressProperty().removeListener(progressChangeListener);
task.messageProperty().removeListener(messageChangeListener);
// task.messageProperty().removeListener(messageChangeListener);
caculateHash7B.setDisable(false);
resetStyle(result7TA);
resetButtonStyle(caculateHash7B, calculateHashOriginalText);
log.info(LoggerMarker.DEBUG_MARKER, "哈希文件生成任务被用户取消");
result7TA.setText("哈希文件生成操作已取消");
});
@ -260,7 +289,54 @@ public class StorageCarrierPaneController {
thread.setDaemon(true);
thread.start();
}
// ======================== 提取的样式变更方法 ========================
// 样式控制工具方法
private void applyLoadingStyle(Button button, String text) {
Platform.runLater(() -> {
button.setText(text);
button.getStyleClass().add("loading");
});
}
private void resetButtonStyle(Button button, String originalText) {
Platform.runLater(() -> {
button.setText(originalText);
button.getStyleClass().remove("loading");
});
}
private void applySuccessStyle(TextField textField) {
Platform.runLater(() -> {
textField.getStyleClass().remove("error");
textField.getStyleClass().add("success");
});
}
private void applySuccessStyle(TextArea textArea) {
Platform.runLater(() -> {
textArea.getStyleClass().removeAll("error");
textArea.getStyleClass().add("success");
});
}
private void applyErrorStyle(TextArea textArea) {
Platform.runLater(() -> {
textArea.getStyleClass().removeAll("success");
textArea.getStyleClass().add("error");
});
}
private void resetStyle(TextField textField) {
Platform.runLater(() -> {
textField.getStyleClass().removeAll("success", "error");
});
}
private void resetStyle(TextArea textArea) {
Platform.runLater(() -> {
textArea.getStyleClass().removeAll("success", "error");
});
}
// ====================================================================
}

View File

@ -2,8 +2,10 @@ package top.r3944realms.docchecktoolrefactored.ui.task;
import javafx.concurrent.Task;
import lombok.extern.slf4j.Slf4j;
import top.r3944realms.docchecktoolrefactored.System;
import top.r3944realms.docchecktoolrefactored.core.AddressFileComparator;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@ -71,9 +73,10 @@ public class AddressFileComparisonTask extends Task<AddressFileComparator.Compar
try {
// 构建显示文本
return comparator.compareFiles(physicalFilePath, logicalFilePath, compareMode)
.get(timeoutSeconds, TimeUnit.SECONDS);
CompletableFuture<AddressFileComparator.ComparisonResult> comparisonResult = comparator.compareFiles(physicalFilePath, logicalFilePath, compareMode);
if (System.getSetting().isEnableTaskTimeout()) {
return comparisonResult.get(timeoutSeconds, TimeUnit.SECONDS);
} else return comparisonResult.get();
} catch (TimeoutException e) {
updateMessage("文件比对超时,请考虑在‘文件’-‘设置’里增加‘步骤任务超时时间’。");
log.error("文件比对超时", e);

View File

@ -71,13 +71,28 @@ public class AddressFileGenerationTask extends Task<String> {
try {
generator.generateAddressFile(sourcePath, outputFile, folderType);
} catch (Exception e) {
throw new RuntimeException("地址文件生成失败", e);
// 不包装已经格式化好的重复档号异常
if (e instanceof RuntimeException &&
e.getMessage() != null &&
e.getMessage().startsWith("存在重复档号:")) {
outputFile.delete(); // 删除空内容的csv文件
throw e; // 直接抛出保留原始消息
}
if (e instanceof RuntimeException &&
e.getMessage() != null &&
e.getMessage().startsWith("存在重复文件名:")) {
outputFile.delete();// 删除空内容的csv文件
throw e; // 直接抛出保留原始消息
}
throw e;
}
});
try {
// 等待执行完成或超时
future.get(System.getSetting().getTaskTimeout(), TimeUnit.SECONDS);
if (System.getSetting().isEnableTaskTimeout()){
future.get(System.getSetting().getTaskTimeout(), TimeUnit.SECONDS);
} else future.get();
return outputFile.getAbsolutePath();
} catch (TimeoutException e) {

View File

@ -25,6 +25,7 @@ public class DuplicateDocumentDetectionTask extends Task<String>{
private final String folderPath;
private final DuplicateFinder duplicateFinder;
public DuplicateDocumentDetectionTask(String folderPath) {
this.folderPath = folderPath;
// 创建带进度更新的扫描器
@ -48,6 +49,7 @@ public class DuplicateDocumentDetectionTask extends Task<String>{
// 用于统计文件总数
AtomicInteger totalFiles = new AtomicInteger(0);
// 使用 RobustParallelScanner MD5HashCalculator 进行并行扫描和哈希计算
// 设置进度回调
duplicateFinder.setProgressCallback(new DuplicateFinder.ProgressCallback() {
@ -66,16 +68,16 @@ public class DuplicateDocumentDetectionTask extends Task<String>{
@Override
public void onPhaseProgress(DuplicateFinder.Phase phase, int current, int total) {
if (total > 0) {
updateProgress(current, total);
switch (phase) {
case GROUP_BY_SIZE:
totalFiles.set(total);
updateMessage(String.format("正在按文件大小分组: %d/%d", current, total));
break;
case CALCULATE_HASH:
updateMessage(String.format("正在计算哈希值: %d/%d", current, total));
break;
}
// 控制更新频率
String msg = switch (phase) {
case GROUP_BY_SIZE -> {
totalFiles.getAndIncrement();
yield String.format("正在按文件大小分组: %d/%d", current, total);
}
case CALCULATE_HASH -> String.format("正在计算哈希值: %d/%d", current, total);
};
updateMessage(msg);
updateProgress(current, total);
}
}
@ -112,11 +114,14 @@ public class DuplicateDocumentDetectionTask extends Task<String>{
findThread.start();
// 简单等待Task取消时会自动中断
long totalTimeout = System.getSetting().getTaskTimeout();
if (!latch.await(totalTimeout, TimeUnit.SECONDS)) {
duplicateFinder.shutdown();
throw new TimeoutException(String.format("任务超时(%d秒请考虑在文件-‘设置’里增加‘步骤任务超时时间’", totalTimeout));
}
boolean isEnableTaskTimout = System.getSetting().isEnableTaskTimeout();
if (isEnableTaskTimout) {
long totalTimeout = System.getSetting().getTaskTimeout();
if (!latch.await(totalTimeout, TimeUnit.SECONDS)) {
duplicateFinder.shutdown();
throw new TimeoutException(String.format("任务超时(%d秒请考虑在文件-‘设置’里增加‘步骤任务超时时间’", totalTimeout));
}
} else latch.await();
// 检查是否有错误
if (errorRef.get() != null) {
@ -127,50 +132,59 @@ public class DuplicateDocumentDetectionTask extends Task<String>{
List<DuplicateGroup> duplicateGroups = resultRef.get();
// 构建最终结果
return generateResult(duplicateGroups, totalFiles);
return generateResult(duplicateGroups, totalFiles,
duplicateFinder.getTargetFilesCount(),
duplicateFinder.getOtherFilesCount());
}
private static @NotNull String generateResult(List<DuplicateGroup> duplicateGroups, AtomicInteger totalFiles) {
StringBuilder result = new StringBuilder();
// Update method signature
private static @NotNull String generateResult(List<DuplicateGroup> duplicateGroups,
AtomicInteger totalFiles,
int targetFilesCount,
int otherFilesCount) {
StringBuilder result = new StringBuilder();
// Calculate total duplicate files
int totalDuplicateFiles = duplicateGroups.stream()
.mapToInt(group -> group.fileMetas().size())
.sum();
// 计算总文件数所有组中的文件数
int totalDuplicateFiles = duplicateGroups.stream()
.mapToInt(group -> group.fileMetas().size())
.sum();
int totalGroups = duplicateGroups.size();
int totalGroups = duplicateGroups.size();
result.append(String.format("总共扫描文件数: %d\n", totalFiles.get()));
result.append(String.format("其中目标文件(jpg jpeg png bmg gif tiff jp2 pdf ofd )数: %d\t", targetFilesCount));
result.append(String.format(" 非目标文件数: %d\n", otherFilesCount));
result.append(String.format("发现重复文件组数: %d\n", totalGroups));
result.append(String.format("重复文件总数: %d\n", totalDuplicateFiles));
result.append(String.format("总共扫描文件数: %d\n", totalFiles.get()));
result.append(String.format("发现重复文件组数: %d\n", totalGroups));
result.append(String.format("重复文件总数: %d\n", totalDuplicateFiles));
// Rest of the existing implementation remains the same
if (!duplicateGroups.isEmpty()) {
result.append("\n详细重复文件信息:\n");
result.append("----------------------------------------\n");
if (!duplicateGroups.isEmpty()) {
result.append("\n详细重复文件信息:\n");
result.append("----------------------------------------\n");
int groupIndex = 1;
for (DuplicateGroup group : duplicateGroups) {
result.append(String.format("第 %d 组 (哈希值: %s, 大小: %d 字节)\n",
groupIndex, group.hash(), group.size()));
int groupIndex = 1;
for (DuplicateGroup group : duplicateGroups) {
result.append(String.format("第 %d 组 (哈希值: %s, 大小: %d 字节)\n",
groupIndex, group.hash(), group.size()));
int fileIndex = 1;
for (var file : group.fileMetas()) {
Path filePath = file.getPath();
result.append(String.format(" 文件%d: %s\n", fileIndex, filePath.toAbsolutePath()));
fileIndex++;
}
result.append("\n");
groupIndex++;
int fileIndex = 1;
for (var file : group.fileMetas()) {
Path filePath = file.getPath();
result.append(String.format(" 文件%d: %s\n", fileIndex, filePath.toAbsolutePath()));
fileIndex++;
}
} else {
result.append("\n没有发现重复文件\n");
result.append("\n");
groupIndex++;
}
result.append("检测完成!\n");
return result.toString();
} else {
result.append("\n没有发现重复文件\n");
}
result.append("检测完成!\n");
return result.toString();
}
@Override
protected void cancelled() {
super.cancelled();
@ -178,4 +192,5 @@ public class DuplicateDocumentDetectionTask extends Task<String>{
duplicateFinder.shutdown();
updateMessage("操作已被取消");
}
}

View File

@ -41,7 +41,7 @@ public class HashFileGenerationTask extends Task<String> {
generator.setCallback((current, total) -> {
updateProgress(current, total);
updateMessage("处理文件: " + current + "/" + total);
if (current % 500 == 0 || current == total) {
if (current % 5000 == 0 || current == total) {
log.info(LoggerMarker.DEBUG_MARKER, "处理进度: {}/{}", current, total);
}
});
@ -57,9 +57,12 @@ public class HashFileGenerationTask extends Task<String> {
});
try {
// 设置超时时间
long timeoutSeconds = System.getSetting().getTaskTimeout();
future.get(timeoutSeconds, TimeUnit.SECONDS);
boolean enableTaskTimeout = System.getSetting().isEnableTaskTimeout();
if (enableTaskTimeout) {
// 设置超时时间
long timeoutSeconds = System.getSetting().getTaskTimeout();
future.get(timeoutSeconds, TimeUnit.SECONDS);
} else future.get();
log.info(LoggerMarker.DEBUG_MARKER, "哈希文件生成任务完成,输出文件: {}", finalOutputFile.getAbsolutePath());
return "哈希列表文件已生成: " + finalOutputFile.getAbsolutePath();

View File

@ -2,16 +2,30 @@ package top.r3944realms.docchecktoolrefactored.ui.utils;
import javafx.application.Platform;
import javafx.scene.control.*;
import javafx.scene.image.Image;
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;
import java.util.Objects;
/**
* 对话框工具类
*/
public class DialogUtil {
// -------------------------- 1. 全局CSS路径定义 --------------------------
// 定义dialog-util.css的资源路径需与项目中CSS文件实际路径一致
private static final String DIALOG_CSS_PATH = Objects.requireNonNull(
Main.class.getResource("/css/inner/dialog.css")
).toExternalForm();
private static final Image DIALOG_ICON = new Image(
Objects.requireNonNull(Main.class.getResourceAsStream("/img/icon.jpg"))
);
/**
* Show exit confirmation boolean.
*
@ -21,6 +35,7 @@ public class DialogUtil {
public static boolean showExitConfirmation(Window owner) {
return showConfirmationDialog("确认退出", "您确定要退出程序吗?", "请确认您的操作");
}
/**
* Show exit confirmation boolean.
*
@ -40,13 +55,19 @@ public class DialogUtil {
* @return the boolean
*/
public static boolean showConfirmationDialog(String title, String header, String content) {
// 创建确认对话框
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
// -------------------------- 2. 绑定CSS + 添加类型标识 --------------------------
bindDialogCss(alert, Alert.AlertType.CONFIRMATION);
setAlertIcon(alert);
alert.setTitle(title);
alert.setHeaderText(header);
alert.setContentText(content);
ButtonType yesButton = new ButtonType("", ButtonBar.ButtonData.YES);
ButtonType noButton = new ButtonType("", ButtonBar.ButtonData.NO);
alert.getButtonTypes().setAll(yesButton, noButton);
Optional<ButtonType> result = alert.showAndWait();
return result.isPresent() && result.get() == yesButton;
}
@ -61,6 +82,7 @@ public class DialogUtil {
public static void showInformationDialog(String title, String header, String content) {
Platform.runLater(() -> {
Alert alert = createAlert(Alert.AlertType.INFORMATION, title, header, content);
setAlertIcon(alert);
alert.showAndWait();
});
}
@ -75,6 +97,7 @@ public class DialogUtil {
public static void showWarningDialog(String title, String header, String content) {
Platform.runLater(() -> {
Alert alert = createAlert(Alert.AlertType.WARNING, title, header, content);
setAlertIcon(alert);
alert.showAndWait();
});
}
@ -89,14 +112,20 @@ public class DialogUtil {
public static void showErrorDialog(String title, String header, String content) {
Platform.runLater(() -> {
Alert alert = createAlert(Alert.AlertType.ERROR, title, header, content);
setAlertIcon(alert);
alert.showAndWait();
});
}
/**
* 创建支持长文本的对话框
*/
private static Alert createAlert(Alert.AlertType alertType, String title, String header, String content) {
Alert alert = new Alert(alertType);
// -------------------------- 2. 绑定CSS + 添加类型标识 --------------------------
bindDialogCss(alert, alertType);
setAlertIcon(alert);
alert.setTitle(title);
alert.setHeaderText(header);
@ -109,19 +138,23 @@ public class DialogUtil {
textArea.setMaxWidth(Double.MAX_VALUE);
textArea.setMaxHeight(Double.MAX_VALUE);
// 设置文本区域样式
textArea.setStyle("-fx-font-family: monospace; -fx-font-size: 12px;");
// -------------------------- 3. 移除内联样式由CSS控制 --------------------------
// 原内联样式textArea.setStyle("-fx-font-family: monospace; -fx-font-size: 12px;");
// 改为添加样式类对应CSS中的 .dialog-pane .content .grid-pane .text-area
textArea.getStyleClass().add("dialog-text-area");
GridPane.setVgrow(textArea, Priority.ALWAYS);
GridPane.setHgrow(textArea, Priority.ALWAYS);
GridPane expContent = new GridPane();
expContent.setMaxWidth(Double.MAX_VALUE);
expContent.add(new Label("详细信息:"), 0, 0);
// 为Label添加样式类对应CSS中的 .dialog-pane .content .grid-pane .label
Label detailLabel = new Label("详细信息:");
detailLabel.getStyleClass().add("dialog-detail-label");
expContent.add(detailLabel, 0, 0);
expContent.add(textArea, 0, 1);
alert.getDialogPane().setContent(expContent);
// 设置对话框大小
alert.getDialogPane().setPrefSize(600, 400);
} else {
@ -138,6 +171,10 @@ public class DialogUtil {
public static void showDetailedErrorDialog(String title, String summary, String details) {
Platform.runLater(() -> {
Alert alert = new Alert(Alert.AlertType.ERROR);
// -------------------------- 2. 绑定CSS + 添加类型标识 --------------------------
bindDialogCss(alert, Alert.AlertType.ERROR);
setAlertIcon(alert);
alert.setTitle(title);
alert.setHeaderText(summary);
@ -147,14 +184,18 @@ public class DialogUtil {
textArea.setWrapText(true);
textArea.setMaxWidth(Double.MAX_VALUE);
textArea.setMaxHeight(Double.MAX_VALUE);
textArea.setStyle("-fx-font-family: monospace; -fx-font-size: 11px;");
// -------------------------- 3. 移除内联样式由CSS控制 --------------------------
textArea.getStyleClass().add("dialog-text-area");
GridPane.setVgrow(textArea, Priority.ALWAYS);
GridPane.setHgrow(textArea, Priority.ALWAYS);
GridPane expContent = new GridPane();
expContent.setMaxWidth(Double.MAX_VALUE);
expContent.add(new Label("错误详情:"), 0, 0);
Label errorLabel = new Label("错误详情:");
errorLabel.getStyleClass().add("dialog-detail-label");
expContent.add(errorLabel, 0, 0);
expContent.add(textArea, 0, 1);
alert.getDialogPane().setContent(expContent);
@ -169,6 +210,10 @@ public class DialogUtil {
public static void showDetailedWarningDialog(String title, String summary, String details) {
Platform.runLater(() -> {
Alert alert = new Alert(Alert.AlertType.WARNING);
// -------------------------- 2. 绑定CSS + 添加类型标识 --------------------------
bindDialogCss(alert, Alert.AlertType.WARNING);
setAlertIcon(alert);
alert.setTitle(title);
alert.setHeaderText(summary);
@ -177,14 +222,18 @@ public class DialogUtil {
textArea.setWrapText(true);
textArea.setMaxWidth(Double.MAX_VALUE);
textArea.setMaxHeight(Double.MAX_VALUE);
textArea.setStyle("-fx-font-family: monospace; -fx-font-size: 11px;");
// -------------------------- 3. 移除内联样式由CSS控制 --------------------------
textArea.getStyleClass().add("dialog-text-area");
GridPane.setVgrow(textArea, Priority.ALWAYS);
GridPane.setHgrow(textArea, Priority.ALWAYS);
GridPane expContent = new GridPane();
expContent.setMaxWidth(Double.MAX_VALUE);
expContent.add(new Label("警告详情:"), 0, 0);
Label warningLabel = new Label("警告详情:");
warningLabel.getStyleClass().add("dialog-detail-label");
expContent.add(warningLabel, 0, 0);
expContent.add(textArea, 0, 1);
alert.getDialogPane().setContent(expContent);
@ -199,6 +248,10 @@ public class DialogUtil {
public static void showDetailedInformationDialog(String title, String summary, String details) {
Platform.runLater(() -> {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
// -------------------------- 2. 绑定CSS + 添加类型标识 --------------------------
bindDialogCss(alert, Alert.AlertType.INFORMATION);
setAlertIcon(alert);
alert.setTitle(title);
alert.setHeaderText(summary);
@ -207,14 +260,18 @@ public class DialogUtil {
textArea.setWrapText(true);
textArea.setMaxWidth(Double.MAX_VALUE);
textArea.setMaxHeight(Double.MAX_VALUE);
textArea.setStyle("-fx-font-family: monospace; -fx-font-size: 11px;");
// -------------------------- 3. 移除内联样式由CSS控制 --------------------------
textArea.getStyleClass().add("dialog-text-area");
GridPane.setVgrow(textArea, Priority.ALWAYS);
GridPane.setHgrow(textArea, Priority.ALWAYS);
GridPane expContent = new GridPane();
expContent.setMaxWidth(Double.MAX_VALUE);
expContent.add(new Label("详细信息:"), 0, 0);
Label infoLabel = new Label("详细信息:");
infoLabel.getStyleClass().add("dialog-detail-label");
expContent.add(infoLabel, 0, 0);
expContent.add(textArea, 0, 1);
alert.getDialogPane().setContent(expContent);
@ -222,13 +279,55 @@ public class DialogUtil {
alert.showAndWait();
});
}
public static void showErrorDialog(String title, String content) {
showErrorDialog(title, title, content);
}
public static void showWarningDialog(String title, String content) {
showWarningDialog(title, title, content);
}
public static void showInformationDialog(String title, String content) {
showInformationDialog(title, title, content);
}
}
/**
* 为对话框绑定CSS并根据类型添加样式类用于CSS差异化渲染
* @param alert 对话框实例
* @param alertType 对话框类型ERROR/WARNING/INFORMATION/CONFIRMATION
*/
private static void bindDialogCss(Alert alert, Alert.AlertType alertType) {
DialogPane dialogPane = alert.getDialogPane();
// 添加dialog-util.css样式
dialogPane.getStylesheets().add(DIALOG_CSS_PATH);
// 根据对话框类型添加样式类对应CSS中的 .dialog-pane:error/.dialog-pane:warning
switch (alertType) {
case ERROR:
dialogPane.getStyleClass().add("error");
break;
case WARNING:
dialogPane.getStyleClass().add("warning");
break;
case INFORMATION:
dialogPane.getStyleClass().add("information");
break;
case CONFIRMATION:
dialogPane.getStyleClass().add("confirmation");
break;
default:
break;
}
// 为对话框面板添加基础样式类确保CSS选择器能匹配
dialogPane.getStyleClass().add("dialog-pane");
}
// 封装设置Alert图标方法
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

@ -4,11 +4,14 @@ import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import top.r3944realms.docchecktoolrefactored.Main;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@ -24,9 +27,26 @@ public class ProgressBar {
private final AtomicBoolean cancelled = new AtomicBoolean(false);
private Runnable onCancelCallback;
// 进度条窗口图标
private static final Image PROGRESS_ICON;
// CSS样式路径
private static final String PROGRESS_CSS;
static {
// 初始化图标和CSS路径
PROGRESS_ICON = new Image(Objects.requireNonNull(
Main.class.getResourceAsStream("/img/logo256x.ico")
));
PROGRESS_CSS = Objects.requireNonNull(
Main.class.getResource("/css/inner/progress.css")
).toExternalForm();
}
public ProgressBar(boolean isCancelable) {
this.isCancelable = isCancelable;
}
public ProgressBar() {
this(true);
}
@ -42,18 +62,22 @@ public class ProgressBar {
progressStage = new Stage();
progressStage.initOwner(ownerStage);
progressStage.initStyle(StageStyle.UTILITY);
// 设置窗口图标
progressStage.getIcons().add(PROGRESS_ICON);
if (isCancelable) {
progressStage.setOnCloseRequest(windowEvent -> {
if (!DialogUtil.showCancelConfirmation(progressStage.getOwner())) {
windowEvent.consume();
}
else {
cancelled.set(true);
if (onCancelCallback != null) {
onCancelCallback.run();
}
closeProgress();
if (!DialogUtil.showCancelConfirmation(progressStage.getOwner())) {
windowEvent.consume();
}
else {
cancelled.set(true);
if (onCancelCallback != null) {
onCancelCallback.run();
}
closeProgress();
}
});
}
progressStage.initModality(Modality.APPLICATION_MODAL);
@ -88,14 +112,21 @@ public class ProgressBar {
});
}
}
// 布局
VBox root = new VBox(10, messageLabel, progressBar);
if (isCancelable) root.getChildren().add(cancelButton);
root.setStyle("-fx-padding: 20; -fx-alignment: center;");
// 布局
VBox root = new VBox(15, messageLabel, progressBar);
root.getStyleClass().add("vbox"); // 添加样式类
if (isCancelable) {
root.getChildren().add(cancelButton);
}
// 应用CSS样式
Scene scene = new Scene(root);
scene.getStylesheets().add(PROGRESS_CSS);
progressStage.setScene(scene);
progressStage.sizeToScene();
progressStage.centerOnScreen(); // 居中显示
progressStage.show();
});
}
@ -153,10 +184,12 @@ public class ProgressBar {
}
}).start();
}
@FunctionalInterface
public interface CancellableTask {
void run(ProgressBar util);
}
public void setOnCancel(Runnable onCancel) {
if (isCancelable) {
if (cancelButton != null) {

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;
}
}

View File

@ -0,0 +1,124 @@
/* ================================= 全局对话框基础样式 ================================= */
/* 对话框容器DialogPane统一背景、边框与阴影 */
.dialog-pane {
-fx-background-color: white; /* 白色背景,符合主流对话框风格 */
-fx-border-color: #DEE2E6; /* 浅灰边框,区分对话框与背景 */
-fx-border-width: 1px;
-fx-border-radius: 8px; /* 圆角优化,避免尖锐边缘 */
-fx-background-radius: 8px;
-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.2), 10, 0, 0, 4); /* 柔和阴影,提升立体感 */
-fx-padding: 15px; /* 内边距15px避免内容贴边 */
}
/* 对话框标题栏(标题文本):统一字体与颜色 */
.dialog-pane .header-panel .label {
-fx-font-family: "Microsoft YaHei", "SimHei", "sans-serif";
-fx-font-size: 16px;
-fx-font-weight: bold;
-fx-text-fill: #212529; /* 深灰文本,突出标题层级 */
-fx-padding: 0 0 10px 0; /* 下内边距10px分隔标题与内容 */
}
/* 对话框内容区文本(短文本场景):优化可读性 */
.dialog-pane .content.label {
-fx-font-family: "Microsoft YaHei", "SimHei", "sans-serif";
-fx-font-size: 14px;
-fx-text-fill: #495057; /* 中灰文本,清晰不刺眼 */
-fx-padding: 5px 0 15px 0; /* 上下内边距,优化内容间距 */
-fx-wrap-text: true; /* 自动换行,避免文本溢出 */
}
/* 对话框图标(信息/警告/错误图标):调整大小与间距 */
.dialog-pane .graphic-container .image-view {
-fx-fit-width: 24px;
-fx-fit-height: 24px;
-fx-padding: 0 10px 0 0; /* 右内边距10px与文本拉开距离 */
}
/* ================================= 按钮栏ButtonBar样式 ================================= */
/* 按钮容器:控制按钮间距与对齐 */
.dialog-pane .button-bar {
-fx-spacing: 10px; /* 按钮间间距10px避免拥挤 */
-fx-padding: 10px 0 0 0; /* 上内边距10px分隔内容与按钮 */
-fx-alignment: CENTER_RIGHT; /* 按钮右对齐,符合操作习惯 */
}
/* 通用按钮基础样式:统一尺寸与字体 */
.dialog-pane .button {
-fx-font-family: "Microsoft YaHei", "SimHei", "sans-serif";
-fx-font-size: 13px;
-fx-padding: 6px 16px; /* 上下6px、左右16px优化点击区域 */
-fx-border-radius: 4px;
-fx-background-radius: 4px;
-fx-cursor: hand; /* 鼠标悬浮为手型,提示可点击 */
}
/* 1. 确认按钮(“是”/“确定”):主要操作,突出显示 */
.dialog-pane .button:default { /* 默认按钮如YES/OK */
-fx-background-color: #0D6EFD; /* 蓝色主按钮,与主界面风格统一 */
-fx-text-fill: white;
-fx-border-color: #0D6EFD;
}
.dialog-pane .button:default:hover {
-fx-background-color: #0B5ED7; /* 加深背景色,反馈明显 */
-fx-border-color: #0A58CA;
}
/* ================================= 带详细信息的对话框(长文本+GridPane样式 ================================= */
/* 详细信息容器GridPane控制标签与文本区域的布局 */
.dialog-pane .content .grid-pane {
-fx-hgap: 10px; /* 列间距10px分隔标签与文本区 */
-fx-vgap: 8px; /* 行间距8px优化垂直紧凑度 */
-fx-alignment: CENTER_LEFT; /* 内容左对齐,符合阅读习惯 */
}
/* 详细信息标签(如“详细信息:”/“错误详情:”):统一样式 */
.dialog-pane .content .grid-pane .label {
-fx-font-family: "Microsoft YaHei", "SimHei", "sans-serif";
-fx-font-size: 14px;
-fx-font-weight: 500; /* 半粗体,突出标签层级 */
-fx-text-fill: #495057;
-fx-padding: 2px 0; /* 上下内边距,避免标签贴边 */
}
/* 长文本显示区域TextArea优化代码/日志类文本显示 */
.dialog-pane .content .grid-pane .text-area {
-fx-font-family: "Consolas", "Monaco", monospace; /* 等宽字体,适配代码/详细日志 */
-fx-font-size: 12px; /* 等宽字体12px保证可读性 */
-fx-text-fill: #212529;
-fx-background-color: #F8F9FA; /* 浅灰背景,区分文本区与容器 */
-fx-border-color: #DEE2E6; /* 浅灰边框,增加轮廓感 */
-fx-border-radius: 4px;
-fx-background-radius: 4px;
-fx-padding: 10px; /* 内边距10px文本不贴边 */
-fx-wrap-text: true; /* 自动换行,避免横向滚动 */
}
/* TextArea聚焦时突出边框提示激活状态 */
.dialog-pane .content .grid-pane .text-area:focused {
-fx-border-color: #80BDFF; /* 浅蓝色边框,符合聚焦反馈 */
-fx-border-width: 1.5px;
-fx-effect: none; /* 移除默认聚焦阴影,避免冲突 */
}
/* TextArea滚动条优化外观减少突兀感 */
.dialog-pane .content .grid-pane .text-area .scroll-bar:vertical,
.dialog-pane .content .grid-pane .text-area .scroll-bar:horizontal {
-fx-background-color: transparent; /* 透明背景,融入文本区 */
}
.dialog-pane .content .grid-pane .text-area .scroll-bar:vertical .thumb,
.dialog-pane .content .grid-pane .text-area .scroll-bar:horizontal .thumb {
-fx-background-color: #ADB5BD; /* 中灰滑块,清晰可辨 */
-fx-background-radius: 4px; /* 滑块圆角,优化手感 */
}
.dialog-pane .content .grid-pane .text-area .scroll-bar:vertical .thumb:hover,
.dialog-pane .content .grid-pane .text-area .scroll-bar:horizontal .thumb:hover {
-fx-background-color: #868E96; /* 深灰hover反馈明显 */
}

View File

@ -0,0 +1,84 @@
/* 进度条窗口主容器 */
.vbox {
-fx-background-color: white;
-fx-border-color: #DEE2E6;
-fx-border-radius: 8px;
-fx-background-radius: 8px;
-fx-padding: 20px;
}
/* 消息标签样式 */
.label {
-fx-font-family: "Microsoft YaHei", "SimHei", sans-serif;
-fx-font-size: 14px;
-fx-text-fill: #495057;
-fx-wrap-text: true;
-fx-prefWidth: 300px;
-fx-alignment: center-left;
}
/* 进度条样式 */
.progress-bar {
-fx-pref-width: 300px;
-fx-pref-height: 8px;
}
/* 进度条轨道 */
.progress-bar .track {
-fx-background-color: #E9ECEF;
-fx-background-radius: 4px;
-fx-border-radius: 4px;
-fx-border-width: 0;
}
/* 进度条填充部分 */
.progress-bar .bar {
-fx-background-color: #0D6EFD;
-fx-background-radius: 4px;
-fx-border-radius: 4px;
-fx-background-insets: 0;
}
/* 不确定进度状态的动画 */
.progress-bar:indeterminate .bar {
-fx-background-color: linear-gradient(
to right,
transparent 0%,
#0D6EFD 50%,
transparent 100%
);
-fx-background-size: 200% 100%;
-fx-animation: progress-animation 1.5s infinite;
}
@keyframes progress-animation {
0% {
-fx-background-position: -200% 0;
}
100% {
-fx-background-position: 200% 0;
}
}
/* 取消按钮样式 */
.button {
-fx-background-color: #6C757D;
-fx-text-fill: white;
-fx-font-family: "Microsoft YaHei", "SimHei", sans-serif;
-fx-font-size: 13px;
-fx-pref-width: 80px;
-fx-pref-height: 30px;
-fx-border-radius: 4px;
-fx-background-radius: 4px;
-fx-cursor: hand;
}
/* 取消按钮悬停效果 */
.button:hover {
-fx-background-color: #5A6268;
}
/* 取消按钮按压效果 */
.button:pressed {
-fx-background-color: #495057;
}

View File

@ -0,0 +1,157 @@
/* ================================= 全局基础样式 ================================= */
/* 主容器VBox样式统一背景与内边距 */
VBox {
-fx-background-color: #F8F9FA; /* 浅灰背景,避免刺眼 */
-fx-padding: 0; /* 移除默认内边距,子组件自行控制 */
-fx-spacing: 0; /* 消除组件间默认间距,保持紧凑 */
}
/* 所有文本组件统一字体(避免系统字体混乱) */
* {
-fx-font-family: "Microsoft YaHei", "SimHei", "sans-serif"; /* 适配中文显示 */
}
/* ================================= 菜单栏MenuBar样式 ================================= */
MenuBar {
-fx-background-color: #343A40; /* 深灰背景,突出菜单层级 */
-fx-text-fill: white; /* 菜单文本白色 */
-fx-pref-height: 36px; /* 固定高度,避免拉伸 */
}
/* 一级菜单样式 */
Menu {
-fx-text-fill: white;
-fx-font-size: 14px;
-fx-padding: 0 15px; /* 菜单内边距,增加点击区域 */
-fx-cursor: hand; /* 鼠标悬浮为手型,提示可点击 */
}
/* 一级菜单 hover 效果 */
Menu:hover {
-fx-background-color: #495057; /* 深灰加深,反馈明显 */
}
/* 菜单项MenuItem样式 */
MenuItem {
-fx-background-color: white;
-fx-text-fill: #212529;
-fx-font-size: 13px;
-fx-padding: 8px 25px; /* 上下8px、左右25px优化点击区域 */
}
/* 菜单项 hover 效果 */
MenuItem:hover {
-fx-background-color: #E9ECEF; /* 浅灰背景,区分选中状态 */
-fx-text-fill: #0D6EFD; /* 蓝色文本,突出选中项 */
}
/* 分隔线SeparatorMenuItem样式 */
SeparatorMenuItem .line {
-fx-background-color: #DEE2E6; /* 浅灰分隔线,不突兀 */
-fx-pref-height: 1px;
}
/* ================================= 标签页TabPane样式 ================================= */
/* 标签容器整体样式 */
TabPane {
-fx-background-color: white;
-fx-border-color: #DEE2E6; /* 浅灰边框,区分区域 */
-fx-border-width: 1px 0; /* 仅上下边框,左右无边框 */
-fx-pref-height: 700px; /* 固定高度适配1000px主容器 */
}
/* 标签栏TabHeaderArea样式标签所在的顶部栏 */
TabPane .tab-header-area {
-fx-background-color: white;
-fx-border-color: #DEE2E6;
-fx-border-width: 0 0 1px 0; /* 仅底部边框,分隔标签与内容区 */
}
/* 单个标签Tab基础样式 */
Tab {
-fx-background-color: transparent; /* 透明背景hover时显示效果 */
-fx-text-fill: #6C757D; /* 未选中标签文本:浅灰 */
-fx-font-size: 15px;
-fx-padding: 8px 25px; /* 上下8px、左右25px增加点击区域 */
-fx-cursor: hand;
}
/* 未选中标签 hover 效果 */
Tab:hover {
-fx-text-fill: #0D6EFD; /* 蓝色文本,提示可点击 */
}
/* 选中标签(.selected样式 */
Tab:selected {
-fx-text-fill: #0D6EFD; /* 蓝色文本,突出选中状态 */
-fx-underline: true; /* 下划线标识,替代默认背景 */
-fx-underline-color: #0D6EFD; /* 下划线与文本同色 */
-fx-underline-width: 2px; /* 下划线加粗,更明显 */
}
/* 标签内容区TabContentArea样式标签对应的内容容器 */
TabPane .tab-content-area {
-fx-background-color: white;
-fx-padding: 20px; /* 内边距20px内容不贴边 */
}
/* 长文本标签自定义styleClass="long-text-tab"适配“2. 查遗漏、存储路径和命名规范” */
Tab.long-text-tab {
-fx-padding: 8px 20px; /* 左右内边距缩减为20px避免文本溢出 */
-fx-font-size: 14px; /* 字体稍小,适配长文本 */
}
/* 首尾标签id="startTab"、id="endTab"):可选,突出首尾步骤 */
Tab#startTab:selected,
Tab#endTab:selected {
-fx-text-fill: #28A745; /* 绿色文本,区分首尾步骤 */
-fx-underline-color: #28A745;
}
/* ================================= 导航按钮HBox + Button样式 ================================= */
/* 按钮容器HBox样式右对齐布局 */
HBox {
-fx-background-color: white;
-fx-padding: 0 20px; /* 左右20px按钮不贴边 */
}
/* 导航按钮(上一步/下一步)基础样式 */
Button {
-fx-background-color: #0D6EFD; /* 蓝色主按钮 */
-fx-text-fill: white;
-fx-font-size: 19px; /* 按FXML要求保持19px粗体 */
-fx-font-weight: bold;
-fx-border-radius: 6px; /* 圆角,避免尖锐 */
-fx-background-radius: 6px;
-fx-cursor: hand;
-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.1), 5, 0, 0, 2); /* 轻微阴影,增加立体感 */
}
/* 按钮 hover 效果:加深背景色 */
Button:hover {
-fx-background-color: #0B5ED7; /* 深蓝色,反馈明显 */
-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.2), 5, 0, 0, 2); /* 阴影加深 */
}
/* 按钮按压效果:移除阴影+背景色再加深 */
Button:pressed {
-fx-background-color: #0A58CA;
-fx-effect: none; /* 按压时无阴影,模拟“按下”质感 */
}
/* 上一步按钮(可选):区分颜色,避免与下一步混淆 */
Button#prevB {
-fx-background-color: #6C757D; /* 浅灰按钮,次要操作 */
}
Button#prevB:hover {
-fx-background-color: #5A6268; /* 深灰 hover 色 */
}
Button#prevB:pressed {
-fx-background-color: #495057;
}

View File

@ -0,0 +1,93 @@
/* 全局样式 */
.root {
-fx-background-color: #F9FAFB;
-fx-font-family: "Microsoft YaHei", "SimHei", sans-serif;
}
/* 标签样式 */
Label {
-fx-text-fill: #111827;
-fx-font-size: 14px;
-fx-font-weight: 500;
}
/* 文本输入框样式 */
TextField {
-fx-background-color: #FFFFFF;
-fx-border-color: #E5E7EB;
-fx-border-width: 1px;
-fx-border-radius: 4px;
-fx-text-fill: #111827;
-fx-font-size: 14px;
-fx-padding: 6px 8px;
-fx-pref-height: 36px;
}
TextField:focused {
-fx-border-color: #1A56DB;
-fx-border-width: 2px;
-fx-background-insets: 0;
-fx-effect: dropshadow(gaussian, rgba(26, 86, 219, 0.2), 4, 0, 0, 1);
}
TextField:hover {
-fx-border-color: #9CA3AF;
}
TextField:disabled {
-fx-background-color: #F3F4F6;
-fx-text-fill: #9CA3AF;
-fx-border-color: #E5E7EB;
}
/* 按钮样式 */
Button {
-fx-background-color: #1A56DB;
-fx-text-fill: #FFFFFF;
-fx-font-size: 14px;
-fx-font-weight: 500;
-fx-border-radius: 4px;
-fx-background-radius: 4px;
-fx-cursor: hand;
}
Button:hover {
-fx-background-color: #1E40AF;
}
Button:pressed {
-fx-background-color: #1E3A8A;
}
Button:armed {
-fx-effect: none;
}
/* 重置按钮特殊样式 */
#infoResetB {
-fx-background-color: #FFFFFF;
-fx-text-fill: #1A56DB;
-fx-border-color: #1A56DB;
-fx-border-width: 1px;
}
#infoResetB:hover {
-fx-background-color: #EFF6FF;
}
/* 网格面板样式 */
GridPane {
-fx-hgap: 16px;
-fx-vgap: 12px;
}
/* 错误状态样式 */
.error {
-fx-border-color: #DC2626 !important;
}
/* 只读状态样式 */
.read-only {
-fx-background-color: #F3F4F6;
-fx-cursor: default;
}

View File

@ -0,0 +1,169 @@
/* ================================= 全局基础样式 ================================= */
/* 主容器VBox控制整体间距与背景 */
VBox {
-fx-background-color: white; /* 白色背景,符合对话框简洁风格 */
-fx-padding: 20px; /* 内边距20px避免内容贴边 */
-fx-spacing: 15px; /* 组件间间距15px层次分明 */
-fx-border-color: #DEE2E6; /* 浅灰边框,区分对话框与主界面 */
-fx-border-width: 1px;
-fx-border-radius: 8px; /* 轻微圆角,避免尖锐 */
-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.15), 8, 0, 0, 3); /* 柔和阴影,提升立体感 */
}
/* 统一字体:适配中文,避免系统字体混乱 */
* {
-fx-font-family: "Microsoft YaHei", "SimHei", "sans-serif";
-fx-font-size: 14px; /* 基础字体大小,保证可读性 */
}
/* ================================= 标题样式“设置”Label ================================= */
/* 标题Label突出显示区分标题与内容 */
Label[text="设置"] {
-fx-font-size: 21px; /* 按FXML要求保持21px字号 */
-fx-font-weight: bold;
-fx-text-fill: #212529; /* 深灰文本,庄重不刺眼 */
-fx-padding: 5px 0 10px 0; /* 上下内边距,增加标题与下方内容的距离 */
-fx-border-color: #DEE2E6;
-fx-border-width: 0 0 1px 0; /* 底部浅灰边框,分隔标题与设置项 */
}
/* ================================= 网格布局GridPane样式 ================================= */
/* 网格容器:控制设置项的整体排列 */
GridPane {
-fx-hgap: 10px; /* 列间距10px避免标签与输入框拥挤 */
-fx-vgap: 8px; /* 行间距8px优化垂直紧凑度 */
-fx-alignment: CENTER_LEFT; /* 内容左对齐,符合阅读习惯 */
}
/* 单位标签(“秒”):统一样式,与输入框对齐 */
GridPane Label[text="秒"] {
-fx-text-fill: #6C757D; /* 浅灰文本,次要信息弱化 */
-fx-alignment: CENTER_LEFT;
-fx-padding: 0 0 0 5px; /* 左内边距5px与Spinner拉开微小距离 */
}
/* ================================= 输入组件样式 ================================= */
/* 1. 数值选择器Spinner优化外观与交互 */
Spinner {
-fx-pref-width: 94px; /* 按FXML列宽适配避免拉伸 */
-fx-border-color: #DEE2E6; /* 浅灰边框,统一风格 */
-fx-border-radius: 4px;
-fx-background-color: white;
}
/* Spinner聚焦时突出边框提示当前激活状态 */
Spinner:focused {
-fx-border-color: #80BDFF; /* 浅蓝色边框符合主流UI聚焦反馈 */
-fx-border-width: 2px;
-fx-effect: none; /* 移除默认聚焦阴影,避免冲突 */
}
/* Spinner按钮上下箭头优化尺寸与颜色 */
Spinner .increment-arrow-button,
Spinner .decrement-arrow-button {
-fx-background-color: #F8F9FA; /* 浅灰背景,区分按钮区域 */
-fx-border-color: #DEE2E6;
-fx-border-width: 0 0 0 1px; /* 仅左侧边框,分隔输入区与按钮区 */
}
/* Spinner箭头图标加深颜色提高辨识度 */
Spinner .increment-arrow,
Spinner .decrement-arrow {
-fx-background-color: #495057;
}
/* 2. 复选框CheckBox优化选中状态与间距 */
CheckBox {
-fx-text-fill: #495057; /* 中灰文本,与设置项标签一致 */
-fx-padding: 5px 0; /* 上下内边距,增加与上下行的距离 */
-fx-cursor: hand; /* 鼠标悬浮为手型,提示可点击 */
}
/* CheckBox选中时自定义勾选颜色突出激活状态 */
CheckBox:selected .box {
-fx-background-color: #0D6EFD; /* 蓝色背景,与主界面按钮风格统一 */
}
/* CheckBox勾选图标白色图标与蓝色背景对比明显 */
CheckBox:selected .mark {
-fx-background-color: white;
}
/* ================================= 按钮样式 ================================= */
/* 1. 恢复默认值按钮resetB次要操作风格弱化 */
Button#resetB {
-fx-background-color: #F8F9FA; /* 浅灰背景,区分次要操作 */
-fx-text-fill: #495057; /* 中灰文本 */
-fx-border-color: #DEE2E6; /* 浅灰边框,增加轮廓感 */
-fx-border-radius: 4px;
-fx-background-radius: 4px;
-fx-pref-width: 120px; /* 适配文本长度,避免按钮过短 */
-fx-padding: 6px 12px;
-fx-cursor: hand;
}
/* resetB hover效果加深背景色反馈明显 */
Button#resetB:hover {
-fx-background-color: #E2E6EA;
-fx-border-color: #D6D8DB;
}
/* 2. 取消按钮cancelB中性操作风格温和 */
Button#cancelB {
-fx-background-color: white; /* 白色背景,中性风格 */
-fx-text-fill: #495057; /* 中灰文本 */
-fx-border-color: #DEE2E6; /* 浅灰边框 */
-fx-border-radius: 4px;
-fx-background-radius: 4px;
-fx-pref-width: 100px; /* 按FXML要求保持100px宽度 */
-fx-padding: 6px 12px;
-fx-cursor: hand;
}
/* cancelB hover效果背景色变浅反馈清晰 */
Button#cancelB:hover {
-fx-background-color: #F8F9FA;
}
/* 3. 保存按钮saveB主要操作突出显示 */
Button#saveB {
-fx-background-color: #0D6EFD; /* 蓝色主按钮,与主界面导航按钮风格统一 */
-fx-text-fill: white; /* 白色文本,对比明显 */
-fx-border-color: #0D6EFD; /* 蓝色边框,强化轮廓 */
-fx-border-radius: 4px;
-fx-background-radius: 4px;
-fx-pref-width: 100px; /* 按FXML要求保持100px宽度 */
-fx-padding: 6px 12px;
-fx-cursor: hand;
}
/* saveB hover效果加深背景色提升交互感 */
Button#saveB:hover {
-fx-background-color: #0B5ED7;
-fx-border-color: #0A58CA;
}
/* saveB按压效果移除多余阴影模拟“按下”质感 */
Button#saveB:pressed {
-fx-effect: none;
}
/* ================================= 按钮容器HBox样式 ================================= */
/* 按钮容器:控制按钮的对齐与间距 */
HBox {
-fx-spacing: 15px; /* 按钮间间距15px避免拥挤 */
-fx-padding: 5px 0 0 0; /* 上内边距5px与上方组件拉开距离 */
}
/* 恢复默认值按钮的容器左侧HBox居中对齐 */
HBox[alignment="CENTER"] {
-fx-padding: 5px 0 10px 0; /* 下内边距10px增加与下方取消/保存按钮的距离 */
}

View File

@ -0,0 +1,159 @@
/* 1. 全局基础样式:统一视觉基调 */
.root {
-fx-background-color: #F9FAFB; /* 符合政企规范的浅灰背景,降低视觉疲劳 */
-fx-font-family: "Microsoft YaHei", "SimHei", sans-serif; /* 适配中文显示 */
-fx-font-size: 14px; /* 基础字号,保证可读性 */
}
/* 2. 网格布局样式:优化间距与对齐 */
GridPane {
-fx-hgap: 16px; /* 列间距统一,避免元素拥挤 */
-fx-vgap: 12px; /* 行间距统一,区分功能区域 */
-fx-alignment: TOP_LEFT; /* 整体左对齐,符合政企软件操作习惯 */
}
/* 3. 标签样式:明确信息层级 */
Label {
-fx-text-fill: #111827; /* 主文本色,保证清晰度 */
-fx-font-weight: 500; /* 标签文字稍粗,突出引导性 */
-fx-padding: 4px 0; /* 上下内边距,避免与输入框垂直错位 */
}
/* 特殊标签:功能标题(如“工作内容”)强化区分 */
Label[text="工作内容:"],
Label[text="结果反馈:"],
Label[text="载入文件夹:"] {
-fx-text-fill: #1A56DB; /* 主色调强调,突出核心功能区标题 */
-fx-font-weight: 600;
}
/* 4. 文本输入框样式:适配文件路径输入场景 */
TextField {
-fx-background-color: #FFFFFF; /* 白色背景,保证输入内容清晰 */
-fx-border-color: #E5E7EB; /* 淡灰边框,区分输入区域 */
-fx-border-width: 1px;
-fx-border-radius: 4px; /* 圆角设计,避免尖锐感 */
-fx-text-fill: #111827;
-fx-padding: 8px 12px; /* 内边距充足,输入文字不贴边 */
-fx-pref-height: 36px; /* 统一高度,与按钮视觉对齐 */
-fx-cursor: text; /* 文本光标,明确可输入状态 */
}
/* 输入框交互状态:提升操作反馈 */
TextField:focused {
-fx-border-color: #1A56DB; /* 聚焦时主色边框,明确当前操作项 */
-fx-border-width: 2px;
-fx-background-insets: 0; /* 消除聚焦时默认内边距偏移 */
-fx-effect: dropshadow(gaussian, rgba(26, 86, 219, 0.2), 4, 0, 0, 1); /* 轻微阴影,增强层次感 */
}
TextField:hover {
-fx-border-color: #9CA3AF; /* 悬停时边框加深,提示可交互 */
}
TextField:disabled {
-fx-background-color: #F3F4F6; /* 禁用时浅灰背景,区分不可操作状态 */
-fx-text-fill: #9CA3AF; /* 禁用文本色减淡 */
-fx-border-color: #E5E7EB;
}
/* 5. 文本域样式:适配结果展示与操作指引 */
TextArea {
-fx-background-color: #FFFFFF;
-fx-border-color: #E5E7EB;
-fx-border-width: 1px;
-fx-border-radius: 4px;
-fx-text-fill: #111827;
-fx-padding: 12px; /* 内边距充足,避免文字贴边 */
-fx-font-size: 14px;
-fx-wrap-text: true; /* 自动换行,适配长文本(如文件路径、检查结果) */
-fx-line-spacing: 6px; /* 行间距优化,提升多行文本质感 */
}
/* 结果展示文本域result1TA强化可读性 */
#result1TA {
-fx-pref-height: 580px; /* 适配FXML中结果区域高度避免滚动条过多 */
-fx-background-color: #FCFCFD; /* 比普通文本域稍浅,突出结果内容 */
}
/* 操作指引文本域:区分功能属性 */
TextArea[text*="点击“选择文件夹”按钮"] {
-fx-text-fill: #4B5563; /* 次要文本色,避免与核心结果冲突 */
-fx-background-color: #F9FAFB; /* 背景与面板一致,突出“指引”属性 */
-fx-border-color: #E5E7EB;
}
/* 文本域交互状态:与输入框保持一致 */
TextArea:focused {
-fx-border-color: #1A56DB;
-fx-border-width: 2px;
-fx-effect: dropshadow(gaussian, rgba(26, 86, 219, 0.2), 4, 0, 0, 1);
}
/* 6. 按钮样式:区分功能优先级 */
Button {
-fx-border-radius: 4px;
-fx-background-radius: 4px;
-fx-font-size: 14px;
-fx-font-weight: 500;
-fx-padding: 8px 16px;
-fx-cursor: hand; /* 手型光标,提示可点击 */
-fx-alignment: CENTER; /* 文字居中,保证视觉整齐 */
}
/* 6.1 核心操作按钮:开始检查(高优先级) */
#start1B {
-fx-background-color: #1A56DB; /* 主色调,突出核心功能 */
-fx-text-fill: #FFFFFF;
-fx-pref-height: 44px; /* 稍高高度,强化视觉权重 */
}
#start1B:hover {
-fx-background-color: #1E40AF; /* hover时加深提升反馈 */
}
#start1B:pressed {
-fx-background-color: #1E3A8A; /* 点击时进一步加深,模拟按压感 */
}
#start1B:disabled {
-fx-background-color: #94A3B8; /* 禁用时灰色,明确不可操作 */
-fx-cursor: default;
}
/* 6.2 辅助操作按钮:选择文件夹(中优先级) */
#selectLoadFolder1B {
-fx-background-color: #FFFFFF; /* 白色背景,次要功能属性 */
-fx-text-fill: #1A56DB; /* 主色文字,保持视觉关联 */
-fx-border-color: #1A56DB;
-fx-border-width: 1px;
-fx-pref-height: 36px; /* 与输入框高度一致,视觉对齐 */
}
#selectLoadFolder1B:hover {
-fx-background-color: #EFF6FF; /* hover时淡蓝背景提示交互 */
}
#selectLoadFolder1B:pressed {
-fx-background-color: #DBEAFE;
}
/* 6.3 取消操作按钮:取消检查(低优先级,危险属性) */
#cancel1B {
-fx-background-color: #FFFFFF;
-fx-text-fill: #DC2626; /* 警示色文字,提示“取消”的中断属性 */
-fx-border-color: #DC2626;
-fx-border-width: 1px;
-fx-pref-height: 36px;
}
#cancel1B:hover {
-fx-background-color: #FEF2F2; /* hover时淡红背景强化警示 */
}
#cancel1B:pressed {
-fx-background-color: #FEE2E2;
}
/* 7. 特殊状态样式:适配业务场景 */
/* 7.1 加载中状态(可配合控制器动态添加) */
.loading {
-fx-background-color: #F3F4F6;
-fx-cursor: wait; /* 等待光标,提示正在处理 */
}
/* 7.2 结果异常状态(如重复文件提示) */
.result-error {
-fx-background-color: #DC2626; /* 错误色,突出异常结果 */
-fx-font-weight: 600;
}
/* 7.3 结果正常状态(如检查完成提示) */
.result-success {
-fx-background-color: #059669; /* 成功色,传递正常反馈 */
-fx-font-weight: 600;
}

View File

@ -0,0 +1,207 @@
/* 1. 全局基础样式:统一视觉基调 */
.root {
-fx-background-color: #F9FAFB; /* 浅灰背景,符合政企软件简洁专业风格 */
-fx-font-family: "Microsoft YaHei", "SimHei", sans-serif; /* 适配中文显示,避免字体错乱 */
-fx-font-size: 14px; /* 基础字号,保证不同设备可读性 */
}
/* 2. 网格布局样式:优化功能区域间距与对齐 */
GridPane {
-fx-hgap: 16px; /* 列间距统一,避免元素拥挤(适配多输入框/按钮布局) */
-fx-vgap: 12px; /* 行间距统一,清晰区分“选择区”“操作区”“结果区” */
-fx-alignment: TOP_LEFT; /* 整体左对齐,符合政企用户操作习惯 */
}
/* 3. 标签样式:明确信息层级,突出引导性 */
Label {
-fx-text-fill: #111827; /* 主文本色,保证清晰度 */
-fx-font-weight: 500; /* 标签文字稍粗,突出输入引导 */
-fx-padding: 6px 0; /* 上下内边距,避免与输入组件垂直错位 */
-fx-pref-width: 100px; /* 统一标签宽度,使输入框对齐(适配“载入目录”“载入文件夹”等标签) */
}
/* 特殊标签:功能标题强化(如“工作内容”“结果反馈”) */
Label[text="工作内容:"],
Label[text="结果反馈:"] {
-fx-text-fill: #1A56DB; /* 主色调强调,明确核心功能区边界 */
-fx-font-weight: 600;
-fx-pref-width: 80px; /* 适配右侧指引区域宽度 */
}
/* 4. 下拉选择框ChoiceBox样式适配“页面级/文件级”选择场景 */
ChoiceBox {
-fx-background-color: #FFFFFF; /* 白色背景,保证选项清晰 */
-fx-border-color: #E5E7EB; /* 淡灰边框,区分选择区域 */
-fx-border-width: 1px;
-fx-border-radius: 4px; /* 圆角设计,避免尖锐感 */
-fx-text-fill: #111827;
-fx-padding: 8px 12px; /* 内边距充足,选项文字不贴边 */
-fx-pref-height: 36px; /* 与输入框/按钮高度一致,视觉对齐 */
-fx-cursor: hand; /* 手型光标,提示可展开选择 */
}
/* 下拉框交互状态:提升操作反馈 */
ChoiceBox:hover {
-fx-border-color: #9CA3AF; /* 悬停时边框加深,提示可交互 */
}
ChoiceBox:focused {
-fx-border-color: #1A56DB; /* 聚焦时主色边框,明确当前操作项 */
-fx-border-width: 2px;
-fx-effect: dropshadow(gaussian, rgba(26, 86, 219, 0.2), 4, 0, 0, 1); /* 轻微阴影,增强层次感 */
}
/* 下拉选项面板样式:与选择框视觉统一 */
ChoiceBox .context-menu {
-fx-background-color: #FFFFFF;
-fx-border-color: #E5E7EB;
-fx-border-radius: 4px;
-fx-padding: 4px 0;
}
ChoiceBox .menu-item {
-fx-padding: 8px 16px;
-fx-font-size: 14px;
}
ChoiceBox .menu-item:hover {
-fx-background-color: #EFF6FF; /* 选项悬停淡蓝背景,与主色调呼应 */
-fx-text-fill: #1A56DB;
}
/* 5. 文本输入框样式:适配路径显示场景 */
TextField {
-fx-background-color: #FFFFFF;
-fx-border-color: #E5E7EB;
-fx-border-width: 1px;
-fx-border-radius: 4px;
-fx-text-fill: #111827;
-fx-padding: 8px 12px; /* 内边距充足,长路径文字不贴边 */
-fx-pref-height: 36px; /* 统一高度,与按钮/下拉框对齐 */
-fx-cursor: text;
}
/* 输入框交互状态:与其他组件保持一致 */
TextField:focused {
-fx-border-color: #1A56DB;
-fx-border-width: 2px;
-fx-background-insets: 0;
-fx-effect: dropshadow(gaussian, rgba(26, 86, 219, 0.2), 4, 0, 0, 1);
}
TextField:hover {
-fx-border-color: #9CA3AF;
}
TextField:disabled {
-fx-background-color: #F3F4F6; /* 禁用时浅灰背景,区分不可操作状态 */
-fx-text-fill: #9CA3AF;
-fx-border-color: #E5E7EB;
}
/* 6. 文本域样式:区分“结果展示”与“操作指引” */
TextArea {
-fx-background-color: #FFFFFF;
-fx-border-color: #E5E7EB;
-fx-border-width: 1px;
-fx-border-radius: 4px;
-fx-text-fill: #111827;
-fx-padding: 12px; /* 内边距充足,避免文字贴边 */
-fx-font-size: 14px;
-fx-wrap-text: true; /* 自动换行,适配长路径比对结果 */
-fx-line-spacing: 6px; /* 行间距优化,提升多行文本质感 */
-fx-pref-height: 550px; /* 适配FXML中结果区域高度减少滚动条干扰 */
}
/* 核心结果文本域result2TA强化可读性 */
#result2TA {
-fx-background-color: #FCFCFD; /* 比普通文本域稍浅,突出结果内容 */
-fx-pref-width: 800px; /* 适配左侧结果区域宽度,避免文字过宽 */
}
/* 操作指引文本域:区分功能属性 */
TextArea[text*="选择“页面级”"] {
-fx-text-fill: #4B5563; /* 次要文本色,避免与核心结果冲突 */
-fx-background-color: #F9FAFB; /* 背景与面板一致,突出“指引”属性 */
-fx-border-color: #E5E7EB;
-fx-pref-width: 400px; /* 适配右侧指引区域宽度 */
}
/* 文本域交互状态:与输入框保持一致 */
TextArea:focused {
-fx-border-color: #1A56DB;
-fx-border-width: 2px;
-fx-effect: dropshadow(gaussian, rgba(26, 86, 219, 0.2), 4, 0, 0, 1);
}
/* 7. 按钮样式:按功能优先级区分,引导操作流程 */
Button {
-fx-border-radius: 4px;
-fx-background-radius: 4px;
-fx-font-size: 14px;
-fx-font-weight: 500;
-fx-padding: 8px 16px;
-fx-cursor: hand; /* 手型光标,提示可点击 */
-fx-alignment: CENTER; /* 文字居中,保证视觉整齐 */
-fx-pref-height: 36px; /* 统一高度,与输入框/下拉框对齐 */
}
/* 7.1 核心操作按钮:开始比对(最高优先级) */
#start2B {
-fx-background-color: #1A56DB; /* 主色调,突出“比对”核心功能 */
-fx-text-fill: #FFFFFF;
-fx-pref-height: 44px; /* 稍高高度,强化视觉权重(适配多按钮布局中的焦点引导) */
-fx-pref-width: 800px; /* 适配FXML中按钮宽度保证操作区域完整 */
}
#start2B:hover {
-fx-background-color: #1E40AF; /* hover时加深提升反馈 */
}
#start2B:pressed {
-fx-background-color: #1E3A8A; /* 点击时进一步加深,模拟按压感 */
}
#start2B:disabled {
-fx-background-color: #94A3B8; /* 禁用时灰色,明确不可操作 */
-fx-cursor: default;
}
/* 7.2 辅助选择按钮:选择文件/文件夹(中优先级) */
#selectLoadCatalog2B,
#selectJPGFolder2B {
-fx-background-color: #FFFFFF; /* 白色背景,次要功能属性 */
-fx-text-fill: #1A56DB; /* 主色文字,保持视觉关联 */
-fx-border-color: #1A56DB;
-fx-border-width: 1px;
}
#selectLoadCatalog2B:hover,
#selectJPGFolder2B:hover {
-fx-background-color: #EFF6FF; /* hover时淡蓝背景提示交互 */
}
#selectLoadCatalog2B:pressed,
#selectJPGFolder2B:pressed {
-fx-background-color: #DBEAFE;
}
/* 7.3 生成操作按钮:生成地址文件(中优先级,功能关联) */
#generateLogicalAddress2B,
#generatePhysicalAddress2B {
-fx-background-color: #059669; /* 成功色,传递“生成文件”的正向功能属性 */
-fx-text-fill: #FFFFFF;
}
#generateLogicalAddress2B:hover,
#generatePhysicalAddress2B:hover {
-fx-background-color: #047857; /* hover时加深提升反馈 */
}
#generateLogicalAddress2B:pressed,
#generatePhysicalAddress2B:pressed {
-fx-background-color: #065F46;
}
#generateLogicalAddress2B:disabled,
#generatePhysicalAddress2B:disabled {
-fx-background-color: #6B7280; /* 禁用时灰色,明确不可操作 */
-fx-cursor: default;
}
/* 8. 特殊状态样式:适配业务场景需求 */
/* 8.1 加载中状态(配合控制器动态添加,如比对过程中) */
.loading {
-fx-background-color: #F3F4F6;
-fx-cursor: wait; /* 等待光标,提示正在处理 */
}
/* 7.2 结果异常状态(如对比发现有问题,或者无法生成逻辑文件) */
.result-error {
-fx-background-color: #DC2626; /* 警示色,突出路径差异结果 */
-fx-font-weight: 600;
}
/* 8.3 比对正常状态(结果区域高亮成功) */
.result-success {
-fx-background-color: #059669; /* 成功色,传递“无差异”反馈 */
-fx-font-weight: 600;
}

View File

@ -0,0 +1,32 @@
/* 1. 全局基础样式:奠定专业简洁基调 */
.root {
-fx-background-color: #F9FAFB; /* 浅灰背景,符合政企软件低视觉疲劳需求 */
-fx-font-family: "Microsoft YaHei", "SimHei", sans-serif; /* 适配中文显示,避免字体模糊 */
}
/* 2. 锚点布局样式:确保内容全屏适配 */
AnchorPane {
-fx-padding: 24px; /* 充足内边距避免文本贴边适配800×1000px面板尺寸 */
}
/* 3. 核心文本域样式:优化验收标准可读性 */
TextArea {
/* 基础视觉属性 */
-fx-background-color: #FFFFFF; /* 白色背景,突出文本内容 */
-fx-border-color: #E5E7EB; /* 淡灰边框,明确内容区域边界 */
-fx-border-width: 1px;
-fx-border-radius: 4px; /* 圆角设计,避免尖锐感,提升专业度 */
-fx-pref-width: 100%; /* 宽度全屏适配符合FXML中1000px预设 */
-fx-pref-height: 100%; /* 高度全屏适配符合FXML中800px预设 */
-fx-wrap-text: true; /* 强制自动换行,避免横向滚动条(适配长句说明) */
/* 文本样式优化:突出层级与重点 */
-fx-font-size: 18px; /* 匹配FXML预设字号保证远距离阅读清晰度 */
-fx-text-fill: #111827; /* 主文本色,确保长时间阅读不费力 */
-fx-line-spacing: 12px; /* 增大行间距区分不同验收步骤比默认多50%,提升可读性) */
-fx-letter-spacing: 0.5px; /* 轻微字间距,优化中文“著录”“倾斜度”等专业术语识别 */
/* 交互体验弱化编辑属性因editable="false" */
-fx-cursor: default; /* 默认光标,明确不可编辑状态 */
-fx-focus-traversable: false; /* 取消焦点边框,避免用户误解“可操作” */
}

View File

@ -0,0 +1,197 @@
/* 1. 全局基础样式:奠定专业简洁基调 */
.root {
-fx-background-color: #F9FAFB; /* 浅灰背景,符合政企软件低视觉疲劳需求 */
-fx-font-family: "Microsoft YaHei", "SimHei", sans-serif; /* 适配中文显示,避免字体模糊 */
-fx-font-size: 14px; /* 基础字号,保证各组件文本一致性 */
}
/* 2. 网格布局样式:优化多组件排版一致性 */
GridPane {
-fx-hgap: 12px; /* 列间距适配多按钮/输入框布局,避免拥挤 */
-fx-vgap: 16px; /* 行间距区分“加载区”“操作区”“结果区”,提升层次感 */
-fx-alignment: TOP_LEFT; /* 整体左对齐,符合政企用户操作习惯 */
-fx-padding: 10px; /* 与FXML中padding呼应确保整体边距统一 */
}
/* 3. 标签样式:明确功能引导,突出核心提示 */
Label {
-fx-text-fill: #111827; /* 主文本色,保证清晰度 */
-fx-font-weight: 500; /* 标签文字稍粗,突出输入引导 */
-fx-pref-width: 120px; /* 统一标签宽度,使“载入数字化成果”“载入压缩包”等输入框对齐 */
}
/* 修复压缩包提示标签显示不全:专项样式 */
/* 匹配文本内容的标签(避免影响其他标签) */
Label[text*="将档案目录、哈希值列表文件"] {
/* 1. 宽度适配强制占满父容器GridPane避免列宽限制截断 */
-fx-max-width: 100%; /* 最大宽度=父容器宽度1065px-20px内边距=1045px */
-fx-pref-width: 100%; /* 优先使用100%宽度覆盖原prefWidth="740.0" */
/* 2. 文本换行:允许长文本折行,避免横向溢出 */
-fx-wrap-text: true; /* 核心属性开启自动换行默认false */
-fx-text-alignment: CENTER; /* 保持居中对齐,符合提示属性 */
/* 3. 内边距与间距:避免文本贴边,提升可读性 */
/* 4. 视觉强化:保持原提示属性,突出核心要求 */
-fx-text-fill: #1A56DB; /* 主色调强调 */
-fx-font-weight: 600;
-fx-font-size: 140px;
}
/* 特殊标签2功能标题如“工作内容”“结果反馈” */
Label[text="工作内容:"],
Label[text="结果反馈:"] {
-fx-text-fill: #1A56DB; /* 主色调强调,明确功能区边界 */
-fx-font-weight: 600;
-fx-pref-width: 80px; /* 适配右侧指引区域宽度 */
}
/* 4. 文本输入框样式:适配文件路径与成果信息展示 */
TextField {
-fx-background-color: #FFFFFF; /* 白色背景,保证输入内容清晰 */
-fx-border-color: #E5E7EB; /* 淡灰边框,区分输入区域 */
-fx-border-width: 1px;
-fx-border-radius: 4px; /* 圆角设计,避免尖锐感 */
-fx-text-fill: #111827;
-fx-padding: 8px 12px; /* 内边距充足,长文件路径不贴边 */
-fx-pref-height: 36px; /* 统一高度,与按钮视觉对齐 */
-fx-cursor: text; /* 文本光标,明确可输入/选择状态 */
-fx-hgrow: ALWAYS; /* 自适应列宽符合FXML中hgrow="ALWAYS"设置 */
}
/* 输入框交互状态:提升操作反馈 */
TextField:focused {
-fx-border-color: #1A56DB; /* 聚焦时主色边框,明确当前操作项 */
-fx-border-width: 2px;
-fx-background-insets: 0;
-fx-effect: dropshadow(gaussian, rgba(26, 86, 219, 0.2), 4, 0, 0, 1); /* 轻微阴影,增强层次感 */
}
TextField:hover {
-fx-border-color: #9CA3AF; /* 悬停时边框加深,提示可交互 */
}
TextField:disabled {
-fx-background-color: #F3F4F6; /* 禁用时浅灰背景,区分不可操作状态 */
-fx-text-fill: #9CA3AF;
-fx-border-color: #E5E7EB;
}
/* 5. 文本域样式:区分“结果展示”与“操作指引” */
TextArea {
-fx-background-color: #FFFFFF;
-fx-border-color: #E5E7EB;
-fx-border-width: 1px;
-fx-border-radius: 4px;
-fx-text-fill: #111827;
-fx-padding: 12px; /* 内边距充足,避免文字贴边 */
-fx-font-size: 14px;
-fx-wrap-text: true; /* 自动换行,适配长步骤说明与哈希结果 */
-fx-line-spacing: 8px; /* 行间距优化,提升多行文本质感 */
-fx-pref-height: 580px; /* 适配FXML中结果区域高度减少滚动条干扰 */
}
/* 核心结果文本域result7TA强化哈希结果可读性 */
#result7TA {
-fx-background-color: #FCFCFD; /* 比普通文本域稍浅,突出结果内容 */
-fx-pref-width: 600px; /* 适配左侧结果区域宽度,避免文字过宽 */
}
/* 操作指引文本域:区分功能属性 */
TextArea[text*="对照《存储载体检查登记表》"] {
-fx-text-fill: #4B5563; /* 次要文本色,避免与核心结果冲突 */
-fx-background-color: #F9FAFB; /* 背景与面板一致,突出“指引”属性 */
-fx-border-color: #E5E7EB;
-fx-pref-width: 400px; /* 适配右侧指引区域宽度 */
}
/* 文本域交互状态弱化编辑属性因editable="false" */
TextArea {
-fx-cursor: default; /* 默认光标,明确不可编辑状态 */
}
TextArea:focused {
-fx-border-color: #1A56DB; /* 聚焦时仍保留边框反馈,避免用户误解“无响应” */
-fx-border-width: 2px;
}
/* 6. 按钮样式:按功能优先级区分,引导“加载-计算-打包-验证”流程 */
Button {
-fx-border-radius: 4px;
-fx-background-radius: 4px;
-fx-font-size: 14px;
-fx-font-weight: 500;
-fx-padding: 8px 16px;
-fx-cursor: hand; /* 手型光标,提示可点击 */
-fx-alignment: CENTER; /* 文字居中,保证视觉整齐 */
-fx-pref-height: 36px; /* 统一高度,与输入框对齐 */
}
/* 6.1 核心操作按钮:生成哈希值/计算哈希值(高优先级) */
#generateHashFile7B,
#caculateHash7B {
-fx-background-color: #1A56DB; /* 主色调,突出“哈希计算”核心功能(数据完整性验证关键步骤) */
-fx-text-fill: #FFFFFF;
-fx-pref-width: 160px; /* 适配按钮文本长度,避免文字换行 */
}
#generateHashFile7B:hover,
#caculateHash7B:hover {
-fx-background-color: #1E40AF; /* hover时加深提升反馈 */
}
#generateHashFile7B:pressed,
#caculateHash7B:pressed {
-fx-background-color: #1E3A8A; /* 点击时进一步加深,模拟按压感 */
}
#generateHashFile7B:disabled,
#caculateHash7B:disabled {
-fx-background-color: #94A3B8; /* 禁用时灰色,明确不可操作(如未选择文件时) */
-fx-cursor: default;
}
/* 6.2 辅助选择按钮:选择文件(中优先级) */
#selectLoadDigitalOutcomes7B,
#selectLoadCompressedFile7B {
-fx-background-color: #FFFFFF; /* 白色背景,次要功能属性 */
-fx-text-fill: #1A56DB; /* 主色文字,保持视觉关联 */
-fx-border-color: #1A56DB;
-fx-border-width: 1px;
-fx-pref-width: 100px; /* 适配“选择文件”文本长度 */
}
#selectLoadDigitalOutcomes7B:hover,
#selectLoadCompressedFile7B:hover {
-fx-background-color: #EFF6FF; /* hover时淡蓝背景提示交互 */
}
#selectLoadDigitalOutcomes7B:pressed,
#selectLoadCompressedFile7B:pressed {
-fx-background-color: #DBEAFE;
}
/* 6.3 清除操作按钮:清除选择(低优先级,辅助功能) */
#clearSelectedFoldersButton {
-fx-background-color: #FFFFFF;
-fx-text-fill: #6B7280; /* 浅灰文字,弱化次要功能视觉权重 */
-fx-border-color: #E5E7EB; /* 淡灰边框,区分按钮区域 */
-fx-border-width: 1px;
-fx-pref-width: 80px; /* 适配“清除”文本长度 */
}
#clearSelectedFoldersButton:hover {
-fx-background-color: #F3F4F6; /* hover时浅灰背景提示交互 */
-fx-text-fill: #111827; /* hover时文字加深提升可读性 */
}
#clearSelectedFoldersButton:pressed {
-fx-background-color: #E5E7EB;
}
/* 7. 特殊状态样式:适配哈希计算与压缩包验证场景 */
/* 7.1 加载中状态(如哈希计算过程中) */
.loading {
-fx-background-color: #F3F4F6;
-fx-cursor: wait; /* 等待光标,提示正在处理 */
}
/* 7.2 成功状态(结果区域高亮) */
.success {
-fx-background-color: #059669; /* 成功色,传递“哈希值一致”反馈 */
-fx-font-weight: 600;
}
/* 7.3 失败状态(结果区域高亮) */
.error {
-fx-background-color: #DC2626; /* 警示色,突出“哈希值不一致”风险 */
-fx-font-weight: 600;
}
/* 7.4 压缩包提示高亮(文本域内关键信息) */
.package-highlight {
-fx-background-color: #1A56DB; /* 主色调,突出“数字化验收检测包.rar”核心文件名 */
-fx-font-weight: 600;
}

Binary file not shown.

View File

@ -12,7 +12,7 @@
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<VBox minHeight="-Infinity" minWidth="-Infinity" prefHeight="1000.0" prefWidth="1200.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="top.r3944realms.docchecktoolrefactored.ui.MainStageController">
<VBox minHeight="-Infinity" minWidth="-Infinity" prefHeight="1000.0" prefWidth="1200.0" stylesheets="@../css/main.css" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="top.r3944realms.docchecktoolrefactored.ui.MainStageController">
<children>
<MenuBar prefWidth="2558.0" VBox.vgrow="ALWAYS">
<menus>
@ -27,7 +27,7 @@
</Menu>
<Menu fx:id="helpM" mnemonicParsing="false" text="帮助">
<items>
<MenuItem fx:id="helpDocMI" mnemonicParsing="false" onAction="#onOpenHelpDoc" text="帮助文档" />
<MenuItem fx:id="helpDocMI" mnemonicParsing="false" onAction="#onOpenHelpDoc" text="用户使用说明书" />
<SeparatorMenuItem mnemonicParsing="false" />
<MenuItem fx:id="aboutSoftwareMI" mnemonicParsing="false" onAction="#onAbout" text="关于软件" />
</items>
@ -76,7 +76,7 @@
</Tab>
</tabs>
</TabPane>
<HBox nodeOrientation="RIGHT_TO_LEFT" prefHeight="100.0" prefWidth="200.0" VBox.vgrow="ALWAYS">
<HBox fx:id="stepAssistant" nodeOrientation="RIGHT_TO_LEFT" prefHeight="100.0" prefWidth="200.0" VBox.vgrow="ALWAYS">
<children>
<Button fx:id="nextB" mnemonicParsing="false" onAction="#onNext" prefHeight="100.0" prefWidth="500.0" text="下一步">
<HBox.margin>

View File

@ -9,7 +9,7 @@
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?>
<GridPane xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="top.r3944realms.docchecktoolrefactored.ui.module.ProjectInfoPaneController">
<GridPane stylesheets="@../../css/project-info-pane.css" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="top.r3944realms.docchecktoolrefactored.ui.module.ProjectInfoPaneController">
<columnConstraints>
<ColumnConstraints hgrow="ALWAYS" maxWidth="-Infinity" minWidth="10.0" prefWidth="80.0" />
<ColumnConstraints hgrow="ALWAYS" minWidth="10.0" prefWidth="100.0" />
@ -134,7 +134,7 @@
<Font size="14.0" />
</font>
</TextField>
<Button fx:id="infoResetB" mnemonicParsing="false" onAction="#onReset" prefHeight="93.0" prefWidth="55.0" text="重置" GridPane.columnIndex="4" GridPane.rowSpan="4">
<Button id="infoResetB " fx:id="infoResetB" mnemonicParsing="false" onAction="#onReset" prefHeight="93.0" prefWidth="55.0" text="重置" GridPane.columnIndex="4" GridPane.rowSpan="4">
<GridPane.margin>
<Insets bottom="10.0" left="20.0" right="10.0" top="10.0" />
</GridPane.margin>

View File

@ -10,10 +10,10 @@
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?>
<GridPane prefHeight="800.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="top.r3944realms.docchecktoolrefactored.ui.module.DuplicateDocumentPaneController">
<GridPane prefHeight="800.0" prefWidth="1000.0" stylesheets="@../../css/step-1-pane.css" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="top.r3944realms.docchecktoolrefactored.ui.module.DuplicateDocumentPaneController">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="288.0" minWidth="0.0" percentWidth="0.0" prefWidth="82.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="1263.9999633789064" minWidth="0.0" prefWidth="745.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="288.0" minWidth="0.0" percentWidth="0.0" prefWidth="113.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="1263.9999633789064" minWidth="0.0" prefWidth="479.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="873.0" minWidth="0.0" prefWidth="82.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="450.0" minWidth="10.0" prefWidth="400.0" />
<ColumnConstraints />
@ -25,7 +25,7 @@
<RowConstraints maxHeight="592.6666666666666" prefHeight="581.3333536783855" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<TextArea fx:id="result1TA" editable="false" prefHeight="414.0" prefWidth="683.0" GridPane.columnSpan="3" GridPane.rowIndex="3">
<TextArea id="result1TA" fx:id="result1TA" editable="false" prefHeight="414.0" prefWidth="683.0" GridPane.columnSpan="3" GridPane.rowIndex="3">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
@ -34,19 +34,19 @@
<GridPane.margin>
<Insets left="10.0" />
</GridPane.margin></Label>
<TextField fx:id="loadFolder1TF" GridPane.columnIndex="1">
<TextField fx:id="loadFolder1TF" prefHeight="37.0" prefWidth="430.0" GridPane.columnIndex="1">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
</TextField>
<Button fx:id="selectLoadFolder1B" mnemonicParsing="false" onAction="#onSelectFolder" text="选择文件夹" GridPane.columnIndex="3" GridPane.columnSpan="2" GridPane.halignment="CENTER" GridPane.valignment="CENTER">
<Button id="selectLoadFolder1B" fx:id="selectLoadFolder1B" mnemonicParsing="false" onAction="#onSelectFolder" text="选择文件夹" GridPane.columnIndex="3" GridPane.columnSpan="2" GridPane.halignment="CENTER" GridPane.valignment="CENTER">
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin></Button>
<Button fx:id="start1B" minWidth="-Infinity" mnemonicParsing="false" onAction="#onStart" prefHeight="58.0" prefWidth="650.0" text="开始检查" GridPane.columnSpan="3" GridPane.halignment="LEFT" GridPane.rowIndex="1" GridPane.valignment="CENTER">
<Button id="start1B" fx:id="start1B" minWidth="-Infinity" mnemonicParsing="false" onAction="#onStart" prefHeight="47.0" prefWidth="582.0" text="开始检查" GridPane.columnSpan="3" GridPane.halignment="LEFT" GridPane.rowIndex="1" GridPane.valignment="CENTER">
<GridPane.margin>
<Insets bottom="2.0" left="10.0" right="2.0" top="2.0" />
</GridPane.margin>
@ -55,7 +55,7 @@
<GridPane.margin>
<Insets left="10.0" />
</GridPane.margin></Label>
<TextArea editable="false" maxWidth="1.7976931348623157E308" prefWidth="400.0" text="1.点击“选择文件夹”按钮,载入需要查找重复文件的数据(一般页面级文件和文件级文件分批载入检查)。&#10;2.点击“开始检查”按钮,软件将对选定区域的数据批量计算文件哈希值,并对比查找重复文件,“结果反馈”区域将显示扫描文件数量、重复文件组和重复文件数量。&#10;3.根据软件反馈的重复文件组,逐一核实确认是否为重复文件。&#10;4.将确认后的检查结果填入查重登记表附件1。" wrapText="true" GridPane.columnIndex="3" GridPane.columnSpan="2" GridPane.rowIndex="3">
<TextArea editable="false" maxWidth="1.7976931348623157E308" prefWidth="400.0" text="1.点击“选择文件夹”按钮,载入需要查找重复文件的数据(一般页面级文件和文件级文件分批载入检查)。&#10;&#10;2.点击“开始检查”按钮,软件将对选定区域的数据批量计算文件哈希值,并对比查找重复文件,“结果反馈”区域将显示扫描文件数量、重复文件组和重复文件数量。&#10;&#10;3.根据软件反馈的重复文件组,逐一核实确认是否为重复文件。&#10;&#10;4.将确认后的检查结果填入查重登记表附件1。" wrapText="true" GridPane.columnIndex="3" GridPane.columnSpan="2" GridPane.rowIndex="3">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
@ -71,7 +71,7 @@
<Insets left="10.0" />
</GridPane.margin>
</Label>
<Button fx:id="cancel1B" alignment="CENTER" mnemonicParsing="false" onAction="#onCancel" prefHeight="52.0" prefWidth="117.0" text="取消检查" GridPane.columnIndex="3" GridPane.rowIndex="1">
<Button id="cancel1B" fx:id="cancel1B" alignment="CENTER" mnemonicParsing="false" nodeOrientation="LEFT_TO_RIGHT" onAction="#onCancel" prefHeight="52.0" prefWidth="117.0" text="取消检查" GridPane.columnIndex="3" GridPane.columnSpan="2" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="CENTER">
<GridPane.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</GridPane.margin>

View File

@ -11,7 +11,7 @@
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?>
<GridPane prefHeight="800.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="top.r3944realms.docchecktoolrefactored.ui.module.PathCheckPaneController">
<GridPane prefHeight="800.0" prefWidth="1000.0" stylesheets="@../../css/step-2-pane.css" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="top.r3944realms.docchecktoolrefactored.ui.module.PathCheckPaneController">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="940.0" minWidth="0.0" percentWidth="0.0" prefWidth="104.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="940.0" minWidth="10.0" percentWidth="0.0" prefWidth="104.0" />
@ -51,7 +51,7 @@
<Insets left="10.0" />
</GridPane.margin>
</Label>
<TextArea fx:id="result2TA" editable="false" GridPane.columnSpan="4" GridPane.rowIndex="4">
<TextArea id="result2TA" fx:id="result2TA" editable="false" GridPane.columnSpan="4" GridPane.rowIndex="4">
<padding>
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
</padding>
@ -59,7 +59,7 @@
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
</TextArea>
<Button fx:id="start2B" mnemonicParsing="false" onAction="#onStart" prefHeight="75.0" prefWidth="800.0" text="开始比对" GridPane.columnSpan="4" GridPane.halignment="LEFT" GridPane.rowIndex="2" GridPane.valignment="CENTER">
<Button id="start2B" fx:id="start2B" mnemonicParsing="false" onAction="#onStart" prefHeight="75.0" prefWidth="800.0" text="开始比对" GridPane.columnSpan="4" GridPane.halignment="LEFT" GridPane.rowIndex="2" GridPane.valignment="CENTER">
<padding>
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
</padding>
@ -67,7 +67,7 @@
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
<font>
<Font size="14.0" />
<Font size="16.0" />
</font>
</Button>
<TextField fx:id="loadCatalog2TF" GridPane.columnIndex="2">
@ -86,7 +86,7 @@
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
</TextField>
<Button fx:id="selectLoadCatalog2B" mnemonicParsing="false" onAction="#onSelectLC" text=" 选择文件 " GridPane.columnIndex="3" GridPane.halignment="CENTER" GridPane.valignment="CENTER">
<Button id="selectLoadCatalog2B" fx:id="selectLoadCatalog2B" mnemonicParsing="false" onAction="#onSelectLC" text=" 选择文件 " GridPane.columnIndex="3" GridPane.halignment="CENTER" GridPane.valignment="CENTER">
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
@ -94,7 +94,7 @@
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
</Button>
<Button fx:id="selectJPGFolder2B" mnemonicParsing="false" onAction="#onSelectJPGF" text=" 选择文件夹" GridPane.columnIndex="3" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="CENTER">
<Button id="selectJPGFolder2B" fx:id="selectJPGFolder2B" mnemonicParsing="false" onAction="#onSelectJPGF" text=" 选择文件夹" GridPane.columnIndex="3" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="CENTER">
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
@ -102,7 +102,7 @@
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
</Button>
<Button fx:id="generateLogicalAddress2B" mnemonicParsing="false" onAction="#onGenerateLA" text="生成逻辑地址文件" GridPane.columnIndex="4" GridPane.halignment="CENTER" GridPane.valignment="CENTER">
<Button id="generateLogicalAddress2B" fx:id="generateLogicalAddress2B" mnemonicParsing="false" onAction="#onGenerateLA" text="生成逻辑地址文件" GridPane.columnIndex="4" GridPane.halignment="CENTER" GridPane.valignment="CENTER">
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
@ -110,7 +110,7 @@
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
</Button>
<Button fx:id="generatePhysicalAddress2B" mnemonicParsing="false" onAction="#onGeneratePA" text="生成物理地址文件" GridPane.columnIndex="4" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="CENTER">
<Button id="generatePhysicalAddress2B" fx:id="generatePhysicalAddress2B" mnemonicParsing="false" onAction="#onGeneratePA" text="生成物理地址文件" GridPane.columnIndex="4" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="CENTER">
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
@ -118,7 +118,7 @@
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
</Button>
<TextArea editable="false" prefHeight="450.0" prefWidth="400.0" text="1.选择“页面级”,载入目录,生成页面级逻辑地址;载入目录对应的页面级数据,生成页面级物理地址,用软件比对出差异,记录软件反馈结果。&#10;2.选择“文件级”,载入目录,生成文件级逻辑地址;载入目录对应的文件级数据,生成文件级物理地址,用软件比对出差异,记录软件反馈结果。&#10;3.逐一核实差异存在的原因,以判断目录和扫描数据存在的问题。&#10;4.根据核实的情况填写《查遗漏、查存储路径和命名规范登记表》附件2。&#10;&#10;" wrapText="true" GridPane.columnIndex="4" GridPane.rowIndex="4">
<TextArea editable="false" prefHeight="450.0" prefWidth="400.0" text="1.选择“页面级”,载入目录,生成页面级逻辑地址;载入目录对应的页面级数据,生成页面级物理地址,用软件比对出差异,记录软件反馈结果。&#10;&#10;2.选择“文件级”,载入目录,生成文件级逻辑地址;载入目录对应的文件级数据,生成文件级物理地址,用软件比对出差异,记录软件反馈结果。&#10;&#10;3.逐一核实差异存在的原因,以判断目录和扫描数据存在的问题。&#10;&#10;4.根据核实的情况填写《查遗漏、查存储路径和命名规范登记表》附件2。&#10;&#10;" wrapText="true" GridPane.columnIndex="4" GridPane.rowIndex="4">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>

View File

@ -5,7 +5,7 @@
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<AnchorPane prefHeight="800.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/17">
<AnchorPane prefHeight="800.0" prefWidth="1000.0" stylesheets="@../../css/step-3456-pane.css" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1">
<children>
<TextArea editable="false" prefHeight="800.0" prefWidth="1000.0" scrollLeft="1.0" text="工作内容:&#10; 1.汇总前两步检查结果计算合格率合格率不是100%则验收不通过,要求整改。&#10; 2.若合格率达到100%则按总页数5%比例抽检:&#10; ①著录准确性/规范性/完整性要求100%合格率);&#10; ②图像清晰度/倾斜度/黑边要求95%以上的合格率);&#10; ③结果填入《质量检查登记表》附件3。" wrapText="true" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<font>

View File

@ -5,7 +5,7 @@
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<AnchorPane prefHeight="800.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1">
<AnchorPane prefHeight="800.0" prefWidth="1000.0" stylesheets="@../../css/step-3456-pane.css" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1">
<children>
<TextArea editable="false" prefHeight="800.0" prefWidth="1000.0" scrollLeft="1.0" text="工作内容:&#10; 对照《元数据检查登记表》附件4检查并登记数字化项目信息、技术环境及技术参数的完整性等情况。" wrapText="true" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<font>

View File

@ -5,7 +5,7 @@
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<AnchorPane prefHeight="800.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1">
<AnchorPane prefHeight="800.0" prefWidth="1000.0" stylesheets="@../../css/step-3456-pane.css" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1">
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>

View File

@ -5,7 +5,7 @@
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<AnchorPane prefHeight="800.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1">
<AnchorPane prefHeight="800.0" prefWidth="1000.0" stylesheets="@../../css/step-3456-pane.css" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1">
<children>
<TextArea editable="false" prefHeight="800.0" prefWidth="1000.0" text="工作内容:&#10; 对照《工作记录检查登记表》附件6检查数字化工作台帐的规范性及与成果的一致性并在表格中登记检查情况。" wrapText="true" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<font>

View File

@ -10,22 +10,23 @@
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?>
<GridPane prefHeight="800.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="top.r3944realms.docchecktoolrefactored.ui.module.StorageCarrierPaneController">
<GridPane prefHeight="800.0" prefWidth="1065.0" stylesheets="@../../css/step-7-pane.css" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="top.r3944realms.docchecktoolrefactored.ui.module.StorageCarrierPaneController">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="226.33331298828125" minWidth="10.0" percentWidth="10.0" prefWidth="108.33333333333334" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="559.3333511352539" minWidth="10.0" percentWidth="40.0" prefWidth="373.00002034505206" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="345.3333435058594" minWidth="10.0" percentWidth="25.0" prefWidth="227.00004069010413" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="226.33331298828125" percentWidth="20.0" prefWidth="400.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="559.3333511352539" minWidth="10.0" percentWidth="40.0" prefWidth="202.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="559.3333511352539" minWidth="10.0" percentWidth="10.0" prefWidth="200.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="345.3333435058594" minWidth="10.0" percentWidth="25.0" prefWidth="227.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="500.0" minWidth="10.0" percentWidth="25.0" prefWidth="400.0" />
<ColumnConstraints />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="151.33334350585938" percentHeight="7.0" prefHeight="55.00001017252603" vgrow="NEVER" />
<RowConstraints maxHeight="508.33333333333326" percentHeight="7.0" prefHeight="73.99999491373697" vgrow="NEVER" />
<RowConstraints maxHeight="592.6666666666666" percentHeight="7.0" prefHeight="581.3333536783855" vgrow="NEVER" />
<RowConstraints maxHeight="1.7976931348623157E308" prefHeight="581.3333536783855" vgrow="SOMETIMES" />
<RowConstraints maxHeight="151.33334350585938" prefHeight="55.00001017252603" vgrow="SOMETIMES" />
<RowConstraints maxHeight="508.33333333333326" percentHeight="7.0" prefHeight="73.99999491373697" vgrow="SOMETIMES" />
<RowConstraints maxHeight="592.6666666666666" prefHeight="51.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="1.7976931348623157E308" minHeight="-Infinity" prefHeight="605.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label text="载入数字化成果:">
<Label prefHeight="23.0" prefWidth="141.0" text="载入数字化成果:">
<padding>
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
</padding>
@ -33,15 +34,7 @@
<Insets left="5.0" />
</GridPane.margin>
</Label>
<Label text="将档案目录、哈希值列表文件和检测过程文件打包制成打包制成“数字化验收检测包.rar”压缩包" GridPane.columnSpan="5" GridPane.halignment="CENTER" GridPane.rowSpan="2" GridPane.valignment="CENTER">
<padding>
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
</padding>
<font>
<Font name="System Bold" size="14.0" />
</font>
</Label>
<Label text="结果反馈:" GridPane.rowIndex="2">
<Label prefWidth="75.0" text="结果反馈:" GridPane.rowIndex="2">
<padding>
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
</padding>
@ -57,7 +50,7 @@
<Insets left="5.0" />
</GridPane.margin>
</Label>
<TextField fx:id="loadDigitalOutcomes" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS">
<TextField fx:id="loadDigitalOutcomes" prefHeight="37.0" prefWidth="308.0" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
@ -65,7 +58,7 @@
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
</padding>
</TextField>
<TextField fx:id="loadCompressedFile" GridPane.columnIndex="1" GridPane.rowIndex="1">
<TextField fx:id="loadCompressedFile" GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="1">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
@ -73,7 +66,7 @@
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
</padding>
</TextField>
<TextArea fx:id="result7TA" editable="false" prefWidth="400.0" GridPane.columnSpan="3" GridPane.rowIndex="3">
<TextArea fx:id="result7TA" editable="false" prefHeight="450.0" prefWidth="400.0" GridPane.columnSpan="3" GridPane.rowIndex="3">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
@ -81,7 +74,7 @@
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
</padding>
</TextArea>
<Button fx:id="selectLoadDigitalOutcomes7B" mnemonicParsing="false" onAction="#onSelectLD" text="选择文件" textAlignment="CENTER" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.valignment="CENTER">
<Button fx:id="selectLoadDigitalOutcomes7B" mnemonicParsing="false" onAction="#onSelectLD" text="选择文件" textAlignment="CENTER" GridPane.columnIndex="3" GridPane.halignment="CENTER" GridPane.valignment="CENTER">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
@ -92,7 +85,7 @@
<Font size="14.0" />
</font>
</Button>
<Button fx:id="generateHashFile7B" mnemonicParsing="false" onAction="#onGenerateHF" text="生成哈希值列表文件" GridPane.columnIndex="3" GridPane.halignment="CENTER" GridPane.valignment="CENTER">
<Button fx:id="selectLoadCompressedFile7B" mnemonicParsing="false" onAction="#onSelectLC" text="选择文件" GridPane.columnIndex="3" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="CENTER">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
@ -103,7 +96,7 @@
<Font size="14.0" />
</font>
</Button>
<Button fx:id="selectLoadCompressedFile7B" mnemonicParsing="false" onAction="#onSelectLC" text="选择文件" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="CENTER">
<Button fx:id="caculateHash7B" mnemonicParsing="false" onAction="#onCaculateHash" text="计算哈希值" GridPane.columnIndex="4" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="CENTER">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
@ -114,7 +107,7 @@
<Font size="14.0" />
</font>
</Button>
<Button fx:id="caculateHash7B" mnemonicParsing="false" onAction="#onCaculateHash" text="计算哈希值" GridPane.columnIndex="3" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="CENTER">
<Button fx:id="generateHashFile7B" mnemonicParsing="false" onAction="#onGenerateHF" prefHeight="35.0" prefWidth="157.0" text="生成哈希值表文件" GridPane.columnIndex="4" GridPane.halignment="CENTER" GridPane.valignment="CENTER">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
@ -125,7 +118,7 @@
<Font size="14.0" />
</font>
</Button>
<TextArea editable="false" maxWidth="1.7976931348623157E308" prefWidth="400.0" text="1.对照《存储载体检查登记表》附件7检查并记录存储载体的类型/数量/内容/可读性情况&#10;2.计算数字化成果(包括单页、多页文件)的MD5码生成列表文件保存在目录所在文件夹&#10;3.将列表文件、目录文件、检测过程文件第2步生成的逻辑地址、物理地址等csv文件打包生成&quot;数字化验收检测包.rar&quot;&#10;4.计算并验证压缩包的MD5码或哈希值&#10;5.结果填入《存储载体检查登记表》附件7" wrapText="true" GridPane.columnIndex="3" GridPane.rowIndex="3">
<TextArea editable="false" prefHeight="450.0" prefWidth="400.0" text="1.对照《存储载体检查登记表》附件7检查并记录存储载体的类型/数量/内容/可读性情况;&#10;&#10;2.计算数字化成果(包括单页、多页文件)的MD5码生成列表文件保存在目录所在文件夹&#10;&#10;3.将列表文件、目录文件、检测过程文件第2步生成的逻辑地址、物理地址等csv文件打包生成&quot;数字化验收检测包.rar&quot;;&#10;&#10;4.计算并验证压缩包的MD5码或哈希值;&#10;5.结果填入《存储载体检查登记表》附件7" wrapText="true" GridPane.columnIndex="3" GridPane.columnSpan="2" GridPane.rowIndex="3">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
@ -144,7 +137,7 @@
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
</padding>
</Label>
<Button fx:id="clearSelectedFoldersButton" mnemonicParsing="false" onAction="#onClearSelectedFolders" text="清除" GridPane.columnIndex="2">
<Button fx:id="clearSelectedFoldersButton" mnemonicParsing="false" onAction="#onClearSelectedFolders" text="清除" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.valignment="CENTER">
<font>
<Font size="14.0" />
</font>

View File

@ -12,19 +12,20 @@
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<VBox xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="top.r3944realms.docchecktoolrefactored.ui.SettingDialogController">
<VBox stylesheets="@../css/setting.css" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="top.r3944realms.docchecktoolrefactored.ui.SettingDialogController">
<children>
<GridPane>
<GridPane VBox.vgrow="ALWAYS">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="138.0" minWidth="10.0" prefWidth="133.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="109.0" minWidth="10.0" prefWidth="94.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="95.0" minWidth="10.0" prefWidth="73.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="192.0" minWidth="10.0" prefWidth="147.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="131.0" minWidth="10.0" prefWidth="119.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="95.0" minWidth="10.0" prefWidth="34.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="ALWAYS" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="ALWAYS" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="ALWAYS" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="ALWAYS" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="ALWAYS" />
</rowConstraints>
<children>
<Label text="步骤扫描超时时间:" GridPane.rowIndex="1">
@ -37,12 +38,12 @@
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
</Spinner>
<Label prefWidth="114.0" text="步骤任务超时时间:" GridPane.rowIndex="2">
<Label prefHeight="19.0" prefWidth="167.0" text="步骤任务超时时间:" GridPane.rowIndex="3">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
</Label>
<Spinner fx:id="taskTimeOutS" onMouseDragExited="#onCheckTwo" GridPane.columnIndex="1" GridPane.rowIndex="2">
<Spinner fx:id="taskTimeOutS" onMouseDragExited="#onCheckThree" GridPane.columnIndex="1" GridPane.rowIndex="3">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
@ -53,8 +54,9 @@
</font>
</Label>
<Label text="秒" GridPane.columnIndex="2" GridPane.rowIndex="1" />
<Label text="秒" GridPane.columnIndex="2" GridPane.rowIndex="2" />
<CheckBox fx:id="enableStepCB" mnemonicParsing="false" onAction="#onSettingThree" text="启用步骤辅助" GridPane.columnSpan="3" GridPane.halignment="CENTER" GridPane.rowIndex="3" GridPane.valignment="CENTER" />
<Label text="秒" GridPane.columnIndex="2" GridPane.rowIndex="3" />
<CheckBox fx:id="enableStepCB" mnemonicParsing="false" onAction="#onSettingFour" text="启用步骤辅助" GridPane.columnSpan="3" GridPane.halignment="CENTER" GridPane.rowIndex="4" GridPane.valignment="CENTER" />
<CheckBox fx:id="enableTaskTimeoutCB" mnemonicParsing="false" onAction="#onSettingTwo" text="启用步骤任务超时终止" GridPane.columnSpan="3" GridPane.halignment="CENTER" GridPane.rowIndex="2" GridPane.valignment="CENTER" />
</children>
</GridPane>
<HBox alignment="CENTER">

View File

@ -3,7 +3,7 @@
<!-- 属性定义保持不变 -->
<property name="APP_NAME" value="DocCheckTool"/>
<property name="LOG_HOME" value="${APP_NAME}/${log.dir:-logs}"/>
<property name="LOG_HOME" value="${log.dir:-logs}"/>
<property name="RELEASE_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss} [%level] %msg%n"/>
<property name="DEBUG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{80} - %msg%n"/>
<property name="TRACKER_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{80} - %msg%n"/>
@ -12,7 +12,7 @@
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<encoder charset="UTF-8" >
<pattern>${DEBUG_PATTERN}</pattern>
</encoder>
</appender>
@ -31,7 +31,7 @@
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<encoder charset="UTF-8">
<pattern>${RELEASE_PATTERN}</pattern>
</encoder>
</appender>
@ -50,7 +50,7 @@
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<encoder charset="UTF-8">
<pattern>${DEBUG_PATTERN}</pattern>
</encoder>
</appender>
@ -69,7 +69,7 @@
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<encoder charset="UTF-8">
<pattern>${TRACKER_PATTERN}</pattern>
</encoder>
</appender>
@ -89,6 +89,13 @@
<appender-ref ref="TRACKER_FILE"/>
<appender-ref ref="STDOUT"/>
</logger>
<!-- 添加你刚刚添加的logger -->
<logger name="top.r3944realms.docchecktoolrefactored" level="DEBUG" additivity="false">
<appender-ref ref="RELEASE_FILE"/>
<appender-ref ref="DEBUG_FILE"/>
<appender-ref ref="TRACKER_FILE"/>
<appender-ref ref="STDOUT"/>
</logger>
<!-- ROOT Logger - 输出到控制台 -->
<root level="INFO">

Binary file not shown.

Binary file not shown.

BIN
用户使用说明书.pdf Normal file

Binary file not shown.