电子书列表
电子书解析数据展示
EbookUpload组件里
this.emit(‘onSuccess’,response.data)
book/components/detail.vue
handleSuccess(data){
console.log(data);
},
拿到data数据以后
之后写个setData方法
setData(data) {
const {
title,
author,
publisher,
language,
rootFile,
cover,
originalName,
url,
contents,
contentsTree,
fileName,
coverPath,
filePath,
unzipPath,
} = data;
this.postForm = {
title,
author,
publisher,
language,
rootFile,
cover,
url,
originalName,
contents,
fileName,
coverPath,
filePath,
unzipPath,
};
},
重新执行
这里的filename大小写写错了,不过文件名称还是换成originalName更好点
树状目录展开
el-tree解决问题
传入data属性,data属性当中是个数组,数组中包含若干对象,每个对象对应一级的tree 里面包含label ,children
https://element.eleme.io/#/zh-CN/component/tree#tree-shu-xing-kong-jian
电子书目录不太一样,我们为了方便解析,只用了一维的数组,而范例是个嵌套的数组
我们去服务端来进行改造 改造成嵌套的 当然在前端做处理也可以
没有识别出pid时说明是一级目录
const chapterTree=[]
chapters.forEach(c=>{
c.children=[]
//没有识别出pid时说明是一级目录
if(c.pid===''){
chapterTree.push(c)
}
})
const chapterTree = []
chapters.forEach(c => {
// 我们前面已经定义过label属性并赋值了
c.children = []
//没有识别出pid时说明是一级目录
if (c.pid === '') {
chapterTree.push(c)
} else {
// pid不为空,说明有parent 先要找到parent
const parent = chapters.find(_ =>
// 如果一样,就找到了parent
_.navId === c.pid
)
parent.children.push(c)
}
})
console.log(chapterTree);
点击以后如何看章节内容
https://element.eleme.cn/#/zh-CN/component/tree#events
看一下data里都有啥
onContentClick(data){
console.log(data)
},
最重要的就是text参数
onContentClick(data){
// console.log(data)
if(data.text){
window.open(data.text)
}
},
随便点进一个章节
但是还是有些书有问题的 比如说这本,点进去和目录不一样 15-3有优化 这里就不做优化了
电子书表单验证功能开发
点击叉号清空
补充defaultForm
校验功能
看element-ui源码可以看到,会返回两个参数
submitForm(){
this.$refs.postForm.validate((valid,fileds)=>{
console.log(valid,fileds);
// 通过验证
if(valid){
}else{
}
})
},
写个rules对象,它的key是el-form-item里面写的prop属性
可以直接在title里写required 这里用的自定义校验规则(传入function)
const validateRequire=(rule,value,callback)=>{
if(value===''||value.length==0){
callback(new Error('title不能为空'))
}else{
callback()
}
}
可以打印一下rule
改造
callback(new Error(rule.field+'必须填写'))
这里有个技巧,可以做个字段映射
写个对象
callback(new Error(fields[rule.field]+‘必须填写’))
现在想拿到这句话 然后展示在页面上
fields[Object.keys(fields)[0]][0].message
submitForm(){
this.$refs.postForm.validate((valid,fields)=>{
console.log(valid,fields);
if(valid){
}else{
// 这是一个通用的异常处理方法
console.log(fields[Object.keys(fields)[0]][0].message)
const message=fields[Object.keys(fields)[0]][0].message
this.$message({
message,type:'error'
})
}
})
},
给作者 出版社 语言 也加个prop author publisher language
增加映射
然后在rules添加规则
rules: {
title: [{
validator: validateRequire }],
author: [{
validator: validateRequire }],
language: [{
validator: validateRequire }],
publisher: [{
validator: validateRequire }],
},
新增电子书前端逻辑
浅拷贝两种方法:扩展运算符/object.assign
请求接口
src/api/book.js
Detail.vue引入
接口开发
utils/index.js 用到了这里的decode
book.js
router.post('/create', (req, res, next) => {
const decoded=decode(req)
// 解出jwt
console.log(decoded);
})
写一个book对象,这个book对象生成的逻辑可以从model/Book.js里写
数据库数据
打印一下data 一定要注意代码规范啊,该驼峰就驼峰 有几个没用驼峰后悔了
createBookFromData(data) {
// data里的字段需要与数据库字段做映射
// console.log(69,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.filePath
this.filePath=data.filePath
this.unzipPath=data.unzipPath
this.coverPath=data.coverPath
this.createUser=data.username
this.createDt=new Date().getTime()
this.updateDt=new Date().getTime()
//为0 表示是默认图书,否则代表来自于互联网(1)
this.updateType=data.updateType===0?data.updateType:1
this.category=data.category||99
this.categoryText=data.categoryText||'自定义'
}
book对象就生成了 这样就可以通过对象生成sql语句了
新增电子书核心逻辑思路
新建service/book.js 有关数据库的操作
const Book=require('../model/Book')
function insertBook(book){
return new Promise((resolve,reject)=>{
try {
// book一定要是Book对象的实例 可以避免某些参数不存在的情况
// 如果是实例的话有个好处 能够保证那些参数都是完备的,
// 不然的话随便传入一个book对象做insert的话可能会产生错误
if(book instanceof Book) {
}else{
reject(new Error('添加的图书对象不合法'))
}
} catch (error) {
reject(error)
}
})
}
module.exports={
insertBook}
router/book.js
router.post('/create', (req, res, next) => {
const decoded = decode(req)
// 解出jwt
// console.log(decoded);
// 前端传入的信息
console.log(req.body);
if (decoded && decoded.username) {
// book.username=decoded.username
req.body.username = decoded.username
}
// const book = new Book(null, req.body)
const book={
}
bookService.insertBook(book).then((res)=>{
console.log(res);
}).catch((err)=>{
// console.log(err);
// 错误与前端联动
next(boom.badImplementation(err))
})
console.log(book)
})
这就是大致框架了
判断电子书是否存在,如果已经存在,就移除掉 需要把数据库里的信息移除掉,还要把文件移除掉
insert以后还要把目录插入到目录表里
操作数据库会存在大量的异步操作,用到async await 变成同步方法 减少回调的次数,如果用promise的话会存在大量嵌套
逻辑就是这样,接下来写具体的方法
数据库操作
insert方法判断第一个参数传入的是否为一个对象
判断是否为对象的方法:utils/index.js
function isObject(o){
return Object.prototype.toString.call(o)==='[object Object]'
}
用这个做判断非常精确
function insert(model,tableName){
return new Promise((resolve,reject)=>{
if(!isObject(model)){
reject(new Error('插入数据库失败,插入数据非对象'))
}
})
}
来实验一下
回到insert方法
数据库操作有个技巧
keys.push(`\`${
key}\``)
为什么呢 比如说select from from book 其中第一个from并不是关键字而是个key,但是数据库会自动识别出他是个关键字就会报错,加了反引号之后就没有这个问题了
// 拼sql语句
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})`
console.log(sql)
}
接着尝试执行sql语句能否成功
// 拼sql语句
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})`
console.log(sql)
const conn=connect()
try {
conn.query(sql,(err,result)=>{
if(err){
reject(err)
}else{
resolve(result)
}
})
} catch (error) {
reject(error)
}finally{
conn.end()
}
}else{
reject(new Error('对象中没有任何属性'))
}
提示数据库没有path这个字段
这里推荐个做法
Book.js新增个方法toDb 对book对象进行过滤而不是整体都拿去使用
把path去掉
toDb() {
return {
fileName: this.fileName,
cover: this.cover,
title: this.title,
author: this.author,
publisher: this.publisher,
bookId: this.bookId,
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
}
}
这个book就要改成book.toDd() 把toDb返回的结果传给insertBook 这样会报错,因为insertBook里面会判断传入的对象是不是Book的实例
写在这里是合适的
这样的话点击添加就可以发现数据库多了条数据
前端交互优化
insertBook(book).then((res)=>{}) 这里不能写res因为success方法会自动传入这里的res 但是实际上应该传入上面红箭头所指的res
前端:
拿到后端的返回值,显示到前端页面
这次不用$message了
https://element.eleme.cn/#/zh-CN/component/notification#notification-tong-zhi
用一个跟他很像的 $notify
还有个细节,上传成功之后可以把列表数据都移除掉 remove方法里用过
this.postForm = Object.assign({
}, defaultForm);
列表是移除了 但是还是有bug
这里写个setDefault方法,来解决这些问题
把这个置空书名就没了
setDefault(){
this.postForm = Object.assign({
}, defaultForm);
this.fileList=[]
},
接下来要把校验结果消除
setDefault(){
// this.postForm = Object.assign({}, defaultForm);
this.fileList=[]
this.$refs.postForm.resetFields()
},
这是因为没有传入prop,如果没有传入prop,他就不认为是一个表单的选项
目录还没有消除
setDefault() {
// this.postForm = Object.assign({}, defaultForm);
this.contentsTree = []; // 消除目录
this.fileList = []; // 标题
this.$refs.postForm.resetFields();
},
移除也是调用这个方法
添加目录到数据库功能
接下来写insertContents方法
需要取到book下的contents
但是前面把contents删掉了 所以传进来没有目录
前端:
后端接收:
可以编写一个getContents
确保能拿到contents之后就可以做插入数据库操作了
对比一下还是有很多冗余字段
可以建一个对象一次赋值,这里有个技巧—通过lodash实现
通过lodash可以使用它的方法来实现我们想要的功能
调用insert方法
await db.insert(_content,‘contents’)
电子书删除功能
下面两个逻辑都写完了 接下来写移除的逻辑
首先把exists函数写了 功能是判断电子书是否存在
为了方便测试 把前端的this.setDefault();注释掉
这种情况就需要把当前上传的电子书给移除
function removeBook(book) {
if(book){
// 删除相关文件(电子书文件,封面,解压后的文件)
book.reset()
}
}
进入Book.js
// 判断路径是否存在静态方法
static pathExists(path){
if(path.startsWith(UPLOAD_PATH)){
return fs.existsSync(path)
}else{
return fs.existsSync(Book.genPath(path))
}
}
来模拟一下
单个文件删除:unlinkSync
解压路径删除记得加上recursive:true fs.rmdirSync(Book.genPath(this.unzipPath),{recursive:true})
这样就只保留一个文件了
老师这里又加了个删除数据库的操作,不知道为啥要加这一步,应该没有写入数据库才对,所以我觉得不用加这个删除数据库的逻辑了
电子书查询api
实现编辑功能首先要拿到fileName,根据filename到数据库查询 查询目录和内容
查到之后返给前端 所以需要在路由中接收个参数
之后可以在Detail.vue里拿到动态路由参数
思路
现在要实现的就是getBook的api
src/api/book.js
注意get方法使用的是params,如果是post则使用data
服务端:
router.book.js
大致框架
getBook方法在service/book.js里写
完善getBook逻辑
需要到book表和contents表进行查询
进一步处理让数据展示出来
resolve(book[0])
但是标题,封面,目录都没有
先解决封面的问题
来解决目录的显示问题
现在的contents是个数组的结构,需要将数组转换成一个contentsTree
其实前面我们已经做过这个逻辑了,
const chapterTree = []
chapters.forEach(c => {
// 我们前面已经定义过label属性并赋值了
c.children = []
//没有识别出pid时说明是一级目录
if (c.pid === '') {
chapterTree.push(c)
} else {
// pid不为空,说明有parent 先要找到parent
const parent = chapters.find(_ =>
// 如果一样,就找到了parent
_.navId === c.pid
)
parent.children.push(c)
}
})
这时候目录还是出不来 前端代码改一下
接下来还有个问题没解决 就是文件的信息
给fileList赋值就ok
编辑电子书
接下来写这里的逻辑
前端增加个接口
虽然还没开发出来接口 但是还是可以去network里面看一下 可以发现接口和参数都能正确发出去了
现在服务端增加接口
和之前create的接口很相似(写错了 应该是update)
现在去service/book.js里写updateBook的逻辑
前端
这里的book.fileName应该要加引号的
数据库操作文件写一个update方法