图书管理系统(https://github.com/plusmultiply0/bookmanagesystem)

特意去github找了一个用flask框架的项目,一起来学习它吧

这个系统包括很多功能:用户权限管理模块(管理员和普通用户),注册登录模块(滑块验证码功能),图书有关信息模块(借阅,收藏,详情),留言板模块,用户画像和个性化推荐模块

哇!真的是一个宝藏项目,一定要吃透它

我打算先前端,再后端

1.首先是起始页

有三个按钮,前端代码在my-app/src/beforeLogin.js里面

import { Form, Button, Layout, Menu, theme, Dropdown, Typography, Space, Table, Modal, Input, Card, Row, Col, Switch, Pagination } from "antd";
import { Link, Navigate, useNavigate, Outlet } from "react-router-dom";
import { LaptopOutlined, UserOutlined, BookOutlined, AppstoreOutlined, ExclamationCircleFilled } from '@ant-design/icons';
import React, { useState, useEffect } from 'react';
import axios from 'axios'
import { Detail } from './bookdata'可以看到他引入了一些ant design里面的一些组件
引入了一些React Router的组件和钩子(他用的是react18)
Link:创建导航链接。
Navigate:渲染时进行重定向。
useNavigate:编程式导航。
Outlet:嵌套路由内容的占位符。
引入axios来进行调用接口
Detail 是什么呢?是来自bookdata的一个组件,可以点击跳转过去看具体的代码
const Detail = (props)=>{let detail = props.dataconst [isModalOpen, setIsModalOpen] = useState(false);const showModal = () => {setIsModalOpen(true);};const handleOk = () => {setIsModalOpen(false);};const handleCancel = () => {setIsModalOpen(false);};return(<><Button type="primary" onClick={showModal}>详情</Button><Modal open={isModalOpen} onOk={handleOk} onCancel={handleCancel} okText="确认" cancelText="取消"><Descriptions title="图书详细信息" bordered><Descriptions.Item label="图书名称" span={2}>{detail.name}</Descriptions.Item><Descriptions.Item label="图书作者">{detail.author}</Descriptions.Item><Descriptions.Item label="出版社" span={2}>{detail.publish}</Descriptions.Item><Descriptions.Item label="ISBN">{detail.isbn}</Descriptions.Item><Descriptions.Item label="价格" span={2}>{detail.price}元</Descriptions.Item><Descriptions.Item label="剩余数量">{detail.number}</Descriptions.Item><Descriptions.Item label="内容简介" span={3}>{detail.intro}</Descriptions.Item><Descriptions.Item label="出版日期">{detail.pubdate}</Descriptions.Item><Descriptions.Item label="类别">{detail.type}</Descriptions.Item></Descriptions></Modal></>)
}
这段代码定义了一个名为 Detail 的 React 函数组件,用于展示图书的详细信息。当用户点击按钮时,会弹出一个模态框(Modal)显示图书的详细信息。

2.进入随便逛逛,可以看到下面的页面

在登录前可以预览图书的图文信息


对了,我们该如何找到这个页面对应的代码在哪里呢,看网页的路径:http://127.0.0.1:5000/preview/books
然后去App.js里面找<Route path="/preview" element={<FrameForAll />}><Route path="/preview/books" element={<BookPreview />} /><Route path="/preview/hotRanking" element={<PreviewHotRanking />} /></Route>
然后直接点击跳转,按住ctrl+点击(vscode中)
还是beforeLogin.js,不过是其中的BookPreview 组件const BookPreview = ()=>{const [bookData, setBookData] = useState([])const [savedata, setSaveData] = useState([])const [conponentshowstatus, setConponentShowStatus] = useState(true)const [current, setCurrent] = useState(1);const [pageSize, setPageSize] = useState(10);const {token: { colorBgContainer },} = theme.useToken();const baseUrl = 'http://127.0.0.1:5000/bookdata'useEffect(() => {// console.log('effect')axios.get(baseUrl).then(response => {const data = response.data// console.log(data)setBookData(data)setSaveData(data)})}, [])//只在第一次渲染时运行const handleChange = (e) => {const values = e.target.value// console.log(values)if (values) {// 这里保存一个原始数据,便于反复查找使用const filterdata = savedata.filter(item => {// console.log(item.value)return item.name.includes(values)})// console.log(filterdata)setBookData(filterdata)} else {setBookData(savedata)}}const onSwitchChange = (checked) => {setConponentShowStatus(!checked)}const onPageChange = (page) => {// console.log(page);setCurrent(page);};const handleShowSizeChange = (current, size) => {const newPage = Math.floor(start / size) + 1;// console.log(newPage,size)setPageSize(size);setCurrent(newPage);};// 对数据切片const start = (current - 1) * pageSize;const end = start + pageSize;const currentData = bookData.slice(start, end);return(<>{/* <p>此为预览页面,仅提供基本功能展示。若想体验完整功能,请先注册并登录!</p> */}<Search placeholder="输入书名" onChange={handleChange} enterButton style={{ width: 200, }} /><Switch checkedChildren="图片版" unCheckedChildren="文字版" onChange={onSwitchChange} className="switch" /><br/><br />{/* 图片组件和文字组件 */}{conponentshowstatus ?<><Row gutter={[8, 16]}>{currentData.map((item) => {return (<Col span={6} key={item.id}><Cardhoverablestyle={{width: 300,}}cover={<img alt="example" src={"http://127.0.0.1:5000/images/" + item.isbn + ".jpg"} />}actions={[<FakeBorrowCollect name="借阅"/>,<Detail data={item} />,<FakeBorrowCollect name="收藏" />]}><Meta title={item.name} description={item.author} /></Card></Col>)})}</Row><br /><Pagination className="pagination" current={current} showSizeChanger onShowSizeChange={handleShowSizeChange} onChange={onPageChange} total={bookData.length} /></>:<Table columns={columns} dataSource={bookData} locale={{ emptyText: '暂无数据' }} />}</>)
}

 一点点看,首先我们要搞清楚图书列表是怎么回事

1)首先是展示,页面这么多书的数据是从何而来,看下面的代码数据应该是来自于下面的代码
useEffect(() => {axios.get(baseUrl).then(response => {const data = response.data;setBookData(data);setSaveData(data);});
}, []);
然后设置?  :  实现文字版和图片版的切换(根据 conponentshowstatus 状态决定显示图片版(用 Card 组件展示图书信息)还是文字版(用 Table 组件展示图书信息))2)再说下搜索功能const handleChange = (e) => {const values = e.target.value// console.log(values)if (values) {// 这里保存一个原始数据,便于反复查找使用const filterdata = savedata.filter(item => {// console.log(item.value)return item.name.includes(values)})// console.log(filterdata)setBookData(filterdata)} else {setBookData(savedata)}}
setBookData 更新 bookData 状态变量,bookData 用于存储和显示当前过滤后的图书数据。
setSaveData 更新 savedata 状态变量,savedata 用于存储从服务器获取的原始图书数据,不直接显示,但用于搜索和过滤操作。3)再说下分页
const [current, setCurrent] = useState(1); // 当前页码
const [pageSize, setPageSize] = useState(10); // 每页显示的条目数
const onPageChange = (page) => {setCurrent(page); // 更新当前页码
};const handleShowSizeChange = (current, size) => {setPageSize(size); // 更新每页显示的条目数setCurrent(1); // 更新 pageSize 时重置到第一页
};
4)最后是前面说到的detail功能(详情展示)
点击详情出来一个弹窗
const Detail = (props)=>{let detail = props.dataconst [isModalOpen, setIsModalOpen] = useState(false);const showModal = () => {setIsModalOpen(true);};const handleOk = () => {setIsModalOpen(false);};const handleCancel = () => {setIsModalOpen(false);};return(<><Button type="primary" onClick={showModal}>详情</Button><Modal open={isModalOpen} onOk={handleOk} onCancel={handleCancel} okText="确认" cancelText="取消"><Descriptions title="图书详细信息" bordered><Descriptions.Item label="图书名称" span={2}>{detail.name}</Descriptions.Item><Descriptions.Item label="图书作者">{detail.author}</Descriptions.Item><Descriptions.Item label="出版社" span={2}>{detail.publish}</Descriptions.Item><Descriptions.Item label="ISBN">{detail.isbn}</Descriptions.Item><Descriptions.Item label="价格" span={2}>{detail.price}元</Descriptions.Item><Descriptions.Item label="剩余数量">{detail.number}</Descriptions.Item><Descriptions.Item label="内容简介" span={3}>{detail.intro}</Descriptions.Item><Descriptions.Item label="出版日期">{detail.pubdate}</Descriptions.Item><Descriptions.Item label="类别">{detail.type}</Descriptions.Item></Descriptions></Modal></>)
}
5)整体布局方面(FrameForAll)
使用了 Ant Design 的 Layout 组件来创建一个包含 Header、Sider、Content 和 Footer 的布局。
主页面布局 (Layout):
最外层的 Layout 组件包含整个页面的布局。
头部 (Header):
使用了 Ant Design 的 Header 组件,类名为 header。
包含一个 Title 元素和一个自定义的 LoginRegisterButton 组件。
Title 组件使用 level={3} 和 type="success" 来设置其样式。
侧边栏 (Sider):
宽度设置为 200 像素,背景色来自主题的 colorBgContainer。
包含一个 Menu 组件,设置了 inline 模式,默认选中的键为 ['1'],默认打开的子菜单为 ['sub1']。
主要内容区域 (Layout):
内部 Layout 设置了 padding 和 margin,确保内容区域有适当的间距。
包含 Content 和 Footer 两个子组件。
内容区域 (Content):
设置了 padding 和 margin,最小高度为 280 像素,背景色来自主题的 colorBgContainer,确保内容区域有滚动条(overflow: 'auto')。
Outlet 组件用于显示嵌套路由的内容。
页脚 (Footer):
设置了居中对齐 (textAlign: 'center'),显示版权信息。
非常好

