feat: 完成基本功能
This commit is contained in:
28
.gitignore
vendored
28
.gitignore
vendored
@@ -290,29 +290,5 @@ Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
### Kotlin template
|
||||
# Compiled class file
|
||||
*.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*
|
||||
|
||||
### Custom
|
||||
*.db
|
||||
|
||||
10
.idea/compiler.xml
generated
10
.idea/compiler.xml
generated
@@ -5,18 +5,10 @@
|
||||
<profile name="Gradle Imported" enabled="true">
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<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$/.gradle/caches/modules-2/files-2.1/org.projectlombok/lombok/1.18.36/5a30490a6e14977d97d9c73c924c1f1b5311ea95/lombok-1.18.36.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" />
|
||||
</processorPath>
|
||||
<module name="bookstore.main" />
|
||||
</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>
|
||||
<bytecodeTargetLevel target="17" />
|
||||
</component>
|
||||
|
||||
@@ -4,8 +4,6 @@ plugins {
|
||||
kotlin("plugin.jpa") version kotlinVersion
|
||||
kotlin("plugin.allopen") 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("io.spring.dependency-management") version "1.1.7"
|
||||
}
|
||||
@@ -20,10 +18,8 @@ java {
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url = uri("http://localhost:3105/threepartrepo")
|
||||
isAllowInsecureProtocol = true
|
||||
}
|
||||
maven("https://maven.aliyun.com/repository/central")
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -3,15 +3,34 @@ package com.lanyuanxiaoyao.bookstore
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
import org.springframework.boot.runApplication
|
||||
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
|
||||
@EnableJpaAuditing
|
||||
class BookstoreApplication
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
runApplication<BookstoreApplication>(*args)
|
||||
}
|
||||
|
||||
@Configuration
|
||||
class WebConfiguration {
|
||||
@Bean
|
||||
fun bookstore(): List<Book> {
|
||||
return emptyList()
|
||||
fun corsFilter(): CorsFilter {
|
||||
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
|
||||
|
||||
import lombok.extern.slf4j.Slf4j
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import cn.hutool.core.util.IdUtil
|
||||
import jakarta.annotation.Resource
|
||||
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
|
||||
@RequestMapping("book")
|
||||
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
|
||||
|
||||
import jakarta.persistence.CascadeType
|
||||
import jakarta.persistence.ConstraintMode
|
||||
import jakarta.persistence.Entity
|
||||
import jakarta.persistence.FetchType
|
||||
import jakarta.persistence.ForeignKey
|
||||
import jakarta.persistence.Id
|
||||
import jakarta.persistence.JoinColumn
|
||||
import jakarta.persistence.Lob
|
||||
import jakarta.persistence.ManyToOne
|
||||
import jakarta.persistence.OneToMany
|
||||
import jakarta.persistence.*
|
||||
import org.hibernate.annotations.DynamicUpdate
|
||||
import org.springframework.data.jpa.repository.EntityGraph
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor
|
||||
import org.springframework.data.jpa.repository.Query
|
||||
import org.springframework.stereotype.Repository
|
||||
|
||||
@Entity
|
||||
@DynamicUpdate
|
||||
@NamedEntityGraph(
|
||||
name = "book.list", attributeNodes = [
|
||||
NamedAttributeNode("tags"),
|
||||
NamedAttributeNode("chapters"),
|
||||
]
|
||||
)
|
||||
class Book(
|
||||
@Id
|
||||
var id: String,
|
||||
var bookId: String,
|
||||
@Column(nullable = false)
|
||||
var name: String,
|
||||
var author: String,
|
||||
var author: String?,
|
||||
@Column(nullable = false)
|
||||
var description: String,
|
||||
var tags: List<String>,
|
||||
@OneToMany(cascade = [CascadeType.REMOVE], fetch = FetchType.LAZY, mappedBy = "book")
|
||||
var chapters: List<Chapter>,
|
||||
var source: String?,
|
||||
@ElementCollection(fetch = FetchType.EAGER)
|
||||
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
|
||||
@DynamicUpdate
|
||||
@NamedEntityGraph(
|
||||
name = "chapter.list", attributeNodes = [
|
||||
NamedAttributeNode("content"),
|
||||
]
|
||||
)
|
||||
class Chapter(
|
||||
@Id
|
||||
var id: String,
|
||||
var chapterId: String,
|
||||
@Column(nullable = false)
|
||||
var sequence: Int,
|
||||
var name: String,
|
||||
var content: List<Line>,
|
||||
var name: String?,
|
||||
var description: String?,
|
||||
@ManyToOne(cascade = [CascadeType.DETACH], fetch = FetchType.LAZY)
|
||||
@JoinColumn(nullable = false, foreignKey = ForeignKey(ConstraintMode.NO_CONSTRAINT))
|
||||
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(
|
||||
@Id
|
||||
var id: String,
|
||||
var sequence: Int,
|
||||
var lineId: String,
|
||||
@Column(nullable = false)
|
||||
var sequence: Long,
|
||||
@Lob
|
||||
@Column(nullable = false)
|
||||
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,30 +17,277 @@
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: none !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
<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>
|
||||
(function () {
|
||||
let debug = true
|
||||
let amis = amisRequire('amis/embed')
|
||||
let amisJSON = {
|
||||
type: 'page',
|
||||
title: '书籍中心',
|
||||
subTitle: '网络书籍精排版工具',
|
||||
body: [
|
||||
'Hello World'
|
||||
{
|
||||
type: 'crud',
|
||||
api: {
|
||||
method: 'get',
|
||||
url: '${base}/book/list',
|
||||
data: {
|
||||
page: '${page|default:1}',
|
||||
size: '${size|default:10}'
|
||||
}
|
||||
},
|
||||
...crudCommonOptions(),
|
||||
headerToolbar: [
|
||||
'reload',
|
||||
{
|
||||
label: '',
|
||||
icon: 'fa fa-add',
|
||||
...bookAddDialog(),
|
||||
},
|
||||
],
|
||||
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}',
|
||||
},
|
||||
]
|
||||
}
|
||||
let debug = false
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
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'
|
||||
base: 'http://127.0.0.1:23890',
|
||||
debug: debug,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user