feat: 初始化提交
This commit is contained in:
245
.gitignore
vendored
Normal file
245
.gitignore
vendored
Normal file
@@ -0,0 +1,245 @@
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
.idea/**/aws.xml
|
||||
.idea/**/contentModel.xml
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
cmake-build-*/
|
||||
.idea/**/mongoSettings.xml
|
||||
*.iws
|
||||
out/
|
||||
.idea_modules/
|
||||
atlassian-ide-plugin.xml
|
||||
.idea/replstate.xml
|
||||
.idea/sonarlint/
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
.idea/httpRequests
|
||||
.idea/caches/build_file_checksums.ser
|
||||
target/
|
||||
pom.xml.tag
|
||||
pom.xml.releaseBackup
|
||||
pom.xml.versionsBackup
|
||||
pom.xml.next
|
||||
release.properties
|
||||
dependency-reduced-pom.xml
|
||||
buildNumber.properties
|
||||
.mvn/timing.properties
|
||||
.mvn/wrapper/maven-wrapper.jar
|
||||
.project
|
||||
.classpath
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
.history/
|
||||
*.vsix
|
||||
.metadata
|
||||
bin/
|
||||
tmp/
|
||||
*.tmp
|
||||
*.bak
|
||||
*.swp
|
||||
*~.nib
|
||||
local.properties
|
||||
.settings/
|
||||
.loadpath
|
||||
.recommenders
|
||||
.externalToolBuilders/
|
||||
*.launch
|
||||
*.pydevproject
|
||||
.cproject
|
||||
.autotools
|
||||
.factorypath
|
||||
.buildpath
|
||||
.target
|
||||
.tern-project
|
||||
.texlipse
|
||||
.springBeans
|
||||
.recommenders/
|
||||
.apt_generated/
|
||||
.apt_generated_test/
|
||||
.cache-main
|
||||
.scala_dependencies
|
||||
.worksheet
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
lib-cov
|
||||
coverage
|
||||
*.lcov
|
||||
.nyc_output
|
||||
.grunt
|
||||
bower_components
|
||||
.lock-wscript
|
||||
build/Release
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
web_modules/
|
||||
*.tsbuildinfo
|
||||
.npm
|
||||
.eslintcache
|
||||
.stylelintcache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
.node_repl_history
|
||||
*.tgz
|
||||
.yarn-integrity
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
.cache
|
||||
.parcel-cache
|
||||
.next
|
||||
out
|
||||
.nuxt
|
||||
dist
|
||||
.cache/
|
||||
.vuepress/dist
|
||||
.temp
|
||||
.docusaurus
|
||||
.serverless/
|
||||
.fusebox/
|
||||
.dynamodb/
|
||||
.tern-port
|
||||
.vscode-test
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
.DS_*
|
||||
**/*.backup.*
|
||||
**/*.back.*
|
||||
node_modules
|
||||
*.sublime*
|
||||
psd
|
||||
thumb
|
||||
sketch
|
||||
Thumbs.db
|
||||
Thumbs.db:encryptable
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
*.stackdump
|
||||
[Dd]esktop.ini
|
||||
$RECYCLE.BIN/
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
*.lnk
|
||||
*.tmlanguage.cache
|
||||
*.tmPreferences.cache
|
||||
*.stTheme.cache
|
||||
*.sublime-workspace
|
||||
sftp-config.json
|
||||
sftp-config-alt*.json
|
||||
Package Control.last-run
|
||||
Package Control.ca-list
|
||||
Package Control.ca-bundle
|
||||
Package Control.system-ca-bundle
|
||||
Package Control.cache/
|
||||
Package Control.ca-certs/
|
||||
Package Control.merged-ca-bundle
|
||||
Package Control.user-ca-bundle
|
||||
oscrypto-ca-bundle.crt
|
||||
bh_unicode_properties.cache
|
||||
GitHub.sublime-settings
|
||||
.vagrant/
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
Icon
|
||||
._*
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
*.class
|
||||
*.ctxt
|
||||
.mtj.tmp/
|
||||
*.jar
|
||||
*.war
|
||||
*.nar
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
hs_err_pid*
|
||||
replay_pid*
|
||||
*.orig
|
||||
*.BACKUP.*
|
||||
*.BASE.*
|
||||
*.LOCAL.*
|
||||
*.REMOTE.*
|
||||
*_BACKUP_*.txt
|
||||
*_BASE_*.txt
|
||||
*_LOCAL_*.txt
|
||||
*_REMOTE_*.txt
|
||||
*~
|
||||
.fuse_hidden*
|
||||
.directory
|
||||
.Trash-*
|
||||
.nfs*
|
||||
.gradle
|
||||
**/build/
|
||||
!src/**/build/
|
||||
gradle-app.setting
|
||||
!gradle-wrapper.jar
|
||||
!gradle-wrapper.properties
|
||||
.gradletasknamecache
|
||||
[._]*.s[a-v][a-z]
|
||||
!*.svg # comment out if you don't need vector files
|
||||
[._]*.sw[a-p]
|
||||
[._]s[a-rt-v][a-z]
|
||||
[._]ss[a-gi-z]
|
||||
[._]sw[a-p]
|
||||
Session.vim
|
||||
Sessionx.vim
|
||||
.netrwhist
|
||||
tags
|
||||
[._]*.un~
|
||||
.yarn/*
|
||||
!.yarn/releases
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
!.yarn/cache
|
||||
*.db
|
||||
10
.idea/.gitignore
generated
vendored
Normal file
10
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
# Zeppelin 忽略的文件
|
||||
/ZeppelinRemoteNotebooks/
|
||||
6
.idea/ApifoxUploaderProjectSetting.xml
generated
Normal file
6
.idea/ApifoxUploaderProjectSetting.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ApifoxUploaderProjectSetting">
|
||||
<option name="apiAccessToken" value="APS-0ZZaS4q0gUiFOlbBJMN8hAmS7viQNi4D" />
|
||||
</component>
|
||||
</project>
|
||||
63
.idea/codeStyles/Project.xml
generated
Normal file
63
.idea/codeStyles/Project.xml
generated
Normal file
@@ -0,0 +1,63 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<JavaCodeStyleSettings>
|
||||
<option name="DELETE_UNUSED_MODULE_IMPORTS" value="true" />
|
||||
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="100" />
|
||||
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="100" />
|
||||
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
|
||||
<value />
|
||||
</option>
|
||||
<option name="IMPORT_LAYOUT_TABLE">
|
||||
<value>
|
||||
<package name="" withSubpackages="true" static="false" module="true" />
|
||||
<package name="" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="" withSubpackages="true" static="true" />
|
||||
</value>
|
||||
</option>
|
||||
</JavaCodeStyleSettings>
|
||||
<JetCodeStyleSettings>
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<ScalaCodeStyleSettings>
|
||||
<option name="MULTILINE_STRING_CLOSING_QUOTES_ON_NEW_LINE" value="true" />
|
||||
</ScalaCodeStyleSettings>
|
||||
<TypeScriptCodeStyleSettings version="0">
|
||||
<option name="USE_SEMICOLON_AFTER_STATEMENT" value="false" />
|
||||
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||
<option name="USE_DOUBLE_QUOTES" value="false" />
|
||||
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||
<option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
|
||||
</TypeScriptCodeStyleSettings>
|
||||
<codeStyleSettings language="JAVA">
|
||||
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
|
||||
<option name="LINE_COMMENT_ADD_SPACE" value="true" />
|
||||
<option name="LINE_COMMENT_ADD_SPACE_ON_REFORMAT" value="true" />
|
||||
<option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="TypeScript">
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
|
||||
<option name="BLOCK_COMMENT_AT_FIRST_COLUMN" value="false" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
||||
99
.idea/compiler.xml
generated
Normal file
99
.idea/compiler.xml
generated
Normal file
@@ -0,0 +1,99 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<annotationProcessing>
|
||||
<profile default="true" name="Default" enabled="true" />
|
||||
<profile name="Maven default annotation processors profile" enabled="true">
|
||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
</profile>
|
||||
<profile name="Annotation profile for bookstore" enabled="true">
|
||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<option name="querydsl.createDefaultVariable" value="true" />
|
||||
<option name="querydsl.entityAccessors" value="true" />
|
||||
<processorPath useClasspath="false">
|
||||
<entry name="$MAVEN_REPOSITORY$/org/projectlombok/lombok/1.18.36/lombok-1.18.36.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/hibernate/orm/hibernate-jpamodelgen/6.6.15.Final/hibernate-jpamodelgen-6.6.15.Final.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/hibernate/orm/hibernate-core/6.6.15.Final/hibernate-core-6.6.15.Final.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/jakarta/persistence/jakarta.persistence-api/3.2.0/jakarta.persistence-api-3.2.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/jakarta/transaction/jakarta.transaction-api/2.0.1/jakarta.transaction-api-2.0.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jboss/logging/jboss-logging/3.5.0.Final/jboss-logging-3.5.0.Final.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/hibernate/common/hibernate-commons-annotations/7.0.3.Final/hibernate-commons-annotations-7.0.3.Final.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/io/smallrye/jandex/3.2.0/jandex-3.2.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/fasterxml/classmate/1.5.1/classmate-1.5.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.15.11/byte-buddy-1.15.11.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/jakarta/xml/bind/jakarta.xml.bind-api/4.0.0/jakarta.xml.bind-api-4.0.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/jakarta/activation/jakarta.activation-api/2.1.0/jakarta.activation-api-2.1.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/glassfish/jaxb/jaxb-runtime/4.0.2/jaxb-runtime-4.0.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/glassfish/jaxb/jaxb-core/4.0.2/jaxb-core-4.0.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/eclipse/angus/angus-activation/2.0.0/angus-activation-2.0.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/glassfish/jaxb/txw2/4.0.2/txw2-4.0.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/sun/istack/istack-commons-runtime/4.1.1/istack-commons-runtime-4.1.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/jakarta/inject/jakarta.inject-api/2.0.1/jakarta.inject-api-2.0.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.13.0/antlr4-runtime-4.13.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/jakarta/validation/jakarta.validation-api/3.0.2/jakarta.validation-api-3.0.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/jakarta/annotation/jakarta.annotation-api/2.1.1/jakarta.annotation-api-2.1.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/io/github/openfeign/querydsl/querydsl-apt/7.0/querydsl-apt-7.0-jpa.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/io/github/openfeign/querydsl/querydsl-codegen/7.0/querydsl-codegen-7.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/io/github/openfeign/querydsl/querydsl-core/7.0/querydsl-core-7.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/io/projectreactor/reactor-core/3.7.6/reactor-core-3.7.6.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/reactivestreams/reactive-streams/1.0.4/reactive-streams-1.0.4.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/io/github/openfeign/querydsl/querydsl-codegen-utils/7.0/querydsl-codegen-utils-7.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/eclipse/jdt/ecj/3.40.0/ecj-3.40.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.179/classgraph-4.8.179.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/annotations/26.0.2/annotations-26.0.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/dev/morphia/morphia/morphia-core/2.5.0/morphia-core-2.5.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/io/smallrye/config/smallrye-config/3.10.1/smallrye-config-3.10.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/io/smallrye/config/smallrye-config-core/3.10.1/smallrye-config-core-3.10.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/eclipse/microprofile/config/microprofile-config-api/3.1/microprofile-config-api-3.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/io/smallrye/common/smallrye-common-annotation/2.8.0/smallrye-common-annotation-2.8.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/io/smallrye/common/smallrye-common-expression/2.8.0/smallrye-common-expression-2.8.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/io/smallrye/common/smallrye-common-function/2.8.0/smallrye-common-function-2.8.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/io/smallrye/common/smallrye-common-constraint/2.8.0/smallrye-common-constraint-2.8.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/io/smallrye/common/smallrye-common-classloader/2.8.0/smallrye-common-classloader-2.8.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/ow2/asm/asm/9.8/asm-9.8.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/io/smallrye/config/smallrye-config-common/3.10.1/smallrye-config-common-3.10.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/mongodb/mongodb-driver-sync/5.4.0/mongodb-driver-sync-5.4.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/mongodb/bson/5.4.0/bson-5.4.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/mongodb/mongodb-driver-core/5.4.0/mongodb-driver-core-5.4.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/mongodb/bson-record-codec/5.4.0/bson-record-codec-5.4.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/mongodb/mongodb-driver-legacy/5.4.0/mongodb-driver-legacy-5.4.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/2.0.17/slf4j-api-2.0.17.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/github/spotbugs/spotbugs-annotations/4.8.6/spotbugs-annotations-4.8.6.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/semver4j/semver4j/5.6.0/semver4j-5.6.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jspecify/jspecify/1.0.0/jspecify-1.0.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jsoup/jsoup/1.18.3/jsoup-1.18.3.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/hibernate/orm/hibernate-envers/7.0.0.Beta1/hibernate-envers-7.0.0.Beta1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/hibernate/models/hibernate-models/0.8.6/hibernate-models-0.8.6.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/io/github/openfeign/querydsl/querydsl-core/7.0/querydsl-core-7.0-tests.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/joda/joda-money/2.0.2/joda-money-2.0.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter/5.13.1/junit-jupiter-5.13.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.13.1/junit-jupiter-api-5.13.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.13.1/junit-platform-commons-1.13.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-params/5.13.1/junit-jupiter-params-5.13.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-engine/5.13.1/junit-jupiter-engine-5.13.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.13.1/junit-platform-engine-1.13.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/assertj/assertj-core/3.27.3/assertj-core-3.27.3.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/junit/vintage/junit-vintage-engine/5.13.1/junit-vintage-engine-5.13.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/junit/junit/4.13.2/junit-4.13.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/easymock/easymock/5.6.0/easymock-5.6.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/objenesis/objenesis/3.4/objenesis-3.4.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/javassist/javassist/3.30.2-GA/javassist-3.30.2-GA.jar" />
|
||||
</processorPath>
|
||||
<module name="bookstore" />
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
</component>
|
||||
<component name="JavacSettings">
|
||||
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
|
||||
<module name="bookstore" options="-Aquerydsl.entityAccessors=true -Aquerydsl.createDefaultVariable=true" />
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
17
.idea/dataSources.xml
generated
Normal file
17
.idea/dataSources.xml
generated
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="bookstore" uuid="44b22720-3d6e-411e-9af3-fa016ca8dd58">
|
||||
<driver-ref>h2.unified</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>org.h2.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:h2:$PROJECT_DIR$/bookstore;DB_CLOSE_ON_EXIT=TRUE</jdbc-url>
|
||||
<jdbc-additional-properties>
|
||||
<property name="com.intellij.clouds.kubernetes.db.host.port" />
|
||||
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
|
||||
<property name="com.intellij.clouds.kubernetes.db.container.port" />
|
||||
</jdbc-additional-properties>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
||||
7
.idea/encodings.xml
generated
Normal file
7
.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
||||
30
.idea/jarRepositories.xml
generated
Normal file
30
.idea/jarRepositories.xml
generated
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Central Repository" />
|
||||
<option name="url" value="https://repo.maven.apache.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="lanyuanxiaoyao-maven-central" />
|
||||
<option name="name" value="lanyuanxiaoyao-maven-central" />
|
||||
<option name="url" value="https://maven.lanyuanxiaoyao.com/central" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jboss.community" />
|
||||
<option name="name" value="JBoss Community repository" />
|
||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Central Repository" />
|
||||
<option name="url" value="https://maven.lanyuanxiaoyao.com/central" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
||||
14
.idea/misc.xml
generated
Normal file
14
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="temurin-21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
1931
client/bun.lock
Normal file
1931
client/bun.lock
Normal file
File diff suppressed because it is too large
Load Diff
32
client/index.html
Normal file
32
client/index.html
Normal file
@@ -0,0 +1,32 @@
|
||||
<!--suppress CssUnknownTarget, HtmlUnknownTarget -->
|
||||
<!doctype html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<link href="icon.png" rel="icon"/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||
<title>我的书架</title>
|
||||
<style>
|
||||
html, body, #root {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'LXGWWenKai';
|
||||
src: url('fonts/LXGWNeoXiHei.ttf') format('truetype');
|
||||
}
|
||||
|
||||
*:not(.fa,.fas) {
|
||||
font-family: LXGWWenKai, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji', serif !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="/src/index.tsx" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
51
client/package.json
Normal file
51
client/package.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "lepoard-web",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"preview": "vite preview",
|
||||
"test": "vitest run",
|
||||
"clean": "rimraf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^6.0.2",
|
||||
"@ant-design/pro-components": "^2.8.10",
|
||||
"@ant-design/x": "^1.6.1",
|
||||
"@echofly/fetch-event-source": "^3.0.2",
|
||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||
"@lightenna/react-mermaid-diagram": "^1.0.21",
|
||||
"ahooks": "^3.9.5",
|
||||
"amis": "^6.13.0",
|
||||
"amis-core": "^6.13.0",
|
||||
"antd": "^5.27.3",
|
||||
"axios": "1.11.0",
|
||||
"chart.js": "^4.5.0",
|
||||
"echarts-for-react": "^3.0.2",
|
||||
"es-toolkit": "^1.39.10",
|
||||
"mermaid": "^11.11.0",
|
||||
"react": "^18.3.1",
|
||||
"react-chartjs-2": "^5.3.0",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-router": "^7.9.1",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"styled-components": "^6.1.19",
|
||||
"yocto-queue": "^1.2.1",
|
||||
"zustand": "^5.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.3.24",
|
||||
"@types/react-dom": "^18.3.7",
|
||||
"@vitejs/plugin-react-swc": "^3.11.0",
|
||||
"globals": "^16.4.0",
|
||||
"rimraf": "^6.0.1",
|
||||
"sass": "^1.92.1",
|
||||
"typescript": "~5.8.3",
|
||||
"vite": "^7.1.5",
|
||||
"vite-plugin-javascript-obfuscator": "^3.1.0",
|
||||
"vitest": "^3.2.4"
|
||||
}
|
||||
}
|
||||
BIN
client/public/fonts/LXGWNeoXiHei.ttf
Normal file
BIN
client/public/fonts/LXGWNeoXiHei.ttf
Normal file
Binary file not shown.
BIN
client/public/icon.png
Normal file
BIN
client/public/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
54
client/src/components/Markdown.tsx
Normal file
54
client/src/components/Markdown.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import 'chart.js/auto'
|
||||
import {MermaidDiagram} from '@lightenna/react-mermaid-diagram'
|
||||
import EChartsReact from 'echarts-for-react'
|
||||
import {Chart} from 'react-chartjs-2'
|
||||
import Markdown from 'react-markdown'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import {trim} from 'es-toolkit'
|
||||
|
||||
type MarkdownOptions = {
|
||||
content: string
|
||||
}
|
||||
|
||||
function MarkdownRender(options: MarkdownOptions) {
|
||||
return (
|
||||
<Markdown
|
||||
remarkPlugins={[
|
||||
remarkGfm,
|
||||
]}
|
||||
children={options.content}
|
||||
components={{
|
||||
code: ({children, className, node, ...rest}) => {
|
||||
switch (trim(className || '')) {
|
||||
case 'language-mermaid':
|
||||
return (
|
||||
<MermaidDiagram
|
||||
children={children as string}
|
||||
/>
|
||||
)
|
||||
case 'language-chartjs':
|
||||
let chartjsData = eval(`(${children as string})`)
|
||||
return (
|
||||
<Chart
|
||||
{...chartjsData}
|
||||
/>
|
||||
)
|
||||
case 'language-echart':
|
||||
let echartData = eval(`(${children as string})`)
|
||||
return (
|
||||
<EChartsReact option={echartData}/>
|
||||
)
|
||||
default:
|
||||
return (
|
||||
<code {...rest} className={className}>
|
||||
{children}
|
||||
</code>
|
||||
)
|
||||
}
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default MarkdownRender
|
||||
15
client/src/components/amis/MarkdownEnhance.scss
Normal file
15
client/src/components/amis/MarkdownEnhance.scss
Normal file
@@ -0,0 +1,15 @@
|
||||
.markdown-enhance {
|
||||
tr {
|
||||
border-top: 1px solid #c6cbd1;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 6px 13px;
|
||||
border: 1px solid #dfe2e5;
|
||||
}
|
||||
|
||||
table tr:nth-child(2n) {
|
||||
background: #f6f8fa;
|
||||
}
|
||||
}
|
||||
22
client/src/components/amis/MarkdownEnhance.tsx
Normal file
22
client/src/components/amis/MarkdownEnhance.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import {Renderer, type RendererProps} from 'amis'
|
||||
import React from 'react'
|
||||
import Markdown from '../Markdown.tsx'
|
||||
import './MarkdownEnhance.scss'
|
||||
import {once} from 'es-toolkit'
|
||||
|
||||
const MarkdownEnhance: React.FC<RendererProps> = props => {
|
||||
return (
|
||||
<div className="markdown-enhance">
|
||||
<Markdown content={props.content}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const register = once(() => {
|
||||
Renderer({
|
||||
type: 'markdown-enhance',
|
||||
autoVar: true,
|
||||
})(React.memo(MarkdownEnhance))
|
||||
})
|
||||
|
||||
register()
|
||||
1
client/src/components/amis/Registry.ts
Normal file
1
client/src/components/amis/Registry.ts
Normal file
@@ -0,0 +1 @@
|
||||
import './MarkdownEnhance.tsx'
|
||||
9
client/src/index.scss
Normal file
9
client/src/index.scss
Normal file
@@ -0,0 +1,9 @@
|
||||
.copyright {
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
// 改写一些amis中控制不到的全局CSS
|
||||
button.btn-deleted:hover {
|
||||
color: #dc2626 !important;
|
||||
}
|
||||
52
client/src/index.tsx
Normal file
52
client/src/index.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import {createRoot} from 'react-dom/client'
|
||||
import {createHashRouter, Navigate, type RouteObject, RouterProvider} from 'react-router'
|
||||
import './index.scss'
|
||||
import './components/amis/Registry.ts'
|
||||
import Overview from './pages/Overview.tsx'
|
||||
import Root from './pages/Root.tsx'
|
||||
import Test from './pages/Test.tsx'
|
||||
import Bookshelf from './pages/book/Bookshelf.tsx'
|
||||
import Book from './pages/book/Book.tsx'
|
||||
import Chapter from './pages/book/Chapter.tsx'
|
||||
|
||||
const routes: RouteObject[] = [
|
||||
{
|
||||
path: '/',
|
||||
Component: Root,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Navigate to="/overview" replace/>,
|
||||
},
|
||||
{
|
||||
path: 'overview',
|
||||
Component: Overview,
|
||||
},
|
||||
{
|
||||
path: 'bookshelf',
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
Component: Bookshelf,
|
||||
},
|
||||
{
|
||||
path: 'book/:id',
|
||||
Component: Book,
|
||||
},
|
||||
{
|
||||
path: 'chapter/:id',
|
||||
Component: Chapter,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'test',
|
||||
Component: Test,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<RouterProvider router={createHashRouter(routes)}/>,
|
||||
)
|
||||
9
client/src/pages/Overview.tsx
Normal file
9
client/src/pages/Overview.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
function Overview() {
|
||||
return (
|
||||
<div className="overview"></div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Overview)
|
||||
120
client/src/pages/Root.tsx
Normal file
120
client/src/pages/Root.tsx
Normal file
@@ -0,0 +1,120 @@
|
||||
import {BookOutlined, DeploymentUnitOutlined, InfoCircleOutlined} from '@ant-design/icons'
|
||||
import {type AppItemProps, ProLayout} from '@ant-design/pro-components'
|
||||
import {ConfigProvider} from 'antd'
|
||||
import React, {useMemo} from 'react'
|
||||
import {NavLink, Outlet, useLocation} from 'react-router'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const ProLayoutDiv = styled.div`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
.ant-menu-sub > .ant-menu-item {
|
||||
//padding-left: 28px !important;
|
||||
}
|
||||
`
|
||||
const apps: AppItemProps[] = []
|
||||
|
||||
const menus = {
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: '概览',
|
||||
icon: <InfoCircleOutlined/>,
|
||||
routes: [
|
||||
{
|
||||
path: '/overview',
|
||||
name: '概览',
|
||||
icon: <InfoCircleOutlined/>,
|
||||
},
|
||||
{
|
||||
path: '/bookshelf',
|
||||
name: '书架',
|
||||
icon: <BookOutlined/>,
|
||||
},
|
||||
{
|
||||
path: '/test',
|
||||
name: '测试',
|
||||
icon: <DeploymentUnitOutlined/>,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const Root: React.FC = () => {
|
||||
const location = useLocation()
|
||||
const currentYear = useMemo(() => new Date().getFullYear(), [])
|
||||
return (
|
||||
<ProLayoutDiv>
|
||||
<ProLayout
|
||||
collapsed={false}
|
||||
collapsedButtonRender={() => <></>}
|
||||
siderWidth={180}
|
||||
token={{
|
||||
colorTextAppListIcon: '#dfdfdf',
|
||||
colorTextAppListIconHover: '#ffffff',
|
||||
header: {
|
||||
colorBgHeader: '#292f33',
|
||||
colorHeaderTitle: '#ffffff',
|
||||
colorTextMenu: '#dfdfdf',
|
||||
colorTextMenuSecondary: '#dfdfdf',
|
||||
colorTextMenuSelected: '#ffffff',
|
||||
colorTextMenuActive: '#ffffff',
|
||||
colorBgMenuItemSelected: '#22272b',
|
||||
colorTextRightActionsItem: '#dfdfdf',
|
||||
},
|
||||
pageContainer: {
|
||||
paddingBlockPageContainerContent: 0,
|
||||
paddingInlinePageContainerContent: 0,
|
||||
marginBlockPageContainerContent: 0,
|
||||
marginInlinePageContainerContent: 0,
|
||||
},
|
||||
}}
|
||||
appList={apps}
|
||||
breakpoint={false}
|
||||
disableMobile={true}
|
||||
logo={<img src="icon.png" alt="logo"/>}
|
||||
title="吉普莉尔"
|
||||
route={menus}
|
||||
location={{pathname: location.pathname}}
|
||||
menu={{type: 'group'}}
|
||||
menuItemRender={(item, defaultDom) =>
|
||||
<NavLink to={item.path || '/'}>{defaultDom}</NavLink>
|
||||
}
|
||||
fixSiderbar={true}
|
||||
layout="side"
|
||||
splitMenus={true}
|
||||
style={{minHeight: '100vh'}}
|
||||
contentStyle={{backgroundColor: 'white', padding: '10px 10px 10px 20px'}}
|
||||
menuFooterRender={props => {
|
||||
return (
|
||||
<div className="copyright" style={{userSelect: 'none', msUserSelect: 'none'}}>
|
||||
{props?.collapsed
|
||||
? undefined
|
||||
: <div>© 2023-{currentYear} 兰缘小妖</div>}
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
>
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
components: {
|
||||
Card: {
|
||||
bodyPadding: 0,
|
||||
bodyPaddingSM: 0,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Outlet/>
|
||||
</ConfigProvider>
|
||||
</ProLayout>
|
||||
</ProLayoutDiv>
|
||||
)
|
||||
}
|
||||
|
||||
export default Root
|
||||
10
client/src/pages/Test.tsx
Normal file
10
client/src/pages/Test.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react'
|
||||
|
||||
function Test() {
|
||||
return (
|
||||
<div className="test">
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Test)
|
||||
225
client/src/pages/book/Book.tsx
Normal file
225
client/src/pages/book/Book.tsx
Normal file
@@ -0,0 +1,225 @@
|
||||
import React from 'react'
|
||||
import {useNavigate, useParams} from 'react-router'
|
||||
import {
|
||||
amisRender,
|
||||
commonInfo,
|
||||
crudCommonOptions,
|
||||
horizontalFormOptions,
|
||||
paginationTemplate,
|
||||
time,
|
||||
} from '../../util/amis.tsx'
|
||||
|
||||
function Book() {
|
||||
const navigate = useNavigate()
|
||||
const {id} = useParams()
|
||||
return (
|
||||
<div className="book">
|
||||
{amisRender(
|
||||
{
|
||||
type: 'page',
|
||||
title: '书籍详情',
|
||||
initApi: `${commonInfo.baseUrl}/book/detail/${id}`,
|
||||
body: [
|
||||
{
|
||||
type: 'property',
|
||||
title: '${name}',
|
||||
items: [
|
||||
{label: '作者', content: '${author}'},
|
||||
{label: '来源', content: '${source}'},
|
||||
{label: '标签', content: '${tags}'},
|
||||
{label: '描述', content: '${description}', span: 3},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'crud',
|
||||
api: {
|
||||
method: 'post',
|
||||
url: `${commonInfo.baseUrl}/chapter/list`,
|
||||
convertKeyToPath: false,
|
||||
data: {
|
||||
query: {
|
||||
equal: {
|
||||
'book.id': id,
|
||||
},
|
||||
},
|
||||
page: {
|
||||
index: '${page}',
|
||||
size: '${perPage}',
|
||||
},
|
||||
sort: [
|
||||
{
|
||||
column: 'sequence',
|
||||
direction: 'ASC',
|
||||
},
|
||||
{
|
||||
column: 'modifiedTime',
|
||||
direction: 'DESC',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
...crudCommonOptions(),
|
||||
...paginationTemplate(
|
||||
undefined,
|
||||
undefined,
|
||||
[
|
||||
{
|
||||
type: 'action',
|
||||
label: '',
|
||||
icon: 'fa fa-rotate-right',
|
||||
actionType: 'ajax',
|
||||
tooltip: '序号重排',
|
||||
tooltipPlacement: 'top',
|
||||
api: `get:${commonInfo.baseUrl}/chapter/generate_sequence`
|
||||
},
|
||||
{
|
||||
type: 'action',
|
||||
label: '',
|
||||
icon: 'fa fa-upload',
|
||||
actionType: 'dialog',
|
||||
dialog: {
|
||||
title: '导入章节',
|
||||
size: 'md',
|
||||
body: {
|
||||
debug: commonInfo.debug,
|
||||
type: 'form',
|
||||
api: `post:${commonInfo.baseUrl}/chapter/save_with_content`,
|
||||
...horizontalFormOptions(),
|
||||
canAccessSuperData: false,
|
||||
body: [
|
||||
{
|
||||
type: 'hidden',
|
||||
name: 'bookId',
|
||||
value: id,
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
name: 'mode',
|
||||
label: '导入方式',
|
||||
selectFirst: true,
|
||||
options: [
|
||||
{
|
||||
label: '新增',
|
||||
value: 'CREATE',
|
||||
},
|
||||
{
|
||||
label: '覆盖',
|
||||
value: 'OVERRIDE',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'input-text',
|
||||
name: 'name',
|
||||
label: '章节名称',
|
||||
required: true,
|
||||
clearable: true,
|
||||
},
|
||||
{
|
||||
type: 'input-text',
|
||||
name: 'description',
|
||||
label: '章节描述',
|
||||
clearable: true,
|
||||
},
|
||||
{
|
||||
type: 'editor',
|
||||
name: 'content',
|
||||
label: '章节内容',
|
||||
required: true,
|
||||
clearable: true,
|
||||
options: {
|
||||
wordWrap: 'on',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
/*{
|
||||
type: 'action',
|
||||
label: '',
|
||||
icon: 'fa fa-plus',
|
||||
actionType: 'dialog',
|
||||
dialog: detailDialog(),
|
||||
},*/
|
||||
]
|
||||
),
|
||||
columns: [
|
||||
{
|
||||
name: 'sequence',
|
||||
label: '序号',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
label: '章节名称',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
label: '描述',
|
||||
width: 250,
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
...time('createdTime'),
|
||||
},
|
||||
{
|
||||
label: '更新时间',
|
||||
...time('modifiedTime'),
|
||||
},
|
||||
{
|
||||
type: 'operation',
|
||||
label: '操作',
|
||||
width: 150,
|
||||
fixed: 'right',
|
||||
buttons: [
|
||||
{
|
||||
type: 'action',
|
||||
label: '详情',
|
||||
level: 'link',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'custom',
|
||||
// @ts-ignore
|
||||
script: (context, action, event) => {
|
||||
navigate(`/bookshelf/chapter/${context.props.data['id']}`)
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
/*{
|
||||
type: 'action',
|
||||
label: '修改',
|
||||
level: 'link',
|
||||
size: 'sm',
|
||||
actionType: 'dialog',
|
||||
dialog: detailDialog(),
|
||||
},*/
|
||||
{
|
||||
className: 'text-danger btn-deleted',
|
||||
type: 'action',
|
||||
label: '删除',
|
||||
level: 'link',
|
||||
actionType: 'ajax',
|
||||
api: `get:${commonInfo.baseUrl}/chapter/remove/\${id}`,
|
||||
confirmText: '确认删除章节<span class="text-lg font-bold mx-2">${name}</span>?',
|
||||
confirmTitle: '删除',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Book)
|
||||
183
client/src/pages/book/Bookshelf.tsx
Normal file
183
client/src/pages/book/Bookshelf.tsx
Normal file
@@ -0,0 +1,183 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
amisRender,
|
||||
commonInfo,
|
||||
crudCommonOptions,
|
||||
horizontalFormOptions,
|
||||
paginationTemplate,
|
||||
time,
|
||||
} from '../../util/amis.tsx'
|
||||
import {useNavigate} from 'react-router'
|
||||
|
||||
const detailDialog = () => {
|
||||
return {
|
||||
title: '添加书架',
|
||||
size: 'md',
|
||||
body: {
|
||||
debug: commonInfo.debug,
|
||||
type: 'form',
|
||||
api: `${commonInfo.baseUrl}/book/save`,
|
||||
initApi: `${commonInfo.baseUrl}/book/detail/\${id}`,
|
||||
initFetchOn: '${id}',
|
||||
...horizontalFormOptions(),
|
||||
canAccessSuperData: false,
|
||||
body: [
|
||||
{
|
||||
type: 'input-text',
|
||||
name: 'name',
|
||||
label: '书名',
|
||||
required: true,
|
||||
clearable: true,
|
||||
},
|
||||
{
|
||||
type: 'input-text',
|
||||
name: 'author',
|
||||
label: '作者',
|
||||
clearable: true,
|
||||
},
|
||||
{
|
||||
type: 'textarea',
|
||||
name: 'description',
|
||||
label: '描述',
|
||||
clearable: true,
|
||||
},
|
||||
{
|
||||
type: 'input-text',
|
||||
name: 'source',
|
||||
label: '来源',
|
||||
clearable: true,
|
||||
validations: {
|
||||
isUrl: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'input-tag',
|
||||
name: 'tags',
|
||||
label: '标签',
|
||||
placeholder: '',
|
||||
clearable: true,
|
||||
source: `${commonInfo.baseUrl}/book/tags`,
|
||||
max: 5,
|
||||
joinValues: false,
|
||||
extractValue: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function Bookshelf() {
|
||||
const navigate = useNavigate()
|
||||
return (
|
||||
<div className="bookshelf">
|
||||
{amisRender(
|
||||
{
|
||||
type: 'page',
|
||||
title: '书架',
|
||||
body: [
|
||||
{
|
||||
type: 'crud',
|
||||
api: `${commonInfo.baseUrl}/book/list`,
|
||||
...crudCommonOptions(),
|
||||
...paginationTemplate(
|
||||
undefined,
|
||||
undefined,
|
||||
[
|
||||
{
|
||||
type: 'action',
|
||||
label: '',
|
||||
icon: 'fa fa-plus',
|
||||
actionType: 'dialog',
|
||||
dialog: detailDialog(),
|
||||
},
|
||||
],
|
||||
),
|
||||
columns: [
|
||||
{
|
||||
name: 'name',
|
||||
label: '书名',
|
||||
width: 150,
|
||||
fixed: 'left',
|
||||
},
|
||||
{
|
||||
name: 'author',
|
||||
label: '作者',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
label: '描述',
|
||||
width: 250,
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
label: '来源',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
name: 'tags',
|
||||
label: '标签',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
...time('createdTime'),
|
||||
},
|
||||
{
|
||||
label: '更新时间',
|
||||
...time('modifiedTime'),
|
||||
},
|
||||
{
|
||||
type: 'operation',
|
||||
label: '操作',
|
||||
width: 150,
|
||||
fixed: 'right',
|
||||
buttons: [
|
||||
{
|
||||
type: 'action',
|
||||
label: '详情',
|
||||
level: 'link',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'custom',
|
||||
// @ts-ignore
|
||||
script: (context, action, event) => {
|
||||
navigate(`/bookshelf/book/${context.props.data['id']}`)
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'action',
|
||||
label: '修改',
|
||||
level: 'link',
|
||||
size: 'sm',
|
||||
actionType: 'dialog',
|
||||
dialog: detailDialog(),
|
||||
},
|
||||
{
|
||||
className: 'text-danger btn-deleted',
|
||||
type: 'action',
|
||||
label: '删除',
|
||||
level: 'link',
|
||||
actionType: 'ajax',
|
||||
api: `get:${commonInfo.baseUrl}/book/remove/\${id}`,
|
||||
confirmText: '确认删除书籍<span class="text-lg font-bold mx-2">${name}</span>?',
|
||||
confirmTitle: '删除',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Bookshelf)
|
||||
144
client/src/pages/book/Chapter.tsx
Normal file
144
client/src/pages/book/Chapter.tsx
Normal file
@@ -0,0 +1,144 @@
|
||||
import React from 'react'
|
||||
import {useParams} from 'react-router'
|
||||
import {amisRender, commonInfo, crudCommonOptions, horizontalFormOptions, paginationTemplate} from '../../util/amis.tsx'
|
||||
|
||||
function Chapter() {
|
||||
// const navigate = useNavigate()
|
||||
const {id} = useParams()
|
||||
return (
|
||||
<div className="chapter">
|
||||
{amisRender(
|
||||
{
|
||||
type: 'page',
|
||||
title: '章节详情',
|
||||
initApi: `${commonInfo.baseUrl}/chapter/detail/${id}`,
|
||||
body: [
|
||||
{
|
||||
type: 'property',
|
||||
title: '${name}',
|
||||
items: [
|
||||
{label: '序号', content: '${sequence}'},
|
||||
{label: '名称', content: '${name}', span: 2},
|
||||
{label: '描述', content: '${description}', span: 3},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'crud',
|
||||
api: {
|
||||
method: 'post',
|
||||
url: `${commonInfo.baseUrl}/line/list`,
|
||||
convertKeyToPath: false,
|
||||
data: {
|
||||
query: {
|
||||
equal: {
|
||||
'chapter.id': id,
|
||||
},
|
||||
},
|
||||
page: {
|
||||
index: '${page}',
|
||||
size: '${perPage}',
|
||||
},
|
||||
sort: [
|
||||
{
|
||||
column: 'sequence',
|
||||
direction: 'ASC',
|
||||
},
|
||||
{
|
||||
column: 'modifiedTime',
|
||||
direction: 'DESC',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
...crudCommonOptions(),
|
||||
...paginationTemplate(
|
||||
undefined,
|
||||
undefined,
|
||||
[
|
||||
{
|
||||
type: 'action',
|
||||
label: '',
|
||||
icon: 'fa fa-rotate-right',
|
||||
actionType: 'ajax',
|
||||
tooltip: '序号重排',
|
||||
tooltipPlacement: 'top',
|
||||
api: `get:${commonInfo.baseUrl}/line/generate_sequence`
|
||||
}
|
||||
]
|
||||
),
|
||||
columns: [
|
||||
{
|
||||
name: 'sequence',
|
||||
label: '序号',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
name: 'text',
|
||||
label: '章节名称',
|
||||
},
|
||||
{
|
||||
type: 'operation',
|
||||
label: '操作',
|
||||
width: 100,
|
||||
fixed: 'right',
|
||||
buttons: [
|
||||
{
|
||||
type: 'action',
|
||||
label: '修改',
|
||||
level: 'link',
|
||||
size: 'sm',
|
||||
actionType: 'dialog',
|
||||
dialog: {
|
||||
title: '修改',
|
||||
size: 'md',
|
||||
body: {
|
||||
type: 'form',
|
||||
...horizontalFormOptions(),
|
||||
api: `post:${commonInfo.baseUrl}/line/save`,
|
||||
body: [
|
||||
{
|
||||
type: 'hidden',
|
||||
name: 'id',
|
||||
},
|
||||
{
|
||||
type: 'hidden',
|
||||
name: 'chapterId',
|
||||
value: id,
|
||||
},
|
||||
{
|
||||
type: 'editor',
|
||||
name: 'text',
|
||||
label: '章节内容',
|
||||
required: true,
|
||||
clearable: true,
|
||||
options: {
|
||||
wordWrap: 'on',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
className: 'text-danger btn-deleted',
|
||||
type: 'action',
|
||||
label: '删除',
|
||||
level: 'link',
|
||||
actionType: 'ajax',
|
||||
api: `get:${commonInfo.baseUrl}/line/remove/\${id}`,
|
||||
confirmText: '确认删除行?',
|
||||
confirmTitle: '删除',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Chapter)
|
||||
340
client/src/util/amis.tsx
Normal file
340
client/src/util/amis.tsx
Normal file
@@ -0,0 +1,340 @@
|
||||
import {AlertComponent, attachmentAdpator, makeTranslator, render, type Schema, ToastComponent} from 'amis'
|
||||
import 'amis/lib/themes/antd.css'
|
||||
import 'amis/lib/helper.css'
|
||||
import 'amis/sdk/iconfont.css'
|
||||
import '@fortawesome/fontawesome-free/css/all.min.css'
|
||||
import axios from 'axios'
|
||||
import {isEqual} from 'es-toolkit'
|
||||
|
||||
export const commonInfo = {
|
||||
debug: isEqual(import.meta.env.MODE, 'development'),
|
||||
baseUrl: isEqual(import.meta.env.MODE, 'development') ? 'http://localhost:27891' : '',
|
||||
}
|
||||
|
||||
const __ = makeTranslator('zh')
|
||||
|
||||
const responseAdaptor = () => (response: any) => {
|
||||
let payload = response.data || {} // blob 下可能会返回内容为空?
|
||||
if (payload.hasOwnProperty('errno')) {
|
||||
payload.status = payload.errno
|
||||
payload.msg = payload.errmsg
|
||||
} else if (payload.hasOwnProperty('no')) {
|
||||
payload.status = payload.no
|
||||
payload.msg = payload.error
|
||||
}
|
||||
return {
|
||||
...response,
|
||||
data: payload,
|
||||
}
|
||||
}
|
||||
|
||||
export const amisRender = (schema: Schema, data: Record<any, any> = {}) => {
|
||||
const theme = 'antd'
|
||||
const locale = 'zh-CN'
|
||||
return (
|
||||
<>
|
||||
<ToastComponent
|
||||
theme={theme}
|
||||
key="toast"
|
||||
position={'top-right'}
|
||||
locale={locale}
|
||||
/>
|
||||
<AlertComponent theme={theme} key="alert" locale={locale}/>
|
||||
{render(
|
||||
schema,
|
||||
{
|
||||
data: {
|
||||
...commonInfo,
|
||||
...data,
|
||||
},
|
||||
theme: theme,
|
||||
},
|
||||
{
|
||||
enableAMISDebug: commonInfo.debug,
|
||||
fetcher: async (api: any) => {
|
||||
let {url, method, data, responseType, config, headers} = api
|
||||
config = config || {}
|
||||
config.url = url
|
||||
config.withCredentials = true
|
||||
responseType && (config.responseType = responseType)
|
||||
|
||||
if (config.cancelExecutor) {
|
||||
config.cancelToken = new (axios as any).CancelToken(
|
||||
config.cancelExecutor,
|
||||
)
|
||||
}
|
||||
|
||||
config.headers = headers || {}
|
||||
config.method = method
|
||||
config.data = data
|
||||
|
||||
if (method === 'get' && data) {
|
||||
config.params = data
|
||||
} else if (data && data instanceof FormData) {
|
||||
// config.headers['Content-Type'] = 'multipart/form-data';
|
||||
} else if (
|
||||
data &&
|
||||
typeof data !== 'string' &&
|
||||
!(data instanceof Blob) &&
|
||||
!(data instanceof ArrayBuffer)
|
||||
) {
|
||||
data = JSON.stringify(data)
|
||||
config.headers['Content-Type'] = 'application/json'
|
||||
}
|
||||
|
||||
// 支持返回各种报错信息
|
||||
config.validateStatus = function () {
|
||||
return true
|
||||
}
|
||||
|
||||
let response = await axios(config)
|
||||
response = await attachmentAdpator(response, __, api)
|
||||
response = responseAdaptor()(response)
|
||||
|
||||
if (response.status >= 400) {
|
||||
if (response.data) {
|
||||
// 主要用于 raw: 模式下,后端自己校验登录,
|
||||
if (
|
||||
response.status === 401 &&
|
||||
response.data.location &&
|
||||
response.data.location.startsWith('http')
|
||||
) {
|
||||
location.href = response.data.location.replace(
|
||||
'{{redirect}}',
|
||||
encodeURIComponent(location.href),
|
||||
)
|
||||
return new Promise(() => {
|
||||
})
|
||||
} else if (response.data.msg) {
|
||||
throw new Error(response.data.msg)
|
||||
} else {
|
||||
throw new Error(
|
||||
'System.requestError' + JSON.stringify(response.data, null, 2),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
`${'System.requestErrorStatus'} ${response.status}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return response
|
||||
},
|
||||
isCancel: (value: any) => (axios as any).isCancel(value),
|
||||
},
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function horizontalFormOptions() {
|
||||
return {
|
||||
mode: 'horizontal',
|
||||
horizontal: {
|
||||
leftFixed: 'sm',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function crudCommonOptions() {
|
||||
return {
|
||||
affixHeader: false,
|
||||
stopAutoRefreshWhenModalIsOpen: true,
|
||||
resizable: false,
|
||||
syncLocation: false,
|
||||
silentPolling: true,
|
||||
columnsTogglable: false,
|
||||
}
|
||||
}
|
||||
|
||||
export function readOnlyDialogOptions() {
|
||||
return {
|
||||
actions: [],
|
||||
showCloseButton: false,
|
||||
closeOnEsc: true,
|
||||
closeOnOutside: true,
|
||||
disabled: true,
|
||||
}
|
||||
}
|
||||
|
||||
export function paginationCommonOptions(perPage = true, maxButtons = 5) {
|
||||
let option = {
|
||||
type: 'pagination',
|
||||
layout: [
|
||||
'pager',
|
||||
],
|
||||
maxButtons: maxButtons,
|
||||
showPageInput: false,
|
||||
perPageAvailable: [10, 15, 20, 50, 100, 200],
|
||||
}
|
||||
if (perPage) {
|
||||
option.layout.push('perPage')
|
||||
}
|
||||
return option
|
||||
}
|
||||
|
||||
export function paginationTemplate(perPage = 20, maxButtons = 5, extraHeaders: Array<Schema | string> = [], extraFooters: Array<Schema | string> = []) {
|
||||
return {
|
||||
perPage: perPage,
|
||||
headerToolbar: [
|
||||
'reload',
|
||||
paginationCommonOptions(true, maxButtons),
|
||||
...extraHeaders,
|
||||
],
|
||||
footerToolbar: [
|
||||
'statistics',
|
||||
paginationCommonOptions(true, maxButtons),
|
||||
...extraFooters,
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
export function copyField(field: string, tips = '复制', ignoreLength = 0) {
|
||||
let tpl = ignoreLength === 0 ? `\${${field}}` : `\${TRUNCATE(${field}, ${ignoreLength})}`
|
||||
return {
|
||||
type: 'wrapper',
|
||||
size: 'none',
|
||||
body: [
|
||||
{
|
||||
type: 'tpl',
|
||||
className: 'mr-1',
|
||||
tpl: tpl,
|
||||
},
|
||||
{
|
||||
type: 'action',
|
||||
level: 'link',
|
||||
label: '',
|
||||
icon: 'fa fa-copy',
|
||||
size: 'xs',
|
||||
actionType: 'copy',
|
||||
content: `\$${field}`,
|
||||
tooltip: `${tips}`,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
export function mappingItem(label: string, value: string, color = 'bg-info') {
|
||||
return {
|
||||
label: label,
|
||||
value: value,
|
||||
color: color,
|
||||
}
|
||||
}
|
||||
|
||||
export function mappingField(field: string, mapping: Array<Record<string, string>>) {
|
||||
let mapData: Record<string, string> = {
|
||||
'*': `<span class='label bg-gray-300'>\${${field}}</span>`,
|
||||
}
|
||||
mapping.forEach(item => {
|
||||
mapData[item['value']] = `<span class='label ${item['color']}'>${item['label']}</span>`
|
||||
})
|
||||
return {
|
||||
type: 'mapping',
|
||||
value: `\${${field}}`,
|
||||
map: mapData,
|
||||
}
|
||||
}
|
||||
|
||||
export function filterableField(mapping: Array<Record<string, any>>, multiple = false) {
|
||||
return {
|
||||
multiple: multiple,
|
||||
options: [
|
||||
...mapping,
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
export function time(field: string) {
|
||||
return {
|
||||
width: 150,
|
||||
align: 'center',
|
||||
type: 'tpl',
|
||||
tpl: `\${IF(${field}, DATETOSTR(${field}, 'YYYY-MM-DD HH:mm:ss'), '/')}`,
|
||||
}
|
||||
}
|
||||
|
||||
export function date(field: string) {
|
||||
return {
|
||||
width: 150,
|
||||
align: 'center',
|
||||
type: 'tpl',
|
||||
tpl: `\${IF(${field}, DATETOSTR(${field}, 'YYYY-MM-DD'), '/')}`,
|
||||
}
|
||||
}
|
||||
|
||||
export function pictureFromIds(field: string) {
|
||||
return `\${ARRAYMAP(${field},id => '${commonInfo.baseUrl}/upload/download/' + id)}`
|
||||
}
|
||||
|
||||
export const formInputFileStaticColumns = [
|
||||
{
|
||||
name: 'filename',
|
||||
label: '文件名',
|
||||
},
|
||||
{
|
||||
type: 'operation',
|
||||
label: '操作',
|
||||
width: 140,
|
||||
buttons: [
|
||||
{
|
||||
type: 'action',
|
||||
label: '预览',
|
||||
level: 'link',
|
||||
icon: 'fas fa-eye',
|
||||
},
|
||||
{
|
||||
type: 'action',
|
||||
label: '下载',
|
||||
level: 'link',
|
||||
icon: 'fa fa-download',
|
||||
actionType: 'ajax',
|
||||
// api: {
|
||||
// ...apiGet('${base}/upload/download/${id}'),
|
||||
// responseType: 'blob',
|
||||
// }
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export function formInputSingleFileStatic(field: string, label: string) {
|
||||
return {
|
||||
visibleOn: '${static}',
|
||||
type: 'control',
|
||||
label: label,
|
||||
required: true,
|
||||
body: {
|
||||
type: 'table',
|
||||
source: `\${${field}|asArray}`,
|
||||
columns: formInputFileStaticColumns,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function formInputMultiFileStatic(field: string, label: string) {
|
||||
return {
|
||||
visibleOn: '${static}',
|
||||
type: 'input-table',
|
||||
label: label,
|
||||
name: field,
|
||||
required: true,
|
||||
resizable: false,
|
||||
columns: formInputFileStaticColumns,
|
||||
}
|
||||
}
|
||||
|
||||
export function remoteOptions(type: string = 'select', name: string) {
|
||||
return {
|
||||
type: type,
|
||||
source: `get:${commonInfo.baseUrl}/constants/options/${name}`,
|
||||
}
|
||||
}
|
||||
|
||||
export function remoteMappings(name: string, field: string) {
|
||||
return {
|
||||
type: 'mapping',
|
||||
source: `get:${commonInfo.baseUrl}/constants/mappings/${name}/${field}`,
|
||||
}
|
||||
}
|
||||
3
client/src/vite-env.d.ts
vendored
Normal file
3
client/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare const __APP_VERSION__: string
|
||||
31
client/tsconfig.json
Normal file
31
client/tsconfig.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": [
|
||||
"ESNext",
|
||||
"DOM",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
||||
39
client/vite.config.ts
Normal file
39
client/vite.config.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import react from '@vitejs/plugin-react-swc'
|
||||
import {defineConfig, type UserConfig} from 'vite'
|
||||
import obfuscatorPlugin from 'vite-plugin-javascript-obfuscator'
|
||||
// @ts-ignore
|
||||
import packageJson from './package.json'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig(({mode}) => {
|
||||
let config: UserConfig = {
|
||||
define: {
|
||||
__APP_VERSION__: JSON.stringify(packageJson.version) ?? '0.0.0',
|
||||
},
|
||||
plugins: [
|
||||
react(),
|
||||
obfuscatorPlugin({
|
||||
apply: config => config['mode'] === 'production',
|
||||
options: {
|
||||
compact: true,
|
||||
controlFlowFlattening: true,
|
||||
controlFlowFlatteningThreshold: 0.75,
|
||||
deadCodeInjection: true,
|
||||
deadCodeInjectionThreshold: 0.4,
|
||||
debugProtection: false,
|
||||
disableConsoleOutput: true,
|
||||
identifierNamesGenerator: 'hexadecimal',
|
||||
renameGlobals: false,
|
||||
stringArrayRotate: true,
|
||||
selfDefending: true,
|
||||
stringArray: true,
|
||||
stringArrayEncoding: ['base64'],
|
||||
stringArrayThreshold: 0.75,
|
||||
transformObjectKeys: true,
|
||||
unicodeEscapeSequence: false,
|
||||
},
|
||||
}),
|
||||
],
|
||||
}
|
||||
return config
|
||||
})
|
||||
122
pom.xml
Normal file
122
pom.xml
Normal file
@@ -0,0 +1,122 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.lanyuanxiaoyao</groupId>
|
||||
<artifactId>bookstore</artifactId>
|
||||
<version>1.0.0</version>
|
||||
|
||||
<properties>
|
||||
<java.version>21</java.version>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
<spring-boot.version>3.5.0</spring-boot.version>
|
||||
<spring-cloud.version>2025.0.0</spring-cloud.version>
|
||||
<hibernate.version>6.6.15.Final</hibernate.version>
|
||||
<querydsl.version>7.0</querydsl.version>
|
||||
|
||||
<hutool.version>5.8.39</hutool.version>
|
||||
<liteflow.version>2.15.0</liteflow.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.lanyuanxiaoyao</groupId>
|
||||
<artifactId>spring-boot-service-template</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>${hutool.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.yomahub</groupId>
|
||||
<artifactId>liteflow-spring-boot-starter</artifactId>
|
||||
<version>${liteflow.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<!-- spring boot 相关依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-dependencies</artifactId>
|
||||
<version>${spring-cloud.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.14.0</version>
|
||||
<configuration>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.36</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-jpamodelgen</artifactId>
|
||||
<version>${hibernate.version}</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>io.github.openfeign.querydsl</groupId>
|
||||
<artifactId>querydsl-apt</artifactId>
|
||||
<version>${querydsl.version}</version>
|
||||
<classifier>jpa</classifier>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>jakarta.persistence</groupId>
|
||||
<artifactId>jakarta.persistence-api</artifactId>
|
||||
<version>3.2.0</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
<compilerArgs>
|
||||
<arg>-Aquerydsl.entityAccessors=true</arg>
|
||||
<arg>-Aquerydsl.createDefaultVariable=true</arg>
|
||||
</compilerArgs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.lanyuanxiaoyao.bookstore;
|
||||
|
||||
import com.blinkfox.fenix.EnableFenix;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||
|
||||
/**
|
||||
* 启动类
|
||||
*
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250922
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@EnableJpaAuditing
|
||||
@EnableFenix
|
||||
@EnableConfigurationProperties
|
||||
public class BookStoreApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(BookStoreApplication.class, args);
|
||||
}
|
||||
}
|
||||
11
src/main/java/com/lanyuanxiaoyao/bookstore/Constants.java
Normal file
11
src/main/java/com/lanyuanxiaoyao/bookstore/Constants.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package com.lanyuanxiaoyao.bookstore;
|
||||
|
||||
/**
|
||||
* 静态变量
|
||||
*
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250922
|
||||
*/
|
||||
public interface Constants {
|
||||
String DATABASE_PREFIX = "bookstore_";
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.lanyuanxiaoyao.bookstore.configuration;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
|
||||
/**
|
||||
* 网络配置
|
||||
*
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250922
|
||||
*/
|
||||
@Configuration
|
||||
public class WebConfiguration {
|
||||
@Bean
|
||||
public CorsFilter corsFilter() {
|
||||
CorsConfiguration configuration = new CorsConfiguration();
|
||||
configuration.setAllowCredentials(true);
|
||||
configuration.addAllowedOriginPattern("*");
|
||||
configuration.addAllowedHeader("*");
|
||||
configuration.addAllowedMethod("*");
|
||||
configuration.setMaxAge(7200L);
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", configuration);
|
||||
return new CorsFilter(source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.lanyuanxiaoyao.bookstore.controller;
|
||||
|
||||
import com.lanyuanxiaoyao.bookstore.entity.Book;
|
||||
import com.lanyuanxiaoyao.bookstore.entity.vo.Option;
|
||||
import com.lanyuanxiaoyao.bookstore.service.BookService;
|
||||
import com.lanyuanxiaoyao.service.template.controller.SimpleControllerSupport;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("book")
|
||||
public class BookController extends SimpleControllerSupport<Book, BookController.SaveItem, BookController.DetailItem, BookController.DetailItem> {
|
||||
private final BookService bookService;
|
||||
|
||||
public BookController(BookService service) {
|
||||
super(service);
|
||||
this.bookService = service;
|
||||
}
|
||||
|
||||
@GetMapping("tags")
|
||||
public List<Option> tags() {
|
||||
return bookService.tags();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Function<SaveItem, Book> saveItemMapper() {
|
||||
return item -> {
|
||||
Book book = new Book();
|
||||
book.setId(item.id());
|
||||
book.setName(item.name());
|
||||
book.setAuthor(item.author());
|
||||
book.setDescription(item.description());
|
||||
book.setSource(item.source());
|
||||
book.setTags(item.tags());
|
||||
return book;
|
||||
};
|
||||
}
|
||||
|
||||
private DetailItem toDetailItem(Book book) {
|
||||
return new DetailItem(
|
||||
book.getId(),
|
||||
book.getName(),
|
||||
book.getAuthor(),
|
||||
book.getDescription(),
|
||||
book.getSource(),
|
||||
book.getTags(),
|
||||
book.getCreatedTime(),
|
||||
book.getModifiedTime()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Function<Book, DetailItem> listItemMapper() {
|
||||
return this::toDetailItem;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Function<Book, DetailItem> detailItemMapper() {
|
||||
return this::toDetailItem;
|
||||
}
|
||||
|
||||
public record SaveItem(
|
||||
Long id,
|
||||
String name,
|
||||
String author,
|
||||
String description,
|
||||
String source,
|
||||
Set<String> tags
|
||||
) {
|
||||
}
|
||||
|
||||
public record DetailItem(
|
||||
Long id,
|
||||
String name,
|
||||
String author,
|
||||
String description,
|
||||
String source,
|
||||
Set<String> tags,
|
||||
LocalDateTime createdTime,
|
||||
LocalDateTime modifiedTime
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
package com.lanyuanxiaoyao.bookstore.controller;
|
||||
|
||||
import com.lanyuanxiaoyao.bookstore.entity.Chapter;
|
||||
import com.lanyuanxiaoyao.bookstore.entity.vo.Option;
|
||||
import com.lanyuanxiaoyao.bookstore.service.BookService;
|
||||
import com.lanyuanxiaoyao.bookstore.service.ChapterService;
|
||||
import com.lanyuanxiaoyao.bookstore.service.LineService;
|
||||
import com.lanyuanxiaoyao.service.template.controller.GlobalResponse;
|
||||
import com.lanyuanxiaoyao.service.template.controller.SimpleControllerSupport;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("chapter")
|
||||
public class ChapterController extends SimpleControllerSupport<Chapter, ChapterController.SaveItem, ChapterController.DetailItem, ChapterController.DetailItem> {
|
||||
private final ChapterService chapterService;
|
||||
private final BookService bookService;
|
||||
private final LineService lineService;
|
||||
|
||||
public ChapterController(ChapterService service, BookService bookService, LineService lineService) {
|
||||
super(service);
|
||||
this.chapterService = service;
|
||||
this.bookService = bookService;
|
||||
this.lineService = lineService;
|
||||
}
|
||||
|
||||
@GetMapping("chapters")
|
||||
public List<Option> chapters() {
|
||||
return chapterService.chapters();
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Throwable.class)
|
||||
@PostMapping("save_with_content")
|
||||
public GlobalResponse<Object> saveWithContent(@RequestBody SaveWithContentItem item) {
|
||||
if (SaveWithContentItem.Mode.CREATE.equals(item.mode())) {
|
||||
var chapter = new Chapter();
|
||||
chapter.setSequence(chapterService.latestSequence(item.bookId()));
|
||||
chapter.setName(item.name());
|
||||
chapter.setDescription(item.description());
|
||||
chapter.setBook(bookService.detailOrThrow(item.bookId()));
|
||||
var chapterId = chapterService.save(chapter);
|
||||
lineService.load(chapterId, item.content());
|
||||
} else if (SaveWithContentItem.Mode.OVERRIDE.equals(item.mode())) {
|
||||
var chapter = chapterService.detailOrThrow(item.id());
|
||||
chapter.setName(item.name());
|
||||
chapter.setDescription(item.description());
|
||||
var chapterId = chapterService.save(chapter);
|
||||
lineService.empty(chapterId);
|
||||
lineService.load(chapterId, item.content());
|
||||
} else {
|
||||
return GlobalResponse.responseError("Invalid mode");
|
||||
}
|
||||
return GlobalResponse.responseSuccess();
|
||||
}
|
||||
|
||||
@GetMapping("generate_sequence")
|
||||
public GlobalResponse<Object> generateSequence() {
|
||||
chapterService.generateSequence();
|
||||
return GlobalResponse.responseSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Function<SaveItem, Chapter> saveItemMapper() {
|
||||
return item -> {
|
||||
var chapter = new Chapter();
|
||||
chapter.setId(item.id());
|
||||
chapter.setSequence(chapterService.latestSequence(item.bookId()));
|
||||
chapter.setName(item.name());
|
||||
chapter.setDescription(item.description());
|
||||
chapter.setBook(bookService.detailOrThrow(item.bookId()));
|
||||
return chapter;
|
||||
};
|
||||
}
|
||||
|
||||
private DetailItem toDetailItem(Chapter chapter) {
|
||||
return new DetailItem(
|
||||
chapter.getId(),
|
||||
chapter.getSequence(),
|
||||
chapter.getName(),
|
||||
chapter.getDescription(),
|
||||
chapter.getCreatedTime(),
|
||||
chapter.getModifiedTime()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Function<Chapter, DetailItem> listItemMapper() {
|
||||
return this::toDetailItem;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Function<Chapter, DetailItem> detailItemMapper() {
|
||||
return this::toDetailItem;
|
||||
}
|
||||
|
||||
public record SaveItem(
|
||||
Long id,
|
||||
Long bookId,
|
||||
String name,
|
||||
String description
|
||||
) {
|
||||
}
|
||||
|
||||
public record SaveWithContentItem(
|
||||
Long id,
|
||||
Long bookId,
|
||||
String name,
|
||||
String description,
|
||||
Mode mode,
|
||||
String content
|
||||
) {
|
||||
public SaveItem toSaveItem() {
|
||||
return new SaveItem(null, bookId, name, description);
|
||||
}
|
||||
|
||||
public enum Mode {
|
||||
CREATE, OVERRIDE
|
||||
}
|
||||
}
|
||||
|
||||
public record DetailItem(
|
||||
Long id,
|
||||
Long sequence,
|
||||
String name,
|
||||
String description,
|
||||
LocalDateTime createdTime,
|
||||
LocalDateTime modifiedTime
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.lanyuanxiaoyao.bookstore.controller;
|
||||
|
||||
import com.lanyuanxiaoyao.bookstore.entity.Line;
|
||||
import com.lanyuanxiaoyao.bookstore.service.LineService;
|
||||
import com.lanyuanxiaoyao.service.template.controller.GlobalResponse;
|
||||
import com.lanyuanxiaoyao.service.template.controller.SimpleControllerSupport;
|
||||
import java.util.function.Function;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("line")
|
||||
public class LineController extends SimpleControllerSupport<Line, LineController.SaveItem, LineController.DetailItem, LineController.DetailItem> {
|
||||
private final LineService lineService;
|
||||
|
||||
public LineController(LineService service) {
|
||||
super(service);
|
||||
this.lineService = service;
|
||||
}
|
||||
|
||||
@PostMapping("load")
|
||||
public GlobalResponse<Object> load(@RequestBody LineController.LoadItem item) {
|
||||
lineService.load(item.chapterId(), item.content());
|
||||
return GlobalResponse.responseSuccess();
|
||||
}
|
||||
|
||||
@GetMapping("generate_sequence")
|
||||
public GlobalResponse<Object> generateSequence() {
|
||||
lineService.generateSequence();
|
||||
return GlobalResponse.responseSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Function<SaveItem, Line> saveItemMapper() {
|
||||
return item -> {
|
||||
var line = lineService.detailOrThrow(item.id());
|
||||
line.setText(item.text());
|
||||
return line;
|
||||
};
|
||||
}
|
||||
|
||||
private DetailItem toDetailItem(Line line) {
|
||||
return new DetailItem(
|
||||
line.getId(),
|
||||
line.getSequence(),
|
||||
line.getText()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Function<Line, DetailItem> listItemMapper() {
|
||||
return this::toDetailItem;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Function<Line, DetailItem> detailItemMapper() {
|
||||
return this::toDetailItem;
|
||||
}
|
||||
|
||||
public record SaveItem(
|
||||
Long id,
|
||||
Long chapterId,
|
||||
String text
|
||||
) {
|
||||
}
|
||||
|
||||
public record LoadItem(
|
||||
Long chapterId,
|
||||
String content
|
||||
) {
|
||||
}
|
||||
|
||||
public record DetailItem(
|
||||
Long id,
|
||||
Long sequence,
|
||||
String text
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
53
src/main/java/com/lanyuanxiaoyao/bookstore/entity/Book.java
Normal file
53
src/main/java/com/lanyuanxiaoyao/bookstore/entity/Book.java
Normal file
@@ -0,0 +1,53 @@
|
||||
package com.lanyuanxiaoyao.bookstore.entity;
|
||||
|
||||
import com.lanyuanxiaoyao.bookstore.Constants;
|
||||
import com.lanyuanxiaoyao.service.template.entity.SimpleEntity;
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.CollectionTable;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.ConstraintMode;
|
||||
import jakarta.persistence.ElementCollection;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EntityListeners;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.ForeignKey;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
import java.util.Set;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
import org.hibernate.annotations.DynamicInsert;
|
||||
import org.hibernate.annotations.DynamicUpdate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
/**
|
||||
* 书籍
|
||||
*
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250922
|
||||
*/
|
||||
@Setter
|
||||
@Getter
|
||||
@ToString(callSuper = true)
|
||||
@FieldNameConstants
|
||||
@Entity
|
||||
@DynamicUpdate
|
||||
@DynamicInsert
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
@Table(name = Constants.DATABASE_PREFIX + "book")
|
||||
public class Book extends SimpleEntity {
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
private String author;
|
||||
private String description;
|
||||
private String source;
|
||||
@ElementCollection(fetch = FetchType.EAGER)
|
||||
@CollectionTable(name = Constants.DATABASE_PREFIX + "book_tags", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
|
||||
private Set<String> tags;
|
||||
|
||||
@OneToMany(cascade = CascadeType.ALL, mappedBy = "book")
|
||||
@ToString.Exclude
|
||||
private Set<Chapter> chapters;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.lanyuanxiaoyao.bookstore.entity;
|
||||
|
||||
import com.lanyuanxiaoyao.bookstore.Constants;
|
||||
import com.lanyuanxiaoyao.service.template.entity.SimpleEntity;
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.ConstraintMode;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EntityListeners;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.ForeignKey;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
import java.util.Set;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
import org.hibernate.annotations.DynamicInsert;
|
||||
import org.hibernate.annotations.DynamicUpdate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
/**
|
||||
* 章节
|
||||
*
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250922
|
||||
*/
|
||||
@Setter
|
||||
@Getter
|
||||
@ToString(callSuper = true)
|
||||
@FieldNameConstants
|
||||
@Entity
|
||||
@DynamicUpdate
|
||||
@DynamicInsert
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
@Table(name = Constants.DATABASE_PREFIX + "chapter")
|
||||
public class Chapter extends SimpleEntity {
|
||||
@Column(nullable = false)
|
||||
private Long sequence;
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
private String description;
|
||||
|
||||
@ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
|
||||
@JoinColumn(nullable = false, foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
|
||||
@ToString.Exclude
|
||||
private Book book;
|
||||
@OneToMany(cascade = CascadeType.ALL, mappedBy = "chapter")
|
||||
@ToString.Exclude
|
||||
private Set<Line> content;
|
||||
}
|
||||
53
src/main/java/com/lanyuanxiaoyao/bookstore/entity/Line.java
Normal file
53
src/main/java/com/lanyuanxiaoyao/bookstore/entity/Line.java
Normal file
@@ -0,0 +1,53 @@
|
||||
package com.lanyuanxiaoyao.bookstore.entity;
|
||||
|
||||
import com.lanyuanxiaoyao.bookstore.Constants;
|
||||
import com.lanyuanxiaoyao.service.template.entity.SimpleEntity;
|
||||
import jakarta.persistence.Basic;
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.ConstraintMode;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EntityListeners;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.ForeignKey;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.Lob;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
import org.hibernate.annotations.DynamicInsert;
|
||||
import org.hibernate.annotations.DynamicUpdate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
/**
|
||||
* 书籍行
|
||||
*
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250922
|
||||
*/
|
||||
@Setter
|
||||
@Getter
|
||||
@ToString(callSuper = true)
|
||||
@FieldNameConstants
|
||||
@Entity
|
||||
@DynamicUpdate
|
||||
@DynamicInsert
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
@Table(name = Constants.DATABASE_PREFIX + "line")
|
||||
public class Line extends SimpleEntity {
|
||||
@Column(nullable = false)
|
||||
private Long sequence;
|
||||
@Lob
|
||||
@Basic(fetch = FetchType.LAZY)
|
||||
@ToString.Exclude
|
||||
@Column(nullable = false)
|
||||
private String text;
|
||||
|
||||
@ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
|
||||
@JoinColumn(nullable = false, foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
|
||||
@ToString.Exclude
|
||||
private Chapter chapter;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.lanyuanxiaoyao.bookstore.entity.vo;
|
||||
|
||||
/**
|
||||
* Amis Option
|
||||
*
|
||||
* @author 选项
|
||||
* @version 20250923
|
||||
*/
|
||||
public record Option(String label, Object value) {
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.lanyuanxiaoyao.bookstore.repository;
|
||||
|
||||
import com.lanyuanxiaoyao.bookstore.entity.Book;
|
||||
import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
|
||||
import java.util.List;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface BookRepository extends SimpleRepository<Book> {
|
||||
@Query("select distinct book.tags from Book book")
|
||||
List<String> findDistinctTags();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.lanyuanxiaoyao.bookstore.repository;
|
||||
|
||||
import com.lanyuanxiaoyao.bookstore.entity.Chapter;
|
||||
import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface ChapterRepository extends SimpleRepository<Chapter> {
|
||||
@Query("select max(chapter.sequence) from Chapter chapter")
|
||||
Optional<Long> findMaxSequence(Long bookId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.lanyuanxiaoyao.bookstore.repository;
|
||||
|
||||
import com.lanyuanxiaoyao.bookstore.entity.Line;
|
||||
import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface LineRepository extends SimpleRepository<Line> {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.lanyuanxiaoyao.bookstore.service;
|
||||
|
||||
import com.lanyuanxiaoyao.bookstore.entity.Book;
|
||||
import com.lanyuanxiaoyao.bookstore.entity.vo.Option;
|
||||
import com.lanyuanxiaoyao.bookstore.repository.BookRepository;
|
||||
import com.lanyuanxiaoyao.service.template.service.SimpleServiceSupport;
|
||||
import java.util.List;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class BookService extends SimpleServiceSupport<Book> {
|
||||
private final BookRepository bookRepository;
|
||||
|
||||
public BookService(BookRepository repository) {
|
||||
super(repository);
|
||||
this.bookRepository = repository;
|
||||
}
|
||||
|
||||
public List<Option> tags() {
|
||||
return bookRepository.findDistinctTags()
|
||||
.stream()
|
||||
.map(tag -> new Option(tag, tag))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.lanyuanxiaoyao.bookstore.service;
|
||||
|
||||
import com.lanyuanxiaoyao.bookstore.entity.Chapter;
|
||||
import com.lanyuanxiaoyao.bookstore.entity.vo.Option;
|
||||
import com.lanyuanxiaoyao.bookstore.repository.ChapterRepository;
|
||||
import com.lanyuanxiaoyao.service.template.service.SimpleServiceSupport;
|
||||
import java.util.List;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ChapterService extends SimpleServiceSupport<Chapter> {
|
||||
private final ChapterRepository chapterRepository;
|
||||
|
||||
public ChapterService(ChapterRepository repository) {
|
||||
super(repository);
|
||||
this.chapterRepository = repository;
|
||||
}
|
||||
|
||||
public List<Option> chapters() {
|
||||
return list()
|
||||
.stream()
|
||||
.map(chapter -> new Option(chapter.getName(), chapter.getId()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
public Long latestSequence(Long bookId) {
|
||||
return chapterRepository.findMaxSequence(bookId).orElse(0L);
|
||||
}
|
||||
|
||||
public void generateSequence() {
|
||||
var chapters = chapterRepository.findAll(Sort.by(Sort.Direction.ASC, Chapter.Fields.sequence));
|
||||
for (int index = 0; index < chapters.size(); index++) {
|
||||
chapters.get(index).setSequence((long) index);
|
||||
}
|
||||
chapterRepository.saveAll(chapters);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.lanyuanxiaoyao.bookstore.service;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.lanyuanxiaoyao.bookstore.entity.Line;
|
||||
import com.lanyuanxiaoyao.bookstore.repository.LineRepository;
|
||||
import com.lanyuanxiaoyao.service.template.entity.IdOnlyEntity;
|
||||
import com.lanyuanxiaoyao.service.template.service.SimpleServiceSupport;
|
||||
import java.util.stream.Stream;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class LineService extends SimpleServiceSupport<Line> {
|
||||
private final LineRepository lineRepository;
|
||||
|
||||
private final ChapterService chapterService;
|
||||
|
||||
public LineService(LineRepository repository, ChapterService chapterService) {
|
||||
super(repository);
|
||||
this.lineRepository = repository;
|
||||
this.chapterService = chapterService;
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Throwable.class)
|
||||
public void load(Long chapterId, String text) {
|
||||
var chapter = chapterService.detailOrThrow(chapterId);
|
||||
var lines = Stream.of(text.split("\n"))
|
||||
.map(StrUtil::trimToNull)
|
||||
.filter(ObjectUtil::isNotNull)
|
||||
.toList();
|
||||
for (int index = 0; index < lines.size(); index++) {
|
||||
var line = new Line();
|
||||
line.setSequence((long) index);
|
||||
line.setText(lines.get(index));
|
||||
line.setChapter(chapter);
|
||||
lineRepository.save(line);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Throwable.class)
|
||||
public void empty(Long chapterId) {
|
||||
repository.delete((root, query, builder) -> builder.equal(root.get(Line.Fields.chapter).get(IdOnlyEntity.Fields.id), chapterId));
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Throwable.class)
|
||||
public void generateSequence() {
|
||||
var lines = lineRepository.findAll(Sort.by(Sort.Direction.ASC, Line.Fields.sequence));
|
||||
for (int index = 0; index < lines.size(); index++) {
|
||||
lines.get(index).setSequence((long) index);
|
||||
}
|
||||
lineRepository.saveAll(lines);
|
||||
}
|
||||
}
|
||||
|
||||
28
src/main/resources/application.yml
Normal file
28
src/main/resources/application.yml
Normal file
@@ -0,0 +1,28 @@
|
||||
server:
|
||||
port: 27891
|
||||
compression:
|
||||
enabled: true
|
||||
spring:
|
||||
application:
|
||||
name: bookstore
|
||||
mvc:
|
||||
async:
|
||||
request-timeout: 3600000
|
||||
datasource:
|
||||
url: jdbc:h2:file:./bookstore;DB_CLOSE_ON_EXIT=TRUE
|
||||
username: bookstore
|
||||
password: bookstore
|
||||
driver-class-name: org.h2.Driver
|
||||
jpa:
|
||||
generate-ddl: false
|
||||
hibernate:
|
||||
ddl-auto: update
|
||||
main:
|
||||
banner-mode: off
|
||||
fenix:
|
||||
print-banner: false
|
||||
liteflow:
|
||||
enable: false
|
||||
print-banner: false
|
||||
check-node-exists: false
|
||||
rule-source: flow.xml
|
||||
6
src/main/resources/flow.xml
Normal file
6
src/main/resources/flow.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!DOCTYPE flow PUBLIC "liteflow" "https://liteflow.cc/liteflow.dtd">
|
||||
<flow>
|
||||
<chain name="empty">
|
||||
</chain>
|
||||
</flow>
|
||||
24
src/main/resources/logback-spring.xml
Normal file
24
src/main/resources/logback-spring.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<configuration>
|
||||
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
|
||||
|
||||
<conversionRule conversionWord="clr" class="org.springframework.boot.logging.logback.ColorConverter"/>
|
||||
<conversionRule conversionWord="wex" class="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
|
||||
<conversionRule conversionWord="wEx" class="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
|
||||
|
||||
<springProperty scope="context" name="LOGGING_PARENT" source="logging.parent"/>
|
||||
<springProperty scope="context" name="APP_NAME" source="spring.application.name"/>
|
||||
|
||||
<appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %clr(%5p) %clr([${HOSTNAME}]){yellow} %clr([%t]){magenta} %clr(%logger{40}){cyan}: %m%n%wEx
|
||||
</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="com.lanyuanxiaoyao.leopard" level="INFO"/>
|
||||
<logger name="org.hibernate.SQL" level="DEBUG"/>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="Console"/>
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.lanyuanxiaoyao.bookstore;
|
||||
|
||||
import com.lanyuanxiaoyao.service.template.Helper;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 创建基础文件
|
||||
*
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250922
|
||||
*/
|
||||
public class BasicFilesGenerator {
|
||||
public static void main(String[] args) throws IOException {
|
||||
Helper.generateBasicFiles(
|
||||
"com.lanyuanxiaoyao.bookstore.entity",
|
||||
"com.lanyuanxiaoyao.bookstore",
|
||||
"/Users/lanyuanxiaoyao/Project/bookstore_new/src/main/java/com/lanyuanxiaoyao/bookstore",
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user