然后是热门排行页面

const PreviewHotRanking = () => {return (<><HotBorrow  /><NewBook  /><HotCollect  /></>)
}
看来是分三个组件
一个个看
1)HotBorrow
简单的调取接口,获取数据,展示
使用了 Ant Design 的 List 组件来显示热门借阅的图书数据
组件结构:
Title 组件显示 "热门借阅" 标题。
List 组件用于显示图书列表。
itemLayout="vertical" 设置列表项为垂直布局。
size="small" 设置列表项大小为小。
dataSource={bookdata} 设置列表的数据源为状态变量 bookdata。
footer 属性用于显示列表底部的额外内容。
renderItem 属性用于自定义列表项的渲染。
2)NewBook  HotCollect 和HotBorrow  也差不多一个道理,换了个布局
NewBook  
网格布局 (Row 和 Col):
使用 Ant Design 的 Row 组件创建水平网格布局。
gutter={[16, 24]} 设置了列之间的水平间距和行之间的垂直间距。
使用 Col 组件来创建每个图书卡片的列。
span={8} 设置每个图书卡片列的宽度占比。
图书卡片 (Card):
使用 Card 组件包裹每个图书。
设置 hoverable 属性,使得鼠标移动到卡片上时有浮动效果。
设置 cover 属性,用于显示图书的封面图片。
actions 属性定义了操作按钮,包括借阅、详情和收藏。
使用 Meta 组件来显示图书的标题和作者。
3)HotCollect  
同HotBorrow

