当前位置:网站首页>EBook list page
EBook list page
2022-06-12 05:55:00 【Snow flies fast】
EBook editing
The page display

- Deleting the e-book will emit
onRemoveMethod , When this method is executed, the data is assigned a default value - Click the tree menu to open a new one tabs
<template>
<ebook-upload :file-list="fileList" :disabled="isEdit" @onSuccess="onUploadSucess" @onRemove="onUploadRemove" />
<el-tree :data="contentsTree" @node-click="onContentClick" />
</template>
<script> const defaultForm = {
title: '', author: '', publisher: '', language: '', rootFile: '', filePath: '', unzipPath: '', coverPath: '', cover: '', originalName: '' } export default {
methods: {
setData(data) {
const {
title, author, publisher, language, rootFile, cover, originalName, contents, coverPath, filePath, unzipPath, contentsTree } = data this.postForm = {
...this.postForm, title, // Title author, // author publisher, // Press. language, // Language rootFile, // Root file path filePath, // File path unzipPath, // The path where the extracted file is located coverPath, // Cover picture path cover, // Cover picture URL contents, // Catalog originalName // file name } this.contentsTree = contentsTree }, setDefault() {
this.postForm = Object.assign({
}, defaultForm) this.contentsTree = [] }, onContentClick(data) {
if (data.text) {
window.open(data.text) } }, onUploadSucess(data) {
this.setData(data) }, onUploadRemove() {
this.setDefault() } } } </script>
problem : The length is not the same
newNavMap and epub.flow Length inconsistency
newNavMap: Information in the directoryepub.flow: Represents the reading order of the entire e-book reader , Some contents may not be reflected in the chapter contents
const path = require('path')
class Book {
// ...
const dir = path.dirname(ncxFilePath).replace(UPLOAD_PATH, '')
// ...
newNavMap.forEach((chapter, index) => {
const src = chapter.content['$'].src
chapter.text = `${
UPLOAD_URL}${
dir}/$`
chapter.label = chapter.navLabel.text || ''
chapter.navId = chapter['$'].id
chapter.filename = filename
chapter.order = index + 1
chapters.push(chapter)
})
}
Form verification
Add... To editable form items
propadopt
fields[Object.keys(fields)[0]][0].messageGet error message
In this way, the effect shown in the figure below can be achieved

const fields = {
title: ' title ',
author: ' author ',
publisher: ' Press. ',
language: ' Language '
}
export default {
data() {
const validateRequire = (rule, value, callback) => {
if (value.length === 0) {
callback(new Error(fields[rule.field] + ' Must fill in '))
} else {
callback()
}
}
return {
rules: {
title: [{
validator: validateRequire }],
author: [{
validator: validateRequire }],
publisher: [{
validator: validateRequire }],
language: [{
validator: validateRequire }]
}
}
},
methods: {
submitForm() {
this.$refs.postForm.validate((valid, fields) => {
if (valid) {
//...
} else {
const message = fields[Object.keys(fields)[0]][0].message
this.$message({
message,
type: 'error',
showClose: true
})
}
})
}
}
}
Submit Form
You need to provide two interfaces when submitting forms ,createBook and updateBook
src\api\book.js
import request from '@/utils/request'
export function createBook(book) {
return request({
url: '/book/create',
method: 'post',
data: book
})
}
modify submitForm Method
import {
createBook } from '@/api/book'
if (valid) {
const book = Object.assign({
}, this.postForm)
delete book.status
delete book.contentsTree
if (!this.isEdit) {
createBook(book)
.then(response => {
const {
msg } = response
this.$notify({
title: ' Successful operation ',
message: msg,
type: 'success',
duration: 2000
})
this.setDefault()
})
.catch(() => {
})
.finally(() => (this.loading = false))
} else {
// updateBook(book)
}
} else {
const message = fields[Object.keys(fields)[0]][0].message
this.$message({
message,
type: 'error',
showClose: true
})
}
After the upload is successful, there will be 2 A question
- After the new form is successfully added, a verification exception will appear when the form is cleared
- The file list was not removed
setDefault() {
//...
this.fileList = []
this.$refs.postForm.resetFields()
}
The backend development API
New books
router\book.jsincreasecreateInterfaceadopt
jwtVerify that the user name is obtained , After thereq.bodyParameters are passed intoBookclass
const {
decoded } = require('../utils')
const bookService = require('../services/book')
router.post('/create', (req, res, next) => {
const decode = decoded(req)
if (decode && decode.username) {
req.body.username = decode.username
}
const book = new Book(null, req.body)
bookService
.insertBook(book)
.then(() => {
new Result(' E-book added successfully ').success(res)
})
.catch(err => next(boom.badImplementation(err)))
})
stay utils\constant.js Fill in the following :
UPDATE_TYPE_FROM_WEB: 1
stay models\Book.js Fill in the following :
Add from
dataObjectBookObject methodsnewly added
toDbMethod , So that when inserting the databaseBookThere are more fields in the object than in the design tablebookThe contents of the design table are as follows :
const {
UPDATE_TYPE_FROM_WEB } = require('../utils/constant')
createBookFromData(data) {
this.fileName = data.fileName
this.cover = data.coverPath
this.title = data.title
this.author = data.author
this.publisher = data.publisher
this.bookId = data.fileName
this.language = data.language
this.rootFile = data.rootFile
this.originalName = data.originalName
this.path = data.path || data.filePath
this.filePath = data.path || data.filePath
this.unzipPath = data.unzipPath
this.coverPath = data.coverPath
this.createUser = data.username
this.createDt = new Date().getTime()
this.updateDt = new Date().getTime()
this.updateType = data.updateType === 0 ? data.updateType : UPDATE_TYPE_FROM_WEB
this.contents = data.contents
this.category = data.category || 99
this.categoryText = data.categoryText || ' Customize '
}
toDb() {
return {
fileName: this.fileName,
cover: this.cover,
title: this.title,
author: this.author,
publisher: this.publisher,
bookId: this.fileName,
language: this.language,
rootFile: this.rootFile,
originalName: this.originalName,
filePath: this.filePath,
unzipPath: this.unzipPath,
coverPath: this.coverPath,
createUser: this.createUser,
createDt: this.createDt,
updateDt: this.updateDt,
updateType: this.updateType,
category: this.category,
categoryText: this.categoryText,
}
}
newly build services\book.js Fill in the following :
- The main task here is to execute
insertMethod , Biography isbook.toDb()
const Book = require('../models/Book')
const db = require('../db')
function exists(book) {
}
function removeBook(book) {
}
function insertContents(book) {
}
function insertBook(book) {
return new Promise(async (resolve, reject) => {
try {
if (book instanceof Book) {
const result = await exists(book)
if (result) {
await removeBook(book)
reject(new Error(' E-books already exist '))
} else {
await db.insert(book.toDb(), 'book')
await insertContents(book)
resolve()
}
} else {
reject(new Error(' The added Book object is illegal '))
}
} catch (e) {
reject(new Error(e))
}
})
}
module.exports = {
insertBook }
stay utils\index.js The following methods are added in the :
function isObject(o) {
return Object.prototype.toString.call(o) === '[object Object]'
}
perfect db\index.js Medium insert.js Method :
- In fact, you can not add
model.hasOwnProperty(), becauseObject.keys()The return includes the object itself ( Without inheritance ) All enumerable properties (enumerable: true)
function insert(model, tableName) {
return new Promise((resolve, reject) => {
if (!isObject(model)) reject(' Insert database failed , Insert data non object ')
const keys = []
const values = []
Object.keys(model).forEach(key => {
if (!model.hasOwnProperty(key)) return
keys.push(`\`${
key}\``) // avoid key And sql Duplicate keyword for
values.push(`'${
model[key]}'`)
})
if (keys.length > 0 && values.length > 0) {
let sql = `INSERT INTO \`${
tableName}\` (`
const keysString = keys.join(',')
const valuesString = values.join(',')
sql = `${
sql}${
keysString}) VALUES (${
valuesString})`
DEBUG && console.log(sql)
const conn = connect()
try {
conn.query(sql, (err, result) => {
if (err) return reject(err)
resolve(result)
})
} catch (e) {
reject(e)
} finally {
conn.end() // Be sure to add , Otherwise, memory leakage will occur
}
} else {
reject(new Error(' Insert database failed , Object doesn't have any properties '))
}
})
}
stay models\Book.js Newly added getContents Method :
getContents() {
return this.contents
}
contents The contents of the design table are as follows :

stay services\book.js New China insertContents Method :
Use
lodashMediumpickMethod , Clean up redundant fields , After cleaning, see the following figure :
const _ = require('lodash')
async function insertContents(book) {
const contents = book.getContents()
if (contents && contents.length > 0) {
for (let i = 0; i < contents.length; i++) {
const content = contents[i]
const _content = _.pick(content, [
'fileName',
'id',
'href',
'text',
'order',
'level',
'label',
'pid',
'navId',
])
await db.insert(_content, 'contents')
}
}
}
Click "add e-book" and the front prompt will appear : E-book added successfully , View that the database has successfully inserted data
E-book duplication check
Determine whether the e-book exists in the database , We can start from three aspects : Title 、 author 、 Press.
stay services\book.js Revision in China exists Method content :
function exists(book) {
const {
title, author, publisher } = book
const sql = `select * from book where title='${
title}' and author='${
author}' and publisher='${
publisher}'`
return db.queryOne(sql)
}
If the ebook exists in the database , From book Table and contents Remove relevant content from the table
async function removeBook(book) {
if (book) {
book.reset()
if (book.fileName) {
const removeBookSql = `delete from book where fileName='${
book.fileName}'`
const removeContentSql = `delete from contents where fileName='${
book.fileName}'`
await db.querySql(removeBookSql)
await db.querySql(removeContentSql)
}
}
}
book Example of reset Method is used to delete the duplicate e-book related resources just uploaded by the server (filePath、coverPath、unzipPath)
fs.unlinkSync: Used to delete the file of the corresponding pathfs.rmdirSync: Used to delete the folder of the corresponding path
reset() {
if (Book.pathExists(this.filePath)) {
fs.unlinkSync(Book.genPath(this.filePath))
}
if (Book.pathExists(this.coverPath)) {
fs.unlinkSync(Book.genPath(this.coverPath))
}
if (Book.pathExists(this.unzipPath)) {
// Be careful node The second attribute... Will not be supported in the lower version ( Iteratively delete )
fs.rmdirSync(Book.genPath(this.unzipPath), {
recursive: true })
}
}
static pathExists(path) {
if (path.startsWith(UPLOAD_PATH)) return fs.existsSync(path)
return fs.existsSync(Book.genPath(path))
}
Since then, the e-book de duplication operation has been completed
E-book query
Modify in the front-end project src\router\index.js stay book/edit After adding :fileName Form a dynamic route
{
path: '/book/edit/:fileName',
component: () => import('@/views/book/edit'),
name: 'bookEdit',
hidden: true,
meta: {
title: ' Editing books ', icon: 'edit', roles: ['admin'], activeMenu: '/book/list' }
},
stay src\views\book\components\Detail.vue New life cycle function in created and getBookData Method
created() {
if (this.isEdit) {
const fileName = this.$route.params.fileName
this.getBookData(fileName)
}
},
methods: {
getBookData(fileName) {
getBook(fileName).then(response => {
this.setData(response.data)
})
},
}
stay src\api\book.js Newly added getBook API:
export function getBook(fileName) {
return request({
url: '/book/get',
method: 'get',
params: {
fileName }
})
}
Then you can start developing getBook The interface . stay router\book.js To add the following :
router.get('/get', (req, res, next) => {
const {
fileName } = req.query
if (!fileName) {
next(boom.badRequest(new Error(' Parameters fileName Can't be empty ')))
} else {
bookService
.getBook(fileName)
.then(book => {
new Result(book, ' Successful acquisition of book information ').success(res)
})
.catch(err => {
next(boom.badImplementation(err))
})
}
})
stay services\book.js To add the following :
function getBook(fileName) {
return new Promise(async (resolve, reject) => {
const bookSql = `select * from book where fileName='${
fileName}'`
const contentSql = `select * from contents where fileName='${
fileName}' order by \`order\``
const book = await db.queryOne(bookSql)
const contents = await db.querySql(contentSql)
if (book) {
book.cover = Book.genCoverUrl(book)
book.contentsTree = Book.genContentsTree(contents)
} else {
reject(new Error(' E-books don't exist '))
}
return resolve(book)
})
}
Restart the server , Refresh the browser , There is something wrong with the cover and contents
stay utils\constant.js add OLD_UPLOAD_URL , Used to be compatible with previous projects cover The address of
const OLD_UPLOAD_URL = env === 'dev'?
'http://127.0.0.1:8089/book/res/img' :
'http://www.book.llmysnow.top/book/res/img'
stay models\Book.js New static transformation in cover Path method :
static genCoverUrl(book) {
const {
cover } = book
if (+book.updateType === 0) {
if (cover) {
if (cover.startsWith('/')) {
return `${
OLD_UPLOAD_URL}${
cover}`
} else {
return `${
OLD_UPLOAD_URL}/${
cover}`
}
} else {
return null
}
} else {
if (cover) {
if (cover.startsWith('/')) {
return `${
UPLOAD_URL}${
cover}`
} else {
return `${
UPLOAD_URL}/${
cover}`
}
} else {
return null
}
}
}
such cover The path is handled , The next step is to deal with content 了 , stay models\Book.js New static transformation in contents Directory method :
static genContentsTree(contents) {
if (contents) {
const contentsTree = []
contents.forEach(c => {
c.children = []
if (c.pid === '') {
contentsTree.push(c)
} else {
const parent = contents.find(_ => _.navId === c.pid)
parent.children.push(c)
}
})
return contentsTree
}
}
After the front end requests the data, it needs to display the upload list , stay setData Add :
setData(data) {
// ....
this.fileList = [{
name: originalName || fileName }]
},
Update ebook
stay src\api\book.js Add the following new content in :
export function updateBook(book) {
return request({
url: '/book/update',
method: 'post',
data: book
})
}
stay src\views\book\components\Detail.vue Of submitForm It is revised as follows :
submitForm() {
const onSuccess = response => {
const {
msg } = response
this.$notify({
title: ' Successful operation ',
message: msg,
type: 'success',
duration: 2000
})
this.loading = false
}
this.loading = true
this.$refs.postForm.validate((valid, fields) => {
if (valid) {
const book = Object.assign({
}, this.postForm)
delete book.status
delete book.contentsTree
if (!this.isEdit) {
createBook(book)
.then(response => {
onSuccess(response)
this.setDefault()
})
.catch(() => {
})
} else {
updateBook(book)
.then(response => {
onSuccess(response)
})
.catch(() => {
})
}
} else {
const message = fields[Object.keys(fields)[0]][0].message
this.$message({
message,
type: 'error',
showClose: true
})
this.loading = false
}
})
},
Next, modify the server , stay router\book.js New interface in :
router.post('/update', (req, res, next) => {
const decode = decoded(req)
if (decode && decode.username) {
req.body.username = decode.username
}
const book = new Book(null, req.body)
bookService
.updateBook(book)
.then(() => {
new Result(' E-book update succeeded ').success(res)
})
.catch(err => next(boom.badImplementation(err)))
})
stay services\book.js New e-book update logic in updateBook Method :
function updateBook(book) {
return new Promise(async (resolve, reject) => {
try {
if (book instanceof Book) {
const result = await getBook(book.fileName)
if (result) {
const model = book.toDb()
if (+result.updateType === 0) {
reject(new Error(' Built in books cannot be edited '))
} else {
await db.update(model, 'book', `where fileName='${
book.fileName}'`)
resolve()
}
}
} else {
reject(new Error(' The added Book object is illegal '))
}
} catch (e) {
reject(new Error(e))
}
})
}
stay db\index.js Add database update in update Method :
function update(zmodel, tableName, where) {
return new Promise((resolve, reject) => {
if (!isObject(model)) reject(' Insert database failed , Insert data non object ')
const entry = []
Object.keys(model).forEach(key => {
if (model.hasOwnProperty(key)) {
entry.push(`\`${
key}\`='${
model[key]}'`)
}
})
if (entry.length > 0) {
let sql = `UPDATE \`${
tableName}\` SET`
sql = `${
sql} ${
entry.join(',')} ${
where}`
DEBUG && console.log(sql)
const conn = connect()
try {
conn.query(sql, (err, result) => {
if (err) return reject(err)
resolve(result)
})
} catch (e) {
reject(e)
} finally {
conn.end()
}
}
})
}

Delete book、img、unzip Contents of Li
Deleting these folders manually is really a bit cumbersome , Create a new directory in the root directory delete.js
- I'm going through each file and delete it in turn ( The advantage is : It can be controlled in the middle , You can specify that a file is not deleted ) Of course, you can also delete the folder directly , Create folder again
const {
UPLOAD_PATH } = require('./utils/constant')
const path = require('path')
const fs = require('fs')
const bookPath = path.resolve(UPLOAD_PATH, 'book')
const imgPath = path.resolve(UPLOAD_PATH, 'img')
const unzipPath = path.resolve(UPLOAD_PATH, 'unzip')
function removeFile(dirPath) {
return new Promise((resolve, reject) => {
fs.readdir(dirPath, (err, files) => {
if (err) return reject(err)
if (!files.length) return reject(`${
dirPath} The folder is empty `)
files.forEach(item => {
const filePath = path.resolve(dirPath, item)
if (fs.statSync(filePath).isFile()) {
fs.unlinkSync(filePath)
} else {
fs.rmdirSync(filePath, {
recursive: true })
}
})
resolve(`${
dirPath} Delete successful `)
})
})
}
Promise.allSettled([removeFile(bookPath), removeFile(imgPath), removeFile(unzipPath)])
.then(res => {
res.forEach(item => {
if (item.value) console.log(item.value)
if (item.reason) console.log(item.reason)
})
})
.catch(err => console.log(err))
边栏推荐
- How much Ma is the driving current of SIM card signal? Is it adjustable?
- Nrf52832 -- official routine ble_ app_ UART adds the LED feature to enable the computer UART and mobile app to control the LED on and off of the development board
- Win10 desktop unlimited refresh
- 基于tensorflow的校园绿植识别
- Laravel8 authentication login
- Chapter 8 - structure
- [untitled]
- Towards End-to-End Lane Detection: an Instance SegmentationApproach
- Heap classical problem
- [Speech] 如何根据不同国家客制化ring back tone
猜你喜欢

Halcon 3D 1 Reading 3D data

数据集成框架SeaTunnel学习笔记

March 4, 2021
![【长时间序列预测】Aotoformer 代码详解之[4]自相关机制](/img/12/27531fc791b3f49306385831309c5e.png)
【长时间序列预测】Aotoformer 代码详解之[4]自相关机制

The application could not be installed: INSTALL_FAILED_TEST_ONLY

Heap classical problem

Individual application for ov type SSL certificate

Rtmp/rtsp/hls public network real available test address

Identification of campus green plants based on tensorflow

从传统网络IO 到 IO多路复用
随机推荐
肝了一個月的 DDD,一文帶你掌握
China's elastic belt market trend report, technical dynamic innovation and market forecast
Research Report on market supply and demand and strategy of China's digital camera lens industry
[untitled]
The relation between virtual function and pure virtual function
The application could not be installed: INSTALL_ FAILED_ TEST_ ONLY
Why can't NAND flash be used as RAM while nor flash can
WiFi band resources
Introduction to Internet Protocol
Reverse linked list
Halcon 3D 1 读取3d数据
Conversion of Halcon 3D depth map to 3D image
Simple introduction to key Wizard
网络加速谁更猛?CDN领域再现新王者
Heap classical problem
XML parameter schema, the same MTK SW version is compatible with two different sets of audio parameters
Market trend report, technical innovation and market forecast of Chinese stump crusher
Liunx Foundation
POI, easyexcel framework use
CCF noi2022 quota allocation scheme