1
0

feat: 优化书籍和章节编辑界面

This commit is contained in:
2024-12-23 09:47:24 +08:00
parent 13590cbe9c
commit e0653d7400
9 changed files with 571 additions and 509 deletions

16
.gitignore vendored
View File

@@ -91,14 +91,14 @@ replay_pid*
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
.idea/artifacts
.idea/compiler.xml
.idea/jarRepositories.xml
.idea/modules.xml
.idea/*.iml
.idea/modules
*.iml
*.ipr
# CMake
cmake-build-*/

7
.idea/jpa.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JpaBuddyIdeaProjectConfig">
<option name="defaultUnitInitialized" value="true" />
<option name="renamerInitialized" value="true" />
</component>
</project>

3
.idea/misc.xml generated
View File

@@ -7,4 +7,7 @@
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="corretto-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
<component name="ProjectType">
<option name="id" value="jpab" />
</component>
</project>

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@@ -33,3 +33,14 @@ spring:
driver-class-name: com.mysql.cj.jdbc.Driver
username: bookstore
password: EzSn+RZ*x2&fHFh9kC+H
---
spring:
config:
activate:
on-profile: mysql-remote
datasource:
url: jdbc:mysql://frp-air.top:43458/bookstore
driver-class-name: com.mysql.cj.jdbc.Driver
username: bookstore
password: EzSn+RZ*x2&fHFh9kC+H

View File

@@ -1,88 +1,208 @@
function bookForm() {
return {
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,
source: '${base}/book/tags'
},
{
type: 'textarea',
name: 'description',
label: '简介',
required: true,
...formInputClearable(),
showCounter: true,
trimContents: true,
minRows: 2,
maxRows: 2,
maxLength: 100,
},
],
}
return {
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,
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',
},
},
}
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',
},
return {
type: 'action',
actionType: 'dialog',
dialog: {
title: '编辑书籍',
size: 'lg',
actions: [],
body: [
{
id: 'book-detail-dialog-form',
...bookForm(),
initApi: '${base}/book/detail/${bookId}',
api: '${base}/book/save',
},
}
{
type: 'flex',
className: 'mt-5',
justify: 'flex-end',
items: [
{
type: 'action',
label: '保存',
level: 'primary',
onEvent: {
click: {
actions: [
{
actionType: 'submit',
componentId: 'book-detail-dialog-form',
},
{
actionType: 'reload',
componentId: 'book-list',
}
]
}
}
}
]
},
{
id: 'chapter-list',
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',
actions: [],
body: {
type: 'service',
className: 'p-10',
api: '${base}/chapter/show/${chapterId}',
silentPolling: true,
body: {
type: 'tpl',
className: 'font-serif',
tpl: '${item|raw}'
}
}
}
},
{
type: 'action',
label: '导出',
actionType: 'download',
api: '${base}/chapter/export/${chapterId}',
},
{
type: 'action',
label: '删除',
level: 'danger',
confirmTitle: '确认删除',
confirmText: '确认删除名称为「${name}」的章节吗?',
actionType: 'ajax',
api: 'get:${base}/chapter/remove/${chapterId}',
},
],
},
],
},
],
},
}
}

View File

@@ -1,68 +1,230 @@
function chapterForm() {
return {
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,
},
],
}
return {
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}',
},
},
}
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}',
},
return {
type: 'action',
actionType: 'dialog',
dialog: {
title: '编辑章节',
size: 'lg',
actions: [],
body: [
{
id: 'chapter-detail-dialog-form',
...chapterForm(),
initApi: '${base}/chapter/detail/${chapterId}',
api: '${base}/chapter/save/${bookId}',
},
}
{
type: 'flex',
className: 'mt-5',
justify: 'flex-end',
items: [
{
type: 'action',
label: '保存',
level: 'primary',
onEvent: {
click: {
actions: [
{
actionType: 'submit',
componentId: 'chapter-detail-dialog-form',
},
{
actionType: 'reload',
componentId: 'chapter-list',
}
]
}
}
}
]
},
{
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',
{
type: 'action',
label: '',
icon: 'fa fa-upload',
actionType: 'dialog',
dialog: {
title: '导入正文',
size: 'lg',
body: {
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,
},
],
},
},
},
],
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: '处理',
actionType: 'dialog',
dialog: {
title: '文本处理',
size: 'lg',
body: {
debug: true,
type: 'form',
canAccessSuperData: false,
initApi: '${base}/line/detail/${lineId}',
api: '${base}/line/save/${chapterId}',
body: [
{
type: 'hidden',
name: 'lineId',
},
{
type: 'textarea',
name: 'text',
label: '正文',
...formInputClearable(),
showCounter: true,
trimContents: true,
minRows: 5,
maxRows: 5,
},
{
type: 'textarea',
name: 'newText',
label: '优化后正文',
...formInputClearable(),
showCounter: true,
trimContents: true,
minRows: 5,
maxRows: 5,
},
]
}
}
},
{
type: 'action',
label: '删除',
level: 'danger',
confirmTitle: '确认删除',
confirmText: '确认删除当前行吗?',
actionType: 'ajax',
api: 'get:${base}/line/remove/${lineId}',
},
]
}
],
},
]
},
}
}