随便逛逛差不多了,但是还有很多没有逛的,比如说借阅,收藏,管理员权限都需要登录才行
我们进入下一部分吧

3.注册功能

registerForm.js
const RegisterForm = () => {const [form] = Form.useForm();const [isAlertShow, setAlertShow] = useState(false)const [isRegister,setRegister] = useState(false)// 注册post函数const ifRegister = async res => {const response = await axios.post(baseUrl, res)// console.log('response.data:',response.data)return response.data}const onFinish = async (values) => {console.log('Received values of form: ', values);try{const res1 = await ifRegister(values)// console.log('res1:', res1)setRegister(true)}catch(exception){// console.log(exception)setAlertShow(true)setTimeout(() => { setAlertShow(false) }, 3000)}};const prefixSelector = (<Form.Item name="prefix" noStyle><Selectstyle={{width: 70,}}><Option value="86">+86</Option><Option value="87">+87</Option></Select></Form.Item>);return (<>{isAlertShow ? <AlertclassName='registeralert'message="Error"description="用户名重复!"type="error"showIconclosable/> : ''}{isRegister?<Resultstatus="success"title="注册成功!"extra={[<Link to="/login"><Button type="primary" key="console">前往登录</Button></Link>,]}/>:<Form{...formItemLayout}form={form}className="registerform"name="register"onFinish={onFinish}initialValues={{prefix: '86',}}scrollToFirstError><Form.Itemname="password"label="密码"rules={[{required: true,message: '请输入你的密码!',},]}hasFeedback><Input.Password /></Form.Item><Form.Itemname="confirm"label="确认密码"dependencies={['password']}hasFeedbackrules={[{required: true,message: '请确认你的密码!',},({ getFieldValue }) => ({validator(_, value) {if (!value || getFieldValue('password') === value) {return Promise.resolve();}return Promise.reject(new Error('两次输入的密码不一致!'));},}),]}><Input.Password /></Form.Item><Form.Itemname="nickname"label="用户名"tooltip="你想让其他人如何称呼你?"rules={[{required: true,message: '请输入你的用户名!',whitespace: true,},]}><Input /></Form.Item><Form.Itemname="phone"label="电话号码"rules={[{required: true,message: '请输入你的电话号码!',},]}><InputaddonBefore={prefixSelector}style={{width: '100%',}}/></Form.Item><Form.Itemname="gender"label="性别"rules={[{required: true,message: '请选择你的性别!',},]}><Select placeholder="选择你的性别"><Option value="男性">男性</Option><Option value="女性">女性</Option><Option value="其他">其他</Option></Select></Form.Item><Form.Itemname="agreement"valuePropName="checked"rules={[{validator: (_, value) =>value ? Promise.resolve() : Promise.reject(new Error('应当同意服务条款!')),},]}{...tailFormItemLayout}><Checkbox>注册即代表同意<a href="">服务条款</a></Checkbox></Form.Item><Form.Item {...tailFormItemLayout}><Button type="primary" htmlType="submit">注册</Button></Form.Item></Form>}</>);
};使用 useState 钩子来管理是否显示警告信息和注册成功的状态。
定义了一个用于执行注册操作的异步函数 ifRegister,并在表单提交时调用。
组件结构:
如果 isAlertShow 为 true,则显示一个错误警告信息。
如果 isRegister 为 true,则显示注册成功的结果页面,否则显示注册表单。
注册表单包括密码、确认密码、用户名、电话号码、性别和服务条款同意复选框等字段。
使用 Ant Design 的 Form 组件包裹表单内容,设置表单的布局、名称和提交处理函数。
在表单中定义了各种输入框、密码框、下拉框和复选框,以及提交按钮。
我想我们得看一下后端代码了
1)首先是一个__init__.py文件
from flask import Flask
from flask_cors import CORS
from flask_sqlalchemy import SQLAlchemy
from flask_jwt_extended import JWTManager
app=Flask('libraryms',template_folder="../templates",static_folder="../static")
app.config.from_pyfile('settings.py')
cors = CORS(app)
db=SQLAlchemy(app)
jwt = JWTManager()
jwt.init_app(app)
from libraryms import views,commands
Flask('libraryms'): 创建一个名为 libraryms 的 Flask 应用实例。
template_folder="../templates": 设置模板文件夹的路径。
static_folder="../static": 设置静态文件夹的路径。
从 settings.py 文件中加载应用程序配置。
启用跨域资源共享(CORS),允许来自不同域的请求访问此 Flask 应用程序。
使用 SQLAlchemy 进行数据库操作并与 Flask 应用集成。
初始化 JWT 身份验证管理器并与 Flask 应用集成。
导入应用程序的视图和命令模块。确保在 libraryms 包中有 views.py 和 commands.py 文件。2)setting.py
import os
from libraryms import app
SQLALCHEMY_DATABASE_URI=os.getenv('DATABASE_URL')
JWT_SECRET_KEY="super-secret"将 SQLALCHEMY_DATABASE_URI 设置为环境变量 os.getenv('DATABASE_URL') 可以帮助保护敏感信息(如数据库连接字符串)不直接暴露在代码中。
3)commands.py
为flask build 填充预置数据做准备
4)models.py
为flask initdb 初始化数据库做准备
5)views.py
接口大集合
这里我们说注册接口@app.route("/register", methods=["POST"])
@cross_origin()
def register():sth = request.json# print(sth)username = sth['nickname']password = sth['password']gender = sth['gender']phone = sth['phone']
#     判断用户名是否重合,普通用户能注册,所以检查普通用户表res1 = normalusr.query.filter(normalusr.username == username).first()if res1:return jsonify({"msg": "用户名重复!"}), 401else:# 普通表写入信息m1 = normalusr(username=username, password=password)db.session.add(m1)db.session.commit()# 信息表填入信息m2 = usrinfo(username=username,tel=phone,sex=gender)db.session.add(m2)db.session.commit()return jsonify({"msg": "注册成功!","ok":"true"}), 200
很简单,一目明了,加了一个判断,巧妙的是他用了models.py里面的
# 普通用户表
class normalusr(db.Model):nid=db.Column(db.Integer,primary_key=True,nullable=False,autoincrement=True)username=db.Column(db.String(30))password=db.Column(db.String(30))
这就大大方便了以后的编写,抽象

