图书订阅管理系统——管理员书籍管理模块
如图为书籍管理模块的需求与功能点
1.书籍管理的整体设计思路总结
(1)整体的页面布局设计思路
——动作:管理员登陆成功先进入主页面,点击书籍管理进行入书籍管理页面,如图4个分块,主要实现前三个。
有了这样的总体设计,首先所有页面的底部都公用,所以让其继承于主页面,那么势必要以共有部分作为父布局组件,其余通过路由跳转、又或者直接组件嵌套;公用底部,因此有了一个AdminLayOut,另外在里面设置背景颜色等基础;底部以上的头和体,则继续分,展示首页、三个底部按钮跳转的页面,他们公用左上角的icon,单独提出来,但是他们的头不一样,因此从这里形成了两组AdminHome和ManageBookLayout,AdminHome用于管理员第一次登陆的展示;ManageBookLayout用于数据管理页总组件,关于书籍管理,主要3个要实现的,他们的头、体都不一样,这里书籍管理页面以及三个功能页面也可以和AdminHome同级,这里之所以单独成ManageBookLayout,方便路由层级管理和理解,不然放一块太乱了。路由层级、文件结构如下图(右2副图)。
(2)增加书籍
——动作:管理员点击增加书籍,进入增加书籍页面,输入书籍的6个信息,点击提交
——实现思路:管理员点击提交后,数据会通过发送请求,服务器通过监听端口,以及请求中的路由匹配机制,解析前端提交给服务器的数据,保存到数据库中,并返回数据保存的结果。
——前端代码逻辑:点击按钮后,页面拿到一份管理员输入的数据,通过调用二次封装的ajax,发送POST请求,主要代码如下:
const onFinish = async (values) => { console.log('Received values of form: ', values, fileList); try { const response = await reqAddBook( { author: values.author, bookName: values.bookName, bookInfo: values.bookInfo, publisher: values.publisher, bookNum: values.bookNum, bookImg: fileList[0].thumbUrl, }); console.log("请求成功", response); if (response.data.status === 1) { navigate('/admin/manageBooks/addBook', { replace: false }); } else if (response.data.status === 0) { // 如果当前用户已经存在就提示一下 alert(response.data.msg) } } catch (error) { console.log("请求失败", error); } }reqAddBook=data => ajax(base.baseurl+base.addBook,data,'POST');//添加图书的接口,发送POST请求// 服务器注册一个路由_管理员增加书籍 router.post("/admin/manageBooks/addbook",(req,res)=>{ addBookServer(req.body,res); })——后台逻辑:服务器接收到数据后,调用addBookServer函数对数据进行进一步处理,存如数据库。但是需要注意,考虑到用户可能同一本书多次添加,本系统没有在前端去加进一步的防误触判定,询问用户是否确定保存,之在服务器端做了判定:如果是同一本书,数量改为相加后的结果。
const BookModel = require("../src/db/book"); const addBookServer = (data, res) => { BookModel.find({ bookName: data.bookName, author: data.author, publisher: data.publisher },(err, docs) => { if (docs.length > 0) { // 修改数据库的bookNum属性+data.bookNum就行 let bookNum = parseInt(docs[0].bookNum)+parseInt(data.bookNum); console.log(bookNum) BookModel.updateOne({ bookName: data.bookName, author: data.author, publisher: data.publisher },{$set:{bookNum:bookNum}},(err)=>{ if(!err) console.log('修改成功') console.log(err); }) res.send({ msg: "数据保存成功", status: 1 }) } else { let model = new BookModel(data) model.save((err) => { if (!err) { console.log("数据保存成功"); res.send({ msg: "数据保存成功", status: 1 }) } else { console.log("数据保存失败") res.send({ msg: `该书已存在${docs.length}本`, status: 0 }) } }) } }) } module.exports = addBookServer;基本的设计过程,3个功能点的设计都类似,代码我不一一去放了,只说一下思路和设计页面,方便理解。
(3)删除书籍
——动作:管理员点击删除书籍,进入删除书籍页面,输入书籍作者和书名,点击删除按钮,每次删除都把数据库的最新的5条消息返回展示在前端
——实现思路:管理员点击提交后,数据会通过发送请求,服务器通过监听端口,以及请求中的路由匹配机制,解析前端提交给服务器的数据,进行数据库数据的更新,并返回数据删除后的结果和删除日志记录。
——前端代码逻辑:点击按钮后,页面拿到一份管理员输入的数据,通过调用二次封装的ajax,发送POST请求;接收到后端返回的日志,保存到本地状态以及localStorge,前者保证”热更新“,后者保证当前页面无操作刷新或者别的页面跳转到当前页面时,页面的日志数据不会消失。在渲染的时候注意写法,先判断本地state,在判断localStorge,这个我还另写了一篇随笔简单介绍。
——后端代码逻辑:服务器拿到数据后,先进行删除,如果删除结果中,删除数量>0,说明成功执行了删除,非无效删除(本没有这个书时进行的删除),保存删除记录,成功后返回最新的5条删除记录。
(4)更改书籍
——动作:管理员点击更改书籍,进入更改书籍页面,输入书籍作者和书名,点击检索按钮,如果能检索到,结果会作为输入框的默认值展示,否则会提示,没有这本书
——实现思路:管理员点击提交后,数据会通过发送请求,服务器通过监听端口,以及请求中的路由匹配机制,解析前端提交给服务器的数据。在这个功能模块里进行了两次请求发送,第一次检索,第二次数据更新,每次都返回数据更新结果。
——前端代码逻辑:点击按钮后,页面拿到一份管理员输入的数据,通过调用二次封装的ajax,发送POST请求;接收到后端返回的书籍信息,保存到本地state,作为输入框默认输入,管理员根据提示进一步填写数据并提交更新数据,在接受后台更新状态结果
——后端代码逻辑:服务器第一次拿到数据后,先进行检索,返回检索结果;第二次接收到服务器数据,进行数据更新。
(5)补充主页展示
主页:搜索框、推荐书籍展示、轮播图设置三个功能。
轮播图直接采用的antd4的样式;整体的设计逻辑是,点击设置---》出现图2,搜索框搜索书籍,数据展示在上方===》选择想要检索的书籍===》在下方选择想要放在轮播图中的书籍===》点击提交,回到图1。
===》
搜索框不只是为了轮播图服务,它的意义还是用来检索,在用户只知道书名或者作者的时候(目前的设计就是只输入一种,要么作者,要么书名,且暂时不支持模糊搜索,也很好修改,服务器端进行字符串匹配进行),轮播图这块出的问题最多,关于消息订阅,同一个uid同一个页面内才有效,页面一旦刷新,useEffert副作用函数返回值消除副作用时会导致消息订阅不到;useEffert的监听机制也是没真的搞清,之前单独写过一个组件是否能在订阅的同时发布?是否可以同时订阅多个?但是没有仔细研究第二参数不同情况下的监听效果,后面做完管理员的所有任务,就把这个具体做一下,更在它后面。