圖書管理系統(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.pswp.cn/bicheng/21672.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/21672.shtml
英文地址,請注明出處:http://en.pswp.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;其核心…

LeetCode 每日一題 數學篇 2520.統計能整除數字的位數

給你一個整數 num &#xff0c;返回 num 中能整除 num 的數位的數目。 如果滿足 nums % val 0 &#xff0c;則認為整數 val 可以整除 nums 。 int countDigits(int num) {int t num, res 0;while (t) {if (num % (t % 10) 0) {res 1;}t / 10;}return res; }解題思路&…

AT_abc348_c [ABC348C] Colorful Beans 題解

題目傳送門 解題思路 對于每種顏色的豆子&#xff0c;我們先找到美味度最小的那個&#xff0c;最后找出這些不同種類的豆子中美味度最大的即可。 那我們怎么找到第 i i i 種豆子中美味度最小的那個呢&#xff1f;這里給出兩種思路&#xff1a; 使用桶的思想標記。對于每一…

向日葵抓住哪三個要點,幫助企業構建專業技術支持服務體系?

售后技術支持是銷售行為的延續&#xff0c;在存量時代企業是否能夠提供優質專業的售后技術支持服務顯得尤為重要&#xff0c;它直接關系到企業產品在市場中的口碑&#xff0c;進而影響企業的發展命運。 因此&#xff0c;企業勢必需要重視技術支持服務體系的搭建&#xff0c;引…

JavaScript 事件循環竟還能這樣玩!

JavaScript 是一種單線程的編程語言&#xff0c;這意味著它一次只能執行一個任務。為了能夠處理異步操作&#xff0c;JavaScript 使用了一種稱為事件循環&#xff08;Event Loop&#xff09;的機制。 本文將深入探討事件循環的工作原理&#xff0c;并展示如何基于這一原理實現一…

git-commit-id-plugin maven插件筆記(git commitId跟蹤工具)

文章目錄 maven依賴git.properties 例子 代碼版本管理比較混亂&#xff0c;如何記錄呢? 一種是手動記錄&#xff0c;也可以實現&#xff0c;顯得有點笨。 也可以通過插件。 maven依賴 <plugin><groupId>pl.project13.maven</groupId><artifactId>git…

面試題系列:Python是什么?使用Python有什么好處?你對 PEP 8 有什么理解?

###面試題系列:Python是什么?使用Python有什么好處?你對 PEP 8 有什么理解? 1、Python是什么? Python是一門動態的(dynamic)且強類型(strong)語言 延伸: 1)靜態類型語言和動態類型語言的判別的標準 如果類型檢查發生在編譯階段(compile time),那么是靜態類型語言(s…

【數據分享】水體分布與五級水系和流域矢量數據+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)扮演著舉…

處理STM32 DMA方式下的HAL_UART_ERROR_ORE錯誤

1. 檢查并調整DMA和UART配置 確保初始化順序&#xff1a;需要確保USART的CR寄存器UE位開關留到最后打開&#xff0c;即完成USART和DMA的所有配置初始化后再使能USART。這樣可以避免初始化順序不當導致的通信問題。配置合適的DMA緩沖區&#xff1a;確保DMA緩沖區足夠大&#xf…

Facebook海外三不限 | 如何降低Facebook頻繁被封的風險

本文將討論Facebook賬戶被封的原因及降低封禁風險的方法&#xff0c;以維護用戶的賬戶安全和社交樂趣。 1. 常見原因&#xff1a;賬戶被封通常與發布違反社區標準的內容有關&#xff0c;如仇恨言論、暴力內容、欺詐虛假信息、非法活動、騷擾、版權侵權等。此外&#xff0c;未授…