4.登录功能

1)先看一下这个滑动验证码,之前我做过正常的那种验证码,就是输入数字和字母那种,用的是canvas<SliderCaptcha初始化背景和拼图图像request={() =>createPuzzle(DemoImage).then(async (res) => {offsetXRef.current = res.x;await waitTime();return {bgUrl: res.bgUrl,puzzleUrl: res.puzzleUrl};})}验证滑动位置onVerify={async (data) => {await waitTime();// console.log(data);if (data.x >= offsetXRef.current - 5 && data.x < offsetXRef.current + 5) {setDuration(data.duration);setVisible(true);await waitTime();setResult(true);offsetXRef.current = 0return Promise.resolve();}return Promise.reject();}}显示滑块bgSize={{width: 250,height: 110}}mode="float"limitErrorCount={3}jigsawContent={visible && (<div className={"successTip"}>{Number((duration / 1000).toFixed(2))}秒内完成,打败了98%用户</div>)}actionRef={actionRef}/>
请求背景和拼图图像
SliderCaptcha 组件的 request 属性定义了获取背景图像和拼图图像的函数。
createPuzzle(DemoImage) 是模拟生成拼图的函数,返回背景图和拼图的位置。
验证滑动位置
onVerify 属性定义了验证滑动位置的函数。
用户滑动完成后,将实际滑动位置 data.x 与预期位置 offsetXRef.current 进行比较。如果在误差范围内(5个像素),则验证通过,否则验证失败。
显示滑块
SliderCaptcha 组件通过 bgUrl 和 puzzleUrl 显示背景图和拼图。
滑块验证结果
如果验证通过,setDuration 和 setVisible 更新滑块验证的状态。
2)然后看看失败次数限制以及账户锁定功能
登录函数 onFinish 中的失败次数处理:
在 onFinish 函数中,如果失败次数超过2 (failAttempt > 1),会显示锁定消息并锁定账户。
锁定时长设置为31秒(const time = 31 * 1000;),并在本地存储中记录锁定时间。
登录失败时,失败次数增加,并显示相应警告消息。locked 状态用于指示账户是否被锁定。
remainingTime 和 lockTime 用于计算和显示剩余锁定时间。
useEffect 中的计时器每秒更新一次锁定状态,如果锁定时间到期,则解锁账户并重置失败次数。

 5.普通用户功能


登录进去发现,好多功能,用户画像和个性化推荐都有,我们一点点来,先看个人中心

个人中心的基本信息页面 self.js
const Self = ()=>{const [info,setInfo] = useState({})// 获取用户信息const self = window.localStorage.getItem('loggedUser')const res = {"username":self}useEffect(() => {// console.log('self info')axios.get(baseUrl,{params:res}).then(response => {const data = response.data// console.log(data)setInfo(data)// console.log('info',data)})}, [])//只在第一次渲染时运行return(<><Descriptions title="用户信息" bordered extra={<EditSelf data={info} handleChange={setInfo}/> }><Descriptions.Item label="用户名" span={3}>{info.username}</Descriptions.Item><Descriptions.Item label="电话号码" span={3}>{info.tel}</Descriptions.Item><Descriptions.Item label="性别" span={3}>{info.sex}</Descriptions.Item><Descriptions.Item label="简介" span={3}>{info.intro}</Descriptions.Item></Descriptions><br/><EditPwd/><br/><br/><IdeaRelease/></>);
}
它这个用户信息在登录后保存到localstorage中,修改密码的时候带过去,然后调用接口直接修改
后端接口有一个判断来确定是否是管理员,然后查询不同的表,这也是登陆时为什么有哪个选项的原因
个人用户 个性化推荐
1)用户画像
这里他还写了一个通用函数
// 通用post函数
const uniPost = async (url, res) => {const response = await axios.post(url, res)// console.log('response.data:',response.data)return response.data
}
好事情
用户画像需要着重于后端代码,让我们看看他是如何实现的'http://127.0.0.1:5000/userprofile'# 用户画像
@app.route('/userprofile', methods=["GET"])
@cross_origin()
def userprofile():sth = request.argsusername = sth['username']tagarray = []bookarray = []# 统计借阅历史res1 = bookBorrowHistory.query.filter(bookBorrowHistory.borrowusr == username).all()for x in res1:bookarray.append(x.name)# 统计收藏信息res2 = bookCollect.query.filter(bookCollect.username == username).all()for x in res2:res3 = bookitem.query.filter(bookitem.isbn == x.isbn).first()bookarray.append(res3.name)# 统计所有标签for x in bookarray:res4 = bookitem.query.filter(bookitem.name == x).first()tagarray.append(res4.type)# print(bookarray)# print(tagarray)# 计算出数量最多的3个标签dict = {}tag = []for x in tagarray:dict[x] = dict.get(x,0)+1# print(dict)if len(dict)>3:while (len(tag)<3):tag.append(max(dict,key=dict.get))dict.pop(max(dict,key=dict.get))else:for key in dict:tag.append(key)# print(tag)return tag
没有我想的那么复杂,没有用到模型,只是通过统计来实现生成用户标签
统计每个标签出现的次数,然后选取出现次数最多的前三个标签作为用户的标签信息。2)个性推荐
收前端传递的包含用户标签信息的请求,根据这些标签信息生成推荐的图书清单,并将其作为 JSON 格式的响应返回给前端
根据类比返回罢了
普通用户心心念的收藏和借阅功能,这里的借阅有些复杂,涉及到还款什么的,先看收藏吧
这里有个问题:图书列表中已收藏和借阅的书籍在跳转页面后再次回到图书列表页面会不再显示已经借阅或者已经收藏(bookdata.js里面的BookList)解决办法:在图书列表中保存收藏和借阅的状态:在获取图书列表时,同时获取每本书籍是否已被当前用户收藏或借阅的状态,并将这些状态存储在 bookData 中。在图书列表加载时检查状态:在 useEffect 中获取图书数据时,额外获取用户的收藏和借阅信息,并更新每本书的状态。在借阅和收藏操作后更新状态:当用户进行借阅或收藏操作时,更新 bookData 中对应书籍的状态。
需要加个状态,一会加一下1.收藏  collect.js
1)搜索功能
搜索功能通过两个主要函数实现:onSearch 和 handleChange。
onSearch:
当用户在搜索框中输入内容并点击搜索按钮时触发。
该函数接收用户输入的值作为参数。
它首先检查用户是否输入了内容,如果输入了内容,则使用 filter 方法过滤 collectdata 数组,只保留包含用户输入内容的项,并将过滤后的结果设置为新的 collectdata。
如果用户没有输入内容,则将 savedata(原始数据)设置回 collectdata,这样就重新
handleChange:
当用户在搜索框中输入内容但不点击搜索按钮时触发(即在输入内容时实时搜索)。
它与 onSearch 相似,不同之处在于它是实时响应输入的变化。
它也首先检查用户是否输入了内容。如果输入了内容,则根据当前选择的搜索项 selectdata 使用 filter 方法过滤 savedata 数组,并将过滤后的结果设置为新的 collectdata。
如果用户没有输入内容,则将 savedata(原始数据)设置回 collectdata。2)导出功能
首先,将表格数据 collectdata 进行清理,以确保导出的数据格式符合预期。在这里,将每一项的属性名称进行映射,并对价格属性进行调整,添加单位。
使用 XLSX.utils.json_to_sheet 方法将清理后的数据转换为 Excel 表格的工作表。
创建一个新的工作簿,并将工作表添加到该工作簿中。
使用 XLSX.writeFile 方法将工作簿写入到名为 "book.xlsx" 的 Excel 文件中。
在导出完成后,使用 Ant Design 的 message 组件显示导出成功的消息。3)取消收藏功能,收藏功能
CancelCollect 组件接收一个 data 属性,其中包含了要取消收藏的图书数据。
组件内部维护了一个 isCollect 状态,用于表示当前是否已收藏。默认为 true。
当用户点击按钮时,触发 info 函数,该函数首先获取当前用户信息和图书的 ISBN 号,然后通过 uniPost 函数向后端发送请求,告知服务器取消收藏。
如果取消成功,显示提示消息,并更新 isCollect 状态为 false,同时刷新页面以更新数据。2.借阅功能  borrow.js
1)在 useEffect 钩子中,组件首次渲染时从后端获取借阅图书数据,并将其保存在 borrowdata 状态中。然后,根据 ischecking 属性筛选出待审核的借阅申请数据,保存在 applyborrowdata 状态中,以便显示在归还图书申请的表格中。
表格使用了 borrowColumns 和 applyBorrowColumns,这些列的配置与 collectColumns 类似,用于指定表格中各列的标题和渲染方式。
这段代码中的主要逻辑是:
通过 axios 发送请求获取借阅图书数据。
将数据保存到相应的状态变量中。
渲染两个表格,分别用于展示借阅图书和归还图书申请。
2)图书违约
获取数据后,计算每本书是否超期及超期天数和罚金:
当前时间戳通过 new Date().getTime() 获取。
遍历数据,判断是否已还书。如果已还书,根据归还日期计算借阅天数;否则,使用当前时间计算借阅天数。
判断借阅天数是否超过 31 天,计算超期天数和罚金。
过滤出所有超期的记录。
后端接口 http://127.0.0.1:5000/defaultdata
使用 Flask 框架定义了一个 API 路由 /defaultdata,接受 GET 请求。
从数据库中查询用户的违约记录,并返回一个包含违约记录的列表。

