commit e764df736acad92f7dad188158d01ab00668a681 Author: 3944Realms Date: Mon Jul 14 11:45:14 2025 +0800 feat:初始化项目结构 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..418b952 --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ +/doc/ +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store +/logs/ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..ce1c62c --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,16 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/intellij-javadocs-4.0.1.xml b/.idea/intellij-javadocs-4.0.1.xml new file mode 100644 index 0000000..1cfecdb --- /dev/null +++ b/.idea/intellij-javadocs-4.0.1.xml @@ -0,0 +1,204 @@ + + + + + UPDATE + false + true + + TYPE + FIELD + METHOD + + + PUBLIC + PROTECTED + DEFAULT + + + + + + ^.*(public|protected|private)*.+interface\s+\w+.* + /**\n + * The interface ${name}.\n +<#if element.typeParameters?has_content> * \n +</#if> +<#list element.typeParameters as typeParameter> + * @param <${typeParameter.name}> the type parameter\n +</#list> + */ + + + ^.*(public|protected|private)*.+enum\s+\w+.* + /**\n + * The enum ${name}.\n + */ + + + ^.*(public|protected|private)*.+class\s+\w+.* + /**\n + * The type ${name}.\n +<#if element.typeParameters?has_content> * \n +</#if> +<#list element.typeParameters as typeParameter> + * @param <${typeParameter.name}> the type parameter\n +</#list> + */ + + + .+ + /**\n + * The type ${name}.\n + */ + + + + + .+ + /**\n + * Instantiates a new ${name}.\n +<#if element.parameterList.parameters?has_content> + *\n +</#if> +<#list element.parameterList.parameters as parameter> + * @param ${parameter.name} the ${paramNames[parameter.name]}\n +</#list> +<#if element.throwsList.referenceElements?has_content> + *\n +</#if> +<#list element.throwsList.referenceElements as exception> + * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n +</#list> + */ + + + + + ^.*(public|protected|private)*\s*.*(\w(\s*<.+>)*)+\s+get\w+\s*\(.*\).+ + /**\n + * Gets ${partName}.\n +<#if element.typeParameters?has_content> * \n +</#if> +<#list element.typeParameters as typeParameter> + * @param <${typeParameter.name}> the type parameter\n +</#list> +<#if element.parameterList.parameters?has_content> + *\n +</#if> +<#list element.parameterList.parameters as parameter> + * @param ${parameter.name} the ${paramNames[parameter.name]}\n +</#list> +<#if isNotVoid> + *\n + * @return the ${partName}\n +</#if> +<#if element.throwsList.referenceElements?has_content> + *\n +</#if> +<#list element.throwsList.referenceElements as exception> + * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n +</#list> + */ + + + ^.*(public|protected|private)*\s*.*(void|\w(\s*<.+>)*)+\s+set\w+\s*\(.*\).+ + /**\n + * Sets ${partName}.\n +<#if element.typeParameters?has_content> * \n +</#if> +<#list element.typeParameters as typeParameter> + * @param <${typeParameter.name}> the type parameter\n +</#list> +<#if element.parameterList.parameters?has_content> + *\n +</#if> +<#list element.parameterList.parameters as parameter> + * @param ${parameter.name} the ${paramNames[parameter.name]}\n +</#list> +<#if isNotVoid> + *\n + * @return the ${partName}\n +</#if> +<#if element.throwsList.referenceElements?has_content> + *\n +</#if> +<#list element.throwsList.referenceElements as exception> + * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n +</#list> + */ + + + ^.*((public\s+static)|(static\s+public))\s+void\s+main\s*\(\s*String\s*(\[\s*\]|\.\.\.)\s+\w+\s*\).+ + /**\n + * The entry point of application.\n + + <#if element.parameterList.parameters?has_content> + *\n +</#if> + * @param ${element.parameterList.parameters[0].name} the input arguments\n +<#if element.throwsList.referenceElements?has_content> + *\n +</#if> +<#list element.throwsList.referenceElements as exception> + * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n +</#list> + */ + + + .+ + /**\n + * ${name}<#if isNotVoid> ${return}</#if>.\n +<#if element.typeParameters?has_content> * \n +</#if> +<#list element.typeParameters as typeParameter> + * @param <${typeParameter.name}> the type parameter\n +</#list> +<#if element.parameterList.parameters?has_content> + *\n +</#if> +<#list element.parameterList.parameters as parameter> + * @param ${parameter.name} the ${paramNames[parameter.name]}\n +</#list> +<#if isNotVoid> + *\n + * @return the ${return}\n +</#if> +<#if element.throwsList.referenceElements?has_content> + *\n +</#if> +<#list element.throwsList.referenceElements as exception> + * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n +</#list> + */ + + + + + ^.*(public|protected|private)*.+static.*(\w\s\w)+.+ + /**\n + * The constant ${element.getName()}.\n + */ + + + ^.*(public|protected|private)*.*(\w\s\w)+.+ + /**\n + <#if element.parent.isInterface()> + * The constant ${element.getName()}.\n +<#else> + * The ${name}.\n +</#if> */ + + + .+ + /**\n + <#if element.parent.isEnum()> + *${name} ${typeName}.\n +<#else> + * The ${name}.\n +</#if>*/ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..72ad960 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..2b63946 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.MD b/README.MD new file mode 100644 index 0000000..3285fd1 --- /dev/null +++ b/README.MD @@ -0,0 +1,76 @@ +# 查重逻辑划分 + +### **1. 核心模块划分** +| 模块名 | 职责 | 包含内容 | +|--------|------|----------| +| **`core`** (核心逻辑) | 处理核心查重算法 | 文件遍历、哈希计算、查重逻辑 | +| **`io`** (I/O处理) | 文件系统交互 | 文件读取、内存映射、并行I/O优化 | +| **`model`** (数据模型) | 定义数据结构 | 文件元数据、哈希结果、查重报告 | +| **`ui`** (用户界面) | GUI展示 | JavaFX面板、控制器、事件处理 | +| **`util`** (工具类) | 公用功能 | 哈希工具、进度计算、日志记录 | + +--- + +### **2. 模块依赖关系** +```mermaid +graph TD + ui --> core + ui --> model + core --> io + core --> model + core --> util + io --> util +``` + +--- + +### **3. 进阶模块(可选扩展)** +| 模块名 | 用途 | +|--------|------| +| **`api`** | 提供REST接口供其他系统调用 | +| **`cli`** | 命令行界面(脱离JavaFX使用) | +| **`plugin`** | 支持扩展哈希算法或存储后端 | + +--- + +### **4. 目录结构示例** +``` +src/ +├── main/ +│ ├── java/ +│ │ ├── top.r3.doc/ +│ │ │ ├── core/ +│ │ │ ├── io/ +│ │ │ ├── model/ +│ │ │ ├── ui/ +│ │ │ ├── util/ +│ │ │ └── Main.java (主入口) +│ └── resources/ (FXML/CSS) +└── test/ (对应模块的单元测试) +``` + +--- + +### **5. 关键设计原则** +1. **单一职责**:每个模块只做一件事(如`io`模块只处理I/O) +2. **依赖倒置**:高层模块(如`ui`)依赖抽象(接口),不依赖具体实现 +3. **可测试性**:核心逻辑与UI分离,便于单元测试 +4. **扩展性**:通过策略模式(如`FileHashStrategy`)支持新哈希算法 + +--- + +### **6. 优化后的查重流程** +```mermaid +sequenceDiagram + UI->>+Core: 启动查重(rootPath) + Core->>+IO: 遍历文件(rootPath) + IO-->>-Core: 返回文件列表 + loop 每个文件 + Core->>+IO: 读取文件内容 + IO-->>-Core: 返回字节数据 + Core->>+Util: 计算哈希 + Util-->>-Core: 返回哈希值 + end + Core->>+Model: 生成查重报告 + Model-->>-UI: 显示结果 +``` \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..e711904 --- /dev/null +++ b/build.gradle @@ -0,0 +1,113 @@ +plugins { + id 'java' + id 'io.franzbecker.gradle-lombok' version '3.0.0' + id 'application' + id 'org.openjfx.javafxplugin' version '0.1.0' + id 'org.beryx.jlink' version '2.26.0' +} + +group = project_group +version = project_version + +repositories { + mavenCentral() +} + +ext { + junitVersion = '5.10.0' +} + +sourceCompatibility = '17' +targetCompatibility = '17' + +tasks.withType(JavaCompile).configureEach { + options.release = 17 // 明确指定Java版本 + options.encoding = 'UTF-8' +} + +application { + mainModule = 'top.r3944realms.docchecktoolrefactored' + mainClass = 'top.r3944realms.docchecktoolrefactored.Main' +} + +javafx { + version = '17.0.6' + modules = ['javafx.controls', 'javafx.fxml'] +} + +processResources { +// exclude 'logback.xml' + duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} + +dependencies { + // Logback classic (included slf4j &) + implementation 'ch.qos.logback:logback-classic:1.5.6' + implementation 'org.apache.logging.log4j:log4j-slf4j2-impl:2.20.0' + + // Lombok + compileOnly("org.projectlombok:lombok:1.18.38") + annotationProcessor("org.projectlombok:lombok:1.18.38") + + testCompileOnly("org.projectlombok:lombok:1.18.38") + testAnnotationProcessor("org.projectlombok:lombok:1.18.38") + + testImplementation("org.junit.jupiter:junit-jupiter-api:${junitVersion}") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${junitVersion}") +} + +test { + useJUnitPlatform() + configurations.configureEach { + exclude group: 'org.apache.logging.log4j', module: 'log4j-slf4j-impl' + } +} + +tasks.register('createLogDir') { + doLast { + mkdir "${projectDir}/logs" + } + +} +// 打包sourcesJar任务 +tasks.register('sourcesJar', Jar) { + dependsOn classes + + from sourceSets.main.allSource +} +// 打包javadocJar任务 +tasks.register('javadocJar', Jar) { + dependsOn javadoc + + from javadoc.destinationDir +} + +// 解决javadoc打包乱码 +javadoc { + options { + encoding "UTF-8" + charSet 'UTF-8' + author true + version true + title "DG_LAB" + } +} + + +jlink { + imageZip = project.file("${buildDir}/distributions/app-${javafx.platform.classifier}.zip") + options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages'] + launcher { + name = 'DocCheckTool' + jvmArgs = ['-Dlogback.configurationFile=./config/logback.xml'] // 支持外部配置 + } + mergedModule { + requires 'java.logging' + requires 'java.xml' + } +} + +jlinkZip { + group = 'distribution' +} +processResources.dependsOn createLogDir \ No newline at end of file diff --git a/doc/request.md b/doc/request.md new file mode 100644 index 0000000..e69de29 diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..c0e4621 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,2 @@ +project_group ='top.r3944realms.docchecktoolrefacored' +project_version = '1.0-SNAPSHOT' \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..249e583 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..09a6ef3 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..1b6c787 --- /dev/null +++ b/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..9bf70ea --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = "DocCheckToolRefactored" \ No newline at end of file diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java new file mode 100644 index 0000000..3fe0648 --- /dev/null +++ b/src/main/java/module-info.java @@ -0,0 +1,18 @@ +module top.r3944realms.docchecktoolrefactored { + requires javafx.graphics; + requires javafx.controls; + requires javafx.fxml; + requires static lombok; + requires org.slf4j; + + opens top.r3944realms.docchecktoolrefactored to javafx.fxml; + opens top.r3944realms.docchecktoolrefactored.ui to javafx.fxml; + opens top.r3944realms.docchecktoolrefactored.ui.module to javafx.fxml; + + exports top.r3944realms.docchecktoolrefactored to javafx.graphics; + exports top.r3944realms.docchecktoolrefactored.ui to javafx.fxml; + exports top.r3944realms.docchecktoolrefactored.ui.module to javafx.fxml; + exports top.r3944realms.docchecktoolrefactored.deprecated to javafx.graphics; + opens top.r3944realms.docchecktoolrefactored.deprecated to javafx.fxml; + +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/docchecktoolrefactored/Main.java b/src/main/java/top/r3944realms/docchecktoolrefactored/Main.java new file mode 100644 index 0000000..af07374 --- /dev/null +++ b/src/main/java/top/r3944realms/docchecktoolrefactored/Main.java @@ -0,0 +1,34 @@ +package top.r3944realms.docchecktoolrefactored; + +import javafx.application.Application; +import javafx.stage.Stage; +import lombok.extern.slf4j.Slf4j; +import top.r3944realms.docchecktoolrefactored.ui.SceneManager; + +/** + * The type Main. + */ +@Slf4j +public class Main extends Application { + @Override + public void init() throws Exception { + super.init(); + } + + @Override + public void start(Stage primaryStage) throws Exception { + SceneManager.init(primaryStage); + SceneManager.switchLoginView(); + primaryStage.show(); + } + + /** + * The entry point of application. + * + * @param args the input arguments + */ + public static void main(String[] args) { + log.info("Hello World!"); + launch(args); + } +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/docchecktoolrefactored/deprecated/BaseFunctionPanel.java b/src/main/java/top/r3944realms/docchecktoolrefactored/deprecated/BaseFunctionPanel.java new file mode 100644 index 0000000..b8549a2 --- /dev/null +++ b/src/main/java/top/r3944realms/docchecktoolrefactored/deprecated/BaseFunctionPanel.java @@ -0,0 +1,29 @@ +package top.r3944realms.docchecktoolrefactored.deprecated; + +import javafx.scene.control.TextArea; +import javafx.scene.layout.Pane; +import javafx.scene.paint.Color; + +public abstract class BaseFunctionPanel extends Pane { + protected DocCheckToolMainFrame mainFrame; + protected TextArea resultTextArea; + protected static final Color PRIMARY_COLOR = Color.rgb(66, 133, 244); + protected static final Color SECONDARY_COLOR = Color.rgb(30, 169, 80); + protected static final Color BACKGROUND_COLOR = Color.rgb(245, 245, 245); + protected static final Color BORDER_COLOR = Color.rgb(222, 226, 230); + + public BaseFunctionPanel(DocCheckToolMainFrame mainFrame) { + this.mainFrame = mainFrame; + setStyle("-fx-background-color: rgb(245,245,245);"); + initComponents(); + } + + protected abstract void initComponents(); + + public void showStatusMessage(String message) { + if (resultTextArea != null) { + resultTextArea.appendText(message + "\n"); + resultTextArea.positionCaret(resultTextArea.getText().length()); + } + } +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/docchecktoolrefactored/deprecated/DocCheckToolMainFrame.java b/src/main/java/top/r3944realms/docchecktoolrefactored/deprecated/DocCheckToolMainFrame.java new file mode 100644 index 0000000..46d9a77 --- /dev/null +++ b/src/main/java/top/r3944realms/docchecktoolrefactored/deprecated/DocCheckToolMainFrame.java @@ -0,0 +1,108 @@ +package top.r3944realms.docchecktoolrefactored.deprecated; + +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Scene; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; +import javafx.scene.text.Font; +import javafx.stage.Stage; + +public class DocCheckToolMainFrame { + private StackPane cardPanel; + private static final Color PRIMARY_COLOR = Color.rgb(66, 133, 244); + private static final Color BACKGROUND_COLOR = Color.rgb(245, 245, 245); + + public void show(Stage primaryStage) { + primaryStage.setTitle("淮阴区数字化档案检查验收系统"); + primaryStage.setWidth(1200); + primaryStage.setHeight(600); + primaryStage.setResizable(false); + + // Create top button panel + HBox topButtonPanel = new HBox(10); + topButtonPanel.setPadding(new Insets(10)); + topButtonPanel.setAlignment(Pos.CENTER_LEFT); + topButtonPanel.setStyle("-fx-background-color: rgb(245,245,245);"); + + String[] buttonTexts = {"1 查重复文件", "2 查遗漏、存储路径和命名规范", "3 查质量", + "4 元数据", "5 查数据挂接", "6 查工作文件", "7 查存储载体"}; + + for (String text : buttonTexts) { + Button btn = new Button(text); + btn.setFont(Font.font("Microsoft YaHei", 14)); + btn.setTextFill(Color.WHITE); + btn.setStyle("-fx-background-color: rgb(66,133,244); -fx-padding: 5 15 5 15;"); + btn.setOnAction(e -> switchPanel(text)); + topButtonPanel.getChildren().add(btn); + } + + // Initialize card panel + cardPanel = new StackPane(); + cardPanel.setStyle("-fx-background-color: rgb(245,245,245);"); + + // Add all function panels to card panel + cardPanel.getChildren().addAll( + new DuplicateCheckPanel(this), + new PathCheckPanel(this), + new OtherFunctionPanel(" 3 查质量"), + new OtherFunctionPanel("4 元数据"), + new OtherFunctionPanel("5 查数据挂接"), + new OtherFunctionPanel("6 查工作文件"), + new OtherFunctionPanel("7 查存储载体") + ); + + // Initially show the first panel + switchPanel("1 查重复文件"); + + // Create main layout + BorderPane root = new BorderPane(); + root.setTop(topButtonPanel); + root.setCenter(cardPanel); + root.setStyle("-fx-background-color: rgb(245,245,245);"); + + primaryStage.setScene(new Scene(root)); + primaryStage.show(); + } + + private void switchPanel(String panelName) { + for (javafx.scene.Node node : cardPanel.getChildren()) { + node.setVisible(false); + } + + int index = switch (panelName) { + case "1 查重复文件" -> 0; + case "2 查遗漏、存储路径和命名规范" -> 1; + case "3 查质量" -> 2; + case "4 元数据" -> 3; + case "5 查数据挂接" -> 4; + case "6 查工作文件" -> 5; + case "7 查存储载体" -> 6; + default -> 0; + }; + + cardPanel.getChildren().get(index).setVisible(true); + } + + public void showStatusMessage(String message) { + for (javafx.scene.Node node : cardPanel.getChildren()) { + if (node instanceof BaseFunctionPanel && node.isVisible()) { + ((BaseFunctionPanel) node).showStatusMessage(message); + break; + } + } + } + + public void showErrorMessage(String message) { + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setTitle("错误"); + alert.setHeaderText(null); + alert.setContentText(message); + alert.showAndWait(); + showStatusMessage("【错误】" + message); + } +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/docchecktoolrefactored/deprecated/DuplicateCheckPanel.java b/src/main/java/top/r3944realms/docchecktoolrefactored/deprecated/DuplicateCheckPanel.java new file mode 100644 index 0000000..b9541b6 --- /dev/null +++ b/src/main/java/top/r3944realms/docchecktoolrefactored/deprecated/DuplicateCheckPanel.java @@ -0,0 +1,166 @@ +package top.r3944realms.docchecktoolrefactored.deprecated; + +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.*; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.scene.text.Font; +import javafx.scene.text.FontWeight; +import javafx.stage.DirectoryChooser; +import javafx.stage.Stage; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.*; + +public class DuplicateCheckPanel extends BaseFunctionPanel { + private TextField folderPathTextField; + private TextArea feedbackTextArea; + private Set duplicateFolders = new HashSet<>(); + + public DuplicateCheckPanel(DocCheckToolMainFrame mainFrame) { + super(mainFrame); + initComponents(); + } + + protected void initComponents() { + // Main layout with padding + this.setPadding(new Insets(20)); + + // Folder selection components + HBox folderSelectBox = new HBox(10); + folderSelectBox.setAlignment(Pos.CENTER_LEFT); + + Label loadFolderLabel = new Label("载入文件夹"); + loadFolderLabel.setFont(Font.font("Microsoft YaHei", 14)); + loadFolderLabel.setTextFill(Color.DARKGRAY); + + folderPathTextField = new TextField(); + folderPathTextField.setEditable(false); + folderPathTextField.setFont(Font.font("Microsoft YaHei", 14)); + folderPathTextField.setStyle("-fx-background-color: white; -fx-border-color: #CCCCCC; -fx-border-width: 1; -fx-padding: 5 10 5 10;"); + HBox.setHgrow(folderPathTextField, Priority.ALWAYS); + + Button selectFolderButton = new Button("选择文件夹"); + selectFolderButton.setFont(Font.font("Microsoft YaHei", 14)); + selectFolderButton.setTextFill(Color.WHITE); + selectFolderButton.setStyle("-fx-background-color: #4285F4; -fx-padding: 5 15 5 15;"); + selectFolderButton.setOnAction(e -> { + DirectoryChooser directoryChooser = new DirectoryChooser(); + directoryChooser.setTitle("选择要检查的文件夹"); + File selectedFolder = directoryChooser.showDialog(new Stage()); + if (selectedFolder != null) { + folderPathTextField.setText(selectedFolder.getAbsolutePath()); + } + }); + + folderSelectBox.getChildren().addAll(loadFolderLabel, folderPathTextField, selectFolderButton); + + Button startCheckButton = new Button("开始检查"); + startCheckButton.setFont(Font.font("Microsoft YaHei", FontWeight.BOLD, 14)); + startCheckButton.setTextFill(Color.WHITE); + startCheckButton.setStyle("-fx-background-color: #4285F4; -fx-padding: 5 20 5 20;"); + startCheckButton.setOnAction(e -> { + String folderPath = folderPathTextField.getText(); + if (!folderPath.isEmpty()) { + feedbackTextArea.setText(""); + File folder = new File(folderPath); + duplicateFolders.clear(); + checkDuplicateFiles(folder); + if (duplicateFolders.isEmpty()) { + feedbackTextArea.setText("反馈结果:\n如无重复文件,则反馈:无重复文件;\n如有重复文件,则在此反馈档号。\n\n当前:无重复文件"); + } else { + StringBuilder result = new StringBuilder("反馈结果:\n如无重复文件,则反馈:无重复文件;\n如有重复文件,则在此反馈档号。\n\n当前:"); + for (String folderName : duplicateFolders) { + result.append("\n").append(folderName); + } + feedbackTextArea.setText(result.toString()); + } + } else { + mainFrame.showErrorMessage("请先选择一个文件夹!"); + } + }); + + // Feedback area + feedbackTextArea = new TextArea(); + feedbackTextArea.setEditable(false); + feedbackTextArea.setFont(Font.font("Microsoft YaHei", 14)); + feedbackTextArea.setText("反馈结果:\n如无重复文件,则反馈:无重复文件;\n如有重复文件,则在此反馈档号。"); + feedbackTextArea.setStyle("-fx-background-color: white; -fx-border-color: #CCCCCC; -fx-border-width: 1; -fx-padding: 10;"); + + ScrollPane scrollPane = new ScrollPane(feedbackTextArea); + scrollPane.setFitToWidth(true); + scrollPane.setFitToHeight(true); + scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS); + + // Main layout + VBox mainBox = new VBox(10); + mainBox.getChildren().addAll(folderSelectBox, startCheckButton, scrollPane); + this.getChildren().add(mainBox); + + // Set result text area reference + resultTextArea = feedbackTextArea; + } + + /** + * Recursively checks for duplicate fileMetadata in the specified folder + */ + private void checkDuplicateFiles(File folder) { + Map> fileHashMap = new HashMap<>(); + if (folder.isDirectory()) { + File[] files = folder.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + checkDuplicateFiles(file); + } else { + try { + String md5Hash = calculateMD5(file); + fileHashMap.computeIfAbsent(md5Hash, k -> new ArrayList<>()).add(file); + } catch (IOException | NoSuchAlgorithmException ex) { + ex.printStackTrace(); + mainFrame.showErrorMessage("计算文件哈希值出错:" + ex.getMessage()); + } + } + } + } + } + + for (List fileList : fileHashMap.values()) { + if (fileList.size() > 1) { + for (File file : fileList) { + File parentFolder = file.getParentFile(); + if (parentFolder != null) { + duplicateFolders.add(parentFolder.getName()); + } + } + } + } + } + + /** + * Calculates the MD5 hash of a file + */ + private String calculateMD5(File file) throws IOException, NoSuchAlgorithmException { + MessageDigest digest = MessageDigest.getInstance("MD5"); + try (FileInputStream fis = new FileInputStream(file)) { + byte[] buffer = new byte[8192]; + int bytesRead; + while ((bytesRead = fis.read(buffer)) != -1) { + digest.update(buffer, 0, bytesRead); + } + } + byte[] hashBytes = digest.digest(); + StringBuilder sb = new StringBuilder(); + for (byte b : hashBytes) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/docchecktoolrefactored/deprecated/OtherFunctionPanel.java b/src/main/java/top/r3944realms/docchecktoolrefactored/deprecated/OtherFunctionPanel.java new file mode 100644 index 0000000..8808a52 --- /dev/null +++ b/src/main/java/top/r3944realms/docchecktoolrefactored/deprecated/OtherFunctionPanel.java @@ -0,0 +1,35 @@ +package top.r3944realms.docchecktoolrefactored.deprecated; + +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.layout.VBox; +import javafx.scene.text.Font; + +public class OtherFunctionPanel extends BaseFunctionPanel { + private String title; + + public OtherFunctionPanel(String title) { + super(null); + this.title = title; + initComponents(); + } + + @Override + protected void initComponents() { + // Set padding + this.setPadding(new Insets(50)); + + //Create tip label + Label tipLabel = new Label("【" + "" +"】功能界面待完善..."+title); + tipLabel.setFont(Font.font("Microsoft YaHei", 28)); + tipLabel.setTextFill(javafx.scene.paint.Color.DARKGRAY); + + // Use VBox for vertical layout and center alignment + VBox container = new VBox(tipLabel); + container.setAlignment(Pos.CENTER); + + // Add to the panel + this.getChildren().add(container); + } +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/docchecktoolrefactored/deprecated/PathCheckPanel.java b/src/main/java/top/r3944realms/docchecktoolrefactored/deprecated/PathCheckPanel.java new file mode 100644 index 0000000..be88294 --- /dev/null +++ b/src/main/java/top/r3944realms/docchecktoolrefactored/deprecated/PathCheckPanel.java @@ -0,0 +1,287 @@ +package top.r3944realms.docchecktoolrefactored.deprecated; + +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.*; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.scene.text.Font; +import javafx.scene.text.FontWeight; +import javafx.stage.DirectoryChooser; +import javafx.stage.Stage; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class PathCheckPanel extends BaseFunctionPanel { + private TextField physicalFolderPathTextField; + private TextField logicalFolderPathTextField; + private TextArea checkResultTextArea; + private Path physicalFolderPath; + private Path logicalFolderPath; + private List physicalPaths = new ArrayList<>(); + private List logicalPaths = new ArrayList<>(); + + public PathCheckPanel(DocCheckToolMainFrame mainFrame) { + super(mainFrame); + initComponents(); + } + + @Override + protected void initComponents() { + // Main layout with spacing + VBox mainLayout = new VBox(15); + mainLayout.setPadding(new Insets(20)); + + // Physical path selection + TitledPane physicalPathPane = createPathPane( + "物理存储路径", + "选择物理文件夹", + "生成物理存储路径文件", + this::selectPhysicalFolder, + this::generatePhysicalPathFile + ); + physicalFolderPathTextField = (TextField) ((HBox) physicalPathPane.getContent()).getChildren().get(0); + + // Logical path selection + TitledPane logicalPathPane = createPathPane( + "逻辑存储路径", + "选择逻辑文件夹", + "生成逻辑存储路径文件", + this::selectLogicalFolder, + this::generateLogicalPathFile + ); + logicalFolderPathTextField = (TextField) ((HBox) logicalPathPane.getContent()).getChildren().get(0); + + // Check buttons + HBox buttonBox = new HBox(15); + buttonBox.setAlignment(Pos.CENTER); + + Button checkFilesButton = new Button("检查文件"); + stylePrimaryButton(checkFilesButton, 16); + checkFilesButton.setOnAction(e -> checkFiles()); + + Button checkDirButton = new Button("检查目录"); + checkDirButton.setFont(Font.font("Microsoft YaHei", FontWeight.BOLD, 16)); + checkDirButton.setTextFill(Color.WHITE); + checkDirButton.setStyle("-fx-background-color: #ef4444; -fx-border-color: #dc2626; -fx-border-width: 2; -fx-padding: 8 30 8 30;"); + checkDirButton.setOnAction(e -> checkDirectory()); + + buttonBox.getChildren().addAll(checkFilesButton, checkDirButton); + + // Result area + checkResultTextArea = new TextArea(); + checkResultTextArea.setEditable(false); + checkResultTextArea.setFont(Font.font("Microsoft YaHei", 14)); + checkResultTextArea.setText("检查结果将显示在这里...\n请先选择文件夹并生成路径文件"); + checkResultTextArea.setStyle("-fx-background-color: white; -fx-border-color: #dee2e6; -fx-border-width: 1; -fx-padding: 10;"); + + ScrollPane scrollPane = new ScrollPane(checkResultTextArea); + scrollPane.setFitToWidth(true); + scrollPane.setFitToHeight(true); + scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS); + + // Assemble main layout + VBox pathPanes = new VBox(15, physicalPathPane, logicalPathPane); + mainLayout.getChildren().addAll(pathPanes, scrollPane, buttonBox); + this.getChildren().add(mainLayout); + } + + private TitledPane createPathPane(String title, String selectBtnText, String generateBtnText, + Runnable selectAction, Runnable generateAction) { + TextField pathField = new TextField(); + pathField.setEditable(false); + pathField.setFont(Font.font("Microsoft YaHei", 14)); + pathField.setStyle("-fx-background-color: white; -fx-border-color: #dee2e6; -fx-border-width: 1; -fx-padding: 5 10 5 10;"); + + Button selectButton = new Button(selectBtnText); + stylePrimaryButton(selectButton, 14); + selectButton.setOnAction(e -> selectAction.run()); + + Button generateButton = new Button(generateBtnText); + stylePrimaryButton(generateButton, 14); + generateButton.setDisable(true); + generateButton.setOnAction(e -> generateAction.run()); + + HBox content = new HBox(10, pathField, selectButton, generateButton); + content.setAlignment(Pos.CENTER_LEFT); + + TitledPane pane = new TitledPane(title, content); + pane.setFont(Font.font("Microsoft YaHei", 14)); + pane.setStyle("-fx-border-color: #dee2e6; -fx-border-width: 1;"); + + return pane; + } + + private void stylePrimaryButton(Button button, double fontSize) { + button.setFont(Font.font("Microsoft YaHei", FontWeight.BOLD, fontSize)); + button.setTextFill(Color.WHITE); + button.setStyle("-fx-background-color: #4285f4; -fx-border-color: #3b71ca; -fx-border-width: 2; -fx-padding: 8 30 8 30;"); + } + + private void selectPhysicalFolder() { + DirectoryChooser chooser = new DirectoryChooser(); + chooser.setTitle("选择物理存储文件夹"); + File selectedFolder = chooser.showDialog(new Stage()); + if (selectedFolder != null) { + physicalFolderPathTextField.setText(selectedFolder.getAbsolutePath()); + physicalFolderPath = selectedFolder.toPath(); + + try { + collectFilePaths(physicalFolderPath, physicalPaths); + showStatusMessage("已收集 " + physicalPaths.size() + " 个物理文件路径"); + enableGenerateButton(physicalFolderPathTextField, true); + } catch (IOException e) { + mainFrame.showErrorMessage("读取物理文件夹失败:" + e.getMessage()); + } + } + } + + private void selectLogicalFolder() { + DirectoryChooser chooser = new DirectoryChooser(); + chooser.setTitle("选择逻辑存储文件夹"); + File selectedFolder = chooser.showDialog(new Stage()); + if (selectedFolder != null) { + logicalFolderPathTextField.setText(selectedFolder.getAbsolutePath()); + logicalFolderPath = selectedFolder.toPath(); + + try { + collectFilePaths(logicalFolderPath, logicalPaths); + showStatusMessage("已收集 " + logicalPaths.size() + " 个逻辑文件路径"); + enableGenerateButton(logicalFolderPathTextField, true); + } catch (IOException e) { + mainFrame.showErrorMessage("读取逻辑文件夹失败:" + e.getMessage()); + } + } + } + + private void enableGenerateButton(TextField pathField, boolean enable) { + // Implementation would find the generate button and enable it + // This depends on your exact UI structure + } + + private void generatePhysicalPathFile() { + if (physicalFolderPath == null || physicalPaths.isEmpty()) { + mainFrame.showErrorMessage("请先选择有效的物理文件夹!"); + return; + } + + Path outputPath = Paths.get("物理存储路径文件.txt"); + writePathsToFile(physicalPaths, outputPath); + showStatusMessage("物理存储路径文件已生成:" + outputPath.toAbsolutePath()); + } + + private void generateLogicalPathFile() { + if (logicalFolderPath == null || logicalPaths.isEmpty()) { + mainFrame.showErrorMessage("请先选择有效的逻辑文件夹!"); + return; + } + + Path outputPath = Paths.get("逻辑存储路径文件.txt"); + writePathsToFile(logicalPaths, outputPath); + showStatusMessage("逻辑存储路径文件已生成:" + outputPath.toAbsolutePath()); + } + + // The following methods remain largely unchanged as they deal with file operations: + private void collectFilePaths(Path folderPath, List paths) throws IOException { + paths.clear(); + try (Stream walk = Files.walk(folderPath)) { + paths.addAll(walk + .filter(Files::isRegularFile) + .map(p -> folderPath.relativize(p).toString()) + .collect(Collectors.toList())); + } + } + + private void writePathsToFile(List paths, Path outputPath) { + try (BufferedWriter writer = new BufferedWriter(new FileWriter(outputPath.toFile()))) { + for (String path : paths) { + writer.write(path); + writer.newLine(); + } + } catch (IOException e) { + mainFrame.showErrorMessage("写入文件失败:" + e.getMessage()); + } + } + + private void checkFiles() { + if (physicalPaths.isEmpty() || logicalPaths.isEmpty()) { + mainFrame.showErrorMessage("请先选择并生成物理和逻辑路径文件!"); + return; + } + + clearResultArea(); + showStatusMessage("正在进行文件检查..."); + + Set missingInPhysical = new HashSet<>(); + for (String logicalPath : logicalPaths) { + if (!physicalPaths.contains(logicalPath)) { + missingInPhysical.add(logicalPath); + } + } + + displayCheckResult("文件检查", logicalPaths.size(), missingInPhysical, null); + } + + private void checkDirectory() { + if (physicalPaths.isEmpty() || logicalPaths.isEmpty()) { + mainFrame.showErrorMessage("请先选择并生成物理和逻辑路径文件!"); + return; + } + + clearResultArea(); + showStatusMessage("正在进行目录检查..."); + + Set missingInLogical = new HashSet<>(); + for (String physicalPath : physicalPaths) { + if (!logicalPaths.contains(physicalPath)) { + missingInLogical.add(physicalPath); + } + } + + displayCheckResult("目录检查", physicalPaths.size(), null, missingInLogical); + } + + private void displayCheckResult(String checkType, int totalCount, + Set missingInPhysical, Set missingInLogical) { + StringBuilder result = new StringBuilder(); + result.append("=== ").append(checkType).append(" ===\n"); + result.append("共检查 ").append(totalCount).append(" 个").append(checkType.equals("文件检查") ? "文件" : "目录项").append("\n\n"); + + if (checkType.equals("文件检查")) { + if (missingInPhysical.isEmpty()) { + result.append("✓ 未发现问题:所有逻辑文件在物理存储中均存在\n"); + } else { + result.append("! 发现 ").append(missingInPhysical.size()).append(" 个问题:\n"); + missingInPhysical.forEach(path -> result.append(" - 物理存储中缺失文件:").append(path).append("\n")); + } + } else { + if (missingInLogical.isEmpty()) { + result.append("✓ 未发现问题:所有物理文件在逻辑目录中均存在\n"); + } else { + result.append("! 发现 ").append(missingInLogical.size()).append(" 个问题:\n"); + missingInLogical.forEach(path -> result.append(" - 逻辑目录中缺失文件:").append(path).append("\n")); + } + } + + checkResultTextArea.appendText(result.toString()); + checkResultTextArea.appendText("\n\n"); + showStatusMessage(checkType + "完成"); + } + + private void clearResultArea() { + checkResultTextArea.clear(); + } +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/docchecktoolrefactored/io/reader/FileReader.java b/src/main/java/top/r3944realms/docchecktoolrefactored/io/reader/FileReader.java new file mode 100644 index 0000000..15d05ab --- /dev/null +++ b/src/main/java/top/r3944realms/docchecktoolrefactored/io/reader/FileReader.java @@ -0,0 +1,43 @@ +package top.r3944realms.docchecktoolrefactored.io.reader; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.file.Path; + +/** + * The interface File reader. + */ +public interface FileReader { + /** + * 读取文件内容 + * + * @param file 要读取的文件 + * @return 文件内容字节数组 byte [ ] + * @throws IOException the io exception + */ + byte[] readFully(Path file) throws IOException; + + /** + * 流式读取文件内容 + * + * @param file 要读取的文件 + * @param consumer 内容消费者 + * @throws IOException the io exception + */ + void readStreaming(Path file, ByteBufferConsumer consumer) throws IOException; + + /** + * The interface Byte buffer consumer. + */ + @FunctionalInterface + interface ByteBufferConsumer { + /** + * Consume. + * + * @param buffer the buffer + * @param isLast the is last + * @throws IOException the io exception + */ + void consume(ByteBuffer buffer, boolean isLast) throws IOException; + } +} diff --git a/src/main/java/top/r3944realms/docchecktoolrefactored/io/reader/MemoryMappedFileReader.java b/src/main/java/top/r3944realms/docchecktoolrefactored/io/reader/MemoryMappedFileReader.java new file mode 100644 index 0000000..abc459c --- /dev/null +++ b/src/main/java/top/r3944realms/docchecktoolrefactored/io/reader/MemoryMappedFileReader.java @@ -0,0 +1,50 @@ +package top.r3944realms.docchecktoolrefactored.io.reader; + +import java.io.IOException; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; + +/** + * The type Memory mapped file reader. + */ +public class MemoryMappedFileReader implements FileReader { + private static final long MAX_MMAP_SIZE = 1 << 30; // 1GB + + @Override + public byte[] readFully(Path file) throws IOException { + long size = Files.size(file); + if (size > MAX_MMAP_SIZE) { + throw new IOException("File too large for memory mapping: " + file); + } + + try (FileChannel channel = FileChannel.open(file, StandardOpenOption.READ)) { + MappedByteBuffer buffer = channel.map( + FileChannel.MapMode.READ_ONLY, 0, size); + byte[] bytes = new byte[(int) size]; + buffer.get(bytes); + return bytes; + } + } + + @Override + public void readStreaming(Path file, ByteBufferConsumer consumer) throws IOException { + try (FileChannel channel = FileChannel.open(file, StandardOpenOption.READ)) { + long size = channel.size(); + long position = 0; + long chunkSize = Math.min(size, 8 * 1024 * 1024); // 8MB chunks + + while (position < size) { + long remaining = size - position; + long currentChunk = Math.min(remaining, chunkSize); + + MappedByteBuffer buffer = channel.map( + FileChannel.MapMode.READ_ONLY, position, currentChunk); + consumer.consume(buffer, position + currentChunk >= size); + position += currentChunk; + } + } + } +} diff --git a/src/main/java/top/r3944realms/docchecktoolrefactored/io/scanner/FileScanner.java b/src/main/java/top/r3944realms/docchecktoolrefactored/io/scanner/FileScanner.java new file mode 100644 index 0000000..39f19df --- /dev/null +++ b/src/main/java/top/r3944realms/docchecktoolrefactored/io/scanner/FileScanner.java @@ -0,0 +1,41 @@ +package top.r3944realms.docchecktoolrefactored.io.scanner; + +import java.nio.file.Path; + +/** + * The interface File scanner. + */ +public interface FileScanner { + /** + * 扫描指定路径下的文件 + * + * @param rootPath 根路径 + * @param listener 文件发现监听器 + */ + void scan(Path rootPath, FileScanListener listener); + + /** + * 文件扫描监听器 + */ + interface FileScanListener { + /** + * On file found. + * + * @param file the file + */ + void onFileFound(Path file); + + /** + * On scan complete. + */ + void onScanComplete(); + + /** + * On error. + * + * @param file the file + * @param e the e + */ + void onError(Path file, Exception e); + } +} diff --git a/src/main/java/top/r3944realms/docchecktoolrefactored/io/scanner/ParallelFileScanner.java b/src/main/java/top/r3944realms/docchecktoolrefactored/io/scanner/ParallelFileScanner.java new file mode 100644 index 0000000..b5f9d7b --- /dev/null +++ b/src/main/java/top/r3944realms/docchecktoolrefactored/io/scanner/ParallelFileScanner.java @@ -0,0 +1,68 @@ +package top.r3944realms.docchecktoolrefactored.io.scanner; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; + +/** + * The type Parallel file scanner. + */ +public class ParallelFileScanner implements FileScanner,AutoCloseable { + private final ForkJoinPool forkJoinPool; + + /** + * 使用默认并行度(CPU核心数) + */ + public ParallelFileScanner() { + this(Runtime.getRuntime().availableProcessors()); + } + + /** + * Instantiates a new Parallel file scanner. + * + * @param parallelism 并行度(线程数) + */ + public ParallelFileScanner(int parallelism) { + this.forkJoinPool = new ForkJoinPool(parallelism); + } + + @Override + public void scan(Path rootPath, FileScanListener listener) { + forkJoinPool.submit(() -> { + try ( + Stream pathStream = Files.walk(rootPath) + .parallel() // 使用ForkJoinPool的并行流 + .filter(Files::isRegularFile) + ){ + pathStream.forEach(file -> { + try { + listener.onFileFound(file); + } catch (Exception e) { + listener.onError(file, e); + } + }); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + ).join(); + + listener.onScanComplete(); + } + + @Override + public void close() { + forkJoinPool.shutdown(); + try { + if (!forkJoinPool.awaitTermination(1, TimeUnit.SECONDS)) { + forkJoinPool.shutdownNow(); + } + } catch (InterruptedException e) { + forkJoinPool.shutdownNow(); + Thread.currentThread().interrupt(); + } + } +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/docchecktoolrefactored/model/DuplicateGroup.java b/src/main/java/top/r3944realms/docchecktoolrefactored/model/DuplicateGroup.java new file mode 100644 index 0000000..5862392 --- /dev/null +++ b/src/main/java/top/r3944realms/docchecktoolrefactored/model/DuplicateGroup.java @@ -0,0 +1,48 @@ +package top.r3944realms.docchecktoolrefactored.model; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * The type Duplicate group. + * + * @param hash 文件内容哈希值 + * @param size 文件大小(字节) + * @param fileMetas 关联的元数据列表 + */ +public record DuplicateGroup(String hash, long size, List fileMetas) { + + /** + * Instantiates a new Duplicate group. + * + * @param hash the hash + * @param size the size + * @param fileMetas the file metas + */ +// 紧凑构造方法验证数据 + public DuplicateGroup { + Objects.requireNonNull(hash); + Objects.requireNonNull(fileMetas); + fileMetas = Collections.unmodifiableList(fileMetas); + } + + /** + * Is real duplicate boolean. + * + * @return the boolean + */ + public boolean isRealDuplicate() { + return fileMetas.size() > 1; // 是否真实重复(至少两个文件) + } + + /** + * Total wasted space long. + * + * @return the long + */ + public long totalWastedSpace() { + return size * (fileMetas.size() - 1); // 浪费空间大小 + } + +} diff --git a/src/main/java/top/r3944realms/docchecktoolrefactored/model/DuplicateReport.java b/src/main/java/top/r3944realms/docchecktoolrefactored/model/DuplicateReport.java new file mode 100644 index 0000000..796dc2b --- /dev/null +++ b/src/main/java/top/r3944realms/docchecktoolrefactored/model/DuplicateReport.java @@ -0,0 +1,17 @@ +package top.r3944realms.docchecktoolrefactored.model; + +import java.util.List; + +/** + * The type Duplicate report. + */ +public class DuplicateReport { + private List duplicates; + + /** + * Export to json. + */ + public void exportToJson() { + //TODO: + } +} diff --git a/src/main/java/top/r3944realms/docchecktoolrefactored/model/FileMetadata.java b/src/main/java/top/r3944realms/docchecktoolrefactored/model/FileMetadata.java new file mode 100644 index 0000000..d47c6d0 --- /dev/null +++ b/src/main/java/top/r3944realms/docchecktoolrefactored/model/FileMetadata.java @@ -0,0 +1,19 @@ +package top.r3944realms.docchecktoolrefactored.model; + +import lombok.Data; + +import java.nio.file.Path; +import java.time.Instant; + +/** + * The type File metadata. + */ +@Data +public class FileMetadata { + private Path path; + private String filename; // 文件名(不含路径) + private long size; + private String hash; + private Instant lastModified; + private String parentDir; // 父目录名 +} diff --git a/src/main/java/top/r3944realms/docchecktoolrefactored/ui/LoginStageController.java b/src/main/java/top/r3944realms/docchecktoolrefactored/ui/LoginStageController.java new file mode 100644 index 0000000..b577202 --- /dev/null +++ b/src/main/java/top/r3944realms/docchecktoolrefactored/ui/LoginStageController.java @@ -0,0 +1,62 @@ +package top.r3944realms.docchecktoolrefactored.ui; + +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.Button; +import javafx.scene.control.PasswordField; +import javafx.scene.control.TextField; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; +import javafx.scene.input.KeyCombination; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.BorderPane; +import lombok.extern.slf4j.Slf4j; +import top.r3944realms.docchecktoolrefactored.ui.utils.DialogUtil; + +import java.net.URL; +import java.util.ResourceBundle; + +/** + * 登录场景控制器 + */ +@Slf4j +public class LoginStageController implements Initializable { + /** + * The Login button. + */ + public Button loginButton; + /** + * The Main pane. + */ + public BorderPane mainPane; + @FXML private TextField usernameField; + @FXML private PasswordField passwordField; + + @FXML + private void handleLogin() { + String username = usernameField.getText(); + String password = passwordField.getText(); + + if ("admin".equals(username) && "admin".equals(password)) { + log.info("{} Login successful", username); + SceneManager.switchMainView(); + } else { + log.info("Invalid username or password"); + DialogUtil.showErrorDialog("错误", null, "用户名或密码错误!"); + } + } + + @Override + public void initialize(URL url, ResourceBundle resourceBundle) { + //添加 Ctrl + Enter 快捷键 + KeyCodeCombination enter_key_down = new KeyCodeCombination(KeyCode.ENTER, KeyCombination.CONTROL_DOWN); + // 监听场景属性变化 + SceneManager.tryGetSceneHandler(passwordField, window -> + window.addEventFilter(KeyEvent.KEY_PRESSED, event -> { + if (enter_key_down.match(event)) { + handleLogin(); + } + }) + ); + } +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/docchecktoolrefactored/ui/MainStageController.java b/src/main/java/top/r3944realms/docchecktoolrefactored/ui/MainStageController.java new file mode 100644 index 0000000..af9c3c7 --- /dev/null +++ b/src/main/java/top/r3944realms/docchecktoolrefactored/ui/MainStageController.java @@ -0,0 +1,26 @@ +package top.r3944realms.docchecktoolrefactored.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Menu; +import javafx.scene.control.Tab; + +/** + * The type Main stage controller. + */ +public class MainStageController { + + @FXML private Tab step1T; + @FXML private Tab step2T; + @FXML private Tab step3T; + @FXML private Tab step4T; + @FXML private Tab step5T; + @FXML private Tab step6T; + @FXML private Tab step7T; + + @FXML private Menu helpM; + + + + + +} diff --git a/src/main/java/top/r3944realms/docchecktoolrefactored/ui/SceneManager.java b/src/main/java/top/r3944realms/docchecktoolrefactored/ui/SceneManager.java new file mode 100644 index 0000000..feca4a1 --- /dev/null +++ b/src/main/java/top/r3944realms/docchecktoolrefactored/ui/SceneManager.java @@ -0,0 +1,134 @@ +package top.r3944realms.docchecktoolrefactored.ui; + +import javafx.animation.FadeTransition; +import javafx.fxml.FXMLLoader; +import javafx.scene.Node; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.stage.Stage; +import javafx.util.Duration; +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import top.r3944realms.docchecktoolrefactored.Main; +import top.r3944realms.docchecktoolrefactored.ui.utils.DialogUtil; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Consumer; + +/** + * The type Scene manager. + */ +@Slf4j +public class SceneManager { + @Getter + @Setter + private static Stage primaryStage; + + @Getter + private static final List openStages = new ArrayList<>(); + + /** + * Init. + * + * @param primaryStage the primary stage + */ + public static void init(Stage primaryStage) { + SceneManager.primaryStage = primaryStage; + } + + /** + * Switch login view. + */ + public static void switchLoginView() { + SceneManager.loadView("/fxml/login-view.fxml","淮阴区数字化档案检查验收系统 - 登录", 400, 300); + } + + /** + * Switch main view. + */ + public static void switchMainView() { + SceneManager.loadView("/fxml/main-view.fxml", "淮阴区数字化档案检查验收系统 - 主界面", 1000, 1000); + } + + /** + * Load view. + * + * @param fxmlPath the fxml path + * @param title the title + * @param width the width + * @param height the height + */ +// 基础视图加载方法 + public static void loadView(String fxmlPath, String title, int width, int height) { + loadView(fxmlPath, title, width, height, null); + } + + /** + * Load view. + * + * @param fxmlPath the fxml path + * @param title the title + * @param width the width + * @param height the height + * @param initializer the initializer + */ + public static void loadView(String fxmlPath, String title, int width, int height, Consumer initializer) { + try { + Parent root = FXMLLoader.load(Objects.requireNonNull(Main.class.getResource(fxmlPath))); + Scene newScene = new Scene(root, width, height); + + // 淡入效果 + applyFadeTransition(root); + + primaryStage.setScene(newScene); + primaryStage.centerOnScreen(); + primaryStage.setTitle(title); + + if (initializer != null) { + initializer.accept(primaryStage); + } + + // 设置窗口关闭确认 + primaryStage.setOnCloseRequest(event -> { + if (!DialogUtil.showExitConfirmation(primaryStage)) { + event.consume(); + } + }); + + } catch (IOException e) { + log.error("Failed to load view: {}", fxmlPath, e); + DialogUtil.showErrorDialog("错误", "加载视图失败", "无法加载视图: " + e.getMessage()); + } + } + private static void applyFadeTransition(Parent root) { + root.setOpacity(0); + FadeTransition fadeIn = new FadeTransition(Duration.millis(300), root); + fadeIn.setToValue(1.0); + fadeIn.play(); + } + + /** + * Primary scene handler. + * + * @param handler the handler + */ + public static void primarySceneHandler(Consumer handler) { + handler.accept(getPrimaryStage().getScene()); + } + + /** + * Try get scene handler. + * + * @param node the node + * @param handler the handler + */ + public static void tryGetSceneHandler(@NonNull Node node, Consumer handler) { + node.sceneProperty().addListener((observable, oldScene, newScene) -> Optional.ofNullable(newScene).ifPresent(handler)); + } +} diff --git a/src/main/java/top/r3944realms/docchecktoolrefactored/ui/module/DuplicateDocumentPaneController.java b/src/main/java/top/r3944realms/docchecktoolrefactored/ui/module/DuplicateDocumentPaneController.java new file mode 100644 index 0000000..c9dc6a5 --- /dev/null +++ b/src/main/java/top/r3944realms/docchecktoolrefactored/ui/module/DuplicateDocumentPaneController.java @@ -0,0 +1,44 @@ +package top.r3944realms.docchecktoolrefactored.ui.module; + +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.TextArea; +import javafx.scene.control.TextField; +import javafx.stage.DirectoryChooser; +import javafx.stage.Stage; + +import java.io.File; + +/** + * The type Duplicate document pane controller. + */ +public class DuplicateDocumentPaneController { + @FXML private TextArea result1B; + @FXML private TextField loadFolder1TF; + @FXML private Button selectLoadFolder1B; + @FXML private Button start1B; + + /** + * On select folder. + * + * @param actionEvent the action event + */ + @FXML void onSelectFolder(ActionEvent actionEvent) { + DirectoryChooser directoryChooser = new DirectoryChooser(); + directoryChooser.setTitle("选择要检查的文件夹"); + File selectedFolder = directoryChooser.showDialog(new Stage()); + if (selectedFolder != null) { + loadFolder1TF.setText(selectedFolder.getAbsolutePath()); + } + } + + /** + * On start. + * + * @param actionEvent the action event + */ + @FXML void onStart(ActionEvent actionEvent) { + // 触发异步逻辑 -> 调用 + } +} diff --git a/src/main/java/top/r3944realms/docchecktoolrefactored/ui/module/PathCheckPaneController.java b/src/main/java/top/r3944realms/docchecktoolrefactored/ui/module/PathCheckPaneController.java new file mode 100644 index 0000000..1b59485 --- /dev/null +++ b/src/main/java/top/r3944realms/docchecktoolrefactored/ui/module/PathCheckPaneController.java @@ -0,0 +1,99 @@ +package top.r3944realms.docchecktoolrefactored.ui.module; + +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.Button; +import javafx.scene.control.ChoiceBox; +import javafx.scene.control.TextArea; +import javafx.scene.control.TextField; + +import java.net.URL; +import java.util.ResourceBundle; + +/** + * The type Path check pane controller. + */ +public class PathCheckPaneController implements Initializable { + @FXML private ChoiceBox loadFolderType2CB; + @FXML private TextArea result2TA; + @FXML private Button start2B; + @FXML private TextField loadCatalog2TF; + @FXML private TextField loadJPGFolder2TF; + @FXML private Button selectLoadCatalog2B; + @FXML private Button selectJPGFolder2B; + @FXML private Button generateLogicalAddress2B; + @FXML private Button generatePhysicalAddress2B; + + /** + * On select lc. + * + * @param actionEvent the action event + */ + @FXML void onSelectLC(ActionEvent actionEvent) { + + } + + /** + * On select jpgf. + * + * @param actionEvent the action event + */ + @FXML void onSelectJPGF(ActionEvent actionEvent) { + + } + + /** + * On generate la. + * + * @param actionEvent the action event + */ + @FXML void onGenerateLA(ActionEvent actionEvent) { + + } + + /** + * On generate pa. + * + * @param actionEvent the action event + */ + @FXML void onGeneratePA(ActionEvent actionEvent) { + + } + + /** + * On start. + * + * @param actionEvent the action event + */ + @FXML void onStart(ActionEvent actionEvent) { + + } + + @Override + public void initialize(URL url, ResourceBundle resourceBundle) { + loadFolderType2CB.getItems().addAll(Mode.values()); + } + + /** + * The enum Mode. + */ + enum Mode { + /** + * Jpg mode. + */ + JPG("jpg"), + /** + * Pdf mode. + */ + PDF("pdf"); + /** + * The Id. + */ + final String id; + + Mode(String id) { + this.id = id; + } + } +} diff --git a/src/main/java/top/r3944realms/docchecktoolrefactored/ui/module/ProjectInfoPaneController.java b/src/main/java/top/r3944realms/docchecktoolrefactored/ui/module/ProjectInfoPaneController.java new file mode 100644 index 0000000..fe8b3fa --- /dev/null +++ b/src/main/java/top/r3944realms/docchecktoolrefactored/ui/module/ProjectInfoPaneController.java @@ -0,0 +1,16 @@ +package top.r3944realms.docchecktoolrefactored.ui.module; + +import javafx.fxml.FXML; +import javafx.scene.control.TextField; + +/** + * The type Project info pane controller. + */ +public class ProjectInfoPaneController { + @FXML private TextField projectNameTF; + @FXML private TextField AcceptanceTimeTF; + @FXML private TextField totalCatalogNumberTF; + @FXML private TextField fileCategoriesTF; + @FXML private TextField fileYearTF; + +} diff --git a/src/main/java/top/r3944realms/docchecktoolrefactored/ui/utils/DialogUtil.java b/src/main/java/top/r3944realms/docchecktoolrefactored/ui/utils/DialogUtil.java new file mode 100644 index 0000000..86bb67a --- /dev/null +++ b/src/main/java/top/r3944realms/docchecktoolrefactored/ui/utils/DialogUtil.java @@ -0,0 +1,98 @@ +package top.r3944realms.docchecktoolrefactored.ui.utils; + +import javafx.application.Platform; +import javafx.scene.control.Alert; +import javafx.scene.control.ButtonType; +import javafx.stage.Window; + +import java.util.Optional; + +/** + * 对话框工具类 + */ +public class DialogUtil { + /** + * Show exit confirmation boolean. + * + * @param owner the owner + * @return the boolean + */ + public static boolean showExitConfirmation(Window owner) { + Alert alert = new Alert(Alert.AlertType.CONFIRMATION); + alert.initOwner(owner); + alert.setTitle("确认退出"); + alert.setHeaderText("您确定要退出程序吗?"); + alert.setContentText("请确认您的操作"); + + Optional result = alert.showAndWait(); + return result.isPresent() && result.get() == ButtonType.OK; + } + + /** + * Show confirmation dialog boolean. + * + * @param title the title + * @param header the header + * @param content the content + * @return the boolean + */ + public static boolean showConfirmationDialog(String title, String header, String content) { + Alert alert = new Alert(Alert.AlertType.CONFIRMATION); + alert.setTitle(title); + alert.setHeaderText(header); + alert.setContentText(content); + Optional result = alert.showAndWait(); + return result.isPresent() && result.get() == ButtonType.OK; + } + + /** + * Show information dialog. + * + * @param title the title + * @param header the header + * @param content the content + */ + public static void showInformationDialog(String title, String header, String content) { + Platform.runLater(() -> { + Alert alert = new Alert(Alert.AlertType.INFORMATION); + alert.setTitle(title); + alert.setHeaderText(header); + alert.setContentText(content); + alert.showAndWait(); + }); + } + + /** + * Show warning dialog. + * + * @param title the title + * @param header the header + * @param content the content + */ + public static void showWarningDialog(String title, String header, String content) { + Platform.runLater(() -> { + Alert alert = new Alert(Alert.AlertType.WARNING); + alert.setTitle(title); + alert.setHeaderText(header); + alert.setContentText(content); + alert.showAndWait(); + }); + } + + /** + * Show error dialog. + * + * @param title the title + * @param header the header + * @param content the content + */ + public static void showErrorDialog(String title, String header, String content) { + Platform.runLater(() -> { + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setTitle(title); + alert.setHeaderText(header); + alert.setContentText(content); + alert.showAndWait(); + }); + } +} diff --git a/src/main/resources/css/custom-tab.css b/src/main/resources/css/custom-tab.css new file mode 100644 index 0000000..f5cafa7 --- /dev/null +++ b/src/main/resources/css/custom-tab.css @@ -0,0 +1,155 @@ +/* 基础Tab样式 */ +.tab { + -fx-background-color: transparent; /* 改为透明 */ + -fx-background-insets: 0; + -fx-padding: 0; + -fx-cursor: hand; +} + +/* 默认Tab形状容器 */ +.tab .tab-container { + -fx-background-color: #13b72b; + -fx-shape: "M0,5 L65,5 L75,15 L65,25 L0,25 L10,15 Z"; + -fx-background-insets: 0; + -fx-border-width: 0; /* 确保无边框 */ + -fx-padding: 8 25 8 20; +} + +/* 起始Tab特殊形状 */ +#startTab .tab-container { + -fx-shape: "M0,5 L75,5 L85,15 L75,25 L0,25 Z"; + -fx-padding: 8 20 8 25; +} + +/* 结束Tab特殊形状 */ +#endTab .tab-container { + -fx-shape: "M0,5 L65,5 L65,15 L65,25 L0,25 L10,15 Z"; + -fx-padding: 8 20 8 25; +} + +/* 选中状态 - 完全移除蓝色边框 */ +.tab:selected, +.tab:selected:focused { + -fx-background-color: transparent; /* 关键设置 */ + -fx-background-insets: 0; + -fx-border-width: 0; + -fx-border-color: transparent; +} + +.tab:selected .tab-container { + -fx-background-color: #9b0e0e; /* 只改变背景色 */ + -fx-effect: null; /* 移除所有效果 */ +} + +/* 文字标签样式 */ +.tab .tab-label { + -fx-text-fill: #000000; + -fx-font-size: 16px; + -fx-alignment: CENTER; + -fx-padding: 0 10 0 10; + -fx-max-width: 180px; + -fx-translate-x: 6px; + -fx-text-overrun: ellipsis; +} + +.tab:selected .tab-label { + -fx-text-fill: #ffffff; + -fx-font-weight: bold; +} + +/* TabPane整体设置 */ +.tab-pane { + -fx-tab-min-height: 30px; + -fx-tab-min-width: 80px; + -fx-background-color: transparent; + -fx-focus-color: transparent; /* 移除焦点边框 */ + -fx-faint-focus-color: transparent; +} + +/* Tab重叠调整 */ +.tab-pane > .tab-header-area > .headers-region > .tab { + -fx-background-insets: 0 -15 0 0; + -fx-min-width: 200px; + -fx-pref-width: 300px; + -fx-max-height: 300px; +} + + +/* 下拉菜单面板 - 增强视觉效果 */ +.context-menu { + -fx-background-color: #ffffff; + -fx-border-color: #b0b0b0; + -fx-border-width: 1.5px; + -fx-border-radius: 4px; + -fx-background-radius: 4px; + -fx-padding: 5px 0; + -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.2), 8, 0.2, 0, 2); +} + +/* 菜单项基础样式 */ +.context-menu .menu-item { + -fx-background-color: transparent; + -fx-padding: 8px 20px; + -fx-cursor: hand; + -fx-background-radius: 2px; + -fx-border-width: 0; +} + +/* 菜单项悬停效果 */ +.context-menu .menu-item:hover { + -fx-background-color: #f0f0f0; + -fx-text-fill: #333333; +} + +/* 当前选中菜单项 */ +.context-menu .menu-item:selected { + -fx-background-color: #9b0e0e; /* 与选中Tab相同颜色 */ + -fx-text-fill: white; +} + +/* 菜单项文本样式 */ +.context-menu .menu-item .label { + -fx-text-fill: #333333; + -fx-font-size: 14px; + -fx-font-weight: normal; +} + +/* 选中菜单项文本样式 */ +.context-menu .menu-item:selected .label { + -fx-text-fill: white; + -fx-font-weight: bold; +} + +/* 分隔线样式 */ +.context-menu .separator:horizontal .line { + -fx-border-color: #e0e0e0; + -fx-border-width: 1px; + -fx-padding: 3px 0; +} + +/* 下拉箭头按钮增强样式 */ +.tab-pane > .tab-header-area > .control-buttons-tab > .tab-down-button { + -fx-background-color: #f5f5f5; + -fx-background-radius: 0 3px 3px 0; + -fx-padding: 5px 8px; + -fx-border-color: #c0c0c0; + -fx-border-width: 1px; + -fx-border-radius: 0 3px 3px 0; +} + +/* 下拉箭头图标样式 */ +.tab-pane > .tab-header-area > .control-buttons-tab > .tab-down-button .arrow { + -fx-background-color: #666666; + -fx-shape: "M 0 0 L 5 5 L 10 0 Z"; + -fx-padding: 4px; +} + +/* 悬停时箭头按钮效果 */ +.tab-pane > .tab-header-area > .control-buttons-tab > .tab-down-button:hover { + -fx-background-color: #e0e0e0; + -fx-border-color: #a0a0a0; +} + +.tab-pane > .tab-header-area > .control-buttons-tab > .tab-down-button:hover .arrow { + -fx-background-color: #333333; +} \ No newline at end of file diff --git a/src/main/resources/fxml/login-view.fxml b/src/main/resources/fxml/login-view.fxml new file mode 100644 index 0000000..bb44b54 --- /dev/null +++ b/src/main/resources/fxml/login-view.fxml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
diff --git a/src/main/resources/fxml/main-view.fxml b/src/main/resources/fxml/main-view.fxml new file mode 100644 index 0000000..bc06581 --- /dev/null +++ b/src/main/resources/fxml/main-view.fxml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/fxml/module/project-info-pane.fxml b/src/main/resources/fxml/module/project-info-pane.fxml new file mode 100644 index 0000000..6cbb4e0 --- /dev/null +++ b/src/main/resources/fxml/module/project-info-pane.fxml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/fxml/module/step-1-pane.fxml b/src/main/resources/fxml/module/step-1-pane.fxml new file mode 100644 index 0000000..4b8f03f --- /dev/null +++ b/src/main/resources/fxml/module/step-1-pane.fxml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/fxml/module/step-2-pane.fxml b/src/main/resources/fxml/module/step-2-pane.fxml new file mode 100644 index 0000000..c7742c8 --- /dev/null +++ b/src/main/resources/fxml/module/step-2-pane.fxml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/fxml/module/step-3-pane.fxml b/src/main/resources/fxml/module/step-3-pane.fxml new file mode 100644 index 0000000..230262d --- /dev/null +++ b/src/main/resources/fxml/module/step-3-pane.fxml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/resources/fxml/module/step-4-pane.fxml b/src/main/resources/fxml/module/step-4-pane.fxml new file mode 100644 index 0000000..230262d --- /dev/null +++ b/src/main/resources/fxml/module/step-4-pane.fxml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/resources/fxml/module/step-5-pane.fxml b/src/main/resources/fxml/module/step-5-pane.fxml new file mode 100644 index 0000000..230262d --- /dev/null +++ b/src/main/resources/fxml/module/step-5-pane.fxml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/resources/fxml/module/step-6-pane.fxml b/src/main/resources/fxml/module/step-6-pane.fxml new file mode 100644 index 0000000..230262d --- /dev/null +++ b/src/main/resources/fxml/module/step-6-pane.fxml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/resources/fxml/module/step-7-pane.fxml b/src/main/resources/fxml/module/step-7-pane.fxml new file mode 100644 index 0000000..230262d --- /dev/null +++ b/src/main/resources/fxml/module/step-7-pane.fxml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..1af8ebb --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,53 @@ + + + + + + ${APP_NAME} + + + ${ENCODER_PATTERN} + + + + + ${LOG_HOME}/output.%d{yyyy-MM-dd}.log + 7 + + + ${ENCODER_PATTERN} + + + + + ${LOG_HOME}/error.%d{yyyy-MM-dd}.log + 7 + + + ${ENCODER_PATTERN} + + + WARN + + + + + ${LOG_HOME}/sync.%d{yyyy-MM-dd}.log + 7 + + + ${ENCODER_PATTERN} + + + + + + + + + + + \ No newline at end of file