feat: 完成基本功能
This commit is contained in:
28
.gitignore
vendored
28
.gitignore
vendored
@@ -290,29 +290,5 @@ Network Trash Folder
|
|||||||
Temporary Items
|
Temporary Items
|
||||||
.apdisk
|
.apdisk
|
||||||
|
|
||||||
### Kotlin template
|
### Custom
|
||||||
# Compiled class file
|
*.db
|
||||||
*.class
|
|
||||||
|
|
||||||
# Log file
|
|
||||||
*.log
|
|
||||||
|
|
||||||
# BlueJ files
|
|
||||||
*.ctxt
|
|
||||||
|
|
||||||
# Mobile Tools for Java (J2ME)
|
|
||||||
.mtj.tmp/
|
|
||||||
|
|
||||||
# Package Files #
|
|
||||||
*.jar
|
|
||||||
*.war
|
|
||||||
*.nar
|
|
||||||
*.ear
|
|
||||||
*.zip
|
|
||||||
*.tar.gz
|
|
||||||
*.rar
|
|
||||||
|
|
||||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
|
||||||
hs_err_pid*
|
|
||||||
replay_pid*
|
|
||||||
|
|
||||||
|
|||||||
10
.idea/compiler.xml
generated
10
.idea/compiler.xml
generated
@@ -5,18 +5,10 @@
|
|||||||
<profile name="Gradle Imported" enabled="true">
|
<profile name="Gradle Imported" enabled="true">
|
||||||
<outputRelativeToContentRoot value="true" />
|
<outputRelativeToContentRoot value="true" />
|
||||||
<processorPath useClasspath="false">
|
<processorPath useClasspath="false">
|
||||||
<entry name="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-configuration-processor/3.4.1/8dfcdae21f559be9c8a4d6d515e77cfd1d9c06a8/spring-boot-configuration-processor-3.4.1.jar" />
|
<entry name="$USER_HOME$/scoop/apps/gradle/current/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-configuration-processor/3.4.1/8dfcdae21f559be9c8a4d6d515e77cfd1d9c06a8/spring-boot-configuration-processor-3.4.1.jar" />
|
||||||
<entry name="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.projectlombok/lombok/1.18.36/5a30490a6e14977d97d9c73c924c1f1b5311ea95/lombok-1.18.36.jar" />
|
|
||||||
</processorPath>
|
</processorPath>
|
||||||
<module name="bookstore.main" />
|
<module name="bookstore.main" />
|
||||||
</profile>
|
</profile>
|
||||||
<profile name="Gradle Imported" enabled="true">
|
|
||||||
<outputRelativeToContentRoot value="true" />
|
|
||||||
<processorPath useClasspath="false">
|
|
||||||
<entry name="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.projectlombok/lombok/1.18.36/5a30490a6e14977d97d9c73c924c1f1b5311ea95/lombok-1.18.36.jar" />
|
|
||||||
</processorPath>
|
|
||||||
<module name="bookstore.test" />
|
|
||||||
</profile>
|
|
||||||
</annotationProcessing>
|
</annotationProcessing>
|
||||||
<bytecodeTargetLevel target="17" />
|
<bytecodeTargetLevel target="17" />
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ plugins {
|
|||||||
kotlin("plugin.jpa") version kotlinVersion
|
kotlin("plugin.jpa") version kotlinVersion
|
||||||
kotlin("plugin.allopen") version kotlinVersion
|
kotlin("plugin.allopen") version kotlinVersion
|
||||||
kotlin("plugin.spring") version kotlinVersion
|
kotlin("plugin.spring") version kotlinVersion
|
||||||
kotlin("plugin.lombok") version kotlinVersion
|
|
||||||
id("io.freefair.lombok") version "8.11"
|
|
||||||
id("org.springframework.boot") version "3.4.1"
|
id("org.springframework.boot") version "3.4.1"
|
||||||
id("io.spring.dependency-management") version "1.1.7"
|
id("io.spring.dependency-management") version "1.1.7"
|
||||||
}
|
}
|
||||||
@@ -20,10 +18,8 @@ java {
|
|||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
maven {
|
maven("https://maven.aliyun.com/repository/central")
|
||||||
url = uri("http://localhost:3105/threepartrepo")
|
mavenCentral()
|
||||||
isAllowInsecureProtocol = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|||||||
@@ -3,15 +3,34 @@ package com.lanyuanxiaoyao.bookstore
|
|||||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||||
import org.springframework.boot.runApplication
|
import org.springframework.boot.runApplication
|
||||||
import org.springframework.context.annotation.Bean
|
import org.springframework.context.annotation.Bean
|
||||||
|
import org.springframework.context.annotation.Configuration
|
||||||
|
import org.springframework.data.jpa.repository.config.EnableJpaAuditing
|
||||||
|
import org.springframework.web.cors.CorsConfiguration
|
||||||
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource
|
||||||
|
import org.springframework.web.filter.CorsFilter
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
|
@EnableJpaAuditing
|
||||||
class BookstoreApplication
|
class BookstoreApplication
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
runApplication<BookstoreApplication>(*args)
|
runApplication<BookstoreApplication>(*args)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
class WebConfiguration {
|
||||||
@Bean
|
@Bean
|
||||||
fun bookstore(): List<Book> {
|
fun corsFilter(): CorsFilter {
|
||||||
return emptyList()
|
val config = CorsConfiguration().apply {
|
||||||
|
allowCredentials = true
|
||||||
|
addAllowedOriginPattern("*")
|
||||||
|
addAllowedHeader("*")
|
||||||
|
addAllowedMethod("*")
|
||||||
|
maxAge = 3600L
|
||||||
|
}
|
||||||
|
val source = UrlBasedCorsConfigurationSource().apply {
|
||||||
|
registerCorsConfiguration("/**", config)
|
||||||
|
}
|
||||||
|
return CorsFilter(source)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,253 @@
|
|||||||
package com.lanyuanxiaoyao.bookstore
|
package com.lanyuanxiaoyao.bookstore
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j
|
import cn.hutool.core.util.IdUtil
|
||||||
import org.springframework.web.bind.annotation.RequestMapping
|
import jakarta.annotation.Resource
|
||||||
import org.springframework.web.bind.annotation.RestController
|
import jakarta.transaction.Transactional
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.springframework.data.domain.PageRequest
|
||||||
|
import org.springframework.web.bind.annotation.*
|
||||||
|
|
||||||
|
data class PageResponse<E>(
|
||||||
|
val items: List<E>,
|
||||||
|
val total: Long,
|
||||||
|
)
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("book")
|
@RequestMapping("book")
|
||||||
class BookController {
|
class BookController {
|
||||||
|
private val log = LoggerFactory.getLogger(javaClass)
|
||||||
|
|
||||||
}
|
@Resource
|
||||||
|
private lateinit var bookRepository: BookRepository
|
||||||
|
|
||||||
|
@GetMapping("list")
|
||||||
|
fun list(
|
||||||
|
@RequestParam("page", defaultValue = "1") page: Int,
|
||||||
|
@RequestParam("size", defaultValue = "10") size: Int
|
||||||
|
): PageResponse<ViewItem> {
|
||||||
|
val pageable = bookRepository.findAll(PageRequest.of(0.coerceAtLeast(page - 1), size))
|
||||||
|
return PageResponse(
|
||||||
|
pageable.content.map { ViewItem(it) },
|
||||||
|
pageable.totalElements,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@PostMapping("save")
|
||||||
|
fun save(@RequestBody item: ViewItem) {
|
||||||
|
bookRepository.save(
|
||||||
|
Book(
|
||||||
|
bookId = item.bookId ?: IdUtil.fastSimpleUUID(),
|
||||||
|
name = item.name,
|
||||||
|
author = item.author,
|
||||||
|
description = item.description,
|
||||||
|
source = item.source,
|
||||||
|
tags = item.tags?.toMutableSet() ?: mutableSetOf()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("detail/{bookId}")
|
||||||
|
fun detail(@PathVariable("bookId") bookId: String): ViewItem {
|
||||||
|
return bookRepository.findById(bookId).orElseThrow().let { ViewItem(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@GetMapping("remove/{bookId}")
|
||||||
|
fun remove(@PathVariable("bookId") bookId: String) {
|
||||||
|
bookRepository.deleteById(bookId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("tags")
|
||||||
|
fun tags(): List<String> {
|
||||||
|
return bookRepository.findAllTag()
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ViewItem(
|
||||||
|
val bookId: String?,
|
||||||
|
val name: String,
|
||||||
|
val author: String?,
|
||||||
|
val description: String,
|
||||||
|
val source: String?,
|
||||||
|
val tags: Set<String>?,
|
||||||
|
) {
|
||||||
|
constructor(book: Book) : this(
|
||||||
|
book.bookId,
|
||||||
|
book.name,
|
||||||
|
book.author,
|
||||||
|
book.description,
|
||||||
|
book.source,
|
||||||
|
book.tags,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("chapter")
|
||||||
|
class ChapterController {
|
||||||
|
private val log = LoggerFactory.getLogger(javaClass)
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private lateinit var bookRepository: BookRepository
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private lateinit var chapterRepository: ChapterRepository
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private lateinit var lineRepository: LineRepository
|
||||||
|
|
||||||
|
@GetMapping("list/{bookId}")
|
||||||
|
fun list(
|
||||||
|
@PathVariable("bookId") bookId: String,
|
||||||
|
@RequestParam("page", defaultValue = "1") page: Int,
|
||||||
|
@RequestParam("size", defaultValue = "10") size: Int
|
||||||
|
): PageResponse<ViewItem> {
|
||||||
|
val pageable = chapterRepository.findAll({ root, _, builder ->
|
||||||
|
builder.equal(root.get<Book>("book").get<String>("bookId"), bookId)
|
||||||
|
}, PageRequest.of(0.coerceAtLeast(page - 1), size))
|
||||||
|
return PageResponse(
|
||||||
|
pageable.content.map { ViewItem(it) },
|
||||||
|
pageable.totalElements,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@PostMapping("save/{bookId}")
|
||||||
|
fun save(@PathVariable("bookId") bookId: String, @RequestBody item: ViewItem) {
|
||||||
|
val book = bookRepository.findById(bookId).orElseThrow()
|
||||||
|
chapterRepository.save(
|
||||||
|
Chapter(
|
||||||
|
chapterId = item.chapterId ?: IdUtil.fastSimpleUUID(),
|
||||||
|
name = item.name,
|
||||||
|
sequence = item.sequence,
|
||||||
|
description = item.description,
|
||||||
|
book = book,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@PostMapping("import/{chapterId}")
|
||||||
|
fun import(@PathVariable("chapterId") chapterId: String, @RequestBody item: ImportItem) {
|
||||||
|
val chapter = chapterRepository.findById(chapterId).orElseThrow()
|
||||||
|
var startIndex = chapter.content.size.toLong()
|
||||||
|
if (item.override) {
|
||||||
|
lineRepository.deleteAllInBatch(chapter.content)
|
||||||
|
startIndex = 0
|
||||||
|
}
|
||||||
|
chapter.content = item.text
|
||||||
|
.split("\n")
|
||||||
|
.mapIndexed { index, line ->
|
||||||
|
Line(
|
||||||
|
lineId = IdUtil.fastSimpleUUID(),
|
||||||
|
sequence = startIndex + index,
|
||||||
|
text = line.trim(),
|
||||||
|
chapter = chapter,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.toMutableSet()
|
||||||
|
chapterRepository.save(chapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("detail/{chapterId}")
|
||||||
|
fun detail(@PathVariable("chapterId") chapterId: String): ViewItem {
|
||||||
|
return chapterRepository.findById(chapterId).orElseThrow().let { ViewItem(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@GetMapping("remove/{chapterId}")
|
||||||
|
fun remove(@PathVariable("chapterId") chapterId: String) {
|
||||||
|
chapterRepository.deleteById(chapterId)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ViewItem(
|
||||||
|
val chapterId: String?,
|
||||||
|
val name: String?,
|
||||||
|
val sequence: Int,
|
||||||
|
val description: String?,
|
||||||
|
) {
|
||||||
|
constructor(chapter: Chapter) : this(
|
||||||
|
chapter.chapterId,
|
||||||
|
chapter.name,
|
||||||
|
chapter.sequence,
|
||||||
|
chapter.description,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ImportItem(
|
||||||
|
val override: Boolean = false,
|
||||||
|
val text: String
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("line")
|
||||||
|
class LineController {
|
||||||
|
private val log = LoggerFactory.getLogger(javaClass)
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private lateinit var chapterRepository: ChapterRepository
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private lateinit var lineRepository: LineRepository
|
||||||
|
|
||||||
|
@GetMapping("list/{chapterId}")
|
||||||
|
fun list(
|
||||||
|
@PathVariable("chapterId") chapterId: String,
|
||||||
|
@RequestParam("page", defaultValue = "1") page: Int,
|
||||||
|
@RequestParam("size", defaultValue = "10") size: Int
|
||||||
|
): PageResponse<ViewItem> {
|
||||||
|
val pageable = lineRepository.findAll({ root, _, builder ->
|
||||||
|
builder.equal(root.get<Chapter>("chapter").get<String>("chapterId"), chapterId)
|
||||||
|
}, PageRequest.of(0.coerceAtLeast(page - 1), size))
|
||||||
|
return PageResponse(
|
||||||
|
pageable.content.map { ViewItem(it) },
|
||||||
|
pageable.totalElements,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@PostMapping("save/{lineId}")
|
||||||
|
fun save(@PathVariable("lineId") lineId: String, @RequestBody item: ViewItem) {
|
||||||
|
val chapter = chapterRepository.findById(lineId).orElseThrow()
|
||||||
|
lineRepository.save(
|
||||||
|
Line(
|
||||||
|
lineId = item.lineId ?: IdUtil.fastSimpleUUID(),
|
||||||
|
sequence = item.sequence,
|
||||||
|
text = item.text,
|
||||||
|
chapter = chapter,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@PostMapping("update/{lineId}")
|
||||||
|
fun update(@PathVariable("lineId") lineId: String, @RequestBody item: ViewItem) {
|
||||||
|
val line = lineRepository.findById(lineId).orElseThrow()
|
||||||
|
line.text = item.text
|
||||||
|
lineRepository.save(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("detail/{lineId}")
|
||||||
|
fun detail(@PathVariable("lineId") lineId: String): ViewItem {
|
||||||
|
return lineRepository.findById(lineId).orElseThrow().let { ViewItem(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@GetMapping("remove/{lineId}")
|
||||||
|
fun remove(@PathVariable("lineId") lineId: String) {
|
||||||
|
lineRepository.deleteById(lineId)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ViewItem(
|
||||||
|
val lineId: String?,
|
||||||
|
val sequence: Long,
|
||||||
|
val text: String,
|
||||||
|
) {
|
||||||
|
constructor(line: Line) : this(
|
||||||
|
line.lineId,
|
||||||
|
line.sequence,
|
||||||
|
line.text,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,44 +1,85 @@
|
|||||||
|
@file:Suppress("FunctionName")
|
||||||
|
|
||||||
package com.lanyuanxiaoyao.bookstore
|
package com.lanyuanxiaoyao.bookstore
|
||||||
|
|
||||||
import jakarta.persistence.CascadeType
|
import jakarta.persistence.*
|
||||||
import jakarta.persistence.ConstraintMode
|
import org.hibernate.annotations.DynamicUpdate
|
||||||
import jakarta.persistence.Entity
|
import org.springframework.data.jpa.repository.EntityGraph
|
||||||
import jakarta.persistence.FetchType
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
import jakarta.persistence.ForeignKey
|
import org.springframework.data.jpa.repository.JpaSpecificationExecutor
|
||||||
import jakarta.persistence.Id
|
import org.springframework.data.jpa.repository.Query
|
||||||
import jakarta.persistence.JoinColumn
|
import org.springframework.stereotype.Repository
|
||||||
import jakarta.persistence.Lob
|
|
||||||
import jakarta.persistence.ManyToOne
|
|
||||||
import jakarta.persistence.OneToMany
|
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
|
@DynamicUpdate
|
||||||
|
@NamedEntityGraph(
|
||||||
|
name = "book.list", attributeNodes = [
|
||||||
|
NamedAttributeNode("tags"),
|
||||||
|
NamedAttributeNode("chapters"),
|
||||||
|
]
|
||||||
|
)
|
||||||
class Book(
|
class Book(
|
||||||
@Id
|
@Id
|
||||||
var id: String,
|
var bookId: String,
|
||||||
|
@Column(nullable = false)
|
||||||
var name: String,
|
var name: String,
|
||||||
var author: String,
|
var author: String?,
|
||||||
|
@Column(nullable = false)
|
||||||
var description: String,
|
var description: String,
|
||||||
var tags: List<String>,
|
var source: String?,
|
||||||
@OneToMany(cascade = [CascadeType.REMOVE], fetch = FetchType.LAZY, mappedBy = "book")
|
@ElementCollection(fetch = FetchType.EAGER)
|
||||||
var chapters: List<Chapter>,
|
var tags: MutableSet<String> = mutableSetOf(),
|
||||||
|
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.LAZY, mappedBy = "book")
|
||||||
|
var chapters: MutableSet<Chapter> = mutableSetOf(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
interface BookRepository : JpaRepository<Book, String>, JpaSpecificationExecutor<Book> {
|
||||||
|
@EntityGraph("book.list")
|
||||||
|
override fun findAll(): List<Book>
|
||||||
|
|
||||||
|
@Query("select distinct book.tags from Book book")
|
||||||
|
fun findAllTag(): List<String>
|
||||||
|
}
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
|
@DynamicUpdate
|
||||||
|
@NamedEntityGraph(
|
||||||
|
name = "chapter.list", attributeNodes = [
|
||||||
|
NamedAttributeNode("content"),
|
||||||
|
]
|
||||||
|
)
|
||||||
class Chapter(
|
class Chapter(
|
||||||
@Id
|
@Id
|
||||||
var id: String,
|
var chapterId: String,
|
||||||
|
@Column(nullable = false)
|
||||||
var sequence: Int,
|
var sequence: Int,
|
||||||
var name: String,
|
var name: String?,
|
||||||
var content: List<Line>,
|
var description: String?,
|
||||||
@ManyToOne(cascade = [CascadeType.DETACH], fetch = FetchType.LAZY)
|
@ManyToOne(cascade = [CascadeType.DETACH], fetch = FetchType.LAZY)
|
||||||
@JoinColumn(nullable = false, foreignKey = ForeignKey(ConstraintMode.NO_CONSTRAINT))
|
@JoinColumn(nullable = false, foreignKey = ForeignKey(ConstraintMode.NO_CONSTRAINT))
|
||||||
var book: Book,
|
var book: Book,
|
||||||
|
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.LAZY, mappedBy = "chapter")
|
||||||
|
var content: MutableSet<Line> = mutableSetOf(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
interface ChapterRepository : JpaRepository<Chapter, String>, JpaSpecificationExecutor<Chapter>
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@DynamicUpdate
|
||||||
class Line(
|
class Line(
|
||||||
@Id
|
@Id
|
||||||
var id: String,
|
var lineId: String,
|
||||||
var sequence: Int,
|
@Column(nullable = false)
|
||||||
|
var sequence: Long,
|
||||||
@Lob
|
@Lob
|
||||||
|
@Column(nullable = false)
|
||||||
var text: String,
|
var text: String,
|
||||||
|
@ManyToOne(cascade = [CascadeType.DETACH], fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(nullable = false, foreignKey = ForeignKey(ConstraintMode.NO_CONSTRAINT))
|
||||||
|
var chapter: Chapter
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
interface LineRepository : JpaRepository<Line, String>, JpaSpecificationExecutor<Line>
|
||||||
|
|||||||
90
src/main/resources/static/components/book.js
Normal file
90
src/main/resources/static/components/book.js
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
function bookForm() {
|
||||||
|
return {
|
||||||
|
debug: '${debug}',
|
||||||
|
type: 'form',
|
||||||
|
...horizontalFormOptions(),
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'hidden',
|
||||||
|
name: 'id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input-text',
|
||||||
|
name: 'name',
|
||||||
|
label: '名称',
|
||||||
|
required: true,
|
||||||
|
...formInputClearable(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input-text',
|
||||||
|
name: 'author',
|
||||||
|
label: '作者',
|
||||||
|
...formInputClearable(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input-text',
|
||||||
|
name: 'source',
|
||||||
|
label: '来源',
|
||||||
|
...formInputClearable(),
|
||||||
|
validations: {
|
||||||
|
isUrl: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input-tag',
|
||||||
|
name: 'tags',
|
||||||
|
label: '标签',
|
||||||
|
clearable: true,
|
||||||
|
joinValues: false,
|
||||||
|
extractValue: true,
|
||||||
|
max: 10,
|
||||||
|
maxTagLength: 10,
|
||||||
|
maxTagCount: 5,
|
||||||
|
source: '${base}/book/tags'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'textarea',
|
||||||
|
name: 'description',
|
||||||
|
label: '简介',
|
||||||
|
required: true,
|
||||||
|
...formInputClearable(),
|
||||||
|
showCounter: true,
|
||||||
|
trimContents: true,
|
||||||
|
minRows: 2,
|
||||||
|
maxRows: 2,
|
||||||
|
maxLength: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function bookAddDialog() {
|
||||||
|
return {
|
||||||
|
type: 'action',
|
||||||
|
actionType: 'dialog',
|
||||||
|
dialog: {
|
||||||
|
title: '新增书籍',
|
||||||
|
size: 'md',
|
||||||
|
body: {
|
||||||
|
...bookForm(),
|
||||||
|
api: '${base}/book/save',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function bookDetailDialog() {
|
||||||
|
return {
|
||||||
|
type: 'action',
|
||||||
|
actionType: 'dialog',
|
||||||
|
dialog: {
|
||||||
|
title: '编辑书籍',
|
||||||
|
size: 'md',
|
||||||
|
body: {
|
||||||
|
...bookForm(),
|
||||||
|
initApi: '${base}/book/detail/${bookId}',
|
||||||
|
api: '${base}/book/save',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
69
src/main/resources/static/components/chapter.js
Normal file
69
src/main/resources/static/components/chapter.js
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
function chapterForm() {
|
||||||
|
return {
|
||||||
|
debug: '${debug}',
|
||||||
|
type: 'form',
|
||||||
|
...horizontalFormOptions(),
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'hidden',
|
||||||
|
name: 'id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input-number',
|
||||||
|
name: 'sequence',
|
||||||
|
label: '章节数',
|
||||||
|
required: true,
|
||||||
|
step: 1,
|
||||||
|
precision: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input-text',
|
||||||
|
name: 'name',
|
||||||
|
label: '名称',
|
||||||
|
...formInputClearable(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'textarea',
|
||||||
|
name: 'description',
|
||||||
|
label: '简介',
|
||||||
|
...formInputClearable(),
|
||||||
|
showCounter: true,
|
||||||
|
trimContents: true,
|
||||||
|
minRows: 2,
|
||||||
|
maxRows: 2,
|
||||||
|
maxLength: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function chapterAddDialog() {
|
||||||
|
return {
|
||||||
|
type: 'action',
|
||||||
|
actionType: 'dialog',
|
||||||
|
dialog: {
|
||||||
|
title: '新增章节',
|
||||||
|
size: 'md',
|
||||||
|
body: {
|
||||||
|
...chapterForm(),
|
||||||
|
api: '${base}/chapter/save/${bookId}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function chapterDetailDialog() {
|
||||||
|
return {
|
||||||
|
type: 'action',
|
||||||
|
actionType: 'dialog',
|
||||||
|
dialog: {
|
||||||
|
title: '编辑章节',
|
||||||
|
size: 'md',
|
||||||
|
body: {
|
||||||
|
...chapterForm(),
|
||||||
|
initApi: '${base}/chapter/detail/${chapterId}',
|
||||||
|
api: '${base}/chapter/save/${bookId}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/main/resources/static/components/helper.js
Normal file
36
src/main/resources/static/components/helper.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
function crudCommonOptions() {
|
||||||
|
return {
|
||||||
|
affixHeader: false,
|
||||||
|
stopAutoRefreshWhenModalIsOpen: true,
|
||||||
|
resizable: false,
|
||||||
|
syncLocation: false,
|
||||||
|
silentPolling: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function horizontalFormOptions() {
|
||||||
|
return {
|
||||||
|
mode: 'horizontal',
|
||||||
|
canAccessSuperData: false,
|
||||||
|
horizontal: {
|
||||||
|
left: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formInputClearable() {
|
||||||
|
return {
|
||||||
|
clearable: true,
|
||||||
|
clearValueOnEmpty: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function paginationOption() {
|
||||||
|
return {
|
||||||
|
type: 'pagination',
|
||||||
|
mode: 'normal',
|
||||||
|
layout: 'total,perPage,pager',
|
||||||
|
maxButtons: 5,
|
||||||
|
showPageInput: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,40 +17,287 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
resize: none !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
</body>
|
</body>
|
||||||
<script src="sdk/sdk.js"></script>
|
<script src="sdk/sdk.js"></script>
|
||||||
|
<script src="components/helper.js"></script>
|
||||||
|
<script src="components/book.js"></script>
|
||||||
|
<script src="components/chapter.js"></script>
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
let amis = amisRequire('amis/embed')
|
let debug = true
|
||||||
let amisJSON = {
|
let amis = amisRequire('amis/embed')
|
||||||
type: 'page',
|
let amisJSON = {
|
||||||
title: '书籍中心',
|
type: 'page',
|
||||||
subTitle: '网络书籍精排版工具',
|
title: '书籍中心',
|
||||||
body: [
|
subTitle: '网络书籍精排版工具',
|
||||||
'Hello World'
|
body: [
|
||||||
]
|
{
|
||||||
}
|
type: 'crud',
|
||||||
let debug = false
|
api: {
|
||||||
amis.embed(
|
method: 'get',
|
||||||
'#root',
|
url: '${base}/book/list',
|
||||||
amisJSON,
|
data: {
|
||||||
{
|
page: '${page|default:1}',
|
||||||
data: {
|
size: '${size|default:10}'
|
||||||
base: 'http://127.0.0.1:23890'
|
}
|
||||||
},
|
},
|
||||||
},
|
...crudCommonOptions(),
|
||||||
{
|
headerToolbar: [
|
||||||
theme: 'antd',
|
'reload',
|
||||||
enableAMISDebug: debug,
|
{
|
||||||
},
|
label: '',
|
||||||
);
|
icon: 'fa fa-add',
|
||||||
if (debug) {
|
...bookAddDialog(),
|
||||||
console.log('Source', amisJSON)
|
},
|
||||||
}
|
],
|
||||||
})()
|
footerToolbar: [
|
||||||
|
paginationOption(),
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
label: '名称',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'description',
|
||||||
|
label: '描述',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'source',
|
||||||
|
label: '来源',
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'operation',
|
||||||
|
label: '操作',
|
||||||
|
fixed: 'right',
|
||||||
|
className: 'nowrap',
|
||||||
|
width: 300,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
type: 'action',
|
||||||
|
label: '跳转',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'action',
|
||||||
|
label: '编辑',
|
||||||
|
...bookDetailDialog(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'action',
|
||||||
|
label: '编辑章节',
|
||||||
|
actionType: 'dialog',
|
||||||
|
dialog: {
|
||||||
|
title: '章节',
|
||||||
|
size: 'lg',
|
||||||
|
actions: [],
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'crud',
|
||||||
|
...crudCommonOptions(),
|
||||||
|
api: {
|
||||||
|
method: 'get',
|
||||||
|
url: '${base}/chapter/list/${bookId}',
|
||||||
|
data: {
|
||||||
|
page: '${page|default:1}',
|
||||||
|
size: '${size|default:10}'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
headerToolbar: [
|
||||||
|
'reload',
|
||||||
|
{
|
||||||
|
label: '',
|
||||||
|
icon: 'fa fa-add',
|
||||||
|
...chapterAddDialog(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
footerToolbar: [
|
||||||
|
paginationOption(),
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'sequence',
|
||||||
|
label: '章节数',
|
||||||
|
width: 50,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
label: '名称',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'description',
|
||||||
|
label: '简介',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'operation',
|
||||||
|
label: '操作',
|
||||||
|
fixed: 'right',
|
||||||
|
className: 'nowrap',
|
||||||
|
width: 200,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
type: 'action',
|
||||||
|
label: '编辑',
|
||||||
|
...chapterDetailDialog(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'action',
|
||||||
|
label: '导入',
|
||||||
|
actionType: 'dialog',
|
||||||
|
dialog: {
|
||||||
|
title: '导入正文',
|
||||||
|
size: 'lg',
|
||||||
|
body: {
|
||||||
|
debug: '${debug}',
|
||||||
|
type: 'form',
|
||||||
|
api: '${base}/chapter/import/${chapterId}',
|
||||||
|
mode: 'normal',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
name: 'override',
|
||||||
|
label: '覆盖导入',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'textarea',
|
||||||
|
name: 'text',
|
||||||
|
label: '正文',
|
||||||
|
required: true,
|
||||||
|
...formInputClearable(),
|
||||||
|
showCounter: true,
|
||||||
|
trimContents: true,
|
||||||
|
minRows: 10,
|
||||||
|
maxRows: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'action',
|
||||||
|
label: '编辑正文',
|
||||||
|
actionType: 'dialog',
|
||||||
|
dialog: {
|
||||||
|
title: '编辑正文',
|
||||||
|
size: 'lg',
|
||||||
|
actions: [],
|
||||||
|
body: {
|
||||||
|
type: 'crud',
|
||||||
|
...crudCommonOptions(),
|
||||||
|
api: {
|
||||||
|
method: 'get',
|
||||||
|
url: '${base}/line/list/${chapterId}',
|
||||||
|
data: {
|
||||||
|
page: '${page|default:1}',
|
||||||
|
size: '${size|default:10}'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
quickSaveItemApi: '${base}/line/update/${lineId}',
|
||||||
|
headerToolbar: [
|
||||||
|
'reload',
|
||||||
|
],
|
||||||
|
footerToolbar: [
|
||||||
|
paginationOption(),
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'sequence',
|
||||||
|
label: '行号',
|
||||||
|
width: 50,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'text',
|
||||||
|
label: '内容',
|
||||||
|
quickEdit: {
|
||||||
|
saveImmediately: true,
|
||||||
|
resetOnFailed: true,
|
||||||
|
type: 'textarea',
|
||||||
|
showCounter: true,
|
||||||
|
trimContents: true,
|
||||||
|
minRows: 10,
|
||||||
|
maxRows: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'operation',
|
||||||
|
label: '操作',
|
||||||
|
fixed: 'right',
|
||||||
|
className: 'nowrap',
|
||||||
|
width: 100,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
type: 'action',
|
||||||
|
label: '删除',
|
||||||
|
level: 'danger',
|
||||||
|
confirmTitle: '确认删除',
|
||||||
|
confirmText: '确认删除当前行吗?',
|
||||||
|
actionType: 'ajax',
|
||||||
|
api: 'get:${base}/line/remove/${lineId}',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'action',
|
||||||
|
label: '删除',
|
||||||
|
level: 'danger',
|
||||||
|
confirmTitle: '确认删除',
|
||||||
|
confirmText: '确认删除名称为「${name}」的章节吗?',
|
||||||
|
actionType: 'ajax',
|
||||||
|
api: 'get:${base}/chapter/remove/${chapterId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'action',
|
||||||
|
label: '删除',
|
||||||
|
level: 'danger',
|
||||||
|
confirmTitle: '确认删除',
|
||||||
|
confirmText: '确认删除名称为「${name}」的书籍吗?',
|
||||||
|
actionType: 'ajax',
|
||||||
|
api: 'get:${base}/book/remove/${bookId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
amis.embed(
|
||||||
|
'#root',
|
||||||
|
amisJSON,
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
base: 'http://127.0.0.1:23890',
|
||||||
|
debug: debug,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
theme: 'antd',
|
||||||
|
enableAMISDebug: debug,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (debug) {
|
||||||
|
console.log('Source', amisJSON)
|
||||||
|
}
|
||||||
|
})()
|
||||||
</script>
|
</script>
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user