6.留言板功能(这里我单独写,是因为感觉不错,有一个评论下回复的功能,需要记一下以及上传图片)但是他只是实现了一个父评论,n个子评论,并没有无限延伸,比如孙评论,一会加一下

messageBoard.js
1)评论
前端代码:
MessageBoard 组件:主留言板组件,包含输入框、表情组件、图片上传组件,以及留言和评论显示。
Comment 组件:递归渲染评论和子评论,并处理回复逻辑。
handleReplySubmit 方法:处理子评论的提交,将新回复添加到对应的父评论中,并发送到服务器。
handleClick 方法:处理新的父评论的提交,并发送到服务器。
后端代码:
get_comments 方法:返回所有评论。
add_comment 方法:根据评论类型(父评论或子评论)将新的评论或回复添加到相应的位置。
2)上传图片前端实现:
文件上传组件:
使用 Ant Design 的 Upload 组件,用户可以通过点击按钮选择要上传的图片文件。
设置 action 属性为上传图片的后端接口地址。
通过 onChange 事件监听文件上传状态的变化,并根据上传结果进行相应的处理。
上传图片处理:用户选择图片后,触发 handleChange 函数。
在 handleChange 函数中,根据文件上传状态(done、uploading、error),对上传的图片进行相应的处理。
如果文件上传成功(status 为 done),从响应中获取上传成功的图片路径,并将图片路径插入到留言内容中,以显示图片。
删除图片处理:用户可以点击已上传图片的删除按钮,触发 handleRemovePics 函数。
在 handleRemovePics 函数中,向后端发送删除图片的请求,根据后端的响应决定是否删除前端对应的图片。
后端实现:
接收上传图片请求:后端提供一个接口来接收前端上传图片的请求,一般是通过 POST 方法发送图片数据。
在 Flask 中,可以使用 Flask-Uploads 或直接处理 request.files 来接收上传的图片文件。
保存图片:后端接收到图片文件后,根据需求将图片保存到服务器的指定目录中。
保存成功后,返回图片的访问路径给前端,以便前端显示上传成功的图片。
删除图片:后端提供一个接口用于删除指定的图片文件,一般是通过 DELETE 方法发送图片文件名或路径。
在删除图片接口中,根据文件名或路径定位到要删除的图片文件,并删除它。

