fix: 优化书籍后台字段展示与提交
Some checks failed
CI / init (pull_request) Has been cancelled
CI / Frontend node 18.16.0 (pull_request) Has been cancelled
CI / Backend go (1.22) (pull_request) Has been cancelled
CI / devops-test (1.22, 18.16.0) (pull_request) Has been cancelled
CI / release-pr (pull_request) Has been cancelled
CI / release-please (pull_request) Has been cancelled
CI / devops-prod (1.22, 18.x) (pull_request) Has been cancelled
CI / docker (pull_request) Has been cancelled
Some checks failed
CI / init (pull_request) Has been cancelled
CI / Frontend node 18.16.0 (pull_request) Has been cancelled
CI / Backend go (1.22) (pull_request) Has been cancelled
CI / devops-test (1.22, 18.16.0) (pull_request) Has been cancelled
CI / release-pr (pull_request) Has been cancelled
CI / release-please (pull_request) Has been cancelled
CI / devops-prod (1.22, 18.x) (pull_request) Has been cancelled
CI / docker (pull_request) Has been cancelled
This commit is contained in:
@@ -38,6 +38,10 @@ flowchart LR
|
|||||||
- `path` 默认与 `name` 同步;只有明确需要参数化路径时才额外拼接,不要把查询条件硬塞进路由 path。
|
- `path` 默认与 `name` 同步;只有明确需要参数化路径时才额外拼接,不要把查询条件硬塞进路由 path。
|
||||||
- 需要缓存页签时设置 `meta.keepAlive`;需要进入后自动关闭 tab 时设置 `meta.closeTab`。
|
- 需要缓存页签时设置 `meta.keepAlive`;需要进入后自动关闭 tab 时设置 `meta.closeTab`。
|
||||||
- 页面进入菜单体系后,新增菜单不等于可访问;还必须补角色授权,否则页面可能存在但用户不可见。
|
- 页面进入菜单体系后,新增菜单不等于可访问;还必须补角色授权,否则页面可能存在但用户不可见。
|
||||||
|
- 列表页的 list item 遇到单图片字段时,必须使用图片预览组件展示;禁止把图片 URL、图片路径或单图片附件地址作为普通文本直接显示。
|
||||||
|
- 新增/编辑功能遇到图片或文件属性时,必须使用上传组件并支持已有值回显;禁止使用 `el-input`、`textarea` 或普通文本输入组件让用户手填 URL/路径。
|
||||||
|
- 列表页、详情页、关联表、related list item 遇到字典字段时,必须声明对应 `dict` 配置并使用字典格式化展示 `Label`;禁止把字典 `Value/key` 作为普通文本直接显示。
|
||||||
|
- 字典字段在新增/编辑/筛选中提交和绑定使用 `Value`,页面展示统一使用字典 `Label`;涉及字典编码和值域时必须先读取后台 `doc-dict` 文档。
|
||||||
|
|
||||||
## curl 联调案例
|
## curl 联调案例
|
||||||
|
|
||||||
@@ -69,3 +73,6 @@ curl --location --request POST "$BASE_URL/menu/addBaseMenu" \
|
|||||||
- 远程路由 `component` 写错路径格式,导致 `asyncRouter.js` 找不到页面组件。
|
- 远程路由 `component` 写错路径格式,导致 `asyncRouter.js` 找不到页面组件。
|
||||||
- 页面里直接写 axios 请求或直接拼 token,绕过 `src/api` 和 `src/utils/request.js`。
|
- 页面里直接写 axios 请求或直接拼 token,绕过 `src/api` 和 `src/utils/request.js`。
|
||||||
- 改了路由 `name/path`,没有同步检查 `keepAlive`、菜单高亮、`defaultRouter` 和未登录跳转。
|
- 改了路由 `name/path`,没有同步检查 `keepAlive`、菜单高亮、`defaultRouter` 和未登录跳转。
|
||||||
|
- list item 的单图片字段直接显示 URL 文本,没有使用图片预览。
|
||||||
|
- 新增/编辑表单把图片或文件字段做成文本输入框,要求用户手动填写 URL 或路径。
|
||||||
|
- 详情页或关联表字段直接显示字典 key,例如 `draft`、`on_shelf`、`completed`,没有通过字典格式化显示名称。
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
"version": "2.9.1",
|
"version": "2.9.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node openDocument.js && vite --host --mode development",
|
"dev": "vite --host --mode development",
|
||||||
"serve": "node openDocument.js && vite --host --mode development",
|
"serve": "vite --host --mode development",
|
||||||
"build": "vite build --mode production",
|
"build": "vite build --mode production",
|
||||||
"limit-build": "npm install increase-memory-limit-fixbug cross-env -g && npm run fix-memory-limit && node ./limit && npm run build",
|
"limit-build": "npm install increase-memory-limit-fixbug cross-env -g && npm run fix-memory-limit && node ./limit && npm run build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
|
|||||||
@@ -194,8 +194,14 @@
|
|||||||
<div v-for="block in relatedBlocks" :key="block.title" class="book-admin-related">
|
<div v-for="block in relatedBlocks" :key="block.title" class="book-admin-related">
|
||||||
<div class="book-admin-related__title">{{ block.title }}</div>
|
<div class="book-admin-related__title">{{ block.title }}</div>
|
||||||
<el-table :data="block.rows" size="small" border>
|
<el-table :data="block.rows" size="small" border>
|
||||||
<el-table-column v-for="column in block.columns" :key="column" :label="column" :prop="column" show-overflow-tooltip>
|
<el-table-column
|
||||||
<template #default="scope">{{ formatLooseCell(scope.row[column]) }}</template>
|
v-for="column in block.columns"
|
||||||
|
:key="column.prop"
|
||||||
|
:label="column.label"
|
||||||
|
:prop="column.prop"
|
||||||
|
show-overflow-tooltip
|
||||||
|
>
|
||||||
|
<template #default="scope">{{ formatRelatedCell(scope.row, column) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</div>
|
</div>
|
||||||
@@ -235,7 +241,17 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<el-table :data="authorRelations" border>
|
<el-table :data="authorRelations" border>
|
||||||
<el-table-column label="作者 ID" prop="authorId" width="120" />
|
<el-table-column
|
||||||
|
v-for="column in bookAuthorRelationDisplayColumns"
|
||||||
|
:key="column.prop"
|
||||||
|
:label="column.label"
|
||||||
|
:min-width="column.minWidth"
|
||||||
|
:prop="column.prop"
|
||||||
|
:width="column.width"
|
||||||
|
show-overflow-tooltip
|
||||||
|
>
|
||||||
|
<template #default="scope">{{ formatLooseCell(scope.row[column.prop]) }}</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="排序" prop="authorSort" width="180">
|
<el-table-column label="排序" prop="authorSort" width="180">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-input-number v-model="scope.row.authorSort" :min="1" :step="1" @change="updateAuthorSort(scope.row)" />
|
<el-input-number v-model="scope.row.authorSort" :min="1" :step="1" @change="updateAuthorSort(scope.row)" />
|
||||||
@@ -258,7 +274,9 @@
|
|||||||
import { getUrl } from '@/utils/image'
|
import { getUrl } from '@/utils/image'
|
||||||
import { filterDict, formatBoolean, formatDate, getBaseUrl } from '@/utils/format'
|
import { filterDict, formatBoolean, formatDate, getBaseUrl } from '@/utils/format'
|
||||||
import { useUserStore } from '@/pinia'
|
import { useUserStore } from '@/pinia'
|
||||||
|
import { bookAuthorRelationDisplayColumns } from '../config/bookAdminConfig'
|
||||||
import { isBookAdminImageColumn, resolveBookAdminColumnType } from '../config/bookAdminDisplay'
|
import { isBookAdminImageColumn, resolveBookAdminColumnType } from '../config/bookAdminDisplay'
|
||||||
|
import { buildBookAdminSubmitPayload, buildBookAuthorRelationSubmitPayload } from '../config/bookAdminPayload'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { computed, reactive, ref } from 'vue'
|
import { computed, reactive, ref } from 'vue'
|
||||||
|
|
||||||
@@ -371,6 +389,19 @@
|
|||||||
return value ?? ''
|
return value ?? ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const normalizeRelatedColumn = (column) => {
|
||||||
|
return typeof column === 'string' ? { label: column, prop: column } : column
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatRelatedCell = (row, column) => {
|
||||||
|
const value = row?.[column.prop]
|
||||||
|
if (column.type === 'datetime') return formatDate(value)
|
||||||
|
if (column.type === 'date') return value ? String(value).slice(0, 10) : ''
|
||||||
|
if (column.type === 'boolean' || column.type === 'switch') return formatBoolean(value)
|
||||||
|
if (column.type === 'dict') return filterDict(value, dictOptions[column.dict]) || value || ''
|
||||||
|
return formatLooseCell(value)
|
||||||
|
}
|
||||||
|
|
||||||
const formatCell = (row, column) => {
|
const formatCell = (row, column) => {
|
||||||
const value = row?.[column.prop]
|
const value = row?.[column.prop]
|
||||||
const columnType = resolveBookAdminColumnType(config.value, column)
|
const columnType = resolveBookAdminColumnType(config.value, column)
|
||||||
@@ -419,6 +450,11 @@
|
|||||||
;[...config.value.searchFields, ...config.value.fields, ...config.value.tableColumns].forEach((field) => {
|
;[...config.value.searchFields, ...config.value.fields, ...config.value.tableColumns].forEach((field) => {
|
||||||
if (field.dict) dictCodes.add(field.dict)
|
if (field.dict) dictCodes.add(field.dict)
|
||||||
})
|
})
|
||||||
|
config.value.related?.forEach((relation) => {
|
||||||
|
relation.columns?.map(normalizeRelatedColumn).forEach((column) => {
|
||||||
|
if (column.dict) dictCodes.add(column.dict)
|
||||||
|
})
|
||||||
|
})
|
||||||
config.value.statusFields?.forEach((status) => {
|
config.value.statusFields?.forEach((status) => {
|
||||||
if (status.dict) dictCodes.add(status.dict)
|
if (status.dict) dictCodes.add(status.dict)
|
||||||
})
|
})
|
||||||
@@ -525,7 +561,8 @@
|
|||||||
const enterDialog = () => {
|
const enterDialog = () => {
|
||||||
formRef.value?.validate(async (valid) => {
|
formRef.value?.validate(async (valid) => {
|
||||||
if (!valid) return
|
if (!valid) return
|
||||||
const res = dialogType.value === 'update' ? await api.value.update(formData.value) : await api.value.create(formData.value)
|
const payload = buildBookAdminSubmitPayload(config.value, formData.value, dialogType.value)
|
||||||
|
const res = dialogType.value === 'update' ? await api.value.update(payload) : await api.value.create(payload)
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
ElMessage({ type: 'success', message: '创建/更改成功' })
|
ElMessage({ type: 'success', message: '创建/更改成功' })
|
||||||
closeDialog()
|
closeDialog()
|
||||||
@@ -576,7 +613,8 @@
|
|||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
type: 'warning'
|
type: 'warning'
|
||||||
}).then(async () => {
|
}).then(async () => {
|
||||||
const res = await api.value.update({ ...row, [status.prop]: value })
|
const payload = buildBookAdminSubmitPayload(config.value, { ...row, [status.prop]: value }, 'update')
|
||||||
|
const res = await api.value.update(payload)
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
ElMessage({ type: 'success', message: '状态已调整' })
|
ElMessage({ type: 'success', message: '状态已调整' })
|
||||||
getTableData()
|
getTableData()
|
||||||
@@ -593,7 +631,7 @@
|
|||||||
if (!listApi) continue
|
if (!listApi) continue
|
||||||
const res = await listApi({ page: 1, pageSize: 10, [relation.filterProp]: row.id })
|
const res = await listApi({ page: 1, pageSize: 10, [relation.filterProp]: row.id })
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
blocks.push({ title: relation.title, columns: relation.columns, rows: res.data.list || [] })
|
blocks.push({ title: relation.title, columns: relation.columns.map(normalizeRelatedColumn), rows: res.data.list || [] })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
relatedBlocks.value = blocks
|
relatedBlocks.value = blocks
|
||||||
@@ -659,7 +697,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updateAuthorSort = async (row) => {
|
const updateAuthorSort = async (row) => {
|
||||||
const res = await bookApi.updateBookAuthorRelation(row)
|
const res = await bookApi.updateBookAuthorRelation(buildBookAuthorRelationSubmitPayload(row, 'update'))
|
||||||
if (res.code === 0) ElMessage({ type: 'success', message: '排序已更新' })
|
if (res.code === 0) ElMessage({ type: 'success', message: '排序已更新' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,51 @@ const imageField = (label, prop, extra = {}) => ({ label, prop, type: 'image', .
|
|||||||
const fileField = (label, prop, extra = {}) => ({ label, prop, type: 'file', ...extra })
|
const fileField = (label, prop, extra = {}) => ({ label, prop, type: 'file', ...extra })
|
||||||
const dictField = (label, prop, dict, extra = {}) => ({ label, prop, type: 'dict', dict, ...extra })
|
const dictField = (label, prop, dict, extra = {}) => ({ label, prop, type: 'dict', dict, ...extra })
|
||||||
const relationField = (label, prop, relation, extra = {}) => ({ label, prop, type: 'relation', relation, ...extra })
|
const relationField = (label, prop, relation, extra = {}) => ({ label, prop, type: 'relation', relation, ...extra })
|
||||||
|
const relatedColumn = (label, prop, extra = {}) => ({ label, prop, ...extra })
|
||||||
|
|
||||||
|
export const bookAuthorRelationDisplayColumns = [
|
||||||
|
{ label: '作者名称', prop: 'authorName', minWidth: 160 },
|
||||||
|
{ label: '作者 ID', prop: 'authorId', width: 120 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const bookSubmitFields = [
|
||||||
|
'id',
|
||||||
|
'title',
|
||||||
|
'subtitle',
|
||||||
|
'bookType',
|
||||||
|
'eraTag',
|
||||||
|
'coverUrl',
|
||||||
|
'publisher',
|
||||||
|
'publishedAt',
|
||||||
|
'intro',
|
||||||
|
'hotScore',
|
||||||
|
'rating',
|
||||||
|
'commentCount',
|
||||||
|
'wordCount',
|
||||||
|
'completionStatus',
|
||||||
|
'publishStatus',
|
||||||
|
'seriesId',
|
||||||
|
'seriesSort',
|
||||||
|
'rawTxtUrl'
|
||||||
|
]
|
||||||
|
|
||||||
|
const chapterSubmitFields = [
|
||||||
|
'id',
|
||||||
|
'bookId',
|
||||||
|
'title',
|
||||||
|
'chapterNo',
|
||||||
|
'isReadable',
|
||||||
|
'contentFileUrl',
|
||||||
|
'totalLines',
|
||||||
|
'isEnabled'
|
||||||
|
]
|
||||||
|
|
||||||
|
const authorSubmitFields = ['id', 'name', 'isEnabled', 'intro', 'coverUrl']
|
||||||
|
const seriesSubmitFields = ['id', 'name', 'coverUrl', 'intro', 'isEnabled']
|
||||||
|
const commentSubmitFields = ['id', 'memberUserId', 'bookId', 'chapterId', 'lineIndex', 'content', 'likeCount', 'commentStatus']
|
||||||
|
const readRecordSubmitFields = ['id', 'memberUserId', 'bookId', 'bookTitleSnapshot', 'readProgress', 'chapterId', 'lineIndex', 'lastReadAt']
|
||||||
|
const favoriteRecordSubmitFields = ['id', 'memberUserId', 'bookId', 'favoritedAt']
|
||||||
|
const commentLikeRecordSubmitFields = ['id', 'commentId', 'memberUserId', 'likedAt']
|
||||||
|
|
||||||
export const bookAdminPageConfigs = [
|
export const bookAdminPageConfigs = [
|
||||||
{
|
{
|
||||||
@@ -37,6 +82,7 @@ export const bookAdminPageConfigs = [
|
|||||||
find: 'findBook',
|
find: 'findBook',
|
||||||
list: 'getBookList'
|
list: 'getBookList'
|
||||||
},
|
},
|
||||||
|
submitFields: bookSubmitFields,
|
||||||
searchFields: [
|
searchFields: [
|
||||||
textField('书名', 'title'),
|
textField('书名', 'title'),
|
||||||
dictField('书籍类型', 'bookType', 'book_type'),
|
dictField('书籍类型', 'bookType', 'book_type'),
|
||||||
@@ -79,9 +125,9 @@ export const bookAdminPageConfigs = [
|
|||||||
{ prop: 'completionStatus', label: '完结状态', dict: 'book_completion_status' }
|
{ prop: 'completionStatus', label: '完结状态', dict: 'book_completion_status' }
|
||||||
],
|
],
|
||||||
related: [
|
related: [
|
||||||
{ title: '关联作者', api: 'getBookAuthorRelationList', filterProp: 'bookId', columns: ['authorId', 'authorSort'] },
|
{ title: '关联作者', api: 'getBookAuthorRelationList', filterProp: 'bookId', columns: ['authorName', 'authorId', 'authorSort'] },
|
||||||
{ title: '关联章节', api: 'getBookChapterList', filterProp: 'bookId', columns: ['id', 'title', 'chapterNo', 'isReadable', 'isEnabled'] },
|
{ title: '关联章节', api: 'getBookChapterList', filterProp: 'bookId', columns: ['id', 'title', 'chapterNo', 'isReadable', 'isEnabled'] },
|
||||||
{ title: '关联评论', api: 'getBookCommentList', filterProp: 'bookId', columns: ['id', 'memberUserId', 'chapterId', 'lineIndex', 'commentStatus'] }
|
{ title: '关联评论', api: 'getBookCommentList', filterProp: 'bookId', columns: ['id', 'memberUserId', 'chapterTitle', 'chapterId', 'lineIndex', 'commentStatus'] }
|
||||||
],
|
],
|
||||||
authorBinding: true
|
authorBinding: true
|
||||||
},
|
},
|
||||||
@@ -97,6 +143,7 @@ export const bookAdminPageConfigs = [
|
|||||||
find: 'findBookChapter',
|
find: 'findBookChapter',
|
||||||
list: 'getBookChapterList'
|
list: 'getBookChapterList'
|
||||||
},
|
},
|
||||||
|
submitFields: chapterSubmitFields,
|
||||||
searchFields: [
|
searchFields: [
|
||||||
relationField('所属书籍', 'bookId', { listApi: 'getBookList', labelProp: 'title', valueProp: 'id', searchProp: 'title' }),
|
relationField('所属书籍', 'bookId', { listApi: 'getBookList', labelProp: 'title', valueProp: 'id', searchProp: 'title' }),
|
||||||
switchField('开放阅读', 'isReadable'),
|
switchField('开放阅读', 'isReadable'),
|
||||||
@@ -105,6 +152,7 @@ export const bookAdminPageConfigs = [
|
|||||||
tableColumns: [
|
tableColumns: [
|
||||||
idColumn,
|
idColumn,
|
||||||
{ label: '章节标题', prop: 'title', minWidth: 180 },
|
{ label: '章节标题', prop: 'title', minWidth: 180 },
|
||||||
|
{ label: '书名', prop: 'bookTitle', minWidth: 180 },
|
||||||
{ label: '书籍 ID', prop: 'bookId', width: 100 },
|
{ label: '书籍 ID', prop: 'bookId', width: 100 },
|
||||||
{ label: '章节序号', prop: 'chapterNo', width: 110 },
|
{ label: '章节序号', prop: 'chapterNo', width: 110 },
|
||||||
{ label: '开放阅读', prop: 'isReadable', type: 'boolean', width: 100 },
|
{ label: '开放阅读', prop: 'isReadable', type: 'boolean', width: 100 },
|
||||||
@@ -138,28 +186,29 @@ export const bookAdminPageConfigs = [
|
|||||||
find: 'findBookAuthor',
|
find: 'findBookAuthor',
|
||||||
list: 'getBookAuthorList'
|
list: 'getBookAuthorList'
|
||||||
},
|
},
|
||||||
|
submitFields: authorSubmitFields,
|
||||||
searchFields: [
|
searchFields: [
|
||||||
textField('作者名称', 'name'),
|
textField('作者名称', 'name'),
|
||||||
dictField('作者状态', 'authorStatus', 'book_author_status')
|
switchField('启用状态', 'isEnabled')
|
||||||
],
|
],
|
||||||
tableColumns: [
|
tableColumns: [
|
||||||
idColumn,
|
idColumn,
|
||||||
{ label: '作者名称', prop: 'name', minWidth: 160 },
|
{ label: '作者名称', prop: 'name', minWidth: 160 },
|
||||||
{ label: '作者状态', prop: 'authorStatus', type: 'dict', dict: 'book_author_status', width: 120 },
|
{ label: '启用状态', prop: 'isEnabled', type: 'boolean', width: 100 },
|
||||||
{ label: '头像/封面 URL', prop: 'coverUrl', minWidth: 180 },
|
{ label: '头像/封面 URL', prop: 'coverUrl', minWidth: 180 },
|
||||||
dateColumn
|
dateColumn
|
||||||
],
|
],
|
||||||
fields: [
|
fields: [
|
||||||
textField('作者名称', 'name', { required: true }),
|
textField('作者名称', 'name', { required: true }),
|
||||||
dictField('作者状态', 'authorStatus', 'book_author_status', { required: true, defaultValue: 'enabled' }),
|
switchField('作者是否启用', 'isEnabled', { defaultValue: true }),
|
||||||
textareaField('作者简介', 'intro'),
|
textareaField('作者简介', 'intro'),
|
||||||
imageField('作者头像或封面', 'coverUrl')
|
imageField('作者头像或封面', 'coverUrl')
|
||||||
],
|
],
|
||||||
statusFields: [
|
statusFields: [
|
||||||
{ prop: 'authorStatus', label: '作者状态', dict: 'book_author_status' }
|
{ prop: 'isEnabled', label: '启用状态', type: 'boolean' }
|
||||||
],
|
],
|
||||||
related: [
|
related: [
|
||||||
{ title: '关联书籍', api: 'getBookAuthorRelationList', filterProp: 'authorId', columns: ['bookId', 'authorSort'] }
|
{ title: '关联书籍', api: 'getBookAuthorRelationList', filterProp: 'authorId', columns: ['bookTitle', 'bookId', 'authorSort'] }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -174,6 +223,7 @@ export const bookAdminPageConfigs = [
|
|||||||
find: 'findBookSeries',
|
find: 'findBookSeries',
|
||||||
list: 'getBookSeriesList'
|
list: 'getBookSeriesList'
|
||||||
},
|
},
|
||||||
|
submitFields: seriesSubmitFields,
|
||||||
searchFields: [
|
searchFields: [
|
||||||
textField('系列名称', 'name'),
|
textField('系列名称', 'name'),
|
||||||
switchField('启用状态', 'isEnabled')
|
switchField('启用状态', 'isEnabled')
|
||||||
@@ -195,7 +245,18 @@ export const bookAdminPageConfigs = [
|
|||||||
{ prop: 'isEnabled', label: '启用状态', type: 'boolean' }
|
{ prop: 'isEnabled', label: '启用状态', type: 'boolean' }
|
||||||
],
|
],
|
||||||
related: [
|
related: [
|
||||||
{ title: '系列下书籍', api: 'getBookList', filterProp: 'seriesId', columns: ['id', 'title', 'publishStatus', 'completionStatus', 'seriesSort'] }
|
{
|
||||||
|
title: '系列下书籍',
|
||||||
|
api: 'getBookList',
|
||||||
|
filterProp: 'seriesId',
|
||||||
|
columns: [
|
||||||
|
relatedColumn('ID', 'id'),
|
||||||
|
relatedColumn('书名', 'title'),
|
||||||
|
relatedColumn('上下架状态', 'publishStatus', { type: 'dict', dict: 'book_publish_status' }),
|
||||||
|
relatedColumn('完结状态', 'completionStatus', { type: 'dict', dict: 'book_completion_status' }),
|
||||||
|
relatedColumn('同系列排序', 'seriesSort')
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -210,6 +271,7 @@ export const bookAdminPageConfigs = [
|
|||||||
find: 'findBookComment',
|
find: 'findBookComment',
|
||||||
list: 'getBookCommentList'
|
list: 'getBookCommentList'
|
||||||
},
|
},
|
||||||
|
submitFields: commentSubmitFields,
|
||||||
searchFields: [
|
searchFields: [
|
||||||
numberField('会员用户 ID', 'memberUserId'),
|
numberField('会员用户 ID', 'memberUserId'),
|
||||||
numberField('书籍 ID', 'bookId'),
|
numberField('书籍 ID', 'bookId'),
|
||||||
@@ -219,7 +281,9 @@ export const bookAdminPageConfigs = [
|
|||||||
tableColumns: [
|
tableColumns: [
|
||||||
idColumn,
|
idColumn,
|
||||||
{ label: '会员用户 ID', prop: 'memberUserId', width: 120 },
|
{ label: '会员用户 ID', prop: 'memberUserId', width: 120 },
|
||||||
|
{ label: '书名', prop: 'bookTitle', minWidth: 180 },
|
||||||
{ label: '书籍 ID', prop: 'bookId', width: 100 },
|
{ label: '书籍 ID', prop: 'bookId', width: 100 },
|
||||||
|
{ label: '章节标题', prop: 'chapterTitle', minWidth: 180 },
|
||||||
{ label: '章节 ID', prop: 'chapterId', width: 100 },
|
{ label: '章节 ID', prop: 'chapterId', width: 100 },
|
||||||
{ label: '行序号', prop: 'lineIndex', width: 90 },
|
{ label: '行序号', prop: 'lineIndex', width: 90 },
|
||||||
{ label: '评论内容', prop: 'content', minWidth: 220 },
|
{ label: '评论内容', prop: 'content', minWidth: 220 },
|
||||||
@@ -253,6 +317,7 @@ export const bookAdminPageConfigs = [
|
|||||||
find: 'findBookReadRecord',
|
find: 'findBookReadRecord',
|
||||||
list: 'getBookReadRecordList'
|
list: 'getBookReadRecordList'
|
||||||
},
|
},
|
||||||
|
submitFields: readRecordSubmitFields,
|
||||||
searchFields: [numberField('会员用户 ID', 'memberUserId'), numberField('书籍 ID', 'bookId')],
|
searchFields: [numberField('会员用户 ID', 'memberUserId'), numberField('书籍 ID', 'bookId')],
|
||||||
tableColumns: [
|
tableColumns: [
|
||||||
idColumn,
|
idColumn,
|
||||||
@@ -260,6 +325,7 @@ export const bookAdminPageConfigs = [
|
|||||||
{ label: '书籍 ID', prop: 'bookId', width: 100 },
|
{ label: '书籍 ID', prop: 'bookId', width: 100 },
|
||||||
{ label: '书名快照', prop: 'bookTitleSnapshot', minWidth: 180 },
|
{ label: '书名快照', prop: 'bookTitleSnapshot', minWidth: 180 },
|
||||||
{ label: '阅读进度', prop: 'readProgress', width: 100 },
|
{ label: '阅读进度', prop: 'readProgress', width: 100 },
|
||||||
|
{ label: '章节标题', prop: 'chapterTitle', minWidth: 180 },
|
||||||
{ label: '续读章节 ID', prop: 'chapterId', width: 120 },
|
{ label: '续读章节 ID', prop: 'chapterId', width: 120 },
|
||||||
{ label: '续读行序号', prop: 'lineIndex', width: 120 },
|
{ label: '续读行序号', prop: 'lineIndex', width: 120 },
|
||||||
{ label: '最后阅读时间', prop: 'lastReadAt', type: 'datetime', width: 180 }
|
{ label: '最后阅读时间', prop: 'lastReadAt', type: 'datetime', width: 180 }
|
||||||
@@ -287,10 +353,12 @@ export const bookAdminPageConfigs = [
|
|||||||
find: 'findBookFavoriteRecord',
|
find: 'findBookFavoriteRecord',
|
||||||
list: 'getBookFavoriteRecordList'
|
list: 'getBookFavoriteRecordList'
|
||||||
},
|
},
|
||||||
|
submitFields: favoriteRecordSubmitFields,
|
||||||
searchFields: [numberField('会员用户 ID', 'memberUserId'), numberField('书籍 ID', 'bookId')],
|
searchFields: [numberField('会员用户 ID', 'memberUserId'), numberField('书籍 ID', 'bookId')],
|
||||||
tableColumns: [
|
tableColumns: [
|
||||||
idColumn,
|
idColumn,
|
||||||
{ label: '会员用户 ID', prop: 'memberUserId', width: 120 },
|
{ label: '会员用户 ID', prop: 'memberUserId', width: 120 },
|
||||||
|
{ label: '书名', prop: 'bookTitle', minWidth: 180 },
|
||||||
{ label: '书籍 ID', prop: 'bookId', width: 100 },
|
{ label: '书籍 ID', prop: 'bookId', width: 100 },
|
||||||
{ label: '收藏时间', prop: 'favoritedAt', type: 'datetime', width: 180 },
|
{ label: '收藏时间', prop: 'favoritedAt', type: 'datetime', width: 180 },
|
||||||
dateColumn
|
dateColumn
|
||||||
@@ -314,9 +382,12 @@ export const bookAdminPageConfigs = [
|
|||||||
find: 'findBookCommentLikeRecord',
|
find: 'findBookCommentLikeRecord',
|
||||||
list: 'getBookCommentLikeRecordList'
|
list: 'getBookCommentLikeRecordList'
|
||||||
},
|
},
|
||||||
|
submitFields: commentLikeRecordSubmitFields,
|
||||||
searchFields: [numberField('评论 ID', 'commentId'), numberField('会员用户 ID', 'memberUserId')],
|
searchFields: [numberField('评论 ID', 'commentId'), numberField('会员用户 ID', 'memberUserId')],
|
||||||
tableColumns: [
|
tableColumns: [
|
||||||
idColumn,
|
idColumn,
|
||||||
|
{ label: '书名', prop: 'bookTitle', minWidth: 180 },
|
||||||
|
{ label: '评论内容', prop: 'commentContent', minWidth: 220 },
|
||||||
{ label: '评论 ID', prop: 'commentId', width: 100 },
|
{ label: '评论 ID', prop: 'commentId', width: 100 },
|
||||||
{ label: '会员用户 ID', prop: 'memberUserId', width: 120 },
|
{ label: '会员用户 ID', prop: 'memberUserId', width: 120 },
|
||||||
{ label: '点赞时间', prop: 'likedAt', type: 'datetime', width: 180 },
|
{ label: '点赞时间', prop: 'likedAt', type: 'datetime', width: 180 },
|
||||||
|
|||||||
@@ -1,13 +1,44 @@
|
|||||||
import assert from 'node:assert/strict'
|
import assert from 'node:assert/strict'
|
||||||
import { bookAdminPageConfigs } from './bookAdminConfig.js'
|
import { bookAdminPageConfigs, bookAuthorRelationDisplayColumns } from './bookAdminConfig.js'
|
||||||
import { isBookAdminImageColumn, resolveBookAdminColumnType } from './bookAdminDisplay.js'
|
import { isBookAdminImageColumn, resolveBookAdminColumnType } from './bookAdminDisplay.js'
|
||||||
|
|
||||||
const getConfig = (key) => bookAdminPageConfigs.find((item) => item.key === key)
|
const getConfig = (key) => bookAdminPageConfigs.find((item) => item.key === key)
|
||||||
const getColumn = (config, prop) => config.tableColumns.find((item) => item.prop === prop)
|
const getColumn = (config, prop) => config.tableColumns.find((item) => item.prop === prop)
|
||||||
|
const getRelated = (config, title) => config.related.find((item) => item.title === title)
|
||||||
|
const getRelatedColumn = (related, prop) => related.columns.find((item) => (typeof item === 'string' ? item : item.prop) === prop)
|
||||||
|
|
||||||
const authorConfig = getConfig('author')
|
const authorConfig = getConfig('author')
|
||||||
|
const bookConfig = getConfig('book')
|
||||||
|
const chapterConfig = getConfig('chapter')
|
||||||
|
const commentConfig = getConfig('comment')
|
||||||
|
const commentLikeRecordConfig = getConfig('commentLikeRecord')
|
||||||
|
const favoriteRecordConfig = getConfig('favoriteRecord')
|
||||||
|
const readRecordConfig = getConfig('readRecord')
|
||||||
const seriesConfig = getConfig('series')
|
const seriesConfig = getConfig('series')
|
||||||
|
|
||||||
|
assert.equal(getColumn(authorConfig, 'isEnabled').type, 'boolean')
|
||||||
|
assert.equal(authorConfig.searchFields.find((item) => item.prop === 'isEnabled').type, 'switch')
|
||||||
|
assert.equal(authorConfig.fields.find((item) => item.prop === 'isEnabled').type, 'switch')
|
||||||
|
assert.equal(authorConfig.fields.find((item) => item.prop === 'isEnabled').defaultValue, true)
|
||||||
|
assert.equal(authorConfig.statusFields.find((item) => item.prop === 'isEnabled').type, 'boolean')
|
||||||
|
assert.equal(authorConfig.searchFields.some((item) => item.prop === 'authorStatus'), false)
|
||||||
|
assert.equal(authorConfig.fields.some((item) => item.prop === 'authorStatus'), false)
|
||||||
|
assert.deepEqual(bookAuthorRelationDisplayColumns.map((item) => item.prop), ['authorName', 'authorId'])
|
||||||
|
assert.deepEqual(getRelated(bookConfig, '关联作者').columns, ['authorName', 'authorId', 'authorSort'])
|
||||||
|
assert.deepEqual(getRelated(authorConfig, '关联书籍').columns, ['bookTitle', 'bookId', 'authorSort'])
|
||||||
|
assert.deepEqual(getRelated(bookConfig, '关联评论').columns, ['id', 'memberUserId', 'chapterTitle', 'chapterId', 'lineIndex', 'commentStatus'])
|
||||||
|
assert.equal(getColumn(chapterConfig, 'bookTitle')?.label, '书名')
|
||||||
|
assert.equal(getColumn(commentConfig, 'bookTitle')?.label, '书名')
|
||||||
|
assert.equal(getColumn(commentConfig, 'chapterTitle')?.label, '章节标题')
|
||||||
|
assert.equal(getColumn(commentLikeRecordConfig, 'bookTitle')?.label, '书名')
|
||||||
|
assert.equal(getColumn(commentLikeRecordConfig, 'commentContent')?.label, '评论内容')
|
||||||
|
assert.equal(getColumn(favoriteRecordConfig, 'bookTitle')?.label, '书名')
|
||||||
|
assert.equal(getColumn(readRecordConfig, 'chapterTitle')?.label, '章节标题')
|
||||||
|
assert.equal(getRelatedColumn(getRelated(seriesConfig, '系列下书籍'), 'publishStatus').type, 'dict')
|
||||||
|
assert.equal(getRelatedColumn(getRelated(seriesConfig, '系列下书籍'), 'publishStatus').dict, 'book_publish_status')
|
||||||
|
assert.equal(getRelatedColumn(getRelated(seriesConfig, '系列下书籍'), 'completionStatus').type, 'dict')
|
||||||
|
assert.equal(getRelatedColumn(getRelated(seriesConfig, '系列下书籍'), 'completionStatus').dict, 'book_completion_status')
|
||||||
|
|
||||||
assert.equal(resolveBookAdminColumnType(authorConfig, getColumn(authorConfig, 'coverUrl')), 'image')
|
assert.equal(resolveBookAdminColumnType(authorConfig, getColumn(authorConfig, 'coverUrl')), 'image')
|
||||||
assert.equal(resolveBookAdminColumnType(seriesConfig, getColumn(seriesConfig, 'coverUrl')), 'image')
|
assert.equal(resolveBookAdminColumnType(seriesConfig, getColumn(seriesConfig, 'coverUrl')), 'image')
|
||||||
|
|
||||||
|
|||||||
31
web/src/view/book/config/bookAdminPayload.js
Normal file
31
web/src/view/book/config/bookAdminPayload.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
export const buildBookAdminSubmitPayload = (config, formData, dialogType) => {
|
||||||
|
if (!config?.submitFields?.length) return formData
|
||||||
|
|
||||||
|
const source = formData || {}
|
||||||
|
const fields = dialogType === 'update'
|
||||||
|
? config.submitFields
|
||||||
|
: config.submitFields.filter((prop) => prop !== 'id')
|
||||||
|
|
||||||
|
return fields.reduce((payload, prop) => {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(source, prop)) {
|
||||||
|
payload[prop] = source[prop]
|
||||||
|
}
|
||||||
|
return payload
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
const bookAuthorRelationSubmitFields = ['id', 'bookId', 'authorId', 'authorSort']
|
||||||
|
|
||||||
|
export const buildBookAuthorRelationSubmitPayload = (formData, dialogType) => {
|
||||||
|
const source = formData || {}
|
||||||
|
const fields = dialogType === 'update'
|
||||||
|
? bookAuthorRelationSubmitFields
|
||||||
|
: bookAuthorRelationSubmitFields.filter((prop) => prop !== 'id')
|
||||||
|
|
||||||
|
return fields.reduce((payload, prop) => {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(source, prop)) {
|
||||||
|
payload[prop] = source[prop]
|
||||||
|
}
|
||||||
|
return payload
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
303
web/src/view/book/config/bookAdminPayload.test.mjs
Normal file
303
web/src/view/book/config/bookAdminPayload.test.mjs
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import { bookAdminPageConfigs } from './bookAdminConfig.js'
|
||||||
|
import { buildBookAdminSubmitPayload, buildBookAuthorRelationSubmitPayload } from './bookAdminPayload.js'
|
||||||
|
|
||||||
|
const getConfig = (key) => bookAdminPageConfigs.find((item) => item.key === key)
|
||||||
|
|
||||||
|
const authorConfig = getConfig('author')
|
||||||
|
const bookConfig = getConfig('book')
|
||||||
|
const chapterConfig = getConfig('chapter')
|
||||||
|
const commentConfig = getConfig('comment')
|
||||||
|
const commentLikeRecordConfig = getConfig('commentLikeRecord')
|
||||||
|
const favoriteRecordConfig = getConfig('favoriteRecord')
|
||||||
|
const readRecordConfig = getConfig('readRecord')
|
||||||
|
const seriesConfig = getConfig('series')
|
||||||
|
|
||||||
|
assert.equal(bookAdminPageConfigs.every((config) => Array.isArray(config.submitFields) && config.submitFields.length > 0), true)
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
buildBookAdminSubmitPayload(
|
||||||
|
authorConfig,
|
||||||
|
{
|
||||||
|
id: 12,
|
||||||
|
name: '鲁迅',
|
||||||
|
isEnabled: false,
|
||||||
|
intro: '作者简介',
|
||||||
|
coverUrl: '/uploads/author.png',
|
||||||
|
authorName: '鲁迅',
|
||||||
|
createdAt: '2026-04-01 10:00:00',
|
||||||
|
updatedAt: '2026-04-02 10:00:00'
|
||||||
|
},
|
||||||
|
'update'
|
||||||
|
),
|
||||||
|
{
|
||||||
|
id: 12,
|
||||||
|
name: '鲁迅',
|
||||||
|
isEnabled: false,
|
||||||
|
intro: '作者简介',
|
||||||
|
coverUrl: '/uploads/author.png'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
buildBookAdminSubmitPayload(
|
||||||
|
authorConfig,
|
||||||
|
{
|
||||||
|
id: 12,
|
||||||
|
name: '鲁迅',
|
||||||
|
isEnabled: true,
|
||||||
|
intro: '',
|
||||||
|
coverUrl: '',
|
||||||
|
createdAt: '2026-04-01 10:00:00'
|
||||||
|
},
|
||||||
|
'create'
|
||||||
|
),
|
||||||
|
{
|
||||||
|
name: '鲁迅',
|
||||||
|
isEnabled: true,
|
||||||
|
intro: '',
|
||||||
|
coverUrl: ''
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
buildBookAdminSubmitPayload(
|
||||||
|
authorConfig,
|
||||||
|
{
|
||||||
|
id: 13,
|
||||||
|
name: '禁用作者',
|
||||||
|
isEnabled: false,
|
||||||
|
intro: '',
|
||||||
|
coverUrl: '',
|
||||||
|
createdAt: '2026-04-01 10:00:00'
|
||||||
|
},
|
||||||
|
'create'
|
||||||
|
),
|
||||||
|
{
|
||||||
|
name: '禁用作者',
|
||||||
|
isEnabled: false,
|
||||||
|
intro: '',
|
||||||
|
coverUrl: ''
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
buildBookAdminSubmitPayload(
|
||||||
|
bookConfig,
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: '测试书籍',
|
||||||
|
subtitle: '副标题',
|
||||||
|
bookType: 'novel',
|
||||||
|
eraTag: 'unknown',
|
||||||
|
coverUrl: '/uploads/book.png',
|
||||||
|
publisher: '测试出版社',
|
||||||
|
publishedAt: '2026-04-01',
|
||||||
|
intro: '简介',
|
||||||
|
hotScore: 0,
|
||||||
|
rating: 8.5,
|
||||||
|
commentCount: 0,
|
||||||
|
wordCount: 1000,
|
||||||
|
completionStatus: 'serializing',
|
||||||
|
publishStatus: 'draft',
|
||||||
|
seriesId: 3,
|
||||||
|
seriesSort: 0,
|
||||||
|
rawTxtUrl: '/uploads/book.txt',
|
||||||
|
createdAt: '2026-04-01 10:00:00',
|
||||||
|
updatedAt: '2026-04-02 10:00:00'
|
||||||
|
},
|
||||||
|
'update'
|
||||||
|
),
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: '测试书籍',
|
||||||
|
subtitle: '副标题',
|
||||||
|
bookType: 'novel',
|
||||||
|
eraTag: 'unknown',
|
||||||
|
coverUrl: '/uploads/book.png',
|
||||||
|
publisher: '测试出版社',
|
||||||
|
publishedAt: '2026-04-01',
|
||||||
|
intro: '简介',
|
||||||
|
hotScore: 0,
|
||||||
|
rating: 8.5,
|
||||||
|
commentCount: 0,
|
||||||
|
wordCount: 1000,
|
||||||
|
completionStatus: 'serializing',
|
||||||
|
publishStatus: 'draft',
|
||||||
|
seriesId: 3,
|
||||||
|
seriesSort: 0,
|
||||||
|
rawTxtUrl: '/uploads/book.txt'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
buildBookAdminSubmitPayload(chapterConfig, {
|
||||||
|
id: 9,
|
||||||
|
bookId: 1,
|
||||||
|
title: '第一章',
|
||||||
|
chapterNo: 1,
|
||||||
|
isReadable: false,
|
||||||
|
contentFileUrl: '/uploads/chapter.txt',
|
||||||
|
totalLines: 0,
|
||||||
|
isEnabled: false,
|
||||||
|
createdAt: '2026-04-01 10:00:00'
|
||||||
|
}, 'update'),
|
||||||
|
{
|
||||||
|
id: 9,
|
||||||
|
bookId: 1,
|
||||||
|
title: '第一章',
|
||||||
|
chapterNo: 1,
|
||||||
|
isReadable: false,
|
||||||
|
contentFileUrl: '/uploads/chapter.txt',
|
||||||
|
totalLines: 0,
|
||||||
|
isEnabled: false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
buildBookAdminSubmitPayload(chapterConfig, {
|
||||||
|
id: 10,
|
||||||
|
bookId: 1,
|
||||||
|
title: '第二章',
|
||||||
|
chapterNo: 2,
|
||||||
|
isReadable: false,
|
||||||
|
contentFileUrl: '/uploads/chapter-2.txt',
|
||||||
|
totalLines: 0,
|
||||||
|
isEnabled: false,
|
||||||
|
createdAt: '2026-04-01 10:00:00'
|
||||||
|
}, 'create'),
|
||||||
|
{
|
||||||
|
bookId: 1,
|
||||||
|
title: '第二章',
|
||||||
|
chapterNo: 2,
|
||||||
|
isReadable: false,
|
||||||
|
contentFileUrl: '/uploads/chapter-2.txt',
|
||||||
|
totalLines: 0,
|
||||||
|
isEnabled: false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
buildBookAdminSubmitPayload(seriesConfig, {
|
||||||
|
id: 6,
|
||||||
|
name: '系列',
|
||||||
|
coverUrl: '',
|
||||||
|
intro: '',
|
||||||
|
isEnabled: false,
|
||||||
|
updatedAt: '2026-04-02 10:00:00'
|
||||||
|
}, 'create'),
|
||||||
|
{
|
||||||
|
name: '系列',
|
||||||
|
coverUrl: '',
|
||||||
|
intro: '',
|
||||||
|
isEnabled: false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
buildBookAdminSubmitPayload(commentConfig, {
|
||||||
|
id: 31,
|
||||||
|
memberUserId: 8,
|
||||||
|
bookId: 1,
|
||||||
|
bookTitle: '边城',
|
||||||
|
chapterId: 4,
|
||||||
|
chapterTitle: '第一章 茶峒',
|
||||||
|
lineIndex: 6,
|
||||||
|
content: '这段很有画面感',
|
||||||
|
likeCount: 2,
|
||||||
|
commentStatus: 'normal',
|
||||||
|
createdAt: '2026-04-01 10:00:00'
|
||||||
|
}, 'update'),
|
||||||
|
{
|
||||||
|
id: 31,
|
||||||
|
memberUserId: 8,
|
||||||
|
bookId: 1,
|
||||||
|
chapterId: 4,
|
||||||
|
lineIndex: 6,
|
||||||
|
content: '这段很有画面感',
|
||||||
|
likeCount: 2,
|
||||||
|
commentStatus: 'normal'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
buildBookAdminSubmitPayload(readRecordConfig, {
|
||||||
|
id: 41,
|
||||||
|
memberUserId: 8,
|
||||||
|
bookId: 1,
|
||||||
|
bookTitleSnapshot: '边城',
|
||||||
|
readProgress: 33.5,
|
||||||
|
chapterId: 4,
|
||||||
|
chapterTitle: '第一章 茶峒',
|
||||||
|
lineIndex: 10,
|
||||||
|
lastReadAt: '2026-04-02 10:00:00',
|
||||||
|
updatedAt: '2026-04-02 10:00:00'
|
||||||
|
}, 'update'),
|
||||||
|
{
|
||||||
|
id: 41,
|
||||||
|
memberUserId: 8,
|
||||||
|
bookId: 1,
|
||||||
|
bookTitleSnapshot: '边城',
|
||||||
|
readProgress: 33.5,
|
||||||
|
chapterId: 4,
|
||||||
|
lineIndex: 10,
|
||||||
|
lastReadAt: '2026-04-02 10:00:00'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
buildBookAdminSubmitPayload(favoriteRecordConfig, {
|
||||||
|
id: 51,
|
||||||
|
memberUserId: 8,
|
||||||
|
bookId: 1,
|
||||||
|
bookTitle: '边城',
|
||||||
|
favoritedAt: '2026-04-02 10:00:00',
|
||||||
|
createdAt: '2026-04-01 10:00:00'
|
||||||
|
}, 'update'),
|
||||||
|
{
|
||||||
|
id: 51,
|
||||||
|
memberUserId: 8,
|
||||||
|
bookId: 1,
|
||||||
|
favoritedAt: '2026-04-02 10:00:00'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
buildBookAdminSubmitPayload(commentLikeRecordConfig, {
|
||||||
|
id: 61,
|
||||||
|
commentId: 31,
|
||||||
|
memberUserId: 8,
|
||||||
|
bookTitle: '边城',
|
||||||
|
commentContent: '这段很有画面感',
|
||||||
|
likedAt: '2026-04-02 10:00:00',
|
||||||
|
updatedAt: '2026-04-02 10:00:00'
|
||||||
|
}, 'update'),
|
||||||
|
{
|
||||||
|
id: 61,
|
||||||
|
commentId: 31,
|
||||||
|
memberUserId: 8,
|
||||||
|
likedAt: '2026-04-02 10:00:00'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
buildBookAuthorRelationSubmitPayload(
|
||||||
|
{
|
||||||
|
id: 21,
|
||||||
|
bookId: 3,
|
||||||
|
authorId: 12,
|
||||||
|
authorSort: 2,
|
||||||
|
bookTitle: '呐喊',
|
||||||
|
authorName: '鲁迅',
|
||||||
|
createdAt: '2026-04-01 10:00:00',
|
||||||
|
updatedAt: '2026-04-02 10:00:00'
|
||||||
|
},
|
||||||
|
'update'
|
||||||
|
),
|
||||||
|
{
|
||||||
|
id: 21,
|
||||||
|
bookId: 3,
|
||||||
|
authorId: 12,
|
||||||
|
authorSort: 2
|
||||||
|
}
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user