View File

@@ -1,37 +1,38 @@
function crudCommonOptions() {
return {
affixHeader: false,
stopAutoRefreshWhenModalIsOpen: true,
resizable: false,
syncLocation: false,
silentPolling: true,
}
return {
affixHeader: false,
stopAutoRefreshWhenModalIsOpen: true,
resizable: false,
syncLocation: false,
silentPolling: true,
}
}
function horizontalFormOptions() {
return {
mode: 'horizontal',
canAccessSuperData: false,
horizontal: {
left: 1,
},
}
function horizontalFormOptions(left = 1) {
return {
mode: 'horizontal',
canAccessSuperData: false,
horizontal: {
left: left,
justify: true,
},
}
}
function formInputClearable() {
return {
clearable: true,
clearValueOnEmpty: true,
}
return {
clearable: true,
clearValueOnEmpty: true,
}
}
function paginationOption() {
return {
type: 'pagination',
mode: 'normal',
layout: 'total,perPage,pager',
maxButtons: 5,
showPageInput: false,
total: '${total}'
}
return {
type: 'pagination',
mode: 'normal',
layout: 'total,perPage,pager',
maxButtons: 5,
showPageInput: false,
total: '${total}'
}
}

View File

@@ -31,345 +31,103 @@
<script src="components/book.js"></script>
<script src="components/chapter.js"></script>
<script>
(function () {
let debug = false
let amis = amisRequire('amis/embed')
let amisJSON = {
type: 'page',
title: '书籍中心',
subTitle: '网络书籍精排版工具',
body: [
(function () {
let debug = false
let amis = amisRequire('amis/embed')
let amisJSON = {
type: 'page',
title: '书籍中心',
subTitle: '网络书籍精排版工具',
body: [
{
id: 'book-list',
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: '描述',
},
{
type: 'operation',
label: '操作',
fixed: 'right',
className: 'nowrap',
width: 100,
buttons: [
{
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: '描述',
},
{
type: 'operation',
label: '操作',
fixed: 'right',
className: 'nowrap',
width: 100,
buttons: [
{
type: 'action',
label: '跳转',
actionType: 'url',
url: '${source}',
blank: true,
},
{
type: 'action',
label: '导出',
actionType: 'download',
api: '${base}/book/export/${bookId}',
},
{
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',
actions: [],
body: {
type: 'service',
className: 'p-10',
api: '${base}/chapter/show/${chapterId}',
silentPolling: true,
body: {
type: 'tpl',
className: 'font-serif',
tpl: '${item|raw}'
}
}
}
},
{
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: 'download',
api: '${base}/chapter/export/${chapterId}',
},
{
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: '处理',
actionType: 'dialog',
dialog: {
title: '文本处理',
size: 'lg',
body: {
debug: true,
type: 'form',
canAccessSuperData: false,
initApi: '${base}/line/detail/${lineId}',
api: '${base}/line/save/${chapterId}',
body: [
{
type: 'hidden',
name: 'lineId',
},
{
type: 'textarea',
name: 'text',
label: '正文',
...formInputClearable(),
showCounter: true,
trimContents: true,
minRows: 5,
maxRows: 5,
},
{
type: 'textarea',
name: 'newText',
label: '优化后正文',
...formInputClearable(),
showCounter: true,
trimContents: true,
minRows: 5,
maxRows: 5,
},
]
}
}
},
{
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}',
},
],
},
],
type: 'action',
label: '跳转',
actionType: 'url',
url: '${source}',
blank: true,
},
],
}
amis.embed(
'#root',
amisJSON,
{
data: {
base: 'http://127.0.0.1:23890',
{
type: 'action',
label: '导出',
actionType: 'download',
api: '${base}/book/export/${bookId}',
},
{
type: 'action',
label: '编辑',
...bookDetailDialog(),
},
{
type: 'action',
label: '删除',
level: 'danger',
confirmTitle: '确认删除',
confirmText: '确认删除名称为「${name}」的书籍吗?',
actionType: 'ajax',
api: 'get:${base}/book/remove/${bookId}',
},
],
},
{
theme: 'antd',
enableAMISDebug: debug,
},
);
if (debug) {
console.log('Source', amisJSON)
}
})()
],
},
],
}
amis.embed(
'#root',
amisJSON,
{
data: {
base: 'http://127.0.0.1:23890',
},
},
{
theme: 'antd',
enableAMISDebug: debug,
},
);
if (debug) {
console.log('Source', amisJSON)
}
})()
</script>
</html>