7.管理员功能(信息审核与读者管理)

selfadmin.js
1.读者管理组件
读者信息展示:
通过请求 /usrdata 接口获取读者信息数据,并将其显示在表格中。
如果没有读者信息,则表格显示暂无数据。
性别统计分析图:
通过请求 /usrsexdata 接口获取读者性别数据,并将其用饼图进行可视化展示。
饼图显示了不同性别的读者比例。
图书收藏信息展示及分析图:
通过请求 /usrcollectdata 接口获取读者收藏信息数据,并将其显示在表格中。
通过请求 /usrcollectanalysisdata 接口获取图书收藏排行数据,并将其用柱状图进行可视化展示。
柱状图显示了图书收藏量排名前十的图书信息。
图书借阅信息展示及分析图:
通过请求 /usrborrowdata 接口获取读者借阅信息数据,并将其显示在表格中。
通过请求 /usrborrowanalysisdata 接口获取图书借阅排行数据,并将其用柱状图进行可视化展示。
柱状图显示了图书借阅量排名前十的图书信息。
前端大同小异,图片生成可以关注一下
他这里是用接口返回的数据和import { Pie, Column } from '@ant-design/plots';的组件来生成饼状图和条状图图片
2.信息审核
bookdata、newbookdata、applyborrowdata:分别用于存储所有书籍数据、新书数据和待审核的借阅申请数据。
axios.get('http://127.0.0.1:5000/bookdata') 获取所有书籍数据。
axios.get('http://127.0.0.1:5000/newbookdata') 获取新书数据。
axios.get('http://127.0.0.1:5000/usrborrowlistdata') 获取用户借阅列表数据,并过滤和计算是否超期。

