Compare commits
10 Commits
4625d30105
...
96f7acb2a9
| Author | SHA1 | Date | |
|---|---|---|---|
| 96f7acb2a9 | |||
|
|
d1608666db | ||
|
|
df39df55d7 | ||
|
|
a447e791e0 | ||
| 98796bf123 | |||
|
|
67a35cc661 | ||
| 0f54983b30 | |||
| 81b420bacb | |||
| b7030ede3c | |||
|
|
0c5a75a17c |
|
|
@ -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$" />
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
86
build.gradle
86
build.gradle
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
project_group =top.r3944realms.docchecktoolrefacored
|
||||
project_name = doc-check-tool
|
||||
project_version = 1.0
|
||||
project_version = 1.1
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 格式
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 + ",停止处理");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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) -> {
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
});
|
||||
}
|
||||
// ====================================================================
|
||||
}
|
||||
|
|
@ -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");
|
||||
});
|
||||
}
|
||||
// ====================================================================
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
});
|
||||
}
|
||||
// ====================================================================
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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("操作已被取消");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
124
src/main/resources/css/inner/dialog.css
Normal file
124
src/main/resources/css/inner/dialog.css
Normal 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,反馈明显 */
|
||||
}
|
||||
84
src/main/resources/css/inner/progress.css
Normal file
84
src/main/resources/css/inner/progress.css
Normal 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;
|
||||
}
|
||||
157
src/main/resources/css/main.css
Normal file
157
src/main/resources/css/main.css
Normal 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;
|
||||
}
|
||||
93
src/main/resources/css/project-info-pane.css
Normal file
93
src/main/resources/css/project-info-pane.css
Normal 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;
|
||||
}
|
||||
169
src/main/resources/css/setting.css
Normal file
169
src/main/resources/css/setting.css
Normal 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,增加与下方取消/保存按钮的距离 */
|
||||
}
|
||||
159
src/main/resources/css/step-1-pane.css
Normal file
159
src/main/resources/css/step-1-pane.css
Normal 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;
|
||||
}
|
||||
207
src/main/resources/css/step-2-pane.css
Normal file
207
src/main/resources/css/step-2-pane.css
Normal 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;
|
||||
}
|
||||
32
src/main/resources/css/step-3456-pane.css
Normal file
32
src/main/resources/css/step-3456-pane.css
Normal 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; /* 取消焦点边框,避免用户误解“可操作” */
|
||||
}
|
||||
197
src/main/resources/css/step-7-pane.css
Normal file
197
src/main/resources/css/step-7-pane.css
Normal 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;
|
||||
}
|
||||
BIN
src/main/resources/docs/UserHelpDocument.pdf
Normal file
BIN
src/main/resources/docs/UserHelpDocument.pdf
Normal file
Binary file not shown.
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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.点击“选择文件夹”按钮,载入需要查找重复文件的数据(一般页面级文件和文件级文件分批载入检查)。 2.点击“开始检查”按钮,软件将对选定区域的数据批量计算文件哈希值,并对比查找重复文件,“结果反馈”区域将显示扫描文件数量、重复文件组和重复文件数量。 3.根据软件反馈的重复文件组,逐一核实确认是否为重复文件。 4.将确认后的检查结果填入查重登记表(附件1)。" wrapText="true" GridPane.columnIndex="3" GridPane.columnSpan="2" GridPane.rowIndex="3">
|
||||
<TextArea editable="false" maxWidth="1.7976931348623157E308" prefWidth="400.0" text="1.点击“选择文件夹”按钮,载入需要查找重复文件的数据(一般页面级文件和文件级文件分批载入检查)。 2.点击“开始检查”按钮,软件将对选定区域的数据批量计算文件哈希值,并对比查找重复文件,“结果反馈”区域将显示扫描文件数量、重复文件组和重复文件数量。 3.根据软件反馈的重复文件组,逐一核实确认是否为重复文件。 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>
|
||||
|
|
|
|||
|
|
@ -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.选择“页面级”,载入目录,生成页面级逻辑地址;载入目录对应的页面级数据,生成页面级物理地址,用软件比对出差异,记录软件反馈结果。 2.选择“文件级”,载入目录,生成文件级逻辑地址;载入目录对应的文件级数据,生成文件级物理地址,用软件比对出差异,记录软件反馈结果。 3.逐一核实差异存在的原因,以判断目录和扫描数据存在的问题。 4.根据核实的情况填写《查遗漏、查存储路径和命名规范登记表》(附件2)。 " wrapText="true" GridPane.columnIndex="4" GridPane.rowIndex="4">
|
||||
<TextArea editable="false" prefHeight="450.0" prefWidth="400.0" text="1.选择“页面级”,载入目录,生成页面级逻辑地址;载入目录对应的页面级数据,生成页面级物理地址,用软件比对出差异,记录软件反馈结果。 2.选择“文件级”,载入目录,生成文件级逻辑地址;载入目录对应的文件级数据,生成文件级物理地址,用软件比对出差异,记录软件反馈结果。 3.逐一核实差异存在的原因,以判断目录和扫描数据存在的问题。 4.根据核实的情况填写《查遗漏、查存储路径和命名规范登记表》(附件2)。 " 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>
|
||||
|
|
|
|||
|
|
@ -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="工作内容: 1.汇总前两步检查结果,计算合格率,合格率不是100%则验收不通过,要求整改。 2.若合格率达到100%,则按总页数5%比例抽检: ①著录准确性/规范性/完整性(要求100%合格率); ②图像清晰度/倾斜度/黑边(要求95%以上的合格率); ③结果填入《质量检查登记表》(附件3)。" wrapText="true" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<font>
|
||||
|
|
|
|||
|
|
@ -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="工作内容: 对照《元数据检查登记表》(附件4)检查并登记数字化项目信息、技术环境及技术参数的完整性等情况。" wrapText="true" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<font>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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="工作内容: 对照《工作记录检查登记表》(附件6)检查数字化工作台帐的规范性及与成果的一致性,并在表格中登记检查情况。" wrapText="true" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<font>
|
||||
|
|
|
|||
|
|
@ -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)检查并记录存储载体的类型/数量/内容/可读性情况。 2.计算数字化成果(包括单页、多页文件)的MD5码,生成列表文件,保存在目录所在文件夹; 3.将列表文件、目录文件、检测过程文件(第2步生成的逻辑地址、物理地址等csv文件)打包生成"数字化验收检测包.rar" 4.计算并验证压缩包的MD5码或哈希值 5.结果填入《存储载体检查登记表》(附件7)" wrapText="true" GridPane.columnIndex="3" GridPane.rowIndex="3">
|
||||
<TextArea editable="false" prefHeight="450.0" prefWidth="400.0" text="1.对照《存储载体检查登记表》(附件7)检查并记录存储载体的类型/数量/内容/可读性情况; 2.计算数字化成果(包括单页、多页文件)的MD5码,生成列表文件,保存在目录所在文件夹; 3.将列表文件、目录文件、检测过程文件(第2步生成的逻辑地址、物理地址等csv文件)打包生成"数字化验收检测包.rar"; 4.计算并验证压缩包的MD5码或哈希值; 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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
BIN
源程序文档_2025-11-27-2.docx
Normal file
BIN
源程序文档_2025-11-27-2.docx
Normal file
Binary file not shown.
BIN
源程序文档_2025-11-27.docx
Normal file
BIN
源程序文档_2025-11-27.docx
Normal file
Binary file not shown.
BIN
用户使用说明书.pdf
Normal file
BIN
用户使用说明书.pdf
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user