写不下去了,快吐了,一会改一下上面提出的两个bug吧
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/21672.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

毫米级精度3D人脸扫描设备,助推打造元宇宙虚拟分身

在元宇宙中&#xff0c;虚拟分身对应的是一个三维模型&#xff0c;数字化的过程则是三维重建过程&#xff0c;通过3D人脸扫描可以通过多相机同步采集人脸部&#xff0c;可快速、准确地重建出真人地脸部模型及贴图&#xff0c;通过3D人脸扫描设备可快速重建出高逼真的虚拟分身。…

Linux系统下+jmeter分布式压测

一.配置jdk&#xff08;Linux机都需配置同一个版本&#xff09; 下载Linux系统的jdk&#xff0c;下载地址&#xff1a;https://repo.huaweicloud.com/java/jdk/ 下载后的jdk文件上传到 /opt目录下 进入opt目录&#xff0c;查看jdk文件 cd /opt ll 1.解压文件 tar xzvf jd…

真国色码上赞,科技流量双剑合璧,商家获客新纪元开启

在数字化浪潮汹涌的今天,真国色研发团队依托红玉房网络科技公司的雄厚实力,凭借科技领先的核心竞争力,推出了创新性的商家曝光引流工具——码上赞。这款工具借助微信支付与视频号已有功能,为实体商家提供了一种全新的引流获客方式,实现了科技与商业的完美融合。 科技领先,流量黑…

CSS 空间转换 动画

目录 1. 空间转换1.1 视距 - perspective1.2 空间转换 - 旋转1.3 立体呈现 - transform-style1.4 空间转换 - 缩放 2. 动画 - animation2.1 动画的基本用法2.1 animation 复合属性2.2 animation 拆分属性2.3 多组动画 正文开始 1. 空间转换 空间&#xff1a;是从坐标轴角度定义…

Paddle实现单目标检测

单目标检测 单目标检测&#xff08;Single Object Detection&#xff09;是人工智能领域中的一个重要研究方向&#xff0c;旨在通过计算机视觉技术&#xff0c;识别和定位图像中的特定目标物体。单目标检测可以应用于各种场景&#xff0c;如智能监控、自动驾驶、医疗影像分析等…

短视频矩阵系统搭建开发,ai智能剪辑系统,矩阵发布,一键管理多个账户

前言&#xff1a; 企业短视频矩阵是企业通过搭建多个短视频平台账号&#xff0c;形成一个多元化的内容传播网络。它旨在通过多平台内容的同步传播&#xff0c;实现企业品牌价值的最大化。短视频矩阵包括抖音、快手、视频号、小红书、百家号等热门短视频平台&#xff0c;其核心…

向日葵抓住哪三个要点,帮助企业构建专业技术支持服务体系?

售后技术支持是销售行为的延续&#xff0c;在存量时代企业是否能够提供优质专业的售后技术支持服务显得尤为重要&#xff0c;它直接关系到企业产品在市场中的口碑&#xff0c;进而影响企业的发展命运。 因此&#xff0c;企业势必需要重视技术支持服务体系的搭建&#xff0c;引…

【数据分享】水体分布与五级水系和流域矢量数据+2000-2022年植被指数(NDVI)数据(全国/分省/分市)

1. 数据介绍 数据分为3个层次结构&#xff0c;分别为省、地级市、县。其中&#xff0c;省级水体31个&#xff08;不包含香港、台湾等&#xff09;&#xff0c; 地级市水体366个&#xff0c;县级市水体2847个。每一个文件夹中都包含该省、地级市或者县的水体矢量数据、行政边界…

数学建模 —— 灰色系统(4)

目录 什么是灰色系统&#xff1f; 一、灰色关联分析 1.1 灰色关联分析模型 1.2 灰色关联因素和关联算子集 1.2.1 灰色关联因素 1.2.2 关联算子集 1.3 灰色关联公理与灰色关联度 1.3.1 灰色关联度 1.3.2 灰色关联度计算步骤 1.4 广义关联度 1.4.1 灰色绝对关联…

一文读懂GDPR

GDPR将对人们的网络足迹、使用的APP和服务如何保护或利用这些数据产生重大影响。 下面我们将对有关GDPR人们最关心的问题进行解读。 GDPR是什么&#xff1f; 一般数据保护条例&#xff08;General Data Protection Regulation&#xff09;是一项全面的法律&#xff0c;赋予了…

风电Weibull+随机出力!利用ARMA模型随机生成风速+风速Weibull分布程序代码!

前言 随着能源问题日益突出&#xff0c;风力发电等以可再生能源为基础的发电技术越来越受到关注。建立能够正确反映实际风速特性的风速模型是研究风力发电系统控制策略以及并网运行特性的重要基础叫。由于风速的随机性和波动性&#xff0c;系统中的机械设备和电气设备以及电网…

计算机网络⑩ —— Linux系统如何收发网络包

转载于小林coding&#xff1a;https://www.xiaolincoding.com/network/1_base/how_os_deal_network_package.html 1. OSI七层模型 应用层&#xff0c;负责给应用程序提供统一的接口&#xff1b;表示层&#xff0c;负责把数据转换成兼容另一个系统能识别的格式&#xff1b;会话…

深度剖析云边对接技术:探索开放API接口的价值与意义

在当今数字化时代的浪潮中&#xff0c;云边对接与开放API接口成为了塑造行业生态的重要驱动力。随着云计算、物联网和边缘计算等技术的快速发展&#xff0c;传统产业正在迈向数字化转型的关键时刻。而在这个过程中&#xff0c;云边对接技术以及开放的应用程序接口(API)扮演着举…

Facebook海外三不限 | 如何降低Facebook频繁被封的风险

本文将讨论Facebook账户被封的原因及降低封禁风险的方法&#xff0c;以维护用户的账户安全和社交乐趣。 1. 常见原因&#xff1a;账户被封通常与发布违反社区标准的内容有关&#xff0c;如仇恨言论、暴力内容、欺诈虚假信息、非法活动、骚扰、版权侵权等。此外&#xff0c;未授…

el-date-picker选择开始日期的近半年

<el-date-pickerv-model"form[val.key]":type"val.datePickerType || daterange":clearable"val.clearable && true"range-separator"~"start-placeholder"开始日期"end-placeholder"结束日期"style&q…

玩转Linux进度条

准备工作&#xff1a; 一.关于缓冲区 首先&#xff0c;咱们先来一段有意思的代码&#xff1a; #include<stdio.h> #include<unistd.h> int main() {printf("you can see me");sleep(5);} 你可以在你的本地运行一下&#xff0c;这里我告诉大家运行结果…

【SAP HANA 33】前端参数多选情况下HANA如何使用in来匹配?

场面描述: 在操作界面经常会出现某个文本框需要多选的情况,然后后台需要根据多选的值进行匹配搜索。 一般处理的情况是: 1、在Java后端动态生成SQL 2、不改变动态SQL的情况,直接当做一个正常的参数进行传递 本次方案是第二个,直接当做一个正常的字符串参数进行传递即…

RFID防盗门:守护您的商品资产安全!

在新零售运营管理中&#xff0c;防盗是至关重要的一环。根据美国零售联合会发布的年度零售安全调查&#xff0c;2022年美国零售商损失了创纪录的1121亿美元。其中年度损失最大因素是由外部盗窃导致库存损失和员工内部盗窃造成的。 然而传统零售业商品资产盘点往往依赖人工排查&…

《科技与健康》是什么级别的期刊?是正规期刊吗?能评职称吗?

问题解答 问&#xff1a;《科技与健康》期刊万方网可查吗 答&#xff1a;万方、维普可查 问&#xff1a;《科技与健康》是正规期刊吗&#xff1f; 答&#xff1a;万方维普收录的正规期刊。主管单位&#xff1a;长江出版传媒股份有限公司 主办单位&#xff1a;湖北科学技术…

孩子出生后为什么要做听力筛查?

孩子出生后为什么要做听力筛查&#xff1f; 新生儿听力筛查&#xff0c;就是对所有新生儿在尽早的时间&#xff08;出生48小时后&#xff09;进行系统的听力筛查测试。据相关文献报道&#xff0c;在我国&#xff0c;正常分娩的新生儿听力障碍的发生率约为0.1&#xff5e;0.3%&a…